aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/asn1/c_src/asn1_erl_nif.c2
-rw-r--r--lib/asn1/doc/src/asn1ct.xml14
-rw-r--r--lib/asn1/doc/src/notes.xml33
-rw-r--r--lib/asn1/test/asn1_SUITE.erl16
-rw-r--r--lib/asn1/vsn.mk2
-rw-r--r--lib/common_test/doc/src/common_test_app.xml26
-rw-r--r--lib/common_test/doc/src/ct.xml142
-rw-r--r--lib/common_test/doc/src/ct_cover.xml8
-rw-r--r--lib/common_test/doc/src/ct_ftp.xml26
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml36
-rw-r--r--lib/common_test/doc/src/ct_master.xml24
-rw-r--r--lib/common_test/doc/src/ct_netconfc.xml110
-rw-r--r--lib/common_test/doc/src/ct_property_test.xml6
-rw-r--r--lib/common_test/doc/src/ct_rpc.xml18
-rw-r--r--lib/common_test/doc/src/ct_slave.xml12
-rw-r--r--lib/common_test/doc/src/ct_snmp.xml38
-rw-r--r--lib/common_test/doc/src/ct_ssh.xml152
-rw-r--r--lib/common_test/doc/src/ct_telnet.xml34
-rw-r--r--lib/common_test/doc/src/ct_testspec.xml4
-rw-r--r--lib/common_test/doc/src/unix_telnet.xml6
-rw-r--r--lib/common_test/src/ct_config.erl8
-rw-r--r--lib/common_test/src/ct_framework.erl32
-rw-r--r--lib/common_test/src/ct_logs.erl10
-rw-r--r--lib/common_test/src/ct_master.erl4
-rw-r--r--lib/common_test/src/ct_repeat.erl2
-rw-r--r--lib/common_test/src/cth_log_redirect.erl24
-rw-r--r--lib/common_test/src/cth_surefire.erl2
-rw-r--r--lib/common_test/src/test_server_ctrl.erl42
-rw-r--r--lib/common_test/test_server/ts_benchmark.erl2
-rw-r--r--lib/common_test/test_server/ts_erl_config.erl6
-rw-r--r--lib/compiler/doc/src/compile.xml35
-rw-r--r--lib/compiler/doc/src/notes.xml75
-rw-r--r--lib/compiler/src/Makefile3
-rw-r--r--lib/compiler/src/beam_a.erl12
-rw-r--r--lib/compiler/src/beam_asm.erl4
-rw-r--r--lib/compiler/src/beam_bs.erl183
-rw-r--r--lib/compiler/src/beam_dict.erl15
-rw-r--r--lib/compiler/src/beam_jump.erl55
-rw-r--r--lib/compiler/src/beam_peep.erl12
-rw-r--r--lib/compiler/src/beam_ssa_bsm.erl3
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl40
-rw-r--r--lib/compiler/src/beam_ssa_opt.erl853
-rw-r--r--lib/compiler/src/beam_ssa_opt.hrl53
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl225
-rw-r--r--lib/compiler/src/beam_ssa_share.erl370
-rw-r--r--lib/compiler/src/beam_ssa_type.erl525
-rw-r--r--lib/compiler/src/beam_trim.erl8
-rw-r--r--lib/compiler/src/beam_validator.erl450
-rw-r--r--lib/compiler/src/beam_z.erl29
-rw-r--r--lib/compiler/src/compile.erl11
-rw-r--r--lib/compiler/src/compiler.app.src2
-rw-r--r--lib/compiler/src/erl_bifs.erl1
-rw-r--r--lib/compiler/src/sys_core_fold.erl20
-rw-r--r--lib/compiler/src/sys_core_inline.erl3
-rw-r--r--lib/compiler/test/Makefile14
-rw-r--r--lib/compiler/test/apply_SUITE.erl8
-rw-r--r--lib/compiler/test/beam_jump_SUITE.erl44
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl32
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl8
-rw-r--r--lib/compiler/test/beam_utils_SUITE.erl6
-rw-r--r--lib/compiler/test/beam_validator_SUITE.erl44
-rw-r--r--lib/compiler/test/bs_construct_SUITE.erl5
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl40
-rw-r--r--lib/compiler/test/compile_SUITE.erl5
-rw-r--r--lib/compiler/test/core_fold_SUITE.erl2
-rw-r--r--lib/compiler/test/float_SUITE.erl15
-rw-r--r--lib/compiler/test/guard_SUITE.erl52
-rw-r--r--lib/compiler/test/map_SUITE.erl95
-rw-r--r--lib/compiler/test/match_SUITE.erl156
-rw-r--r--lib/compiler/test/misc_SUITE.erl8
-rw-r--r--lib/compiler/test/receive_SUITE.erl27
-rw-r--r--lib/compiler/test/regressions_SUITE.erl2
-rw-r--r--lib/compiler/test/test_lib.erl8
-rw-r--r--lib/compiler/test/trycatch_SUITE.erl26
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/Makefile.in63
-rw-r--r--lib/crypto/c_src/aead.c233
-rw-r--r--lib/crypto/c_src/aead.h29
-rw-r--r--lib/crypto/c_src/aes.c282
-rw-r--r--lib/crypto/c_src/aes.h36
-rw-r--r--lib/crypto/c_src/algorithms.c320
-rw-r--r--lib/crypto/c_src/algorithms.h30
-rw-r--r--lib/crypto/c_src/atoms.c271
-rw-r--r--lib/crypto/c_src/atoms.h147
-rw-r--r--lib/crypto/c_src/block.c105
-rw-r--r--lib/crypto/c_src/block.h28
-rw-r--r--lib/crypto/c_src/bn.c121
-rw-r--r--lib/crypto/c_src/bn.h36
-rw-r--r--lib/crypto/c_src/chacha20.c83
-rw-r--r--lib/crypto/c_src/chacha20.h29
-rw-r--r--lib/crypto/c_src/cipher.c117
-rw-r--r--lib/crypto/c_src/cipher.h50
-rw-r--r--lib/crypto/c_src/cmac.c70
-rw-r--r--lib/crypto/c_src/cmac.h28
-rw-r--r--lib/crypto/c_src/common.h36
-rw-r--r--lib/crypto/c_src/crypto.c5907
-rw-r--r--lib/crypto/c_src/dh.c204
-rw-r--r--lib/crypto/c_src/dh.h29
-rw-r--r--lib/crypto/c_src/digest.c111
-rw-r--r--lib/crypto/c_src/digest.h40
-rw-r--r--lib/crypto/c_src/dss.c85
-rw-r--r--lib/crypto/c_src/dss.h29
-rw-r--r--lib/crypto/c_src/ec.c360
-rw-r--r--lib/crypto/c_src/ec.h35
-rw-r--r--lib/crypto/c_src/ecdh.c80
-rw-r--r--lib/crypto/c_src/ecdh.h28
-rw-r--r--lib/crypto/c_src/eddsa.c51
-rw-r--r--lib/crypto/c_src/eddsa.h30
-rw-r--r--lib/crypto/c_src/engine.c720
-rw-r--r--lib/crypto/c_src/engine.h49
-rw-r--r--lib/crypto/c_src/evp.c124
-rw-r--r--lib/crypto/c_src/evp.h29
-rw-r--r--lib/crypto/c_src/evp_compat.h196
-rw-r--r--lib/crypto/c_src/fips.c52
-rw-r--r--lib/crypto/c_src/fips.h29
-rw-r--r--lib/crypto/c_src/hash.c405
-rw-r--r--lib/crypto/c_src/hash.h33
-rw-r--r--lib/crypto/c_src/hmac.c185
-rw-r--r--lib/crypto/c_src/hmac.h33
-rw-r--r--lib/crypto/c_src/info.c81
-rw-r--r--lib/crypto/c_src/info.h35
-rw-r--r--lib/crypto/c_src/math.c43
-rw-r--r--lib/crypto/c_src/math.h28
-rw-r--r--lib/crypto/c_src/openssl_config.h337
-rw-r--r--lib/crypto/c_src/otp_test_engine.c178
-rw-r--r--lib/crypto/c_src/pkey.c1170
-rw-r--r--lib/crypto/c_src/pkey.h31
-rw-r--r--lib/crypto/c_src/poly1305.c78
-rw-r--r--lib/crypto/c_src/poly1305.h28
-rw-r--r--lib/crypto/c_src/rand.c99
-rw-r--r--lib/crypto/c_src/rand.h31
-rw-r--r--lib/crypto/c_src/rc4.c66
-rw-r--r--lib/crypto/c_src/rc4.h29
-rw-r--r--lib/crypto/c_src/rsa.c195
-rw-r--r--lib/crypto/c_src/rsa.h31
-rw-r--r--lib/crypto/c_src/srp.c229
-rw-r--r--lib/crypto/c_src/srp.h30
-rw-r--r--lib/crypto/doc/src/crypto.xml150
-rw-r--r--lib/crypto/doc/src/notes.xml37
-rw-r--r--lib/crypto/src/crypto.erl6
-rw-r--r--lib/crypto/test/Makefile3
-rw-r--r--lib/crypto/test/crypto.spec5
-rw-r--r--lib/crypto/test/crypto_SUITE.erl81
-rw-r--r--lib/crypto/test/crypto_bench.spec3
-rw-r--r--lib/crypto/test/crypto_bench_SUITE.erl387
-rw-r--r--lib/crypto/test/engine_SUITE.erl132
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/doc/src/debugger.xml12
-rw-r--r--lib/debugger/doc/src/i.xml64
-rw-r--r--lib/debugger/doc/src/int.xml64
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml12
-rw-r--r--lib/diameter/doc/src/diameter.xml28
-rw-r--r--lib/diameter/doc/src/diameter_app.xml18
-rw-r--r--lib/diameter/doc/src/diameter_codec.xml6
-rw-r--r--lib/diameter/doc/src/diameter_make.xml10
-rw-r--r--lib/diameter/doc/src/diameter_sctp.xml4
-rw-r--r--lib/diameter/doc/src/diameter_tcp.xml4
-rw-r--r--lib/diameter/doc/src/diameter_transport.xml4
-rw-r--r--lib/eldap/doc/src/eldap.xml68
-rw-r--r--lib/erl_docgen/doc/src/docgen_xml_check.xml4
-rw-r--r--lib/erl_docgen/priv/css/otp_doc.css33
-rw-r--r--lib/erl_docgen/priv/dtd/cref.dtd2
-rw-r--r--lib/erl_docgen/priv/dtd/erlref.dtd2
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl125
-rw-r--r--lib/erl_interface/configure.in20
-rw-r--r--lib/erl_interface/doc/src/ei.xml176
-rw-r--r--lib/erl_interface/doc/src/ei_connect.xml414
-rw-r--r--lib/erl_interface/doc/src/erl_connect.xml50
-rw-r--r--lib/erl_interface/doc/src/erl_error.xml10
-rw-r--r--lib/erl_interface/doc/src/erl_eterm.xml58
-rw-r--r--lib/erl_interface/doc/src/erl_format.xml4
-rw-r--r--lib/erl_interface/doc/src/erl_global.xml8
-rw-r--r--lib/erl_interface/doc/src/erl_malloc.xml16
-rw-r--r--lib/erl_interface/doc/src/erl_marshal.xml18
-rw-r--r--lib/erl_interface/doc/src/registry.xml40
-rw-r--r--lib/erl_interface/include/ei.h43
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c1086
-rw-r--r--lib/erl_interface/src/connect/eirecv.c62
-rw-r--r--lib/erl_interface/src/connect/send.c74
-rw-r--r--lib/erl_interface/src/connect/send_exit.c25
-rw-r--r--lib/erl_interface/src/connect/send_reg.c64
-rw-r--r--lib/erl_interface/src/epmd/epmd_port.c85
-rw-r--r--lib/erl_interface/src/epmd/epmd_publish.c36
-rw-r--r--lib/erl_interface/src/epmd/epmd_unpublish.c33
-rw-r--r--lib/erl_interface/src/legacy/erl_connect.c9
-rw-r--r--lib/erl_interface/src/misc/ei_internal.h18
-rw-r--r--lib/erl_interface/src/misc/ei_portio.c858
-rw-r--r--lib/erl_interface/src/misc/ei_portio.h95
-rw-r--r--lib/erl_interface/src/not_used/send_link.c3
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE.erl11
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c39
-rw-r--r--lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c86
-rw-r--r--lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c42
-rw-r--r--lib/et/doc/src/et.xml14
-rw-r--r--lib/et/doc/src/et_collector.xml46
-rw-r--r--lib/et/doc/src/et_selector.xml8
-rw-r--r--lib/et/doc/src/et_viewer.xml14
-rw-r--r--lib/et/doc/src/notes.xml17
-rw-r--r--lib/et/src/et_wx_viewer.erl37
-rw-r--r--lib/et/vsn.mk2
-rw-r--r--lib/ftp/doc/src/ftp.xml82
-rw-r--r--lib/hipe/doc/src/notes.xml16
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/inets/doc/src/http_uri.xml12
-rw-r--r--lib/inets/doc/src/httpc.xml50
-rw-r--r--lib/inets/doc/src/httpd.xml26
-rw-r--r--lib/inets/doc/src/httpd_custom_api.xml8
-rw-r--r--lib/inets/doc/src/httpd_socket.xml8
-rw-r--r--lib/inets/doc/src/httpd_util.xml55
-rw-r--r--lib/inets/doc/src/inets.xml20
-rw-r--r--lib/inets/doc/src/mod_alias.xml10
-rw-r--r--lib/inets/doc/src/mod_auth.xml58
-rw-r--r--lib/inets/doc/src/mod_esi.xml8
-rw-r--r--lib/inets/doc/src/mod_security.xml34
-rw-r--r--lib/inets/doc/src/notes.xml26
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl10
-rw-r--r--lib/inets/test/httpd_bench_SUITE.erl11
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/kernel/doc/src/application.xml74
-rw-r--r--lib/kernel/doc/src/auth.xml12
-rw-r--r--lib/kernel/doc/src/code.xml84
-rw-r--r--lib/kernel/doc/src/disk_log.xml74
-rw-r--r--lib/kernel/doc/src/erl_boot_server.xml12
-rw-r--r--lib/kernel/doc/src/erl_ddll.xml32
-rw-r--r--lib/kernel/doc/src/erl_epmd.xml16
-rw-r--r--lib/kernel/doc/src/error_handler.xml8
-rw-r--r--lib/kernel/doc/src/error_logger.xml46
-rw-r--r--lib/kernel/doc/src/file.xml122
-rw-r--r--lib/kernel/doc/src/gen_sctp.xml42
-rw-r--r--lib/kernel/doc/src/gen_tcp.xml24
-rw-r--r--lib/kernel/doc/src/gen_udp.xml16
-rw-r--r--lib/kernel/doc/src/global.xml42
-rw-r--r--lib/kernel/doc/src/global_group.xml22
-rw-r--r--lib/kernel/doc/src/heart.xml18
-rw-r--r--lib/kernel/doc/src/inet.xml101
-rw-r--r--lib/kernel/doc/src/inet_res.xml38
-rw-r--r--lib/kernel/doc/src/logger.xml234
-rw-r--r--lib/kernel/doc/src/logger_chapter.xml60
-rw-r--r--lib/kernel/doc/src/logger_disk_log_h.xml4
-rw-r--r--lib/kernel/doc/src/logger_filters.xml10
-rw-r--r--lib/kernel/doc/src/logger_formatter.xml8
-rw-r--r--lib/kernel/doc/src/logger_std_h.xml4
-rw-r--r--lib/kernel/doc/src/net_adm.xml22
-rw-r--r--lib/kernel/doc/src/net_kernel.xml28
-rw-r--r--lib/kernel/doc/src/notes.xml117
-rw-r--r--lib/kernel/doc/src/os.xml38
-rw-r--r--lib/kernel/doc/src/pg2.xml22
-rw-r--r--lib/kernel/doc/src/rpc.xml56
-rw-r--r--lib/kernel/doc/src/seq_trace.xml20
-rw-r--r--lib/kernel/doc/src/wrap_log_reader.xml12
-rw-r--r--lib/kernel/src/Makefile6
-rw-r--r--lib/kernel/src/kernel.app.src2
-rw-r--r--lib/kernel/src/kernel.appup.src49
-rw-r--r--lib/kernel/src/logger.erl79
-rw-r--r--lib/kernel/src/logger_config.erl12
-rw-r--r--lib/kernel/src/logger_disk_log_h.erl716
-rw-r--r--lib/kernel/src/logger_h_common.erl613
-rw-r--r--lib/kernel/src/logger_h_common.hrl188
-rw-r--r--lib/kernel/src/logger_internal.hrl9
-rw-r--r--lib/kernel/src/logger_olp.erl626
-rw-r--r--lib/kernel/src/logger_olp.hrl180
-rw-r--r--lib/kernel/src/logger_proxy.erl165
-rw-r--r--lib/kernel/src/logger_server.erl34
-rw-r--r--lib/kernel/src/logger_simple_h.erl7
-rw-r--r--lib/kernel/src/logger_std_h.erl786
-rw-r--r--lib/kernel/src/logger_sup.erl4
-rw-r--r--lib/kernel/src/net_kernel.erl3
-rw-r--r--lib/kernel/src/seq_trace.erl2
-rw-r--r--lib/kernel/test/Makefile3
-rw-r--r--lib/kernel/test/gen_tcp_misc_SUITE.erl54
-rw-r--r--lib/kernel/test/init_SUITE.erl23
-rw-r--r--lib/kernel/test/kernel_bench.spec1
-rw-r--r--lib/kernel/test/logger.cover5
-rw-r--r--lib/kernel/test/logger.spec2
-rw-r--r--lib/kernel/test/logger_disk_log_h_SUITE.erl263
-rw-r--r--lib/kernel/test/logger_env_var_SUITE.erl16
-rw-r--r--lib/kernel/test/logger_olp_SUITE.erl90
-rw-r--r--lib/kernel/test/logger_proxy_SUITE.erl274
-rw-r--r--lib/kernel/test/logger_std_h_SUITE.erl203
-rw-r--r--lib/kernel/test/logger_stress_SUITE.erl456
-rw-r--r--lib/kernel/test/logger_test_lib.erl10
-rw-r--r--lib/kernel/test/sendfile_SUITE.erl26
-rw-r--r--lib/kernel/test/seq_trace_SUITE.erl39
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/megaco/doc/src/megaco.xml102
-rw-r--r--lib/megaco/doc/src/megaco_codec_meas.xml6
-rw-r--r--lib/megaco/doc/src/megaco_codec_mstone1.xml26
-rw-r--r--lib/megaco/doc/src/megaco_codec_mstone2.xml6
-rw-r--r--lib/megaco/doc/src/megaco_codec_transform.xml6
-rw-r--r--lib/megaco/doc/src/megaco_edist_compress.xml6
-rw-r--r--lib/megaco/doc/src/megaco_encoder.xml14
-rw-r--r--lib/megaco/doc/src/megaco_flex_scanner.xml12
-rw-r--r--lib/megaco/doc/src/megaco_tcp.xml30
-rw-r--r--lib/megaco/doc/src/megaco_transport.xml8
-rw-r--r--lib/megaco/doc/src/megaco_udp.xml30
-rw-r--r--lib/megaco/doc/src/megaco_user.xml48
-rw-r--r--lib/mnesia/doc/src/mnesia.xml243
-rw-r--r--lib/mnesia/doc/src/mnesia_frag_hash.xml12
-rw-r--r--lib/mnesia/doc/src/mnesia_registry.xml6
-rw-r--r--lib/observer/doc/src/crashdump.xml8
-rw-r--r--lib/observer/doc/src/etop.xml14
-rw-r--r--lib/observer/doc/src/notes.xml37
-rw-r--r--lib/observer/doc/src/observer.xml4
-rw-r--r--lib/observer/doc/src/ttb.xml47
-rw-r--r--lib/observer/src/cdv_html_wx.erl11
-rw-r--r--lib/observer/src/crashdump_viewer.erl78
-rw-r--r--lib/observer/test/crashdump_helper.erl20
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl12
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/c_src/odbcserver.c5
-rw-r--r--lib/odbc/doc/src/odbc.xml52
-rw-r--r--lib/os_mon/doc/src/cpu_sup.xml14
-rw-r--r--lib/os_mon/doc/src/disksup.xml12
-rw-r--r--lib/os_mon/doc/src/memsup.xml24
-rw-r--r--lib/os_mon/doc/src/notes.xml18
-rw-r--r--lib/os_mon/doc/src/nteventlog.xml8
-rw-r--r--lib/os_mon/doc/src/os_sup.xml10
-rw-r--r--lib/os_mon/vsn.mk2
-rw-r--r--lib/parsetools/doc/src/leex.xml19
-rw-r--r--lib/parsetools/doc/src/yecc.xml6
-rw-r--r--lib/public_key/doc/src/notes.xml18
-rw-r--r--lib/public_key/doc/src/public_key.xml108
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/reltool/doc/src/notes.xml20
-rw-r--r--lib/reltool/doc/src/reltool.xml32
-rw-r--r--lib/reltool/src/reltool_utils.erl13
-rw-r--r--lib/reltool/test/reltool_server_SUITE.erl16
-rw-r--r--lib/reltool/vsn.mk2
-rw-r--r--lib/runtime_tools/doc/src/dbg.xml114
-rw-r--r--lib/runtime_tools/doc/src/dyntrace.xml32
-rw-r--r--lib/runtime_tools/doc/src/erts_alloc_config.xml10
-rw-r--r--lib/runtime_tools/doc/src/msacc.xml32
-rw-r--r--lib/runtime_tools/doc/src/scheduler.xml12
-rw-r--r--lib/runtime_tools/doc/src/system_information.xml6
-rw-r--r--lib/runtime_tools/examples/dist.systemtap26
-rw-r--r--lib/runtime_tools/examples/driver1.systemtap44
-rw-r--r--lib/runtime_tools/examples/function-calls.systemtap22
-rw-r--r--lib/runtime_tools/examples/garbage-collection.systemtap16
-rw-r--r--lib/runtime_tools/examples/memory1.systemtap16
-rw-r--r--lib/runtime_tools/examples/messages.systemtap16
-rw-r--r--lib/runtime_tools/examples/port1.systemtap28
-rw-r--r--lib/runtime_tools/examples/process-scheduling.systemtap14
-rw-r--r--lib/runtime_tools/examples/spawn-exit.systemtap16
-rw-r--r--lib/runtime_tools/examples/user-probe-n.systemtap13
-rw-r--r--lib/runtime_tools/examples/user-probe.systemtap12
-rw-r--r--lib/sasl/doc/src/alarm_handler.xml8
-rw-r--r--lib/sasl/doc/src/notes.xml17
-rw-r--r--lib/sasl/doc/src/rb.xml38
-rw-r--r--lib/sasl/doc/src/release_handler.xml42
-rw-r--r--lib/sasl/doc/src/systools.xml16
-rw-r--r--lib/sasl/src/sasl.app.src2
-rw-r--r--lib/sasl/src/sasl.appup.src35
-rw-r--r--lib/sasl/src/systools.erl6
-rw-r--r--lib/sasl/src/systools_relup.erl2
-rw-r--r--lib/sasl/vsn.mk2
-rw-r--r--lib/snmp/doc/src/snmp.xml82
-rw-r--r--lib/snmp/doc/src/snmp_community_mib.xml12
-rw-r--r--lib/snmp/doc/src/snmp_framework_mib.xml10
-rw-r--r--lib/snmp/doc/src/snmp_generic.xml30
-rw-r--r--lib/snmp/doc/src/snmp_index.xml18
-rw-r--r--lib/snmp/doc/src/snmp_notification_mib.xml10
-rw-r--r--lib/snmp/doc/src/snmp_pdus.xml26
-rw-r--r--lib/snmp/doc/src/snmp_standard_mib.xml14
-rw-r--r--lib/snmp/doc/src/snmp_target_mib.xml16
-rw-r--r--lib/snmp/doc/src/snmp_user_based_sm_mib.xml10
-rw-r--r--lib/snmp/doc/src/snmp_view_based_acm_mib.xml18
-rw-r--r--lib/snmp/doc/src/snmpa.xml252
-rw-r--r--lib/snmp/doc/src/snmpa_conf.xml112
-rw-r--r--lib/snmp/doc/src/snmpa_discovery_handler.xml4
-rw-r--r--lib/snmp/doc/src/snmpa_error.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_io.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_logger.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_error_report.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_local_db.xml24
-rw-r--r--lib/snmp/doc/src/snmpa_mib_data.xml34
-rw-r--r--lib/snmp/doc/src/snmpa_mib_storage.xml26
-rw-r--r--lib/snmp/doc/src/snmpa_mpd.xml20
-rw-r--r--lib/snmp/doc/src/snmpa_network_interface.xml12
-rw-r--r--lib/snmp/doc/src/snmpa_network_interface_filter.xml10
-rw-r--r--lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml6
-rw-r--r--lib/snmp/doc/src/snmpa_notification_filter.xml4
-rw-r--r--lib/snmp/doc/src/snmpa_supervisor.xml10
-rw-r--r--lib/snmp/doc/src/snmpc.xml10
-rw-r--r--lib/snmp/doc/src/snmpm.xml224
-rw-r--r--lib/snmp/doc/src/snmpm_conf.xml48
-rw-r--r--lib/snmp/doc/src/snmpm_mpd.xml10
-rw-r--r--lib/snmp/doc/src/snmpm_network_interface.xml20
-rw-r--r--lib/snmp/doc/src/snmpm_network_interface_filter.xml10
-rw-r--r--lib/snmp/doc/src/snmpm_user.xml16
-rw-r--r--lib/ssh/doc/src/notes.xml93
-rw-r--r--lib/ssh/doc/src/ssh.xml48
-rw-r--r--lib/ssh/doc/src/ssh_client_channel.xml32
-rw-r--r--lib/ssh/doc/src/ssh_client_key_api.xml8
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml36
-rw-r--r--lib/ssh/doc/src/ssh_file.xml12
-rw-r--r--lib/ssh/doc/src/ssh_server_channel.xml10
-rw-r--r--lib/ssh/doc/src/ssh_server_key_api.xml6
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml108
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml4
-rw-r--r--lib/ssh/src/ssh.erl60
-rw-r--r--lib/ssh/src/ssh_sftpd.erl7
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl22
-rw-r--r--lib/ssh/test/ssh_compat_SUITE.erl18
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl4
-rw-r--r--lib/ssh/test/ssh_engine_SUITE.erl11
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml129
-rw-r--r--lib/ssl/doc/src/ssl.xml142
-rw-r--r--lib/ssl/doc/src/ssl_app.xml14
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache.xml8
-rw-r--r--lib/ssl/doc/src/ssl_crl_cache_api.xml10
-rw-r--r--lib/ssl/doc/src/ssl_session_cache_api.xml18
-rw-r--r--lib/ssl/src/dtls_connection.erl352
-rw-r--r--lib/ssl/src/dtls_handshake.erl18
-rw-r--r--lib/ssl/src/ssl.erl34
-rw-r--r--lib/ssl/src/ssl_cipher.erl65
-rw-r--r--lib/ssl/src/ssl_connection.erl1033
-rw-r--r--lib/ssl/src/ssl_connection.hrl200
-rw-r--r--lib/ssl/src/ssl_handshake.erl149
-rw-r--r--lib/ssl/src/ssl_internal.hrl7
-rw-r--r--lib/ssl/src/ssl_logger.erl48
-rw-r--r--lib/ssl/src/ssl_manager.erl42
-rw-r--r--lib/ssl/src/ssl_record.erl17
-rw-r--r--lib/ssl/src/ssl_session.erl12
-rw-r--r--lib/ssl/src/tls_connection.erl505
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl90
-rw-r--r--lib/ssl/src/tls_handshake.erl18
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl412
-rw-r--r--lib/ssl/src/tls_handshake_1_3.hrl16
-rw-r--r--lib/ssl/src/tls_record.erl144
-rw-r--r--lib/ssl/src/tls_record_1_3.erl168
-rw-r--r--lib/ssl/src/tls_sender.erl123
-rw-r--r--lib/ssl/src/tls_v1.erl264
-rw-r--r--lib/ssl/test/make_certs.erl12
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_handshake.erl15
-rw-r--r--lib/ssl/test/ssl.spec14
-rw-r--r--lib/ssl/test/ssl_alpn_handshake_SUITE.erl44
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl677
-rw-r--r--lib/ssl/test/ssl_bench.spec1
-rw-r--r--lib/ssl/test/ssl_bench_SUITE.erl28
-rw-r--r--lib/ssl/test/ssl_crl_SUITE.erl7
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl323
-rw-r--r--lib/ssl/test/ssl_dist_test_lib.erl5
-rw-r--r--lib/ssl/test/ssl_engine_SUITE.erl15
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl36
-rw-r--r--lib/ssl/test/ssl_npn_handshake_SUITE.erl62
-rw-r--r--lib/ssl/test/ssl_packet_SUITE.erl25
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl632
-rw-r--r--lib/ssl/test/ssl_pem_cache_SUITE.erl15
-rw-r--r--lib/ssl/test/ssl_session_cache_SUITE.erl126
-rw-r--r--lib/ssl/test/ssl_test_lib.erl212
-rw-r--r--lib/ssl/test/ssl_to_openssl_SUITE.erl90
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/array.xml60
-rw-r--r--lib/stdlib/doc/src/base64.xml14
-rw-r--r--lib/stdlib/doc/src/beam_lib.xml34
-rw-r--r--lib/stdlib/doc/src/binary.xml56
-rw-r--r--lib/stdlib/doc/src/c.xml70
-rw-r--r--lib/stdlib/doc/src/calendar.xml70
-rw-r--r--lib/stdlib/doc/src/dets.xml90
-rw-r--r--lib/stdlib/doc/src/dict.xml44
-rw-r--r--lib/stdlib/doc/src/digraph.xml66
-rw-r--r--lib/stdlib/doc/src/digraph_utils.xml38
-rw-r--r--lib/stdlib/doc/src/epp.xml34
-rw-r--r--lib/stdlib/doc/src/erl_anno.xml36
-rw-r--r--lib/stdlib/doc/src/erl_eval.xml32
-rw-r--r--lib/stdlib/doc/src/erl_expand_records.xml4
-rw-r--r--lib/stdlib/doc/src/erl_id_trans.xml4
-rw-r--r--lib/stdlib/doc/src/erl_internal.xml22
-rw-r--r--lib/stdlib/doc/src/erl_lint.xml12
-rw-r--r--lib/stdlib/doc/src/erl_parse.xml32
-rw-r--r--lib/stdlib/doc/src/erl_pp.xml32
-rw-r--r--lib/stdlib/doc/src/erl_scan.xml30
-rw-r--r--lib/stdlib/doc/src/erl_tar.xml30
-rw-r--r--lib/stdlib/doc/src/ets.xml261
-rw-r--r--lib/stdlib/doc/src/file_sorter.xml30
-rw-r--r--lib/stdlib/doc/src/filelib.xml30
-rw-r--r--lib/stdlib/doc/src/filename.xml46
-rw-r--r--lib/stdlib/doc/src/gb_sets.xml72
-rw-r--r--lib/stdlib/doc/src/gb_trees.xml54
-rw-r--r--lib/stdlib/doc/src/gen_event.xml52
-rw-r--r--lib/stdlib/doc/src/gen_server.xml56
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml48
-rw-r--r--lib/stdlib/doc/src/io.xml96
-rw-r--r--lib/stdlib/doc/src/io_lib.xml64
-rw-r--r--lib/stdlib/doc/src/lists.xml172
-rw-r--r--lib/stdlib/doc/src/log_mf_h.xml6
-rw-r--r--lib/stdlib/doc/src/maps.xml80
-rw-r--r--lib/stdlib/doc/src/math.xml52
-rw-r--r--lib/stdlib/doc/src/ms_transform.xml8
-rw-r--r--lib/stdlib/doc/src/notes.xml79
-rw-r--r--lib/stdlib/doc/src/orddict.xml44
-rw-r--r--lib/stdlib/doc/src/ordsets.xml38
-rw-r--r--lib/stdlib/doc/src/pool.xml18
-rw-r--r--lib/stdlib/doc/src/proc_lib.xml58
-rw-r--r--lib/stdlib/doc/src/proplists.xml40
-rw-r--r--lib/stdlib/doc/src/qlc.xml54
-rw-r--r--lib/stdlib/doc/src/queue.xml62
-rw-r--r--lib/stdlib/doc/src/rand.xml38
-rw-r--r--lib/stdlib/doc/src/random.xml18
-rw-r--r--lib/stdlib/doc/src/re.xml22
-rw-r--r--lib/stdlib/doc/src/sets.xml38
-rw-r--r--lib/stdlib/doc/src/shell.xml16
-rw-r--r--lib/stdlib/doc/src/slave.xml22
-rw-r--r--lib/stdlib/doc/src/sofs.xml168
-rw-r--r--lib/stdlib/doc/src/string.xml142
-rw-r--r--lib/stdlib/doc/src/supervisor.xml24
-rw-r--r--lib/stdlib/doc/src/supervisor_bridge.xml10
-rw-r--r--lib/stdlib/doc/src/sys.xml244
-rw-r--r--lib/stdlib/doc/src/timer.xml44
-rw-r--r--lib/stdlib/doc/src/unicode.xml32
-rw-r--r--lib/stdlib/doc/src/uri_string.xml18
-rw-r--r--lib/stdlib/doc/src/win32reg.xml28
-rw-r--r--lib/stdlib/doc/src/zip.xml44
-rw-r--r--lib/stdlib/src/calendar.erl11
-rw-r--r--lib/stdlib/src/erl_lint.erl9
-rw-r--r--lib/stdlib/src/erl_parse.yrl9
-rw-r--r--lib/stdlib/src/gen_fsm.erl154
-rw-r--r--lib/stdlib/src/gen_server.erl53
-rw-r--r--lib/stdlib/src/gen_statem.erl2302
-rw-r--r--lib/stdlib/src/ms_transform.erl4
-rw-r--r--lib/stdlib/src/otp_internal.erl8
-rw-r--r--lib/stdlib/src/stdlib.appup.src43
-rw-r--r--lib/stdlib/src/sys.erl150
-rw-r--r--lib/stdlib/test/calendar_SUITE.erl68
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl76
-rw-r--r--lib/stdlib/test/gen_fsm_SUITE.erl12
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl43
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE.erl79
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl4
-rw-r--r--lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl10
-rw-r--r--lib/stdlib/test/unicode_util_SUITE.erl27
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl5
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE.erl36
-rw-r--r--lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl18
-rw-r--r--lib/tftp/doc/src/tftp.xml36
-rw-r--r--lib/tools/doc/src/cover.xml84
-rw-r--r--lib/tools/doc/src/cprof.xml42
-rw-r--r--lib/tools/doc/src/eprof.xml38
-rw-r--r--lib/tools/doc/src/fprof.xml50
-rw-r--r--lib/tools/doc/src/instrument.xml10
-rw-r--r--lib/tools/doc/src/lcnt.xml68
-rw-r--r--lib/tools/doc/src/make.xml10
-rw-r--r--lib/tools/doc/src/notes.xml15
-rw-r--r--lib/tools/doc/src/tags.xml16
-rw-r--r--lib/tools/doc/src/xref.xml64
-rw-r--r--lib/tools/priv/styles.css5
-rw-r--r--lib/tools/src/cover.erl4
-rw-r--r--lib/tools/src/eprof.erl52
-rw-r--r--lib/tools/src/tools.app.src2
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/api_gen/wx_gen_cpp.erl9
-rw-r--r--lib/wx/c_src/gen/wxe_funcs.cpp10
-rw-r--r--lib/wx/c_src/wxe_helpers.cpp3
-rw-r--r--lib/wx/c_src/wxe_helpers.h2
-rw-r--r--lib/wx/c_src/wxe_impl.cpp8
-rw-r--r--lib/wx/c_src/wxe_impl.h2
-rw-r--r--lib/wx/doc/src/notes.xml16
-rw-r--r--lib/wx/vsn.mk2
-rw-r--r--lib/xmerl/doc/src/notes.xml31
-rw-r--r--lib/xmerl/doc/src/xmerl_sax_parser.xml10
-rw-r--r--lib/xmerl/src/xmerl_sax_parser.erl136
-rw-r--r--lib/xmerl/test/xmerl_xsd_lib.erl7
-rw-r--r--lib/xmerl/vsn.mk2
565 files changed, 28587 insertions, 18053 deletions
diff --git a/lib/asn1/c_src/asn1_erl_nif.c b/lib/asn1/c_src/asn1_erl_nif.c
index 797be6d4f8..da43af3405 100644
--- a/lib/asn1/c_src/asn1_erl_nif.c
+++ b/lib/asn1/c_src/asn1_erl_nif.c
@@ -999,7 +999,7 @@ static int ber_decode_value(ErlNifEnv* env, ERL_NIF_TERM *value, unsigned char *
while (*ib_index < end_index) {
if ((maybe_ret = ber_decode(env, &term, in_buf, ib_index,
- *ib_index + len)) <= ASN1_ERROR
+ end_index )) <= ASN1_ERROR
)
return maybe_ret;
curr_head = enif_make_list_cell(env, term, curr_head);
diff --git a/lib/asn1/doc/src/asn1ct.xml b/lib/asn1/doc/src/asn1ct.xml
index ccf07a9cc1..e86dbd9f5e 100644
--- a/lib/asn1/doc/src/asn1ct.xml
+++ b/lib/asn1/doc/src/asn1ct.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>asn1.sgml</file>
</header>
- <module>asn1ct</module>
+ <module since="">asn1ct</module>
<modulesummary>ASN.1 compiler and compile-time support functions</modulesummary>
<description>
<p>The ASN.1 compiler takes an ASN.1 module as input and generates a
@@ -72,8 +72,8 @@
<funcs>
<func>
- <name>compile(Asn1module) -> ok | {error, Reason}</name>
- <name>compile(Asn1module, Options) -> ok | {error, Reason}</name>
+ <name since="">compile(Asn1module) -> ok | {error, Reason}</name>
+ <name since="">compile(Asn1module, Options) -> ok | {error, Reason}</name>
<fsummary>Compiles an ASN.1 module and generates encode/decode functions according to encoding rules BER or PER.</fsummary>
<type>
<v>Asn1module = atom() | string()</v>
@@ -336,7 +336,7 @@ File3.asn</pre>
</func>
<func>
- <name>value(Module, Type) -> {ok, Value} | {error, Reason}</name>
+ <name since="">value(Module, Type) -> {ok, Value} | {error, Reason}</name>
<fsummary>Creates an ASN.1 value for test purposes.</fsummary>
<type>
<v>Module = Type = atom()</v>
@@ -361,9 +361,9 @@ File3.asn</pre>
</func>
<func>
- <name>test(Module) -> ok | {error, Reason}</name>
- <name>test(Module, Type | Options) -> ok | {error, Reason}</name>
- <name>test(Module, Type, Value | Options) -> ok | {error, Reason}</name>
+ <name since="">test(Module) -> ok | {error, Reason}</name>
+ <name since="">test(Module, Type | Options) -> ok | {error, Reason}</name>
+ <name since="">test(Module, Type, Value | Options) -> ok | {error, Reason}</name>
<fsummary>Performs a test of encode and decode for types in an ASN.1 module.</fsummary>
<type>
<v>Module = Type = atom()</v>
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml
index 086b5f38c6..22ca7840de 100644
--- a/lib/asn1/doc/src/notes.xml
+++ b/lib/asn1/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the asn1 application.</p>
+<section><title>Asn1 5.0.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Handle erroneous length during decode (BER only) without
+ crashing.</p>
+ <p>
+ Own Id: OTP-15470 Aux Id: ERIERL-278 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Asn1 5.0.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -69,6 +85,22 @@
</section>
+<section><title>Asn1 5.0.5.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Handle erroneous length during decode (BER only) without
+ crashing.</p>
+ <p>
+ Own Id: OTP-15470 Aux Id: ERIERL-278 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Asn1 5.0.5.1</title>
<section><title>Known Bugs and Problems</title>
@@ -1980,4 +2012,3 @@
<!-- p>There are also release notes for <url href="notes_history.html">older versions</url>.</p -->
</section>
</chapter>
-
diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl
index 5506923341..ab78678110 100644
--- a/lib/asn1/test/asn1_SUITE.erl
+++ b/lib/asn1/test/asn1_SUITE.erl
@@ -63,7 +63,8 @@ groups() ->
constraint_equivalence]},
{ber, Parallel,
- [ber_choiceinseq,
+ [ber_decode_invalid_length,
+ ber_choiceinseq,
% Uses 'SOpttest'
ber_optional,
tagdefault_automatic]},
@@ -665,6 +666,19 @@ module_test(M0, Config, Rule, Opts) ->
end
end.
+ber_decode_invalid_length(_Config) ->
+ Bin = <<48,129,157,48,0,2,1,2,164,0,48,129,154,49,24,48,22,6,
+ 3,85,4,10,19,15,69,120,97,109,112,108,101,32,67,111,
+ 109,112,97,110,121,49,29,48,27,6,9,42,134,72,134,247,
+ 13,1,9,1,22,14,99,97,64,101,120,97,109,112,108,101,46,
+ 99,111,109,49,13,48,11,6,3,85,4,7,19,4,79,117,108,117,
+ 49,26,48,24,6,3,85,4,8,19,17,80,111,104,106,111,105,
+ 115,45,80,111,104,106,97,110,109,97,97,49,11,48,9,6,3,
+ 85,4,6,19,2,70,73,49,19,48,17,6,3,85,4,3,19,10,69,120,
+ 97,109,112,108,101,32,67,65,49,11,48,16,6,3,85,4,11,
+ 19,9,84,101>>,
+ {'EXIT',{error,{asn1,{invalid_value,12}}}} = (catch asn1rt_nif:decode_ber_tlv(Bin)),
+ ok.
ber_choiceinseq(Config) ->
test(Config, fun ber_choiceinseq/3, [ber]).
diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk
index 970479cc44..69f1af28e8 100644
--- a/lib/asn1/vsn.mk
+++ b/lib/asn1/vsn.mk
@@ -1 +1 @@
-ASN1_VSN = 5.0.7
+ASN1_VSN = 5.0.8
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index a3b3f927eb..7887a2c3ea 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>common_test_app.sgml</file>
</header>
- <module>common_test</module>
+ <module since="">common_test</module>
<modulesummary>A framework for automated testing of any target nodes.</modulesummary>
<description>
@@ -68,7 +68,7 @@
<funcs>
<func>
- <name>Module:all() -> Tests | {skip,Reason} </name>
+ <name since="">Module:all() -> Tests | {skip,Reason} </name>
<fsummary>Returns the list of all test case groups and test cases
in the module.</fsummary>
<type>
@@ -115,7 +115,7 @@
</func>
<func>
- <name>Module:groups() -> GroupDefs</name>
+ <name since="">Module:groups() -> GroupDefs</name>
<fsummary>Returns a list of test case group definitions.</fsummary>
<type>
<v>GroupDefs = [Group]</v>
@@ -140,7 +140,7 @@
</func>
<func>
- <name>Module:suite() -> [Info] </name>
+ <name since="">Module:suite() -> [Info] </name>
<fsummary>Test suite info function (providing default data
for the suite).</fsummary>
<type>
@@ -213,7 +213,7 @@
</func>
<func>
- <name>Module:init_per_suite(Config) -> NewConfig | {skip,Reason} |
+ <name since="">Module:init_per_suite(Config) -> NewConfig | {skip,Reason} |
{skip_and_save,Reason,SaveConfig}</name>
<fsummary>Test suite initializations.</fsummary>
<type>
@@ -248,7 +248,7 @@
</func>
<func>
- <name>Module:end_per_suite(Config) -> term() |
+ <name since="">Module:end_per_suite(Config) -> term() |
{save_config,SaveConfig}</name>
<fsummary>Test suite finalization.</fsummary>
<type>
@@ -272,7 +272,7 @@
</func>
<func>
- <name>Module:group(GroupName) -> [Info] </name>
+ <name since="OTP R15B">Module:group(GroupName) -> [Info] </name>
<fsummary>Test case group information function (providing default data
for a test case group, that is, its test cases and
subgroups).</fsummary>
@@ -352,7 +352,7 @@
</func>
<func>
- <name>Module:init_per_group(GroupName, Config) -> NewConfig |
+ <name since="">Module:init_per_group(GroupName, Config) -> NewConfig |
{skip,Reason}</name>
<fsummary>Test case group initializations.</fsummary>
<type>
@@ -390,7 +390,7 @@
</func>
<func>
- <name>Module:end_per_group(GroupName, Config) -> term() |
+ <name since="">Module:end_per_group(GroupName, Config) -> term() |
{return_group_result,Status}</name>
<fsummary>Test case group finalization.</fsummary>
<type>
@@ -424,7 +424,7 @@
</func>
<func>
- <name>Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name>
+ <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name>
<fsummary>Test case initializations.</fsummary>
<type>
<v> TestCase = atom()</v>
@@ -454,7 +454,7 @@
</func>
<func>
- <name>Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name>
+ <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name>
<fsummary>Test case finalization.</fsummary>
<type>
<v>TestCase = atom()</v>
@@ -486,7 +486,7 @@
</func>
<func>
- <name>Module:Testcase() -> [Info] </name>
+ <name since="OTP R14B">Module:Testcase() -> [Info] </name>
<fsummary>Test case information function.</fsummary>
<type>
<v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns}</v>
@@ -560,7 +560,7 @@
</func>
<func>
- <name>Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
+ <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
<fsummary>A test case.</fsummary>
<type>
<v>Config = SaveConfig = [{Key,Value}]</v>
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index c0380c4142..83c0ecb309 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct.xml</file>
</header>
- <module>ct</module>
+ <module since="">ct</module>
<modulesummary>Main user interface for the Common Test framework.</modulesummary>
<description>
@@ -139,7 +139,7 @@
<funcs>
<func>
- <name>abort_current_testcase(Reason) -&gt; ok | {error, ErrorReason}</name>
+ <name since="">abort_current_testcase(Reason) -&gt; ok | {error, ErrorReason}</name>
<fsummary>Aborts the currently executing test case.</fsummary>
<type>
<v>Reason = term()</v>
@@ -157,7 +157,7 @@
</func>
<func>
- <name>add_config(Callback, Config) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R14B">add_config(Callback, Config) -&gt; ok | {error, Reason}</name>
<fsummary>Loads configuration variables using the specified callback
module and configuration string.</fsummary>
<type>
@@ -176,7 +176,7 @@
</func>
<func>
- <name>break(Comment) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B02">break(Comment) -&gt; ok | {error, Reason}</name>
<fsummary>Cancels any active timetrap and pause the execution of the
current test case until the user calls function continue/0.</fsummary>
<type>
@@ -206,7 +206,7 @@
</func>
<func>
- <name>break(TestCase, Comment) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B02">break(TestCase, Comment) -&gt; ok | {error, Reason}</name>
<fsummary>Works the same way as break/1, only argument TestCase makes it
possible to pause a test case executing in a parallel group.</fsummary>
<type>
@@ -228,7 +228,7 @@
</func>
<func>
- <name>capture_get() -&gt; ListOfStrings</name>
+ <name since="OTP R15B">capture_get() -&gt; ListOfStrings</name>
<fsummary>Equivalent to capture_get([default]).</fsummary>
<type>
<v>ListOfStrings = [string()]</v>
@@ -240,7 +240,7 @@
</func>
<func>
- <name>capture_get(ExclCategories) -&gt; ListOfStrings</name>
+ <name since="OTP R15B">capture_get(ExclCategories) -&gt; ListOfStrings</name>
<fsummary>Returns and purges the list of text strings buffered during
the latest session of capturing printouts to stdout.</fsummary>
<type>
@@ -262,7 +262,7 @@
</func>
<func>
- <name>capture_start() -&gt; ok</name>
+ <name since="OTP R15B">capture_start() -&gt; ok</name>
<fsummary>Starts capturing all text strings printed to stdout
during execution of the test case.</fsummary>
<desc><marker id="capture_start-0"/>
@@ -276,7 +276,7 @@
</func>
<func>
- <name>capture_stop() -&gt; ok</name>
+ <name since="OTP R15B">capture_stop() -&gt; ok</name>
<fsummary>Stops capturing text strings (a session started with
capture_start/0).</fsummary>
<desc><marker id="capture_stop-0"/>
@@ -290,7 +290,7 @@
</func>
<func>
- <name>comment(Comment) -&gt; ok</name>
+ <name since="">comment(Comment) -&gt; ok</name>
<fsummary>Prints the specified Comment in the comment field in the
table on the test suite result page.</fsummary>
<type>
@@ -307,7 +307,7 @@
</func>
<func>
- <name>comment(Format, Args) -&gt; ok</name>
+ <name since="OTP R15B">comment(Format, Args) -&gt; ok</name>
<fsummary>Prints the formatted string in the comment field in the
table on the test suite result page.</fsummary>
<type>
@@ -326,7 +326,7 @@
</func>
<func>
- <name>continue() -&gt; ok</name>
+ <name since="OTP R15B02">continue() -&gt; ok</name>
<fsummary>This function must be called to continue after a test
case (not executing in a parallel group) has called break/1.</fsummary>
<desc><marker id="continue-0"/>
@@ -337,7 +337,7 @@
</func>
<func>
- <name>continue(TestCase) -&gt; ok</name>
+ <name since="OTP R15B02">continue(TestCase) -&gt; ok</name>
<fsummary>This function must be called to continue after a test case
has called break/2.</fsummary>
<type>
@@ -353,7 +353,7 @@
</func>
<func>
- <name>decrypt_config_file(EncryptFileName, TargetFileName) -&gt; ok | {error, Reason}</name>
+ <name since="">decrypt_config_file(EncryptFileName, TargetFileName) -&gt; ok | {error, Reason}</name>
<fsummary>Decrypts EncryptFileName, previously generated with
encrypt_config_file/2,3.</fsummary>
<type>
@@ -372,7 +372,7 @@
</func>
<func>
- <name>decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
+ <name since="">decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
<fsummary>Decrypts EncryptFileName, previously generated with
encrypt_config_file/2,3.</fsummary>
<type>
@@ -390,7 +390,7 @@
</func>
<func>
- <name>encrypt_config_file(SrcFileName, EncryptFileName) -&gt; ok | {error, Reason}</name>
+ <name since="">encrypt_config_file(SrcFileName, EncryptFileName) -&gt; ok | {error, Reason}</name>
<fsummary>Encrypts the source configuration file with DES3 and saves the
result in file EncryptFileName.</fsummary>
<type>
@@ -416,7 +416,7 @@
</func>
<func>
- <name>encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
+ <name since="">encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -&gt; ok | {error, Reason}</name>
<fsummary>Encrypts the source configuration file with DES3 and saves the
result in the target file EncryptFileName.</fsummary>
<type>
@@ -442,7 +442,7 @@
</func>
<func>
- <name>fail(Reason) -&gt; ok</name>
+ <name since="">fail(Reason) -&gt; ok</name>
<fsummary>Terminates a test case with the specified error
Reason.</fsummary>
<type>
@@ -454,7 +454,7 @@
</func>
<func>
- <name>fail(Format, Args) -&gt; ok</name>
+ <name since="OTP R15B">fail(Format, Args) -&gt; ok</name>
<fsummary>Terminates a test case with an error message specified by
a format string and a list of values (used as arguments to
io_lib:format/2).</fsummary>
@@ -470,7 +470,7 @@
</func>
<func>
- <name>get_config(Required) -&gt; Value</name>
+ <name since="">get_config(Required) -&gt; Value</name>
<fsummary>Equivalent to get_config(Required, undefined, []).</fsummary>
<desc><marker id="get_config-1"/>
<p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required,
@@ -479,7 +479,7 @@
</func>
<func>
- <name>get_config(Required, Default) -&gt; Value</name>
+ <name since="">get_config(Required, Default) -&gt; Value</name>
<fsummary>Equivalent to get_config(Required, Default, []).</fsummary>
<desc><marker id="get_config-2"/>
<p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required,
@@ -488,7 +488,7 @@
</func>
<func>
- <name>get_config(Required, Default, Opts) -&gt; ValueOrElement</name>
+ <name since="">get_config(Required, Default, Opts) -&gt; ValueOrElement</name>
<fsummary>Reads configuration data values.</fsummary>
<type>
<v>Required = KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey}</v>
@@ -554,7 +554,7 @@
</func>
<func>
- <name>get_event_mgr_ref() -&gt; EvMgrRef</name>
+ <name since="OTP 17.5">get_event_mgr_ref() -&gt; EvMgrRef</name>
<fsummary>Gets a reference to the <c>Common Test</c> event manager.</fsummary>
<type>
<v>EvMgrRef = atom()</v>
@@ -572,7 +572,7 @@
</func>
<func>
- <name>get_progname() -&gt; string()</name>
+ <name since="OTP 21.0">get_progname() -&gt; string()</name>
<fsummary>Returns the command used to start this Erlang instance.</fsummary>
<desc><marker id="get_progname-0"/>
<p>Returns the command used to start this Erlang instance.
@@ -582,7 +582,7 @@
</func>
<func>
- <name>get_status() -&gt; TestStatus | {error, Reason} | no_tests_running</name>
+ <name since="">get_status() -&gt; TestStatus | {error, Reason} | no_tests_running</name>
<fsummary>Returns status of ongoing test.</fsummary>
<type>
<v>TestStatus = [StatusElem]</v>
@@ -608,7 +608,7 @@
</func>
<func>
- <name>get_target_name(Handle) -&gt; {ok, TargetName} | {error, Reason}</name>
+ <name since="">get_target_name(Handle) -&gt; {ok, TargetName} | {error, Reason}</name>
<fsummary>Returns the name of the target that the specified connection
belongs to.</fsummary>
<type>
@@ -622,7 +622,7 @@
</func>
<func>
- <name>get_testspec_terms() -&gt; TestSpecTerms | undefined</name>
+ <name since="OTP 18.0">get_testspec_terms() -&gt; TestSpecTerms | undefined</name>
<fsummary>Gets a list of all test specification terms used to
configure and run this test.</fsummary>
<type>
@@ -636,7 +636,7 @@
</func>
<func>
- <name>get_testspec_terms(Tags) -&gt; TestSpecTerms | undefined</name>
+ <name since="OTP 18.0">get_testspec_terms(Tags) -&gt; TestSpecTerms | undefined</name>
<fsummary>Reads one or more terms from the test specification used to
configure and run this test.</fsummary>
<type>
@@ -663,7 +663,7 @@
</func>
<func>
- <name>get_timetrap_info() -&gt; {Time, {Scaling,ScaleVal}}</name>
+ <name since="OTP R15B">get_timetrap_info() -&gt; {Time, {Scaling,ScaleVal}}</name>
<fsummary>Reads information about the timetrap set for the current
test case.</fsummary>
<type>
@@ -682,7 +682,7 @@
</func>
<func>
- <name>get_verbosity(Category) -&gt; Level | undefined</name>
+ <name since="OTP 19.1">get_verbosity(Category) -&gt; Level | undefined</name>
<fsummary>Read the verbosity level for a logging category.</fsummary>
<type>
<v>Category = default | atom()</v>
@@ -697,7 +697,7 @@
</func>
<func>
- <name>install(Opts) -&gt; ok | {error, Reason}</name>
+ <name since="">install(Opts) -&gt; ok | {error, Reason}</name>
<fsummary>Installs configuration files and event handlers.</fsummary>
<type>
<v>Opts = [Opt]</v>
@@ -724,7 +724,7 @@
</func>
<func>
- <name>listenv(Telnet) -&gt; [Env]</name>
+ <name since="">listenv(Telnet) -&gt; [Env]</name>
<fsummary>Performs command listenv on the specified Telnet connection
and returns the result as a list of key-value pairs.</fsummary>
<type>
@@ -740,7 +740,7 @@
</func>
<func>
- <name>log(Format) -&gt; ok</name>
+ <name since="">log(Format) -&gt; ok</name>
<fsummary>Equivalent to log(default, 50, Format, [], []).</fsummary>
<desc><marker id="log-1"/>
<p>Equivalent to
@@ -749,7 +749,7 @@
</func>
<func>
- <name>log(X1, X2) -&gt; ok</name>
+ <name since="">log(X1, X2) -&gt; ok</name>
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, []).</fsummary>
<type>
@@ -763,7 +763,7 @@
</func>
<func>
- <name>log(X1, X2, X3) -&gt; ok</name>
+ <name since="">log(X1, X2, X3) -&gt; ok</name>
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -778,7 +778,7 @@
</func>
<func>
- <name>log(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02">log(X1, X2, X3, X4) -&gt; ok</name>
<fsummary>Equivalent to log(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -794,7 +794,7 @@
</func>
<func>
- <name>log(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 18.3">log(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
<fsummary>Prints from a test case to the log file.</fsummary>
<type>
<v>Category = atom()</v>
@@ -825,7 +825,7 @@
</func>
<func>
- <name>make_priv_dir() -&gt; ok | {error, Reason}</name>
+ <name since="OTP R15B01">make_priv_dir() -&gt; ok | {error, Reason}</name>
<fsummary>If the test has been started with option create_priv_dir
set to manual_per_tc, in order for the test case to use the private
directory, it must first create it by calling this function.</fsummary>
@@ -841,7 +841,7 @@
</func>
<func>
- <name>notify(Name, Data) -&gt; ok</name>
+ <name since="OTP R15B02">notify(Name, Data) -&gt; ok</name>
<fsummary>Sends an asynchronous notification of type Name with Data
to the <c>Common Test</c> event manager.</fsummary>
<type>
@@ -859,7 +859,7 @@
</func>
<func>
- <name>pal(Format) -&gt; ok</name>
+ <name since="">pal(Format) -&gt; ok</name>
<fsummary>Equivalent to pal(default, 50, Format, [], []).</fsummary>
<desc><marker id="pal-1"/>
<p>Equivalent to
@@ -869,7 +869,7 @@
</func>
<func>
- <name>pal(X1, X2) -&gt; ok</name>
+ <name since="">pal(X1, X2) -&gt; ok</name>
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, []).</fsummary>
<type>
@@ -883,7 +883,7 @@
</func>
<func>
- <name>pal(X1, X2, X3) -&gt; ok</name>
+ <name since="">pal(X1, X2, X3) -&gt; ok</name>
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -898,7 +898,7 @@
</func>
<func>
- <name>pal(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02">pal(X1, X2, X3, X4) -&gt; ok</name>
<fsummary>Equivalent to pal(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -914,7 +914,7 @@
</func>
<func>
- <name>pal(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 19.2">pal(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
<fsummary>Prints and logs from a test case.</fsummary>
<type>
<v>Category = atom()</v>
@@ -945,7 +945,7 @@
</func>
<func>
- <name>parse_table(Data) -&gt; {Heading, Table}</name>
+ <name since="">parse_table(Data) -&gt; {Heading, Table}</name>
<fsummary>Parses the printout from an SQL table and returns a list of
tuples.</fsummary>
<type>
@@ -967,7 +967,7 @@
</func>
<func>
- <name>print(Format) -&gt; ok</name>
+ <name since="">print(Format) -&gt; ok</name>
<fsummary>Equivalent to print(default, 50, Format, [], []).</fsummary>
<desc><marker id="print-1"/>
<p>Equivalent to <seealso marker="#print-5"><c>ct:print(default,
@@ -976,7 +976,7 @@
</func>
<func>
- <name>print(X1, X2) -&gt; ok</name>
+ <name since="OTP R15B02">print(X1, X2) -&gt; ok</name>
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, []).</fsummary>
<type>
@@ -990,7 +990,7 @@
</func>
<func>
- <name>print(X1, X2, X3) -&gt; ok</name>
+ <name since="">print(X1, X2, X3) -&gt; ok</name>
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -1005,7 +1005,7 @@
</func>
<func>
- <name>print(X1, X2, X3, X4) -&gt; ok</name>
+ <name since="OTP R15B02">print(X1, X2, X3, X4) -&gt; ok</name>
<fsummary>Equivalent to print(Category, Importance, Format,
FormatArgs, Opts).</fsummary>
<type>
@@ -1021,7 +1021,7 @@
</func>
<func>
- <name>print(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
+ <name since="OTP 19.2">print(Category, Importance, Format, FormatArgs, Opts) -&gt; ok</name>
<fsummary>Prints from a test case to the console.</fsummary>
<type>
<v>Category = atom()</v>
@@ -1048,7 +1048,7 @@
</func>
<func>
- <name>reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
+ <name since="OTP R14B">reload_config(Required) -&gt; ValueOrElement | {error, Reason}</name>
<fsummary>Reloads configuration file containing specified configuration
key.</fsummary>
<type>
@@ -1071,7 +1071,7 @@
</func>
<func>
- <name>remaining_test_procs() -&gt; {TestProcs,SharedGL,OtherGLs}</name>
+ <name since="OTP 20.2">remaining_test_procs() -&gt; {TestProcs,SharedGL,OtherGLs}</name>
<fsummary>>This function will return the identity of test- and group
leader processes that are still running at the time of this call.</fsummary>
<type>
@@ -1107,7 +1107,7 @@
</func>
<func>
- <name>remove_config(Callback, Config) -&gt; ok</name>
+ <name since="OTP R14B">remove_config(Callback, Config) -&gt; ok</name>
<fsummary>Removes configuration variables (together with
their aliases) that were loaded with specified callback module and
configuration string.</fsummary>
@@ -1124,7 +1124,7 @@
</func>
<func>
- <name>require(Required) -&gt; ok | {error, Reason}</name>
+ <name since="">require(Required) -&gt; ok | {error, Reason}</name>
<fsummary>Checks if the required configuration is available.</fsummary>
<type>
<v>Required = Key | {Key, SubKeys} | {Key, SubKey, SubKeys}</v>
@@ -1178,7 +1178,7 @@
</func>
<func>
- <name>require(Name, Required) -&gt; ok | {error, Reason}</name>
+ <name since="">require(Name, Required) -&gt; ok | {error, Reason}</name>
<fsummary>Checks if the required configuration is available and gives
it a name.</fsummary>
<type>
@@ -1237,7 +1237,7 @@
</func>
<func>
- <name>run(TestDirs) -&gt; Result</name>
+ <name since="">run(TestDirs) -&gt; Result</name>
<fsummary>Runs all test cases in all suites in the specified
directories.</fsummary>
<type>
@@ -1251,7 +1251,7 @@
</func>
<func>
- <name>run(TestDir, Suite) -&gt; Result</name>
+ <name since="">run(TestDir, Suite) -&gt; Result</name>
<fsummary>Runs all test cases in the specified suite.</fsummary>
<desc><marker id="run-2"/>
<p>Runs all test cases in the specified suite.</p>
@@ -1261,7 +1261,7 @@
</func>
<func>
- <name>run(TestDir, Suite, Cases) -&gt; Result</name>
+ <name since="">run(TestDir, Suite, Cases) -&gt; Result</name>
<fsummary>Runs the specified test cases.</fsummary>
<type>
<v>TestDir = string()</v>
@@ -1283,7 +1283,7 @@
</func>
<func>
- <name>run_test(Opts) -&gt; Result</name>
+ <name since="">run_test(Opts) -&gt; Result</name>
<fsummary>Runs tests as specified by the combination of options in
Opts.</fsummary>
<type>
@@ -1355,7 +1355,7 @@
</func>
<func>
- <name>run_testspec(TestSpec) -&gt; Result</name>
+ <name since="">run_testspec(TestSpec) -&gt; Result</name>
<fsummary>Runs a test specified by TestSpec.</fsummary>
<type>
<v>TestSpec = [term()]</v>
@@ -1375,7 +1375,7 @@
</func>
<func>
- <name>set_verbosity(Category, Level) -&gt; ok</name>
+ <name since="OTP 19.1">set_verbosity(Category, Level) -&gt; ok</name>
<fsummary>Set the verbosity level for a logging category.</fsummary>
<type>
<v>Category = default | atom()</v>
@@ -1390,7 +1390,7 @@
</func>
<func>
- <name>sleep(Time) -&gt; ok</name>
+ <name since="OTP R14B">sleep(Time) -&gt; ok</name>
<fsummary>This function, similar to timer:sleep/1, suspends the
test case for a specified time.</fsummary>
<type>
@@ -1412,7 +1412,7 @@
</func>
<func>
- <name>start_interactive() -&gt; ok</name>
+ <name since="">start_interactive() -&gt; ok</name>
<fsummary>Starts <c>Common Test</c> in interactive mode.</fsummary>
<desc><marker id="start_interactive-0"/>
<p>Starts <c>Common Test</c> in interactive mode.</p>
@@ -1440,7 +1440,7 @@
</func>
<func>
- <name>step(TestDir, Suite, Case) -&gt; Result</name>
+ <name since="">step(TestDir, Suite, Case) -&gt; Result</name>
<fsummary>Steps through a test case with the debugger.</fsummary>
<type>
<v>Case = atom()</v>
@@ -1453,7 +1453,7 @@
</func>
<func>
- <name>step(TestDir, Suite, Case, Opts) -&gt; Result</name>
+ <name since="">step(TestDir, Suite, Case, Opts) -&gt; Result</name>
<fsummary>Steps through a test case with the debugger.</fsummary>
<type>
<v>Case = atom()</v>
@@ -1470,7 +1470,7 @@
</func>
<func>
- <name>stop_interactive() -&gt; ok</name>
+ <name since="">stop_interactive() -&gt; ok</name>
<fsummary>Exits the interactive mode.</fsummary>
<desc><marker id="stop_interactive-0"/>
<p>Exits the interactive mode.</p>
@@ -1482,7 +1482,7 @@
</func>
<func>
- <name>sync_notify(Name, Data) -&gt; ok</name>
+ <name since="OTP R15B02">sync_notify(Name, Data) -&gt; ok</name>
<fsummary>Sends a synchronous notification of type Name with Data to
the <c>Common Test</c> event manager.</fsummary>
<type>
@@ -1501,7 +1501,7 @@
</func>
<func>
- <name>testcases(TestDir, Suite) -&gt; Testcases | {error, Reason}</name>
+ <name since="">testcases(TestDir, Suite) -&gt; Testcases | {error, Reason}</name>
<fsummary>Returns all test cases in the specified suite.</fsummary>
<type>
<v>TestDir = string()</v>
@@ -1515,7 +1515,7 @@
</func>
<func>
- <name>timetrap(Time) -&gt; ok</name>
+ <name since="OTP R14B">timetrap(Time) -&gt; ok</name>
<fsummary>Sets a new timetrap for the running test case.</fsummary>
<type>
<v>Time = {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity | Func</v>
@@ -1539,7 +1539,7 @@
</func>
<func>
- <name>userdata(TestDir, Suite) -&gt; SuiteUserData | {error, Reason}</name>
+ <name since="">userdata(TestDir, Suite) -&gt; SuiteUserData | {error, Reason}</name>
<fsummary>Returns any data specified with tag userdata in the list of
tuples returned from Suite:suite/0.</fsummary>
<type>
@@ -1556,7 +1556,7 @@
</func>
<func>
- <name>userdata(TestDir, Suite, Case::GroupOrCase) -&gt; TCUserData | {error, Reason}</name>
+ <name since="">userdata(TestDir, Suite, Case::GroupOrCase) -&gt; TCUserData | {error, Reason}</name>
<fsummary>Returns any data specified with tag userdata in the list of
tuples returned from Suite:group(GroupName) or Suite:Case().</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_cover.xml b/lib/common_test/doc/src/ct_cover.xml
index 89d944acbe..61365d3522 100644
--- a/lib/common_test/doc/src/ct_cover.xml
+++ b/lib/common_test/doc/src/ct_cover.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_cover.xml</file>
</header>
- <module>ct_cover</module>
+ <module since="">ct_cover</module>
<modulesummary>Common Test framework code coverage support module.
</modulesummary>
@@ -47,7 +47,7 @@
<funcs>
<func>
- <name>add_nodes(Nodes) -&gt; {ok, StartedNodes} | {error, Reason}</name>
+ <name since="">add_nodes(Nodes) -&gt; {ok, StartedNodes} | {error, Reason}</name>
<fsummary>Adds nodes to current cover test (only works if cover support
is active).</fsummary>
<type>
@@ -67,7 +67,7 @@
</func>
<func>
- <name>cross_cover_analyse(Level, Tests) -&gt; ok</name>
+ <name since="OTP R16B">cross_cover_analyse(Level, Tests) -&gt; ok</name>
<fsummary>Accumulates cover results over multiple tests.</fsummary>
<type>
<v>Level = overview | details</v>
@@ -83,7 +83,7 @@
</func>
<func>
- <name>remove_nodes(Nodes) -&gt; ok | {error, Reason}</name>
+ <name since="">remove_nodes(Nodes) -&gt; ok | {error, Reason}</name>
<fsummary>Removes nodes from the current cover test.</fsummary>
<type>
<v>Nodes = [atom()]</v>
diff --git a/lib/common_test/doc/src/ct_ftp.xml b/lib/common_test/doc/src/ct_ftp.xml
index 592c5eb05d..7ee6049486 100644
--- a/lib/common_test/doc/src/ct_ftp.xml
+++ b/lib/common_test/doc/src/ct_ftp.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_ftp.xml</file>
</header>
- <module>ct_ftp</module>
+ <module since="">ct_ftp</module>
<modulesummary>FTP client module (based on the FTP application).</modulesummary>
<description>
@@ -59,7 +59,7 @@
<funcs>
<func>
- <name>cd(Connection, Dir) -&gt; ok | {error, Reason}</name>
+ <name since="">cd(Connection, Dir) -&gt; ok | {error, Reason}</name>
<fsummary>Changes directory on remote host.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -71,7 +71,7 @@
</func>
<func>
- <name>close(Connection) -&gt; ok | {error, Reason}</name>
+ <name since="">close(Connection) -&gt; ok | {error, Reason}</name>
<fsummary>Closes the FTP connection.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -82,7 +82,7 @@
</func>
<func>
- <name>delete(Connection, File) -&gt; ok | {error, Reason}</name>
+ <name since="">delete(Connection, File) -&gt; ok | {error, Reason}</name>
<fsummary>Deletes a file on remote host.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -94,7 +94,7 @@
</func>
<func>
- <name>get(KeyOrName, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
+ <name since="">get(KeyOrName, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
<fsummary>Opens an FTP connection and fetches a file from the remote
host.</fsummary>
<type>
@@ -122,7 +122,7 @@
</func>
<func>
- <name>ls(Connection, Dir) -&gt; {ok, Listing} | {error, Reason}</name>
+ <name since="">ls(Connection, Dir) -&gt; {ok, Listing} | {error, Reason}</name>
<fsummary>Lists directory Dir.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -135,7 +135,7 @@
</func>
<func>
- <name>open(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens an FTP connection to the specified node.</fsummary>
<type>
<v>KeyOrName = Key | Name</v>
@@ -164,7 +164,7 @@
</func>
<func>
- <name>put(KeyOrName, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
+ <name since="">put(KeyOrName, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
<fsummary>Opens an FTP connection and sends a file to the remote
host.</fsummary>
<type>
@@ -203,7 +203,7 @@
</func>
<func>
- <name>recv(Connection, RemoteFile) -&gt; ok | {error, Reason}</name>
+ <name since="">recv(Connection, RemoteFile) -&gt; ok | {error, Reason}</name>
<fsummary>Fetches a file over FTP.</fsummary>
<desc><marker id="recv-2"/>
<p>Fetches a file over FTP.</p>
@@ -215,7 +215,7 @@
</func>
<func>
- <name>recv(Connection, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
+ <name since="">recv(Connection, RemoteFile, LocalFile) -&gt; ok | {error, Reason}</name>
<fsummary>Fetches a file over FTP.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -230,7 +230,7 @@
</func>
<func>
- <name>send(Connection, LocalFile) -&gt; ok | {error, Reason}</name>
+ <name since="">send(Connection, LocalFile) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a file over FTP.</fsummary>
<desc><marker id="send-2"/>
<p>Sends a file over FTP.</p>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>send(Connection, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
+ <name since="">send(Connection, LocalFile, RemoteFile) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a file over FTP.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -258,7 +258,7 @@
</func>
<func>
- <name>type(Connection, Type) -&gt; ok | {error, Reason}</name>
+ <name since="">type(Connection, Type) -&gt; ok | {error, Reason}</name>
<fsummary>Changes the file transfer type.</fsummary>
<type>
<v>Connection = connection()</v>
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index 2f853d133d..ff0d0117cd 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>ct_hooks.sgml</file>
</header>
- <module>ct_hooks</module>
+ <module since="OTP R14B02">ct_hooks</module>
<modulesummary>A callback interface on top of Common Test.</modulesummary>
<description>
@@ -75,7 +75,7 @@
<funcs>
<func>
- <name>Module:init(Id, Opts) -&gt; {ok, State} | {ok, State, Priority}</name>
+ <name since="OTP R14B02">Module:init(Id, Opts) -&gt; {ok, State} | {ok, State, Priority}</name>
<fsummary>Initiates the Common Test Hook.</fsummary>
<type>
<v>Id = reference() | term()</v>
@@ -109,7 +109,7 @@
</func>
<func>
- <name>Module:pre_init_per_suite(SuiteName, InitData, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:pre_init_per_suite(SuiteName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -161,7 +161,7 @@
</func>
<func>
- <name>Module:post_init_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:post_init_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after init_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -208,7 +208,7 @@
</func>
<func>
- <name>Module:pre_init_per_group(SuiteName, GroupName, InitData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_init_per_group(SuiteName, GroupName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -241,7 +241,7 @@
</func>
<func>
- <name>Module:post_init_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_init_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after init_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -274,7 +274,7 @@
</func>
<func>
- <name>Module:pre_init_per_testcase(SuiteName, TestcaseName, InitData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_init_per_testcase(SuiteName, TestcaseName, InitData, CTHState) -&gt; Result</name>
<fsummary>Called before init_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -311,7 +311,7 @@
</func>
<func>
- <name>Module:post_init_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_init_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after init_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -344,7 +344,7 @@
</func>
<func>
- <name>Module:pre_end_per_testcase(SuiteName, TestcaseName, EndData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_end_per_testcase(SuiteName, TestcaseName, EndData, CTHState) -&gt; Result</name>
<fsummary>Called before end_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -380,7 +380,7 @@
</func>
<func>
- <name>Module:post_end_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_end_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after end_per_testcase.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -413,7 +413,7 @@
</func>
<func>
- <name>Module:pre_end_per_group(SuiteName, GroupName, EndData, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:pre_end_per_group(SuiteName, GroupName, EndData, CTHState) -&gt; Result</name>
<fsummary>Called before end_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -446,7 +446,7 @@
</func>
<func>
- <name>Module:post_end_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP 19.3">Module:post_end_per_group(SuiteName, GroupName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after end_per_group.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -479,7 +479,7 @@
</func>
<func>
- <name>Module:pre_end_per_suite(SuiteName, EndData, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:pre_end_per_suite(SuiteName, EndData, CTHState) -&gt; Result</name>
<fsummary>Called before end_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -506,7 +506,7 @@
</func>
<func>
- <name>Module:post_end_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
+ <name since="OTP R14B02">Module:post_end_per_suite(SuiteName, Config, Return, CTHState) -&gt; Result</name>
<fsummary>Called after end_per_suite.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -533,7 +533,7 @@
</func>
<func>
- <name>Module:on_tc_fail(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
+ <name since="OTP 19.3">Module:on_tc_fail(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
<fsummary>Called after the CTH scope ends.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -577,7 +577,7 @@
</func>
<func>
- <name>Module:on_tc_skip(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
+ <name since="OTP 19.3">Module:on_tc_skip(SuiteName, TestName, Reason, CTHState) -&gt; NewCTHState</name>
<fsummary>Called after the CTH scope ends.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -623,7 +623,7 @@
</func>
<func>
- <name>Module:terminate(CTHState)</name>
+ <name since="OTP R14B02">Module:terminate(CTHState)</name>
<fsummary>Called after the CTH scope ends.</fsummary>
<type>
<v>CTHState = term()</v>
@@ -637,7 +637,7 @@
</func>
<func>
- <name>Module:id(Opts) -&gt; Id</name>
+ <name since="OTP R14B02">Module:id(Opts) -&gt; Id</name>
<fsummary>Called before the init function of a CTH.</fsummary>
<type>
<v>Opts = term()</v>
diff --git a/lib/common_test/doc/src/ct_master.xml b/lib/common_test/doc/src/ct_master.xml
index 6bde4644c6..2ab421fe9e 100644
--- a/lib/common_test/doc/src/ct_master.xml
+++ b/lib/common_test/doc/src/ct_master.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_master.xml</file>
</header>
- <module>ct_master</module>
+ <module since="">ct_master</module>
<modulesummary>Distributed test execution control for Common Test.</modulesummary>
<description>
@@ -46,7 +46,7 @@
<funcs>
<func>
- <name>abort() -&gt; ok</name>
+ <name since="">abort() -&gt; ok</name>
<fsummary>Stops all running tests.</fsummary>
<desc><marker id="abort-0"/>
<p>Stops all running tests.</p>
@@ -54,7 +54,7 @@
</func>
<func>
- <name>abort(Nodes) -&gt; ok</name>
+ <name since="">abort(Nodes) -&gt; ok</name>
<fsummary>Stops tests on specified nodes.</fsummary>
<type>
<v>Nodes = atom() | [atom()]</v>
@@ -65,7 +65,7 @@
</func>
<func>
- <name>basic_html(Bool) -&gt; ok</name>
+ <name since="OTP R15B01">basic_html(Bool) -&gt; ok</name>
<fsummary>If set to true, the ct_master logs are written on a primitive
HTML format, not using the <c>Common Test</c> CSS style sheet.</fsummary>
<type>
@@ -79,7 +79,7 @@
</func>
<func>
- <name>get_event_mgr_ref() -&gt; MasterEvMgrRef</name>
+ <name since="OTP 17.5">get_event_mgr_ref() -&gt; MasterEvMgrRef</name>
<fsummary>Gets a reference to the <c>Common Test</c> master event
manager.</fsummary>
<type>
@@ -98,7 +98,7 @@
</func>
<func>
- <name>progress() -&gt; [{Node, Status}]</name>
+ <name since="">progress() -&gt; [{Node, Status}]</name>
<fsummary>Returns test progress.</fsummary>
<type>
<v>Node = atom()</v>
@@ -112,7 +112,7 @@
</func>
<func>
- <name>run(TestSpecs) -&gt; ok</name>
+ <name since="">run(TestSpecs) -&gt; ok</name>
<fsummary>Equivalent to run(TestSpecs, false, [], []).</fsummary>
<type>
<v>TestSpecs = string() | [SeparateOrMerged]</v>
@@ -124,7 +124,7 @@
</func>
<func>
- <name>run(TestSpecs, InclNodes, ExclNodes) -&gt; ok</name>
+ <name since="">run(TestSpecs, InclNodes, ExclNodes) -&gt; ok</name>
<fsummary>Equivalent to run(TestSpecs, false, InclNodes, ExclNodes).
</fsummary>
<type>
@@ -140,7 +140,7 @@
</func>
<func>
- <name>run(TestSpecs, AllowUserTerms, InclNodes, ExclNodes) -&gt; ok</name>
+ <name since="">run(TestSpecs, AllowUserTerms, InclNodes, ExclNodes) -&gt; ok</name>
<fsummary>Tests are spawned on the nodes as specified in TestSpecs.
</fsummary>
<type>
@@ -162,7 +162,7 @@
</func>
<func>
- <name>run_on_node(TestSpecs, Node) -&gt; ok</name>
+ <name since="">run_on_node(TestSpecs, Node) -&gt; ok</name>
<fsummary>Equivalent to run_on_node(TestSpecs, false, Node).</fsummary>
<type>
<v>TestSpecs = string() | [SeparateOrMerged]</v>
@@ -177,7 +177,7 @@
</func>
<func>
- <name>run_on_node(TestSpecs, AllowUserTerms, Node) -&gt; ok</name>
+ <name since="">run_on_node(TestSpecs, AllowUserTerms, Node) -&gt; ok</name>
<fsummary>Tests are spawned on Node according to TestSpecs.</fsummary>
<type>
<v>TestSpecs = string() | [SeparateOrMerged]</v>
@@ -191,7 +191,7 @@
</func>
<func>
- <name>run_test(Node, Opts) -&gt; ok</name>
+ <name since="">run_test(Node, Opts) -&gt; ok</name>
<fsummary>Tests are spawned on Node using ct:run_test/1.</fsummary>
<type>
<v>Node = atom()</v>
diff --git a/lib/common_test/doc/src/ct_netconfc.xml b/lib/common_test/doc/src/ct_netconfc.xml
index 7ec8f23073..8fbe5f3df6 100644
--- a/lib/common_test/doc/src/ct_netconfc.xml
+++ b/lib/common_test/doc/src/ct_netconfc.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_netconfc.xml</file>
</header>
- <module>ct_netconfc</module>
+ <module since="OTP R15B02">ct_netconfc</module>
<modulesummary>NETCONF client module.</modulesummary>
<description>
@@ -312,8 +312,8 @@
<funcs>
<func>
- <name name="action" arity="2"/>
- <name name="action" arity="3"/>
+ <name name="action" arity="2" since="OTP R15B02"/>
+ <name name="action" arity="3" since="OTP R15B02"/>
<fsummary>Executes an action.</fsummary>
<desc>
<p>Executes an action. If the return type is void, <c>ok</c> is
@@ -322,8 +322,8 @@
</func>
<func>
- <name name="close_session" arity="1"/>
- <name name="close_session" arity="2"/>
+ <name name="close_session" arity="1" since="OTP R15B02"/>
+ <name name="close_session" arity="2" since="OTP R15B02"/>
<fsummary>Requests graceful termination of the session associated with
the client.</fsummary>
<desc>
@@ -339,7 +339,7 @@
</func>
<func>
- <name name="connect" arity="1"/>
+ <name name="connect" arity="1" since="OTP 20.0"/>
<fsummary>Opens an SSH connection to a NETCONF server.</fsummary>
<desc>
<p>Opens an SSH connection to a NETCONF server.</p>
@@ -361,7 +361,7 @@
</func>
<func>
- <name name="connect" arity="2"/>
+ <name name="connect" arity="2" since="OTP 20.0"/>
<fsummary>Opens an SSH connection to a named NETCONF server.</fsummary>
<desc>
<p>Open an SSH connection to a named NETCONF server.</p>
@@ -399,8 +399,8 @@
</func>
<func>
- <name name="copy_config" arity="3"/>
- <name name="copy_config" arity="4"/>
+ <name name="copy_config" arity="3" since="OTP R15B02"/>
+ <name name="copy_config" arity="4" since="OTP R15B02"/>
<fsummary>Copies configuration data.</fsummary>
<desc>
<p>Copies configuration data.</p>
@@ -412,12 +412,12 @@
</func>
<func>
- <name>create_subscription(Client) -> Result</name>
- <name>create_subscription(Client, Stream) -> Result</name>
- <name>create_subscription(Client, Stream, Filter) -> Result</name>
- <name>create_subscription(Client, Stream, Filter, Timeout) -> Result</name>
- <name name="create_subscription" arity="5" clause_i="2"/>
- <name name="create_subscription" arity="6"/>
+ <name since="OTP R15B02">create_subscription(Client) -> Result</name>
+ <name since="OTP R15B02">create_subscription(Client, Stream) -> Result</name>
+ <name since="OTP R15B02">create_subscription(Client, Stream, Filter) -> Result</name>
+ <name since="OTP R15B02">create_subscription(Client, Stream, Filter, Timeout) -> Result</name>
+ <name name="create_subscription" arity="5" clause_i="2" since="OTP R15B02"/>
+ <name name="create_subscription" arity="6" since="OTP R15B02"/>
<fsummary>Creates a subscription for event notifications.</fsummary>
<desc>
<p>Creates a subscription for event notifications.</p>
@@ -490,8 +490,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="delete_config" arity="2"/>
- <name name="delete_config" arity="3"/>
+ <name name="delete_config" arity="2" since="OTP R15B02"/>
+ <name name="delete_config" arity="3" since="OTP R15B02"/>
<fsummary>Deletes configuration data.</fsummary>
<desc>
<p>Deletes configuration data.</p>
@@ -502,7 +502,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="disconnect" arity="1"/>
+ <name name="disconnect" arity="1" since="OTP 20.0"/>
<fsummary>Closes the given SSH connection.</fsummary>
<desc>
<p>Closes the given SSH connection.</p>
@@ -514,10 +514,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="edit_config" arity="3"/>
- <name name="edit_config" arity="4" clause_i="1"/>
- <name name="edit_config" arity="4" clause_i="2"/>
- <name name="edit_config" arity="5"/>
+ <name name="edit_config" arity="3" since="OTP R15B02"/>
+ <name name="edit_config" arity="4" clause_i="1" since="OTP 18.0"/>
+ <name name="edit_config" arity="4" clause_i="2" since="OTP R15B02"/>
+ <name name="edit_config" arity="5" since="OTP 18.0"/>
<fsummary>Edits configuration data.</fsummary>
<desc>
<p>Edits configuration data.</p>
@@ -542,8 +542,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get" arity="2"/>
- <name name="get" arity="3"/>
+ <name name="get" arity="2" since="OTP R15B02"/>
+ <name name="get" arity="3" since="OTP R15B02"/>
<fsummary>Gets data.</fsummary>
<desc>
<p>Gets data.</p>
@@ -557,8 +557,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_capabilities" arity="1"/>
- <name name="get_capabilities" arity="2"/>
+ <name name="get_capabilities" arity="1" since="OTP R15B02"/>
+ <name name="get_capabilities" arity="2" since="OTP R15B02"/>
<fsummary>Returns the server side capabilities.</fsummary>
<desc>
<p>Returns the server side capabilities.</p>
@@ -582,8 +582,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_config" arity="3"/>
- <name name="get_config" arity="4"/>
+ <name name="get_config" arity="3" since="OTP R15B02"/>
+ <name name="get_config" arity="4" since="OTP R15B02"/>
<fsummary>Gets configuration data.</fsummary>
<desc>
<p>Gets configuration data.</p>
@@ -597,10 +597,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_event_streams" arity="1"/>
- <name name="get_event_streams" arity="2" clause_i="1"/>
- <name name="get_event_streams" arity="2" clause_i="2"/>
- <name name="get_event_streams" arity="3"/>
+ <name name="get_event_streams" arity="1" since="OTP 20.0"/>
+ <name name="get_event_streams" arity="2" clause_i="1" since="OTP R15B02"/>
+ <name name="get_event_streams" arity="2" clause_i="2" since="OTP 20.0"/>
+ <name name="get_event_streams" arity="3" since="OTP R15B02"/>
<fsummary>Sends a request to get the specified event streams.</fsummary>
<desc>
<p>Sends a request to get the specified event streams.</p>
@@ -637,8 +637,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="get_session_id" arity="1"/>
- <name name="get_session_id" arity="2"/>
+ <name name="get_session_id" arity="1" since="OTP R15B02"/>
+ <name name="get_session_id" arity="2" since="OTP R15B02"/>
<fsummary>Returns the session Id associated with the specified
client.</fsummary>
<desc>
@@ -647,9 +647,9 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="hello" arity="1"/>
- <name name="hello" arity="2"/>
- <name name="hello" arity="3"/>
+ <name name="hello" arity="1" since="OTP R15B02"/>
+ <name name="hello" arity="2" since="OTP R15B02"/>
+ <name name="hello" arity="3" since="OTP 17.5.3"/>
<fsummary>Exchanges hello messages with the server.</fsummary>
<desc>
<p>Exchanges <c>hello</c> messages with the server.</p>
@@ -660,8 +660,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="kill_session" arity="2"/>
- <name name="kill_session" arity="3"/>
+ <name name="kill_session" arity="2" since="OTP R15B02"/>
+ <name name="kill_session" arity="3" since="OTP R15B02"/>
<fsummary>Forces termination of the session associated with the supplied
session Id.</fsummary>
<desc>
@@ -682,8 +682,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="lock" arity="2"/>
- <name name="lock" arity="3"/>
+ <name name="lock" arity="2" since="OTP R15B02"/>
+ <name name="lock" arity="3" since="OTP R15B02"/>
<fsummary>Locks the configuration target.</fsummary>
<desc>
<p>Locks the configuration target.</p>
@@ -703,7 +703,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="only_open" arity="1"/>
+ <name name="only_open" arity="1" since="OTP R15B02"/>
<fsummary>Opens a NETCONF session, but does not send hello.</fsummary>
<desc>
<p>Opens a NETCONF session, but does not send <c>hello</c>.</p>
@@ -714,7 +714,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="only_open" arity="2"/>
+ <name name="only_open" arity="2" since="OTP R15B02"/>
<fsummary>Opens a named NETCONF session, but does not send hello.</fsummary>
<desc>
<p>Opens a named NETCONF session, but does not send <c>hello</c>.</p>
@@ -725,7 +725,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since="OTP R15B02"/>
<fsummary>Opens a NETCONF session and exchanges hello messages.</fsummary>
<desc>
<p>Opens a NETCONF session and exchanges <c>hello</c> messages.</p>
@@ -749,7 +749,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="open" arity="2"/>
+ <name name="open" arity="2" since="OTP R15B02"/>
<fsummary>Opens a named NETCONF session and exchanges hello
messages.</fsummary>
<desc>
@@ -791,8 +791,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="send" arity="2"/>
- <name name="send" arity="3"/>
+ <name name="send" arity="2" since="OTP R16B02"/>
+ <name name="send" arity="3" since="OTP R16B02"/>
<fsummary>Sends an XML document to the server.</fsummary>
<desc>
<p>Sends an XML document to the server.</p>
@@ -804,8 +804,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="send_rpc" arity="2"/>
- <name name="send_rpc" arity="3"/>
+ <name name="send_rpc" arity="2" since="OTP R16B02"/>
+ <name name="send_rpc" arity="3" since="OTP R16B02"/>
<fsummary>Sends a NETCONF rpc request to the server.</fsummary>
<desc>
<p>Sends a NETCONF <c>rpc</c> request to the server.</p>
@@ -820,10 +820,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="session" arity="1"/>
- <name name="session" arity="2" clause_i="1"/>
- <name name="session" arity="2" clause_i="2"/>
- <name name="session" arity="3"/>
+ <name name="session" arity="1" since="OTP 20.0"/>
+ <name name="session" arity="2" clause_i="1" since="OTP 20.0"/>
+ <name name="session" arity="2" clause_i="2" since="OTP 20.0"/>
+ <name name="session" arity="3" since="OTP 20.0"/>
<fsummary>Opens a NETCONF session as a channel on the given SSH
connection, and exchanges hello messages with the
server.</fsummary>
@@ -848,8 +848,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre>
</func>
<func>
- <name name="unlock" arity="2"/>
- <name name="unlock" arity="3"/>
+ <name name="unlock" arity="2" since="OTP R15B02"/>
+ <name name="unlock" arity="3" since="OTP R15B02"/>
<fsummary>Unlocks the configuration target.</fsummary>
<desc>
<p>Unlocks the configuration target.</p>
diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml
index 028e5eb69f..1e01d9a5d7 100644
--- a/lib/common_test/doc/src/ct_property_test.xml
+++ b/lib/common_test/doc/src/ct_property_test.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_property_test.xml</file>
</header>
- <module>ct_property_test</module>
+ <module since="OTP 17.3">ct_property_test</module>
<modulesummary>EXPERIMENTAL support in Common Test for calling
property-based tests.</modulesummary>
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name>init_per_suite(Config) -&gt; Config | {skip, Reason}</name>
+ <name since="OTP 17.3">init_per_suite(Config) -&gt; Config | {skip, Reason}</name>
<fsummary>Initializes Config for property testing.</fsummary>
<desc><marker id="init_per_suite-1"/>
<p>Initializes <c>Config</c> for property testing.</p>
@@ -98,7 +98,7 @@
</func>
<func>
- <name>quickcheck(Property, Config) -&gt; true | {fail, Reason}</name>
+ <name since="OTP 17.3">quickcheck(Property, Config) -&gt; true | {fail, Reason}</name>
<fsummary>Calls quickcheck and returns the result in a form suitable for
Common Test.</fsummary>
<desc><marker id="quickcheck-2"/>
diff --git a/lib/common_test/doc/src/ct_rpc.xml b/lib/common_test/doc/src/ct_rpc.xml
index 90e6b833f7..00a4dcec08 100644
--- a/lib/common_test/doc/src/ct_rpc.xml
+++ b/lib/common_test/doc/src/ct_rpc.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_rpc.xml</file>
</header>
- <module>ct_rpc</module>
+ <module since="">ct_rpc</module>
<modulesummary>Common Test specific layer on Erlang/OTP rpc.</modulesummary>
<description>
@@ -43,7 +43,7 @@
<funcs>
<func>
- <name>app_node(App, Candidates) -&gt; NodeName</name>
+ <name since="">app_node(App, Candidates) -&gt; NodeName</name>
<fsummary>From a set of candidate nodes determines which of them is
running the application App.</fsummary>
<type>
@@ -61,7 +61,7 @@
</func>
<func>
- <name>app_node(App, Candidates, FailOnBadRPC) -&gt; NodeName</name>
+ <name since="">app_node(App, Candidates, FailOnBadRPC) -&gt; NodeName</name>
<fsummary>Same as app_node/2, except that argument FailOnBadRPC
determines if the search for a candidate node is to stop if
badrpc is received at some point.</fsummary>
@@ -81,7 +81,7 @@
</func>
<func>
- <name>app_node(App, Candidates, FailOnBadRPC, Cookie) -&gt; NodeName</name>
+ <name since="">app_node(App, Candidates, FailOnBadRPC, Cookie) -&gt; NodeName</name>
<fsummary>Same as app_node/2, except that argument FailOnBadRPC
determines if the search for a candidate node is to stop if badrpc is
received at some point.</fsummary>
@@ -105,7 +105,7 @@
</func>
<func>
- <name>call(Node, Module, Function, Args) -&gt; term() | {badrpc, Reason}</name>
+ <name since="">call(Node, Module, Function, Args) -&gt; term() | {badrpc, Reason}</name>
<fsummary>Same as call(Node, Module, Function, Args, infinity).</fsummary>
<desc><marker id="call-4"/>
<p>Same as <c>call(Node, Module, Function, Args, infinity)</c>.</p>
@@ -113,7 +113,7 @@
</func>
<func>
- <name>call(Node, Module, Function, Args, TimeOut) -&gt; term() | {badrpc, Reason}</name>
+ <name since="">call(Node, Module, Function, Args, TimeOut) -&gt; term() | {badrpc, Reason}</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
@@ -136,7 +136,7 @@
</func>
<func>
- <name>call(Node, Module, Function, Args, TimeOut, Cookie) -&gt; term() | {badrpc, Reason}</name>
+ <name since="">call(Node, Module, Function, Args, TimeOut, Cookie) -&gt; term() | {badrpc, Reason}</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>cast(Node, Module, Function, Args) -&gt; ok</name>
+ <name since="">cast(Node, Module, Function, Args) -&gt; ok</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
@@ -187,7 +187,7 @@
</func>
<func>
- <name>cast(Node, Module, Function, Args, Cookie) -&gt; ok</name>
+ <name since="">cast(Node, Module, Function, Args, Cookie) -&gt; ok</name>
<fsummary>Evaluates apply(Module, Function, Args) on the node
Node.</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_slave.xml b/lib/common_test/doc/src/ct_slave.xml
index 9d9aa50051..84e619482d 100644
--- a/lib/common_test/doc/src/ct_slave.xml
+++ b/lib/common_test/doc/src/ct_slave.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_slave.xml</file>
</header>
- <module>ct_slave</module>
+ <module since="OTP R14B">ct_slave</module>
<modulesummary>Common Test framework functions for starting and stopping
nodes for Large-Scale Testing.</modulesummary>
@@ -50,7 +50,7 @@
<funcs>
<func>
- <name>start(Node) -&gt; Result</name>
+ <name since="OTP R14B">start(Node) -&gt; Result</name>
<fsummary>Starts an Erlang node with name Node on the local
host.</fsummary>
<type>
@@ -68,7 +68,7 @@
</func>
<func>
- <name>start(HostOrNode, NodeOrOpts) -&gt; Result</name>
+ <name since="OTP R14B">start(HostOrNode, NodeOrOpts) -&gt; Result</name>
<fsummary>Starts an Erlang node with default options on a specified
host, or on the local host with specified options.</fsummary>
<type>
@@ -90,7 +90,7 @@
</func>
<func>
- <name>start(Host, Node, Opts) -&gt; Result</name>
+ <name since="OTP R14B">start(Host, Node, Opts) -&gt; Result</name>
<fsummary>Starts an Erlang node with name Node on host Host as
specified by the combination of options in Opts.</fsummary>
<type>
@@ -184,7 +184,7 @@
</func>
<func>
- <name>stop(Node) -&gt; Result</name>
+ <name since="OTP R14B">stop(Node) -&gt; Result</name>
<fsummary>Stops the running Erlang node with name Node on the local
host.</fsummary>
<type>
@@ -199,7 +199,7 @@
</func>
<func>
- <name>stop(Host, Node) -&gt; Result</name>
+ <name since="OTP R14B">stop(Host, Node) -&gt; Result</name>
<fsummary>Stops the running Erlang node with name Node on host
Host.</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_snmp.xml b/lib/common_test/doc/src/ct_snmp.xml
index 0a5e52b16c..343781814a 100644
--- a/lib/common_test/doc/src/ct_snmp.xml
+++ b/lib/common_test/doc/src/ct_snmp.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_snmp.xml</file>
</header>
- <module>ct_snmp</module>
+ <module since="">ct_snmp</module>
<modulesummary>Common Test user interface module for the SNMP application.</modulesummary>
<description>
@@ -240,7 +240,7 @@
<funcs>
<func>
- <name>get_next_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
+ <name since="">get_next_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
<fsummary>Issues a synchronous SNMP get next request.</fsummary>
<type>
<v>Agent = agent_name()</v>
@@ -254,7 +254,7 @@
</func>
<func>
- <name>get_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
+ <name since="">get_values(Agent, Oids, MgrAgentConfName) -&gt; SnmpReply</name>
<fsummary>Issues a synchronous SNMP get request.</fsummary>
<type>
<v>Agent = agent_name()</v>
@@ -268,7 +268,7 @@
</func>
<func>
- <name>load_mibs(Mibs) -&gt; ok | {error, Reason}</name>
+ <name since="">load_mibs(Mibs) -&gt; ok | {error, Reason}</name>
<fsummary>Loads the MIBs into agent snmp_master_agent.</fsummary>
<type>
<v>Mibs = [MibName]</v>
@@ -281,7 +281,7 @@
</func>
<func>
- <name>register_agents(MgrAgentConfName, ManagedAgents) -&gt; ok | {error, Reason}</name>
+ <name since="">register_agents(MgrAgentConfName, ManagedAgents) -&gt; ok | {error, Reason}</name>
<fsummary>Explicitly instructs the manager to handle this
agent.</fsummary>
<type>
@@ -300,7 +300,7 @@
</func>
<func>
- <name>register_users(MgrAgentConfName, Users) -&gt; ok | {error, Reason}</name>
+ <name since="">register_users(MgrAgentConfName, Users) -&gt; ok | {error, Reason}</name>
<fsummary>Registers the manager entity (=user) responsible for specific
agent(s).</fsummary>
<type>
@@ -319,7 +319,7 @@
</func>
<func>
- <name>register_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok | {error, Reason}</name>
+ <name since="">register_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok | {error, Reason}</name>
<fsummary>Explicitly instructs the manager to handle this USM user.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -337,7 +337,7 @@
</func>
<func>
- <name>set_info(Config) -&gt; [{Agent, OldVarsAndVals, NewVarsAndVals}]</name>
+ <name since="">set_info(Config) -&gt; [{Agent, OldVarsAndVals, NewVarsAndVals}]</name>
<fsummary>Returns a list of all successful set requests performed in the
test case in reverse order.</fsummary>
<type>
@@ -357,7 +357,7 @@
</func>
<func>
- <name>set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -&gt; SnmpReply</name>
+ <name since="">set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -&gt; SnmpReply</name>
<fsummary>Issues a synchronous SNMP set request.</fsummary>
<type>
<v>Agent = agent_name()</v>
@@ -372,7 +372,7 @@
</func>
<func>
- <name>start(Config, MgrAgentConfName) -&gt; ok</name>
+ <name since="">start(Config, MgrAgentConfName) -&gt; ok</name>
<fsummary>Equivalent to start(Config, MgrAgentConfName,
undefined).</fsummary>
<desc><marker id="start-2"/>
@@ -383,7 +383,7 @@
</func>
<func>
- <name>start(Config, MgrAgentConfName, SnmpAppConfName) -&gt; ok</name>
+ <name since="">start(Config, MgrAgentConfName, SnmpAppConfName) -&gt; ok</name>
<fsummary>Starts an SNMP manager and/or agent.</fsummary>
<type>
<v>Config = [{Key, Value}]</v>
@@ -415,7 +415,7 @@
</func>
<func>
- <name>stop(Config) -&gt; ok</name>
+ <name since="">stop(Config) -&gt; ok</name>
<fsummary>Stops the SNMP manager and/or agent, and removes all files
created.</fsummary>
<type>
@@ -430,7 +430,7 @@
</func>
<func>
- <name>unload_mibs(Mibs) -&gt; ok | {error, Reason}</name>
+ <name since="OTP R16B">unload_mibs(Mibs) -&gt; ok | {error, Reason}</name>
<fsummary>Unloads the MIBs from agent snmp_master_agent.</fsummary>
<type>
<v>Mibs = [MibName]</v>
@@ -443,7 +443,7 @@
</func>
<func>
- <name>unregister_agents(MgrAgentConfName) -&gt; ok</name>
+ <name since="">unregister_agents(MgrAgentConfName) -&gt; ok</name>
<fsummary>Unregisters all managed agents.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -455,7 +455,7 @@
</func>
<func>
- <name>unregister_agents(MgrAgentConfName, ManagedAgents) -&gt; ok</name>
+ <name since="OTP R16B">unregister_agents(MgrAgentConfName, ManagedAgents) -&gt; ok</name>
<fsummary>Unregisters the specified managed agents.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -468,7 +468,7 @@
</func>
<func>
- <name>unregister_users(MgrAgentConfName) -&gt; ok</name>
+ <name since="">unregister_users(MgrAgentConfName) -&gt; ok</name>
<fsummary>Unregisters all users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -480,7 +480,7 @@
</func>
<func>
- <name>unregister_users(MgrAgentConfName, Users) -&gt; ok</name>
+ <name since="OTP R16B">unregister_users(MgrAgentConfName, Users) -&gt; ok</name>
<fsummary>Unregisters the specified users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -493,7 +493,7 @@
</func>
<func>
- <name>unregister_usm_users(MgrAgentConfName) -&gt; ok</name>
+ <name since="OTP R16B">unregister_usm_users(MgrAgentConfName) -&gt; ok</name>
<fsummary>Unregisters all USM users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
@@ -505,7 +505,7 @@
</func>
<func>
- <name>unregister_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok</name>
+ <name since="OTP R16B">unregister_usm_users(MgrAgentConfName, UsmUsers) -&gt; ok</name>
<fsummary>Unregisters the specified USM users.</fsummary>
<type>
<v>MgrAgentConfName = atom()</v>
diff --git a/lib/common_test/doc/src/ct_ssh.xml b/lib/common_test/doc/src/ct_ssh.xml
index 0c7efed154..8d9f31aff8 100644
--- a/lib/common_test/doc/src/ct_ssh.xml
+++ b/lib/common_test/doc/src/ct_ssh.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_ssh.xml</file>
</header>
- <module>ct_ssh</module>
+ <module since="">ct_ssh</module>
<modulesummary>SSH/SFTP client module.</modulesummary>
<description>
@@ -95,7 +95,7 @@
<funcs>
<func>
- <name>apread(SSH, Handle, Position, Length) -&gt; Result</name>
+ <name since="">apread(SSH, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -109,7 +109,7 @@
</func>
<func>
- <name>apread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
+ <name since="">apread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -123,7 +123,7 @@
</func>
<func>
- <name>apwrite(SSH, Handle, Position, Data) -&gt; Result</name>
+ <name since="">apwrite(SSH, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -137,7 +137,7 @@
</func>
<func>
- <name>apwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
+ <name since="">apwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name>aread(SSH, Handle, Len) -&gt; Result</name>
+ <name since="">aread(SSH, Handle, Len) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -165,7 +165,7 @@
</func>
<func>
- <name>aread(SSH, Server, Handle, Len) -&gt; Result</name>
+ <name since="">aread(SSH, Server, Handle, Len) -&gt; Result</name>
<fsummary>For inforamtion and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -179,7 +179,7 @@
</func>
<func>
- <name>awrite(SSH, Handle, Data) -&gt; Result</name>
+ <name since="">awrite(SSH, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -193,7 +193,7 @@
</func>
<func>
- <name>awrite(SSH, Server, Handle, Data) -&gt; Result</name>
+ <name since="">awrite(SSH, Server, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -207,7 +207,7 @@
</func>
<func>
- <name>close(SSH, Handle) -&gt; Result</name>
+ <name since="">close(SSH, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -221,7 +221,7 @@
</func>
<func>
- <name>close(SSH, Server, Handle) -&gt; Result</name>
+ <name since="">close(SSH, Server, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -235,7 +235,7 @@
</func>
<func>
- <name>connect(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">connect(KeyOrName) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to connect(KeyOrName, host, []).</fsummary>
<desc><marker id="connect-1"/>
<p>Equivalent to
@@ -245,7 +245,7 @@
</func>
<func>
- <name>connect(KeyOrName, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">connect(KeyOrName, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to connect(KeyOrName, ConnType, []).</fsummary>
<desc><marker id="connect-2"/>
<p>Equivalent to
@@ -255,7 +255,7 @@
</func>
<func>
- <name>connect(KeyOrName, ConnType, ExtraOpts) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">connect(KeyOrName, ConnType, ExtraOpts) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens an SSH or SFTP connection using the information
associated with KeyOrName.</fsummary>
<type>
@@ -301,7 +301,7 @@
</func>
<func>
- <name>del_dir(SSH, Name) -&gt; Result</name>
+ <name since="">del_dir(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -315,7 +315,7 @@
</func>
<func>
- <name>del_dir(SSH, Server, Name) -&gt; Result</name>
+ <name since="">del_dir(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -329,7 +329,7 @@
</func>
<func>
- <name>delete(SSH, Name) -&gt; Result</name>
+ <name since="">delete(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -343,7 +343,7 @@
</func>
<func>
- <name>delete(SSH, Server, Name) -&gt; Result</name>
+ <name since="">delete(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -357,7 +357,7 @@
</func>
<func>
- <name>disconnect(SSH) -&gt; ok | {error, Reason}</name>
+ <name since="">disconnect(SSH) -&gt; ok | {error, Reason}</name>
<fsummary>Closes an SSH/SFTP connection.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -369,7 +369,7 @@
</func>
<func>
- <name>exec(SSH, Command) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">exec(SSH, Command) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to exec(SSH, Command, DefaultTimeout).</fsummary>
<desc><marker id="exec-2"/>
<p>Equivalent to
@@ -379,7 +379,7 @@
</func>
<func>
- <name>exec(SSH, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">exec(SSH, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Requests server to perform Command.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -396,7 +396,7 @@
</func>
<func>
- <name>exec(SSH, ChannelId, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">exec(SSH, ChannelId, Command, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Requests server to perform Command.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -414,7 +414,7 @@
</func>
<func>
- <name>get_file_info(SSH, Handle) -&gt; Result</name>
+ <name since="">get_file_info(SSH, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -428,7 +428,7 @@
</func>
<func>
- <name>get_file_info(SSH, Server, Handle) -&gt; Result</name>
+ <name since="">get_file_info(SSH, Server, Handle) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -442,7 +442,7 @@
</func>
<func>
- <name>list_dir(SSH, Path) -&gt; Result</name>
+ <name since="">list_dir(SSH, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -456,7 +456,7 @@
</func>
<func>
- <name>list_dir(SSH, Server, Path) -&gt; Result</name>
+ <name since="">list_dir(SSH, Server, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -470,7 +470,7 @@
</func>
<func>
- <name>make_dir(SSH, Name) -&gt; Result</name>
+ <name since="">make_dir(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -484,7 +484,7 @@
</func>
<func>
- <name>make_dir(SSH, Server, Name) -&gt; Result</name>
+ <name since="">make_dir(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -498,7 +498,7 @@
</func>
<func>
- <name>make_symlink(SSH, Name, Target) -&gt; Result</name>
+ <name since="">make_symlink(SSH, Name, Target) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -512,7 +512,7 @@
</func>
<func>
- <name>make_symlink(SSH, Server, Name, Target) -&gt; Result</name>
+ <name since="">make_symlink(SSH, Server, Name, Target) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -526,7 +526,7 @@
</func>
<func>
- <name>open(SSH, File, Mode) -&gt; Result</name>
+ <name since="">open(SSH, File, Mode) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -540,7 +540,7 @@
</func>
<func>
- <name>open(SSH, Server, File, Mode) -&gt; Result</name>
+ <name since="">open(SSH, Server, File, Mode) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -554,7 +554,7 @@
</func>
<func>
- <name>opendir(SSH, Path) -&gt; Result</name>
+ <name since="">opendir(SSH, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -568,7 +568,7 @@
</func>
<func>
- <name>opendir(SSH, Server, Path) -&gt; Result</name>
+ <name since="">opendir(SSH, Server, Path) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -582,7 +582,7 @@
</func>
<func>
- <name>position(SSH, Handle, Location) -&gt; Result</name>
+ <name since="">position(SSH, Handle, Location) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -596,7 +596,7 @@
</func>
<func>
- <name>position(SSH, Server, Handle, Location) -&gt; Result</name>
+ <name since="">position(SSH, Server, Handle, Location) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -610,7 +610,7 @@
</func>
<func>
- <name>pread(SSH, Handle, Position, Length) -&gt; Result</name>
+ <name since="">pread(SSH, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -624,7 +624,7 @@
</func>
<func>
- <name>pread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
+ <name since="">pread(SSH, Server, Handle, Position, Length) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -638,7 +638,7 @@
</func>
<func>
- <name>pwrite(SSH, Handle, Position, Data) -&gt; Result</name>
+ <name since="">pwrite(SSH, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -652,7 +652,7 @@
</func>
<func>
- <name>pwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
+ <name since="">pwrite(SSH, Server, Handle, Position, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -666,7 +666,7 @@
</func>
<func>
- <name>read(SSH, Handle, Len) -&gt; Result</name>
+ <name since="">read(SSH, Handle, Len) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -680,7 +680,7 @@
</func>
<func>
- <name>read(SSH, Server, Handle, Len) -&gt; Result</name>
+ <name since="">read(SSH, Server, Handle, Len) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -694,7 +694,7 @@
</func>
<func>
- <name>read_file(SSH, File) -&gt; Result</name>
+ <name since="">read_file(SSH, File) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -708,7 +708,7 @@
</func>
<func>
- <name>read_file(SSH, Server, File) -&gt; Result</name>
+ <name since="">read_file(SSH, Server, File) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -722,7 +722,7 @@
</func>
<func>
- <name>read_file_info(SSH, Name) -&gt; Result</name>
+ <name since="">read_file_info(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -736,7 +736,7 @@
</func>
<func>
- <name>read_file_info(SSH, Server, Name) -&gt; Result</name>
+ <name since="">read_file_info(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -750,7 +750,7 @@
</func>
<func>
- <name>read_link(SSH, Name) -&gt; Result</name>
+ <name since="">read_link(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -764,7 +764,7 @@
</func>
<func>
- <name>read_link(SSH, Server, Name) -&gt; Result</name>
+ <name since="">read_link(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -778,7 +778,7 @@
</func>
<func>
- <name>read_link_info(SSH, Name) -&gt; Result</name>
+ <name since="">read_link_info(SSH, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -792,7 +792,7 @@
</func>
<func>
- <name>read_link_info(SSH, Server, Name) -&gt; Result</name>
+ <name since="">read_link_info(SSH, Server, Name) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -806,7 +806,7 @@
</func>
<func>
- <name>receive_response(SSH, ChannelId) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">receive_response(SSH, ChannelId) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to receive_response(SSH, ChannelId,
close).</fsummary>
<desc><marker id="receive_response-2"/>
@@ -817,7 +817,7 @@ ChannelId, close)</c></seealso>.</p>
</func>
<func>
- <name>receive_response(SSH, ChannelId, End) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">receive_response(SSH, ChannelId, End) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to receive_response(SSH, ChannelId, End,
DefaultTimeout).</fsummary>
<desc><marker id="receive_response-3"/>
@@ -828,7 +828,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>receive_response(SSH, ChannelId, End, Timeout) -&gt; {ok, Data} | {timeout, Data} | {error, Reason}</name>
+ <name since="">receive_response(SSH, ChannelId, End, Timeout) -&gt; {ok, Data} | {timeout, Data} | {error, Reason}</name>
<fsummary>Receives expected data from server on the specified session
channel.</fsummary>
<type>
@@ -863,7 +863,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>rename(SSH, OldName, NewName) -&gt; Result</name>
+ <name since="">rename(SSH, OldName, NewName) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -877,7 +877,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>rename(SSH, Server, OldName, NewName) -&gt; Result</name>
+ <name since="">rename(SSH, Server, OldName, NewName) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -891,7 +891,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send(SSH, ChannelId, Data) -&gt; ok | {error, Reason}</name>
+ <name since="">send(SSH, ChannelId, Data) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(SSH, ChannelId, 0, Data,
DefaultTimeout).</fsummary>
<desc><marker id="send-3"/>
@@ -901,7 +901,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send(SSH, ChannelId, Data, Timeout) -&gt; ok | {error, Reason}</name>
+ <name since="">send(SSH, ChannelId, Data, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(SSH, ChannelId, 0, Data, Timeout).</fsummary>
<desc><marker id="send-4"/>
<p>Equivalent to <seealso marker="#send-5"><c>ct_ssh:send(SSH,
@@ -910,7 +910,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send(SSH, ChannelId, Type, Data, Timeout) -&gt; ok | {error, Reason}</name>
+ <name since="">send(SSH, ChannelId, Type, Data, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Sends data to server on specified session channel.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -926,7 +926,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Data) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Data) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to send_and_receive(SSH, ChannelId, Data,
close).</fsummary>
<desc><marker id="send_and_receive-3"/>
@@ -937,7 +937,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Data, End) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Data, End) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to send_and_receive(SSH, ChannelId, 0, Data, End,
DefaultTimeout).</fsummary>
<desc><marker id="send_and_receive-4"/>
@@ -948,7 +948,7 @@ ChannelId, 0, Data, End, DefaultTimeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to send_and_receive(SSH, ChannelId, 0, Data, End,
Timeout).</fsummary>
<desc><marker id="send_and_receive-5"/>
@@ -959,7 +959,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Sends data to server on specified session channel and waits
to receive the server response.</fsummary>
<type>
@@ -981,7 +981,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>session_close(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
+ <name since="">session_close(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
<fsummary>Closes an SSH session channel.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -994,7 +994,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>session_open(SSH) -&gt; {ok, ChannelId} | {error, Reason}</name>
+ <name since="">session_open(SSH) -&gt; {ok, ChannelId} | {error, Reason}</name>
<fsummary>Equivalent to session_open(SSH, DefaultTimeout).</fsummary>
<desc><marker id="session_open-1"/>
<p>Equivalent to
@@ -1004,7 +1004,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>session_open(SSH, Timeout) -&gt; {ok, ChannelId} | {error, Reason}</name>
+ <name since="">session_open(SSH, Timeout) -&gt; {ok, ChannelId} | {error, Reason}</name>
<fsummary>Opens a channel for an SSH session.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1018,7 +1018,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>sftp_connect(SSH) -&gt; {ok, Server} | {error, Reason}</name>
+ <name since="">sftp_connect(SSH) -&gt; {ok, Server} | {error, Reason}</name>
<fsummary>Starts an SFTP session on an already existing SSH
connection.</fsummary>
<type>
@@ -1034,7 +1034,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>shell(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 20.0">shell(SSH, ChannelId) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to shell(SSH, ChannelId, DefaultTimeout).</fsummary>
<desc><marker id="shell-2"/>
<p>Equivalent to
@@ -1044,7 +1044,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>shell(SSH, ChannelId, Timeout) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 20.0">shell(SSH, ChannelId, Timeout) -&gt; ok | {error, Reason}</name>
<fsummary>Requests that the user default shell is executed at the
server end.</fsummary>
<type>
@@ -1061,7 +1061,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>subsystem(SSH, ChannelId, Subsystem) -&gt; Status | {error, Reason}</name>
+ <name since="">subsystem(SSH, ChannelId, Subsystem) -&gt; Status | {error, Reason}</name>
<fsummary>Equivalent to subsystem(SSH, ChannelId, Subsystem,
DefaultTimeout).</fsummary>
<desc><marker id="subsystem-3"/>
@@ -1072,7 +1072,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>subsystem(SSH, ChannelId, Subsystem, Timeout) -&gt; Status | {error, Reason}</name>
+ <name since="">subsystem(SSH, ChannelId, Subsystem, Timeout) -&gt; Status | {error, Reason}</name>
<fsummary>Sends a request to execute a predefined subsystem.</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1088,7 +1088,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write(SSH, Handle, Data) -&gt; Result</name>
+ <name since="">write(SSH, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1102,7 +1102,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write(SSH, Server, Handle, Data) -&gt; Result</name>
+ <name since="">write(SSH, Server, Handle, Data) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1116,7 +1116,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file(SSH, File, Iolist) -&gt; Result</name>
+ <name since="">write_file(SSH, File, Iolist) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1130,7 +1130,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file(SSH, Server, File, Iolist) -&gt; Result</name>
+ <name since="">write_file(SSH, Server, File, Iolist) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1144,7 +1144,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file_info(SSH, Name, Info) -&gt; Result</name>
+ <name since="">write_file_info(SSH, Name, Info) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
@@ -1158,7 +1158,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p>
</func>
<func>
- <name>write_file_info(SSH, Server, Name, Info) -&gt; Result</name>
+ <name since="">write_file_info(SSH, Server, Name, Info) -&gt; Result</name>
<fsummary>For information and other types, see ssh_sftp(3).</fsummary>
<type>
<v>SSH = connection()</v>
diff --git a/lib/common_test/doc/src/ct_telnet.xml b/lib/common_test/doc/src/ct_telnet.xml
index 8e85cccc99..9a12ce79ed 100644
--- a/lib/common_test/doc/src/ct_telnet.xml
+++ b/lib/common_test/doc/src/ct_telnet.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_telnet.xml</file>
</header>
- <module>ct_telnet</module>
+ <module since="">ct_telnet</module>
<modulesummary>Common Test specific layer on top of Telnet client ct_telnet_client.erl</modulesummary>
<description>
@@ -205,7 +205,7 @@
<funcs>
<func>
- <name>close(Connection) -&gt; ok | {error, Reason}</name>
+ <name since="">close(Connection) -&gt; ok | {error, Reason}</name>
<fsummary>Closes the Telnet connection and stops the process managing
it.</fsummary>
<type>
@@ -223,7 +223,7 @@
</func>
<func>
- <name>cmd(Connection, Cmd) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmd(Connection, Cmd) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to cmd(Connection, Cmd, []).</fsummary>
<desc><marker id="cmd-2"/>
<p>Equivalent to
@@ -233,7 +233,7 @@
</func>
<func>
- <name>cmd(Connection, Cmd, Opts) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmd(Connection, Cmd, Opts) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Sends a command through Telnet and waits for prompt.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -262,7 +262,7 @@
</func>
<func>
- <name>cmdf(Connection, CmdFormat, Args) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmdf(Connection, CmdFormat, Args) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Equivalent to cmdf(Connection, CmdFormat, Args, []).</fsummary>
<desc><marker id="cmdf-3"/>
<p>Equivalent to
@@ -272,7 +272,7 @@
</func>
<func>
- <name>cmdf(Connection, CmdFormat, Args, Opts) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">cmdf(Connection, CmdFormat, Args, Opts) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Sends a Telnet command and waits for prompt (uses a format
string and a list of arguments to build the command).</fsummary>
<type>
@@ -294,7 +294,7 @@
</func>
<func>
- <name>expect(Connection, Patterns) -&gt; term()</name>
+ <name since="">expect(Connection, Patterns) -&gt; term()</name>
<fsummary>Equivalent to expect(Connections, Patterns, []).</fsummary>
<desc><marker id="expect-2"/>
<p>Equivalent to
@@ -304,7 +304,7 @@
</func>
<func>
- <name>expect(Connection, Patterns, Opts) -&gt; {ok, Match} | {ok, MatchList, HaltReason} | {error, Reason}</name>
+ <name since="">expect(Connection, Patterns, Opts) -&gt; {ok, Match} | {ok, MatchList, HaltReason} | {error, Reason}</name>
<fsummary>Gets data from Telnet and waits for the expected
pattern.</fsummary>
<type>
@@ -422,7 +422,7 @@
</func>
<func>
- <name>get_data(Connection) -&gt; {ok, Data} | {error, Reason}</name>
+ <name since="">get_data(Connection) -&gt; {ok, Data} | {error, Reason}</name>
<fsummary>Gets all data received by the Telnet client since the last
command was sent.</fsummary>
<type>
@@ -446,7 +446,7 @@
</func>
<func>
- <name>open(Name) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(Name) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to open(Name, telnet).</fsummary>
<desc><marker id="open-1"/>
<p>Equivalent to
@@ -456,7 +456,7 @@
</func>
<func>
- <name>open(Name, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(Name, ConnType) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens a Telnet connection to the specified target
host.</fsummary>
<type>
@@ -471,7 +471,7 @@
</func>
<func>
- <name>open(KeyOrName, ConnType, TargetMod) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(KeyOrName, ConnType, TargetMod) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Equivalent to open(KeyOrName, ConnType, TargetMod, []).</fsummary>
<desc><marker id="open-3"/>
<p>Equivalent to
@@ -481,7 +481,7 @@
</func>
<func>
- <name>open(KeyOrName, ConnType, TargetMod, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="">open(KeyOrName, ConnType, TargetMod, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Opens a Telnet connection to the specified target
host.</fsummary>
<type>
@@ -531,7 +531,7 @@
</func>
<func>
- <name>send(Connection, Cmd) -&gt; ok | {error, Reason}</name>
+ <name since="">send(Connection, Cmd) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to send(Connection, Cmd, []).</fsummary>
<desc><marker id="send-2"/>
<p>Equivalent to
@@ -541,7 +541,7 @@
</func>
<func>
- <name>send(Connection, Cmd, Opts) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 17.4">send(Connection, Cmd, Opts) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a Telnet command and returns immediately.</fsummary>
<type>
<v>Connection = connection()</v>
@@ -566,7 +566,7 @@
</func>
<func>
- <name>sendf(Connection, CmdFormat, Args) -&gt; ok | {error, Reason}</name>
+ <name since="">sendf(Connection, CmdFormat, Args) -&gt; ok | {error, Reason}</name>
<fsummary>Equivalent to sendf(Connection, CmdFormat, Args, []).</fsummary>
<desc><marker id="sendf-3"/>
<p>Equivalent to
@@ -576,7 +576,7 @@
</func>
<func>
- <name>sendf(Connection, CmdFormat, Args, Opts) -&gt; ok | {error, Reason}</name>
+ <name since="OTP 17.4">sendf(Connection, CmdFormat, Args, Opts) -&gt; ok | {error, Reason}</name>
<fsummary>Sends a Telnet command and returns immediately (uses a format
string and a list of arguments to build the command).</fsummary>
<type>
diff --git a/lib/common_test/doc/src/ct_testspec.xml b/lib/common_test/doc/src/ct_testspec.xml
index 36893f66cf..9cb9a2ae9f 100644
--- a/lib/common_test/doc/src/ct_testspec.xml
+++ b/lib/common_test/doc/src/ct_testspec.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ct_testspec.xml</file>
</header>
- <module>ct_testspec</module>
+ <module since="OTP 19.3">ct_testspec</module>
<modulesummary>Parsing of test specifications for Common Test.
</modulesummary>
@@ -46,7 +46,7 @@
<funcs>
<func>
- <name>get_tests(SpecsIn) -&gt; {ok, [{Specs,Tests}]} | {error, Reason}</name>
+ <name since="OTP 19.3">get_tests(SpecsIn) -&gt; {ok, [{Specs,Tests}]} | {error, Reason}</name>
<fsummary>Parse the given test specification files and return the tests to run and skip.</fsummary>
<type>
<v>SpecsIn = [string()] | [[string()]]</v>
diff --git a/lib/common_test/doc/src/unix_telnet.xml b/lib/common_test/doc/src/unix_telnet.xml
index b2314a53ec..03d91b7dbe 100644
--- a/lib/common_test/doc/src/unix_telnet.xml
+++ b/lib/common_test/doc/src/unix_telnet.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>unix_telnet.xml</file>
</header>
- <module>unix_telnet</module>
+ <module since="">unix_telnet</module>
<modulesummary>Callback module for ct_telnet, for connecting to a Telnet
server on a UNIX host.</modulesummary>
@@ -80,7 +80,7 @@
<funcs>
<func>
- <name>connect(ConnName, Ip, Port, Timeout, KeepAlive, TCPNoDelay, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
+ <name since="OTP 18.3.3">connect(ConnName, Ip, Port, Timeout, KeepAlive, TCPNoDelay, Extra) -&gt; {ok, Handle} | {error, Reason}</name>
<fsummary>Callback for ct_telnet.erl.</fsummary>
<type>
<v>ConnName = target_name()</v>
@@ -107,7 +107,7 @@
</func>
<func>
- <name>get_prompt_regexp() -&gt; PromptRegexp</name>
+ <name since="">get_prompt_regexp() -&gt; PromptRegexp</name>
<fsummary>Callback for ct_telnet.erl.</fsummary>
<type>
<v>PromptRegexp = prompt_regexp()</v>
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index a10d939919..a07e61199b 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -592,7 +592,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) ->
encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
_ = crypto:start(),
- {Key,IVec} = make_crypto_key(Key),
+ {CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(SrcFileName) of
{ok,Bin0} ->
Bin1 = term_to_binary({SrcFileName,Bin0}),
@@ -600,7 +600,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
0 -> Bin1;
N -> list_to_binary([Bin1,random_bytes(8-N)])
end,
- EncBin = crypto:block_encrypt(des3_cbc, Key, IVec, Bin2),
+ EncBin = crypto:block_encrypt(des3_cbc, CryptoKey, IVec, Bin2),
case file:write_file(EncryptFileName, EncBin) of
ok ->
io:format("~ts --(encrypt)--> ~ts~n",
@@ -631,10 +631,10 @@ decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) ->
decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
_ = crypto:start(),
- {Key,IVec} = make_crypto_key(Key),
+ {CryptoKey,IVec} = make_crypto_key(Key),
case file:read_file(EncryptFileName) of
{ok,Bin} ->
- DecBin = crypto:block_decrypt(des3_cbc, Key, IVec, Bin),
+ DecBin = crypto:block_decrypt(des3_cbc, CryptoKey, IVec, Bin),
case catch binary_to_term(DecBin) of
{'EXIT',_} ->
{error,bad_file};
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 7e98e6395f..506147474f 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -314,8 +314,8 @@ add_defaults(Mod,Func, GroupPath) ->
ErrStr = io_lib:format("~n*** ERROR *** "
"~w:suite/0 failed: ~tp~n",
[Suite,Reason]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,{exited,Reason}};
SuiteInfo when is_list(SuiteInfo) ->
case lists:all(fun(E) when is_tuple(E) -> true;
@@ -337,16 +337,16 @@ add_defaults(Mod,Func, GroupPath) ->
"Invalid return value from "
"~w:suite/0: ~tp~n",
[Suite,SuiteInfo]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,bad_return_value}
end;
SuiteInfo ->
ErrStr = io_lib:format("~n*** ERROR *** "
"Invalid return value from "
"~w:suite/0: ~tp~n", [Suite,SuiteInfo]),
- io:format(ErrStr, []),
- io:format(?def_gl, ErrStr, []),
+ io:format("~ts", [ErrStr]),
+ io:format(?def_gl, "~ts", [ErrStr]),
{suite0_failed,bad_return_value}
end.
@@ -373,8 +373,8 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) ->
"Invalid return value from "
"~w:group(~tw): ~tp~n",
[Mod,GrName,BadGr0Val]),
- io:format(Gr0ErrStr, []),
- io:format(?def_gl, Gr0ErrStr, []),
+ io:format("~ts", [Gr0ErrStr]),
+ io:format(?def_gl, "~ts", [Gr0ErrStr]),
{group0_failed,bad_return_value};
_ ->
Args = if Func == init_per_group ; Func == end_per_group ->
@@ -395,8 +395,8 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) ->
"Invalid return value from "
"~w:~tw/0: ~tp~n",
[Mod,Func,BadTC0Val]),
- io:format(TC0ErrStr, []),
- io:format(?def_gl, TC0ErrStr, []),
+ io:format("~ts", [TC0ErrStr]),
+ io:format(?def_gl, "~ts", [TC0ErrStr]),
{testcase0_failed,bad_return_value};
_ ->
%% let test case info (also for all config funcs) override
@@ -972,11 +972,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end,
PrintError = fun(ErrorFormat, ErrorArgs) ->
- Div = "~n- - - - - - - - - - - - - - - - - - - "
- "- - - - - - - - - - - - - - - - - - - - -~n",
+ Div = "\n- - - - - - - - - - - - - - - - - - - "
+ "- - - - - - - - - - - - - - - - - - - - -\n",
ErrorStr2 = io_lib:format(ErrorFormat, ErrorArgs),
- io:format(?def_gl, lists:concat([Div,ErrorStr2,Div,"~n"]),
- []),
+ io:format(?def_gl, "~ts~n", [lists:concat([Div,ErrorStr2,Div])]),
Link =
"\n\n<a href=\"#end\">"
"Full error description and stacktrace"
@@ -985,7 +984,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
ct_logs:tc_log(ct_error_notify,
?MAX_IMPORTANCE,
"CT Error Notification",
- ErrorHtml2++Link, [], [])
+ "~ts", [ErrorHtml2++Link],
+ [])
end,
case Loc of
[{?MODULE,error_in_suite}] ->
@@ -1181,7 +1181,7 @@ get_all(Mod, ConfTests) ->
ErrStr = io_lib:format("~n*** ERROR *** "
"~w:all/0 failed: ~tp~n",
[Mod,ExitReason]),
- io:format(?def_gl, ErrStr, []),
+ io:format(?def_gl, "~ts", [ErrStr]),
%% save the error info so it doesn't get printed twice
ct_util:set_testdata_async({{error_in_suite,Mod},
ExitReason});
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 07a1693d5d..814b80b8bd 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -542,7 +542,7 @@ tc_print(Category,Importance,Format,Args,Opts) ->
undefined -> atom_to_list(Category);
Hd -> Hd
end,
- Str = lists:concat([get_header(Heading),Format,"\n\n"]),
+ Str = lists:flatten([get_header(Heading),Format,"\n\n"]),
try
io:format(?def_gl, Str, Args)
catch
@@ -935,7 +935,7 @@ create_io_fun(FromPid, CtLogFd, EscChars) ->
{_HdOrFt,S,A} -> {false,S,A};
{S,A} -> {true,S,A}
end,
- try io_lib:format(Str, Args) of
+ try io_lib:format(lists:flatten(Str), Args) of
IoStr when Escapable, EscChars, IoList == [] ->
escape_chars(IoStr);
IoStr when Escapable, EscChars ->
@@ -1138,10 +1138,10 @@ set_evmgr_gl(GL) ->
open_ctlog(MiscIoName) ->
{ok,Fd} = file:open(?ct_log_name,[write,{encoding,utf8}]),
- io:format(Fd, header("Common Test Framework Log", {[],[1,2],[]}), []),
+ io:format(Fd, "~ts", [header("Common Test Framework Log", {[],[1,2],[]})]),
case file:consult(ct_run:variables_file_name("../")) of
{ok,Vars} ->
- io:format(Fd, config_table(Vars), []);
+ io:format(Fd, "~ts", [config_table(Vars)]);
{error,Reason} ->
{ok,Cwd} = file:get_cwd(),
Dir = filename:dirname(Cwd),
@@ -1213,7 +1213,7 @@ print_style_error(Fd, IoFormat, StyleSheet, Reason) ->
close_ctlog(Fd) ->
io:format(Fd, "\n</pre>\n", []),
- io:format(Fd, [xhtml("<br><br>\n", "<br /><br />\n") | footer()], []),
+ io:format(Fd, "~ts", [[xhtml("<br><br>\n", "<br /><br />\n") | footer()]]),
ok = file:close(Fd).
%%%-----------------------------------------------------------------
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index fd33ee2280..9fc169789c 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -350,7 +350,7 @@ master_loop(#state{node_ctrl_pids=[],
io_lib:format("~-40.40.*ts~tp\n",
[$_,atom_to_list(Node),Result])
end,lists:reverse(Finished)),
- log(all,"TEST RESULTS",Str,[]),
+ log(all,"TEST RESULTS","~ts", [Str]),
log(all,"Info","Updating log files",[]),
refresh_logs(LogDirs,[]),
@@ -574,7 +574,7 @@ refresh_logs([],Refreshed) ->
io_lib:format("Refreshing logs in ~tp... ~tp",
[D,Result])
end,Refreshed),
- log(all,"Info",Str,[]).
+ log(all,"Info","~ts", [Str]).
%%%-----------------------------------------------------------------
%%% NODE CONTROLLER, runs and controls tests on a test node.
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index 8b1c7d47bb..b97c6e59e7 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -278,7 +278,7 @@ log_loop_info(Args) ->
ForceStop ->
io_lib:format("force_stop is set to: ~w",[ForceStop])
end,
- ct_logs:log("Test loop info",LogStr1++LogStr2++LogStr3++LogStr4,[])
+ ct_logs:log("Test loop info","~ts", [LogStr1++LogStr2++LogStr3++LogStr4])
end.
ts(Secs) ->
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 86081369b9..fe869a4373 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -194,10 +194,10 @@ handle_call({log,
case LogFunc of
tc_log ->
ct_logs:tc_log(Category, ?STD_IMPORTANCE,
- Header, String, [], []);
+ Header, "~ts", [String], []);
tc_log_async ->
ct_logs:tc_log_async(sasl, ?STD_IMPORTANCE,
- Header, String, [])
+ Header, "~ts", [String])
end,
{reply,ok,State};
@@ -261,34 +261,34 @@ handle_remote_events(Bool) ->
format_header(#eh_state{curr_suite = undefined,
curr_group = undefined,
curr_func = undefined}) ->
- io_lib:format("System report", []);
+ lists:flatten(io_lib:format("System report", []));
format_header(#eh_state{curr_suite = Suite,
curr_group = undefined,
curr_func = undefined}) ->
- io_lib:format("System report during ~w", [Suite]);
+ lists:flatten(io_lib:format("System report during ~w", [Suite]));
format_header(#eh_state{curr_suite = Suite,
curr_group = undefined,
curr_func = TcOrConf}) ->
- io_lib:format("System report during ~w:~tw/1",
- [Suite,TcOrConf]);
+ lists:flatten(io_lib:format("System report during ~w:~tw/1",
+ [Suite,TcOrConf]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
curr_func = Conf}) when Conf == init_per_group;
Conf == end_per_group ->
- io_lib:format("System report during ~w:~w/2 for ~tw",
- [Suite,Conf,Group]);
+ lists:flatten(io_lib:format("System report during ~w:~w/2 for ~tw",
+ [Suite,Conf,Group]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
parallel_tcs = true}) ->
- io_lib:format("System report during ~tw in ~w",
- [Group,Suite]);
+ lists:flatten(io_lib:format("System report during ~tw in ~w",
+ [Group,Suite]));
format_header(#eh_state{curr_suite = Suite,
curr_group = Group,
curr_func = TC}) ->
- io_lib:format("System report during ~w:~tw/1 in ~tw",
- [Suite,TC,Group]).
+ lists:flatten(io_lib:format("System report during ~w:~tw/1 in ~tw",
+ [Suite,TC,Group])).
diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl
index b0742717ae..c9b4cb10c6 100644
--- a/lib/common_test/src/cth_surefire.erl
+++ b/lib/common_test/src/cth_surefire.erl
@@ -235,7 +235,7 @@ close_suite(#state{ test_cases = TCs, url_base = UrlBase } = State) ->
terminate(State = #state{ test_cases = [] }) ->
{ok,D} = file:open(State#state.filepath,[write,{encoding,utf8}]),
io:format(D, "<?xml version=\"1.0\" encoding= \"UTF-8\" ?>", []),
- io:format(D, to_xml(State), []),
+ io:format(D, "~ts", [to_xml(State)]),
catch file:sync(D),
catch file:close(D);
terminate(State) ->
diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl
index 34f2feb33c..1518c6e8d6 100644
--- a/lib/common_test/src/test_server_ctrl.erl
+++ b/lib/common_test/src/test_server_ctrl.erl
@@ -1558,7 +1558,7 @@ do_test_cases(TopCases, SkipCases,
Html1}
end,
- print(html, Header),
+ print(html, "~ts", [Header]),
print(html, xhtml("<p>", "<h4>")),
print_timestamp(html, "Test started at "),
@@ -1605,10 +1605,10 @@ do_test_cases(TopCases, SkipCases,
[?suitelog_name,CoverLog,?unexpected_io_log]),
print(html,
"<p>~ts</p>\n" ++
- xhtml(["<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n",
- "<thead>\n"],
- ["<table id=\"",?sortable_table_name,"\">\n",
- "<thead>\n"]) ++
+ xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n" ++
+ "<thead>\n",
+ "<table id=\"" ++ ?sortable_table_name ++ "\">\n" ++
+ "<thead>\n") ++
"<tr><th>Num</th><th>Module</th><th>Group</th>" ++
"<th>Case</th><th>Log</th><th>Time</th><th>Result</th>" ++
"<th>Comment</th></tr>\n</thead>\n<tbody>\n",
@@ -3306,7 +3306,8 @@ skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) ->
true ->
print(2,"*** Skipping test case #~w ~tw ***", [CaseNum,{Mod,Func}])
end,
- TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]),
+ TR = xhtml("<tr valign=\"top\">",
+ "<tr class=\"" ++ odd_or_even() ++ "\">"),
GroupName = case get_name(Mode) of
undefined -> "";
Name -> cast_to_list(Name)
@@ -3796,8 +3797,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
print(minor,
- escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print]))]),
print(minor, "Current directory is ~tp\n", [Cwd]),
GrNameStr = case GrName of
@@ -3806,7 +3807,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
end,
print(major, "=started ~s", [lists:flatten(timestamp_get(""))]),
{{Col0,Col1},Style} = get_font_style((RunInit==run_init), Mode),
- TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]),
+ TR = xhtml("<tr valign=\"top\">", "<tr class=\"" ++ odd_or_even() ++ "\">"),
EncMinorBase = uri_encode(MinorBase),
print(html, TR ++ "<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>"
"<td>" ++ Col0 ++ "~w" ++ Col1 ++ "</td>"
@@ -3831,7 +3832,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit,
print(minor, "<a name=\"end\"></a>", [], internal_raw),
print(minor, "\n", [], internal_raw),
print_timestamp(minor, "Ended at "),
- print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]),
+ print(major, "=ended ~s", [timestamp_get("")]),
do_unless_parallel(Main, fun() -> file:set_cwd(filename:dirname(TSDir)) end),
@@ -4075,9 +4076,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, {testcase_aborted,Reason}, _T,
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== Location: ~ts", [FormatLoc]),
print(minor,
- escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}",
- [Reason])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}",
+ [Reason]))]),
failed;
progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
@@ -4115,8 +4116,8 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time,
print(minor, "=== Location: ~w", [unknown]),
{FStr,FormattedReason} = format_exception(Reason),
print(minor,
- escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason]))]),
failed;
progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
@@ -4150,8 +4151,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time,
FormatLoc = test_server_sup:format_loc(LocMin),
print(minor, "=== Location: ~ts", [FormatLoc]),
{FStr,FormattedReason} = format_exception(Reason),
- print(minor, "=== Reason: " ++
- escape_chars(io_lib:format(FStr, [FormattedReason])), []),
+ print(minor, "~ts",
+ ["=== Reason: " ++
+ escape_chars(io_lib:format(FStr, [FormattedReason]))]),
failed;
progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time,
@@ -4184,8 +4186,8 @@ progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time,
"~ts</tr>\n",
[TimeStr,Comment]),
print(minor,
- escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal])),
- []),
+ "~ts",
+ [escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal]))]),
ok.
%%--------------------------------------------------------------------
@@ -4542,7 +4544,7 @@ timestamp_get(Leader) ->
timestamp_get_internal(Leader, Format) ->
{YY,MM,DD,H,M,S} = time_get(),
- io_lib:format(Format, [Leader,YY,MM,DD,H,M,S]).
+ lists:flatten(io_lib:format(Format, [Leader,YY,MM,DD,H,M,S])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% time_get() -> {YY,MM,DD,H,M,S}
diff --git a/lib/common_test/test_server/ts_benchmark.erl b/lib/common_test/test_server/ts_benchmark.erl
index e4e06b54c2..a9486262b3 100644
--- a/lib/common_test/test_server/ts_benchmark.erl
+++ b/lib/common_test/test_server/ts_benchmark.erl
@@ -45,7 +45,7 @@ run(Specs, Opts, Vars) ->
|| Spec <- Specs],
file:delete(filename:join(Cwd,"latest_benchmark")),
{ok,D} = file:open(filename:join(Cwd,"latest_benchmark"),[write]),
- io:format(D,BDir,[]),
+ io:format(D,"~ts", [BDir]),
file:close(D).
diff --git a/lib/common_test/test_server/ts_erl_config.erl b/lib/common_test/test_server/ts_erl_config.erl
index 537628e39a..f3972bea4e 100644
--- a/lib/common_test/test_server/ts_erl_config.erl
+++ b/lib/common_test/test_server/ts_erl_config.erl
@@ -208,7 +208,11 @@ erl_interface(Vars,OsType) ->
{filename:join(Dir, "lib"),
filename:join([Dir, "src", "eidefs.mk"])};
{srctree, _Root, Target} ->
- {filename:join([Dir, "obj", Target]),
+ Obj = case is_debug_build() of
+ true -> "obj.debug";
+ false -> "obj"
+ end,
+ {filename:join([Dir, Obj, Target]),
filename:join([Dir, "src", Target, "eidefs.mk"])}
end}
end,
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index 45e442f5c2..5219ba0f5d 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -29,7 +29,7 @@
<rev>A</rev>
<file>compile.sgml</file>
</header>
- <module>compile</module>
+ <module since="">compile</module>
<modulesummary>Erlang Compiler</modulesummary>
<description>
<p>This module provides an interface to the standard Erlang
@@ -40,7 +40,7 @@
<funcs>
<func>
- <name>env_compiler_options()</name>
+ <name since="OTP 19.0">env_compiler_options()</name>
<fsummary>
Compiler options defined via the environment variable
<c>ERL_COMPILER_OPTIONS</c>
@@ -53,7 +53,7 @@
</desc>
</func>
<func>
- <name>file(File)</name>
+ <name since="">file(File)</name>
<fsummary>Compiles a file.</fsummary>
<desc>
<p>Is the same as
@@ -63,7 +63,7 @@
</func>
<func>
- <name>file(File, Options) -> CompRet</name>
+ <name since="">file(File, Options) -> CompRet</name>
<fsummary>Compiles a file.</fsummary>
<type>
<v>CompRet = ModRet | BinRet | ErrRet</v>
@@ -695,12 +695,13 @@ module.beam: module.erl \
</note>
<note>
- <p>The options <c>{nowarn_unused_function, FAs}</c>,
- <c>{nowarn_bif_clash, FAs}</c>, and
- <c>{nowarn_deprecated_function, MFAs}</c> are only
- recognized when given in files. They are not affected by
- options <c>warn_unused_function</c>, <c>warn_bif_clash</c>, or
- <c>warn_deprecated_function</c>.</p>
+ <p>Before OTP 22, the option <c>{nowarn_deprecated_function,
+ MFAs}</c> was only recognized when given in the file with
+ attribute <c>-compile()</c>. (The option
+ <c>{nowarn_unused_function,FAs}</c> was incorrectly documented
+ to only work in a file, but it also worked when given in the
+ option list.) Starting from OTP 22, all options that can be
+ given in the file can also be given in the option list.</p>
</note>
<p>For debugging of the compiler, or for pure curiosity,
@@ -729,7 +730,7 @@ module.beam: module.erl \
</func>
<func>
- <name>forms(Forms)</name>
+ <name since="">forms(Forms)</name>
<fsummary>Compiles a list of forms.</fsummary>
<desc>
<p>Is the same as
@@ -739,7 +740,7 @@ module.beam: module.erl \
</func>
<func>
- <name>forms(Forms, Options) -> CompRet</name>
+ <name since="">forms(Forms, Options) -> CompRet</name>
<fsummary>Compiles a list of forms.</fsummary>
<type>
<v>Forms = [Form]</v>
@@ -760,7 +761,7 @@ module.beam: module.erl \
</func>
<func>
- <name>format_error(ErrorDescriptor) -> chars()</name>
+ <name since="">format_error(ErrorDescriptor) -> chars()</name>
<fsummary>Formats an error descriptor.</fsummary>
<type>
<v>ErrorDescriptor = errordesc()</v>
@@ -775,7 +776,7 @@ module.beam: module.erl \
</func>
<func>
- <name>output_generated(Options) -> true | false</name>
+ <name since="">output_generated(Options) -> true | false</name>
<fsummary>Determines whether the compiler generates an output file.</fsummary>
<type>
<v>Options = [term()]</v>
@@ -790,7 +791,7 @@ module.beam: module.erl \
</func>
<func>
- <name>noenv_file(File, Options) -> CompRet</name>
+ <name since="">noenv_file(File, Options) -> CompRet</name>
<fsummary>Compiles a file (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<desc>
<p>Works like <seealso marker="#file/2">file/2</seealso>,
@@ -800,7 +801,7 @@ module.beam: module.erl \
</func>
<func>
- <name>noenv_forms(Forms, Options) -> CompRet</name>
+ <name since="">noenv_forms(Forms, Options) -> CompRet</name>
<fsummary>Compiles a list of forms (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<desc>
<p>Works like <seealso marker="#forms/2">forms/2</seealso>,
@@ -810,7 +811,7 @@ module.beam: module.erl \
</func>
<func>
- <name>noenv_output_generated(Options) -> true | false</name>
+ <name since="">noenv_output_generated(Options) -> true | false</name>
<fsummary>Determines whether the compiler generates an output file
(ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary>
<type>
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index e0e5bc832b..02e6203137 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,81 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>An optimization that avoided allocation of a stack
+ frame for some <c>case</c> expressions was introduced in
+ OTP 21. (ERL-504/OTP-14808) It turns out that in rare
+ circumstances, this optimization is not safe. Therefore,
+ this optimization has been disabled.</p>
+ <p>A similar optimization will be included in OTP 22 in a
+ safe way.</p>
+ <p>
+ Own Id: OTP-15501 Aux Id: ERL-807, ERL-514, OTP-14808 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a rare internal consistency failure caused by a
+ bug in the <c>beam_jump</c> pass. (Thanks to Simon
+ Cornish for reporting this bug.)</p>
+ <p>
+ Own Id: OTP-15400 Aux Id: ERL-759 </p>
+ </item>
+ <item>
+ <p>The compiler could fail with an internal consistency
+ check failure when compiling code that used the
+ <c>is_function/2</c> BIF.</p>
+ <p>
+ Own Id: OTP-15435 Aux Id: ERL-778 </p>
+ </item>
+ <item>
+ <p>When an external fun was used, warnings for unused
+ variables could be suppressed.</p>
+ <p>
+ Own Id: OTP-15437 Aux Id: ERL-762 </p>
+ </item>
+ <item>
+ <p>The compiler would crash when compiling an
+ <c>after</c> block that called <c>erlang:raise/3</c> like
+ this: <c>erlang:raise(Class, Stacktrace,
+ Stacktrace)</c></p>
+ <p>
+ Own Id: OTP-15481</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>When specified, the <c>+{source,Name}</c> option will
+ now override the actual file name in stack traces,
+ instead of only affecting the return value of
+ <c>Mod:module_info()</c>.</p>
+ <p>The <c>+deterministic</c> flag will also affect stack
+ traces now, omitting all path information except the file
+ name, fixing a long-standing issue where deterministic
+ builds required deterministic paths.</p>
+ <p>
+ Own Id: OTP-15245 Aux Id: ERL-706 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.2.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index d475e5a19a..97c73d0e07 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -49,7 +49,6 @@ MODULES = \
beam_a \
beam_asm \
beam_block \
- beam_bs \
beam_clean \
beam_dict \
beam_disasm \
@@ -69,6 +68,7 @@ MODULES = \
beam_ssa_pp \
beam_ssa_pre_codegen \
beam_ssa_recv \
+ beam_ssa_share \
beam_ssa_type \
beam_kernel_to_ssa \
beam_trim \
@@ -103,6 +103,7 @@ BEAM_H = $(wildcard ../priv/beam_h/*.h)
HRL_FILES= \
beam_disasm.hrl \
+ beam_ssa_opt.hrl \
beam_ssa.hrl \
core_parse.hrl \
v3_kernel.hrl
diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl
index dd2537a699..1ac892a8f1 100644
--- a/lib/compiler/src/beam_a.erl
+++ b/lib/compiler/src/beam_a.erl
@@ -100,8 +100,12 @@ rename_instr({bs_put_utf16=I,F,Fl,Src}) ->
{bs_put,F,{I,Fl},[Src]};
rename_instr({bs_put_utf32=I,F,Fl,Src}) ->
{bs_put,F,{I,Fl},[Src]};
-rename_instr({bs_put_string,_,_}=I) ->
- {bs_put,{f,0},I,[]};
+rename_instr({bs_put_string,_,{string,String}}) ->
+ %% Only happens when compiling from .S files. In old
+ %% .S files, String is a list. In .S in OTP 22 and later,
+ %% String is a binary.
+ {bs_put,{f,0},{bs_put_binary,8,{field_flags,[unsigned,big]}},
+ [{atom,all},{literal,iolist_to_binary([String])}]};
rename_instr({bs_add=I,F,[Src1,Src2,U],Dst}) when is_integer(U) ->
{bif,I,F,[Src1,Src2,{integer,U}],Dst};
rename_instr({bs_utf8_size=I,F,Src,Dst}) ->
@@ -118,8 +122,8 @@ rename_instr({bs_private_append=I,F,Sz,U,Src,Flags,Dst}) ->
{bs_init,F,{I,U,Flags},none,[Sz,Src],Dst};
rename_instr(bs_init_writable=I) ->
{bs_init,{f,0},I,1,[{x,0}],{x,0}};
-rename_instr({test,Op,F,[Ctx,Bits,{string,Str}]}) ->
- %% When compiling from a .S file.
+rename_instr({test,bs_match_string=Op,F,[Ctx,Bits,{string,Str}]}) when is_list(Str) ->
+ %% When compiling from an old .S file. Starting from OTP 22, Str is a binary.
<<Bs:Bits/bits,_/bits>> = list_to_binary(Str),
{test,Op,F,[Ctx,Bs]};
rename_instr({put_map_assoc,Fail,S,D,R,L}) ->
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index df0321e85a..bc1290f6fd 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -424,8 +424,8 @@ encode_arg({f, W}, Dict) ->
{encode(?tag_f, W), Dict};
%% encode_arg({'char', C}, Dict) ->
%% {encode(?tag_h, C), Dict};
-encode_arg({string, String}, Dict0) ->
- {Offset, Dict} = beam_dict:string(String, Dict0),
+encode_arg({string, BinString}, Dict0) when is_binary(BinString) ->
+ {Offset, Dict} = beam_dict:string(BinString, Dict0),
{encode(?tag_u, Offset), Dict};
encode_arg({extfunc, M, F, A}, Dict0) ->
{Index, Dict} = beam_dict:import(M, F, A, Dict0),
diff --git a/lib/compiler/src/beam_bs.erl b/lib/compiler/src/beam_bs.erl
deleted file mode 100644
index 15d8d687fc..0000000000
--- a/lib/compiler/src/beam_bs.erl
+++ /dev/null
@@ -1,183 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-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%
-%%
-%% Purpose: Peephole optimization of binary syntax instructions.
-
--module(beam_bs).
-
--export([module/2]).
--import(lists, [reverse/1]).
-
--spec module(beam_utils:module_code(), [compile:option()]) ->
- {'ok',beam_utils:module_code()}.
-
-module({Mod,Exp,Attr,Fs0,Lc}, _Opt) ->
- Fs = [function(F) || F <- Fs0],
- {ok,{Mod,Exp,Attr,Fs,Lc}}.
-
-function({function,Name,Arity,CLabel,Is0}) ->
- try
- Is = bs_opt(Is0),
- {function,Name,Arity,CLabel,Is}
- catch
- Class:Error:Stack ->
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
- end.
-
-%%%
-%%% Evaluate construction of constant bit fields.
-%%% Combine bs_skip_bits2 and bs_test_tail2 instructions.
-%%%
-
-bs_opt([{bs_put,_,_,_}=I|Is0]) ->
- {BsPuts0,Is} = collect_bs_puts(Is0, [I]),
- BsPuts = opt_bs_puts(BsPuts0),
- BsPuts ++ bs_opt(Is);
-bs_opt([{test,bs_skip_bits2,F,[Ctx,{integer,I},Unit,_Flags]},
- {test,bs_test_tail2,F,[Ctx,Bits]}|Is]) ->
- [{test,bs_test_tail2,F,[Ctx,Bits+I*Unit]}|bs_opt(Is)];
-bs_opt([{test,bs_skip_bits2,F,[Ctx,{integer,I1},Unit1,Flags]},
- {test,bs_skip_bits2,F,[Ctx,{integer,I2},Unit2,_]}|Is]) ->
- I = {test,bs_skip_bits2,F,
- [Ctx,{integer,I1*Unit1+I2*Unit2},1,Flags]},
- bs_opt([I|Is]);
-bs_opt([I|Is]) ->
- [I|bs_opt(Is)];
-bs_opt([]) -> [].
-
-collect_bs_puts([{bs_put,_,_,_}=I|Is], Acc) ->
- collect_bs_puts(Is, [I|Acc]);
-collect_bs_puts([_|_]=Is, Acc) ->
- {reverse(Acc),Is}.
-
-opt_bs_puts(Is) ->
- opt_bs_1(Is, []).
-
-opt_bs_1([{bs_put,Fail,
- {bs_put_float,1,Flags0},[{integer,Sz},Src]}=I0|Is], Acc) ->
- try eval_put_float(Src, Sz, Flags0) of
- <<Int:Sz>> ->
- Flags = force_big(Flags0),
- I = {bs_put,Fail,{bs_put_integer,1,Flags},
- [{integer,Sz},{integer,Int}]},
- opt_bs_1([I|Is], Acc)
- catch
- error:_ ->
- opt_bs_1(Is, [I0|Acc])
- end;
-opt_bs_1([{bs_put,_,{bs_put_integer,1,_},[{integer,8},{integer,_}]}|_]=IsAll,
- Acc0) ->
- {Is,Acc} = bs_collect_string(IsAll, Acc0),
- opt_bs_1(Is, Acc);
-opt_bs_1([{bs_put,Fail,{bs_put_integer,1,F},[{integer,Sz},{integer,N}]}=I|Is0],
- Acc) when Sz > 8 ->
- case field_endian(F) of
- big ->
- %% We can do this optimization for any field size without
- %% risk for code explosion.
- case bs_split_int(N, Sz, Fail, Is0) of
- no_split -> opt_bs_1(Is0, [I|Acc]);
- Is -> opt_bs_1(Is, Acc)
- end;
- little when Sz < 128 ->
- %% We only try to optimize relatively small fields, to
- %% avoid an explosion in code size.
- <<Int:Sz>> = <<N:Sz/little>>,
- Flags = force_big(F),
- Is = [{bs_put,Fail,{bs_put_integer,1,Flags},
- [{integer,Sz},{integer,Int}]}|Is0],
- opt_bs_1(Is, Acc);
- _ -> %native or too wide little field
- opt_bs_1(Is0, [I|Acc])
- end;
-opt_bs_1([{bs_put,Fail,{Op,U,F},[{integer,Sz},Src]}|Is], Acc) when U > 1 ->
- opt_bs_1([{bs_put,Fail,{Op,1,F},[{integer,U*Sz},Src]}|Is], Acc);
-opt_bs_1([I|Is], Acc) ->
- opt_bs_1(Is, [I|Acc]);
-opt_bs_1([], Acc) -> reverse(Acc).
-
-eval_put_float(Src, Sz, Flags) when Sz =< 256 ->
- %%Only evaluate if Sz is reasonable.
- Val = value(Src),
- case field_endian(Flags) of
- little -> <<Val:Sz/little-float-unit:1>>;
- big -> <<Val:Sz/big-float-unit:1>>
- %% native intentionally not handled here - we can't optimize
- %% it.
- end.
-
-value({integer,I}) -> I;
-value({float,F}) -> F.
-
-bs_collect_string(Is, [{bs_put,_,{bs_put_string,Len,{string,Str}},[]}|Acc]) ->
- bs_coll_str_1(Is, Len, reverse(Str), Acc);
-bs_collect_string(Is, Acc) ->
- bs_coll_str_1(Is, 0, [], Acc).
-
-bs_coll_str_1([{bs_put,_,{bs_put_integer,U,_},[{integer,Sz},{integer,V}]}|Is],
- Len, StrAcc, IsAcc) when U*Sz =:= 8 ->
- Byte = V band 16#FF,
- bs_coll_str_1(Is, Len+1, [Byte|StrAcc], IsAcc);
-bs_coll_str_1(Is, Len, StrAcc, IsAcc) ->
- {Is,[{bs_put,{f,0},{bs_put_string,Len,{string,reverse(StrAcc)}},[]}|IsAcc]}.
-
-field_endian({field_flags,F}) -> field_endian_1(F).
-
-field_endian_1([big=E|_]) -> E;
-field_endian_1([little=E|_]) -> E;
-field_endian_1([native=E|_]) -> E;
-field_endian_1([_|Fs]) -> field_endian_1(Fs).
-
-force_big({field_flags,F}) ->
- {field_flags,force_big_1(F)}.
-
-force_big_1([big|_]=Fs) -> Fs;
-force_big_1([little|Fs]) -> [big|Fs];
-force_big_1([F|Fs]) -> [F|force_big_1(Fs)].
-
-bs_split_int(0, Sz, _, _) when Sz > 64 ->
- %% We don't want to split in this case because the
- %% string will consist of only zeroes.
- no_split;
-bs_split_int(-1, Sz, _, _) when Sz > 64 ->
- %% We don't want to split in this case because the
- %% string will consist of only 255 bytes.
- no_split;
-bs_split_int(N, Sz, Fail, Acc) ->
- FirstByteSz = case Sz rem 8 of
- 0 -> 8;
- Rem -> Rem
- end,
- bs_split_int_1(N, FirstByteSz, Sz, Fail, Acc).
-
-bs_split_int_1(-1, _, Sz, Fail, Acc) when Sz > 64 ->
- I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}},
- [{integer,Sz},{integer,-1}]},
- [I|Acc];
-bs_split_int_1(0, _, Sz, Fail, Acc) when Sz > 64 ->
- I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}},
- [{integer,Sz},{integer,0}]},
- [I|Acc];
-bs_split_int_1(N, ByteSz, Sz, Fail, Acc) when Sz > 0 ->
- Mask = (1 bsl ByteSz) - 1,
- I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}},
- [{integer,ByteSz},{integer,N band Mask}]},
- bs_split_int_1(N bsr ByteSz, 8, Sz-ByteSz, Fail, [I|Acc]);
-bs_split_int_1(_, _, _, _, Acc) -> Acc.
diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl
index 990e86062a..b2056332e6 100644
--- a/lib/compiler/src/beam_dict.erl
+++ b/lib/compiler/src/beam_dict.erl
@@ -126,18 +126,17 @@ import(Mod0, Name0, Arity, #asm{imports=Imp0,next_import=NextIndex}=D0)
{NextIndex,D2#asm{imports=Imp,next_import=NextIndex+1}}
end.
-%% Returns the index for a string in the string table (adding the string to the
-%% table if necessary).
+%% Returns the index for a binary string in the string table (adding
+%% the string to the table if necessary).
%% string(String, Dict) -> {Offset, Dict'}
--spec string(string(), bdict()) -> {non_neg_integer(), bdict()}.
+-spec string(binary(), bdict()) -> {non_neg_integer(), bdict()}.
-string(Str, Dict) when is_list(Str) ->
+string(BinString, Dict) when is_binary(BinString) ->
#asm{strings=Strings,string_offset=NextOffset} = Dict,
- StrBin = list_to_binary(Str),
- case old_string(StrBin, Strings) of
+ case old_string(BinString, Strings) of
none ->
- NewDict = Dict#asm{strings = <<Strings/binary,StrBin/binary>>,
- string_offset=NextOffset+byte_size(StrBin)},
+ NewDict = Dict#asm{strings = <<Strings/binary,BinString/binary>>,
+ string_offset=NextOffset+byte_size(BinString)},
{NextOffset,NewDict};
Offset when is_integer(Offset) ->
{NextOffset-Offset,Dict}
diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl
index d3a618d211..8b0e3e32f8 100644
--- a/lib/compiler/src/beam_jump.erl
+++ b/lib/compiler/src/beam_jump.erl
@@ -101,6 +101,10 @@
%%% always keep the label. (beam_clean will remove any unused
%%% labels.)
%%%
+%%% (7) Replace a jump to a return instruction with a return instruction.
+%%% Similarly, replace a jump to deallocate + return with those
+%%% instructions.
+%%%
%%% Note: This modules depends on (almost) all branches and jumps only
%%% going forward, so that we can remove instructions (including definition
%%% of labels) after any label that has not been referenced by the code
@@ -150,7 +154,8 @@ function({function,Name,Arity,CLabel,Asm0}, Lc0) ->
Asm3 = share(Asm2),
Asm4 = move(Asm3),
Asm5 = opt(Asm4, CLabel),
- Asm = remove_unused_labels(Asm5),
+ Asm6 = unshare(Asm5),
+ Asm = remove_unused_labels(Asm6),
{{function,Name,Arity,CLabel,Asm},Lc}
catch
Class:Error:Stack ->
@@ -393,14 +398,13 @@ extract_seq_1(_, _) -> no.
{
entry :: beam_asm:label(), %Entry label (must not be moved).
replace :: #{beam_asm:label() := beam_asm:label()}, %Labels to replace.
- labels :: cerl_sets:set(), %Set of referenced labels.
- index :: beam_utils:code_index() | {lazy,[beam_utils:instruction()]} %Index built lazily only if needed
+ labels :: cerl_sets:set() %Set of referenced labels.
}).
opt(Is0, CLabel) ->
find_fixpoint(fun(Is) ->
Lbls = initial_labels(Is),
- St = #st{entry=CLabel,replace=#{},labels=Lbls,index={lazy,Is}},
+ St = #st{entry=CLabel,replace=#{},labels=Lbls},
opt(Is, [], St)
end, Is0).
@@ -425,16 +429,6 @@ opt([{test,_,{f,L}=Lbl,_}=I|[{jump,{f,L}}|_]=Is], Acc, St) ->
%% as in the jump that follows -- thus it is not needed.
opt(Is, Acc, St)
end;
-opt([{test,_,{f,L}=Lbl,_}=I|[{label,L}|_]=Is], Acc, St) ->
- %% Similar to the above, except we have a fall-through rather than jump
- %% Test Label Ops
- %% label Label
- case beam_utils:is_pure_test(I) of
- false ->
- opt(Is, [I|Acc], label_used(Lbl, St));
- true ->
- opt(Is, Acc, St)
- end;
opt([{test,Test0,{f,L}=Lbl,Ops}=I|[{jump,To}|Is]=Is0], Acc, St) ->
case is_label_defined(Is, L) of
false ->
@@ -625,6 +619,39 @@ drop_upto_label([{label,_}|_]=Is) -> Is;
drop_upto_label([_|Is]) -> drop_upto_label(Is);
drop_upto_label([]) -> [].
+%% unshare([Instruction]) -> [Instruction].
+%% Replace a jump to a return sequence (a `return` instruction
+%% optionally preced by a `deallocate` instruction) with the return
+%% sequence. This always saves execution time and may also save code
+%% space (depending on the architecture). Eliminating `jump`
+%% instructions also gives beam_trim more opportunities to trim the
+%% stack.
+
+unshare(Is) ->
+ Short = unshare_collect_short(Is, #{}),
+ unshare_short(Is, Short).
+
+unshare_collect_short([{label,L},return|Is], Map) ->
+ unshare_collect_short(Is, Map#{L=>[return]});
+unshare_collect_short([{label,L},{deallocate,_}=D,return|Is], Map) ->
+ %% `deallocate` and `return` are combined into one instruction by
+ %% the loader.
+ unshare_collect_short(Is, Map#{L=>[D,return]});
+unshare_collect_short([_|Is], Map) ->
+ unshare_collect_short(Is, Map);
+unshare_collect_short([], Map) -> Map.
+
+unshare_short([{jump,{f,F}}=I|Is], Map) ->
+ case Map of
+ #{F:=Seq} ->
+ Seq ++ unshare_short(Is, Map);
+ #{} ->
+ [I|unshare_short(Is, Map)]
+ end;
+unshare_short([I|Is], Map) ->
+ [I|unshare_short(Is, Map)];
+unshare_short([], _Map) -> [].
+
%% ulbl(Instruction, UsedCerlSet) -> UsedCerlSet'
%% Update the cerl_set UsedCerlSet with any function-local labels
%% (i.e. not with labels in call instructions) referenced by
diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl
index 2323a439e9..5730e9704e 100644
--- a/lib/compiler/src/beam_peep.erl
+++ b/lib/compiler/src/beam_peep.erl
@@ -94,30 +94,26 @@ peep([{gc_bif,_,_,_,_,Dst}=I|Is], SeenTests0, Acc) ->
peep([{jump,{f,L}},{label,L}=I|Is], _, Acc) ->
%% Sometimes beam_jump has missed this optimization.
peep(Is, gb_sets:empty(), [I|Acc]);
-peep([{select,Op,R,F,Vls0}|Is], SeenTests0, Acc0) ->
+peep([{select,select_val,R,F,Vls0}|Is], SeenTests0, Acc0) ->
case prune_redundant_values(Vls0, F) of
[] ->
%% No values left. Must convert to plain jump.
I = {jump,F},
peep([I|Is], gb_sets:empty(), Acc0);
- [{atom,_}=Value,Lbl] when Op =:= select_val ->
+ [{atom,_}=Value,Lbl] ->
%% Single value left. Convert to regular test.
Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is],
peep(Is1, SeenTests0, Acc0);
- [{integer,_}=Value,Lbl] when Op =:= select_val ->
+ [{integer,_}=Value,Lbl] ->
%% Single value left. Convert to regular test.
Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is],
peep(Is1, SeenTests0, Acc0);
- [Arity,Lbl] when Op =:= select_tuple_arity ->
- %% Single value left. Convert to regular test
- Is1 = [{test,test_arity,F,[R,Arity]},{jump,Lbl}|Is],
- peep(Is1, SeenTests0, Acc0);
[{atom,B1},Lbl,{atom,B2},Lbl] when B1 =:= not B2 ->
%% Replace with is_boolean test.
Is1 = [{test,is_boolean,F,[R]},{jump,Lbl}|Is],
peep(Is1, SeenTests0, Acc0);
[_|_]=Vls ->
- I = {select,Op,R,F,Vls},
+ I = {select,select_val,R,F,Vls},
peep(Is, gb_sets:empty(), [I|Acc0])
end;
peep([{get_map_elements,Fail,Src,List}=I|Is], _SeenTests, Acc0) ->
diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl
index 9631bf3334..466337db0e 100644
--- a/lib/compiler/src/beam_ssa_bsm.erl
+++ b/lib/compiler/src/beam_ssa_bsm.erl
@@ -877,7 +877,8 @@ annotate_context_parameters(F, ModInfo) ->
%% Assertion.
error(conflicting_parameter_types);
(K, suitable_for_reuse, Acc) ->
- Acc#{ K => match_context };
+ T = beam_validator:type_anno(match_context),
+ Acc#{ K => T };
(_K, _V, Acc) ->
Acc
end, TypeAnno0, ParamInfo),
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index d3facc5911..c2d5035b19 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -161,7 +161,7 @@ add_parameter_annos([{label, _}=Entry | Body], Anno) ->
(_K, _V, Acc) ->
Acc
end, [], maps:get(registers, Anno)),
- [Entry | Annos] ++ Body.
+ [Entry | sort(Annos)] ++ Body.
cg_fun(Blocks, St0) ->
Linear0 = linearize(Blocks),
@@ -1071,8 +1071,8 @@ cg_block([#cg_set{op={bif,Name},dst=Dst0,args=Args0}]=Is0, {Dst0,Fail}, St0) ->
{z,_} ->
%% The result of the BIF call will only be used once. Convert to
%% a test instruction.
- Test = bif_to_test(Name, Args, ensure_label(Fail, St0)),
- {Test,St0};
+ {Test,St1} = bif_to_test(Name, Args, ensure_label(Fail, St0), St0),
+ {Test,St1};
_ ->
%% Must explicitly call the BIF since the result will be used
%% more than once.
@@ -1269,16 +1269,17 @@ cg_copy_1([], _St) -> [].
element(1, Val) =:= atom orelse
element(1, Val) =:= literal)).
+bif_to_test('or', [V1,V2], {f,Lbl}=Fail, St0) when Lbl =/= 0 ->
+ {SuccLabel,St} = new_label(St0),
+ {[{test,is_eq_exact,{f,SuccLabel},[V1,{atom,false}]},
+ {test,is_eq_exact,Fail,[V2,{atom,true}]},
+ {label,SuccLabel}],St};
+bif_to_test(Op, Args, Fail, St) ->
+ {bif_to_test(Op, Args, Fail),St}.
+
bif_to_test('and', [V1,V2], Fail) ->
[{test,is_eq_exact,Fail,[V1,{atom,true}]},
{test,is_eq_exact,Fail,[V2,{atom,true}]}];
-bif_to_test('or', [V1,V2], {f,Lbl}=Fail) when Lbl =/= 0 ->
- %% Labels are spaced 2 apart. We can create a new
- %% label by incrementing the Fail label.
- SuccLabel = Lbl + 1,
- [{test,is_eq_exact,{f,SuccLabel},[V1,{atom,false}]},
- {test,is_eq_exact,Fail,[V2,{atom,true}]},
- {label,SuccLabel}];
bif_to_test('not', [Var], Fail) ->
[{test,is_eq_exact,Fail,[Var,{atom,false}]}];
bif_to_test(Name, Args, Fail) ->
@@ -1448,7 +1449,12 @@ cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=[#b_local{}=Func0|Args0]},
Line = call_line(Where, local, Anno),
Call = build_call(call, Arity, {f,FuncLbl}, Context, Dst),
Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call,
- {Is,St};
+ case Anno of
+ #{ result_type := Info } ->
+ {Is ++ [{'%', {type_info, Dst, Info}}], St};
+ #{} ->
+ {Is, St}
+ end;
cg_call(#cg_set{anno=Anno0,op=call,dst=Dst0,args=[#b_remote{}=Func0|Args0]},
Where, Context, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
@@ -1724,6 +1730,14 @@ copy(Src, Dst) -> [{move,Src,Dst}].
force_reg({literal,_}=Lit, Reg) ->
{Reg,[{move,Lit,Reg}]};
+force_reg({integer,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg({atom,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg({float,_}=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
+force_reg(nil=Lit, Reg) ->
+ {Reg,[{move,Lit,Reg}]};
force_reg({Kind,_}=R, _) when Kind =:= x; Kind =:= y ->
{R,[]}.
@@ -2017,9 +2031,7 @@ is_gc_bif(Bif, Args) ->
%% new_label(St) -> {L,St}.
new_label(#cg{lcount=Next}=St) ->
- %% Advance the label counter by 2 to allow us to create
- %% a label for 'or' by incrementing an existing label.
- {Next,St#cg{lcount=Next+2}}.
+ {Next,St#cg{lcount=Next+1}}.
%% call_line(tail|body, Func, Anno) -> [] | [{line,...}].
%% Produce a line instruction if it will be needed by the
diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl
index ac2d943fef..2c898ba6f8 100644
--- a/lib/compiler/src/beam_ssa_opt.erl
+++ b/lib/compiler/src/beam_ssa_opt.erl
@@ -18,63 +18,167 @@
%% %CopyrightEnd%
%%
+%%%
+%%% This is a collection of various optimizations that don't need a separate
+%%% pass by themselves and/or are mutually beneficial to other passes.
+%%%
+%%% The optimizations are applied in "phases," each with a list of sub-passes
+%%% to run. These sub-passes are applied on all functions in a module before
+%%% moving on to the next phase, which lets us gather module-level information
+%%% in one phase and then apply it in the next without having to risk working
+%%% with incomplete information.
+%%%
+%%% Each sub-pass operates on a #st{} record and a func_info_db(), where the
+%%% former is just a #b_function{} whose blocks can be represented either in
+%%% linear or map form, and the latter is a map with information about all
+%%% functions in the module (see beam_ssa_opt.hrl for more details).
+%%%
+
-module(beam_ssa_opt).
-export([module/2]).
--include("beam_ssa.hrl").
--import(lists, [all/2,append/1,foldl/3,keyfind/3,member/2,reverse/1,reverse/2,
- splitwith/2,takewhile/2,unzip/1]).
+-include("beam_ssa_opt.hrl").
+
+-import(lists, [all/2,append/1,duplicate/2,foldl/3,keyfind/3,member/2,
+ reverse/1,reverse/2,
+ splitwith/2,sort/1,takewhile/2,unzip/1]).
+
+-define(DEFAULT_REPETITIONS, 2).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
{'ok',beam_ssa:b_module()}.
-module(#b_module{body=Fs0}=Module, Opts) ->
- Ps = passes(Opts),
- Fs = functions(Fs0, Ps),
- {ok,Module#b_module{body=Fs}}.
+-record(st, {ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] |
+ beam_ssa:block_map(),
+ args :: [beam_ssa:b_var()],
+ cnt :: beam_ssa:label(),
+ anno :: beam_ssa:anno()}).
+-type st_map() :: #{ func_id() => #st{} }.
+
+module(Module, Opts) ->
+ FuncDb0 = case proplists:get_value(no_module_opt, Opts, false) of
+ false -> build_func_db(Module);
+ true -> #{}
+ end,
+
+ %% Passes that perform module-level optimizations are often aided by
+ %% optimizing callers before callees and vice versa, so we optimize all
+ %% functions in call order, flipping it as required.
+ StMap0 = build_st_map(Module),
+ Order = get_call_order_po(StMap0, FuncDb0),
+
+ Phases =
+ [{Order, prologue_passes(Opts)}] ++
+ repeat(Opts, repeated_passes(Opts), Order) ++
+ [{Order, epilogue_passes(Opts)}],
+
+ {StMap, _FuncDb} = foldl(fun({FuncIds, Ps}, {StMap, FuncDb}) ->
+ phase(FuncIds, Ps, StMap, FuncDb)
+ end, {StMap0, FuncDb0}, Phases),
+
+ {ok, finish(Module, StMap)}.
+
+phase([FuncId | Ids], Ps, StMap, FuncDb0) ->
+ try
+ {St, FuncDb} =
+ compile:run_sub_passes(Ps, {map_get(FuncId, StMap), FuncDb0}),
+
+ phase(Ids, Ps, StMap#{ FuncId => St }, FuncDb)
+ catch
+ Class:Error:Stack ->
+ #b_local{name=Name,arity=Arity} = FuncId,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end;
+phase([], _Ps, StMap, FuncDb) ->
+ {StMap, FuncDb}.
+
+%% Repeats the given passes, alternating the order between runs to make the
+%% type pass more efficient.
+repeat(Opts, Ps, OrderA) ->
+ Repeat = proplists:get_value(ssa_opt_repeat, Opts, ?DEFAULT_REPETITIONS),
+ OrderB = reverse(OrderA),
+ repeat_1(Repeat, Ps, OrderA, OrderB).
-functions([F|Fs], Ps) ->
- [function(F, Ps)|functions(Fs, Ps)];
-functions([], _Ps) -> [].
+repeat_1(0, _Opts, _OrderA, _OrderB) ->
+ [];
+repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 0 ->
+ [{OrderA, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)];
+repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 1 ->
+ [{OrderB, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)].
--type b_blk() :: beam_ssa:b_blk().
--type b_var() :: beam_ssa:b_var().
--type label() :: beam_ssa:label().
+%%
+
+get_func_id(F) ->
+ {_Mod, Name, Arity} = beam_ssa:get_anno(func_info, F),
+ #b_local{name=#b_literal{val=Name}, arity=Arity}.
+
+-spec build_st_map(#b_module{}) -> st_map().
+build_st_map(#b_module{body=Fs}) ->
+ build_st_map_1(Fs, #{}).
+
+build_st_map_1([F | Fs], Map) ->
+ #b_function{anno=Anno,args=Args,cnt=Counter,bs=Bs} = F,
+ St = #st{anno=Anno,args=Args,cnt=Counter,ssa=Bs},
+ build_st_map_1(Fs, Map#{ get_func_id(F) => St });
+build_st_map_1([], Map) ->
+ Map.
+
+-spec finish(#b_module{}, st_map()) -> #b_module{}.
+finish(#b_module{body=Fs0}=Module, StMap) ->
+ Module#b_module{body=finish_1(Fs0, StMap)}.
+
+finish_1([F0 | Fs], StMap) ->
+ #st{anno=Anno,cnt=Counter,ssa=Blocks} = map_get(get_func_id(F0), StMap),
+ F = F0#b_function{anno=Anno,bs=Blocks,cnt=Counter},
+ [F | finish_1(Fs, StMap)];
+finish_1([], _StMap) ->
+ [].
+
+%%
--record(st, {ssa :: beam_ssa:block_map() | [{label(),b_blk()}],
- args :: [b_var()],
- cnt :: label()}).
-define(PASS(N), {N,fun N/1}).
-passes(Opts0) ->
+prologue_passes(Opts) ->
Ps = [?PASS(ssa_opt_split_blocks),
?PASS(ssa_opt_coalesce_phis),
+ ?PASS(ssa_opt_tail_phis),
?PASS(ssa_opt_element),
?PASS(ssa_opt_linearize),
+ ?PASS(ssa_opt_tuple_size),
?PASS(ssa_opt_record),
-
- %% Run ssa_opt_cse twice, because it will help ssa_opt_dead,
- %% and ssa_opt_dead will help ssa_opt_cse. Run ssa_opt_live
- %% twice, because it will help ssa_opt_dead and ssa_opt_dead
- %% will help ssa_opt_live.
- ?PASS(ssa_opt_cse),
- ?PASS(ssa_opt_type),
- ?PASS(ssa_opt_live),
+ ?PASS(ssa_opt_cse), %Helps the first type pass.
+ ?PASS(ssa_opt_type_start)],
+ passes_1(Ps, Opts).
+
+%% These passes all benefit from each other (in roughly this order), so they
+%% are repeated as required.
+repeated_passes(Opts) ->
+ Ps = [?PASS(ssa_opt_live),
+ ?PASS(ssa_opt_bs_puts),
?PASS(ssa_opt_dead),
- ?PASS(ssa_opt_cse), %Second time.
- ?PASS(ssa_opt_float),
- ?PASS(ssa_opt_live), %Second time.
+ ?PASS(ssa_opt_cse),
+ ?PASS(ssa_opt_tail_phis),
+ ?PASS(ssa_opt_type_continue)], %Must run after ssa_opt_dead to
+ %clean up phi nodes.
+ passes_1(Ps, Opts).
+epilogue_passes(Opts) ->
+ Ps = [?PASS(ssa_opt_type_finish),
+ ?PASS(ssa_opt_float),
+ ?PASS(ssa_opt_live), %One last time to clean up the
+ %mess left by the float pass.
?PASS(ssa_opt_bsm),
?PASS(ssa_opt_bsm_units),
?PASS(ssa_opt_bsm_shortcut),
- ?PASS(ssa_opt_misc),
- ?PASS(ssa_opt_tuple_size),
?PASS(ssa_opt_sw),
?PASS(ssa_opt_blockify),
?PASS(ssa_opt_sink),
?PASS(ssa_opt_merge_blocks),
?PASS(ssa_opt_trim_unreachable)],
+ passes_1(Ps, Opts).
+
+passes_1(Ps, Opts0) ->
Negations = [{list_to_atom("no_"++atom_to_list(N)),N} ||
{N,_} <- Ps],
Opts = proplists:substitute_negations(Negations, Opts0),
@@ -86,36 +190,132 @@ passes(Opts0) ->
{NoName,fun(S) -> S end}
end || {Name,_}=P <- Ps].
-function(#b_function{anno=Anno,bs=Blocks0,args=Args,cnt=Count0}=F, Ps) ->
+%% Builds a function information map with basic information about incoming and
+%% outgoing local calls, as well as whether the function is exported.
+-spec build_func_db(#b_module{}) -> func_info_db().
+build_func_db(#b_module{body=Fs,exports=Exports}) ->
try
- St = #st{ssa=Blocks0,args=Args,cnt=Count0},
- #st{ssa=Blocks,cnt=Count} = compile:run_sub_passes(Ps, St),
- F#b_function{bs=Blocks,cnt=Count}
+ fdb_1(Fs, gb_sets:from_list(Exports), #{})
catch
- Class:Error:Stack ->
- #{func_info:={_,Name,Arity}} = Anno,
- io:fwrite("Function: ~w/~w\n", [Name,Arity]),
- erlang:raise(Class, Error, Stack)
+ %% All module-level optimizations are invalid when a NIF can override a
+ %% function, so we have to bail out.
+ throw:load_nif -> #{}
end.
+fdb_1([#b_function{ args=Args,bs=Bs }=F | Fs], Exports, FuncDb0) ->
+ Id = get_func_id(F),
+
+ #b_local{name=#b_literal{val=Name}, arity=Arity} = Id,
+ Exported = gb_sets:is_element({Name, Arity}, Exports),
+ ArgTypes = duplicate(length(Args), #{}),
+
+ FuncDb1 = case FuncDb0 of
+ %% We may have an entry already if someone's called us.
+ #{ Id := Info } ->
+ FuncDb0#{ Id := Info#func_info{ exported=Exported,
+ arg_types=ArgTypes }};
+ #{} ->
+ FuncDb0#{ Id => #func_info{ exported=Exported,
+ arg_types=ArgTypes }}
+ end,
+
+ FuncDb = beam_ssa:fold_rpo(fun(_L, #b_blk{is=Is}, FuncDb) ->
+ fdb_is(Is, Id, FuncDb)
+ end, FuncDb1, Bs),
+
+ fdb_1(Fs, Exports, FuncDb);
+fdb_1([], _Exports, FuncDb) ->
+ FuncDb.
+
+fdb_is([#b_set{op=call,
+ args=[#b_local{}=Callee | _]} | Is],
+ Caller, FuncDb) ->
+ fdb_is(Is, Caller, fdb_update(Caller, Callee, FuncDb));
+fdb_is([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=erlang},
+ name=#b_literal{val=load_nif}},
+ _Path, _LoadInfo]} | _Is], _Caller, _FuncDb) ->
+ throw(load_nif);
+fdb_is([_ | Is], Caller, FuncDb) ->
+ fdb_is(Is, Caller, FuncDb);
+fdb_is([], _Caller, FuncDb) ->
+ FuncDb.
+
+fdb_update(Caller, Callee, FuncDb) ->
+ CallerVertex = maps:get(Caller, FuncDb, #func_info{}),
+ CalleeVertex = maps:get(Callee, FuncDb, #func_info{}),
+
+ Calls = ordsets:add_element(Callee, CallerVertex#func_info.out),
+ CalledBy = ordsets:add_element(Caller, CalleeVertex#func_info.in),
+
+ FuncDb#{ Caller => CallerVertex#func_info{out=Calls},
+ Callee => CalleeVertex#func_info{in=CalledBy} }.
+
+%% Returns the post-order of all local calls in this module. That is, it starts
+%% with the functions that don't call any others and then walks up the call
+%% chain.
+%%
+%% Functions where module-level optimization is disabled are added last in
+%% arbitrary order.
+
+get_call_order_po(StMap, FuncDb) ->
+ Leaves = maps:fold(fun(Id, #func_info{out=[]}, Acc) ->
+ [Id | Acc];
+ (_, _, Acc) ->
+ Acc
+ end, [], FuncDb),
+
+ Order = gco_po_1(sort(Leaves), FuncDb, [], #{}),
+
+ Order ++ maps:fold(fun(K, _V, Acc) ->
+ case is_map_key(K, FuncDb) of
+ false -> [K | Acc];
+ true -> Acc
+ end
+ end, [], StMap).
+
+gco_po_1([Id | Ids], FuncDb, Children, Seen) when not is_map_key(Id, Seen) ->
+ [Id | gco_po_1(Ids, FuncDb, [Id | Children], Seen#{ Id => true })];
+gco_po_1([_Id | Ids], FuncDb, Children, Seen) ->
+ gco_po_1(Ids, FuncDb, Children, Seen);
+gco_po_1([], FuncDb, [_|_]=Children, Seen) ->
+ gco_po_1(gco_po_parents(Children, FuncDb), FuncDb, [], Seen);
+gco_po_1([], _FuncDb, [], _Seen) ->
+ [].
+
+gco_po_parents([Child | Children], FuncDb) ->
+ #{ Child := #func_info{in=Parents}} = FuncDb,
+ Parents ++ gco_po_parents(Children, FuncDb);
+gco_po_parents([], _FuncDb) ->
+ [].
+
%%%
%%% Trivial sub passes.
%%%
-ssa_opt_dead(#st{ssa=Linear}=St) ->
- St#st{ssa=beam_ssa_dead:opt(Linear)}.
+ssa_opt_dead({#st{ssa=Linear}=St, FuncDb}) ->
+ {St#st{ssa=beam_ssa_dead:opt(Linear)}, FuncDb}.
+
+ssa_opt_linearize({#st{ssa=Blocks}=St, FuncDb}) ->
+ {St#st{ssa=beam_ssa:linearize(Blocks)}, FuncDb}.
+
+ssa_opt_type_start({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
+ {Linear, FuncDb} = beam_ssa_type:opt_start(Linear0, Args, Anno, FuncDb0),
+ {St0#st{ssa=Linear}, FuncDb}.
-ssa_opt_linearize(#st{ssa=Blocks}=St) ->
- St#st{ssa=beam_ssa:linearize(Blocks)}.
+ssa_opt_type_continue({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) ->
+ {Linear, FuncDb} = beam_ssa_type:opt_continue(Linear0, Args, Anno, FuncDb0),
+ {St0#st{ssa=Linear}, FuncDb}.
-ssa_opt_type(#st{ssa=Linear,args=Args}=St) ->
- St#st{ssa=beam_ssa_type:opt(Linear, Args)}.
+ssa_opt_type_finish({#st{args=Args,anno=Anno0}=St0, FuncDb0}) ->
+ {Anno, FuncDb} = beam_ssa_type:opt_finish(Args, Anno0, FuncDb0),
+ {St0#st{anno=Anno}, FuncDb}.
-ssa_opt_blockify(#st{ssa=Linear}=St) ->
- St#st{ssa=maps:from_list(Linear)}.
+ssa_opt_blockify({#st{ssa=Linear}=St, FuncDb}) ->
+ {St#st{ssa=maps:from_list(Linear)}, FuncDb}.
-ssa_opt_trim_unreachable(#st{ssa=Blocks}=St) ->
- St#st{ssa=beam_ssa:trim_unreachable(Blocks)}.
+ssa_opt_trim_unreachable({#st{ssa=Blocks}=St, FuncDb}) ->
+ {St#st{ssa=beam_ssa:trim_unreachable(Blocks)}, FuncDb}.
%%%
%%% Split blocks before certain instructions to enable more optimizations.
@@ -127,14 +327,14 @@ ssa_opt_trim_unreachable(#st{ssa=Blocks}=St) ->
%%% for sinking get_tuple_element instructions.
%%%
-ssa_opt_split_blocks(#st{ssa=Blocks0,cnt=Count0}=St) ->
+ssa_opt_split_blocks({#st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) ->
P = fun(#b_set{op={bif,element}}) -> true;
(#b_set{op=call}) -> true;
(#b_set{op=make_fun}) -> true;
(_) -> false
end,
{Blocks,Count} = beam_ssa:split_blocks(P, Blocks0, Count0),
- St#st{ssa=Blocks,cnt=Count}.
+ {St#st{ssa=Blocks,cnt=Count}, FuncDb}.
%%%
%%% Coalesce phi nodes.
@@ -158,10 +358,10 @@ ssa_opt_split_blocks(#st{ssa=Blocks0,cnt=Count0}=St) ->
%%% different registers).
%%%
-ssa_opt_coalesce_phis(#st{ssa=Blocks0}=St) ->
+ssa_opt_coalesce_phis({#st{ssa=Blocks0}=St, FuncDb}) ->
Ls = beam_ssa:rpo(Blocks0),
Blocks = c_phis_1(Ls, Blocks0),
- St#st{ssa=Blocks}.
+ {St#st{ssa=Blocks}, FuncDb}.
c_phis_1([L|Ls], Blocks0) ->
case maps:get(L, Blocks0) of
@@ -233,6 +433,160 @@ c_fix_branches([{_,Pred}|As], L, Blocks0) ->
c_fix_branches([], _, Blocks) -> Blocks.
%%%
+%%% Eliminate phi nodes in the tail of a function.
+%%%
+%%% Try to eliminate short blocks that starts with a phi node
+%%% and end in a return. For example:
+%%%
+%%% Result = phi { Res1, 4 }, { literal true, 5 }
+%%% Ret = put_tuple literal ok, Result
+%%% ret Ret
+%%%
+%%% The code in this block can be inserted at the end blocks 4 and
+%%% 5. Thus, the following code can be inserted into block 4:
+%%%
+%%% Ret:1 = put_tuple literal ok, Res1
+%%% ret Ret:1
+%%%
+%%% And the following code into block 5:
+%%%
+%%% Ret:2 = put_tuple literal ok, literal true
+%%% ret Ret:2
+%%%
+%%% Which can be further simplified to:
+%%%
+%%% ret literal {ok, true}
+%%%
+%%% This transformation may lead to more code improvements:
+%%%
+%%% - Stack trimming
+%%% - Fewer test_heap instructions
+%%% - Smaller stack frames
+%%%
+
+ssa_opt_tail_phis({#st{ssa=SSA0,cnt=Count0}=St, FuncDb}) ->
+ {SSA,Count} = opt_tail_phis(SSA0, Count0),
+ {St#st{ssa=SSA,cnt=Count}, FuncDb}.
+
+opt_tail_phis(Blocks, Count) when is_map(Blocks) ->
+ opt_tail_phis(maps:values(Blocks), Blocks, Count);
+opt_tail_phis(Linear0, Count0) when is_list(Linear0) ->
+ Blocks0 = maps:from_list(Linear0),
+ {Blocks,Count} = opt_tail_phis(Blocks0, Count0),
+ {beam_ssa:linearize(Blocks),Count}.
+
+opt_tail_phis([#b_blk{is=Is0,last=Last}|Bs], Blocks0, Count0) ->
+ case {Is0,Last} of
+ {[#b_set{op=phi,args=[_,_|_]}|_],#b_ret{arg=#b_var{}}=Ret} ->
+ {Phis,Is} = splitwith(fun(#b_set{op=Op}) -> Op =:= phi end, Is0),
+ case suitable_tail_ops(Is) of
+ true ->
+ {Blocks,Count} = opt_tail_phi(Phis, Is, Ret,
+ Blocks0, Count0),
+ opt_tail_phis(Bs, Blocks, Count);
+ false ->
+ opt_tail_phis(Bs, Blocks0, Count0)
+ end;
+ {_,_} ->
+ opt_tail_phis(Bs, Blocks0, Count0)
+ end;
+opt_tail_phis([], Blocks, Count) ->
+ {Blocks,Count}.
+
+opt_tail_phi(Phis0, Is, Ret, Blocks0, Count0) ->
+ Phis = rel2fam(reduce_phis(Phis0)),
+ {Blocks,Count,Cost} =
+ foldl(fun(PhiArg, Acc) ->
+ opt_tail_phi_arg(PhiArg, Is, Ret, Acc)
+ end, {Blocks0,Count0,0}, Phis),
+ MaxCost = length(Phis) * 3 + 2,
+ if
+ Cost =< MaxCost ->
+ %% The transformation would cause at most a slight
+ %% increase in code size if no more optimizations
+ %% can be applied.
+ {Blocks,Count};
+ true ->
+ %% The code size would be increased too much.
+ {Blocks0,Count0}
+ end.
+
+reduce_phis([#b_set{dst=PhiDst,args=PhiArgs}|Is]) ->
+ [{L,{PhiDst,Val}} || {Val,L} <- PhiArgs] ++ reduce_phis(Is);
+reduce_phis([]) -> [].
+
+opt_tail_phi_arg({PredL,Sub0}, Is0, Ret0, {Blocks0,Count0,Cost0}) ->
+ Blk0 = map_get(PredL, Blocks0),
+ #b_blk{is=IsPrefix,last=#b_br{succ=Next,fail=Next}} = Blk0,
+ case is_exit_bif(IsPrefix) of
+ false ->
+ Sub1 = maps:from_list(Sub0),
+ {Is1,Count,Sub} = new_names(Is0, Sub1, Count0, []),
+ Is2 = [sub(I, Sub) || I <- Is1],
+ Cost = build_cost(Is2, Cost0),
+ Is = IsPrefix ++ Is2,
+ Ret = sub(Ret0, Sub),
+ Blk = Blk0#b_blk{is=Is,last=Ret},
+ Blocks = Blocks0#{PredL:=Blk},
+ {Blocks,Count,Cost};
+ true ->
+ %% The block ends in a call to a function that
+ %% will cause an exception.
+ {Blocks0,Count0,Cost0+3}
+ end.
+
+is_exit_bif([#b_set{op=call,
+ args=[#b_remote{mod=#b_literal{val=Mod},
+ name=#b_literal{val=Name}}|Args]}]) ->
+ erl_bifs:is_exit_bif(Mod, Name, length(Args));
+is_exit_bif(_) -> false.
+
+new_names([#b_set{dst=Dst}=I|Is], Sub0, Count0, Acc) ->
+ {NewDst,Count} = new_var(Dst, Count0),
+ Sub = Sub0#{Dst=>NewDst},
+ new_names(Is, Sub, Count, [I#b_set{dst=NewDst}|Acc]);
+new_names([], Sub, Count, Acc) ->
+ {reverse(Acc),Count,Sub}.
+
+suitable_tail_ops(Is) ->
+ all(fun(#b_set{op=Op}) ->
+ is_suitable_tail_op(Op)
+ end, Is).
+
+is_suitable_tail_op({bif,_}) -> true;
+is_suitable_tail_op(put_list) -> true;
+is_suitable_tail_op(put_tuple) -> true;
+is_suitable_tail_op(_) -> false.
+
+build_cost([#b_set{op=put_list,args=Args}|Is], Cost) ->
+ case are_all_literals(Args) of
+ true ->
+ build_cost(Is, Cost);
+ false ->
+ build_cost(Is, Cost + 1)
+ end;
+build_cost([#b_set{op=put_tuple,args=Args}|Is], Cost) ->
+ case are_all_literals(Args) of
+ true ->
+ build_cost(Is, Cost);
+ false ->
+ build_cost(Is, Cost + length(Args) + 1)
+ end;
+build_cost([#b_set{op={bif,_},args=Args}|Is], Cost) ->
+ case are_all_literals(Args) of
+ true ->
+ build_cost(Is, Cost);
+ false ->
+ build_cost(Is, Cost + 1)
+ end;
+build_cost([], Cost) -> Cost.
+
+are_all_literals(Args) ->
+ all(fun(#b_literal{}) -> true;
+ (_) -> false
+ end, Args).
+
+%%%
%%% Order element/2 calls.
%%%
%%% Order an unbroken chain of element/2 calls for the same tuple
@@ -241,7 +595,7 @@ c_fix_branches([], _, Blocks) -> Blocks.
%%% be replaced with get_tuple_element/3 instructions.
%%%
-ssa_opt_element(#st{ssa=Blocks}=St) ->
+ssa_opt_element({#st{ssa=Blocks}=St, FuncDb}) ->
%% Collect the information about element instructions in this
%% function.
GetEls = collect_element_calls(beam_ssa:linearize(Blocks)),
@@ -253,7 +607,7 @@ ssa_opt_element(#st{ssa=Blocks}=St) ->
%% For each chain, swap the first element call with the
%% element call with the highest index.
- St#st{ssa=swap_element_calls(Chains, Blocks)}.
+ {St#st{ssa=swap_element_calls(Chains, Blocks)}, FuncDb}.
collect_element_calls([{L,#b_blk{is=Is0,last=Last}}|Bs]) ->
case {Is0,Last} of
@@ -314,9 +668,9 @@ swap_element_calls_1([], _, Blocks) ->
%%% when applicable.
%%%
-ssa_opt_record(#st{ssa=Linear}=St) ->
+ssa_opt_record({#st{ssa=Linear}=St, FuncDb}) ->
Blocks = maps:from_list(Linear),
- St#st{ssa=record_opt(Linear, Blocks)}.
+ {St#st{ssa=record_opt(Linear, Blocks)}, FuncDb}.
record_opt([{L,#b_blk{is=Is0,last=Last}=Blk0}|Bs], Blocks) ->
Is = record_opt_is(Is0, Last, Blocks),
@@ -400,9 +754,9 @@ is_tagged_tuple_4([], _, _) -> no.
%%% subexpressions across instructions that clobber the X registers.
%%%
-ssa_opt_cse(#st{ssa=Linear}=St) ->
+ssa_opt_cse({#st{ssa=Linear}=St, FuncDb}) ->
M = #{0=>#{}},
- St#st{ssa=cse(Linear, #{}, M)}.
+ {St#st{ssa=cse(Linear, #{}, M)}, FuncDb}.
cse([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs], Sub0, M0) ->
Es0 = maps:get(L, M0),
@@ -543,13 +897,13 @@ cse_suitable(#b_set{}) -> false.
bs :: beam_ssa:block_map()
}).
-ssa_opt_float(#st{ssa=Linear0,cnt=Count0}=St) ->
+ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
NonGuards0 = float_non_guards(Linear0),
NonGuards = gb_sets:from_list(NonGuards0),
Blocks = maps:from_list(Linear0),
Fs = #fs{non_guards=NonGuards,bs=Blocks},
{Linear,Count} = float_opt(Linear0, Count0, Fs),
- St#st{ssa=Linear,cnt=Count}.
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
float_non_guards([{L,#b_blk{is=Is}}|Bs]) ->
case Is of
@@ -787,15 +1141,20 @@ float_flush_regs(#fs{regs=Rs}) ->
%%% with a cheaper instructions
%%%
-ssa_opt_live(#st{ssa=Linear}=St) ->
- St#st{ssa=live_opt(reverse(Linear), #{}, [])}.
+ssa_opt_live({#st{ssa=Linear0}=St, FuncDb}) ->
+ RevLinear = reverse(Linear0),
+ Blocks0 = maps:from_list(RevLinear),
+ Blocks = live_opt(RevLinear, #{}, Blocks0),
+ Linear = beam_ssa:linearize(Blocks),
+ {St#st{ssa=Linear}, FuncDb}.
-live_opt([{L,Blk0}|Bs], LiveMap0, Acc) ->
- Successors = beam_ssa:successors(Blk0),
+live_opt([{L,Blk0}|Bs], LiveMap0, Blocks) ->
+ Blk1 = beam_ssa_share:block(Blk0, Blocks),
+ Successors = beam_ssa:successors(Blk1),
Live0 = live_opt_succ(Successors, L, LiveMap0),
- {Blk,Live} = live_opt_blk(Blk0, Live0),
+ {Blk,Live} = live_opt_blk(Blk1, Live0),
LiveMap = live_opt_phis(Blk#b_blk.is, L, Live, LiveMap0),
- live_opt(Bs, LiveMap, [{L,Blk}|Acc]);
+ live_opt(Bs, LiveMap, Blocks#{L:=Blk});
live_opt([], _, Acc) -> Acc.
live_opt_succ([S|Ss], L, LiveMap) ->
@@ -849,14 +1208,9 @@ live_opt_is([#b_set{op=succeeded,dst=SuccDst=SuccDstVar,
#b_set{dst=Dst}=I|Is], Live0, Acc) ->
case gb_sets:is_member(Dst, Live0) of
true ->
- case gb_sets:is_member(SuccDst, Live0) of
- true ->
- Live1 = gb_sets:add(Dst, Live0),
- Live = gb_sets:delete_any(SuccDst, Live1),
- live_opt_is([I|Is], Live, [SuccI|Acc]);
- false ->
- live_opt_is([I|Is], Live0, Acc)
- end;
+ Live1 = gb_sets:add(Dst, Live0),
+ Live = gb_sets:delete_any(SuccDst, Live1),
+ live_opt_is([I|Is], Live, [SuccI|Acc]);
false ->
case live_opt_unused(I) of
{replace,NewI0} ->
@@ -896,24 +1250,32 @@ live_opt_unused(#b_set{op=get_map_element}=Set) ->
live_opt_unused(_) -> keep.
%%%
-%%% Optimize binary matching instructions.
+%%% Optimize binary matching.
+%%%
+%%% * If the value of segment is never extracted, rewrite
+%%% to a bs_skip instruction.
+%%%
+%%% * Coalesce adjacent bs_skip instructions and skip instructions
+%%% with bs_test_tail.
%%%
-ssa_opt_bsm(#st{ssa=Linear}=St) ->
+ssa_opt_bsm({#st{ssa=Linear}=St, FuncDb}) ->
Extracted0 = bsm_extracted(Linear),
Extracted = cerl_sets:from_list(Extracted0),
- St#st{ssa=bsm_skip(Linear, Extracted)}.
+ {St#st{ssa=bsm_skip(Linear, Extracted)}, FuncDb}.
-bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs], Extracted) ->
+bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs0], Extracted) ->
+ Bs = bsm_skip(Bs0, Extracted),
Is = bsm_skip_is(Is0, Extracted),
- [{L,Blk#b_blk{is=Is}}|bsm_skip(Bs, Extracted)];
+ coalesce_skips({L,Blk#b_blk{is=Is}}, Bs);
bsm_skip([], _) -> [].
bsm_skip_is([I0|Is], Extracted) ->
case I0 of
- #b_set{op=bs_match,args=[#b_literal{val=string}|_]} ->
- [I0|bsm_skip_is(Is, Extracted)];
- #b_set{op=bs_match,dst=Ctx,args=[Type,PrevCtx|Args0]} ->
+ #b_set{op=bs_match,
+ dst=Ctx,
+ args=[#b_literal{val=T}=Type,PrevCtx|Args0]}
+ when T =/= string, T =/= skip ->
I = case cerl_sets:is_element(Ctx, Extracted) of
true ->
I0;
@@ -937,18 +1299,75 @@ bsm_extracted([{_,#b_blk{is=Is}}|Bs]) ->
end;
bsm_extracted([]) -> [].
+coalesce_skips({L,#b_blk{is=[#b_set{op=bs_extract}=Extract|Is0],
+ last=Last0}=Blk0}, Bs0) ->
+ case coalesce_skips_is(Is0, Last0, Bs0) of
+ not_possible ->
+ [{L,Blk0}|Bs0];
+ {Is,Last,Bs} ->
+ Blk = Blk0#b_blk{is=[Extract|Is],last=Last},
+ [{L,Blk}|Bs]
+ end;
+coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0) ->
+ case coalesce_skips_is(Is0, Last0, Bs0) of
+ not_possible ->
+ [{L,Blk0}|Bs0];
+ {Is,Last,Bs} ->
+ Blk = Blk0#b_blk{is=Is,last=Last},
+ [{L,Blk}|Bs]
+ end.
+
+coalesce_skips_is([#b_set{op=bs_match,
+ args=[#b_literal{val=skip},
+ Ctx0,Type,Flags,
+ #b_literal{val=Size0},
+ #b_literal{val=Unit0}]}=Skip0,
+ #b_set{op=succeeded}],
+ #b_br{succ=L2,fail=Fail}=Br0,
+ Bs0) when is_integer(Size0) ->
+ case Bs0 of
+ [{L2,#b_blk{is=[#b_set{op=bs_match,
+ dst=SkipDst,
+ args=[#b_literal{val=skip},_,_,_,
+ #b_literal{val=Size1},
+ #b_literal{val=Unit1}]},
+ #b_set{op=succeeded}=Succeeded],
+ last=#b_br{fail=Fail}=Br}}|Bs] when is_integer(Size1) ->
+ SkipBits = Size0 * Unit0 + Size1 * Unit1,
+ Skip = Skip0#b_set{dst=SkipDst,
+ args=[#b_literal{val=skip},Ctx0,
+ Type,Flags,
+ #b_literal{val=SkipBits},
+ #b_literal{val=1}]},
+ Is = [Skip,Succeeded],
+ {Is,Br,Bs};
+ [{L2,#b_blk{is=[#b_set{op=bs_test_tail,
+ args=[_Ctx,#b_literal{val=TailSkip}]}],
+ last=#b_br{succ=NextSucc,fail=Fail}}}|Bs] ->
+ SkipBits = Size0 * Unit0,
+ TestTail = Skip0#b_set{op=bs_test_tail,
+ args=[Ctx0,#b_literal{val=SkipBits+TailSkip}]},
+ Br = Br0#b_br{bool=TestTail#b_set.dst,succ=NextSucc},
+ Is = [TestTail],
+ {Is,Br,Bs};
+ _ ->
+ not_possible
+ end;
+coalesce_skips_is(_, _, _) ->
+ not_possible.
+
%%%
%%% Short-cutting binary matching instructions.
%%%
-ssa_opt_bsm_shortcut(#st{ssa=Linear}=St) ->
+ssa_opt_bsm_shortcut({#st{ssa=Linear}=St, FuncDb}) ->
Positions = bsm_positions(Linear, #{}),
case map_size(Positions) of
0 ->
%% No binary matching instructions.
- St;
+ {St, FuncDb};
_ ->
- St#st{ssa=bsm_shortcut(Linear, Positions)}
+ {St#st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb}
end.
bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) ->
@@ -1010,8 +1429,8 @@ bsm_shortcut([], _PosMap) -> [].
%%% Eliminate redundant bs_test_unit2 instructions.
%%%
-ssa_opt_bsm_units(#st{ssa=Linear}=St) ->
- St#st{ssa=bsm_units(Linear, #{})}.
+ssa_opt_bsm_units({#st{ssa=Linear}=St, FuncDb}) ->
+ {St#st{ssa=bsm_units(Linear, #{})}, FuncDb}.
bsm_units([{L,#b_blk{last=#b_br{succ=Succ,fail=Fail}}=Block0} | Bs], UnitMaps0) ->
UnitsIn = maps:get(L, UnitMaps0, #{}),
@@ -1111,91 +1530,172 @@ bsm_units_join_1([], _MapA, Right) ->
Right.
%%%
-%%% Miscellanous optimizations in execution order.
+%%% Optimize binary construction.
+%%%
+%%% If an integer segment or a float segment has a literal size and
+%%% a literal value, convert to a binary segment. Coalesce adjacent
+%%% literal binary segments. Literal binary segments will be converted
+%%% to bs_put_string instructions in later pass.
%%%
-ssa_opt_misc(#st{ssa=Linear}=St) ->
- St#st{ssa=misc_opt(Linear, #{})}.
+ssa_opt_bs_puts({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
+ {Linear,Count} = opt_bs_puts(Linear0, Count0, []),
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
-misc_opt([{L,#b_blk{is=Is0,last=Last0}=Blk0}|Bs], Sub0) ->
- {Is,Sub} = misc_opt_is(Is0, Sub0, []),
- Last = sub(Last0, Sub),
- Blk = Blk0#b_blk{is=Is,last=Last},
- [{L,Blk}|misc_opt(Bs, Sub)];
-misc_opt([], _) -> [].
+opt_bs_puts([{L,#b_blk{is=Is}=Blk0}|Bs], Count0, Acc0) ->
+ case Is of
+ [#b_set{op=bs_put}=I0] ->
+ case opt_bs_put(L, I0, Blk0, Count0, Acc0) of
+ not_possible ->
+ opt_bs_puts(Bs, Count0, [{L,Blk0}|Acc0]);
+ {Count,Acc1} ->
+ Acc = opt_bs_puts_merge(Acc1),
+ opt_bs_puts(Bs, Count, Acc)
+ end;
+ _ ->
+ opt_bs_puts(Bs, Count0, [{L,Blk0}|Acc0])
+ end;
+opt_bs_puts([], Count, Acc) ->
+ {reverse(Acc),Count}.
-misc_opt_is([#b_set{op=phi}=I0|Is], Sub0, Acc) ->
- #b_set{dst=Dst,args=Args} = I = sub(I0, Sub0),
- case all_same(Args) of
+opt_bs_puts_merge([{L1,#b_blk{is=Is}=Blk0},{L2,#b_blk{is=AccIs}}=BAcc|Acc]) ->
+ case {AccIs,Is} of
+ {[#b_set{op=bs_put,
+ args=[#b_literal{val=binary},
+ #b_literal{},
+ #b_literal{val=Bin0},
+ #b_literal{val=all},
+ #b_literal{val=1}]}],
+ [#b_set{op=bs_put,
+ args=[#b_literal{val=binary},
+ #b_literal{},
+ #b_literal{val=Bin1},
+ #b_literal{val=all},
+ #b_literal{val=1}]}=I0]} ->
+ %% Coalesce the two segments to one.
+ Bin = <<Bin0/bitstring,Bin1/bitstring>>,
+ I = I0#b_set{args=bs_put_args(binary, Bin, all)},
+ Blk = Blk0#b_blk{is=[I]},
+ [{L2,Blk}|Acc];
+ {_,_} ->
+ [{L1,Blk0},BAcc|Acc]
+ end.
+
+opt_bs_put(L, I0, #b_blk{last=Br0}=Blk0, Count0, Acc) ->
+ case opt_bs_put(I0) of
+ [Bin] when is_bitstring(Bin) ->
+ Args = bs_put_args(binary, Bin, all),
+ I = I0#b_set{args=Args},
+ Blk = Blk0#b_blk{is=[I]},
+ {Count0,[{L,Blk}|Acc]};
+ [{int,Int,Size},Bin] when is_bitstring(Bin) ->
+ %% Construct a bs_put_integer instruction following
+ %% by a bs_put_binary instruction.
+ IntArgs = bs_put_args(integer, Int, Size),
+ BinArgs = bs_put_args(binary, Bin, all),
+ {BinL,BinVarNum} = {Count0,Count0+1},
+ Count = Count0 + 2,
+ BinVar = #b_var{name={'@ssa_bool',BinVarNum}},
+ BinI = I0#b_set{dst=BinVar,args=BinArgs},
+ BinBlk = Blk0#b_blk{is=[BinI],last=Br0#b_br{bool=BinVar}},
+ IntI = I0#b_set{args=IntArgs},
+ IntBlk = Blk0#b_blk{is=[IntI],last=Br0#b_br{succ=BinL}},
+ {Count,[{BinL,BinBlk},{L,IntBlk}|Acc]};
+ not_possible ->
+ not_possible
+ end.
+
+opt_bs_put(#b_set{args=[#b_literal{val=binary},_,#b_literal{val=Val},
+ #b_literal{val=all},#b_literal{val=Unit}]})
+ when is_bitstring(Val) ->
+ if
+ bit_size(Val) rem Unit =:= 0 ->
+ [Val];
true ->
- %% Eliminate the phi node if there is just one source
- %% value or if the values are identical.
- [{Val,_}|_] = Args,
- Sub = Sub0#{Dst=>Val},
- misc_opt_is(Is, Sub, Acc);
- false ->
- misc_opt_is(Is, Sub0, [I|Acc])
- end;
-misc_opt_is([#b_set{op={bif,'and'}}=I0], Sub, Acc) ->
- #b_set{dst=Dst,args=Args} = I = sub(I0, Sub),
- case eval_and(Args) of
- error ->
- misc_opt_is([], Sub, [I|Acc]);
- Val ->
- misc_opt_is([], Sub#{Dst=>Val}, Acc)
- end;
-misc_opt_is([#b_set{op={bif,'or'}}=I0], Sub, Acc) ->
- #b_set{dst=Dst,args=Args} = I = sub(I0, Sub),
- case eval_or(Args) of
- error ->
- misc_opt_is([], Sub, [I|Acc]);
- Val ->
- misc_opt_is([], Sub#{Dst=>Val}, Acc)
+ not_possible
end;
-misc_opt_is([#b_set{}=I0|Is], Sub, Acc) ->
- #b_set{op=Op,dst=Dst,args=Args} = I = sub(I0, Sub),
- case make_literal(Op, Args) of
- #b_literal{}=Literal ->
- misc_opt_is(Is, Sub#{Dst=>Literal}, Acc);
- error ->
- misc_opt_is(Is, Sub, [I|Acc])
+opt_bs_put(#b_set{args=[#b_literal{val=Type},#b_literal{val=Flags},
+ #b_literal{val=Val},#b_literal{val=Size},
+ #b_literal{val=Unit}]}=I0) when is_integer(Size) ->
+ EffectiveSize = Size * Unit,
+ if
+ EffectiveSize > 0 ->
+ case {Type,opt_bs_put_endian(Flags)} of
+ {integer,big} when is_integer(Val) ->
+ if
+ EffectiveSize < 64 ->
+ [<<Val:EffectiveSize>>];
+ true ->
+ opt_bs_put_split_int(Val, EffectiveSize)
+ end;
+ {integer,little} when is_integer(Val), EffectiveSize < 128 ->
+ %% To avoid an explosion in code size, we only try
+ %% to optimize relatively small fields.
+ <<Int:EffectiveSize>> = <<Val:EffectiveSize/little>>,
+ Args = bs_put_args(Type, Int, EffectiveSize),
+ I = I0#b_set{args=Args},
+ opt_bs_put(I);
+ {binary,_} when is_bitstring(Val) ->
+ <<Bitstring:EffectiveSize/bits,_/bits>> = Val,
+ [Bitstring];
+ {float,Endian} ->
+ try
+ [opt_bs_put_float(Val, EffectiveSize, Endian)]
+ catch error:_ ->
+ not_possible
+ end;
+ {_,_} ->
+ not_possible
+ end;
+ true ->
+ not_possible
end;
-misc_opt_is([], Sub, Acc) ->
- {reverse(Acc),Sub}.
+opt_bs_put(#b_set{}) -> not_possible.
-all_same([{H,_}|T]) ->
- all(fun({E,_}) -> E =:= H end, T).
-
-make_literal(put_tuple, Args) ->
- case make_literal_list(Args, []) of
- error ->
- error;
- List ->
- #b_literal{val=list_to_tuple(List)}
- end;
-make_literal(put_list, [#b_literal{val=H},#b_literal{val=T}]) ->
- #b_literal{val=[H|T]};
-make_literal(_, _) -> error.
+opt_bs_put_float(N, Sz, Endian) ->
+ case Endian of
+ big -> <<N:Sz/big-float-unit:1>>;
+ little -> <<N:Sz/little-float-unit:1>>
+ end.
-make_literal_list([#b_literal{val=H}|T], Acc) ->
- make_literal_list(T, [H|Acc]);
-make_literal_list([_|_], _) ->
- error;
-make_literal_list([], Acc) ->
- reverse(Acc).
-
-eval_and(Args) ->
- case Args of
- [_,#b_literal{val=false}=Res] -> Res;
- [Res,#b_literal{val=true}] -> Res;
- [_,_] -> error
+bs_put_args(Type, Val, Size) ->
+ [#b_literal{val=Type},
+ #b_literal{val=[unsigned,big]},
+ #b_literal{val=Val},
+ #b_literal{val=Size},
+ #b_literal{val=1}].
+
+opt_bs_put_endian([big=E|_]) -> E;
+opt_bs_put_endian([little=E|_]) -> E;
+opt_bs_put_endian([native=E|_]) -> E;
+opt_bs_put_endian([_|Fs]) -> opt_bs_put_endian(Fs).
+
+opt_bs_put_split_int(Int, Size) ->
+ Pos = opt_bs_put_split_int_1(Int, 0, Size - 1),
+ UpperSize = Size - Pos,
+ if
+ Pos =:= 0 ->
+ %% Value is 0 or -1 -- keep the original instruction.
+ not_possible;
+ UpperSize < 64 ->
+ %% No or few leading zeroes or ones.
+ [<<Int:Size>>];
+ true ->
+ %% There are 64 or more leading ones or zeroes in
+ %% the resulting binary. Split into two separate
+ %% segments to avoid an explosion in code size.
+ [{int,Int bsr Pos,UpperSize},<<Int:Pos>>]
end.
-eval_or(Args) ->
- case Args of
- [Res,#b_literal{val=false}] -> Res;
- [_,#b_literal{val=true}=Res] -> Res;
- [_,_] -> error
+opt_bs_put_split_int_1(_Int, L, R) when L > R ->
+ 8 * ((L + 7) div 8);
+opt_bs_put_split_int_1(Int, L, R) ->
+ Mid = (L + R) div 2,
+ case Int bsr Mid of
+ Upper when Upper =:= 0; Upper =:= -1 ->
+ opt_bs_put_split_int_1(Int, L, Mid - 1);
+ _ ->
+ opt_bs_put_split_int_1(Int, Mid + 1, R)
end.
%%%
@@ -1258,9 +1758,9 @@ eval_or(Args) ->
%%% is_tuple_of_arity instruction by the loader.
%%%
-ssa_opt_tuple_size(#st{ssa=Linear0,cnt=Count0}=St) ->
+ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
{Linear,Count} = opt_tup_size(Linear0, Count0, []),
- St#st{ssa=Linear,cnt=Count}.
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], Count0, Acc0) ->
case {Is,Last} of
@@ -1333,9 +1833,9 @@ opt_tup_size_is([], _, _, _Acc) -> none.
%%% is 'true' or 'false' can be rewritten to a is_boolean test.
%%%
-ssa_opt_sw(#st{ssa=Linear0,cnt=Count0}=St) ->
+ssa_opt_sw({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) ->
{Linear,Count} = opt_sw(Linear0, #{}, Count0, []),
- St#st{ssa=Linear,cnt=Count}.
+ {St#st{ssa=Linear,cnt=Count}, FuncDb}.
opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Last0}=Blk0}|Bs], Phis0, Count0, Acc) ->
Phis = opt_sw_phis(Is, Phis0),
@@ -1426,9 +1926,10 @@ opt_sw_literals([], Acc) -> Acc.
%%% Merge blocks.
%%%
-ssa_opt_merge_blocks(#st{ssa=Blocks}=St) ->
+ssa_opt_merge_blocks({#st{ssa=Blocks}=St, FuncDb}) ->
Preds = beam_ssa:predecessors(Blocks),
- St#st{ssa=merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks)}.
+ Merged = merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks),
+ {St#st{ssa=Merged}, FuncDb}.
merge_blocks_1([L|Ls], Preds0, Blocks0) ->
case Preds0 of
@@ -1438,6 +1939,7 @@ merge_blocks_1([L|Ls], Preds0, Blocks0) ->
true ->
#b_blk{is=Is0} = Blk0,
#b_blk{is=Is1} = Blk1,
+ verify_merge_is(Is1),
Is = Is0 ++ Is1,
Blk = Blk1#b_blk{is=Is},
Blocks1 = maps:remove(L, Blocks0),
@@ -1463,6 +1965,13 @@ merge_update_preds([], _, _, Preds) -> Preds.
rename_label(From, From, To) -> To;
rename_label(Lbl, _, _) -> Lbl.
+verify_merge_is([#b_set{op=Op}|_]) ->
+ %% The merged block has only one predecessor, so it should not have any phi
+ %% nodes.
+ true = Op =/= phi; %Assertion.
+verify_merge_is(_) ->
+ ok.
+
is_merge_allowed(_, _, #b_blk{is=[#b_set{op=peek_message}|_]}) ->
false;
is_merge_allowed(L, Blk0, #b_blk{}) ->
@@ -1487,7 +1996,7 @@ is_merge_allowed(L, Blk0, #b_blk{}) ->
%%% extracted values.
%%%
-ssa_opt_sink(#st{ssa=Blocks0}=St) ->
+ssa_opt_sink({#st{ssa=Blocks0}=St, FuncDb}) ->
Linear = beam_ssa:linearize(Blocks0),
%% Create a map with all variables that define get_tuple_element
@@ -1528,7 +2037,7 @@ ssa_opt_sink(#st{ssa=Blocks0}=St) ->
From = maps:get(V, Defs),
move_defs(V, From, To, A)
end, Blocks0, DefLoc),
- St#st{ssa=Blocks}.
+ {St#st{ssa=Blocks}, FuncDb}.
def_blocks([{L,#b_blk{is=Is}}|Bs]) ->
def_blocks_is(Is, L, def_blocks(Bs));
@@ -1763,3 +2272,9 @@ sub_arg(Old, Sub) ->
#{Old:=New} -> New;
#{} -> Old
end.
+
+new_var(#b_var{name={Base,N}}, Count) ->
+ true = is_integer(N), %Assertion.
+ {#b_var{name={Base,Count}},Count+1};
+new_var(#b_var{name=Base}, Count) ->
+ {#b_var{name={Base,Count}},Count+1}.
diff --git a/lib/compiler/src/beam_ssa_opt.hrl b/lib/compiler/src/beam_ssa_opt.hrl
new file mode 100644
index 0000000000..37711a6f48
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_opt.hrl
@@ -0,0 +1,53 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+
+-include("beam_ssa.hrl").
+
+-record(func_info,
+ {%% Local calls going in/out of this function.
+ in = ordsets:new() :: ordsets:ordset(func_id()),
+ out = ordsets:new() :: ordsets:ordset(func_id()),
+
+ %% Whether the function is exported or not; some optimizations may
+ %% need to be suppressed if it is.
+ exported = true :: boolean(),
+
+ %% The inferred types of each argument (as opposed to parameter),
+ %% indexed by call site.
+ %%
+ %% This is more effective than the naive approach of joining into a
+ %% "parameter_type" as we go as it lets us narrow parameter types
+ %% without having to visit all callers on each pass, which helps a lot
+ %% when dealing with co-recursive functions.
+ arg_types = [] :: list(arg_type_map()),
+
+ %% The inferred return type of this function, this is either [type()]
+ %% or [] to note absence.
+ ret_type = [] :: list()}).
+
+-type arg_key() :: {CallerId :: func_id(),
+ CallDst :: beam_ssa:b_var()}.
+-type arg_type_map() :: #{ arg_key() => term() }.
+
+%% Per-function metadata used by various optimization passes to perform
+%% module-level optimization. If a function is absent it means that
+%% module-level optimization has been turned off for said function.
+-type func_id() :: beam_ssa:b_local().
+-type func_info_db() :: #{ func_id() => #func_info{} }.
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index 32232e9b9f..fde1118c29 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -72,7 +72,7 @@
-import(lists, [all/2,any/2,append/1,duplicate/2,
foldl/3,last/1,map/2,member/2,partition/2,
- reverse/1,reverse/2,sort/1,zip/2]).
+ reverse/1,reverse/2,sort/1,splitwith/2,zip/2]).
-spec module(beam_ssa:b_module(), [compile:option()]) ->
{'ok',beam_ssa:b_module()}.
@@ -1031,7 +1031,7 @@ need_frame_1([#b_set{op=call,args=[Func|_]}|Is], Context) ->
case Func of
#b_remote{mod=#b_literal{val=Mod},
name=#b_literal{val=Name},
- arity=Arity} ->
+ arity=Arity} when is_atom(Mod), is_atom(Name) ->
case erl_bifs:is_exit_bif(Mod, Name, Arity) of
true ->
false;
@@ -1993,19 +1993,35 @@ reserve_zregs(Blocks, Intervals, Res) ->
end,
beam_ssa:fold_rpo(F, [0], Res, Blocks).
+reserve_zreg([#b_set{op=call,dst=Dst}],
+ #b_br{bool=Dst}, _ShortLived, A) ->
+ %% If type optimization has determined that the result of a call can be
+ %% used directly in a branch, we must avoid reserving a z register or code
+ %% generation will fail.
+ A;
reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst},
- #b_set{op={bif,'=:='},args=[Dst,Val]}], _Last, ShortLived, A0) ->
- case Val of
- #b_literal{val=Arity} when Arity bsr 32 =:= 0 ->
+ #b_set{op={bif,'=:='},args=[Dst,Val]}], Last, ShortLived, A0) ->
+ case {Val,Last} of
+ {#b_literal{val=Arity},#b_br{bool=#b_var{}}} when Arity bsr 32 =:= 0 ->
%% These two instructions can be combined to a test_arity
%% instruction provided that the arity variable is short-lived.
reserve_zreg_1(Dst, ShortLived, A0);
- _ ->
+ {_,_} ->
+ %% Either the arity is too big, or the boolean value is not
+ %% used in a conditional branch.
A0
end;
reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}],
#b_switch{}, ShortLived, A) ->
reserve_zreg_1(Dst, ShortLived, A);
+reserve_zreg([#b_set{op={bif,'xor'}}], _Last, _ShortLived, A) ->
+ %% There is no short, easy way to rewrite 'xor' to a series of
+ %% test instructions.
+ A;
+reserve_zreg([#b_set{op={bif,is_record}}], _Last, _ShortLived, A) ->
+ %% There is no short, easy way to rewrite is_record/2 to a series of
+ %% test instructions.
+ A;
reserve_zreg([#b_set{op=Op,dst=Dst}|Is], Last, ShortLived, A0) ->
IsZReg = case Op of
bs_match_string -> true;
@@ -2070,23 +2086,95 @@ reserve_freg([], Res) -> Res.
%% will allocate the lowest free X register for the variable.
reserve_xregs(Blocks, Res) ->
- F = fun(L, #b_blk{is=Is,last=Last}, R) ->
- {Xs0,Used0} = reserve_terminator(L, Last, Blocks, R),
- reserve_xregs_is(reverse(Is), R, Xs0, Used0)
- end,
- beam_ssa:fold_po(F, Res, Blocks).
-
+ Ls = reverse(beam_ssa:rpo(Blocks)),
+ reserve_xregs(Ls, Blocks, #{}, Res).
+
+reserve_xregs([L|Ls], Blocks, XsMap0, Res0) ->
+ #b_blk{anno=Anno,is=Is0,last=Last} = map_get(L, Blocks),
+
+ %% Calculate mapping from variable name to the preferred
+ %% register.
+ Xs0 = reserve_terminator(L, Is0, Last, Blocks, XsMap0, Res0),
+
+ %% We need to figure out where the code generator will
+ %% place instructions that will do a garbage collection.
+ %% Insert 'gc' markers as pseudo-instructions in the
+ %% instruction sequence.
+ Is1 = reverse(Is0),
+ Is2 = res_place_gc_instrs(Is1, []),
+ Is = res_place_allocate(Anno, Is2),
+
+ %% Add register hints for variables that are defined
+ %% in the (reversed) instruction sequence.
+ {Res,Xs} = reserve_xregs_is(Is, Res0, Xs0, []),
+
+ XsMap = XsMap0#{L=>Xs},
+ reserve_xregs(Ls, Blocks, XsMap, Res);
+reserve_xregs([], _, _, Res) -> Res.
+
+%% Insert explicit 'gc' markers points where there will
+%% be a garbage collection. (Note that the instruction
+%% sequence passed to this function is reversed.)
+
+res_place_gc_instrs([#b_set{op=phi}=I|Is], Acc) ->
+ res_place_gc_instrs(Is, [I|Acc]);
+res_place_gc_instrs([#b_set{op=Op}=I|Is], Acc)
+ when Op =:= call; Op =:= make_fun ->
+ case Acc of
+ [] ->
+ res_place_gc_instrs(Is, [I|Acc]);
+ [GC|_] when GC =:= gc; GC =:= test_heap ->
+ res_place_gc_instrs(Is, [I,gc|Acc]);
+ [_|_] ->
+ res_place_gc_instrs(Is, [I,gc|Acc])
+ end;
+res_place_gc_instrs([#b_set{op=Op,args=Args}=I|Is], Acc0) ->
+ case beam_ssa_codegen:classify_heap_need(Op, Args) of
+ neutral ->
+ case Acc0 of
+ [test_heap|Acc] ->
+ res_place_gc_instrs(Is, [test_heap,I|Acc]);
+ Acc ->
+ res_place_gc_instrs(Is, [I|Acc])
+ end;
+ {put,_} ->
+ case Acc0 of
+ [test_heap|Acc] ->
+ res_place_gc_instrs(Is, [test_heap,I|Acc]);
+ Acc ->
+ res_place_gc_instrs(Is, [test_heap,I|Acc])
+ end;
+ _ ->
+ res_place_gc_instrs(Is, [gc,I|Acc0])
+ end;
+res_place_gc_instrs([], Acc) ->
+ %% Reverse and replace 'test_heap' markers with 'gc'.
+ %% (The distinction is no longer useful.)
+ res_place_gc_instrs_rev(Acc, []).
+
+res_place_gc_instrs_rev([test_heap|Is], [gc|_]=Acc) ->
+ res_place_gc_instrs_rev(Is, Acc);
+res_place_gc_instrs_rev([test_heap|Is], Acc) ->
+ res_place_gc_instrs_rev(Is, [gc|Acc]);
+res_place_gc_instrs_rev([gc|Is], [gc|_]=Acc) ->
+ res_place_gc_instrs_rev(Is, Acc);
+res_place_gc_instrs_rev([I|Is], Acc) ->
+ res_place_gc_instrs_rev(Is, [I|Acc]);
+res_place_gc_instrs_rev([], Acc) -> Acc.
+
+res_place_allocate(#{yregs:=_}, Is) ->
+ %% There will be an 'allocate' instruction inserted here.
+ Is ++ [gc];
+res_place_allocate(#{}, Is) -> Is.
+
+reserve_xregs_is([gc|Is], Res, Xs0, Used) ->
+ %% At this point, the code generator will place an instruction
+ %% that does a garbage collection. We must prune the remembered
+ %% registers.
+ Xs = res_xregs_prune(Xs0, Used, Res),
+ reserve_xregs_is(Is, Res, Xs, Used);
reserve_xregs_is([#b_set{op=Op,dst=Dst,args=Args}=I|Is], Res0, Xs0, Used0) ->
- Xs1 = case is_gc_safe(I) of
- true ->
- Xs0;
- false ->
- %% There may be a garbage collection after executing this
- %% instruction. We will need prune the list of preferred
- %% X registers.
- res_xregs_prune(Xs0, Used0, Res0)
- end,
- Res = reserve_xreg(Dst, Xs1, Res0),
+ Res = reserve_xreg(Dst, Xs0, Res0),
Used1 = ordsets:union(Used0, beam_ssa:used(I)),
Used = ordsets:del_element(Dst, Used1),
case Op of
@@ -2097,28 +2185,74 @@ reserve_xregs_is([#b_set{op=Op,dst=Dst,args=Args}=I|Is], Res0, Xs0, Used0) ->
Xs = reserve_call_args(tl(Args)),
reserve_xregs_is(Is, Res, Xs, Used);
_ ->
- reserve_xregs_is(Is, Res, Xs1, Used)
+ reserve_xregs_is(Is, Res, Xs0, Used)
end;
-reserve_xregs_is([], Res, _Xs, _Used) -> Res.
-
-reserve_terminator(L, #b_br{bool=#b_literal{val=true},succ=Succ}, Blocks, Res) ->
- case maps:get(Succ, Blocks) of
+reserve_xregs_is([], Res, Xs, _Used) ->
+ {Res,Xs}.
+
+%% Pick up register hints from the successors of this blocks.
+reserve_terminator(_L, _Is, #b_br{bool=#b_var{},succ=Succ,fail=?BADARG_BLOCK},
+ _Blocks, XsMap, _Res) ->
+ %% We know that no variables are used at ?BADARG_BLOCK, so
+ %% any register hints from the success blocks are safe to use.
+ map_get(Succ, XsMap);
+reserve_terminator(L, Is, #b_br{bool=#b_var{},succ=Succ,fail=Fail},
+ Blocks, XsMap, Res) when Succ =/= Fail ->
+ #{Succ:=SuccBlk,Fail:=FailBlk} = Blocks,
+ case {SuccBlk,FailBlk} of
+ {#b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}},
+ #b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}}} ->
+ %% Both branches ultimately transfer to the same
+ %% block (via two blocks with no instructions).
+ %% Pick up register hints from the phi nodes
+ %% in the common block.
+ #{PhiL:=#b_blk{is=PhiIs}} = Blocks,
+ Xs = res_xregs_from_phi(PhiIs, Succ, Res, #{}),
+ res_xregs_from_phi(PhiIs, Fail, Res, Xs);
+ {_,_} when Is =/= [] ->
+ case last(Is) of
+ #b_set{op=succeeded,args=[Arg]} ->
+ %% We know that Arg will not be used at the failure
+ %% label, so we can pick up register hints from the
+ %% success label.
+ Br = #b_br{bool=#b_literal{val=true},succ=Succ,fail=Succ},
+ case reserve_terminator(L, [], Br, Blocks, XsMap, Res) of
+ #{Arg:=Reg} -> #{Arg=>Reg};
+ #{} -> #{}
+ end;
+ _ ->
+ %% Register hints from the success block may not
+ %% be safe at the failure block, and vice versa.
+ #{}
+ end;
+ {_,_} ->
+ %% Register hints from the success block may not
+ %% be safe at the failure block, and vice versa.
+ #{}
+ end;
+reserve_terminator(L, Is, #b_br{bool=#b_literal{val=true},succ=Succ},
+ Blocks, XsMap, Res) ->
+ case map_get(Succ, Blocks) of
#b_blk{is=[],last=Last} ->
- reserve_terminator(Succ, Last, Blocks, Res);
- #b_blk{is=[_|_]=Is} ->
- {res_xregs_from_phi(Is, L, Res, #{}),[]}
+ reserve_terminator(Succ, Is, Last, Blocks, XsMap, Res);
+ #b_blk{is=[_|_]=PhiIs} ->
+ res_xregs_from_phi(PhiIs, L, Res, #{})
end;
-reserve_terminator(_, Last, _, _) ->
- {#{},beam_ssa:used(Last)}.
+reserve_terminator(_, _, _, _, _, _) -> #{}.
+%% Pick up a reservation from a phi node.
res_xregs_from_phi([#b_set{op=phi,dst=Dst,args=Args}|Is],
Pred, Res, Acc) ->
case [V || {#b_var{}=V,L} <- Args, L =:= Pred] of
[] ->
+ %% The value of the phi node for this predecessor
+ %% is a literal. Nothing to do here.
res_xregs_from_phi(Is, Pred, Res, Acc);
[V] ->
case Res of
#{Dst:={prefer,Reg}} ->
+ %% Try placing V in the same register as for
+ %% the phi node.
res_xregs_from_phi(Is, Pred, Res, Acc#{V=>Reg});
#{Dst:=_} ->
res_xregs_from_phi(Is, Pred, Res, Acc)
@@ -2138,12 +2272,12 @@ reserve_call_args([], _, Xs) -> Xs.
reserve_xreg(V, Xs, Res) ->
case Res of
#{V:=_} ->
- %% Already reserved.
+ %% Already reserved (but not as an X register).
Res;
#{} ->
case Xs of
#{V:=X} ->
- %% Add a hint that a specific X register is
+ %% Add a hint that this specific X register is
%% preferred, unless it is already in use.
Res#{V=>{prefer,X}};
#{} ->
@@ -2152,23 +2286,15 @@ reserve_xreg(V, Xs, Res) ->
end
end.
-is_gc_safe(#b_set{op=phi}) ->
- false;
-is_gc_safe(#b_set{op=Op,args=Args}) ->
- case beam_ssa_codegen:classify_heap_need(Op, Args) of
- neutral -> true;
- {put,_} -> true;
- _ -> false
- end.
-
%% res_xregs_prune(PreferredRegs, Used, Res) -> PreferredRegs.
-%% Prune the list of preferred to only include X registers that
-%% are guaranteed to survice a garbage collection.
+%% Prune the list of preferred registers, to make sure that
+%% there are no "holes" (uninitialized X registers) when
+%% invoking the garbage collector.
-res_xregs_prune(Xs, Used, Res) ->
+res_xregs_prune(Xs, Used, Res) when map_size(Xs) =/= 0 ->
%% The number of safe registers is the number of the X registers
%% used after this point. The actual number of safe registers may
- %% be highter than this number, but this is a conservative safe
+ %% be higher than this number, but this is a conservative safe
%% estimate.
NumSafe = foldl(fun(V, N) ->
case Res of
@@ -2180,7 +2306,8 @@ res_xregs_prune(Xs, Used, Res) ->
%% Remove unsafe registers from the list of potential
%% preferred registers.
- maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs).
+ maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs);
+res_xregs_prune(Xs, _Used, _Res) -> Xs.
%%%
%%% Register allocation using linear scan.
@@ -2486,7 +2613,7 @@ rel2fam(S0) ->
sofs:to_external(S).
split_phis(Is) ->
- partition(fun(#b_set{op=Op}) -> Op =:= phi end, Is).
+ splitwith(fun(#b_set{op=Op}) -> Op =:= phi end, Is).
is_yreg({y,_}) -> true;
is_yreg({x,_}) -> false;
diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl
new file mode 100644
index 0000000000..426efa2cc9
--- /dev/null
+++ b/lib/compiler/src/beam_ssa_share.erl
@@ -0,0 +1,370 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+
+%%
+%% Share code for semantically equivalent blocks referred to
+%% to by `br` and `switch` instructions.
+%%
+%% A similar optimization is done in beam_jump, but doing it here as
+%% well is beneficial as it may enable other optimizations. If there
+%% are many semantically equivalent clauses, this optimization can
+%% substanstially decrease compilation times.
+%%
+%% block/2 is called from the liveness optimization pass in
+%% beam_ssa_opt, as code sharing helps the liveness pass and vice
+%% versa.
+%%
+
+-module(beam_ssa_share).
+-export([module/2,block/2]).
+
+-include("beam_ssa.hrl").
+
+-import(lists, [keyfind/3,reverse/1,sort/1]).
+
+-spec module(beam_ssa:b_module(), [compile:option()]) ->
+ {'ok',beam_ssa:b_module()}.
+
+module(#b_module{body=Fs0}=Module, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
+ {ok,Module#b_module{body=Fs}}.
+
+-spec block(Blk0, Blocks0) -> Blk when
+ Blk0 :: beam_ssa:b_blk(),
+ Blocks0 :: beam_ssa:block_map(),
+ Blk :: beam_ssa:b_blk().
+
+block(#b_blk{last=Last0}=Blk, Blocks) ->
+ case share_terminator(Last0, Blocks) of
+ none -> Blk;
+ Last -> Blk#b_blk{last=beam_ssa:normalize(Last)}
+ end.
+
+%%%
+%%% Local functions.
+%%%
+
+function(#b_function{anno=Anno,bs=Blocks0}=F) ->
+ try
+ PO = reverse(beam_ssa:rpo(Blocks0)),
+ {Blocks1,Changed} = blocks(PO, Blocks0, false),
+ Blocks = case Changed of
+ true ->
+ beam_ssa:trim_unreachable(Blocks1);
+ false ->
+ Blocks0
+ end,
+ F#b_function{bs=Blocks}
+ catch
+ Class:Error:Stack ->
+ #{func_info:={_,Name,Arity}} = Anno,
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+blocks([L|Ls], Blocks, Changed) ->
+ #b_blk{last=Last0} = Blk0 = map_get(L, Blocks),
+ case block(Blk0, Blocks) of
+ #b_blk{last=Last0} ->
+ blocks(Ls, Blocks, Changed);
+ #b_blk{}=Blk ->
+ blocks(Ls, Blocks#{L:=Blk}, true)
+ end;
+blocks([], Blocks, Changed) ->
+ {Blocks,Changed}.
+
+share_terminator(#b_br{bool=#b_var{},succ=Succ0,fail=Fail0}=Br, Blocks) ->
+ {Succ,SuccBlk} = shortcut_nonempty_block(Succ0, Blocks),
+ {Fail,FailBlk} = shortcut_nonempty_block(Fail0, Blocks),
+ case are_equivalent(Succ, SuccBlk, Fail, FailBlk, Blocks) of
+ true ->
+ %% The blocks are semantically equivalent.
+ Br#b_br{succ=Succ,fail=Succ};
+ false ->
+ if
+ Succ =:= Succ0, Fail =:= Fail0 ->
+ %% None of blocks were cut short.
+ none;
+ true ->
+ %% One or both labels were cut short
+ %% to avoid jumping to an empty block.
+ Br#b_br{succ=Succ,fail=Fail}
+ end
+ end;
+share_terminator(#b_switch{}=Sw, Blocks) ->
+ share_switch(Sw, Blocks);
+share_terminator(_Last, _Blocks) -> none.
+
+%% Test whether the two blocks are semantically equivalent. This
+%% function is specially optimized to return `false` as fast as
+%% possible if the blocks are not equivalent, as that is the common
+%% case.
+
+are_equivalent(_Succ, _, ?BADARG_BLOCK, _, _Blocks) ->
+ %% ?BADARG_BLOCK is special. Sharing could be incorrect.
+ false;
+are_equivalent(_Succ, #b_blk{is=Is1,last=#b_ret{arg=RetVal1}=Ret1},
+ _Fail, #b_blk{is=Is2,last=#b_ret{arg=RetVal2}=Ret2}, _Blocks) ->
+ case {RetVal1,RetVal2} of
+ {#b_literal{},#b_literal{}} ->
+ case RetVal1 =:= RetVal2 of
+ true ->
+ %% The return values are identical literals. We
+ %% only need to compare the canonicalized bodies.
+ Can1 = canonical_is(Is1),
+ Can2 = canonical_is(Is2),
+ Can1 =:= Can2;
+ false ->
+ %% Non-equal literals.
+ false
+ end;
+ {#b_var{},#b_var{}} ->
+ %% The return values are varibles. We must canonicalize
+ %% the blocks (including returns) and compare them.
+ Can1 = canonical_is(Is1 ++ [Ret1]),
+ Can2 = canonical_is(Is2 ++ [Ret2]),
+ Can1 =:= Can2;
+ {_,_} ->
+ %% One literal and one variable.
+ false
+ end;
+are_equivalent(Succ,
+ #b_blk{is=Is1,
+ last=#b_br{bool=#b_literal{val=true},
+ succ=Target}},
+ Fail,
+ #b_blk{is=Is2,
+ last=#b_br{bool=#b_literal{val=true},
+ succ=Target}},
+ Blocks) ->
+ %% Both blocks end with an unconditional branch to the
+ %% same target block. If the target block has phi nodes,
+ %% we must pick up the values from the phi nodes and
+ %% compare them.
+ #b_blk{is=Is} = map_get(Target, Blocks),
+ Phis1 = canonical_terminator_phis(Is, Succ),
+ Phis2 = canonical_terminator_phis(Is, Fail),
+ case {Phis1,Phis2} of
+ {[#b_set{args=[#b_literal{}]}|_],_} when Phis1 =/= Phis2 ->
+ %% Different values are used in the phi nodes.
+ false;
+ {_,[#b_set{args=[#b_literal{}]}|_]} when Phis1 =/= Phis2 ->
+ %% Different values are used in the phi nodes.
+ false;
+ {_,_} ->
+ %% The values in the phi nodes are variables or identical
+ %% literals. We must canonicalize the blocks and compare
+ %% them.
+ Can1 = canonical_is(Is1 ++ Phis1),
+ Can2 = canonical_is(Is2 ++ Phis2),
+ Can1 =:= Can2
+ end;
+are_equivalent(Succ0, #b_blk{is=Is1,last=#b_br{bool=#b_var{},fail=Same}},
+ Fail0, #b_blk{is=Is2,last=#b_br{bool=#b_var{},fail=Same}},
+ Blocks) ->
+ %% Two-way branches with identical failure labels. First compare the
+ %% canonicalized bodies of the blocks.
+ case canonical_is(Is1) =:= canonical_is(Is2) of
+ false ->
+ %% Different bodies.
+ false;
+ true ->
+ %% Bodies were equal. That is fairly uncommon, so to keep
+ %% the code simple we will rewrite the `br` to a `switch`
+ %% and let share_switch/2 do the work of following the
+ %% branches.
+ Sw = #b_switch{arg=#b_var{name=not_used},fail=Fail0,
+ list=[{#b_literal{},Succ0}]},
+ #b_switch{fail=Fail,list=[{_,Succ}]} = share_switch(Sw, Blocks),
+ Fail =:= Succ
+ end;
+are_equivalent(_, _, _, _, _) -> false.
+
+share_switch(#b_switch{fail=Fail0,list=List0}=Sw, Blocks) ->
+ Prep = share_prepare_sw([{value,Fail0}|List0], Blocks, 0, []),
+ Res = do_share_switch(Prep, Blocks, []),
+ [{_,Fail}|List] = [VL || {_,VL} <- sort(Res)],
+ Sw#b_switch{fail=Fail,list=List}.
+
+share_prepare_sw([{V,L0}|T], Blocks, N, Acc) ->
+ {L,_Blk} = shortcut_nonempty_block(L0, Blocks),
+ share_prepare_sw(T, Blocks, N+1, [{{L,#{}},{N,{V,L}}}|Acc]);
+share_prepare_sw([], _, _, Acc) -> Acc.
+
+do_share_switch(Prep, Blocks, Acc) ->
+ Map = share_switch_1(Prep, Blocks, #{}),
+ share_switch_2(maps:values(Map), Blocks, Acc).
+
+share_switch_1([{Next0,Res}|T], Blocks, Map) ->
+ {Can,Next} = canonical_block(Next0, Blocks),
+ case Map of
+ #{Can:=Ls} ->
+ share_switch_1(T, Blocks, Map#{Can:=[{Next,Res}|Ls]});
+ #{} ->
+ share_switch_1(T, Blocks, Map#{Can=>[{Next,Res}]})
+ end;
+share_switch_1([], _Blocks, Map) -> Map.
+
+share_switch_2([[{_,{N,Res}}]|T], Blocks, Acc) ->
+ %% This block is not equivalent to any other block.
+ share_switch_2(T, Blocks, [{N,Res}|Acc]);
+share_switch_2([[{done,{_,{_,Common}}}|_]=Eqs|T], Blocks, Acc0) ->
+ %% Two or more blocks are semantically equivalent, and all blocks
+ %% are either terminated with a `ret` or a `br` to the same target
+ %% block. Replace the labels in the `switch` for all of those
+ %% blocks with the label for the first of the blocks.
+ Acc = [{N,{V,Common}} || {done,{N,{V,_}}} <- Eqs] ++ Acc0,
+ share_switch_2(T, Blocks, Acc);
+share_switch_2([[{_,_}|_]=Prep|T], Blocks, Acc0) ->
+ %% Two or more blocks are semantically equivalent, but they have
+ %% different successful successor blocks. Now we must check
+ %% recursively whether the successor blocks are equivalent too.
+ Acc = do_share_switch(Prep, Blocks, Acc0),
+ share_switch_2(T, Blocks, Acc);
+share_switch_2([], _, Acc) -> Acc.
+
+canonical_block({L,VarMap0}, Blocks) ->
+ #b_blk{is=Is,last=Last0} = map_get(L, Blocks),
+ case canonical_terminator(L, Last0, Blocks) of
+ none ->
+ %% The block has a terminator that we don't handle.
+ {{none,L},done};
+ {Last,done} ->
+ %% The block ends with a `ret` or an unconditional `br` to
+ %% another block.
+ {Can,_VarMap} = canonical_is(Is ++ Last, VarMap0, []),
+ {Can,done};
+ {Last,Next} ->
+ %% The block ends with a conditional branch.
+ {Can,VarMap} = canonical_is(Is ++ Last, VarMap0, []),
+ {Can,{Next,VarMap}}
+ end.
+
+%% Translate a sequence of instructions to a canonical representation. If the
+%% canonical representation of two blocks compare equal, the blocks are
+%% semantically equivalent. The following translations are done:
+%%
+%% * Variables defined in the instruction sequence are replaced with
+%% {var,0}, {var,1}, and so on. Free variables are not changed.
+%%
+%% * `location` annotations that would produce a `line` instruction are
+%% kept. All other annotations are cleared.
+%%
+%% * Instructions are repackaged into tuples instead of into the
+%% usual records. The main reason is to avoid violating the types for
+%% the SSA records. We can simplify things a little by linking the
+%% instructions directly instead of putting them into a list.
+
+canonical_is(Is) ->
+ {Can,_} = canonical_is(Is, #{}, []),
+ Can.
+
+canonical_is([#b_set{op=Op,dst=Dst,args=Args0}=I|Is], VarMap0, Acc) ->
+ Args = [canonical_arg(Arg, VarMap0) || Arg <-Args0],
+ Var = {var,map_size(VarMap0)},
+ VarMap = VarMap0#{Dst=>Var},
+ LineAnno = case Op of
+ bs_match ->
+ %% The location annotation for a bs_match instruction
+ %% is only used in warnings, never to emit a `line`
+ %% instruction. Therefore, it should not be included.
+ [];
+ _ ->
+ %% The location annotation will be used in a `line`
+ %% instruction. It must be included.
+ beam_ssa:get_anno(location, I, none)
+ end,
+ canonical_is(Is, VarMap, {Op,LineAnno,Var,Args,Acc});
+canonical_is([#b_ret{arg=Arg}], VarMap, Acc0) ->
+ Acc1 = case Acc0 of
+ {call,_Anno,Var,[#b_local{}|_]=Args,PrevAcc} ->
+ %% This is a tail-recursive call to a local function.
+ %% There will be no line instruction generated;
+ %% thus, the annotation is not significant.
+ {call,[],Var,Args,PrevAcc};
+ _ ->
+ Acc0
+ end,
+ {{ret,canonical_arg(Arg, VarMap),Acc1},VarMap};
+canonical_is([#b_br{bool=#b_var{},fail=Fail}], VarMap, Acc) ->
+ {{br,succ,Fail,Acc},VarMap};
+canonical_is([#b_br{succ=Succ}], VarMap, Acc) ->
+ {{br,Succ,Acc},VarMap};
+canonical_is([], VarMap, Acc) ->
+ {Acc,VarMap}.
+
+canonical_terminator(_L, #b_ret{}=Ret, _Blocks) ->
+ {[Ret],done};
+canonical_terminator(L, #b_br{bool=#b_literal{val=true},succ=Succ}=Br, Blocks) ->
+ #b_blk{is=Is} = map_get(Succ, Blocks),
+ case canonical_terminator_phis(Is, L) of
+ [] ->
+ {[],Succ};
+ [_|_]=Phis ->
+ {Phis ++ [Br],done}
+ end;
+canonical_terminator(_L, #b_br{bool=#b_var{},succ=Succ}=Br, _Blocks) ->
+ {[Br],Succ};
+canonical_terminator(_, _, _) -> none.
+
+canonical_terminator_phis([#b_set{op=phi,args=PhiArgs}=Phi|Is], L) ->
+ {Value,L} = keyfind(L, 2, PhiArgs),
+ [Phi#b_set{op=copy,args=[Value]}|canonical_terminator_phis(Is, L)];
+canonical_terminator_phis([#b_set{op=peek_message}=I|_], L) ->
+ %% We could get stuck into an infinite loop if we allowed the
+ %% comparisons to continue into this block. Force an unequal
+ %% compare with all other predecessors of this block.
+ [I#b_set{op=copy,args=[#b_literal{val=L}]}];
+canonical_terminator_phis(_, _) -> [].
+
+canonical_arg(#b_var{}=Var, VarMap) ->
+ case VarMap of
+ #{Var:=CanonicalVar} ->
+ CanonicalVar;
+ #{} ->
+ Var
+ end;
+canonical_arg(#b_remote{mod=Mod,name=Name}, VarMap) ->
+ {remote,canonical_arg(Mod, VarMap),
+ canonical_arg(Name, VarMap)};
+canonical_arg(Other, _VarMap) -> Other.
+
+%% Shortcut branches to empty blocks if safe.
+
+shortcut_nonempty_block(L, Blocks) ->
+ case map_get(L, Blocks) of
+ #b_blk{is=[],last=#b_br{bool=#b_literal{val=true},succ=Succ}}=Blk ->
+ %% This block is empty.
+ case is_forbidden(Succ, Blocks) of
+ false ->
+ shortcut_nonempty_block(Succ, Blocks);
+ true ->
+ {L,Blk}
+ end;
+ #b_blk{}=Blk ->
+ {L,Blk}
+ end.
+
+is_forbidden(L, Blocks) ->
+ case map_get(L, Blocks) of
+ #b_blk{is=[#b_set{op=phi}|_]} -> true;
+ #b_blk{is=[#b_set{op=peek_message}|_]} -> true;
+ #b_blk{} -> false
+ end.
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 95fc3bb0e9..38ea5e6914 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -19,19 +19,22 @@
%%
-module(beam_ssa_type).
--export([opt/2]).
+-export([opt_start/4, opt_continue/4, opt_finish/3]).
--include("beam_ssa.hrl").
+-include("beam_ssa_opt.hrl").
-import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2,
- reverse/1,sort/1]).
+ partition/2,reverse/1,sort/1]).
-define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}).
--record(d, {ds :: #{beam_ssa:b_var():=beam_ssa:b_set()},
- ls :: #{beam_ssa:label():=type_db()},
- once :: cerl_sets:set(beam_ssa:b_var()),
- sub :: #{beam_ssa:b_var():=beam_ssa:value()}
- }).
+-record(d,
+ {ds :: #{beam_ssa:b_var():=beam_ssa:b_set()},
+ ls :: #{beam_ssa:label():=type_db()},
+ once :: cerl_sets:set(beam_ssa:b_var()),
+ func_id :: func_id(),
+ func_db :: func_info_db(),
+ sub = #{} :: #{beam_ssa:b_var():=beam_ssa:value()},
+ ret_type = [] :: [type()]}).
-define(ATOM_SET_SIZE, 5).
@@ -49,36 +52,155 @@
{'binary',pos_integer()} | 'cons' | 'float' | 'list' | 'map' | 'nil' |'number'.
-type type_db() :: #{beam_ssa:var_name():=type()}.
--spec opt([{Label0,Block0}], Args) -> [{Label,Block}] when
- Label0 :: beam_ssa:label(),
- Block0 :: beam_ssa:b_blk(),
+-spec opt_start(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
+ Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
Args :: [beam_ssa:b_var()],
- Label :: beam_ssa:label(),
- Block :: beam_ssa:b_blk().
-
-opt(Linear, Args) ->
- UsedOnce = used_once(Linear, Args),
+ Anno :: beam_ssa:anno(),
+ FuncDb :: func_info_db().
+opt_start(Linear, Args, Anno, FuncDb) ->
+ %% This is the first run through the module, so our arg_types can be
+ %% incomplete as we may not have visited all call sites at least once.
Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ opt_continue_1(Linear, Args, get_func_id(Anno), Ts, FuncDb).
+
+-spec opt_continue(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when
+ Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
+ Args :: [beam_ssa:b_var()],
+ Anno :: beam_ssa:anno(),
+ FuncDb :: func_info_db().
+opt_continue(Linear, Args, Anno, FuncDb) ->
+ Id = get_func_id(Anno),
+ case FuncDb of
+ #{ Id := #func_info{exported=false,arg_types=ArgTypes} } ->
+ %% This is a local function and we're guaranteed to have visited
+ %% every call site at least once, so we know that the parameter
+ %% types are at least as narrow as the join of all argument types.
+ Ts = join_arg_types(Args, ArgTypes, Anno),
+ opt_continue_1(Linear, Args, Id, Ts, FuncDb);
+ #{} ->
+ %% We can't infer the parameter types of exported functions, nor
+ %% the ones where module-level optimization is disabled, but
+ %% running the pass again could still help other functions.
+ Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]),
+ opt_continue_1(Linear, Args, Id, Ts, FuncDb)
+ end.
+
+join_arg_types(Args, ArgTypes, Anno) ->
+ %% We suppress type optimization for parameters that have already been
+ %% optimized by another pass, as they may have done things we have no idea
+ %% how to interpret and running them over could generate incorrect code.
+ ParamTypes = maps:get(parameter_type_info, Anno, #{}),
+ Ts0 = join_arg_types_1(Args, ArgTypes, #{}),
+ maps:fold(fun(Arg, _V, Ts) ->
+ maps:put(Arg, any, Ts)
+ end, Ts0, ParamTypes).
+
+join_arg_types_1([Arg | Args], [TM | TMs], Ts) when map_size(TM) =/= 0 ->
+ join_arg_types_1(Args, TMs, Ts#{ Arg => join(maps:values(TM))});
+join_arg_types_1([Arg | Args], [_TM | TMs], Ts) ->
+ join_arg_types_1(Args, TMs, Ts#{ Arg => any });
+join_arg_types_1([], [], Ts) ->
+ Ts.
+
+-spec opt_continue_1(Linear, Args, Id, Ts, FuncDb) -> Result when
+ Linear :: [{non_neg_integer(), beam_ssa:b_blk()}],
+ Args :: [beam_ssa:b_var()],
+ Id :: func_id(),
+ Ts :: type_db(),
+ FuncDb :: func_info_db(),
+ Result :: {Linear, FuncDb}.
+opt_continue_1(Linear0, Args, Id, Ts, FuncDb0) ->
+ UsedOnce = used_once(Linear0, Args),
FakeCall = #b_set{op=call,args=[#b_remote{mod=#b_literal{val=unknown},
name=#b_literal{val=unknown},
arity=0}]},
Defs = maps:from_list([{Var,FakeCall#b_set{dst=Var}} ||
#b_var{}=Var <- Args]),
- D = #d{ds=Defs,ls=#{0=>Ts,?BADARG_BLOCK=>#{}},
- once=UsedOnce,sub=#{}},
- opt_1(Linear, D).
-opt_1([{L,Blk}|Bs], #d{ls=Ls}=D) ->
+ D = #d{ func_db=FuncDb0,
+ func_id=Id,
+ ds=Defs,
+ ls=#{0=>Ts,?BADARG_BLOCK=>#{}},
+ once=UsedOnce },
+
+ {Linear, FuncDb, NewRet} = opt_1(Linear0, D, []),
+
+ case FuncDb of
+ #{ Id := Entry0 } ->
+ Entry = Entry0#func_info{ret_type=NewRet},
+ {Linear, FuncDb#{ Id := Entry }};
+ #{} ->
+ %% Module-level optimizations have been turned off for this
+ %% function.
+ {Linear, FuncDb}
+ end.
+
+-spec opt_finish(Args, Anno, FuncDb) -> {Anno, FuncDb} when
+ Args :: [beam_ssa:b_var()],
+ Anno :: beam_ssa:anno(),
+ FuncDb :: func_info_db().
+opt_finish(Args, Anno, FuncDb) ->
+ Id = get_func_id(Anno),
+ case FuncDb of
+ #{ Id := #func_info{exported=false,arg_types=ArgTypes} } ->
+ ParamInfo0 = maps:get(parameter_type_info, Anno, #{}),
+ ParamInfo = opt_finish_1(Args, ArgTypes, ParamInfo0),
+ {Anno#{ parameter_type_info => ParamInfo }, FuncDb};
+ #{} ->
+ {Anno, FuncDb}
+ end.
+
+opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo)
+ when is_map_key(Arg, ParamInfo); %% See join_arg_types/3
+ map_size(TypeMap) =:= 0 ->
+ opt_finish_1(Args, TypeMaps, ParamInfo);
+opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) ->
+ case join(maps:values(TypeMap)) of
+ any ->
+ opt_finish_1(Args, TypeMaps, ParamInfo0);
+ JoinedType ->
+ JoinedType = verified_type(JoinedType),
+ ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) },
+ opt_finish_1(Args, TypeMaps, ParamInfo)
+ end;
+opt_finish_1([], [], ParamInfo) ->
+ ParamInfo.
+
+validator_anno(#t_tuple{size=Size,exact=Exact}) ->
+ beam_validator:type_anno(tuple, Size, Exact);
+validator_anno(#t_integer{elements={Same,Same}}) ->
+ beam_validator:type_anno(integer, Same);
+validator_anno(#t_integer{}) ->
+ beam_validator:type_anno(integer);
+validator_anno(float) ->
+ beam_validator:type_anno(float);
+validator_anno(#t_atom{elements=[Val]}) ->
+ beam_validator:type_anno(atom, Val);
+validator_anno(#t_atom{}=A) ->
+ case t_is_boolean(A) of
+ true -> beam_validator:type_anno(bool);
+ false -> beam_validator:type_anno(atom)
+ end;
+validator_anno(T) ->
+ beam_validator:type_anno(T).
+
+get_func_id(Anno) ->
+ #{func_info:={_Mod, Name, Arity}} = Anno,
+ #b_local{name=#b_literal{val=Name}, arity=Arity}.
+
+opt_1([{L,Blk}|Bs], #d{ls=Ls}=D, Acc) ->
case Ls of
#{L:=Ts} ->
- opt_2(L, Blk, Bs, Ts, D);
+ opt_2(L, Blk, Bs, Ts, D, Acc);
#{} ->
%% This block is never reached. Discard it.
- opt_1(Bs, D)
+ opt_1(Bs, D, Acc)
end;
-opt_1([], #d{}) -> [].
+opt_1([], D, Acc) ->
+ #d{func_db=FuncDb,ret_type=NewRet} = D,
+ {reverse(Acc), FuncDb, NewRet}.
-opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, #d{sub=Sub}=D0) ->
+opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, #d{sub=Sub}=D0, Acc) ->
case Is0 of
[#b_set{op=call,dst=Dst,
args=[#b_remote{mod=#b_literal{val=Mod},
@@ -94,34 +216,43 @@ opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, #d{sub=Sub}=D0) ->
Ret = #b_ret{arg=Dst},
Blk = Blk0#b_blk{is=[I],last=Ret},
Ls = maps:remove(L, D0#d.ls),
- D = D0#d{ls=Ls},
- [{L,Blk}|opt_1(Bs, D)];
+
+ %% We potentially lack a return value.
+ RetType = join([none | D0#d.ret_type]),
+
+ D = D0#d{ls=Ls,ret_type=[RetType]},
+ opt_1(Bs, D, [{L,Blk} | Acc]);
false ->
- opt_3(L, Blk0, Bs, Ts, D0)
+ opt_3(L, Blk0, Bs, Ts, D0, Acc)
end;
_ ->
- opt_3(L, Blk0, Bs, Ts, D0)
+ opt_3(L, Blk0, Bs, Ts, D0, Acc)
end.
opt_3(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0,
- #d{ds=Ds0,ls=Ls0,sub=Sub0}=D0) ->
- {Is,Ts,Ds,Sub} = opt_is(Is0, Ts0, Ds0, Ls0, Sub0, []),
- D1 = D0#d{ds=Ds,sub=Sub},
- Last1 = simplify_terminator(Last0, Sub, Ts),
+ #d{ds=Ds0,ls=Ls0,sub=Sub0,func_db=Fdb0}=D0, Acc) ->
+ {Is,Ts,Ds,Fdb,Sub} = opt_is(Is0, Ts0, Ds0, Fdb0, Ls0, D0, Sub0, []),
+ D1 = D0#d{ds=Ds,sub=Sub,func_db=Fdb},
+ Last1 = simplify_terminator(Last0, Sub, Ts, Ds),
Last = opt_terminator(Last1, Ts, Ds),
D = update_successors(Last, Ts, D1),
Blk = Blk0#b_blk{is=Is,last=Last},
- [{L,Blk}|opt_1(Bs, D)].
+ opt_1(Bs, D, [{L,Blk} | Acc]).
-simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts) ->
+simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts, _Ds) ->
Br#b_br{bool=simplify_arg(Bool, Sub, Ts)};
-simplify_terminator(#b_switch{arg=Arg}=Sw, Sub, Ts) ->
+simplify_terminator(#b_switch{arg=Arg}=Sw, Sub, Ts, _Ds) ->
Sw#b_switch{arg=simplify_arg(Arg, Sub, Ts)};
-simplify_terminator(#b_ret{arg=Arg}=Ret, Sub, Ts) ->
- Ret#b_ret{arg=simplify_arg(Arg, Sub, Ts)}.
+simplify_terminator(#b_ret{arg=Arg}=Ret, Sub, Ts, Ds) ->
+ %% Reducing the result of a call to a literal (fairly common for 'ok')
+ %% breaks tail call optimization.
+ case Ds of
+ #{ Arg := #b_set{op=call}} -> Ret;
+ #{} -> Ret#b_ret{arg=simplify_arg(Arg, Sub, Ts)}
+ end.
opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is],
- Ts0, Ds0, Ls, Sub0, Acc) ->
+ Ts0, Ds0, Fdb, Ls, D, Sub0, Acc) ->
%% Simplify the phi node by removing all predecessor blocks that no
%% longer exists or no longer branches to this block.
Args = [{simplify_arg(Arg, Sub0, Ts0),From} ||
@@ -132,15 +263,61 @@ opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is],
%% value or if the values are identical.
[{Val,_}|_] = Args,
Sub = Sub0#{Dst=>Val},
- opt_is(Is, Ts0, Ds0, Ls, Sub, Acc);
+ opt_is(Is, Ts0, Ds0, Fdb, Ls, D, Sub, Acc);
false ->
I = I0#b_set{args=Args},
Ts = update_types(I, Ts0, Ds0),
Ds = Ds0#{Dst=>I},
- opt_is(Is, Ts, Ds, Ls, Sub0, [I|Acc])
+ opt_is(Is, Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc])
+ end;
+opt_is([#b_set{op=call,args=Args0,dst=Dst}=I0 | Is],
+ Ts0, Ds0, Fdb0, Ls, D, Sub, Acc) ->
+ Args = simplify_args(Args0, Sub, Ts0),
+ I1 = beam_ssa:normalize(I0#b_set{args=Args}),
+
+ %% This is a bit of a kludge; we know that any instruction whose return
+ %% type is 'none' will fail at runtime, but we don't yet have a way to cut
+ %% a block short so we move on like nothing nothing happened.
+ %%
+ %% This complicates argument type optimization as unreachable calls can
+ %% add types that will never occur, so we skip optimizing this call if
+ %% the type of any of its arguments is 'none'.
+ [_Callee | Rest] = Args,
+ case all(fun(Arg) -> get_type(Arg, Ts0) =/= none end, Rest) of
+ true ->
+ {Ts, Ds, Fdb, I} = opt_call(I1, D, Ts0, Ds0, Fdb0),
+ opt_is(Is, Ts, Ds, Fdb, Ls, D, Sub, [I|Acc]);
+ false ->
+ Ts = Ts0#{ Dst => any },
+ Ds = Ds0#{ Dst => I1 },
+ opt_is(Is, Ts, Ds, Fdb0, Ls, D, Sub, [I1|Acc])
+ end;
+opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I],
+ Ts0, Ds0, Fdb, Ls, D, Sub0, Acc) ->
+ case Ds0 of
+ #{ Arg := #b_set{op=call} } ->
+ %% The success check of a call is part of exception handling and
+ %% must not be optimized away. We still have to update its type
+ %% though.
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{Dst=>I},
+
+ opt_is([], Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc]);
+ #{} ->
+ Args = simplify_args([Arg], Sub0, Ts0),
+ Type = type(succeeded, Args, Ts0, Ds0),
+ case get_literal_from_type(Type) of
+ #b_literal{}=Lit ->
+ Sub = Sub0#{Dst=>Lit},
+ opt_is([], Ts0, Ds0, Fdb, Ls, D, Sub, Acc);
+ none ->
+ Ts = Ts0#{Dst=>Type},
+ Ds = Ds0#{Dst=>I},
+ opt_is([], Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc])
+ end
end;
opt_is([#b_set{args=Args0,dst=Dst}=I0|Is],
- Ts0, Ds0, Ls, Sub0, Acc) ->
+ Ts0, Ds0, Fdb, Ls, D, Sub0, Acc) ->
Args = simplify_args(Args0, Sub0, Ts0),
I1 = beam_ssa:normalize(I0#b_set{args=Args}),
case simplify(I1, Ts0) of
@@ -148,16 +325,76 @@ opt_is([#b_set{args=Args0,dst=Dst}=I0|Is],
I = beam_ssa:normalize(I2),
Ts = update_types(I, Ts0, Ds0),
Ds = Ds0#{Dst=>I},
- opt_is(Is, Ts, Ds, Ls, Sub0, [I|Acc]);
+ opt_is(Is, Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc]);
#b_literal{}=Lit ->
Sub = Sub0#{Dst=>Lit},
- opt_is(Is, Ts0, Ds0, Ls, Sub, Acc);
+ opt_is(Is, Ts0, Ds0, Fdb, Ls, D, Sub, Acc);
#b_var{}=Var ->
- Sub = Sub0#{Dst=>Var},
- opt_is(Is, Ts0, Ds0, Ls, Sub, Acc)
+ case Is of
+ [#b_set{op=succeeded,dst=SuccDst,args=[Dst]}] ->
+ %% We must remove this 'succeeded' instruction.
+ Sub = Sub0#{Dst=>Var,SuccDst=>#b_literal{val=true}},
+ opt_is([], Ts0, Ds0, Fdb, Ls, D, Sub, Acc);
+ _ ->
+ Sub = Sub0#{Dst=>Var},
+ opt_is(Is, Ts0, Ds0, Fdb, Ls, D, Sub, Acc)
+ end
end;
-opt_is([], Ts, Ds, _Ls, Sub, Acc) ->
- {reverse(Acc),Ts,Ds,Sub}.
+opt_is([], Ts, Ds, Fdb, _Ls, _D, Sub, Acc) ->
+ {reverse(Acc), Ts, Ds, Fdb, Sub}.
+
+opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) ->
+ {Ts, Ds, I} = opt_local_call(I0, Ts0, Ds0, Fdb0),
+ case Fdb0 of
+ #{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } ->
+ %% Update the argument types of *this exact call*, the types
+ %% will be joined later when the callee is optimized.
+ CallId = {D#d.func_id, Dst},
+ ArgTypes = update_arg_types(Args, ArgTypes0, CallId, Ts0),
+
+ Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} },
+ {Ts, Ds, Fdb, I};
+ #{} ->
+ %% We can't narrow the argument types of exported functions as they
+ %% can receive anything as part of an external call.
+ {Ts, Ds, Fdb0, I}
+ end;
+opt_call(#b_set{dst=Dst}=I, _D, Ts0, Ds0, Fdb) ->
+ Ts = update_types(I, Ts0, Ds0),
+ Ds = Ds0#{ Dst => I },
+ {Ts, Ds, Fdb, I}.
+
+opt_local_call(#b_set{dst=Dst,args=[Id|_]}=I0, Ts0, Ds0, Fdb) ->
+ %% We skip propagating 'none' as we don't yet have a good way to cut a
+ %% block short.
+ Type = case Fdb of
+ #{ Id := #func_info{ret_type=[T]} } when T =/= none -> T;
+ #{} -> any
+ end,
+ I = case Type of
+ any -> I0;
+ _ -> beam_ssa:add_anno(result_type, validator_anno(Type), I0)
+ end,
+ Ts = Ts0#{ Dst => Type },
+ Ds = Ds0#{ Dst => I },
+ {Ts, Ds, I}.
+
+update_arg_types([Arg | Args], [TypeMap0 | TypeMaps], CallId, Ts) ->
+ %% Match contexts are treated as bitstrings when optimizing arguments, as
+ %% we don't yet support removing the "bs_start_match3" instruction.
+ NewType = case get_type(Arg, Ts) of
+ #t_bs_match{} -> {binary, 1};
+ Type -> Type
+ end,
+ PrevType = maps:get(CallId, TypeMap0, NewType),
+
+ %% The new type must be narrower than the old one.
+ true = meet(NewType, PrevType) =/= none, %Assertion.
+
+ TypeMap = TypeMap0#{ CallId => NewType },
+ [TypeMap | update_arg_types(Args, TypeMaps, CallId, Ts)];
+update_arg_types([], [], _CallId, _Ts) ->
+ [].
simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) ->
case is_safe_bool_op(Args, Ts) of
@@ -289,7 +526,7 @@ simplify(#b_set{op=put_tuple,args=Args}=I, _Ts) ->
none -> I;
List -> #b_literal{val=list_to_tuple(List)}
end;
-simplify(#b_set{op=succeeded,args=[#b_literal{}]}, _Ts) ->
+simplify(#b_set{op=wait_timeout,args=[#b_literal{val=0}]}, _Ts) ->
#b_literal{val=true};
simplify(#b_set{op=wait_timeout,args=[#b_literal{val=infinity}]}=I, _Ts) ->
I#b_set{op=wait,args=[]};
@@ -436,12 +673,12 @@ update_successors(#b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail}, Ts0, D0) ->
%% no need to include the type database passed on to the
%% successors of this block.
Ts = maps:remove(Bool, Ts0),
- D = update_successor(Fail, Ts, D0),
- SuccTs = infer_types(Bool, Ts, D0),
+ {SuccTs,FailTs} = infer_types(Bool, Ts, D0),
+ D = update_successor(Fail, FailTs, D0),
update_successor(Succ, SuccTs, D);
false ->
- D = update_successor_bool(Bool, false, Fail, Ts0, D0),
- SuccTs = infer_types(Bool, Ts0, D0),
+ {SuccTs,FailTs} = infer_types(Bool, Ts0, D0),
+ D = update_successor_bool(Bool, false, Fail, FailTs, D0),
update_successor_bool(Bool, true, Succ, SuccTs, D)
end;
update_successors(#b_switch{arg=#b_var{}=V,fail=Fail,list=List}, Ts0, D0) ->
@@ -458,14 +695,36 @@ update_successors(#b_switch{arg=#b_var{}=V,fail=Fail,list=List}, Ts0, D0) ->
end,
foldl(F, D, List);
false ->
- D = update_successor(Fail, Ts0, D0),
+ %% V can not be equal to any of the values in List at the fail
+ %% block.
+ FailTs = subtract_sw_list(V, List, Ts0),
+ D = update_successor(Fail, FailTs, D0),
F = fun({Val,S}, A) ->
T = get_type(Val, Ts0),
update_successor(S, Ts0#{V=>T}, A)
end,
foldl(F, D, List)
- end;
-update_successors(#b_ret{}, _Ts, D) -> D.
+ end;
+update_successors(#b_ret{arg=Arg}, Ts, D) ->
+ FuncId = D#d.func_id,
+ case D#d.ds of
+ #{ Arg := #b_set{op=call,args=[FuncId | _]} } ->
+ %% Returning a call to ourselves doesn't affect our own return
+ %% type.
+ D;
+ #{} ->
+ RetType = join([get_type(Arg, Ts) | D#d.ret_type]),
+ D#d{ret_type=[RetType]}
+ end.
+
+subtract_sw_list(V, List, Ts) ->
+ Ts#{ V := sub_sw_list_1(get_type(V, Ts), List, Ts) }.
+
+sub_sw_list_1(Type, [{Val,_}|T], Ts) ->
+ ValType = get_type(Val, Ts),
+ sub_sw_list_1(subtract(Type, ValType), T, Ts);
+sub_sw_list_1(Type, [], _Ts) ->
+ Type.
update_successor_bool(#b_var{}=Var, BoolValue, S, Ts, D) ->
case t_is_boolean(get_type(Var, Ts)) of
@@ -542,6 +801,14 @@ type(call, [#b_remote{mod=#b_literal{val=Mod},
{_,_} ->
#t_tuple{}
end;
+ {erlang,'++',[List1,List2]} ->
+ case get_type(List1, Ts) =:= cons orelse
+ get_type(List2, Ts) =:= cons of
+ true -> cons;
+ false -> list
+ end;
+ {erlang,'--',[_,_]} ->
+ list;
{math,_,_} ->
case is_math_bif(Name, length(Args)) of
false -> any;
@@ -607,6 +874,8 @@ type(succeeded, [#b_var{}=Src], Ts, Ds) ->
#b_set{} ->
t_boolean()
end;
+type(succeeded, [#b_literal{}], _Ts, _Ds) ->
+ t_atom(true);
type(_, _, _, _) -> any.
arith_op_type(Args, Ts) ->
@@ -873,10 +1142,84 @@ get_type(#b_literal{val=Val}, _Ts) ->
any
end.
-infer_types(#b_var{}=V, Ts, #d{ds=Ds}) ->
+%% infer_types(Var, Types, #d{}) -> {SuccTypes,FailTypes}
+%% Looking at the expression that defines the variable Var, infer
+%% the types for the variables in the arguments. Return the updated
+%% type database for the case that the expression evaluates to
+%% true, and and for the case that it evaluates to false.
+%%
+%% Here is an example. The variable being asked about is
+%% the variable Bool, which is defined like this:
+%%
+%% Bool = is_nonempty_list L
+%%
+%% If 'is_nonempty_list L' evaluates to 'true', L must
+%% must be cons. The meet of the previously known type of L and 'cons'
+%% will be added to SuccTypes.
+%%
+%% On the other hand, if 'is_nonempty_list L' evaluates to false, L
+%% is not cons and cons can be subtracted from the previously known
+%% type for L. For example, if L was known to be 'list', subtracting
+%% 'cons' would give 'nil' as the only possible type. The result of the
+%% subtraction for L will be added to FailTypes.
+%%
+%% Here is another example, asking about the variable Bool:
+%%
+%% Head = bif:hd L
+%% Bool = succeeded Head
+%%
+%% 'succeeded Head' will evaluate to 'true' if the instrution that
+%% defined Head succeeded. In this case, it is the 'bif:hd L'
+%% instruction, which will succeed if L is 'cons'. Thus, the meet of
+%% the previous type for L and 'cons' will be added to SuccTypes.
+%%
+%% If 'succeeded Head' evaluates to 'false', it means that 'bif:hd L'
+%% failed and that L is not 'cons'. 'cons' can be subtracted from the
+%% previously known type for L and the result put in FailTypes.
+
+infer_types(#b_var{}=V, Ts, #d{ds=Ds,once=Once}) ->
#{V:=#b_set{op=Op,args=Args}} = Ds,
- Types = infer_type(Op, Args, Ds),
- meet_types(Types, Ts).
+ Types0 = infer_type(Op, Args, Ds),
+
+ %% We must be careful with types inferred from '=:='.
+ %%
+ %% If we have seen L =:= [a], we know that L is 'cons' if the
+ %% comparison succeeds. However, if the comparison fails, L could
+ %% still be 'cons'. Therefore, we must not subtract 'cons' from the
+ %% previous type of L.
+ %%
+ %% However, it is safe to subtract a type inferred from '=:=' if
+ %% it is single-valued, e.g. if it is [] or the atom 'true'.
+ EqTypes0 = infer_eq_type(Op, Args, Ts, Ds),
+ {Types1,EqTypes} = partition(fun({_,T}) ->
+ is_singleton_type(T)
+ end, EqTypes0),
+
+ %% Don't bother updating the types for variables that
+ %% are never used again.
+ Types2 = Types1 ++ Types0,
+ Types = [P || {InfV,_}=P <- Types2, not cerl_sets:is_element(InfV, Once)],
+
+ {meet_types(EqTypes++Types, Ts),subtract_types(Types, Ts)}.
+
+infer_eq_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) ->
+ Def = maps:get(Src, Ds),
+ Type = get_type(Lit, Ts),
+ [{Src,Type}|infer_tuple_size(Def, Lit) ++
+ infer_first_element(Def, Lit)];
+infer_eq_type({bif,'=:='}, [#b_var{}=Arg0,#b_var{}=Arg1], Ts, _Ds) ->
+ %% As an example, assume that L1 is known to be 'list', and L2 is
+ %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it can
+ %% be inferred that L1 is 'cons' (the meet of 'cons' and 'list').
+ Type0 = get_type(Arg0, Ts),
+ Type1 = get_type(Arg1, Ts),
+ Type = meet(Type0, Type1),
+ [{V,MeetType} ||
+ {V,OrigType,MeetType} <-
+ [{Arg0,Type0,Type},{Arg1,Type1,Type}],
+ OrigType =/= MeetType];
+infer_eq_type(_Op, _Args, _Ts, _Ds) ->
+ [].
infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
if
@@ -885,20 +1228,27 @@ infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
true ->
[]
end;
-infer_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ds) ->
- Def = maps:get(Src, Ds),
- Type = get_type(Lit, #{}),
- [{Src,Type}|infer_tuple_size(Def, Lit) ++
- infer_first_element(Def, Lit)];
+infer_type({bif,element}, [#b_var{}=Position,#b_var{}=Tuple], _Ds) ->
+ [{Position,t_integer()},{Tuple,#t_tuple{}}];
infer_type({bif,Bif}, [#b_var{}=Src]=Args, _Ds) ->
case inferred_bif_type(Bif, Args) of
any -> [];
T -> [{Src,T}]
end;
+infer_type({bif,binary_part}, [#b_var{}=Src,_], _Ds) ->
+ [{Src,{binary,8}}];
infer_type({bif,is_map_key}, [_,#b_var{}=Src], _Ds) ->
[{Src,map}];
infer_type({bif,map_get}, [_,#b_var{}=Src], _Ds) ->
[{Src,map}];
+infer_type({bif,Bif}, [_,_]=Args, _Ds) ->
+ case inferred_bif_type(Bif, Args) of
+ any -> [];
+ T -> [{A,T} || #b_var{}=A <- Args]
+ end;
+infer_type({bif,binary_part}, [#b_var{}=Src,Pos,Len], _Ds) ->
+ [{Src,{binary,8}}|
+ [{V,t_integer()} || #b_var{}=V <- [Pos,Len]]];
infer_type(bs_start_match, [#b_var{}=Bin], _Ds) ->
[{Bin,{binary,1}}];
infer_type(is_nonempty_list, [#b_var{}=Src], _Ds) ->
@@ -969,6 +1319,7 @@ inferred_bif_type(is_number, [_]) -> number;
inferred_bif_type(is_tuple, [_]) -> #t_tuple{};
inferred_bif_type(abs, [_]) -> number;
inferred_bif_type(bit_size, [_]) -> {binary,1};
+inferred_bif_type('bnot', [_]) -> t_integer();
inferred_bif_type(byte_size, [_]) -> {binary,1};
inferred_bif_type(ceil, [_]) -> number;
inferred_bif_type(float, [_]) -> number;
@@ -976,10 +1327,25 @@ inferred_bif_type(floor, [_]) -> number;
inferred_bif_type(hd, [_]) -> cons;
inferred_bif_type(length, [_]) -> list;
inferred_bif_type(map_size, [_]) -> map;
+inferred_bif_type('not', [_]) -> t_boolean();
inferred_bif_type(round, [_]) -> number;
inferred_bif_type(trunc, [_]) -> number;
inferred_bif_type(tl, [_]) -> cons;
inferred_bif_type(tuple_size, [_]) -> #t_tuple{};
+inferred_bif_type('and', [_,_]) -> t_boolean();
+inferred_bif_type('or', [_,_]) -> t_boolean();
+inferred_bif_type('xor', [_,_]) -> t_boolean();
+inferred_bif_type('band', [_,_]) -> t_integer();
+inferred_bif_type('bor', [_,_]) -> t_integer();
+inferred_bif_type('bsl', [_,_]) -> t_integer();
+inferred_bif_type('bsr', [_,_]) -> t_integer();
+inferred_bif_type('bxor', [_,_]) -> t_integer();
+inferred_bif_type('div', [_,_]) -> t_integer();
+inferred_bif_type('rem', [_,_]) -> t_integer();
+inferred_bif_type('+', [_,_]) -> number;
+inferred_bif_type('-', [_,_]) -> number;
+inferred_bif_type('*', [_,_]) -> number;
+inferred_bif_type('/', [_,_]) -> number;
inferred_bif_type(_, _) -> any.
infer_tuple_size(#b_set{op={bif,tuple_size},args=[#b_var{}=Tuple]},
@@ -1088,6 +1454,9 @@ t_tuple_size(#t_tuple{size=Size,exact=true}) ->
t_tuple_size(_) ->
none.
+is_singleton_type(Type) ->
+ get_literal_from_type(Type) =/= none.
+
%% join(Type1, Type2) -> Type
%% Return the "join" of Type1 and Type2. The join is a more general
%% type than Type1 and Type2. For example:
@@ -1152,14 +1521,40 @@ gcd(A, B) ->
meet_types([{V,T0}|Vs], Ts) ->
#{V:=T1} = Ts,
- T = meet(T0, T1),
- meet_types(Vs, Ts#{V:=T});
+ case meet(T0, T1) of
+ T1 -> meet_types(Vs, Ts);
+ T -> meet_types(Vs, Ts#{V:=T})
+ end;
meet_types([], Ts) -> Ts.
meet([T1,T2|Ts]) ->
meet([meet(T1, T2)|Ts]);
meet([T]) -> T.
+subtract_types([{V,T0}|Vs], Ts) ->
+ #{V:=T1} = Ts,
+ case subtract(T1, T0) of
+ T1 -> subtract_types(Vs, Ts);
+ T -> subtract_types(Vs, Ts#{V:=T})
+ end;
+subtract_types([], Ts) -> Ts.
+
+%% subtract(Type1, Type2) -> Type.
+%% Subtract Type2 from Type1. Example:
+%%
+%% subtract(list, cons) -> nil
+
+subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) ->
+ case ordsets:subtract(Set0, Set1) of
+ [] -> none;
+ [_|_]=Set -> #t_atom{elements=Set}
+ end;
+subtract(number, float) -> #t_integer{};
+subtract(number, #t_integer{elements=any}) -> float;
+subtract(list, cons) -> nil;
+subtract(list, nil) -> cons;
+subtract(T, _) -> T.
+
%% meet(Type1, Type2) -> Type
%% Return the "meet" of Type1 and Type2. The meet is a narrower
%% type than Type1 and Type2. For example:
diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl
index 51ff580a7a..acf3838da4 100644
--- a/lib/compiler/src/beam_trim.erl
+++ b/lib/compiler/src/beam_trim.erl
@@ -200,6 +200,8 @@ create_map(Trim, Moves) ->
(Any) -> Any
end.
+remap([{'%',_}=I|Is], Map, Acc) ->
+ remap(Is, Map, [I|Acc]);
remap([{block,Bl0}|Is], Map, Acc) ->
Bl = remap_block(Bl0, Map, []),
remap(Is, Map, [{block,Bl}|Acc]);
@@ -279,6 +281,8 @@ safe_labels([_|Is], Acc) ->
safe_labels(Is, Acc);
safe_labels([], Acc) -> cerl_sets:from_list(Acc).
+is_safe_label([{'%',_}|Is]) ->
+ is_safe_label(Is);
is_safe_label([{line,_}|Is]) ->
is_safe_label(Is);
is_safe_label([{badmatch,{Tag,_}}|_]) ->
@@ -337,6 +341,8 @@ frame_layout_2(Is) -> reverse(Is).
%% to safe labels (i.e., the code at those labels don't depend
%% on the contents of any Y register).
+frame_size([{'%',_}|Is], Safe) ->
+ frame_size(Is, Safe);
frame_size([{block,_}|Is], Safe) ->
frame_size(Is, Safe);
frame_size([{call_fun,_}|Is], Safe) ->
@@ -393,6 +399,8 @@ frame_size_branch(L, Is, Safe) ->
%% This function handles the same instructions as frame_size/2. It
%% assumes that any labels in the instructions are safe labels.
+is_not_used(Y, [{'%',_}|Is]) ->
+ is_not_used(Y, Is);
is_not_used(Y, [{apply,_}|Is]) ->
is_not_used(Y, Is);
is_not_used(Y, [{bif,_,{f,_},Ss,Dst}|Is]) ->
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 7d908df3bf..4081e366a5 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -26,6 +26,7 @@
%% Interface for compiler.
-export([module/2, format_error/1]).
+-export([type_anno/1, type_anno/2, type_anno/3]).
-import(lists, [any/2,dropwhile/2,foldl/3,map/2,foreach/2,reverse/1]).
@@ -44,6 +45,33 @@ module({Mod,Exp,Attr,Fs,Lc}=Code, _Opts)
{error,[{atom_to_list(Mod),Es}]}
end.
+%% Provides a stable interface for type annotations, used by certain passes to
+%% indicate that we can safely assume that a register has a given type.
+-spec type_anno(term()) -> term().
+type_anno(atom) -> {atom,[]};
+type_anno(bool) -> bool;
+type_anno({binary,_}) -> term;
+type_anno(cons) -> cons;
+type_anno(float) -> {float,[]};
+type_anno(integer) -> {integer,[]};
+type_anno(list) -> list;
+type_anno(map) -> map;
+type_anno(match_context) -> match_context;
+type_anno(number) -> number;
+type_anno(nil) -> nil.
+
+-spec type_anno(term(), term()) -> term().
+type_anno(atom, Value) -> {atom, Value};
+type_anno(float, Value) -> {float, Value};
+type_anno(integer, Value) -> {integer, Value}.
+
+-spec type_anno(term(), term(), term()) -> term().
+type_anno(tuple, Size, Exact) when is_integer(Size) ->
+ case Exact of
+ true -> {tuple, Size};
+ false -> {tuple, [Size]}
+ end.
+
-spec format_error(term()) -> iolist().
format_error({{_M,F,A},{I,Off,limit}}) ->
@@ -93,28 +121,6 @@ validate(Module, Fs) ->
Ft = index_parameter_types(Fs, []),
validate_0(Module, Fs, Ft).
-index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) ->
- Code = dropwhile(fun({label,L}) when L =:= Entry -> false;
- (_) -> true
- end, Code0),
- case Code of
- [{label,Entry}|Is] ->
- Acc = index_parameter_types_1(Is, Entry, Acc0),
- index_parameter_types(Fs, Acc);
- _ ->
- %% Something serious is wrong. Ignore it for now.
- %% It will be detected and diagnosed later.
- index_parameter_types(Fs, Acc0)
- end;
-index_parameter_types([], Acc) ->
- gb_trees:from_orddict(lists:sort(Acc)).
-
-index_parameter_types_1([{'%', {type_info, Reg, Type}} | Is], Entry, Acc) ->
- Key = {Entry, Reg},
- index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]);
-index_parameter_types_1(_, _, Acc) ->
- Acc.
-
validate_0(_Module, [], _) -> [];
validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
try validate_1(Code, Name, Ar, Entry, Ft) of
@@ -167,6 +173,32 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) ->
slots=0 :: non_neg_integer() %Number of slots
}).
+index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) ->
+ Code = dropwhile(fun({label,L}) when L =:= Entry -> false;
+ (_) -> true
+ end, Code0),
+ case Code of
+ [{label,Entry}|Is] ->
+ Acc = index_parameter_types_1(Is, Entry, Acc0),
+ index_parameter_types(Fs, Acc);
+ _ ->
+ %% Something serious is wrong. Ignore it for now.
+ %% It will be detected and diagnosed later.
+ index_parameter_types(Fs, Acc0)
+ end;
+index_parameter_types([], Acc) ->
+ gb_trees:from_orddict(lists:sort(Acc)).
+
+index_parameter_types_1([{'%', {type_info, Reg, Type0}} | Is], Entry, Acc) ->
+ Type = case Type0 of
+ match_context -> #ms{};
+ _ -> Type0
+ end,
+ Key = {Entry, Reg},
+ index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]);
+index_parameter_types_1(_, _, Acc) ->
+ Acc.
+
validate_1(Is, Name, Arity, Entry, Ft) ->
validate_2(labels(Is), Name, Arity, Entry, Ft).
@@ -386,25 +418,19 @@ valfun_1(remove_message, Vst) ->
%% The message term is no longer fragile. It can be used
%% without restrictions.
remove_fragility(Vst);
-valfun_1({'%', {type_info, Reg, Info0}}, Vst0) ->
+valfun_1({'%', {type_info, Reg, match_context}}, Vst0) ->
+ set_aliased_type(#ms{}, Reg, Vst0);
+valfun_1({'%', {type_info, Reg, NewType0}}, Vst0) ->
%% Explicit type information inserted by optimization passes to indicate
%% that Reg has a certain type, so that we can accept cross-function type
%% optimizations.
- %%
- %% At the moment we only allow this when narrowing from 'term' which is
- %% what to expect with function parameters, but in theory any narrowing
- %% conversion should be legal.
- case get_move_term_type(Reg, Vst0) of
- term ->
- Type0 = case Info0 of
- match_context -> #ms{};
- _ -> Info0
- end,
- Type = propagate_fragility(Type0, [Reg], Vst0),
- set_type_reg(Type, Reg, Vst0);
- _ ->
- error(bad_type_info)
- end;
+ OldType = get_durable_term_type(Reg, Vst0),
+ NewType = case meet(NewType0, OldType) of
+ none -> error({bad_type_info, Reg, NewType0, OldType});
+ T -> T
+ end,
+ Type = propagate_fragility(NewType, [Reg], Vst0),
+ set_aliased_type(Type, Reg, Vst0);
valfun_1({'%',_}, Vst) ->
Vst;
valfun_1({line,_}, Vst) ->
@@ -643,7 +669,12 @@ valfun_4({gc_bif,Op,{f,Fail},Live,Src,Dst}, #vst{current=St0}=Vst0) ->
Vst1 = Vst0#vst{current=St},
Vst2 = branch_state(Fail, Vst1),
Vst3 = prune_x_regs(Live, Vst2),
+ SrcType = get_term_type(hd(Src), Vst3),
Vst = case Op of
+ length when SrcType =/= cons, SrcType =/= nil ->
+ %% If we already know we have a cons cell or nil, it
+ %% shouldn't be demoted to list.
+ set_type(list, hd(Src), Vst3);
map_size ->
set_type(map, hd(Src), Vst3);
_ ->
@@ -786,6 +817,12 @@ valfun_4({bs_set_position, Ctx, Pos}, Vst) ->
Vst;
%% Other test instructions.
+valfun_4({test,is_atom,{f,Lbl},[Src]}, Vst) ->
+ assert_term(Src, Vst),
+ set_aliased_type({atom,[]}, Src, branch_state(Lbl, Vst));
+valfun_4({test,is_boolean,{f,Lbl},[Src]}, Vst) ->
+ assert_term(Src, Vst),
+ set_aliased_type(bool, Src, branch_state(Lbl, Vst));
valfun_4({test,is_float,{f,Lbl},[Float]}, Vst) ->
assert_term(Float, Vst),
set_type({float,[]}, Float, branch_state(Lbl, Vst));
@@ -793,6 +830,9 @@ valfun_4({test,is_tuple,{f,Lbl},[Tuple]}, Vst) ->
Type0 = get_term_type(Tuple, Vst),
Type = upgrade_tuple_type({tuple,[0]}, Type0),
set_aliased_type(Type, Tuple, branch_state(Lbl, Vst));
+valfun_4({test,is_integer,{f,Lbl},[Src]}, Vst) ->
+ assert_term(Src, Vst),
+ set_aliased_type({integer,[]}, Src, branch_state(Lbl, Vst));
valfun_4({test,is_nonempty_list,{f,Lbl},[Cons]}, Vst) ->
assert_term(Cons, Vst),
Type = cons,
@@ -809,6 +849,14 @@ valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) ->
assert_type(map, Src, Vst),
assert_unique_map_keys(List),
branch_state(Lbl, Vst);
+valfun_4({test,is_list,{f,Lbl},[Src]}, Vst) ->
+ validate_src([Src], Vst),
+ Type = case get_term_type(Src, Vst) of
+ cons -> cons;
+ nil -> nil;
+ _ -> list
+ end,
+ set_aliased_type(Type, Src, branch_state(Lbl, Vst));
valfun_4({test,is_map,{f,Lbl},[Src]}, Vst0) ->
Vst = branch_state(Lbl, Vst0),
case Src of
@@ -820,20 +868,28 @@ valfun_4({test,is_map,{f,Lbl},[Src]}, Vst0) ->
_ ->
kill_state(Vst0)
end;
+valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst0) ->
+ Vst = case get_term_type(Src, Vst0) of
+ list ->
+ branch_state(Lbl, set_aliased_type(cons, Src, Vst0));
+ _ ->
+ branch_state(Lbl, Vst0)
+ end,
+ set_aliased_type(nil, Src, Vst);
valfun_4({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst0) ->
validate_src(Ss, Vst0),
Infer = infer_types(Src, Vst0),
Vst1 = Infer(Val, Vst0),
- Vst = branch_state(Lbl, Vst1),
- case Val of
- {literal,Tuple} when is_tuple(Tuple) ->
- Type0 = get_term_type(Val, Vst),
- Type = upgrade_tuple_type({tuple,tuple_size(Tuple)},
- Type0),
- set_aliased_type(Type, Src, Vst);
- _ ->
- Vst
- end;
+ Vst2 = upgrade_ne_types(Src, Val, Vst1),
+ Vst3 = branch_state(Lbl, Vst2),
+ Vst = Vst3#vst{current=Vst1#vst.current},
+ upgrade_eq_types(Src, Val, Vst);
+valfun_4({test,is_ne_exact,{f,Lbl},[Src,Val]=Ss}, Vst0) ->
+ validate_src(Ss, Vst0),
+ Vst1 = upgrade_eq_types(Src, Val, Vst0),
+ Vst2 = branch_state(Lbl, Vst1),
+ Vst = Vst2#vst{current=Vst0#vst.current},
+ upgrade_ne_types(Src, Val, Vst);
valfun_4({test,_Op,{f,Lbl},Src}, Vst) ->
validate_src(Src, Vst),
branch_state(Lbl, Vst);
@@ -920,6 +976,25 @@ valfun_4({get_map_elements,{f,Fail},Src,{list,List}}, Vst) ->
valfun_4(_, _) ->
error(unknown_instruction).
+upgrade_ne_types(Src1, Src2, Vst0) ->
+ T1 = get_durable_term_type(Src1, Vst0),
+ T2 = get_durable_term_type(Src2, Vst0),
+ Type = subtract(T1, T2),
+ set_aliased_type(Type, Src1, Vst0).
+
+upgrade_eq_types(Src1, Src2, Vst0) ->
+ T1 = get_durable_term_type(Src1, Vst0),
+ T2 = get_durable_term_type(Src2, Vst0),
+ Meet = meet(T1, T2),
+ Vst = case T1 =/= Meet of
+ true -> set_aliased_type(Meet, Src1, Vst0);
+ false -> Vst0
+ end,
+ case T2 =/= Meet of
+ true -> set_aliased_type(Meet, Src2, Vst);
+ false -> Vst
+ end.
+
verify_get_map(Fail, Src, List, Vst0) ->
assert_not_literal(Src), %OTP 22.
assert_type(map, Src, Vst0),
@@ -1046,39 +1121,58 @@ verify_call_args_1(N, Vst) ->
verify_call_args_1(X, Vst).
verify_local_call(Lbl, Live, Vst) ->
- F = fun({R, _Ctx}) ->
- verify_call_match_context(Lbl, R, Vst)
- end,
- MsRegs = all_ms_in_x_regs(Live, Vst),
- verify_no_ms_aliases(MsRegs),
- foreach(F, MsRegs).
+ F = fun({R, Type}) ->
+ verify_arg_type(Lbl, R, Type, Vst)
+ end,
+ TRegs = typed_call_regs(Live, Vst),
+ verify_no_ms_aliases(TRegs),
+ foreach(F, TRegs).
-all_ms_in_x_regs(0, _Vst) ->
+typed_call_regs(0, _Vst) ->
[];
-all_ms_in_x_regs(Live0, Vst) ->
+typed_call_regs(Live0, Vst) ->
Live = Live0 - 1,
R = {x,Live},
- case get_move_term_type(R, Vst) of
- #ms{}=M -> [{R,M} | all_ms_in_x_regs(Live, Vst)];
- _ -> all_ms_in_x_regs(Live, Vst)
- end.
+ [{R, get_move_term_type(R, Vst)} | typed_call_regs(Live, Vst)].
%% Verifies that the same match context isn't present twice.
-verify_no_ms_aliases(MsRegs) ->
- CtxIds = [Id || {_, #ms{id=Id}} <- MsRegs],
+verify_no_ms_aliases(Regs) ->
+ CtxIds = [Id || {_, #ms{id=Id}} <- Regs],
UniqueCtxIds = ordsets:from_list(CtxIds),
if
length(UniqueCtxIds) < length(CtxIds) ->
- error({multiple_match_contexts, MsRegs});
+ error({multiple_match_contexts, Regs});
length(UniqueCtxIds) =:= length(CtxIds) ->
ok
end.
-%% Verifies that the target label accepts match contexts in the given register.
-verify_call_match_context(Lbl, Ctx, #vst{ft=Ft}) ->
- case gb_trees:lookup({Lbl, Ctx}, Ft) of
- {value, match_context} -> ok;
- none -> error(no_bs_start_match2)
+%% Verifies that the given argument narrows to what the function expects.
+verify_arg_type(Lbl, Reg, #ms{}, #vst{ft=Ft}) ->
+ %% Match contexts require explicit support, and may not be passed to a
+ %% function that accepts arbitrary terms.
+ case gb_trees:lookup({Lbl, Reg}, Ft) of
+ {value, #ms{}} -> ok;
+ _ -> error(no_bs_start_match2)
+ end;
+verify_arg_type(Lbl, Reg, GivenType, #vst{ft=Ft}) ->
+ case gb_trees:lookup({Lbl, Reg}, Ft) of
+ {value, bool} when GivenType =:= {atom, true};
+ GivenType =:= {atom, false};
+ GivenType =:= {atom, []} ->
+ %% We don't yet support upgrading true/false to bool, so we
+ %% assume unknown atoms can be bools when validating calls.
+ ok;
+ {value, #ms{}} ->
+ %% Functions that accept match contexts also accept all other
+ %% terms. This will change once we support union types.
+ ok;
+ {value, RequiredType} ->
+ case meet(GivenType, RequiredType) of
+ none -> error({bad_arg_type, Reg, GivenType, RequiredType});
+ _ -> ok
+ end;
+ none ->
+ ok
end.
allocate(Zero, Stk, Heap, Live, #vst{current=#st{numy=none}}=Vst0) ->
@@ -1298,27 +1392,27 @@ bsm_restore(Reg, SavePoint, Vst) ->
_ -> error({illegal_restore,SavePoint,range})
end.
-
select_val_branches(Src, Choices, Vst) ->
Infer = infer_types(Src, Vst),
- select_val_branches_1(Choices, Infer, Vst).
+ select_val_branches_1(Choices, Src, Infer, Vst).
-select_val_branches_1([Val,{f,L}|T], Infer, Vst0) ->
- Vst = branch_state(L, Infer(Val, Vst0)),
- select_val_branches_1(T, Infer, Vst);
-select_val_branches_1([], _, Vst) -> Vst.
+select_val_branches_1([Val,{f,L}|T], Src, Infer, Vst0) ->
+ Vst1 = set_aliased_type(Val, Src, Infer(Val, Vst0)),
+ Vst = branch_state(L, Vst1),
+ select_val_branches_1(T, Src, Infer, Vst);
+select_val_branches_1([], _, _, Vst) -> Vst.
infer_types(Src, Vst) ->
case get_def(Src, Vst) of
{bif,is_map,{f,_},[Map],_} ->
- fun({atom,true}, S) -> set_type_reg(map, Map, S);
+ fun({atom,true}, S) -> set_aliased_type(map, Map, S);
(_, S) -> S
end;
{bif,tuple_size,{f,_},[Tuple],_} ->
fun({integer,Arity}, S) ->
Type0 = get_term_type(Tuple, S),
Type = upgrade_tuple_type({tuple,Arity}, Type0),
- set_type(Type, Tuple, S);
+ set_aliased_type(Type, Tuple, S);
(_, S) -> S
end;
{bif,'=:=',{f,_},[ArityReg,{integer,_}=Val],_} when ArityReg =/= Src ->
@@ -1366,8 +1460,10 @@ kill_aliases(Reg, #st{aliases=Aliases0}=St) ->
St
end.
-set_type(Type, {x,_}=Reg, Vst) -> set_type_reg(Type, Reg, Vst);
-set_type(Type, {y,_}=Reg, Vst) -> set_type_y(Type, Reg, Vst);
+set_type(Type, {x,_}=Reg, Vst) ->
+ set_type_reg(Type, Reg, Reg, Vst);
+set_type(Type, {y,_}=Reg, Vst) ->
+ set_type_reg(Type, Reg, Reg, Vst);
set_type(_, _, #vst{}=Vst) -> Vst.
set_type_reg(Type, Src, Dst, Vst) ->
@@ -1507,12 +1603,16 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}).
%%
%% term Any valid Erlang (but not of the special types above).
%%
+%% binary Binary or bitstring.
+%%
%% bool The atom 'true' or the atom 'false'.
%%
%% cons Cons cell: [_|_]
%%
%% nil Empty list: []
%%
+%% list List: [] or [_|_]
+%%
%% {tuple,[Sz]} Tuple. An element has been accessed using
%% element/2 or setelement/3 so that it is known that
%% the type is a tuple of size at least Sz.
@@ -1533,7 +1633,7 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}).
%%
%% map Map.
%%
-%%
+%% none A conflict in types. There will be an exception at runtime.
%%
%% FRAGILITY
%% ---------
@@ -1546,6 +1646,51 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}).
%% Such terms are wrapped in a {fragile,Type} tuple, where Type is one
%% of the types described above.
+%% meet(Type1, Type2) -> Type
+%% Return the meet of two types. The meet is a more specific type.
+%% It will be 'none' if the types are in conflict.
+
+meet(Same, Same) ->
+ Same;
+meet(term, Other) ->
+ Other;
+meet(Other, term) ->
+ Other;
+meet(T1, T2) ->
+ case {erlang:min(T1, T2),erlang:max(T1, T2)} of
+ {{atom,_}=A,{atom,[]}} -> A;
+ {bool,{atom,B}=Atom} when is_boolean(B) -> Atom;
+ {bool,{atom,[]}} -> bool;
+ {cons,list} -> cons;
+ {{float,_}=T,{float,[]}} -> T;
+ {{integer,_}=T,{integer,[]}} -> T;
+ {list,nil} -> nil;
+ {number,{integer,_}=T} -> T;
+ {number,{float,_}=T} -> T;
+ {{tuple,Size1},{tuple,Size2}} ->
+ case {Size1,Size2} of
+ {[Sz1],[Sz2]} ->
+ {tuple,[erlang:max(Sz1, Sz2)]};
+ {Sz1,[Sz2]} when Sz2 =< Sz1 ->
+ {tuple,Sz1};
+ {_,_} ->
+ none
+ end;
+ {_,_} -> none
+ end.
+
+%% subtract(Type1, Type2) -> Type
+%% Subtract Type2 from Type2. Example:
+%% subtract(list, nil) -> cons
+
+subtract(list, nil) -> cons;
+subtract(list, cons) -> nil;
+subtract(number, {integer,[]}) -> {float,[]};
+subtract(number, {float,[]}) -> {integer,[]};
+subtract(bool, {atom,false}) -> {atom, true};
+subtract(bool, {atom,true}) -> {atom, false};
+subtract(Type, _) -> Type.
+
assert_type(WantedType, Term, Vst) ->
case get_term_type(Term, Vst) of
{fragile,Type} ->
@@ -1579,25 +1724,27 @@ assert_type(Needed, Actual) ->
%% be executed at run-time.
upgrade_tuple_type(NewType, {fragile,OldType}) ->
- make_fragile(upgrade_tuple_type_1(NewType, OldType));
+ Type = upgrade_tuple_type_1(NewType, OldType),
+ make_fragile(Type);
upgrade_tuple_type(NewType, OldType) ->
upgrade_tuple_type_1(NewType, OldType).
-upgrade_tuple_type_1({tuple,[Sz]}, {tuple,[OldSz]}=T) when Sz < OldSz ->
- %% The old type has a higher value for the least tuple size.
- T;
-upgrade_tuple_type_1({tuple,[Sz]}, {tuple,OldSz}=T)
- when is_integer(Sz), is_integer(OldSz), Sz =< OldSz ->
- %% The old size is exact, and the new size is smaller than the old size.
- T;
-upgrade_tuple_type_1({tuple,_}=T, _) ->
- %% The new type information is exact or has a higher value for
- %% the least tuple size.
- %% Note that inconsistencies are also handled in this
- %% clause, e.g. if the old type was an integer or a tuple accessed
- %% outside its size; inconsistences will generally cause an exception
- %% at run-time but are safe from our point of view.
- T.
+upgrade_tuple_type_1(NewType, OldType) ->
+ case meet(NewType, OldType) of
+ none ->
+ %% Unoptimized code may look like this:
+ %%
+ %% {test,is_list,Fail,[Reg]}.
+ %% {test,is_tuple,Fail,[Reg]}.
+ %% {test,test_arity,Fail,[Reg,5]}.
+ %%
+ %% Note that the test_arity instruction can never be reached.
+ %% To make sure it's not rejected, set the type of Reg to
+ %% NewType instead of 'none'.
+ NewType;
+ Type ->
+ Type
+ end.
get_tuple_size({integer,[]}) -> 0;
get_tuple_size({integer,Sz}) -> Sz;
@@ -1606,6 +1753,17 @@ get_tuple_size(_) -> 0.
validate_src(Ss, Vst) when is_list(Ss) ->
foreach(fun(S) -> get_term_type(S, Vst) end, Ss).
+%% get_durable_term_type(Src, ValidatorState) -> Type
+%% Get the type of the source Src. The returned type Type will be
+%% a standard Erlang type (no catch/try tags or match contexts).
+%% Fragility will be stripped.
+
+get_durable_term_type(Src, Vst) ->
+ case get_term_type(Src, Vst) of
+ {fragile,Type} -> Type;
+ Type -> Type
+ end.
+
%% get_move_term_type(Src, ValidatorState) -> Type
%% Get the type of the source Src. The returned type Type will be
%% a standard Erlang type (no catch/try tags). Match contexts are OK.
@@ -1639,6 +1797,8 @@ get_term_type_1(nil=T, _) -> T;
get_term_type_1({atom,A}=T, _) when is_atom(A) -> T;
get_term_type_1({float,F}=T, _) when is_float(F) -> T;
get_term_type_1({integer,I}=T, _) when is_integer(I) -> T;
+get_term_type_1({literal,[_|_]}, _) -> cons;
+get_term_type_1({literal,Bitstring}, _) when is_bitstring(Bitstring) -> binary;
get_term_type_1({literal,Map}, _) when is_map(Map) -> map;
get_term_type_1({literal,Tuple}, _) when is_tuple(Tuple) ->
{tuple,tuple_size(Tuple)};
@@ -1744,7 +1904,7 @@ merge_regs_1([{R1,_}|Rs1], [{R2,_}|_]=Rs2) when R1 < R2 ->
merge_regs_1([{R1,_}|_]=Rs1, [{R2,_}|Rs2]) when R1 > R2 ->
merge_regs_1(Rs1, Rs2);
merge_regs_1([{R,Type1}|Rs1], [{R,Type2}|Rs2]) ->
- [{R,merge_types(Type1, Type2)}|merge_regs_1(Rs1, Rs2)];
+ [{R,join(Type1, Type2)}|merge_regs_1(Rs1, Rs2)];
merge_regs_1([], []) -> [];
merge_regs_1([], [_|_]) -> [];
merge_regs_1([_|_], []) -> [].
@@ -1763,63 +1923,90 @@ merge_y_regs_1(Y, S, Regs0) when Y >= 0 ->
Type0 ->
merge_y_regs_1(Y-1, S, Regs0);
Type1 ->
- Type = merge_types(Type0, Type1),
+ Type = join(Type0, Type1),
Regs = gb_trees:update(Y, Type, Regs0),
merge_y_regs_1(Y-1, S, Regs)
end;
merge_y_regs_1(_, _, Regs) -> Regs.
-%% merge_types(Type1, Type2) -> Type
+%% join(Type1, Type2) -> Type
%% Return the most specific type possible.
%% Note: Type1 must NOT be the same as Type2.
-merge_types({fragile,Same}=Type, Same) ->
+join({literal,_}=T1, T2) ->
+ join_literal(T1, T2);
+join(T1, {literal,_}=T2) ->
+ join_literal(T2, T1);
+join({fragile,Same}=Type, Same) ->
Type;
-merge_types({fragile,T1}, T2) ->
- make_fragile(merge_types(T1, T2));
-merge_types(Same, {fragile,Same}=Type) ->
+join({fragile,T1}, T2) ->
+ make_fragile(join(T1, T2));
+join(Same, {fragile,Same}=Type) ->
Type;
-merge_types(T1, {fragile,T2}) ->
- make_fragile(merge_types(T1, T2));
-merge_types(uninitialized=I, _) -> I;
-merge_types(_, uninitialized=I) -> I;
-merge_types(initialized=I, _) -> I;
-merge_types(_, initialized=I) -> I;
-merge_types({catchtag,T0},{catchtag,T1}) ->
+join(T1, {fragile,T2}) ->
+ make_fragile(join(T1, T2));
+join(uninitialized=I, _) -> I;
+join(_, uninitialized=I) -> I;
+join(initialized=I, _) -> I;
+join(_, initialized=I) -> I;
+join({catchtag,T0},{catchtag,T1}) ->
{catchtag,ordsets:from_list(T0++T1)};
-merge_types({trytag,T0},{trytag,T1}) ->
+join({trytag,T0},{trytag,T1}) ->
{trytag,ordsets:from_list(T0++T1)};
-merge_types({tuple,A}, {tuple,B}) ->
+join({tuple,A}, {tuple,B}) ->
{tuple,[min(tuple_sz(A), tuple_sz(B))]};
-merge_types({Type,A}, {Type,B})
+join({Type,A}, {Type,B})
when Type =:= atom; Type =:= integer; Type =:= float ->
if A =:= B -> {Type,A};
true -> {Type,[]}
end;
-merge_types({Type,_}, number)
+join({Type,_}, number)
when Type =:= integer; Type =:= float ->
number;
-merge_types(number, {Type,_})
+join(number, {Type,_})
when Type =:= integer; Type =:= float ->
number;
-merge_types(bool, {atom,A}) ->
+join(bool, {atom,A}) ->
merge_bool(A);
-merge_types({atom,A}, bool) ->
+join({atom,A}, bool) ->
merge_bool(A);
-merge_types(cons, {literal,[_|_]}) ->
- cons;
-merge_types({literal,[_|_]}, cons) ->
- cons;
-merge_types({literal,[_|_]}, {literal,[_|_]}) ->
- cons;
-merge_types(#ms{id=Id1,valid=B1,slots=Slots1},
+join({atom,_}, {atom,_}) ->
+ {atom,[]};
+join(#ms{id=Id1,valid=B1,slots=Slots1},
#ms{id=Id2,valid=B2,slots=Slots2}) ->
Id = if
Id1 =:= Id2 -> Id1;
true -> make_ref()
end,
#ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)};
-merge_types(T1, T2) when T1 =/= T2 ->
- %% Too different. All we know is that the type is a 'term'.
+join(T1, T2) when T1 =/= T2 ->
+ %% We've exhaused all other options, so the type must either be a list or
+ %% a 'term'.
+ join_list(T1, T2).
+
+%% Merges types of literals. Note that the left argument must either be a
+%% literal or exactly equal to the second argument.
+join_literal(Same, Same) ->
+ Same;
+join_literal({literal,[_|_]}, T) ->
+ join_literal(T, cons);
+join_literal({literal,#{}}, T) ->
+ join_literal(T, map);
+join_literal({literal,Tuple}, T) when is_tuple(Tuple) ->
+ join_literal(T, {tuple, tuple_size(Tuple)});
+join_literal({literal,_}, T) ->
+ %% Bitstring, fun, or similar.
+ join_literal(T, term);
+join_literal(T1, T2) ->
+ %% We're done extracting the types, try merging them again.
+ join(T1, T2).
+
+join_list(nil, cons) -> list;
+join_list(nil, list) -> list;
+join_list(cons, list) -> list;
+join_list(T, nil) -> join_list(nil, T);
+join_list(T, cons) -> join_list(cons, T);
+join_list(_, _) ->
+ %% Not a list, so it must be a term.
term.
tuple_sz([Sz]) -> Sz;
@@ -1930,6 +2117,9 @@ bif_type(abs, [Num], Vst) ->
end;
bif_type(float, _, _) -> {float,[]};
bif_type('/', _, _) -> {float,[]};
+%% Binary operations
+bif_type('byte_size', _, _) -> {integer,[]};
+bif_type('bit_size', _, _) -> {integer,[]};
%% Integer operations.
bif_type(ceil, [_], _) -> {integer,[]};
bif_type('div', [_,_], _) -> {integer,[]};
@@ -2009,11 +2199,13 @@ is_bif_safe(_, _) -> false.
arith_type([A], Vst) ->
%% Unary '+' or '-'.
case get_term_type(A, Vst) of
+ {integer,_} -> {integer,[]};
{float,_} -> {float,[]};
_ -> number
end;
arith_type([A,B], Vst) ->
case {get_term_type(A, Vst),get_term_type(B, Vst)} of
+ {{integer,_},{integer,_}} -> {integer,[]};
{{float,_},_} -> {float,[]};
{_,{float,_}} -> {float,[]};
{_,_} -> number
@@ -2039,6 +2231,14 @@ return_type_1(erlang, setelement, 3, Vst) ->
{integer,I} -> upgrade_tuple_type({tuple,[I]}, TupleType);
_ -> TupleType
end;
+return_type_1(erlang, '++', 2, Vst) ->
+ case get_term_type({x,0}, Vst) =:= cons orelse
+ get_term_type({x,1}, Vst) =:= cons of
+ true -> cons;
+ false -> list
+ end;
+return_type_1(erlang, '--', 2, _Vst) ->
+ list;
return_type_1(erlang, F, A, _) ->
return_type_erl(F, A);
return_type_1(math, F, A, _) ->
diff --git a/lib/compiler/src/beam_z.erl b/lib/compiler/src/beam_z.erl
index 677094b3cd..415b579240 100644
--- a/lib/compiler/src/beam_z.erl
+++ b/lib/compiler/src/beam_z.erl
@@ -71,6 +71,31 @@ undo_renames([{get_hd,Src,Dst1},{get_tl,Src,Dst2}|Is]) ->
[{get_list,Src,Dst1,Dst2}|undo_renames(Is)];
undo_renames([{get_tl,Src,Dst2},{get_hd,Src,Dst1}|Is]) ->
[{get_list,Src,Dst1,Dst2}|undo_renames(Is)];
+undo_renames([{bs_put,_,{bs_put_binary,1,_},
+ [{atom,all},{literal,<<>>}]}|Is]) ->
+ undo_renames(Is);
+undo_renames([{bs_put,Fail,{bs_put_binary,1,_Flags},
+ [{atom,all},{literal,BinString}]}|Is0]) ->
+ Bits = bit_size(BinString),
+ Bytes = Bits div 8,
+ case Bits rem 8 of
+ 0 ->
+ I = {bs_put_string,byte_size(BinString),
+ {string,BinString}},
+ [undo_rename(I)|undo_renames(Is0)];
+ Rem ->
+ <<Binary:Bytes/bytes,Int:Rem>> = BinString,
+ PutInt = {bs_put_integer,Fail,{integer,Rem},1,
+ {field_flags,[unsigned,big]},{integer,Int}},
+ Is = [PutInt|undo_renames(Is0)],
+ case Binary of
+ <<>> ->
+ Is;
+ _ ->
+ [{bs_put_string,byte_size(Binary),
+ {string,Binary}}|Is]
+ end
+ end;
undo_renames([I|Is]) ->
[undo_rename(I)|undo_renames(Is)];
undo_renames([]) -> [].
@@ -79,8 +104,6 @@ undo_rename({bs_put,F,{I,U,Fl},[Sz,Src]}) ->
{I,F,Sz,U,Fl,Src};
undo_rename({bs_put,F,{I,Fl},[Src]}) ->
{I,F,Fl,Src};
-undo_rename({bs_put,{f,0},{bs_put_string,_,_}=I,[]}) ->
- I;
undo_rename({bif,bs_add=I,F,[Src1,Src2,{integer,U}],Dst}) ->
{I,F,[Src1,Src2,U],Dst};
undo_rename({bif,bs_utf8_size=I,F,[Src],Dst}) ->
@@ -101,7 +124,7 @@ undo_rename({test,bs_match_string=Op,F,[Ctx,Bin0]}) ->
0 -> Bin0;
Rem -> <<Bin0/bitstring,0:(8-Rem)>>
end,
- {test,Op,F,[Ctx,Bits,{string,binary_to_list(Bin)}]};
+ {test,Op,F,[Ctx,Bits,{string,Bin}]};
undo_rename({put_map,Fail,assoc,S,D,R,L}) ->
{put_map_assoc,Fail,S,D,R,L};
undo_rename({put_map,Fail,exact,S,D,R,L}) ->
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 65c4f140c9..53d3cec2d7 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -268,6 +268,10 @@ expand_opt(r21, Os) ->
[no_put_tuple2 | expand_opt(no_bsm3, Os)];
expand_opt({debug_info_key,_}=O, Os) ->
[encrypt_debug_info,O|Os];
+expand_opt(no_type_opt, Os) ->
+ [no_ssa_opt_type_start,
+ no_ssa_opt_type_continue,
+ no_ssa_opt_type_finish | Os];
expand_opt(O, Os) -> [O|Os].
expand_opt_before_21(Os) ->
@@ -823,6 +827,9 @@ kernel_passes() ->
{pass,beam_kernel_to_ssa},
{iff,dssa,{listing,"ssa"}},
{iff,ssalint,{pass,beam_ssa_lint}},
+ {unless,no_share_opt,{pass,beam_ssa_share}},
+ {iff,dssashare,{listing,"ssashare"}},
+ {iff,ssalint,{pass,beam_ssa_lint}},
{unless,no_bsm_opt,{pass,beam_ssa_bsm}},
{iff,dssabsm,{listing,"ssabsm"}},
{iff,ssalint,{pass,beam_ssa_lint}},
@@ -852,8 +859,6 @@ asm_passes() ->
{iff,dblk,{listing,"block"}},
{unless,no_except,{pass,beam_except}},
{iff,dexcept,{listing,"except"}},
- {unless,no_bs_opt,{pass,beam_bs}},
- {iff,dbs,{listing,"bs"}},
{unless,no_jopt,{pass,beam_jump}},
{iff,djmp,{listing,"jump"}},
{unless,no_peep_opt,{pass,beam_peep}},
@@ -2081,7 +2086,6 @@ pre_load() ->
L = [beam_a,
beam_asm,
beam_block,
- beam_bs,
beam_clean,
beam_dict,
beam_except,
@@ -2098,6 +2102,7 @@ pre_load() ->
beam_ssa_opt,
beam_ssa_pre_codegen,
beam_ssa_recv,
+ beam_ssa_share,
beam_ssa_type,
beam_trim,
beam_utils,
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index 86259270bd..108a0ca100 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -24,7 +24,6 @@
beam_a,
beam_asm,
beam_block,
- beam_bs,
beam_clean,
beam_dict,
beam_disasm,
@@ -45,6 +44,7 @@
beam_ssa_pp,
beam_ssa_pre_codegen,
beam_ssa_recv,
+ beam_ssa_share,
beam_ssa_type,
beam_trim,
beam_utils,
diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl
index ce9762899e..d925decce6 100644
--- a/lib/compiler/src/erl_bifs.erl
+++ b/lib/compiler/src/erl_bifs.erl
@@ -195,6 +195,7 @@ is_safe(erlang, is_float, 1) -> true;
is_safe(erlang, is_function, 1) -> true;
is_safe(erlang, is_integer, 1) -> true;
is_safe(erlang, is_list, 1) -> true;
+is_safe(erlang, is_map, 1) -> true;
is_safe(erlang, is_number, 1) -> true;
is_safe(erlang, is_pid, 1) -> true;
is_safe(erlang, is_port, 1) -> true;
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index d848cd8f19..43c99be982 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -2667,12 +2667,20 @@ opt_build_stacktrace(#c_let{vars=[#c_var{name=Cooked}],
#c_call{module=#c_literal{val=erlang},
name=#c_literal{val=raise},
args=[Class,Exp,#c_var{name=Cooked}]} ->
- %% The stacktrace is only used in a call to erlang:raise/3.
- %% There is no need to build the stacktrace. Replace the
- %% call to erlang:raise/3 with the the raw_raise/3 instruction,
- %% which will use a raw stacktrace.
- #c_primop{name=#c_literal{val=raw_raise},
- args=[Class,Exp,RawStk]};
+ case core_lib:is_var_used(Cooked, #c_cons{hd=Class,tl=Exp}) of
+ true ->
+ %% Not safe. The stacktrace is used in the class or
+ %% reason.
+ Let;
+ false ->
+ %% The stacktrace is only used in the last
+ %% argument for erlang:raise/3. There is no need
+ %% to build the stacktrace. Replace the call to
+ %% erlang:raise/3 with the the raw_raise/3
+ %% instruction, which will use a raw stacktrace.
+ #c_primop{name=#c_literal{val=raw_raise},
+ args=[Class,Exp,RawStk]}
+ end;
#c_let{vars=[#c_var{name=V}],arg=Arg,body=B0} when V =/= Cooked ->
case core_lib:is_var_used(Cooked, Arg) of
false ->
diff --git a/lib/compiler/src/sys_core_inline.erl b/lib/compiler/src/sys_core_inline.erl
index 5a6cc45e4a..3380e3f1bd 100644
--- a/lib/compiler/src/sys_core_inline.erl
+++ b/lib/compiler/src/sys_core_inline.erl
@@ -195,6 +195,9 @@ kill_id_anns(Body) ->
cerl_trees:map(fun(#c_fun{anno=A0}=CFun) ->
A = kill_id_anns_1(A0),
CFun#c_fun{anno=A};
+ (#c_var{anno=A0}=Var) ->
+ A = kill_id_anns_1(A0),
+ Var#c_var{anno=A};
(Expr) ->
%% Mark everything as compiler generated to
%% suppress bogus warnings.
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 40428b7f2d..f042a5cb51 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -105,6 +105,8 @@ CORE_MODULES = \
lfe_andor_SUITE \
lfe_guard_SUITE
+NO_MOD_OPT = $(NO_OPT)
+
NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE)
NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl)
POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE)
@@ -113,6 +115,8 @@ INLINE_MODULES= $(INLINE:%=%_inline_SUITE)
INLINE_ERL_FILES= $(INLINE_MODULES:%=%.erl)
R21_MODULES= $(R21:%=%_r21_SUITE)
R21_ERL_FILES= $(R21_MODULES:%=%.erl)
+NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE)
+NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl)
ERL_FILES= $(MODULES:%=%.erl)
CORE_FILES= $(CORE_MODULES:%=%.core)
@@ -142,7 +146,7 @@ EBIN = .
# ----------------------------------------------------
make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \
- $(INLINE_ERL_FILES) $(R21_ERL_FILES)
+ $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \
@@ -154,6 +158,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \
-o$(EBIN) $(INLINE_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +r21 $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(R21_MODULES) >> $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile +no_module_opt $(ERL_COMPILE_FLAGS) \
+ -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE)
$(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \
-o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE)
@@ -183,6 +189,9 @@ docs:
%_r21_SUITE.erl: %_SUITE.erl
sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+%_no_module_opt_SUITE.erl: %_SUITE.erl
+ sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@
+
# ----------------------------------------------------
# Release Target
# ----------------------------------------------------
@@ -195,7 +204,8 @@ release_tests_spec: make_emakefile
$(INSTALL_DATA) compiler.spec compiler.cover \
$(EMAKEFILE) $(ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \
- $(INLINE_ERL_FILES) $(R21_ERL_FILES) "$(RELSYSDIR)"
+ $(INLINE_ERL_FILES) $(R21_ERL_FILES) \
+ $(NO_MOD_OPT_ERL_FILES) "$(RELSYSDIR)"
$(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)"
for file in $(ERL_DUMMY_FILES); do \
module=`basename $$file .erl`; \
diff --git a/lib/compiler/test/apply_SUITE.erl b/lib/compiler/test/apply_SUITE.erl
index 0f82a56fb7..2ee518b1a0 100644
--- a/lib/compiler/test/apply_SUITE.erl
+++ b/lib/compiler/test/apply_SUITE.erl
@@ -73,6 +73,7 @@ mfa(Config) when is_list(Config) ->
{'EXIT',_} = (catch ?APPLY2(Mod, (id(bazzzzzz)), a, b)),
{'EXIT',_} = (catch ?APPLY2({}, baz, a, b)),
{'EXIT',_} = (catch ?APPLY2(?MODULE, [], a, b)),
+ {'EXIT',_} = (catch bad_literal_call(1)),
ok = apply(Mod, foo, id([])),
{[a,b|c]} = apply(Mod, bar, id([[a,b|c]])),
@@ -92,6 +93,13 @@ mfa(Config) when is_list(Config) ->
apply(Mod, foo, []).
+%% The single call to this function with a literal argument caused type
+%% optimization to swap out the 'mod' field of a #b_remote{}, which was
+%% mishandled during code generation as it assumed that the module would always
+%% be an atom.
+bad_literal_call(I) ->
+ I:foo().
+
foo() ->
ok.
diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl
index 40eb6f06c3..759d884dc4 100644
--- a/lib/compiler/test/beam_jump_SUITE.erl
+++ b/lib/compiler/test/beam_jump_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
undefined_label/1,ambiguous_catch_try_state/1,
- unsafe_move_elimination/1,build_tuple/1]).
+ unsafe_move_elimination/1,build_tuple/1,
+ coverage/1]).
suite() ->
[{ct_hooks,[ts_install_cth]}].
@@ -35,7 +36,8 @@ groups() ->
[undefined_label,
ambiguous_catch_try_state,
unsafe_move_elimination,
- build_tuple
+ build_tuple,
+ coverage
]}].
init_per_suite(Config) ->
@@ -126,6 +128,44 @@ do_build_tuple(Message) ->
{Message#message3.id, Res}
end.
+coverage(_Config) ->
+ ok = coverage_1(ok),
+ {error,badarg} = coverage_1({error,badarg}),
+
+ gt = coverage_2(100, 42),
+ le = coverage_2(100, 999),
+ le = coverage_2([], []),
+ gt = coverage_2([], xxx),
+
+ ok.
+
+coverage_1(Var) ->
+ case id(Var) of
+ ok -> ok;
+ Error -> Error
+ end.
+
+%% Cover beam_jump:invert_test(is_ne_exact).
+coverage_2(Pre1, Pre2) ->
+ case
+ case Pre1 == [] of
+ false ->
+ false;
+ true ->
+ Pre2 /= []
+ end
+ of
+ true ->
+ gt;
+ false ->
+ case Pre1 > Pre2 of
+ true ->
+ gt;
+ false ->
+ le
+ end
+ end.
+
id(I) ->
I.
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index 5536abbdde..15cf9bcbf3 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -22,7 +22,7 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
calls/1,tuple_matching/1,recv/1,maps/1,
- cover_ssa_dead/1,combine_sw/1]).
+ cover_ssa_dead/1,combine_sw/1,share_opt/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -36,7 +36,8 @@ groups() ->
recv,
maps,
cover_ssa_dead,
- combine_sw
+ combine_sw,
+ share_opt
]}].
init_per_suite(Config) ->
@@ -91,7 +92,13 @@ start_it([_|_]=MFA) ->
end.
tuple_matching(_Config) ->
- do_tuple_matching({tag,42}).
+ do_tuple_matching({tag,42}),
+
+ true = is_two_tuple({a,b}),
+ false = is_two_tuple({a,b,c}),
+ false = is_two_tuple(atom),
+
+ ok.
do_tuple_matching(Arg) ->
Res = do_tuple_matching_1(Arg),
@@ -117,6 +124,12 @@ do_tuple_matching_3(Tuple) when is_tuple(Tuple) ->
{ok,element(2, Tuple)}
end.
+is_two_tuple(Arg) ->
+ case is_tuple(Arg) of
+ false -> false;
+ true -> tuple_size(Arg) == 2
+ end.
+
-record(reporter_state, {res,run_config}).
-record(run_config, {report_interval=0}).
@@ -467,5 +480,18 @@ do_comb_sw_2(X) ->
end,
erase(?MODULE).
+share_opt(_Config) ->
+ ok = do_share_opt(0).
+
+do_share_opt(A) ->
+ %% The compiler would be stuck in an infinite loop in beam_ssa_share.
+ case A of
+ 0 -> a;
+ 1 -> b;
+ 2 -> c
+ end,
+ receive after 1 -> ok end.
+
+
%% The identity function.
id(I) -> I.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index a4459b95bf..6efa98de44 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -116,8 +116,8 @@ do_integers_4(_, _, Res) ->
Res.
do_integers_5(X0, Y0) ->
- %% X and Y will use the same register.
- X = X0 band 1,
+ %% _X and Y will use the same register.
+ _X = X0 band 1,
Y = Y0 band 3,
case Y of
0 -> zero;
@@ -213,6 +213,10 @@ coverage(Config) ->
[_|_] ->
ok
end,
+
+ %% Cover beam_type:verified_type(none).
+ {'EXIT',{badarith,_}} = (catch (id(2) / id(1)) band 16#ff),
+
ok.
booleans(_Config) ->
diff --git a/lib/compiler/test/beam_utils_SUITE.erl b/lib/compiler/test/beam_utils_SUITE.erl
index ff0f72d519..eb0af59f9d 100644
--- a/lib/compiler/test/beam_utils_SUITE.erl
+++ b/lib/compiler/test/beam_utils_SUITE.erl
@@ -197,7 +197,7 @@ do_bs_init_4(Arg1, Arg2) ->
id(Rewrite)
end/binary,
"/shared">>);
- Other ->
+ _Other ->
error
end.
@@ -553,7 +553,7 @@ not_used_p(_C, S, K, L) when is_record(K, k) ->
id(K)
end.
-is_used_fr(Config) ->
+is_used_fr(_Config) ->
1 = is_used_fr(self(), self()),
1 = is_used_fr(self(), other),
receive 1 -> ok end,
@@ -572,7 +572,7 @@ is_used_fr(X, Y) ->
X ! 1.
%% ERL-778.
-unsafe_is_function(Config) ->
+unsafe_is_function(_Config) ->
{undefined,any} = unsafe_is_function(undefined, any),
{ok,any} = unsafe_is_function(fun() -> ok end, any),
{'EXIT',{{case_clause,_},_}} = (catch unsafe_is_function(fun(_) -> ok end, any)),
diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl
index 661b48a080..585d0e7191 100644
--- a/lib/compiler/test/beam_validator_SUITE.erl
+++ b/lib/compiler/test/beam_validator_SUITE.erl
@@ -579,14 +579,26 @@ receive_stacked(Config) ->
ok.
+aliased_types(Config) ->
+ Seq = lists:seq(1, 5),
+ 1 = aliased_types_1(Seq, Config),
+
+ {1,1} = aliased_types_2(Seq),
+ {42,none} = aliased_types_2([]),
+
+ gurka = aliased_types_3([gurka]),
+ gaffel = aliased_types_3([gaffel]),
+
+ ok.
+
%% ERL-735: validator failed to track types on aliased registers, rejecting
%% legitimate optimizations.
%%
%% move x0 y0
%% bif hd L1 x0
%% get_hd y0 %% The validator failed to see that y0 was a list
-aliased_types(Config) when is_list(Config) ->
- Bug = lists:seq(1, 5),
+%%
+aliased_types_1(Bug, Config) ->
if
Config =/= [gurka, gaffel] -> %% Pointless branch.
_ = hd(Bug),
@@ -594,6 +606,31 @@ aliased_types(Config) when is_list(Config) ->
hd(Bug)
end.
+%% ERL-832: validator failed to realize that a Y register was a cons.
+aliased_types_2(Bug) ->
+ Res = case Bug of
+ [] -> id(42);
+ _ -> hd(Bug)
+ end,
+ {Res,case Bug of
+ [] -> none;
+ _ -> hd(Bug)
+ end}.
+
+%% ERL-832 part deux; validator failed to realize that an aliased register was
+%% a cons.
+aliased_types_3(Bug) ->
+ List = [Y || Y <- Bug],
+ case List of
+ [] -> Bug;
+ _ ->
+ if
+ hd(List) -> a:a();
+ true -> ok
+ end,
+ hd(List)
+ end.
+
%%%-------------------------------------------------------------------------
transform_remove(Remove, Module) ->
@@ -652,3 +689,6 @@ night(Turned) ->
ok.
participating(_, _, _, _) -> ok.
+
+id(I) ->
+ I.
diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl
index ccc49df005..69017d87e7 100644
--- a/lib/compiler/test/bs_construct_SUITE.erl
+++ b/lib/compiler/test/bs_construct_SUITE.erl
@@ -153,6 +153,8 @@ l(I_13, I_big1, I_16, Bin) ->
[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
16#77,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,
16#FF,16#FF,16#FF,16#FF,16#FF,16#FF]),
+ ?T(<< (<<"abc",7:3>>):3/binary >>,
+ [$a,$b,$c]),
%% Mix different units.
?T(<<37558955:(I_16-12)/unit:8,1:1>>,
@@ -311,6 +313,9 @@ fail(Config) when is_list(Config) ->
{'EXIT',{badarg,_}} = (catch <<0:(-(1 bsl 100))>>),
{'EXIT',{badarg,_}} = (catch <<Bin/binary,0:(-(1 bsl 100))>>),
+ %% Unaligned sizes with literal binaries.
+ {'EXIT',{badarg,_}} = (catch <<0,(<<7777:17>>)/binary>>),
+
ok.
float_bin(Config) when is_list(Config) ->
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index 01f302ad21..2cfcb841a7 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -743,9 +743,19 @@ coverage(Config) when is_list(Config) ->
bitstring = coverage_bitstring(<<7:4>>),
other = coverage_bitstring([a]),
+ %% Cover code in beam_trim.
+
{done,<<17,53>>,[253,155,200]} =
coverage_trim(<<253,155,200,17,53>>, e0, e1, e2, e3, []),
+ <<"(right|linux)">> = coverage_trim_1(<<"">>, <<"right">>, <<"linux">>),
+ <<"/(right|linux)">> = coverage_trim_1(<<"/">>, <<"right">>, <<"linux">>),
+ <<"(left|linux)/(right|linux)">> =
+ coverage_trim_1(<<"left">>, <<"right">>, <<"linux">>),
+
+ {10,<<"-">>,""} = coverage_trim_2(<<"-">>, 10, []),
+ {8,<<"-">>,"aa"} = coverage_trim_2(<<"aa-">>, 10, []),
+
ok.
coverage_fold(Fun, Acc, <<H,T/binary>>) ->
@@ -848,6 +858,29 @@ coverage_trim(<<C:8,T/binary>> = Bin, E0, E1, E2, E3, Acc) ->
{done,Bin,lists:reverse(Acc)}
end.
+coverage_trim_1(<<>>, Right, OsType) ->
+ do_coverage_trim_1(Right, OsType);
+coverage_trim_1(<<"/">>, Right, OsType) ->
+ <<"/",(do_coverage_trim_1(Right, OsType))/binary>>;
+coverage_trim_1(Left, Right, OsType) ->
+ <<(do_coverage_trim_1(Left, OsType))/binary,
+ "/",
+ (do_coverage_trim_1(Right, OsType))/binary>>.
+
+do_coverage_trim_1(A, OsType) ->
+ <<"(",A/binary,"|",OsType/binary,")">>.
+
+coverage_trim_2(<<C/utf8,R/binary>> = Bin, I, L) ->
+ case printable_char(C) of
+ true ->
+ coverage_trim_2(R, I - 1, [C | L]);
+ false ->
+ {I,Bin,lists:reverse(L)}
+ end.
+
+printable_char($a) -> true;
+printable_char(_) -> false.
+
multiple_uses(Config) when is_list(Config) ->
{344,62879,345,<<245,159,1,89>>} = multiple_uses_1(<<1,88,245,159,1,89>>),
true = multiple_uses_2(<<0,0,197,18>>),
@@ -1814,11 +1847,10 @@ do_erl_689_2b(_, <<Length, Data/binary>>) ->
%% ERL-753
bs_start_match2_defs(_Config) ->
- {<<"http://127.0.0.1:1234/vsaas/hello">>} = api_url(<<"hello">>, dummy),
- {"https://127.0.0.1:4321/vsaas/hello"} = api_url({https, "hello"}, dummy).
+ {<<"http://127.0.0.1:1234/vsaas/hello">>} = api_url(<<"hello">>),
+ {"https://127.0.0.1:4321/vsaas/hello"} = api_url({https, "hello"}).
-api_url(URL, Auth) ->
- Header = [],
+api_url(URL) ->
case URL of
<<_/binary>> -> {<<"http://127.0.0.1:1234/vsaas/",URL/binary>>};
{https, [_|_] = URL1} -> {"https://127.0.0.1:4321/vsaas/"++URL1}
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 8d8bbe9543..7452466666 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -391,7 +391,6 @@ do_file_listings(DataDir, PrivDir, [File|Files]) ->
do_listing(Simple, TargetDir, dcg, ".codegen"),
do_listing(Simple, TargetDir, dblk, ".block"),
do_listing(Simple, TargetDir, dexcept, ".except"),
- do_listing(Simple, TargetDir, dbs, ".bs"),
do_listing(Simple, TargetDir, djmp, ".jump"),
do_listing(Simple, TargetDir, dclean, ".clean"),
do_listing(Simple, TargetDir, dpeep, ".peep"),
@@ -1250,8 +1249,8 @@ do_opt_guards_fun([]) -> [].
is_exception(guard_SUITE, {'-complex_not/1-fun-4-',1}) -> true;
is_exception(guard_SUITE, {'-complex_not/1-fun-5-',1}) -> true;
is_exception(guard_SUITE, {bad_guards,1}) -> true;
-is_exception(guard_SUITE, {bad_guards_3,2}) -> true;
-is_exception(guard_SUITE, {nested_not_2b,4}) -> true;
+is_exception(guard_SUITE, {nested_not_2b,6}) -> true; %% w/o type optimization
+is_exception(guard_SUITE, {nested_not_2b,2}) -> true; %% with type optimization
is_exception(_, _) -> false.
sys_pre_attributes(Config) ->
diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl
index 3fca1434ae..adfebd5158 100644
--- a/lib/compiler/test/core_fold_SUITE.erl
+++ b/lib/compiler/test/core_fold_SUITE.erl
@@ -502,7 +502,7 @@ source(true, Activities) ->
Activities
end.
-tim(#{reduction := Emergency}) ->
+tim(#{reduction := _Emergency}) ->
try
fun() -> surgery end
catch
diff --git a/lib/compiler/test/float_SUITE.erl b/lib/compiler/test/float_SUITE.erl
index 012810aba2..831e8279aa 100644
--- a/lib/compiler/test/float_SUITE.erl
+++ b/lib/compiler/test/float_SUITE.erl
@@ -20,7 +20,8 @@
-module(float_SUITE).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1]).
+ pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1,
+ subtract_number_type/1]).
-include_lib("common_test/include/ct.hrl").
@@ -28,7 +29,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[pending, bif_calls, math_functions,
- mixed_float_and_int].
+ mixed_float_and_int, subtract_number_type].
groups() ->
[].
@@ -176,5 +177,15 @@ mixed_float_and_int(Config) when is_list(Config) ->
pc(Cov, NotCov, X) ->
round(Cov/(Cov+NotCov)*100) + 42 + 2.0*X.
+subtract_number_type(Config) when is_list(Config) ->
+ 120 = fact(5).
+
+fact(N) ->
+ fact(N, 1).
+
+fact(0, P) -> P;
+fact(1, P) -> P;
+fact(N, P) -> fact(N-1, P*N).
+
id(I) -> I.
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index 1c05129dc4..ed0a56f064 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -1295,6 +1295,32 @@ rel_ops(Config) when is_list(Config) ->
Empty = id([]),
?T(==, [], Empty),
+ %% Cover beam_ssa_dead:turn_op('/=').
+ ok = (fun(A, B) when is_atom(A) ->
+ X = id(A /= B),
+ if
+ X -> ok;
+ true -> error
+ end
+ end)(a, b),
+ ok = (fun(A, B) when is_atom(A) ->
+ X = id(B /= A),
+ if
+ X -> ok;
+ true -> error
+ end
+ end)(a, b),
+
+ %% Cover beam_ssa_dead.
+ Arrow = fun([T1,T2]) when T1 == $>, T2 == $>;
+ T1 == $<, T2 == $| -> true;
+ (_) -> false
+ end,
+ true = Arrow(">>"),
+ true = Arrow("<|"),
+ false = Arrow("><"),
+ false = Arrow(""),
+
ok.
-undef(TestOp).
@@ -1328,6 +1354,9 @@ rel_op_combinations_1(N, Digits) ->
Bool = is_digit_6(N),
Bool = is_digit_7(N),
Bool = is_digit_8(N),
+ Bool = is_digit_9(42, N),
+ Bool = is_digit_10(N, 0),
+ Bool = is_digit_11(N, 0),
rel_op_combinations_1(N-1, Digits).
is_digit_1(X) when 16#0660 =< X, X =< 16#0669 -> true;
@@ -1371,6 +1400,24 @@ is_digit_8(X) when X =< 16#0669, X > (16#0660-1) -> true;
is_digit_8(16#0670) -> false;
is_digit_8(_) -> false.
+is_digit_9(A, 0) when A =:= 42 -> false;
+is_digit_9(_, X) when X > 16#065F, X < 16#066A -> true;
+is_digit_9(_, X) when 16#0030 =< X, X =< 16#0039 -> true;
+is_digit_9(_, X) when 16#06F0 =< X, X =< 16#06F9 -> true;
+is_digit_9(_, _) -> false.
+
+is_digit_10(0, 0) -> false;
+is_digit_10(X, _) when X < 16#066A, 16#0660 =< X -> true;
+is_digit_10(X, _) when 16#0030 =< X, X =< 16#0039 -> true;
+is_digit_10(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true;
+is_digit_10(_, _) -> false.
+
+is_digit_11(0, 0) -> false;
+is_digit_11(X, _) when X =< 16#0669, 16#0660 =< X -> true;
+is_digit_11(X, _) when 16#0030 =< X, X =< 16#0039 -> true;
+is_digit_11(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true;
+is_digit_11(_, _) -> false.
+
rel_op_combinations_2(0, _) ->
ok;
rel_op_combinations_2(N, Range) ->
@@ -1471,6 +1518,7 @@ rel_op_combinations_3(N, Red) ->
Val = redundant_9(N),
Val = redundant_10(N),
Val = redundant_11(N),
+ Val = redundant_11(N),
rel_op_combinations_3(N-1, Red).
redundant_1(X) when X >= 51, X =< 80 -> 5*X;
@@ -1525,6 +1573,10 @@ redundant_11(X) when X =:= 10 -> 2*X;
redundant_11(X) when X >= 51, X =< 80 -> 5*X;
redundant_11(_) -> none.
+redundant_12(X) when X >= 50, X =< 80 -> 2*X;
+redundant_12(X) when X < 51 -> 5*X;
+redundant_12(_) -> none.
+
%% Test type tests on literal values. (From emulator test suites.)
literal_type_tests(Config) when is_list(Config) ->
case ?MODULE of
diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl
index 3e0ab78390..440b632381 100644
--- a/lib/compiler/test/map_SUITE.erl
+++ b/lib/compiler/test/map_SUITE.erl
@@ -70,7 +70,10 @@
t_bad_update/1,
%% new in OTP 21
- t_reused_key_variable/1
+ t_reused_key_variable/1,
+
+ %% new in OTP 22
+ t_mixed_clause/1,cover_beam_trim/1
]).
suite() -> [].
@@ -124,7 +127,10 @@ all() ->
t_bad_update,
%% new in OTP 21
- t_reused_key_variable
+ t_reused_key_variable,
+
+ %% new in OTP 22
+ t_mixed_clause,cover_beam_trim
].
groups() -> [].
@@ -1373,22 +1379,22 @@ map_usage(Def, Used) ->
t_guard_sequence(Config) when is_list(Config) ->
- {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}),
- {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}),
- {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}),
- {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}),
- {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}),
-
- {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})),
- {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})),
- {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})),
- {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})),
- {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})),
-
- %% error case
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})),
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})),
- ok.
+ {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}),
+ {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}),
+ {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}),
+ {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}),
+ {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}),
+
+ {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})),
+ {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})),
+ {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})),
+
+ %% error case
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})),
+ ok.
t_guard_sequence_large(Config) when is_list(Config) ->
M0 = id(#{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00",03]}=>"10",
@@ -1443,21 +1449,21 @@ t_guard_sequence_large(Config) when is_list(Config) ->
18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" }),
- {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>id("a")}),
- {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>id("b")}),
- {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>id("c")}),
- {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>id("d")}),
- {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>id("e")}),
+ {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>id("a")}),
+ {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>id("b")}),
+ {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>id("c")}),
+ {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>id("d")}),
+ {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>id("e")}),
- {1,M1} = map_guard_sequence_2(M1 = id(M0#{a=>3})),
- {2,M2} = map_guard_sequence_2(M2 = id(M0#{a=>4, b=>4})),
- {3,gg,M3} = map_guard_sequence_2(M3 = id(M0#{a=>gg, b=>4})),
- {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(M0#{a=>sc, b=>3, c=>sc2})),
- {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(M0#{a=>kk, b=>other, c=>sc2})),
+ {1,M1} = map_guard_sequence_2(M1 = id(M0#{a=>3})),
+ {2,M2} = map_guard_sequence_2(M2 = id(M0#{a=>4, b=>4})),
+ {3,gg,M3} = map_guard_sequence_2(M3 = id(M0#{a=>gg, b=>4})),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(M0#{a=>sc, b=>3, c=>sc2})),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(M0#{a=>kk, b=>other, c=>sc2})),
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>id("e")})),
- {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})),
- ok.
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>id("e")})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})),
+ ok.
map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val};
map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val};
@@ -2079,7 +2085,7 @@ t_register_corruption(Config) when is_list(Config) ->
{3,wanted,<<"value">>} = register_corruption_foo(wanted,M),
ok.
-register_corruption_foo(A,#{a := V1, b := V2}) ->
+register_corruption_foo(_,#{a := V1, b := V2}) ->
register_corruption_dummy_call(1,V1,V2);
register_corruption_foo(A,#{b := V}) ->
register_corruption_dummy_call(2,A,V);
@@ -2161,6 +2167,31 @@ t_reused_key_variable(Config) when is_list(Config) ->
ok
end.
+t_mixed_clause(_Config) ->
+ put(fool_inliner, x),
+ K = get(fool_inliner),
+ {42,100} = case #{K=>42,y=>100} of
+ #{x:=X,y:=Y} ->
+ {X,Y}
+ end,
+ nomatch = case #{K=>42,y=>100} of
+ #{x:=X,y:=0} ->
+ {X,Y};
+ #{} ->
+ nomatch
+ end,
+ ok.
+
+cover_beam_trim(_Config) ->
+ val = do_cover_beam_trim(id, max, max, id, #{id=>val}),
+ ok.
+
+do_cover_beam_trim(Id, OldMax, Max, Id, M) ->
+ OldMax = id(Max),
+ #{Id:=Val} = id(M),
+ Val.
+
+
%% aux
rand_terms(0) -> [];
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 229c3093d7..60ab969929 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -25,7 +25,7 @@
match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1,
selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1,
coverage/1,grab_bag/1,literal_binary/1,
- unary_op/1]).
+ unary_op/1,eq_types/1]).
-include_lib("common_test/include/ct.hrl").
@@ -40,7 +40,7 @@ groups() ->
match_in_call,untuplify,
shortcut_boolean,letify_guard,selectify,deselectify,
underscore,match_map,map_vars_used,coverage,
- grab_bag,literal_binary,unary_op]}].
+ grab_bag,literal_binary,unary_op,eq_types]}].
init_per_suite(Config) ->
@@ -390,6 +390,13 @@ untuplify(Config) when is_list(Config) ->
%% We do this to cover sys_core_fold:unalias_pat/1.
{1,2,3,4,alias,{[1,2],{3,4},alias}} = untuplify_1([1,2], {3,4}, alias),
error = untuplify_1([1,2], {3,4}, 42),
+
+ %% Test that a previous bug in v3_codegen is gone. (The sinking of
+ %% stack frames into only the case arms that needed them was not always
+ %% safe.)
+ [33, -1, -33, 1] = untuplify_2(32, 65),
+ {33, 1, -33, -1} = untuplify_2(65, 32),
+
ok.
untuplify_1(A, B, C) ->
@@ -402,6 +409,21 @@ untuplify_1(A, B, C) ->
error
end.
+untuplify_2(V1, V2) ->
+ {D1,D2,D3,D4} =
+ if V1 > V2 ->
+ %% The 1 value was overwritten by the value of V2-V1.
+ {V1-V2, 1, V2-V1, -1};
+ true ->
+ {V2-V1, -1, V1-V2, 1}
+ end,
+ if
+ D2 > D4 ->
+ {D1, D2, D3, D4};
+ true ->
+ [D1, D2, D3, D4]
+ end.
+
%% Coverage of beam_dead:shortcut_boolean_label/4.
shortcut_boolean(Config) when is_list(Config) ->
false = shortcut_boolean_1([0]),
@@ -483,9 +505,8 @@ sel_same_value2(V) when V =:= 42; V =:= 43 ->
sel_same_value2(_) ->
error.
-%% Test deconstruction of select_val instructions in beam_peep into
-%% regular tests with just one possible value left. Hitting proper cases
-%% in beam_peep relies on unification of labels by beam_jump.
+%% Test deconstruction of select_val instructions to regular tests
+%% with zero or one values left.
deselectify(Config) when is_list(Config) ->
one_or_other = desel_tuple_arity({1}),
@@ -506,7 +527,31 @@ deselectify(Config) when is_list(Config) ->
one_or_other = dsel_atom_typecheck(one),
two = dsel_atom_typecheck(two),
- one_or_other = dsel_atom_typecheck(three).
+ one_or_other = dsel_atom_typecheck(three),
+
+ %% Cover deconstruction of select_val instructions in
+ %% beam_peep.
+
+ stop = dsel_peek_0(stop),
+ ignore = dsel_peek_0(ignore),
+ Config = dsel_peek_0(Config),
+
+ stop = dsel_peek_1(stop, any),
+ Config = dsel_peek_1(ignore, Config),
+ other = dsel_peek_1(other, ignored),
+
+ 0 = dsel_peek_2(0, any),
+ Config = dsel_peek_2(1, Config),
+ 2 = dsel_peek_2(2, ignored),
+
+ true = dsel_peek_3(true),
+ false = dsel_peek_3(false),
+ {error,Config} = dsel_peek_3(Config),
+
+ ok.
+
+%% The following will be optimized by the sharing optimizations
+%% in beam_ssa_opt.
desel_tuple_arity(Tuple) when is_tuple(Tuple) ->
case Tuple of
@@ -543,6 +588,39 @@ dsel_atom_typecheck(Val) when is_atom(Val) ->
_ -> one_or_other
end.
+%% The following functions are carefully crafted so that the sharing
+%% optimizations in beam_ssa_opt can't be applied. After applying the
+%% beam_jump:eliminate_moves/1 optimization and beam_clean:clean_labels/1
+%% has unified labels, beam_peep is able to optimize these functions.
+
+dsel_peek_0(A0) ->
+ case id(A0) of
+ stop -> stop;
+ ignore -> ignore;
+ A -> A
+ end.
+
+dsel_peek_1(A0, B) ->
+ case id(A0) of
+ stop -> stop;
+ ignore -> B;
+ A -> A
+ end.
+
+dsel_peek_2(A0, B) ->
+ case id(A0) of
+ 0 -> 0;
+ 1 -> B;
+ A -> A
+ end.
+
+dsel_peek_3(A0) ->
+ case id(A0) of
+ true -> true;
+ false -> false;
+ Other -> {error,Other}
+ end.
+
underscore(Config) when is_list(Config) ->
case Config of
[] ->
@@ -585,13 +663,26 @@ do_map_vars_used(X, Y, Map) ->
Val
end.
+-record(coverage_id, {bool=false,id}).
coverage(Config) when is_list(Config) ->
%% Cover beam_dead.
ok = coverage_1(x, a),
ok = coverage_1(x, b),
%% Cover sys_pre_expand.
- ok = coverage_3("abc").
+ ok = coverage_3("abc"),
+
+ %% Cover beam_ssa_dead.
+ {expr,key} = coverage_4([literal,get], [[expr,key]]),
+ {expr,key} = coverage_4([expr,key], []),
+
+ a = coverage_5([8,8,8], #coverage_id{bool=true}),
+ b = coverage_5([], #coverage_id{bool=true}),
+
+ %% Cover beam_ssa_opt.
+ ok = coverage_6(),
+
+ ok.
coverage_1(B, Tag) ->
case Tag of
@@ -604,6 +695,37 @@ coverage_2(2, b, x) -> ok.
coverage_3([$a]++[]++"bc") -> ok.
+%% Cover beam_ssa_dead:eval_type_test_1(is_nonempty_list, Arg).
+coverage_4([literal,get], [Expr]) ->
+ coverage_4(Expr, []);
+coverage_4([Expr,Key], []) ->
+ {Expr,Key}.
+
+%% Cover beam_ssa_dead:eval_type_test_1(is_tagged_tuple, Arg).
+coverage_5(Config, TermId)
+ when TermId =:= #coverage_id{bool=true},
+ Config =:= [8,8,8] ->
+ a;
+coverage_5(_Config, #coverage_id{bool=true}) ->
+ b.
+
+coverage_6() ->
+ X = 17,
+ case
+ case id(1) > 0 of
+ true ->
+ 17;
+ false ->
+ 42
+ end
+ of
+ X ->
+ ok;
+ V ->
+ %% Cover beam_ssa_opt:make_literal/2.
+ error([error,X,V])
+ end.
+
grab_bag(_Config) ->
[_|T] = id([a,b,c]),
[b,c] = id(T),
@@ -748,5 +870,25 @@ unary_op_1(Vop@1) ->
end
end.
+eq_types(_Config) ->
+ Ref = make_ref(),
+ Ref = eq_types(Ref, any),
+ ok.
+
+eq_types(A, B) ->
+ %% {put_tuple2,{y,0},{list,[{x,0},{x,1}]}}.
+ Term0 = {A, B},
+ Term = id(Term0),
+
+ %% {test,is_eq_exact,{f,3},[{y,0},{x,0}]}.
+ %% Here beam_validator must infer that {x,0} has the
+ %% same type as {y,0}.
+ Term = Term0,
+
+ %% {get_tuple_element,{x,0},0,{x,0}}.
+ {Ref22,_} = Term,
+
+ Ref22.
+
id(I) -> I.
diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl
index d6fc51448f..e999c8ffae 100644
--- a/lib/compiler/test/misc_SUITE.erl
+++ b/lib/compiler/test/misc_SUITE.erl
@@ -183,6 +183,7 @@ silly_coverage(Config) when is_list(Config) ->
%% beam_ssa_lint
%% beam_ssa_recv
+ %% beam_ssa_share
%% beam_ssa_pre_codegen
%% beam_ssa_opt
%% beam_ssa_codegen
@@ -190,6 +191,7 @@ silly_coverage(Config) when is_list(Config) ->
[{b_function,#{func_info=>{mod,foo,0}},args,bad_blocks,0}]},
expect_error(fun() -> beam_ssa_lint:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_recv:module(BadSSA, []) end),
+ expect_error(fun() -> beam_ssa_share:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_pre_codegen:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_opt:module(BadSSA, []) end),
expect_error(fun() -> beam_ssa_codegen:module(BadSSA, []) end),
@@ -221,10 +223,6 @@ silly_coverage(Config) when is_list(Config) ->
{label,2}|non_proper_list]}],99},
expect_error(fun() -> beam_block:module(BlockInput, []) end),
- %% beam_bs
- BsInput = BlockInput,
- expect_error(fun() -> beam_bs:module(BsInput, []) end),
-
%% beam_except
ExceptInput = {?MODULE,[{foo,0}],[],
[{function,foo,0,2,
@@ -258,7 +256,7 @@ silly_coverage(Config) when is_list(Config) ->
[{function,foo,0,2,
[{label,1},
{func_info,{atom,?MODULE},{atom,foo},0},
- {label,2},{select,op,r,{f,2},[{f,2}]}]}],
+ {label,2},{select,select_val,r,{f,2},[{f,2}]}]}],
2},
expect_error(fun() -> beam_peep:module(PeepInput, []) end),
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 4219768d6f..12108445f0 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -25,7 +25,7 @@
init_per_group/2,end_per_group/2,
init_per_testcase/2,end_per_testcase/2,
export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1,
- wait/1,recv_in_try/1,double_recv/1]).
+ wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1]).
-include_lib("common_test/include/ct.hrl").
@@ -45,7 +45,7 @@ all() ->
groups() ->
[{p,test_lib:parallel(),
[recv,coverage,otp_7980,ref_opt,export,wait,
- recv_in_try,double_recv]}].
+ recv_in_try,double_recv,receive_var_zero]}].
init_per_suite(Config) ->
@@ -378,4 +378,27 @@ do_double_recv(_, Msg) ->
error
end.
+%% Test 'after Z', when Z =:= 0 been propagated as an immediate by the type
+%% optimization pass.
+receive_var_zero(Config) when is_list(Config) ->
+ self() ! x,
+ self() ! y,
+ Z = zero(),
+ timeout = receive
+ z -> ok
+ after Z -> timeout
+ end,
+ timeout = receive
+ after Z -> timeout
+ end,
+ self() ! w,
+ receive
+ x -> ok;
+ Other ->
+ ct:fail({bad_message,Other})
+ end.
+
+zero() -> 0.
+
+
id(I) -> I.
diff --git a/lib/compiler/test/regressions_SUITE.erl b/lib/compiler/test/regressions_SUITE.erl
index 9b0b9b0c38..39febf060f 100644
--- a/lib/compiler/test/regressions_SUITE.erl
+++ b/lib/compiler/test/regressions_SUITE.erl
@@ -23,7 +23,7 @@
-export([all/0,groups/0,init_per_testcase/2,end_per_testcase/2,
init_per_group/2,end_per_group/2,
- init_per_testcase/2,end_per_testcase/2,
+ init_per_suite/1,end_per_suite/1,
suite/0]).
-export([maps/1]).
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index 4502f5b68a..26149e11e6 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -81,6 +81,8 @@ opt_opts(Mod) ->
(no_put_tuple2) -> true;
(no_bsm3) -> true;
(no_bsm_opt) -> true;
+ (no_module_opt) -> true;
+ (no_type_opt) -> true;
(_) -> false
end, Opts).
@@ -93,8 +95,9 @@ get_data_dir(Config) ->
Opts = [{return,list}],
Data1 = re:replace(Data0, "_no_opt_SUITE", "_SUITE", Opts),
Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts),
- Data = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts),
- re:replace(Data, "_r21_SUITE", "_SUITE", Opts).
+ Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts),
+ Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts),
+ re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts).
is_cloned_mod(Mod) ->
is_cloned_mod_1(atom_to_list(Mod)).
@@ -105,6 +108,7 @@ is_cloned_mod_1("no_opt_SUITE") -> true;
is_cloned_mod_1("post_opt_SUITE") -> true;
is_cloned_mod_1("inline_SUITE") -> true;
is_cloned_mod_1("21_SUITE") -> true;
+is_cloned_mod_1("no_module_opt_SUITE") -> true;
is_cloned_mod_1([_|T]) -> is_cloned_mod_1(T);
is_cloned_mod_1([]) -> false.
diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl
index 1b7ef4ddb0..8f9cd9ab1e 100644
--- a/lib/compiler/test/trycatch_SUITE.erl
+++ b/lib/compiler/test/trycatch_SUITE.erl
@@ -1189,7 +1189,8 @@ bad_raise(Expr) ->
test_raise(Expr) ->
test_raise_1(Expr),
test_raise_2(Expr),
- test_raise_3(Expr).
+ test_raise_3(Expr),
+ test_raise_4(Expr).
test_raise_1(Expr) ->
erase(exception),
@@ -1263,5 +1264,28 @@ do_test_raise_3(Expr) ->
erlang:raise(exit, {exception,C,E}, Stk)
end.
+test_raise_4(Expr) ->
+ try
+ do_test_raise_4(Expr)
+ catch
+ exit:{exception,C,E,Stk}:Stk ->
+ try
+ Expr()
+ catch
+ C:E:S ->
+ [StkTop|_] = S,
+ [StkTop|_] = Stk
+ end
+ end.
+
+do_test_raise_4(Expr) ->
+ try
+ Expr()
+ catch
+ C:E:Stk ->
+ %% Here the stacktrace must be built.
+ erlang:raise(exit, {exception,C,E,Stk}, Stk)
+ end.
+
id(I) -> I.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 92f8aec424..efedb414ad 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.2.7
+COMPILER_VSN = 7.3.1
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index cd0e5442e9..508e1c40ee 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -71,7 +71,35 @@ PRIVDIR = ../priv
OBJDIR = $(PRIVDIR)/obj/$(TARGET)
LIBDIR = $(PRIVDIR)/lib/$(TARGET)
-CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o
+CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \
+ $(OBJDIR)/aead$(TYPEMARKER).o \
+ $(OBJDIR)/aes$(TYPEMARKER).o \
+ $(OBJDIR)/algorithms$(TYPEMARKER).o \
+ $(OBJDIR)/atoms$(TYPEMARKER).o \
+ $(OBJDIR)/block$(TYPEMARKER).o \
+ $(OBJDIR)/bn$(TYPEMARKER).o \
+ $(OBJDIR)/chacha20$(TYPEMARKER).o \
+ $(OBJDIR)/cipher$(TYPEMARKER).o \
+ $(OBJDIR)/cmac$(TYPEMARKER).o \
+ $(OBJDIR)/dh$(TYPEMARKER).o \
+ $(OBJDIR)/digest$(TYPEMARKER).o \
+ $(OBJDIR)/dss$(TYPEMARKER).o \
+ $(OBJDIR)/ec$(TYPEMARKER).o \
+ $(OBJDIR)/ecdh$(TYPEMARKER).o \
+ $(OBJDIR)/eddsa$(TYPEMARKER).o \
+ $(OBJDIR)/engine$(TYPEMARKER).o \
+ $(OBJDIR)/evp$(TYPEMARKER).o \
+ $(OBJDIR)/fips$(TYPEMARKER).o \
+ $(OBJDIR)/hash$(TYPEMARKER).o \
+ $(OBJDIR)/hmac$(TYPEMARKER).o \
+ $(OBJDIR)/info$(TYPEMARKER).o \
+ $(OBJDIR)/math$(TYPEMARKER).o \
+ $(OBJDIR)/pkey$(TYPEMARKER).o \
+ $(OBJDIR)/poly1305$(TYPEMARKER).o \
+ $(OBJDIR)/rand$(TYPEMARKER).o \
+ $(OBJDIR)/rc4$(TYPEMARKER).o \
+ $(OBJDIR)/rsa$(TYPEMARKER).o \
+ $(OBJDIR)/srp$(TYPEMARKER).o
CALLBACK_OBJS = $(OBJDIR)/crypto_callback$(TYPEMARKER).o
NIF_MAKEFILE = $(PRIVDIR)/Makefile
CRYPTO_STATIC_OBJS = $(OBJDIR)/crypto_static$(TYPEMARKER).o\
@@ -172,24 +200,21 @@ $(LIBDIR)/crypto_callback$(TYPEMARKER).dll: $(CALLBACK_OBJS)
endif
-clean:
- rm -f $(LIBDIR)/crypto.@DED_EXT@
- rm -f $(LIBDIR)/crypto.debug.@DED_EXT@
- rm -f $(LIBDIR)/crypto.valgrind.@DED_EXT@
- rm -f $(LIBDIR)/crypto_callback.@DED_EXT@
- rm -f $(LIBDIR)/crypto_callback.debug.@DED_EXT@
- rm -f $(LIBDIR)/crypto_callback.valgrind.@DED_EXT@
- rm -f $(LIBDIR)/otp_test_engine.@DED_EXT@
- rm -f $(OBJDIR)/crypto.o
- rm -f $(OBJDIR)/crypto_static.o
- rm -f $(OBJDIR)/crypto.debug.o
- rm -f $(OBJDIR)/crypto_static.debug.o
- rm -f $(OBJDIR)/crypto.valgrind.o
- rm -f $(OBJDIR)/crypto_static.valgrind.o
- rm -f $(OBJDIR)/crypto_callback.o
- rm -f $(OBJDIR)/crypto_callback.debug.o
- rm -f $(OBJDIR)/crypto_callback.valgrind.o
- rm -f $(OBJDIR)/otp_test_engine.o
+CLEAN_OBJS_RAW = $(CRYPTO_OBJS) $(CALLBACK_OBJS) $(CRYPTO_STATIC_OBJS) $(TEST_ENGINE_OBJS)
+CLEAN_OBJS_O = $(patsubst %.debug.o,%.o,$(CLEAN_OBJS_RAW:.valgrind.o=.o))
+
+CLEAN_LIBS_RAW = $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB)
+CLEAN_LIBS_SO = $(patsubst %.debug.@DED_EXT@,%.@DED_EXT@,$(CLEAN_LIBS_RAW:.valgrind.@DED_EXT@=.@DED_EXT@))
+
+clean_dynamic_libs:
+ rm -f $(CLEAN_LIBS_SO)
+ rm -f $(foreach T,.valgrind.@DED_EXT@ .debug.@DED_EXT@,$(CLEAN_LIBS_SO:.@DED_EXT@=$T))
+
+clean_objs:
+ rm -f $(CLEAN_OBJS_O)
+ rm -f $(foreach T,.valgrind.o .debug.o,$(CLEAN_OBJS_O:.o=$T))
+
+clean: clean_objs clean_dynamic_libs
rm -f core *~
docs:
diff --git a/lib/crypto/c_src/aead.c b/lib/crypto/c_src/aead.c
new file mode 100644
index 0000000000..b7ed06e3bc
--- /dev/null
+++ b/lib/crypto/c_src/aead.c
@@ -0,0 +1,233 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "aead.h"
+#include "aes.h"
+
+ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Key,Iv,AAD,In) */
+#if defined(HAVE_AEAD)
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher = NULL;
+ ErlNifBinary key, iv, aad, in;
+ unsigned int tag_len;
+ unsigned char *outp, *tagp;
+ ERL_NIF_TERM type, out, out_tag;
+ int len, ctx_ctrl_set_ivlen, ctx_ctrl_get_tag;
+
+ type = argv[0];
+
+ if (!enif_is_atom(env, type)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &key)
+ || !enif_inspect_binary(env, argv[2], &iv)
+ || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
+ || !enif_inspect_iolist_as_binary(env, argv[4], &in)
+ || !enif_get_uint(env, argv[5], &tag_len)) {
+ return enif_make_badarg(env);
+ }
+
+ /* Use cipher_type some day. Must check block_encrypt|decrypt first */
+#if defined(HAVE_GCM)
+ if (type == atom_aes_gcm) {
+ if ((iv.size > 0)
+ && (1 <= tag_len && tag_len <= 16)) {
+ ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN;
+ ctx_ctrl_get_tag = EVP_CTRL_GCM_GET_TAG;
+ if (key.size == 16) cipher = EVP_aes_128_gcm();
+ else if (key.size == 24) cipher = EVP_aes_192_gcm();
+ else if (key.size == 32) cipher = EVP_aes_256_gcm();
+ else enif_make_badarg(env);
+ } else
+ enif_make_badarg(env);
+ } else
+#endif
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if ((7 <= iv.size && iv.size <= 13)
+ && (4 <= tag_len && tag_len <= 16)
+ && ((tag_len & 1) == 0)
+ ) {
+ ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN;
+ ctx_ctrl_get_tag = EVP_CTRL_CCM_GET_TAG;
+ if (key.size == 16) cipher = EVP_aes_128_ccm();
+ else if (key.size == 24) cipher = EVP_aes_192_ccm();
+ else if (key.size == 32) cipher = EVP_aes_256_ccm();
+ else enif_make_badarg(env);
+ } else
+ enif_make_badarg(env);
+ } else
+#endif
+#if defined(HAVE_CHACHA20_POLY1305)
+ if (type == atom_chacha20_poly1305) {
+ if ((key.size == 32)
+ && (1 <= iv.size && iv.size <= 16)
+ && (tag_len == 16)
+ ) {
+ ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN;
+ ctx_ctrl_get_tag = EVP_CTRL_AEAD_GET_TAG,
+ cipher = EVP_chacha20_poly1305();
+ } else enif_make_badarg(env);
+ } else
+#endif
+ return enif_raise_exception(env, atom_notsup);
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err;
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err;
+
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL) != 1) goto out_err;
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
+ if (EVP_EncryptUpdate(ctx, NULL, &len, NULL, in.size) != 1) goto out_err;
+ } else
+#endif
+ if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
+
+ if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err;
+
+ outp = enif_make_new_binary(env, in.size, &out);
+
+ if (EVP_EncryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err;
+ if (EVP_EncryptFinal_ex(ctx, outp/*+len*/, &len) != 1) goto out_err;
+
+ tagp = enif_make_new_binary(env, tag_len, &out_tag);
+
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_get_tag, tag_len, tagp) != 1) goto out_err;
+
+ EVP_CIPHER_CTX_free(ctx);
+ CONSUME_REDS(env, in);
+ return enif_make_tuple2(env, out, out_tag);
+
+out_err:
+ EVP_CIPHER_CTX_free(ctx);
+ return atom_error;
+
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
+ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Key,Iv,AAD,In,Tag) */
+#if defined(HAVE_AEAD)
+ EVP_CIPHER_CTX *ctx;
+ const EVP_CIPHER *cipher = NULL;
+ ErlNifBinary key, iv, aad, in, tag;
+ unsigned char *outp;
+ ERL_NIF_TERM type, out;
+ int len, ctx_ctrl_set_ivlen, ctx_ctrl_set_tag;
+
+ type = argv[0];
+#if defined(HAVE_GCM_EVP_DECRYPT_BUG)
+ if (type == atom_aes_gcm)
+ return aes_gcm_decrypt_NO_EVP(env, argc, argv);
+#endif
+
+ if (!enif_is_atom(env, type)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &key)
+ || !enif_inspect_binary(env, argv[2], &iv)
+ || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
+ || !enif_inspect_iolist_as_binary(env, argv[4], &in)
+ || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) {
+ return enif_make_badarg(env);
+ }
+
+ /* Use cipher_type some day. Must check block_encrypt|decrypt first */
+#if defined(HAVE_GCM)
+ if (type == atom_aes_gcm) {
+ if (iv.size > 0) {
+ ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN;
+ ctx_ctrl_set_tag = EVP_CTRL_GCM_SET_TAG;
+ if (key.size == 16) cipher = EVP_aes_128_gcm();
+ else if (key.size == 24) cipher = EVP_aes_192_gcm();
+ else if (key.size == 32) cipher = EVP_aes_256_gcm();
+ else enif_make_badarg(env);
+ } else
+ enif_make_badarg(env);
+ } else
+#endif
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if (iv.size > 0) {
+ ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN;
+ if (key.size == 16) cipher = EVP_aes_128_ccm();
+ else if (key.size == 24) cipher = EVP_aes_192_ccm();
+ else if (key.size == 32) cipher = EVP_aes_256_ccm();
+ else enif_make_badarg(env);
+ } else
+ enif_make_badarg(env);
+ } else
+#endif
+#if defined(HAVE_CHACHA20_POLY1305)
+ if (type == atom_chacha20_poly1305) {
+ if ((key.size == 32)
+ && (1 <= iv.size && iv.size <= 16)
+ && tag.size == 16
+ ) {
+ ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN;
+ ctx_ctrl_set_tag = EVP_CTRL_AEAD_SET_TAG;
+ cipher = EVP_chacha20_poly1305();
+ } else enif_make_badarg(env);
+ } else
+#endif
+ return enif_raise_exception(env, atom_notsup);
+
+ outp = enif_make_new_binary(env, in.size, &out);
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err;
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err;
+
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag.size, tag.data) != 1) goto out_err;
+ }
+#endif
+
+ if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
+
+#if defined(HAVE_CCM)
+ if (type == atom_aes_ccm) {
+ if (1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, in.size)) goto out_err;
+ }
+#endif
+
+ if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err;
+ if (EVP_DecryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err;
+
+#if defined(HAVE_GCM) || defined(HAVE_CHACHA20_POLY1305)
+ if (type == atom_aes_gcm) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, tag.size, tag.data) != 1) goto out_err;
+ if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1) goto out_err;
+ }
+#endif
+ EVP_CIPHER_CTX_free(ctx);
+
+ CONSUME_REDS(env, in);
+ return out;
+
+out_err:
+ EVP_CIPHER_CTX_free(ctx);
+ return atom_error;
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
diff --git a/lib/crypto/c_src/aead.h b/lib/crypto/c_src/aead.h
new file mode 100644
index 0000000000..54c0711535
--- /dev/null
+++ b/lib/crypto/c_src/aead.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_AEAD_H__
+#define E_AEAD_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_AEAD_H__ */
diff --git a/lib/crypto/c_src/aes.c b/lib/crypto/c_src/aes.c
new file mode 100644
index 0000000000..36cd02933f
--- /dev/null
+++ b/lib/crypto/c_src/aes.c
@@ -0,0 +1,282 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "aes.h"
+#include "cipher.h"
+
+ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+ ErlNifBinary key, ivec, text;
+ AES_KEY aes_key;
+ unsigned char ivec_clone[16]; /* writable copy */
+ int new_ivlen = 0;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
+ || !(key.size == 16 || key.size == 24 || key.size == 32)
+ || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
+ || !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
+ return enif_make_badarg(env);
+ }
+
+ memcpy(ivec_clone, ivec.data, 16);
+ AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
+ AES_cfb8_encrypt((unsigned char *) text.data,
+ enif_make_new_binary(env, text.size, &ret),
+ text.size, &aes_key, ivec_clone, &new_ivlen,
+ (argv[3] == atom_true));
+ CONSUME_REDS(env,text);
+ return ret;
+}
+
+ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+ ErlNifBinary key, ivec, text;
+ AES_KEY aes_key;
+ unsigned char ivec_clone[16]; /* writable copy */
+ int new_ivlen = 0;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
+ || !(key.size == 16 || key.size == 24 || key.size == 32)
+ || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
+ || !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
+ return enif_make_badarg(env);
+ }
+
+ memcpy(ivec_clone, ivec.data, 16);
+ AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
+ AES_cfb128_encrypt((unsigned char *) text.data,
+ enif_make_new_binary(env, text.size, &ret),
+ text.size, &aes_key, ivec_clone, &new_ivlen,
+ (argv[3] == atom_true));
+ CONSUME_REDS(env,text);
+ return ret;
+}
+
+ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+#ifdef HAVE_AES_IGE
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ AES_KEY aes_key;
+ unsigned char ivec[32];
+ int i;
+ unsigned char* ret_ptr;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || (key_bin.size != 16 && key_bin.size != 32)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 32
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)
+ || data_bin.size % 16 != 0) {
+
+ return enif_make_badarg(env);
+ }
+
+ if (argv[3] == atom_true) {
+ i = AES_ENCRYPT;
+ AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key);
+ }
+ else {
+ i = AES_DECRYPT;
+ AES_set_decrypt_key(key_bin.data, key_bin.size*8, &aes_key);
+ }
+
+ ret_ptr = enif_make_new_binary(env, data_bin.size, &ret);
+ memcpy(ivec, ivec_bin.data, 32); /* writable copy */
+ AES_ige_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, i);
+ CONSUME_REDS(env,data_bin);
+ return ret;
+#else
+ return atom_notsup;
+#endif
+}
+
+
+/* Initializes state for ctr streaming (de)encryption
+*/
+#ifdef HAVE_EVP_AES_CTR
+ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec) */
+ ErlNifBinary key_bin, ivec_bin;
+ struct evp_cipher_ctx *ctx;
+ const EVP_CIPHER *cipher;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 16) {
+ return enif_make_badarg(env);
+ }
+
+ switch (key_bin.size)
+ {
+ case 16: cipher = EVP_aes_128_ctr(); break;
+ case 24: cipher = EVP_aes_192_ctr(); break;
+ case 32: cipher = EVP_aes_256_ctr(); break;
+ default: return enif_make_badarg(env);
+ }
+
+ ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
+ ctx->ctx = EVP_CIPHER_CTX_new();
+ EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
+ key_bin.data, ivec_bin.data, 1);
+ EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
+}
+
+ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ struct evp_cipher_ctx *ctx, *new_ctx;
+ ErlNifBinary data_bin;
+ ERL_NIF_TERM ret, cipher_term;
+ unsigned char *out;
+ int outl = 0;
+
+ if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+ new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
+ new_ctx->ctx = EVP_CIPHER_CTX_new();
+ EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx);
+ out = enif_make_new_binary(env, data_bin.size, &cipher_term);
+ EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size);
+ ASSERT(outl == data_bin.size);
+
+ ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
+ enif_release_resource(new_ctx);
+ CONSUME_REDS(env,data_bin);
+ return ret;
+}
+
+#else /* if not HAVE_EVP_AES_CTR */
+
+ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec) */
+ ErlNifBinary key_bin, ivec_bin;
+ ERL_NIF_TERM ecount_bin;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || !(key_bin.size == 16 || key_bin.size == 24 || key_bin.size ==32)
+ || ivec_bin.size != 16) {
+ return enif_make_badarg(env);
+ }
+
+ memset(enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin),
+ 0, AES_BLOCK_SIZE);
+ return enif_make_tuple4(env, argv[0], argv[1], ecount_bin, enif_make_int(env, 0));
+}
+
+ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* ({Key, IVec, ECount, Num}, Data) */
+ ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin;
+ AES_KEY aes_key;
+ unsigned int num;
+ ERL_NIF_TERM ret, num2_term, cipher_term, ivec2_term, ecount2_term, new_state_term;
+ int state_arity;
+ const ERL_NIF_TERM *state_term;
+ unsigned char * ivec2_buf;
+ unsigned char * ecount2_buf;
+
+ if (!enif_get_tuple(env, argv[0], &state_arity, &state_term)
+ || state_arity != 4
+ || !enif_inspect_iolist_as_binary(env, state_term[0], &key_bin)
+ || AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key) != 0
+ || !enif_inspect_binary(env, state_term[1], &ivec_bin) || ivec_bin.size != 16
+ || !enif_inspect_binary(env, state_term[2], &ecount_bin) || ecount_bin.size != AES_BLOCK_SIZE
+ || !enif_get_uint(env, state_term[3], &num)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &text_bin)) {
+ return enif_make_badarg(env);
+ }
+
+ ivec2_buf = enif_make_new_binary(env, ivec_bin.size, &ivec2_term);
+ ecount2_buf = enif_make_new_binary(env, ecount_bin.size, &ecount2_term);
+
+ memcpy(ivec2_buf, ivec_bin.data, 16);
+ memcpy(ecount2_buf, ecount_bin.data, ecount_bin.size);
+
+ AES_ctr128_encrypt((unsigned char *) text_bin.data,
+ enif_make_new_binary(env, text_bin.size, &cipher_term),
+ text_bin.size, &aes_key, ivec2_buf, ecount2_buf, &num);
+
+ num2_term = enif_make_uint(env, num);
+ new_state_term = enif_make_tuple4(env, state_term[0], ivec2_term, ecount2_term, num2_term);
+ ret = enif_make_tuple2(env, new_state_term, cipher_term);
+ CONSUME_REDS(env,text_bin);
+ return ret;
+}
+#endif /* !HAVE_EVP_AES_CTR */
+
+#ifdef HAVE_GCM_EVP_DECRYPT_BUG
+ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Key,Iv,AAD,In,Tag) */
+ GCM128_CONTEXT *ctx;
+ ErlNifBinary key, iv, aad, in, tag;
+ AES_KEY aes_key;
+ unsigned char *outp;
+ ERL_NIF_TERM out;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
+ || AES_set_encrypt_key(key.data, key.size*8, &aes_key) != 0
+ || !enif_inspect_binary(env, argv[2], &iv) || iv.size == 0
+ || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
+ || !enif_inspect_iolist_as_binary(env, argv[4], &in)
+ || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) {
+ return enif_make_badarg(env);
+ }
+
+ if (!(ctx = CRYPTO_gcm128_new(&aes_key, (block128_f)AES_encrypt)))
+ return atom_error;
+
+ CRYPTO_gcm128_setiv(ctx, iv.data, iv.size);
+
+ if (CRYPTO_gcm128_aad(ctx, aad.data, aad.size))
+ goto out_err;
+
+ outp = enif_make_new_binary(env, in.size, &out);
+
+ /* decrypt */
+ if (CRYPTO_gcm128_decrypt(ctx, in.data, outp, in.size))
+ goto out_err;
+
+ /* calculate and check the tag */
+ if (CRYPTO_gcm128_finish(ctx, tag.data, tag.size))
+ goto out_err;
+
+ CRYPTO_gcm128_release(ctx);
+ CONSUME_REDS(env, in);
+
+ return out;
+
+out_err:
+ CRYPTO_gcm128_release(ctx);
+ return atom_error;
+}
+#endif /* HAVE_GCM_EVP_DECRYPT_BUG */
+
diff --git a/lib/crypto/c_src/aes.h b/lib/crypto/c_src/aes.h
new file mode 100644
index 0000000000..09c984f84a
--- /dev/null
+++ b/lib/crypto/c_src/aes.h
@@ -0,0 +1,36 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_AES_H__
+#define E_AES_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#ifdef HAVE_GCM_EVP_DECRYPT_BUG
+ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+#endif
+
+#endif /* E_AES_H__ */
diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c
new file mode 100644
index 0000000000..a6e61cc9b2
--- /dev/null
+++ b/lib/crypto/c_src/algorithms.c
@@ -0,0 +1,320 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "algorithms.h"
+
+static int algo_hash_cnt, algo_hash_fips_cnt;
+static ERL_NIF_TERM algo_hash[12]; /* increase when extending the list */
+static int algo_pubkey_cnt, algo_pubkey_fips_cnt;
+static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */
+static int algo_cipher_cnt, algo_cipher_fips_cnt;
+static ERL_NIF_TERM algo_cipher[25]; /* increase when extending the list */
+static int algo_mac_cnt, algo_mac_fips_cnt;
+static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */
+static int algo_curve_cnt, algo_curve_fips_cnt;
+static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */
+static int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt;
+static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */
+
+void init_algorithms_types(ErlNifEnv* env)
+{
+ // Validated algorithms first
+ algo_hash_cnt = 0;
+ algo_hash[algo_hash_cnt++] = atom_sha;
+#ifdef HAVE_SHA224
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha224");
+#endif
+#ifdef HAVE_SHA256
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha256");
+#endif
+#ifdef HAVE_SHA384
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha384");
+#endif
+#ifdef HAVE_SHA512
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha512");
+#endif
+#ifdef HAVE_SHA3_224
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_224");
+#endif
+#ifdef HAVE_SHA3_256
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_256");
+#endif
+#ifdef HAVE_SHA3_384
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_384");
+#endif
+#ifdef HAVE_SHA3_512
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512");
+#endif
+ // Non-validated algorithms follow
+ algo_hash_fips_cnt = algo_hash_cnt;
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md4");
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md5");
+ algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160");
+
+ algo_pubkey_cnt = 0;
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa");
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dss");
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dh");
+#if defined(HAVE_EC)
+#if !defined(OPENSSL_NO_EC2M)
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ec_gf2m");
+#endif
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdsa");
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdh");
+#endif
+ // Non-validated algorithms follow
+ algo_pubkey_fips_cnt = algo_pubkey_cnt;
+ // Don't know if Edward curves are fips validated
+#if defined(HAVE_EDDSA)
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa");
+#endif
+ algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
+
+ // Validated algorithms first
+ algo_cipher_cnt = 0;
+#ifndef OPENSSL_NO_DES
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbc");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des_ede3");
+#ifdef HAVE_DES_ede3_cfb_encrypt
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbf");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cfb");
+#endif
+#endif
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc128");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb8");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb128");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc256");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ctr");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ecb");
+#if defined(HAVE_GCM)
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_gcm");
+#endif
+#if defined(HAVE_CCM)
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ccm");
+#endif
+ // Non-validated algorithms follow
+ algo_cipher_fips_cnt = algo_cipher_cnt;
+#ifdef HAVE_AES_IGE
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ige256");
+#endif
+#ifndef OPENSSL_NO_DES
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cbc");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cfb");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_ecb");
+#endif
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cbc");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cfb64");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ofb64");
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ecb");
+#ifndef OPENSSL_NO_RC2
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc2_cbc");
+#endif
+#ifndef OPENSSL_NO_RC4
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc4");
+#endif
+#if defined(HAVE_CHACHA20_POLY1305)
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20_poly1305");
+#endif
+#if defined(HAVE_CHACHA20)
+ algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20");
+#endif
+
+ // Validated algorithms first
+ algo_mac_cnt = 0;
+ algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac");
+#ifdef HAVE_CMAC
+ algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac");
+#endif
+#ifdef HAVE_POLY1305
+ algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305");
+#endif
+ // Non-validated algorithms follow
+ algo_mac_fips_cnt = algo_mac_cnt;
+
+ // Validated algorithms first
+ algo_curve_cnt = 0;
+#if defined(HAVE_EC)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1");
+#if !defined(OPENSSL_NO_EC2M)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11");
+#endif
+#endif
+ // Non-validated algorithms follow
+ algo_curve_fips_cnt = algo_curve_cnt;
+#if defined(HAVE_EC)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8");
+#if !defined(OPENSSL_NO_EC2M)
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4");
+#endif
+#endif
+ //--
+#ifdef HAVE_EDDSA
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448");
+#endif
+#ifdef HAVE_ED_CURVE_DH
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519");
+ algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448");
+#endif
+
+ // Validated algorithms first
+ algo_rsa_opts_cnt = 0;
+#ifdef HAS_EVP_PKEY_CTX
+# ifdef HAVE_RSA_PKCS1_PSS_PADDING
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding");
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen");
+# endif
+# ifdef HAVE_RSA_MGF1_MD
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md");
+# endif
+# ifdef HAVE_RSA_OAEP_PADDING
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
+# endif
+# ifdef HAVE_RSA_OAEP_MD
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label");
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md");
+# endif
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"signature_md");
+#endif
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_padding");
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_x931_padding");
+#ifdef HAVE_RSA_SSLV23_PADDING
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_sslv23_padding");
+#endif
+ algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding");
+ algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt;
+
+
+ // Check that the max number of algos is updated
+ ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_cipher_cnt <= sizeof(algo_cipher)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
+ ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM));
+}
+
+ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef FIPS_SUPPORT
+ int fips_mode = FIPS_mode();
+ int hash_cnt = fips_mode ? algo_hash_fips_cnt : algo_hash_cnt;
+ int pubkey_cnt = fips_mode ? algo_pubkey_fips_cnt : algo_pubkey_cnt;
+ int cipher_cnt = fips_mode ? algo_cipher_fips_cnt : algo_cipher_cnt;
+ int mac_cnt = fips_mode ? algo_mac_fips_cnt : algo_mac_cnt;
+ int curve_cnt = fips_mode ? algo_curve_fips_cnt : algo_curve_cnt;
+ int rsa_opts_cnt = fips_mode ? algo_rsa_opts_fips_cnt : algo_rsa_opts_cnt;
+#else
+ int hash_cnt = algo_hash_cnt;
+ int pubkey_cnt = algo_pubkey_cnt;
+ int cipher_cnt = algo_cipher_cnt;
+ int mac_cnt = algo_mac_cnt;
+ int curve_cnt = algo_curve_cnt;
+ int rsa_opts_cnt = algo_rsa_opts_cnt;
+#endif
+ return enif_make_tuple6(env,
+ enif_make_list_from_array(env, algo_hash, hash_cnt),
+ enif_make_list_from_array(env, algo_pubkey, pubkey_cnt),
+ enif_make_list_from_array(env, algo_cipher, cipher_cnt),
+ enif_make_list_from_array(env, algo_mac, mac_cnt),
+ enif_make_list_from_array(env, algo_curve, curve_cnt),
+ enif_make_list_from_array(env, algo_rsa_opts, rsa_opts_cnt)
+ );
+}
diff --git a/lib/crypto/c_src/algorithms.h b/lib/crypto/c_src/algorithms.h
new file mode 100644
index 0000000000..068fb661ec
--- /dev/null
+++ b/lib/crypto/c_src/algorithms.h
@@ -0,0 +1,30 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_ALGORITHMS_H__
+#define E_ALGORITHMS_H__ 1
+
+#include "common.h"
+
+void init_algorithms_types(ErlNifEnv* env);
+
+ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_ALGORITHMS_H__ */
diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
new file mode 100644
index 0000000000..3a028b9a67
--- /dev/null
+++ b/lib/crypto/c_src/atoms.c
@@ -0,0 +1,271 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "atoms.h"
+
+ERL_NIF_TERM atom_true;
+ERL_NIF_TERM atom_false;
+ERL_NIF_TERM atom_sha;
+ERL_NIF_TERM atom_error;
+ERL_NIF_TERM atom_rsa_pkcs1_padding;
+ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
+ERL_NIF_TERM atom_rsa_no_padding;
+ERL_NIF_TERM atom_signature_md;
+ERL_NIF_TERM atom_undefined;
+
+ERL_NIF_TERM atom_ok;
+ERL_NIF_TERM atom_not_prime;
+ERL_NIF_TERM atom_not_strong_prime;
+ERL_NIF_TERM atom_unable_to_check_generator;
+ERL_NIF_TERM atom_not_suitable_generator;
+ERL_NIF_TERM atom_check_failed;
+ERL_NIF_TERM atom_unknown;
+ERL_NIF_TERM atom_none;
+ERL_NIF_TERM atom_notsup;
+ERL_NIF_TERM atom_digest;
+#ifdef FIPS_SUPPORT
+ERL_NIF_TERM atom_enabled;
+ERL_NIF_TERM atom_not_enabled;
+#else
+ERL_NIF_TERM atom_not_supported;
+#endif
+
+#if defined(HAVE_EC)
+ERL_NIF_TERM atom_ec;
+ERL_NIF_TERM atom_prime_field;
+ERL_NIF_TERM atom_characteristic_two_field;
+ERL_NIF_TERM atom_tpbasis;
+ERL_NIF_TERM atom_ppbasis;
+ERL_NIF_TERM atom_onbasis;
+#endif
+
+ERL_NIF_TERM atom_aes_cfb8;
+ERL_NIF_TERM atom_aes_cfb128;
+#ifdef HAVE_GCM
+ERL_NIF_TERM atom_aes_gcm;
+#endif
+#ifdef HAVE_CCM
+ERL_NIF_TERM atom_aes_ccm;
+#endif
+#ifdef HAVE_CHACHA20_POLY1305
+ERL_NIF_TERM atom_chacha20_poly1305;
+#endif
+#ifdef HAVE_ECB_IVEC_BUG
+ERL_NIF_TERM atom_aes_ecb;
+ERL_NIF_TERM atom_des_ecb;
+ERL_NIF_TERM atom_blowfish_ecb;
+#endif
+
+ERL_NIF_TERM atom_rsa;
+ERL_NIF_TERM atom_dss;
+ERL_NIF_TERM atom_ecdsa;
+
+#ifdef HAVE_ED_CURVE_DH
+ERL_NIF_TERM atom_x25519;
+ERL_NIF_TERM atom_x448;
+#endif
+
+ERL_NIF_TERM atom_eddsa;
+#ifdef HAVE_EDDSA
+ERL_NIF_TERM atom_ed25519;
+ERL_NIF_TERM atom_ed448;
+#endif
+
+ERL_NIF_TERM atom_rsa_mgf1_md;
+ERL_NIF_TERM atom_rsa_oaep_label;
+ERL_NIF_TERM atom_rsa_oaep_md;
+ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */
+ERL_NIF_TERM atom_rsa_padding;
+ERL_NIF_TERM atom_rsa_pkcs1_pss_padding;
+#ifdef HAVE_RSA_SSLV23_PADDING
+ERL_NIF_TERM atom_rsa_sslv23_padding;
+#endif
+ERL_NIF_TERM atom_rsa_x931_padding;
+ERL_NIF_TERM atom_rsa_pss_saltlen;
+ERL_NIF_TERM atom_sha224;
+ERL_NIF_TERM atom_sha256;
+ERL_NIF_TERM atom_sha384;
+ERL_NIF_TERM atom_sha512;
+ERL_NIF_TERM atom_sha3_224;
+ERL_NIF_TERM atom_sha3_256;
+ERL_NIF_TERM atom_sha3_384;
+ERL_NIF_TERM atom_sha3_512;
+ERL_NIF_TERM atom_md5;
+ERL_NIF_TERM atom_ripemd160;
+
+#ifdef HAS_ENGINE_SUPPORT
+ERL_NIF_TERM atom_bad_engine_method;
+ERL_NIF_TERM atom_bad_engine_id;
+ERL_NIF_TERM atom_ctrl_cmd_failed;
+ERL_NIF_TERM atom_engine_init_failed;
+ERL_NIF_TERM atom_register_engine_failed;
+ERL_NIF_TERM atom_add_engine_failed;
+ERL_NIF_TERM atom_remove_engine_failed;
+ERL_NIF_TERM atom_engine_method_not_supported;
+
+ERL_NIF_TERM atom_engine_method_rsa;
+ERL_NIF_TERM atom_engine_method_dsa;
+ERL_NIF_TERM atom_engine_method_dh;
+ERL_NIF_TERM atom_engine_method_rand;
+ERL_NIF_TERM atom_engine_method_ecdh;
+ERL_NIF_TERM atom_engine_method_ecdsa;
+ERL_NIF_TERM atom_engine_method_ciphers;
+ERL_NIF_TERM atom_engine_method_digests;
+ERL_NIF_TERM atom_engine_method_store;
+ERL_NIF_TERM atom_engine_method_pkey_meths;
+ERL_NIF_TERM atom_engine_method_pkey_asn1_meths;
+ERL_NIF_TERM atom_engine_method_ec;
+
+ERL_NIF_TERM atom_engine;
+ERL_NIF_TERM atom_key_id;
+ERL_NIF_TERM atom_password;
+#endif
+
+int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM load_info) {
+ atom_true = enif_make_atom(env,"true");
+ atom_false = enif_make_atom(env,"false");
+ /* Enter FIPS mode */
+ if (fips_mode == atom_true) {
+#ifdef FIPS_SUPPORT
+ if (!FIPS_mode_set(1)) {
+#else
+ {
+#endif
+ PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
+ return 0;
+ }
+ } else if (fips_mode != atom_false) {
+ PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info);
+ return 0;
+ }
+
+ atom_sha = enif_make_atom(env,"sha");
+ atom_error = enif_make_atom(env,"error");
+ atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding");
+ atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
+ atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding");
+ atom_signature_md = enif_make_atom(env,"signature_md");
+ atom_undefined = enif_make_atom(env,"undefined");
+ atom_ok = enif_make_atom(env,"ok");
+ atom_not_prime = enif_make_atom(env,"not_prime");
+ atom_not_strong_prime = enif_make_atom(env,"not_strong_prime");
+ atom_unable_to_check_generator = enif_make_atom(env,"unable_to_check_generator");
+ atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator");
+ atom_check_failed = enif_make_atom(env,"check_failed");
+ atom_unknown = enif_make_atom(env,"unknown");
+ atom_none = enif_make_atom(env,"none");
+ atom_notsup = enif_make_atom(env,"notsup");
+ atom_digest = enif_make_atom(env,"digest");
+
+#if defined(HAVE_EC)
+ atom_ec = enif_make_atom(env,"ec");
+ atom_prime_field = enif_make_atom(env,"prime_field");
+ atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field");
+ atom_tpbasis = enif_make_atom(env,"tpbasis");
+ atom_ppbasis = enif_make_atom(env,"ppbasis");
+ atom_onbasis = enif_make_atom(env,"onbasis");
+#endif
+
+ atom_aes_cfb8 = enif_make_atom(env, "aes_cfb8");
+ atom_aes_cfb128 = enif_make_atom(env, "aes_cfb128");
+#ifdef HAVE_GCM
+ atom_aes_gcm = enif_make_atom(env, "aes_gcm");
+#endif
+#ifdef HAVE_CCM
+ atom_aes_ccm = enif_make_atom(env, "aes_ccm");
+#endif
+#ifdef HAVE_CHACHA20_POLY1305
+ atom_chacha20_poly1305 = enif_make_atom(env,"chacha20_poly1305");
+#endif
+#ifdef HAVE_ECB_IVEC_BUG
+ atom_aes_ecb = enif_make_atom(env, "aes_ecb");
+ atom_des_ecb = enif_make_atom(env, "des_ecb");
+ atom_blowfish_ecb = enif_make_atom(env, "blowfish_ecb");
+#endif
+
+#ifdef FIPS_SUPPORT
+ atom_enabled = enif_make_atom(env,"enabled");
+ atom_not_enabled = enif_make_atom(env,"not_enabled");
+#else
+ atom_not_supported = enif_make_atom(env,"not_supported");
+#endif
+ atom_rsa = enif_make_atom(env,"rsa");
+ atom_dss = enif_make_atom(env,"dss");
+ atom_ecdsa = enif_make_atom(env,"ecdsa");
+#ifdef HAVE_ED_CURVE_DH
+ atom_x25519 = enif_make_atom(env,"x25519");
+ atom_x448 = enif_make_atom(env,"x448");
+#endif
+ atom_eddsa = enif_make_atom(env,"eddsa");
+#ifdef HAVE_EDDSA
+ atom_ed25519 = enif_make_atom(env,"ed25519");
+ atom_ed448 = enif_make_atom(env,"ed448");
+#endif
+ atom_rsa_mgf1_md = enif_make_atom(env,"rsa_mgf1_md");
+ atom_rsa_oaep_label = enif_make_atom(env,"rsa_oaep_label");
+ atom_rsa_oaep_md = enif_make_atom(env,"rsa_oaep_md");
+ atom_rsa_pad = enif_make_atom(env,"rsa_pad"); /* backwards compatibility */
+ atom_rsa_padding = enif_make_atom(env,"rsa_padding");
+ atom_rsa_pkcs1_pss_padding = enif_make_atom(env,"rsa_pkcs1_pss_padding");
+#ifdef HAVE_RSA_SSLV23_PADDING
+ atom_rsa_sslv23_padding = enif_make_atom(env,"rsa_sslv23_padding");
+#endif
+ atom_rsa_x931_padding = enif_make_atom(env,"rsa_x931_padding");
+ atom_rsa_pss_saltlen = enif_make_atom(env,"rsa_pss_saltlen");
+ atom_sha224 = enif_make_atom(env,"sha224");
+ atom_sha256 = enif_make_atom(env,"sha256");
+ atom_sha384 = enif_make_atom(env,"sha384");
+ atom_sha512 = enif_make_atom(env,"sha512");
+ atom_sha3_224 = enif_make_atom(env,"sha3_224");
+ atom_sha3_256 = enif_make_atom(env,"sha3_256");
+ atom_sha3_384 = enif_make_atom(env,"sha3_384");
+ atom_sha3_512 = enif_make_atom(env,"sha3_512");
+ atom_md5 = enif_make_atom(env,"md5");
+ atom_ripemd160 = enif_make_atom(env,"ripemd160");
+
+#ifdef HAS_ENGINE_SUPPORT
+ atom_bad_engine_method = enif_make_atom(env,"bad_engine_method");
+ atom_bad_engine_id = enif_make_atom(env,"bad_engine_id");
+ atom_ctrl_cmd_failed = enif_make_atom(env,"ctrl_cmd_failed");
+ atom_engine_init_failed = enif_make_atom(env,"engine_init_failed");
+ atom_engine_method_not_supported = enif_make_atom(env,"engine_method_not_supported");
+ atom_add_engine_failed = enif_make_atom(env,"add_engine_failed");
+ atom_remove_engine_failed = enif_make_atom(env,"remove_engine_failed");
+
+ atom_engine_method_rsa = enif_make_atom(env,"engine_method_rsa");
+ atom_engine_method_dsa = enif_make_atom(env,"engine_method_dsa");
+ atom_engine_method_dh = enif_make_atom(env,"engine_method_dh");
+ atom_engine_method_rand = enif_make_atom(env,"engine_method_rand");
+ atom_engine_method_ecdh = enif_make_atom(env,"engine_method_ecdh");
+ atom_engine_method_ecdsa = enif_make_atom(env,"engine_method_ecdsa");
+ atom_engine_method_store = enif_make_atom(env,"engine_method_store");
+ atom_engine_method_ciphers = enif_make_atom(env,"engine_method_ciphers");
+ atom_engine_method_digests = enif_make_atom(env,"engine_method_digests");
+ atom_engine_method_pkey_meths = enif_make_atom(env,"engine_method_pkey_meths");
+ atom_engine_method_pkey_asn1_meths = enif_make_atom(env,"engine_method_pkey_asn1_meths");
+ atom_engine_method_ec = enif_make_atom(env,"engine_method_ec");
+
+ atom_engine = enif_make_atom(env,"engine");
+ atom_key_id = enif_make_atom(env,"key_id");
+ atom_password = enif_make_atom(env,"password");
+#endif
+
+ return 1;
+}
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
new file mode 100644
index 0000000000..9ddf0131ac
--- /dev/null
+++ b/lib/crypto/c_src/atoms.h
@@ -0,0 +1,147 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_ATOMS_H__
+#define E_ATOMS_H__ 1
+
+#include <erl_nif.h>
+#include "openssl_config.h"
+
+extern ERL_NIF_TERM atom_true;
+extern ERL_NIF_TERM atom_false;
+extern ERL_NIF_TERM atom_sha;
+extern ERL_NIF_TERM atom_error;
+extern ERL_NIF_TERM atom_rsa_pkcs1_padding;
+extern ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
+extern ERL_NIF_TERM atom_rsa_no_padding;
+extern ERL_NIF_TERM atom_signature_md;
+extern ERL_NIF_TERM atom_undefined;
+
+extern ERL_NIF_TERM atom_ok;
+extern ERL_NIF_TERM atom_not_prime;
+extern ERL_NIF_TERM atom_not_strong_prime;
+extern ERL_NIF_TERM atom_unable_to_check_generator;
+extern ERL_NIF_TERM atom_not_suitable_generator;
+extern ERL_NIF_TERM atom_check_failed;
+extern ERL_NIF_TERM atom_unknown;
+extern ERL_NIF_TERM atom_none;
+extern ERL_NIF_TERM atom_notsup;
+extern ERL_NIF_TERM atom_digest;
+#ifdef FIPS_SUPPORT
+extern ERL_NIF_TERM atom_enabled;
+extern ERL_NIF_TERM atom_not_enabled;
+#else
+extern ERL_NIF_TERM atom_not_supported;
+#endif
+
+#if defined(HAVE_EC)
+extern ERL_NIF_TERM atom_ec;
+extern ERL_NIF_TERM atom_prime_field;
+extern ERL_NIF_TERM atom_characteristic_two_field;
+extern ERL_NIF_TERM atom_tpbasis;
+extern ERL_NIF_TERM atom_ppbasis;
+extern ERL_NIF_TERM atom_onbasis;
+#endif
+
+extern ERL_NIF_TERM atom_aes_cfb8;
+extern ERL_NIF_TERM atom_aes_cfb128;
+#ifdef HAVE_GCM
+extern ERL_NIF_TERM atom_aes_gcm;
+#endif
+#ifdef HAVE_CCM
+extern ERL_NIF_TERM atom_aes_ccm;
+#endif
+#ifdef HAVE_CHACHA20_POLY1305
+extern ERL_NIF_TERM atom_chacha20_poly1305;
+#endif
+#ifdef HAVE_ECB_IVEC_BUG
+extern ERL_NIF_TERM atom_aes_ecb;
+extern ERL_NIF_TERM atom_des_ecb;
+extern ERL_NIF_TERM atom_blowfish_ecb;
+#endif
+
+extern ERL_NIF_TERM atom_rsa;
+extern ERL_NIF_TERM atom_dss;
+extern ERL_NIF_TERM atom_ecdsa;
+
+#ifdef HAVE_ED_CURVE_DH
+extern ERL_NIF_TERM atom_x25519;
+extern ERL_NIF_TERM atom_x448;
+#endif
+
+extern ERL_NIF_TERM atom_eddsa;
+#ifdef HAVE_EDDSA
+extern ERL_NIF_TERM atom_ed25519;
+extern ERL_NIF_TERM atom_ed448;
+#endif
+
+extern ERL_NIF_TERM atom_rsa_mgf1_md;
+extern ERL_NIF_TERM atom_rsa_oaep_label;
+extern ERL_NIF_TERM atom_rsa_oaep_md;
+extern ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */
+extern ERL_NIF_TERM atom_rsa_padding;
+extern ERL_NIF_TERM atom_rsa_pkcs1_pss_padding;
+#ifdef HAVE_RSA_SSLV23_PADDING
+extern ERL_NIF_TERM atom_rsa_sslv23_padding;
+#endif
+extern ERL_NIF_TERM atom_rsa_x931_padding;
+extern ERL_NIF_TERM atom_rsa_pss_saltlen;
+extern ERL_NIF_TERM atom_sha224;
+extern ERL_NIF_TERM atom_sha256;
+extern ERL_NIF_TERM atom_sha384;
+extern ERL_NIF_TERM atom_sha512;
+extern ERL_NIF_TERM atom_sha3_224;
+extern ERL_NIF_TERM atom_sha3_256;
+extern ERL_NIF_TERM atom_sha3_384;
+extern ERL_NIF_TERM atom_sha3_512;
+extern ERL_NIF_TERM atom_md5;
+extern ERL_NIF_TERM atom_ripemd160;
+
+#ifdef HAS_ENGINE_SUPPORT
+extern ERL_NIF_TERM atom_bad_engine_method;
+extern ERL_NIF_TERM atom_bad_engine_id;
+extern ERL_NIF_TERM atom_ctrl_cmd_failed;
+extern ERL_NIF_TERM atom_engine_init_failed;
+extern ERL_NIF_TERM atom_register_engine_failed;
+extern ERL_NIF_TERM atom_add_engine_failed;
+extern ERL_NIF_TERM atom_remove_engine_failed;
+extern ERL_NIF_TERM atom_engine_method_not_supported;
+
+extern ERL_NIF_TERM atom_engine_method_rsa;
+extern ERL_NIF_TERM atom_engine_method_dsa;
+extern ERL_NIF_TERM atom_engine_method_dh;
+extern ERL_NIF_TERM atom_engine_method_rand;
+extern ERL_NIF_TERM atom_engine_method_ecdh;
+extern ERL_NIF_TERM atom_engine_method_ecdsa;
+extern ERL_NIF_TERM atom_engine_method_ciphers;
+extern ERL_NIF_TERM atom_engine_method_digests;
+extern ERL_NIF_TERM atom_engine_method_store;
+extern ERL_NIF_TERM atom_engine_method_pkey_meths;
+extern ERL_NIF_TERM atom_engine_method_pkey_asn1_meths;
+extern ERL_NIF_TERM atom_engine_method_ec;
+
+extern ERL_NIF_TERM atom_engine;
+extern ERL_NIF_TERM atom_key_id;
+extern ERL_NIF_TERM atom_password;
+#endif
+
+int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM load_info);
+
+#endif /* E_ATOMS_H__ */
diff --git a/lib/crypto/c_src/block.c b/lib/crypto/c_src/block.c
new file mode 100644
index 0000000000..2ba3290e9f
--- /dev/null
+++ b/lib/crypto/c_src/block.c
@@ -0,0 +1,105 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "block.h"
+#include "aes.h"
+#include "cipher.h"
+
+ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */
+ struct cipher_type_t *cipherp = NULL;
+ const EVP_CIPHER *cipher;
+ ErlNifBinary key, ivec, text;
+ EVP_CIPHER_CTX* ctx;
+ ERL_NIF_TERM ret;
+ unsigned char *out;
+ int ivec_size, out_size = 0;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
+ || !(cipherp = get_cipher_type(argv[0], key.size))
+ || !enif_inspect_iolist_as_binary(env, argv[argc - 2], &text)) {
+ return enif_make_badarg(env);
+ }
+ cipher = cipherp->cipher.p;
+ if (!cipher) {
+ return enif_raise_exception(env, atom_notsup);
+ }
+
+ if (argv[0] == atom_aes_cfb8
+ && (key.size == 24 || key.size == 32)) {
+ /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
+ * Fall back on low level API
+ */
+ return aes_cfb_8_crypt(env, argc-1, argv+1);
+ }
+ else if (argv[0] == atom_aes_cfb128
+ && (key.size == 24 || key.size == 32)) {
+ /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
+ * Fall back on low level API
+ */
+ return aes_cfb_128_crypt_nif(env, argc-1, argv+1);
+ }
+
+ ivec_size = EVP_CIPHER_iv_length(cipher);
+
+#ifdef HAVE_ECB_IVEC_BUG
+ if (argv[0] == atom_aes_ecb || argv[0] == atom_blowfish_ecb ||
+ argv[0] == atom_des_ecb)
+ ivec_size = 0; /* 0.9.8l returns faulty ivec_size */
+#endif
+
+ if (text.size % EVP_CIPHER_block_size(cipher) != 0 ||
+ (ivec_size == 0 ? argc != 4
+ : (argc != 5 ||
+ !enif_inspect_iolist_as_binary(env, argv[2], &ivec) ||
+ ivec.size != ivec_size))) {
+ return enif_make_badarg(env);
+ }
+
+ out = enif_make_new_binary(env, text.size, &ret);
+
+ ctx = EVP_CIPHER_CTX_new();
+ if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL,
+ (argv[argc - 1] == atom_true)) ||
+ !EVP_CIPHER_CTX_set_key_length(ctx, key.size) ||
+ !(EVP_CIPHER_type(cipher) != NID_rc2_cbc ||
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, key.size * 8, NULL)) ||
+ !EVP_CipherInit_ex(ctx, NULL, NULL,
+ key.data, ivec_size ? ivec.data : NULL, -1) ||
+ !EVP_CIPHER_CTX_set_padding(ctx, 0)) {
+
+ EVP_CIPHER_CTX_free(ctx);
+ return enif_raise_exception(env, atom_notsup);
+ }
+
+ if (text.size > 0 && /* OpenSSL 0.9.8h asserts text.size > 0 */
+ (!EVP_CipherUpdate(ctx, out, &out_size, text.data, text.size)
+ || (ASSERT(out_size == text.size), 0)
+ || !EVP_CipherFinal_ex(ctx, out + out_size, &out_size))) {
+
+ EVP_CIPHER_CTX_free(ctx);
+ return enif_raise_exception(env, atom_notsup);
+ }
+ ASSERT(out_size == 0);
+ EVP_CIPHER_CTX_free(ctx);
+ CONSUME_REDS(env, text);
+
+ return ret;
+}
diff --git a/lib/crypto/c_src/block.h b/lib/crypto/c_src/block.h
new file mode 100644
index 0000000000..cc5e78ce12
--- /dev/null
+++ b/lib/crypto/c_src/block.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_BLOCK_H__
+#define E_BLOCK_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_BLOCK_H__ */
diff --git a/lib/crypto/c_src/bn.c b/lib/crypto/c_src/bn.c
new file mode 100644
index 0000000000..b576c46e1e
--- /dev/null
+++ b/lib/crypto/c_src/bn.c
@@ -0,0 +1,121 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "bn.h"
+
+
+int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
+{
+ ErlNifBinary bin;
+ int sz;
+ if (!enif_inspect_binary(env,term,&bin)) {
+ return 0;
+ }
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
+ sz = bin.size - 4;
+ if (sz < 0 || get_int32(bin.data) != sz) {
+ return 0;
+ }
+ *bnp = BN_bin2bn(bin.data+4, sz, NULL);
+ return 1;
+}
+
+int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
+{
+ ErlNifBinary bin;
+ if (!enif_inspect_binary(env,term,&bin)) {
+ return 0;
+ }
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
+ *bnp = BN_bin2bn(bin.data, bin.size, NULL);
+ return 1;
+}
+
+ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn)
+{
+ int bn_len;
+ unsigned char *bin_ptr;
+ ERL_NIF_TERM term;
+
+ /* Copy the bignum into an erlang binary. */
+ bn_len = BN_num_bytes(bn);
+ bin_ptr = enif_make_new_binary(env, bn_len, &term);
+ BN_bn2bin(bn, bin_ptr);
+
+ return term;
+}
+
+ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Base,Exponent,Modulo,bin_hdr) */
+ BIGNUM *bn_base=NULL, *bn_exponent=NULL, *bn_modulo=NULL, *bn_result;
+ BN_CTX *bn_ctx;
+ unsigned char* ptr;
+ unsigned dlen;
+ unsigned bin_hdr; /* return type: 0=plain binary, 4: mpint */
+ unsigned extra_byte;
+ ERL_NIF_TERM ret;
+
+ if (!get_bn_from_bin(env, argv[0], &bn_base)
+ || !get_bn_from_bin(env, argv[1], &bn_exponent)
+ || !get_bn_from_bin(env, argv[2], &bn_modulo)
+ || !enif_get_uint(env,argv[3],&bin_hdr) || (bin_hdr & ~4)) {
+
+ if (bn_base) BN_free(bn_base);
+ if (bn_exponent) BN_free(bn_exponent);
+ if (bn_modulo) BN_free(bn_modulo);
+ return enif_make_badarg(env);
+ }
+ bn_result = BN_new();
+ bn_ctx = BN_CTX_new();
+ BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx);
+ dlen = BN_num_bytes(bn_result);
+ extra_byte = bin_hdr && BN_is_bit_set(bn_result, dlen*8-1);
+ ptr = enif_make_new_binary(env, bin_hdr+extra_byte+dlen, &ret);
+ if (bin_hdr) {
+ put_int32(ptr, extra_byte+dlen);
+ ptr[4] = 0; /* extra zeroed byte to ensure a positive mpint */
+ ptr += bin_hdr + extra_byte;
+ }
+ BN_bn2bin(bn_result, ptr);
+ BN_free(bn_result);
+ BN_CTX_free(bn_ctx);
+ BN_free(bn_modulo);
+ BN_free(bn_exponent);
+ BN_free(bn_base);
+ return ret;
+}
+
+#ifdef HAVE_EC
+ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn)
+{
+ unsigned dlen;
+ unsigned char* ptr;
+ ERL_NIF_TERM ret;
+
+ if (!bn)
+ return atom_undefined;
+
+ dlen = BN_num_bytes(bn);
+ ptr = enif_make_new_binary(env, dlen, &ret);
+ BN_bn2bin(bn, ptr);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen);
+ return ret;
+}
+#endif
diff --git a/lib/crypto/c_src/bn.h b/lib/crypto/c_src/bn.h
new file mode 100644
index 0000000000..332b06e79d
--- /dev/null
+++ b/lib/crypto/c_src/bn.h
@@ -0,0 +1,36 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_BN_H__
+#define E_BN_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn);
+ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#ifdef HAVE_EC
+ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn);
+#endif
+
+int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp);
+int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp);
+
+#endif /* E_BN_H__ */
diff --git a/lib/crypto/c_src/chacha20.c b/lib/crypto/c_src/chacha20.c
new file mode 100644
index 0000000000..8b21a0c7af
--- /dev/null
+++ b/lib/crypto/c_src/chacha20.c
@@ -0,0 +1,83 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "chacha20.h"
+#include "cipher.h"
+
+ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IV) */
+#if defined(HAVE_CHACHA20)
+ ErlNifBinary key_bin, ivec_bin;
+ struct evp_cipher_ctx *ctx;
+ const EVP_CIPHER *cipher;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || key_bin.size != 32
+ || ivec_bin.size != 16) {
+ return enif_make_badarg(env);
+ }
+
+ cipher = EVP_chacha20();
+
+ ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
+ ctx->ctx = EVP_CIPHER_CTX_new();
+
+
+ EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
+ key_bin.data, ivec_bin.data, 1);
+ EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+};
+
+ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (State, Data) */
+#if defined(HAVE_CHACHA20)
+ struct evp_cipher_ctx *ctx, *new_ctx;
+ ErlNifBinary data_bin;
+ ERL_NIF_TERM ret, cipher_term;
+ unsigned char *out;
+ int outl = 0;
+
+ if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+ new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
+ new_ctx->ctx = EVP_CIPHER_CTX_new();
+ EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx);
+ out = enif_make_new_binary(env, data_bin.size, &cipher_term);
+ EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size);
+ ASSERT(outl == data_bin.size);
+
+ ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
+ enif_release_resource(new_ctx);
+ CONSUME_REDS(env,data_bin);
+ return ret;
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+};
diff --git a/lib/crypto/c_src/chacha20.h b/lib/crypto/c_src/chacha20.h
new file mode 100644
index 0000000000..7e2ccae2bb
--- /dev/null
+++ b/lib/crypto/c_src/chacha20.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_CHACHA20_H__
+#define E_CHACHA20_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_CHACHA20_H__ */
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
new file mode 100644
index 0000000000..6580cb183f
--- /dev/null
+++ b/lib/crypto/c_src/cipher.c
@@ -0,0 +1,117 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "cipher.h"
+
+#ifdef OPENSSL_NO_DES
+#define COND_NO_DES_PTR(Ptr) (NULL)
+#else
+#define COND_NO_DES_PTR(Ptr) (Ptr)
+#endif
+
+static struct cipher_type_t cipher_types[] =
+{
+ {{"rc2_cbc"},
+#ifndef OPENSSL_NO_RC2
+ {&EVP_rc2_cbc}
+#else
+ {NULL}
+#endif
+ },
+ {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}},
+ {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}},
+ {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}},
+ {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}},
+ {{"des_ede3_cbf"}, /* Misspelled, retained */
+#ifdef HAVE_DES_ede3_cfb_encrypt
+ {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}
+#else
+ {NULL}
+#endif
+ },
+ {{"des_ede3_cfb"},
+#ifdef HAVE_DES_ede3_cfb_encrypt
+ {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}
+#else
+ {NULL}
+#endif
+ },
+ {{"blowfish_cbc"}, {&EVP_bf_cbc}},
+ {{"blowfish_cfb64"}, {&EVP_bf_cfb64}},
+ {{"blowfish_ofb64"}, {&EVP_bf_ofb}},
+ {{"blowfish_ecb"}, {&EVP_bf_ecb}},
+ {{"aes_cbc"}, {&EVP_aes_128_cbc}, 16},
+ {{"aes_cbc"}, {&EVP_aes_192_cbc}, 24},
+ {{"aes_cbc"}, {&EVP_aes_256_cbc}, 32},
+ {{"aes_cbc128"}, {&EVP_aes_128_cbc}},
+ {{"aes_cbc256"}, {&EVP_aes_256_cbc}},
+ {{"aes_cfb8"}, {&EVP_aes_128_cfb8}},
+ {{"aes_cfb128"}, {&EVP_aes_128_cfb128}},
+ {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16},
+ {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24},
+ {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32},
+ {{NULL}}
+};
+
+#ifdef HAVE_EVP_AES_CTR
+ErlNifResourceType* evp_cipher_ctx_rtype;
+
+static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) {
+ EVP_CIPHER_CTX_free(ctx->ctx);
+}
+#endif
+
+int init_cipher_ctx(ErlNifEnv *env) {
+#ifdef HAVE_EVP_AES_CTR
+ evp_cipher_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_CIPHER_CTX",
+ (ErlNifResourceDtor*) evp_cipher_ctx_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (evp_cipher_ctx_rtype == NULL) {
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_CIPHER_CTX'");
+ return 0;
+ }
+#endif
+
+ return 1;
+}
+
+void init_cipher_types(ErlNifEnv* env)
+{
+ struct cipher_type_t* p = cipher_types;
+
+ for (p = cipher_types; p->type.str; p++) {
+ p->type.atom = enif_make_atom(env, p->type.str);
+ if (p->cipher.funcp)
+ p->cipher.p = p->cipher.funcp();
+ }
+ p->type.atom = atom_false; /* end marker */
+}
+
+struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len)
+{
+ struct cipher_type_t* p = NULL;
+ for (p = cipher_types; p->type.atom != atom_false; p++) {
+ if (type == p->type.atom && (!p->key_len || key_len == p->key_len)) {
+ return p;
+ }
+ }
+ return NULL;
+}
diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h
new file mode 100644
index 0000000000..3fb27f0ba3
--- /dev/null
+++ b/lib/crypto/c_src/cipher.h
@@ -0,0 +1,50 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_CIPHER_H__
+#define E_CIPHER_H__ 1
+
+#include "common.h"
+
+struct cipher_type_t {
+ union {
+ const char* str; /* before init */
+ ERL_NIF_TERM atom; /* after init */
+ }type;
+ union {
+ const EVP_CIPHER* (*funcp)(void); /* before init, NULL if notsup */
+ const EVP_CIPHER* p; /* after init, NULL if notsup */
+ }cipher;
+ const size_t key_len; /* != 0 to also match on key_len */
+};
+
+#ifdef HAVE_EVP_AES_CTR
+extern ErlNifResourceType* evp_cipher_ctx_rtype;
+struct evp_cipher_ctx {
+ EVP_CIPHER_CTX* ctx;
+};
+#endif
+
+int init_cipher_ctx(ErlNifEnv *env);
+
+void init_cipher_types(ErlNifEnv* env);
+struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len);
+
+#endif /* E_CIPHER_H__ */
diff --git a/lib/crypto/c_src/cmac.c b/lib/crypto/c_src/cmac.c
new file mode 100644
index 0000000000..526de11a01
--- /dev/null
+++ b/lib/crypto/c_src/cmac.c
@@ -0,0 +1,70 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "cmac.h"
+#include "cipher.h"
+
+ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key, Data) */
+#if defined(HAVE_CMAC)
+ struct cipher_type_t *cipherp = NULL;
+ const EVP_CIPHER *cipher;
+ CMAC_CTX *ctx;
+ ErlNifBinary key;
+ ErlNifBinary data;
+ ERL_NIF_TERM ret;
+ size_t ret_size;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
+ || !(cipherp = get_cipher_type(argv[0], key.size))
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data)) {
+ return enif_make_badarg(env);
+ }
+ cipher = cipherp->cipher.p;
+ if (!cipher) {
+ return enif_raise_exception(env, atom_notsup);
+ }
+
+ ctx = CMAC_CTX_new();
+ if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL)) {
+ CMAC_CTX_free(ctx);
+ return atom_notsup;
+ }
+
+ if (!CMAC_Update(ctx, data.data, data.size) ||
+ !CMAC_Final(ctx,
+ enif_make_new_binary(env, EVP_CIPHER_block_size(cipher), &ret),
+ &ret_size)) {
+ CMAC_CTX_free(ctx);
+ return atom_notsup;
+ }
+ ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher));
+
+ CMAC_CTX_free(ctx);
+ CONSUME_REDS(env, data);
+ return ret;
+#else
+ /* The CMAC functionality was introduced in OpenSSL 1.0.1
+ * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are
+ * no longer maintained. */
+ return atom_notsup;
+#endif
+}
+
diff --git a/lib/crypto/c_src/cmac.h b/lib/crypto/c_src/cmac.h
new file mode 100644
index 0000000000..14488def58
--- /dev/null
+++ b/lib/crypto/c_src/cmac.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_CMAC_H__
+#define E_CMAC_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_CMAC_H__ */
diff --git a/lib/crypto/c_src/common.h b/lib/crypto/c_src/common.h
new file mode 100644
index 0000000000..1259ba1f36
--- /dev/null
+++ b/lib/crypto/c_src/common.h
@@ -0,0 +1,36 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_COMMON_H__
+#define E_COMMON_H__ 1
+
+#ifdef __WIN32__
+# include <windows.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <erl_nif.h>
+#include "openssl_config.h"
+#include "atoms.h"
+
+#endif /* E_COMMON_H__ */
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index ecb716c98c..fde3d99fa8 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -23,580 +23,41 @@
* Based on OpenSSL.
*/
-#ifdef __WIN32__
- #include <windows.h>
-#endif
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <erl_nif.h>
-
-#define OPENSSL_THREAD_DEFINES
-#include <openssl/opensslconf.h>
-
-#include <openssl/crypto.h>
-#ifndef OPENSSL_NO_DES
-#include <openssl/des.h>
-#endif /* #ifndef OPENSSL_NO_DES */
-/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
-#include <openssl/dsa.h>
-#include <openssl/rsa.h>
-#include <openssl/aes.h>
-#include <openssl/md5.h>
-#include <openssl/md4.h>
-#include <openssl/sha.h>
-#include <openssl/ripemd.h>
-#include <openssl/bn.h>
-#include <openssl/objects.h>
-#ifndef OPENSSL_NO_RC4
- #include <openssl/rc4.h>
-#endif /* OPENSSL_NO_RC4 */
-#ifndef OPENSSL_NO_RC2
- #include <openssl/rc2.h>
-#endif
-#include <openssl/blowfish.h>
-#include <openssl/rand.h>
-#include <openssl/evp.h>
-#include <openssl/hmac.h>
-#include <openssl/err.h>
-
-/* Helper macro to construct a OPENSSL_VERSION_NUMBER.
- * See openssl/opensslv.h
- */
-#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \
- ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf)
-
-#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \
- PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1))
-
-
-/* LibreSSL was cloned from OpenSSL 1.0.1g and claims to be API and BPI compatible
- * with 1.0.1.
- *
- * LibreSSL has the same names on include files and symbols as OpenSSL, but defines
- * the OPENSSL_VERSION_NUMBER to be >= 2.0.0
- *
- * Therefor works tests like this as intendend:
- * OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
- * (The test is for example "2.4.2" >= "1.0.0" although the test
- * with the cloned OpenSSL test would be "1.0.1" >= "1.0.0")
- *
- * But tests like this gives wrong result:
- * OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
- * (The test is false since "2.4.2" < "1.1.0". It should have been
- * true because the LibreSSL API version is "1.0.1")
- *
- */
-
-#ifdef LIBRESSL_VERSION_NUMBER
-/* A macro to test on in this file */
-#define HAS_LIBRESSL
-#endif
-
-#ifdef HAS_LIBRESSL
-/* LibreSSL dislikes FIPS */
-# ifdef FIPS_SUPPORT
-# undef FIPS_SUPPORT
-# endif
-
-# if LIBRESSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(2,7,0)
-/* LibreSSL wants the 1.0.1 API */
-# define NEED_EVP_COMPATIBILITY_FUNCTIONS
-# endif
-#endif
-
-
-#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
-# define NEED_EVP_COMPATIBILITY_FUNCTIONS
-#endif
-
-
-#ifndef HAS_LIBRESSL
-# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-# define HAS_EVP_PKEY_CTX
-# endif
-#endif
-
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-#include <openssl/modes.h>
-#endif
-
-#include "crypto_callback.h"
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA224) && defined(NID_sha224) \
- && !defined(OPENSSL_NO_SHA256) /* disabled like this in my sha.h (?) */
-# define HAVE_SHA224
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA256) && defined(NID_sha256)
-# define HAVE_SHA256
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA384) && defined(NID_sha384)\
- && !defined(OPENSSL_NO_SHA512) /* disabled like this in my sha.h (?) */
-# define HAVE_SHA384
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
- && !defined(OPENSSL_NO_SHA512) && defined(NID_sha512)
-# define HAVE_SHA512
-#endif
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,7,'e')
-# define HAVE_DES_ede3_cfb_encrypt
-#endif
-
-// SHA3:
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1)
-// An error in beta releases of 1.1.1 fixed in production release
-# ifdef NID_sha3_224
-# define HAVE_SHA3_224
-# endif
-# ifdef NID_sha3_256
-# define HAVE_SHA3_256
-# endif
-#endif
-# ifdef NID_sha3_384
-# define HAVE_SHA3_384
-# endif
-# ifdef NID_sha3_512
-# define HAVE_SHA3_512
-# endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \
- && !defined(OPENSSL_NO_EC) \
- && !defined(OPENSSL_NO_ECDH) \
- && !defined(OPENSSL_NO_ECDSA)
-# define HAVE_EC
-#endif
-
-// (test for >= 1.1.1pre8)
-#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \
- && !defined(HAS_LIBRESSL) \
- && defined(HAVE_EC)
-# define HAVE_ED_CURVE_DH
-# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1))
-# define HAVE_EDDSA
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'c')
-# define HAVE_AES_IGE
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1)
-# define HAVE_EVP_AES_CTR
-# define HAVE_AEAD
-# define HAVE_GCM
-# define HAVE_CCM
-# define HAVE_CMAC
-# if defined(RSA_PKCS1_OAEP_PADDING)
-# define HAVE_RSA_OAEP_PADDING
-# endif
-# define HAVE_RSA_MGF1_MD
-# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d')
-# define HAVE_GCM_EVP_DECRYPT_BUG
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
-# ifndef HAS_LIBRESSL
-# define HAVE_CHACHA20
-# define HAVE_CHACHA20_POLY1305
-# define HAVE_RSA_OAEP_MD
-# endif
-#endif
-
-// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8
-#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7)
-# ifndef HAS_LIBRESSL
-# define HAVE_POLY1305
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l')
-# define HAVE_ECB_IVEC_BUG
-#endif
-
-#ifndef HAS_LIBRESSL
-# ifdef RSA_SSLV23_PADDING
-# define HAVE_RSA_SSLV23_PADDING
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-# ifdef RSA_PKCS1_PSS_PADDING
-# define HAVE_RSA_PKCS1_PSS_PADDING
-# endif
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'h') \
- && defined(HAVE_EC)
-/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h
- So if EC is disabled, you can't use Engine either....
-*/
-# define HAS_ENGINE_SUPPORT
-#endif
-
-
-#if defined(HAS_ENGINE_SUPPORT)
-# include <openssl/engine.h>
-#endif
-
-#if defined(HAVE_CMAC)
-#include <openssl/cmac.h>
-#endif
-
-#if defined(HAVE_EC)
-#include <openssl/ec.h>
-#include <openssl/ecdh.h>
-#include <openssl/ecdsa.h>
-#endif
-
-#ifdef VALGRIND
- # include <valgrind/memcheck.h>
-
-/* libcrypto mixes supplied buffer contents into its entropy pool,
- which makes valgrind complain about the use of uninitialized data.
- We use this valgrind "request" to make sure that no such seemingly
- undefined data is returned.
-*/
- # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \
- VALGRIND_MAKE_MEM_DEFINED(ptr,size)
-
- # define ERL_VALGRIND_ASSERT_MEM_DEFINED(Ptr,Size) \
- do { \
- int __erl_valgrind_mem_defined = VALGRIND_CHECK_MEM_IS_DEFINED((Ptr),(Size)); \
- if (__erl_valgrind_mem_defined != 0) { \
- fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%ld) failed at %s:%d\r\n", \
- (Ptr),(long)(Size), __FILE__, __LINE__); \
- abort(); \
- } \
- } while (0)
-
-#else
- # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size)
- # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size)
-#endif
-
-#ifdef DEBUG
- # define ASSERT(e) \
- ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
- #e, __FILE__, __LINE__), abort(), 0)))
-#else
- # define ASSERT(e) ((void) 1)
-#endif
-
-#ifdef __GNUC__
- # define INLINE __inline__
-#elif defined(__WIN32__)
- # define INLINE __forceinline
-#else
- # define INLINE
-#endif
-
-
-#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \
- (((unsigned char*) (s))[1] << 16) | \
- (((unsigned char*) (s))[2] << 8) | \
- (((unsigned char*) (s))[3]))
-
-#define put_int32(s,i) \
-{ (s)[0] = (char)(((i) >> 24) & 0xff);\
- (s)[1] = (char)(((i) >> 16) & 0xff);\
- (s)[2] = (char)(((i) >> 8) & 0xff);\
- (s)[3] = (char)((i) & 0xff);\
-}
-
-/* This shall correspond to the similar macro in crypto.erl */
-/* Current value is: erlang:system_info(context_reductions) * 10 */
-#define MAX_BYTES_TO_NIF 20000
-
-#define CONSUME_REDS(NifEnv, Ibin) \
-do { \
- int _cost = ((Ibin).size * 100) / MAX_BYTES_TO_NIF;\
- if (_cost) { \
- (void) enif_consume_timeslice((NifEnv), \
- (_cost > 100) ? 100 : _cost); \
- } \
- } while (0)
-
-
-#ifdef NEED_EVP_COMPATIBILITY_FUNCTIONS
-/*
- * In OpenSSL 1.1.0, most structs are opaque. That means that
- * the structs cannot be allocated as automatic variables on the
- * C stack (because the size is unknown) and that it is necessary
- * to use access functions.
- *
- * For backward compatibility to previous versions of OpenSSL, define
- * on our versions of the new functions defined in 1.1.0 here, so that
- * we don't have to sprinkle ifdefs throughout the code.
- */
-
-static HMAC_CTX *HMAC_CTX_new(void);
-static void HMAC_CTX_free(HMAC_CTX *ctx);
-
-static HMAC_CTX *HMAC_CTX_new()
-{
- HMAC_CTX *ctx = CRYPTO_malloc(sizeof(HMAC_CTX), __FILE__, __LINE__);
- HMAC_CTX_init(ctx);
- return ctx;
-}
-
-static void HMAC_CTX_free(HMAC_CTX *ctx)
-{
- HMAC_CTX_cleanup(ctx);
- CRYPTO_free(ctx);
-}
-
-#define EVP_MD_CTX_new() EVP_MD_CTX_create()
-#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx)
-
-static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb);
-
-static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb)
-{
- return cb->arg;
-}
-
-static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d);
-static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d);
-static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q);
-static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q);
-static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp);
-static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp);
-
-static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
-{
- r->n = n;
- r->e = e;
- r->d = d;
- return 1;
-}
-
-static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
-{
- *n = r->n;
- *e = r->e;
- *d = r->d;
-}
-
-static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q)
-{
- r->p = p;
- r->q = q;
- return 1;
-}
-
-static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
-{
- *p = r->p;
- *q = r->q;
-}
-
-static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
-{
- r->dmp1 = dmp1;
- r->dmq1 = dmq1;
- r->iqmp = iqmp;
- return 1;
-}
-
-static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp)
-{
- *dmp1 = r->dmp1;
- *dmq1 = r->dmq1;
- *iqmp = r->iqmp;
-}
-
-static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key);
-static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g);
-static INLINE void DSA_get0_pqg(const DSA *dsa,
- const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
-static INLINE void DSA_get0_key(const DSA *dsa,
- const BIGNUM **pub_key, const BIGNUM **priv_key);
-
-static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
-{
- d->pub_key = pub_key;
- d->priv_key = priv_key;
- return 1;
-}
-
-static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
-{
- d->p = p;
- d->q = q;
- d->g = g;
- return 1;
-}
-
-static INLINE void
-DSA_get0_pqg(const DSA *dsa, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
-{
- *p = dsa->p;
- *q = dsa->q;
- *g = dsa->g;
-}
-
-static INLINE void
-DSA_get0_key(const DSA *dsa, const BIGNUM **pub_key, const BIGNUM **priv_key)
-{
- if (pub_key) *pub_key = dsa->pub_key;
- if (priv_key) *priv_key = dsa->priv_key;
-}
-
-
-
-static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key);
-static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g);
-static INLINE int DH_set_length(DH *dh, long length);
-static INLINE void DH_get0_pqg(const DH *dh,
- const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
-static INLINE void DH_get0_key(const DH *dh,
- const BIGNUM **pub_key, const BIGNUM **priv_key);
-
-static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
-{
- dh->pub_key = pub_key;
- dh->priv_key = priv_key;
- return 1;
-}
-
-static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
-{
- dh->p = p;
- dh->q = q;
- dh->g = g;
- return 1;
-}
-
-static INLINE int DH_set_length(DH *dh, long length)
-{
- dh->length = length;
- return 1;
-}
-
-
-
-static INLINE void
-DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
-{
- *p = dh->p;
- *q = dh->q;
- *g = dh->g;
-}
-
-static INLINE void
-DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
-{
- if (pub_key) *pub_key = dh->pub_key;
- if (priv_key) *priv_key = dh->priv_key;
-}
-
-#else /* End of compatibility definitions. */
-
-#define HAVE_OPAQUE_BN_GENCB
-
-#endif
+#include "common.h"
+
+#include "aead.h"
+#include "aes.h"
+#include "algorithms.h"
+#include "block.h"
+#include "bn.h"
+#include "chacha20.h"
+#include "cipher.h"
+#include "cmac.h"
+#include "dh.h"
+#include "digest.h"
+#include "dss.h"
+#include "ec.h"
+#include "ecdh.h"
+#include "eddsa.h"
+#include "engine.h"
+#include "evp.h"
+#include "fips.h"
+#include "hash.h"
+#include "hmac.h"
+#include "info.h"
+#include "math.h"
+#include "pkey.h"
+#include "poly1305.h"
+#include "rand.h"
+#include "rc4.h"
+#include "rsa.h"
+#include "srp.h"
/* NIF interface declarations */
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info);
static void unload(ErlNifEnv* env, void* priv_data);
-/* The NIFs: */
-static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-#ifdef HAVE_GCM_EVP_DECRYPT_BUG
-static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-#endif
-
-static ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
-
-/* helpers */
-static void init_algorithms_types(ErlNifEnv*);
-static void init_digest_types(ErlNifEnv* env);
-static void init_cipher_types(ErlNifEnv* env);
-#ifdef HAVE_EC
-static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg);
-static int term2point(ErlNifEnv* env, ERL_NIF_TERM term,
- EC_GROUP *group, EC_POINT **pptr);
-#endif
-static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn);
-
-#ifdef HAS_ENGINE_SUPPORT
-static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i);
-static int zero_terminate(ErlNifBinary bin, char **buf);
-#endif
-
static int library_refc = 0; /* number of users of this dynamic library */
static int library_initialized = 0;
@@ -676,329 +137,6 @@ static ErlNifFunc nif_funcs[] = {
ERL_NIF_INIT(crypto,nif_funcs,load,NULL,upgrade,unload)
-#define MD5_CTX_LEN (sizeof(MD5_CTX))
-#define MD4_CTX_LEN (sizeof(MD4_CTX))
-#define RIPEMD160_CTX_LEN (sizeof(RIPEMD160_CTX))
-
-
-static ERL_NIF_TERM atom_true;
-static ERL_NIF_TERM atom_false;
-static ERL_NIF_TERM atom_sha;
-static ERL_NIF_TERM atom_error;
-static ERL_NIF_TERM atom_rsa_pkcs1_padding;
-static ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
-static ERL_NIF_TERM atom_rsa_no_padding;
-static ERL_NIF_TERM atom_signature_md;
-static ERL_NIF_TERM atom_undefined;
-
-static ERL_NIF_TERM atom_ok;
-static ERL_NIF_TERM atom_not_prime;
-static ERL_NIF_TERM atom_not_strong_prime;
-static ERL_NIF_TERM atom_unable_to_check_generator;
-static ERL_NIF_TERM atom_not_suitable_generator;
-static ERL_NIF_TERM atom_check_failed;
-static ERL_NIF_TERM atom_unknown;
-static ERL_NIF_TERM atom_none;
-static ERL_NIF_TERM atom_notsup;
-static ERL_NIF_TERM atom_digest;
-#ifdef FIPS_SUPPORT
-static ERL_NIF_TERM atom_enabled;
-static ERL_NIF_TERM atom_not_enabled;
-#else
-static ERL_NIF_TERM atom_not_supported;
-#endif
-
-#if defined(HAVE_EC)
-static ERL_NIF_TERM atom_ec;
-static ERL_NIF_TERM atom_prime_field;
-static ERL_NIF_TERM atom_characteristic_two_field;
-static ERL_NIF_TERM atom_tpbasis;
-static ERL_NIF_TERM atom_ppbasis;
-static ERL_NIF_TERM atom_onbasis;
-#endif
-
-static ERL_NIF_TERM atom_aes_cfb8;
-static ERL_NIF_TERM atom_aes_cfb128;
-#ifdef HAVE_GCM
-static ERL_NIF_TERM atom_aes_gcm;
-#endif
-#ifdef HAVE_CCM
-static ERL_NIF_TERM atom_aes_ccm;
-#endif
-#ifdef HAVE_CHACHA20_POLY1305
-static ERL_NIF_TERM atom_chacha20_poly1305;
-#endif
-#ifdef HAVE_ECB_IVEC_BUG
-static ERL_NIF_TERM atom_aes_ecb;
-static ERL_NIF_TERM atom_des_ecb;
-static ERL_NIF_TERM atom_blowfish_ecb;
-#endif
-
-static ERL_NIF_TERM atom_rsa;
-static ERL_NIF_TERM atom_dss;
-static ERL_NIF_TERM atom_ecdsa;
-
-#ifdef HAVE_ED_CURVE_DH
-static ERL_NIF_TERM atom_x25519;
-static ERL_NIF_TERM atom_x448;
-#endif
-
-static ERL_NIF_TERM atom_eddsa;
-#ifdef HAVE_EDDSA
-static ERL_NIF_TERM atom_ed25519;
-static ERL_NIF_TERM atom_ed448;
-#endif
-
-static ERL_NIF_TERM atom_rsa_mgf1_md;
-static ERL_NIF_TERM atom_rsa_oaep_label;
-static ERL_NIF_TERM atom_rsa_oaep_md;
-static ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */
-static ERL_NIF_TERM atom_rsa_padding;
-static ERL_NIF_TERM atom_rsa_pkcs1_pss_padding;
-#ifdef HAVE_RSA_SSLV23_PADDING
-static ERL_NIF_TERM atom_rsa_sslv23_padding;
-#endif
-static ERL_NIF_TERM atom_rsa_x931_padding;
-static ERL_NIF_TERM atom_rsa_pss_saltlen;
-static ERL_NIF_TERM atom_sha224;
-static ERL_NIF_TERM atom_sha256;
-static ERL_NIF_TERM atom_sha384;
-static ERL_NIF_TERM atom_sha512;
-static ERL_NIF_TERM atom_sha3_224;
-static ERL_NIF_TERM atom_sha3_256;
-static ERL_NIF_TERM atom_sha3_384;
-static ERL_NIF_TERM atom_sha3_512;
-static ERL_NIF_TERM atom_md5;
-static ERL_NIF_TERM atom_ripemd160;
-
-#ifdef HAS_ENGINE_SUPPORT
-static ERL_NIF_TERM atom_bad_engine_method;
-static ERL_NIF_TERM atom_bad_engine_id;
-static ERL_NIF_TERM atom_ctrl_cmd_failed;
-static ERL_NIF_TERM atom_engine_init_failed;
-static ERL_NIF_TERM atom_register_engine_failed;
-static ERL_NIF_TERM atom_add_engine_failed;
-static ERL_NIF_TERM atom_remove_engine_failed;
-static ERL_NIF_TERM atom_engine_method_not_supported;
-
-static ERL_NIF_TERM atom_engine_method_rsa;
-static ERL_NIF_TERM atom_engine_method_dsa;
-static ERL_NIF_TERM atom_engine_method_dh;
-static ERL_NIF_TERM atom_engine_method_rand;
-static ERL_NIF_TERM atom_engine_method_ecdh;
-static ERL_NIF_TERM atom_engine_method_ecdsa;
-static ERL_NIF_TERM atom_engine_method_ciphers;
-static ERL_NIF_TERM atom_engine_method_digests;
-static ERL_NIF_TERM atom_engine_method_store;
-static ERL_NIF_TERM atom_engine_method_pkey_meths;
-static ERL_NIF_TERM atom_engine_method_pkey_asn1_meths;
-static ERL_NIF_TERM atom_engine_method_ec;
-
-static ERL_NIF_TERM atom_engine;
-static ERL_NIF_TERM atom_key_id;
-static ERL_NIF_TERM atom_password;
-#endif
-
-static ErlNifResourceType* hmac_context_rtype;
-struct hmac_context
-{
- ErlNifMutex* mtx;
- int alive;
- HMAC_CTX* ctx;
-};
-static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*);
-
-struct digest_type_t {
- union {
- const char* str; /* before init, NULL for end-of-table */
- ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */
- }type;
- union {
- const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */
- const EVP_MD* p; /* after init, NULL if notsup */
- }md;
-};
-
-static struct digest_type_t digest_types[] =
-{
- {{"md4"}, {&EVP_md4}},
- {{"md5"}, {&EVP_md5}},
- {{"ripemd160"}, {&EVP_ripemd160}},
- {{"sha"}, {&EVP_sha1}},
- {{"sha224"},
-#ifdef HAVE_SHA224
- {&EVP_sha224}
-#else
- {NULL}
-#endif
- },
- {{"sha256"},
-#ifdef HAVE_SHA256
- {&EVP_sha256}
-#else
- {NULL}
-#endif
- },
- {{"sha384"},
-#ifdef HAVE_SHA384
- {&EVP_sha384}
-#else
- {NULL}
-#endif
- },
- {{"sha512"},
-#ifdef HAVE_SHA512
- {&EVP_sha512}
-#else
- {NULL}
-#endif
- },
- {{"sha3_224"},
-#ifdef HAVE_SHA3_224
- {&EVP_sha3_224}
-#else
- {NULL}
-#endif
- },
- {{"sha3_256"},
-#ifdef HAVE_SHA3_256
- {&EVP_sha3_256}
-#else
- {NULL}
-#endif
- },
- {{"sha3_384"},
-#ifdef HAVE_SHA3_384
- {&EVP_sha3_384}
-#else
- {NULL}
-#endif
- },
- {{"sha3_512"},
-#ifdef HAVE_SHA3_512
- {&EVP_sha3_512}
-#else
- {NULL}
-#endif
- },
-
- {{NULL}}
-};
-
-static struct digest_type_t* get_digest_type(ERL_NIF_TERM type);
-
-struct cipher_type_t {
- union {
- const char* str; /* before init */
- ERL_NIF_TERM atom; /* after init */
- }type;
- union {
- const EVP_CIPHER* (*funcp)(void); /* before init, NULL if notsup */
- const EVP_CIPHER* p; /* after init, NULL if notsup */
- }cipher;
- const size_t key_len; /* != 0 to also match on key_len */
-};
-
-#ifdef OPENSSL_NO_DES
-#define COND_NO_DES_PTR(Ptr) (NULL)
-#else
-#define COND_NO_DES_PTR(Ptr) (Ptr)
-#endif
-
-static struct cipher_type_t cipher_types[] =
-{
- {{"rc2_cbc"},
-#ifndef OPENSSL_NO_RC2
- {&EVP_rc2_cbc}
-#else
- {NULL}
-#endif
- },
- {{"des_cbc"}, {COND_NO_DES_PTR(&EVP_des_cbc)}},
- {{"des_cfb"}, {COND_NO_DES_PTR(&EVP_des_cfb8)}},
- {{"des_ecb"}, {COND_NO_DES_PTR(&EVP_des_ecb)}},
- {{"des_ede3_cbc"}, {COND_NO_DES_PTR(&EVP_des_ede3_cbc)}},
- {{"des_ede3_cbf"}, /* Misspelled, retained */
-#ifdef HAVE_DES_ede3_cfb_encrypt
- {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}
-#else
- {NULL}
-#endif
- },
- {{"des_ede3_cfb"},
-#ifdef HAVE_DES_ede3_cfb_encrypt
- {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)}
-#else
- {NULL}
-#endif
- },
- {{"blowfish_cbc"}, {&EVP_bf_cbc}},
- {{"blowfish_cfb64"}, {&EVP_bf_cfb64}},
- {{"blowfish_ofb64"}, {&EVP_bf_ofb}},
- {{"blowfish_ecb"}, {&EVP_bf_ecb}},
- {{"aes_cbc"}, {&EVP_aes_128_cbc}, 16},
- {{"aes_cbc"}, {&EVP_aes_192_cbc}, 24},
- {{"aes_cbc"}, {&EVP_aes_256_cbc}, 32},
- {{"aes_cbc128"}, {&EVP_aes_128_cbc}},
- {{"aes_cbc256"}, {&EVP_aes_256_cbc}},
- {{"aes_cfb8"}, {&EVP_aes_128_cfb8}},
- {{"aes_cfb128"}, {&EVP_aes_128_cfb128}},
- {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16},
- {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24},
- {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32},
- {{NULL}}
-};
-
-static struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len);
-
-
-/*
-#define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n")
-#define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1)
-#define PRINTF_ERR2(FMT, A1, A2) enif_fprintf(stderr, FMT "\n", A1, A2)
-*/
-
-#define PRINTF_ERR0(FMT)
-#define PRINTF_ERR1(FMT,A1)
-#define PRINTF_ERR2(FMT,A1,A2)
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-/* Define resource types for OpenSSL context structures. */
-static ErlNifResourceType* evp_md_ctx_rtype;
-struct evp_md_ctx {
- EVP_MD_CTX* ctx;
-};
-static void evp_md_ctx_dtor(ErlNifEnv* env, struct evp_md_ctx *ctx) {
- EVP_MD_CTX_free(ctx->ctx);
-}
-#endif
-
-#ifdef HAVE_EVP_AES_CTR
-static ErlNifResourceType* evp_cipher_ctx_rtype;
-struct evp_cipher_ctx {
- EVP_CIPHER_CTX* ctx;
-};
-static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) {
- EVP_CIPHER_CTX_free(ctx->ctx);
-}
-#endif
-
-// Engine
-#ifdef HAS_ENGINE_SUPPORT
-static ErlNifResourceType* engine_ctx_rtype;
-struct engine_ctx {
- ENGINE *engine;
- char *id;
-};
-static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) {
- PRINTF_ERR0("engine_ctx_dtor");
- if(ctx->id) {
- PRINTF_ERR1(" non empty ctx->id=%s", ctx->id);
- enif_free(ctx->id);
- } else
- PRINTF_ERR0(" empty ctx->id=NULL");
-}
-#endif
static int verify_lib_version(void)
{
@@ -1015,46 +153,6 @@ static int verify_lib_version(void)
return 1;
}
-#ifdef FIPS_SUPPORT
-/* In FIPS mode non-FIPS algorithms are disabled and return badarg. */
-#define CHECK_NO_FIPS_MODE() { if (FIPS_mode()) return atom_notsup; }
-#else
-#define CHECK_NO_FIPS_MODE()
-#endif
-
-#ifdef HAVE_DYNAMIC_CRYPTO_LIB
-
-# if defined(DEBUG)
-static char crypto_callback_name[] = "crypto_callback.debug";
-# elif defined(VALGRIND)
-static char crypto_callback_name[] = "crypto_callback.valgrind";
-# else
-static char crypto_callback_name[] = "crypto_callback";
-# endif
-
-static int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile)
-{
- int i;
-
- for (i = bin->size; i > 0; i--) {
- if (bin->data[i-1] == '/')
- break;
- }
- if (i + strlen(newfile) >= bufsz) {
- PRINTF_ERR0("CRYPTO: lib name too long");
- return 0;
- }
- memcpy(buf, bin->data, i);
- strcpy(buf+i, newfile);
- return 1;
-}
-
-static void error_handler(void* null, const char* errstr)
-{
- PRINTF_ERR1("CRYPTO LOADING ERROR: '%s'", errstr);
-}
-#endif /* HAVE_DYNAMIC_CRYPTO_LIB */
-
static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
{
#ifdef OPENSSL_THREADS
@@ -1083,44 +181,18 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
return __LINE__;
}
- hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context",
- (ErlNifResourceDtor*) hmac_context_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!hmac_context_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'");
+ if (!init_hmac_ctx(env)) {
return __LINE__;
}
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
- evp_md_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_MD_CTX",
- (ErlNifResourceDtor*) evp_md_ctx_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!evp_md_ctx_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_MD_CTX'");
+ if (!init_hash_ctx(env)) {
return __LINE__;
}
-#endif
-#ifdef HAVE_EVP_AES_CTR
- evp_cipher_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_CIPHER_CTX",
- (ErlNifResourceDtor*) evp_cipher_ctx_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!evp_cipher_ctx_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_CIPHER_CTX'");
+ if (!init_cipher_ctx(env)) {
return __LINE__;
}
-#endif
-#ifdef HAS_ENGINE_SUPPORT
- engine_ctx_rtype = enif_open_resource_type(env, NULL, "ENGINE_CTX",
- (ErlNifResourceDtor*) engine_ctx_dtor,
- ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
- NULL);
- if (!engine_ctx_rtype) {
- PRINTF_ERR0("CRYPTO: Could not open resource type 'ENGINE_CTX'");
+ if (!init_engine_ctx(env)) {
return __LINE__;
}
-#endif
if (library_initialized) {
/* Repeated loading of this library (module upgrade).
@@ -1129,135 +201,10 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
return 0;
}
- atom_true = enif_make_atom(env,"true");
- atom_false = enif_make_atom(env,"false");
- /* Enter FIPS mode */
- if (tpl_array[2] == atom_true) {
-#ifdef FIPS_SUPPORT
- if (!FIPS_mode_set(1)) {
-#else
- {
-#endif
- PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
- return 0;
- }
- } else if (tpl_array[2] != atom_false) {
- PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info);
- return 0;
+ if (!init_atoms(env, tpl_array[2], load_info)) {
+ return __LINE__;
}
- atom_sha = enif_make_atom(env,"sha");
- atom_error = enif_make_atom(env,"error");
- atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding");
- atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
- atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding");
- atom_signature_md = enif_make_atom(env,"signature_md");
- atom_undefined = enif_make_atom(env,"undefined");
- atom_ok = enif_make_atom(env,"ok");
- atom_not_prime = enif_make_atom(env,"not_prime");
- atom_not_strong_prime = enif_make_atom(env,"not_strong_prime");
- atom_unable_to_check_generator = enif_make_atom(env,"unable_to_check_generator");
- atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator");
- atom_check_failed = enif_make_atom(env,"check_failed");
- atom_unknown = enif_make_atom(env,"unknown");
- atom_none = enif_make_atom(env,"none");
- atom_notsup = enif_make_atom(env,"notsup");
- atom_digest = enif_make_atom(env,"digest");
-
-#if defined(HAVE_EC)
- atom_ec = enif_make_atom(env,"ec");
- atom_prime_field = enif_make_atom(env,"prime_field");
- atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field");
- atom_tpbasis = enif_make_atom(env,"tpbasis");
- atom_ppbasis = enif_make_atom(env,"ppbasis");
- atom_onbasis = enif_make_atom(env,"onbasis");
-#endif
-
- atom_aes_cfb8 = enif_make_atom(env, "aes_cfb8");
- atom_aes_cfb128 = enif_make_atom(env, "aes_cfb128");
-#ifdef HAVE_GCM
- atom_aes_gcm = enif_make_atom(env, "aes_gcm");
-#endif
-#ifdef HAVE_CCM
- atom_aes_ccm = enif_make_atom(env, "aes_ccm");
-#endif
-#ifdef HAVE_CHACHA20_POLY1305
- atom_chacha20_poly1305 = enif_make_atom(env,"chacha20_poly1305");
-#endif
-#ifdef HAVE_ECB_IVEC_BUG
- atom_aes_ecb = enif_make_atom(env, "aes_ecb");
- atom_des_ecb = enif_make_atom(env, "des_ecb");
- atom_blowfish_ecb = enif_make_atom(env, "blowfish_ecb");
-#endif
-
-#ifdef FIPS_SUPPORT
- atom_enabled = enif_make_atom(env,"enabled");
- atom_not_enabled = enif_make_atom(env,"not_enabled");
-#else
- atom_not_supported = enif_make_atom(env,"not_supported");
-#endif
- atom_rsa = enif_make_atom(env,"rsa");
- atom_dss = enif_make_atom(env,"dss");
- atom_ecdsa = enif_make_atom(env,"ecdsa");
-#ifdef HAVE_ED_CURVE_DH
- atom_x25519 = enif_make_atom(env,"x25519");
- atom_x448 = enif_make_atom(env,"x448");
-#endif
- atom_eddsa = enif_make_atom(env,"eddsa");
-#ifdef HAVE_EDDSA
- atom_ed25519 = enif_make_atom(env,"ed25519");
- atom_ed448 = enif_make_atom(env,"ed448");
-#endif
- atom_rsa_mgf1_md = enif_make_atom(env,"rsa_mgf1_md");
- atom_rsa_oaep_label = enif_make_atom(env,"rsa_oaep_label");
- atom_rsa_oaep_md = enif_make_atom(env,"rsa_oaep_md");
- atom_rsa_pad = enif_make_atom(env,"rsa_pad"); /* backwards compatibility */
- atom_rsa_padding = enif_make_atom(env,"rsa_padding");
- atom_rsa_pkcs1_pss_padding = enif_make_atom(env,"rsa_pkcs1_pss_padding");
-#ifdef HAVE_RSA_SSLV23_PADDING
- atom_rsa_sslv23_padding = enif_make_atom(env,"rsa_sslv23_padding");
-#endif
- atom_rsa_x931_padding = enif_make_atom(env,"rsa_x931_padding");
- atom_rsa_pss_saltlen = enif_make_atom(env,"rsa_pss_saltlen");
- atom_sha224 = enif_make_atom(env,"sha224");
- atom_sha256 = enif_make_atom(env,"sha256");
- atom_sha384 = enif_make_atom(env,"sha384");
- atom_sha512 = enif_make_atom(env,"sha512");
- atom_sha3_224 = enif_make_atom(env,"sha3_224");
- atom_sha3_256 = enif_make_atom(env,"sha3_256");
- atom_sha3_384 = enif_make_atom(env,"sha3_384");
- atom_sha3_512 = enif_make_atom(env,"sha3_512");
- atom_md5 = enif_make_atom(env,"md5");
- atom_ripemd160 = enif_make_atom(env,"ripemd160");
-
-#ifdef HAS_ENGINE_SUPPORT
- atom_bad_engine_method = enif_make_atom(env,"bad_engine_method");
- atom_bad_engine_id = enif_make_atom(env,"bad_engine_id");
- atom_ctrl_cmd_failed = enif_make_atom(env,"ctrl_cmd_failed");
- atom_engine_init_failed = enif_make_atom(env,"engine_init_failed");
- atom_engine_method_not_supported = enif_make_atom(env,"engine_method_not_supported");
- atom_add_engine_failed = enif_make_atom(env,"add_engine_failed");
- atom_remove_engine_failed = enif_make_atom(env,"remove_engine_failed");
-
- atom_engine_method_rsa = enif_make_atom(env,"engine_method_rsa");
- atom_engine_method_dsa = enif_make_atom(env,"engine_method_dsa");
- atom_engine_method_dh = enif_make_atom(env,"engine_method_dh");
- atom_engine_method_rand = enif_make_atom(env,"engine_method_rand");
- atom_engine_method_ecdh = enif_make_atom(env,"engine_method_ecdh");
- atom_engine_method_ecdsa = enif_make_atom(env,"engine_method_ecdsa");
- atom_engine_method_store = enif_make_atom(env,"engine_method_store");
- atom_engine_method_ciphers = enif_make_atom(env,"engine_method_ciphers");
- atom_engine_method_digests = enif_make_atom(env,"engine_method_digests");
- atom_engine_method_pkey_meths = enif_make_atom(env,"engine_method_pkey_meths");
- atom_engine_method_pkey_asn1_meths = enif_make_atom(env,"engine_method_pkey_asn1_meths");
- atom_engine_method_ec = enif_make_atom(env,"engine_method_ec");
-
- atom_engine = enif_make_atom(env,"engine");
- atom_key_id = enif_make_atom(env,"key_id");
- atom_password = enif_make_atom(env,"password");
-#endif
-
-
#ifdef HAVE_DYNAMIC_CRYPTO_LIB
{
void* handle;
@@ -1345,4787 +292,3 @@ static void unload(ErlNifEnv* env, void* priv_data)
{
--library_refc;
}
-
-static int algo_hash_cnt, algo_hash_fips_cnt;
-static ERL_NIF_TERM algo_hash[12]; /* increase when extending the list */
-static int algo_pubkey_cnt, algo_pubkey_fips_cnt;
-static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */
-static int algo_cipher_cnt, algo_cipher_fips_cnt;
-static ERL_NIF_TERM algo_cipher[25]; /* increase when extending the list */
-static int algo_mac_cnt, algo_mac_fips_cnt;
-static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */
-static int algo_curve_cnt, algo_curve_fips_cnt;
-static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */
-static int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt;
-static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */
-
-static void init_algorithms_types(ErlNifEnv* env)
-{
- // Validated algorithms first
- algo_hash_cnt = 0;
- algo_hash[algo_hash_cnt++] = atom_sha;
-#ifdef HAVE_SHA224
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha224");
-#endif
-#ifdef HAVE_SHA256
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha256");
-#endif
-#ifdef HAVE_SHA384
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha384");
-#endif
-#ifdef HAVE_SHA512
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha512");
-#endif
-#ifdef HAVE_SHA3_224
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_224");
-#endif
-#ifdef HAVE_SHA3_256
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_256");
-#endif
-#ifdef HAVE_SHA3_384
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_384");
-#endif
-#ifdef HAVE_SHA3_512
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512");
-#endif
- // Non-validated algorithms follow
- algo_hash_fips_cnt = algo_hash_cnt;
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md4");
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md5");
- algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160");
-
- algo_pubkey_cnt = 0;
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa");
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dss");
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dh");
-#if defined(HAVE_EC)
-#if !defined(OPENSSL_NO_EC2M)
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ec_gf2m");
-#endif
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdsa");
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdh");
-#endif
- // Non-validated algorithms follow
- algo_pubkey_fips_cnt = algo_pubkey_cnt;
- // Don't know if Edward curves are fips validated
-#if defined(HAVE_EDDSA)
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa");
-#endif
- algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
-
- // Validated algorithms first
- algo_cipher_cnt = 0;
-#ifndef OPENSSL_NO_DES
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des_ede3");
-#ifdef HAVE_DES_ede3_cfb_encrypt
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cbf");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "des3_cfb");
-#endif
-#endif
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc128");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb8");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb128");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc256");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ctr");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ecb");
-#if defined(HAVE_GCM)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_gcm");
-#endif
-#if defined(HAVE_CCM)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ccm");
-#endif
- // Non-validated algorithms follow
- algo_cipher_fips_cnt = algo_cipher_cnt;
-#ifdef HAVE_AES_IGE
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ige256");
-#endif
-#ifndef OPENSSL_NO_DES
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_cfb");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"des_ecb");
-#endif
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cbc");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_cfb64");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ofb64");
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ecb");
-#ifndef OPENSSL_NO_RC2
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc2_cbc");
-#endif
-#ifndef OPENSSL_NO_RC4
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc4");
-#endif
-#if defined(HAVE_CHACHA20_POLY1305)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20_poly1305");
-#endif
-#if defined(HAVE_CHACHA20)
- algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20");
-#endif
-
- // Validated algorithms first
- algo_mac_cnt = 0;
- algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac");
-#ifdef HAVE_CMAC
- algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac");
-#endif
-#ifdef HAVE_POLY1305
- algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305");
-#endif
- // Non-validated algorithms follow
- algo_mac_fips_cnt = algo_mac_cnt;
-
- // Validated algorithms first
- algo_curve_cnt = 0;
-#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11");
-#endif
-#endif
- // Non-validated algorithms follow
- algo_curve_fips_cnt = algo_curve_cnt;
-#if defined(HAVE_EC)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8");
-#if !defined(OPENSSL_NO_EC2M)
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4");
-#endif
-#endif
- //--
-#ifdef HAVE_EDDSA
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448");
-#endif
-#ifdef HAVE_ED_CURVE_DH
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519");
- algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448");
-#endif
-
- // Validated algorithms first
- algo_rsa_opts_cnt = 0;
-#ifdef HAS_EVP_PKEY_CTX
-# ifdef HAVE_RSA_PKCS1_PSS_PADDING
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding");
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen");
-# endif
-# ifdef HAVE_RSA_MGF1_MD
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md");
-# endif
-# ifdef HAVE_RSA_OAEP_PADDING
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
-# endif
-# ifdef HAVE_RSA_OAEP_MD
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label");
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md");
-# endif
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"signature_md");
-#endif
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_padding");
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_x931_padding");
-#ifdef HAVE_RSA_SSLV23_PADDING
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_sslv23_padding");
-#endif
- algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding");
- algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt;
-
-
- // Check that the max number of algos is updated
- ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_cipher_cnt <= sizeof(algo_cipher)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM));
- ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM));
-}
-
-static ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
-#ifdef FIPS_SUPPORT
- int fips_mode = FIPS_mode();
- int hash_cnt = fips_mode ? algo_hash_fips_cnt : algo_hash_cnt;
- int pubkey_cnt = fips_mode ? algo_pubkey_fips_cnt : algo_pubkey_cnt;
- int cipher_cnt = fips_mode ? algo_cipher_fips_cnt : algo_cipher_cnt;
- int mac_cnt = fips_mode ? algo_mac_fips_cnt : algo_mac_cnt;
- int curve_cnt = fips_mode ? algo_curve_fips_cnt : algo_curve_cnt;
- int rsa_opts_cnt = fips_mode ? algo_rsa_opts_fips_cnt : algo_rsa_opts_cnt;
-#else
- int hash_cnt = algo_hash_cnt;
- int pubkey_cnt = algo_pubkey_cnt;
- int cipher_cnt = algo_cipher_cnt;
- int mac_cnt = algo_mac_cnt;
- int curve_cnt = algo_curve_cnt;
- int rsa_opts_cnt = algo_rsa_opts_cnt;
-#endif
- return enif_make_tuple6(env,
- enif_make_list_from_array(env, algo_hash, hash_cnt),
- enif_make_list_from_array(env, algo_pubkey, pubkey_cnt),
- enif_make_list_from_array(env, algo_cipher, cipher_cnt),
- enif_make_list_from_array(env, algo_mac, mac_cnt),
- enif_make_list_from_array(env, algo_curve, curve_cnt),
- enif_make_list_from_array(env, algo_rsa_opts, rsa_opts_cnt)
- );
-}
-
-static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */
-
- static const char libname[] = "OpenSSL";
- unsigned name_sz = strlen(libname);
- const char* ver = SSLeay_version(SSLEAY_VERSION);
- unsigned ver_sz = strlen(ver);
- ERL_NIF_TERM name_term, ver_term;
- int ver_num = OPENSSL_VERSION_NUMBER;
- /* R16:
- * Ignore library version number from SSLeay() and instead show header
- * version. Otherwise user might try to call a function that is implemented
- * by a newer library but not supported by the headers used at compile time.
- * Example: DES_ede3_cfb_encrypt in 0.9.7i but not in 0.9.7d.
- *
- * Version string is still from library though.
- */
-
- memcpy(enif_make_new_binary(env, name_sz, &name_term), libname, name_sz);
- memcpy(enif_make_new_binary(env, ver_sz, &ver_term), ver, ver_sz);
-
- return enif_make_list1(env, enif_make_tuple3(env, name_term,
- enif_make_int(env, ver_num),
- ver_term));
-}
-
-static ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
-#ifdef FIPS_SUPPORT
- return FIPS_mode() ? atom_enabled : atom_not_enabled;
-#else
- return atom_not_supported;
-#endif
-}
-
-static ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Boolean) */
- if (argv[0] == atom_true) {
-#ifdef FIPS_SUPPORT
- if (FIPS_mode_set(1)) {
- return atom_true;
- }
-#endif
- PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
- return atom_false;
- } else if (argv[0] == atom_false) {
-#ifdef FIPS_SUPPORT
- if (!FIPS_mode_set(0)) {
- return atom_false;
- }
-#endif
- return atom_true;
- } else {
- return enif_make_badarg(env);
- }
-}
-
-
-#if defined(HAVE_EC)
-static ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env)
-{
- ERL_NIF_TERM reason;
- if (enif_has_pending_exception(env, &reason))
- return reason; /* dummy return value ignored */
- else
- return enif_make_badarg(env);
-}
-#endif
-
-static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Data) */
- struct digest_type_t *digp = NULL;
- const EVP_MD *md;
- ErlNifBinary data;
- ERL_NIF_TERM ret;
- unsigned ret_size;
-
- digp = get_digest_type(argv[0]);
- if (!digp ||
- !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
- md = digp->md.p;
- if (!md) {
- return atom_notsup;
- }
-
- ret_size = (unsigned)EVP_MD_size(md);
- ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
- if (!EVP_Digest(data.data, data.size,
- enif_make_new_binary(env, ret_size, &ret), &ret_size,
- md, NULL)) {
- return atom_notsup;
- }
- ASSERT(ret_size == (unsigned)EVP_MD_size(md));
-
- CONSUME_REDS(env, data);
- return ret;
-}
-
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
-
-static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type) */
- struct digest_type_t *digp = NULL;
- struct evp_md_ctx *ctx;
- ERL_NIF_TERM ret;
-
- digp = get_digest_type(argv[0]);
- if (!digp) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx));
- ctx->ctx = EVP_MD_CTX_new();
- if (!EVP_DigestInit(ctx->ctx, digp->md.p)) {
- enif_release_resource(ctx);
- return atom_notsup;
- }
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
- return ret;
-}
-static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- struct evp_md_ctx *ctx, *new_ctx;
- ErlNifBinary data;
- ERL_NIF_TERM ret;
-
- if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx) ||
- !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
-
- new_ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx));
- new_ctx->ctx = EVP_MD_CTX_new();
- if (!EVP_MD_CTX_copy(new_ctx->ctx, ctx->ctx) ||
- !EVP_DigestUpdate(new_ctx->ctx, data.data, data.size)) {
- enif_release_resource(new_ctx);
- return atom_notsup;
- }
-
- ret = enif_make_resource(env, new_ctx);
- enif_release_resource(new_ctx);
- CONSUME_REDS(env, data);
- return ret;
-}
-static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context) */
- struct evp_md_ctx *ctx;
- EVP_MD_CTX *new_ctx;
- ERL_NIF_TERM ret;
- unsigned ret_size;
-
- if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx)) {
- return enif_make_badarg(env);
- }
-
- ret_size = (unsigned)EVP_MD_CTX_size(ctx->ctx);
- ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
-
- new_ctx = EVP_MD_CTX_new();
- if (!EVP_MD_CTX_copy(new_ctx, ctx->ctx) ||
- !EVP_DigestFinal(new_ctx,
- enif_make_new_binary(env, ret_size, &ret),
- &ret_size)) {
- EVP_MD_CTX_free(new_ctx);
- return atom_notsup;
- }
- EVP_MD_CTX_free(new_ctx);
- ASSERT(ret_size == (unsigned)EVP_MD_CTX_size(ctx->ctx));
-
- return ret;
-}
-
-#else /* if OPENSSL_VERSION_NUMBER < 1.0 */
-
-static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type) */
- typedef int (*init_fun)(unsigned char*);
- struct digest_type_t *digp = NULL;
- ERL_NIF_TERM ctx;
- size_t ctx_size = 0;
- init_fun ctx_init = 0;
-
- digp = get_digest_type(argv[0]);
- if (!digp) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- switch (EVP_MD_type(digp->md.p))
- {
- case NID_md4:
- ctx_size = MD4_CTX_LEN;
- ctx_init = (init_fun)(&MD4_Init);
- break;
- case NID_md5:
- ctx_size = MD5_CTX_LEN;
- ctx_init = (init_fun)(&MD5_Init);
- break;
- case NID_ripemd160:
- ctx_size = RIPEMD160_CTX_LEN;
- ctx_init = (init_fun)(&RIPEMD160_Init);
- break;
- case NID_sha1:
- ctx_size = sizeof(SHA_CTX);
- ctx_init = (init_fun)(&SHA1_Init);
- break;
-#ifdef HAVE_SHA224
- case NID_sha224:
- ctx_size = sizeof(SHA256_CTX);
- ctx_init = (init_fun)(&SHA224_Init);
- break;
-#endif
-#ifdef HAVE_SHA256
- case NID_sha256:
- ctx_size = sizeof(SHA256_CTX);
- ctx_init = (init_fun)(&SHA256_Init);
- break;
-#endif
-#ifdef HAVE_SHA384
- case NID_sha384:
- ctx_size = sizeof(SHA512_CTX);
- ctx_init = (init_fun)(&SHA384_Init);
- break;
-#endif
-#ifdef HAVE_SHA512
- case NID_sha512:
- ctx_size = sizeof(SHA512_CTX);
- ctx_init = (init_fun)(&SHA512_Init);
- break;
-#endif
- default:
- return atom_notsup;
- }
- ASSERT(ctx_size);
- ASSERT(ctx_init);
-
- ctx_init(enif_make_new_binary(env, ctx_size, &ctx));
- return enif_make_tuple2(env, argv[0], ctx);
-}
-static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* ({Type, Context}, Data) */
- typedef int (*update_fun)(unsigned char*, const unsigned char*, size_t);
- ERL_NIF_TERM new_ctx;
- ErlNifBinary ctx, data;
- const ERL_NIF_TERM *tuple;
- int arity;
- struct digest_type_t *digp = NULL;
- unsigned char *ctx_buff;
- size_t ctx_size = 0;
- update_fun ctx_update = 0;
-
- if (!enif_get_tuple(env, argv[0], &arity, &tuple) ||
- arity != 2 ||
- !(digp = get_digest_type(tuple[0])) ||
- !enif_inspect_binary(env, tuple[1], &ctx) ||
- !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- switch (EVP_MD_type(digp->md.p))
- {
- case NID_md4:
- ctx_size = MD4_CTX_LEN;
- ctx_update = (update_fun)(&MD4_Update);
- break;
- case NID_md5:
- ctx_size = MD5_CTX_LEN;
- ctx_update = (update_fun)(&MD5_Update);
- break;
- case NID_ripemd160:
- ctx_size = RIPEMD160_CTX_LEN;
- ctx_update = (update_fun)(&RIPEMD160_Update);
- break;
- case NID_sha1:
- ctx_size = sizeof(SHA_CTX);
- ctx_update = (update_fun)(&SHA1_Update);
- break;
-#ifdef HAVE_SHA224
- case NID_sha224:
- ctx_size = sizeof(SHA256_CTX);
- ctx_update = (update_fun)(&SHA224_Update);
- break;
-#endif
-#ifdef HAVE_SHA256
- case NID_sha256:
- ctx_size = sizeof(SHA256_CTX);
- ctx_update = (update_fun)(&SHA256_Update);
- break;
-#endif
-#ifdef HAVE_SHA384
- case NID_sha384:
- ctx_size = sizeof(SHA512_CTX);
- ctx_update = (update_fun)(&SHA384_Update);
- break;
-#endif
-#ifdef HAVE_SHA512
- case NID_sha512:
- ctx_size = sizeof(SHA512_CTX);
- ctx_update = (update_fun)(&SHA512_Update);
- break;
-#endif
- default:
- return atom_notsup;
- }
- ASSERT(ctx_size);
- ASSERT(ctx_update);
-
- if (ctx.size != ctx_size) {
- return enif_make_badarg(env);
- }
-
- ctx_buff = enif_make_new_binary(env, ctx_size, &new_ctx);
- memcpy(ctx_buff, ctx.data, ctx_size);
- ctx_update(ctx_buff, data.data, data.size);
-
- CONSUME_REDS(env, data);
- return enif_make_tuple2(env, tuple[0], new_ctx);
-}
-static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* ({Type, Context}) */
- typedef int (*final_fun)(unsigned char*, void*);
- ERL_NIF_TERM ret;
- ErlNifBinary ctx;
- const ERL_NIF_TERM *tuple;
- int arity;
- struct digest_type_t *digp = NULL;
- const EVP_MD *md;
- void *new_ctx;
- size_t ctx_size = 0;
- final_fun ctx_final = 0;
-
- if (!enif_get_tuple(env, argv[0], &arity, &tuple) ||
- arity != 2 ||
- !(digp = get_digest_type(tuple[0])) ||
- !enif_inspect_binary(env, tuple[1], &ctx)) {
- return enif_make_badarg(env);
- }
- md = digp->md.p;
- if (!md) {
- return atom_notsup;
- }
-
-
- switch (EVP_MD_type(md))
- {
- case NID_md4:
- ctx_size = MD4_CTX_LEN;
- ctx_final = (final_fun)(&MD4_Final);
- break;
- case NID_md5:
- ctx_size = MD5_CTX_LEN;
- ctx_final = (final_fun)(&MD5_Final);
- break;
- case NID_ripemd160:
- ctx_size = RIPEMD160_CTX_LEN;
- ctx_final = (final_fun)(&RIPEMD160_Final);
- break;
- case NID_sha1:
- ctx_size = sizeof(SHA_CTX);
- ctx_final = (final_fun)(&SHA1_Final);
- break;
-#ifdef HAVE_SHA224
- case NID_sha224:
- ctx_size = sizeof(SHA256_CTX);
- ctx_final = (final_fun)(&SHA224_Final);
- break;
-#endif
-#ifdef HAVE_SHA256
- case NID_sha256:
- ctx_size = sizeof(SHA256_CTX);
- ctx_final = (final_fun)(&SHA256_Final);
- break;
-#endif
-#ifdef HAVE_SHA384
- case NID_sha384:
- ctx_size = sizeof(SHA512_CTX);
- ctx_final = (final_fun)(&SHA384_Final);
- break;
-#endif
-#ifdef HAVE_SHA512
- case NID_sha512:
- ctx_size = sizeof(SHA512_CTX);
- ctx_final = (final_fun)(&SHA512_Final);
- break;
-#endif
- default:
- return atom_notsup;
- }
- ASSERT(ctx_size);
- ASSERT(ctx_final);
-
- if (ctx.size != ctx_size) {
- return enif_make_badarg(env);
- }
-
- new_ctx = enif_alloc(ctx_size);
- memcpy(new_ctx, ctx.data, ctx_size);
- ctx_final(enif_make_new_binary(env, (size_t)EVP_MD_size(md), &ret),
- new_ctx);
- enif_free(new_ctx);
-
- return ret;
-}
-#endif /* OPENSSL_VERSION_NUMBER < 1.0 */
-
-
-static ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */
- struct digest_type_t *digp = NULL;
- ErlNifBinary key, data;
- unsigned char buff[EVP_MAX_MD_SIZE];
- unsigned size = 0, req_size = 0;
- ERL_NIF_TERM ret;
-
- digp = get_digest_type(argv[0]);
- if (!digp ||
- !enif_inspect_iolist_as_binary(env, argv[1], &key) ||
- !enif_inspect_iolist_as_binary(env, argv[2], &data) ||
- (argc == 4 && !enif_get_uint(env, argv[3], &req_size))) {
- return enif_make_badarg(env);
- }
-
- if (!digp->md.p ||
- !HMAC(digp->md.p,
- key.data, key.size,
- data.data, data.size,
- buff, &size)) {
- return atom_notsup;
- }
- ASSERT(0 < size && size <= EVP_MAX_MD_SIZE);
- CONSUME_REDS(env, data);
-
- if (argc == 4) {
- if (req_size <= size) {
- size = req_size;
- }
- else {
- return enif_make_badarg(env);
- }
- }
- memcpy(enif_make_new_binary(env, size, &ret), buff, size);
- return ret;
-}
-
-static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj)
-{
- if (obj->alive) {
- HMAC_CTX_free(obj->ctx);
- obj->alive = 0;
- }
- enif_mutex_destroy(obj->mtx);
-}
-
-static ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key) */
- struct digest_type_t *digp = NULL;
- ErlNifBinary key;
- ERL_NIF_TERM ret;
- struct hmac_context *obj;
-
- digp = get_digest_type(argv[0]);
- if (!digp ||
- !enif_inspect_iolist_as_binary(env, argv[1], &key)) {
- return enif_make_badarg(env);
- }
- if (!digp->md.p) {
- return atom_notsup;
- }
-
- obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context));
- obj->mtx = enif_mutex_create("crypto.hmac");
- obj->alive = 1;
- obj->ctx = HMAC_CTX_new();
-#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
- // Check the return value of HMAC_Init: it may fail in FIPS mode
- // for disabled algorithms
- if (!HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL)) {
- enif_release_resource(obj);
- return atom_notsup;
- }
-#else
- HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL);
-#endif
-
- ret = enif_make_resource(env, obj);
- enif_release_resource(obj);
- return ret;
-}
-
-static ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- ErlNifBinary data;
- struct hmac_context* obj;
-
- if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj)
- || !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
- return enif_make_badarg(env);
- }
- enif_mutex_lock(obj->mtx);
- if (!obj->alive) {
- enif_mutex_unlock(obj->mtx);
- return enif_make_badarg(env);
- }
- HMAC_Update(obj->ctx, data.data, data.size);
- enif_mutex_unlock(obj->mtx);
-
- CONSUME_REDS(env,data);
- return argv[0];
-}
-
-static ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context) or (Context, HashLen) */
- ERL_NIF_TERM ret;
- struct hmac_context* obj;
- unsigned char mac_buf[EVP_MAX_MD_SIZE];
- unsigned char * mac_bin;
- unsigned int req_len = 0;
- unsigned int mac_len;
-
- if (!enif_get_resource(env,argv[0],hmac_context_rtype, (void**)&obj)
- || (argc == 2 && !enif_get_uint(env, argv[1], &req_len))) {
- return enif_make_badarg(env);
- }
-
- enif_mutex_lock(obj->mtx);
- if (!obj->alive) {
- enif_mutex_unlock(obj->mtx);
- return enif_make_badarg(env);
- }
-
- HMAC_Final(obj->ctx, mac_buf, &mac_len);
- HMAC_CTX_free(obj->ctx);
- obj->alive = 0;
- enif_mutex_unlock(obj->mtx);
-
- if (argc == 2 && req_len < mac_len) {
- /* Only truncate to req_len bytes if asked. */
- mac_len = req_len;
- }
- mac_bin = enif_make_new_binary(env, mac_len, &ret);
- memcpy(mac_bin, mac_buf, mac_len);
-
- return ret;
-}
-
-static ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Data) */
-#if defined(HAVE_CMAC)
- struct cipher_type_t *cipherp = NULL;
- const EVP_CIPHER *cipher;
- CMAC_CTX *ctx;
- ErlNifBinary key;
- ErlNifBinary data;
- ERL_NIF_TERM ret;
- size_t ret_size;
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !(cipherp = get_cipher_type(argv[0], key.size))
- || !enif_inspect_iolist_as_binary(env, argv[2], &data)) {
- return enif_make_badarg(env);
- }
- cipher = cipherp->cipher.p;
- if (!cipher) {
- return enif_raise_exception(env, atom_notsup);
- }
-
- ctx = CMAC_CTX_new();
- if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL)) {
- CMAC_CTX_free(ctx);
- return atom_notsup;
- }
-
- if (!CMAC_Update(ctx, data.data, data.size) ||
- !CMAC_Final(ctx,
- enif_make_new_binary(env, EVP_CIPHER_block_size(cipher), &ret),
- &ret_size)) {
- CMAC_CTX_free(ctx);
- return atom_notsup;
- }
- ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher));
-
- CMAC_CTX_free(ctx);
- CONSUME_REDS(env, data);
- return ret;
-#else
- /* The CMAC functionality was introduced in OpenSSL 1.0.1
- * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are
- * no longer maintained. */
- return atom_notsup;
-#endif
-}
-
-/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */
-static ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, Text) */
-#ifdef HAVE_POLY1305
- ErlNifBinary key_bin, text, ret_bin;
- ERL_NIF_TERM ret = atom_error;
- EVP_PKEY *key = NULL;
- EVP_MD_CTX *mctx = NULL;
- EVP_PKEY_CTX *pctx = NULL;
- const EVP_MD *md = NULL;
- size_t size;
- int type;
-
- type = EVP_PKEY_POLY1305;
-
- if (!enif_inspect_binary(env, argv[0], &key_bin) ||
- !(key_bin.size == 32) ) {
- return enif_make_badarg(env);
- }
-
- if (!enif_inspect_binary(env, argv[1], &text) ) {
- return enif_make_badarg(env);
- }
-
- key = EVP_PKEY_new_raw_private_key(type, /*engine*/ NULL, key_bin.data, key_bin.size);
-
- if (!key ||
- !(mctx = EVP_MD_CTX_new()) ||
- !EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) ||
- !EVP_DigestSignUpdate(mctx, text.data, text.size)) {
- goto err;
- }
-
- if (!EVP_DigestSignFinal(mctx, NULL, &size) ||
- !enif_alloc_binary(size, &ret_bin) ||
- !EVP_DigestSignFinal(mctx, ret_bin.data, &size)) {
- goto err;
- }
-
- if ((size != ret_bin.size) &&
- !enif_realloc_binary(&ret_bin, size)) {
- goto err;
- }
-
- ret = enif_make_binary(env, &ret_bin);
-
- err:
- EVP_MD_CTX_free(mctx);
- EVP_PKEY_free(key);
- return ret;
-
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */
- struct cipher_type_t *cipherp = NULL;
- const EVP_CIPHER *cipher;
- ErlNifBinary key, ivec, text;
- EVP_CIPHER_CTX* ctx;
- ERL_NIF_TERM ret;
- unsigned char *out;
- int ivec_size, out_size = 0;
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !(cipherp = get_cipher_type(argv[0], key.size))
- || !enif_inspect_iolist_as_binary(env, argv[argc - 2], &text)) {
- return enif_make_badarg(env);
- }
- cipher = cipherp->cipher.p;
- if (!cipher) {
- return enif_raise_exception(env, atom_notsup);
- }
-
- if (argv[0] == atom_aes_cfb8
- && (key.size == 24 || key.size == 32)) {
- /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
- * Fall back on low level API
- */
- return aes_cfb_8_crypt(env, argc-1, argv+1);
- }
- else if (argv[0] == atom_aes_cfb128
- && (key.size == 24 || key.size == 32)) {
- /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes?
- * Fall back on low level API
- */
- return aes_cfb_128_crypt_nif(env, argc-1, argv+1);
- }
-
- ivec_size = EVP_CIPHER_iv_length(cipher);
-
-#ifdef HAVE_ECB_IVEC_BUG
- if (argv[0] == atom_aes_ecb || argv[0] == atom_blowfish_ecb ||
- argv[0] == atom_des_ecb)
- ivec_size = 0; /* 0.9.8l returns faulty ivec_size */
-#endif
-
- if (text.size % EVP_CIPHER_block_size(cipher) != 0 ||
- (ivec_size == 0 ? argc != 4
- : (argc != 5 ||
- !enif_inspect_iolist_as_binary(env, argv[2], &ivec) ||
- ivec.size != ivec_size))) {
- return enif_make_badarg(env);
- }
-
- out = enif_make_new_binary(env, text.size, &ret);
-
- ctx = EVP_CIPHER_CTX_new();
- if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL,
- (argv[argc - 1] == atom_true)) ||
- !EVP_CIPHER_CTX_set_key_length(ctx, key.size) ||
- !(EVP_CIPHER_type(cipher) != NID_rc2_cbc ||
- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, key.size * 8, NULL)) ||
- !EVP_CipherInit_ex(ctx, NULL, NULL,
- key.data, ivec_size ? ivec.data : NULL, -1) ||
- !EVP_CIPHER_CTX_set_padding(ctx, 0)) {
-
- EVP_CIPHER_CTX_free(ctx);
- return enif_raise_exception(env, atom_notsup);
- }
-
- if (text.size > 0 && /* OpenSSL 0.9.8h asserts text.size > 0 */
- (!EVP_CipherUpdate(ctx, out, &out_size, text.data, text.size)
- || (ASSERT(out_size == text.size), 0)
- || !EVP_CipherFinal_ex(ctx, out + out_size, &out_size))) {
-
- EVP_CIPHER_CTX_free(ctx);
- return enif_raise_exception(env, atom_notsup);
- }
- ASSERT(out_size == 0);
- EVP_CIPHER_CTX_free(ctx);
- CONSUME_REDS(env, text);
-
- return ret;
-}
-
-static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec, Data, IsEncrypt) */
- ErlNifBinary key, ivec, text;
- AES_KEY aes_key;
- unsigned char ivec_clone[16]; /* writable copy */
- int new_ivlen = 0;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
- || !(key.size == 16 || key.size == 24 || key.size == 32)
- || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
- || !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
- return enif_make_badarg(env);
- }
-
- memcpy(ivec_clone, ivec.data, 16);
- AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
- AES_cfb8_encrypt((unsigned char *) text.data,
- enif_make_new_binary(env, text.size, &ret),
- text.size, &aes_key, ivec_clone, &new_ivlen,
- (argv[3] == atom_true));
- CONSUME_REDS(env,text);
- return ret;
-}
-
-static ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec, Data, IsEncrypt) */
- ErlNifBinary key, ivec, text;
- AES_KEY aes_key;
- unsigned char ivec_clone[16]; /* writable copy */
- int new_ivlen = 0;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
- || !(key.size == 16 || key.size == 24 || key.size == 32)
- || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
- || !enif_inspect_iolist_as_binary(env, argv[2], &text)) {
- return enif_make_badarg(env);
- }
-
- memcpy(ivec_clone, ivec.data, 16);
- AES_set_encrypt_key(key.data, key.size * 8, &aes_key);
- AES_cfb128_encrypt((unsigned char *) text.data,
- enif_make_new_binary(env, text.size, &ret),
- text.size, &aes_key, ivec_clone, &new_ivlen,
- (argv[3] == atom_true));
- CONSUME_REDS(env,text);
- return ret;
-}
-
-static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec, Data, IsEncrypt) */
-#ifdef HAVE_AES_IGE
- ErlNifBinary key_bin, ivec_bin, data_bin;
- AES_KEY aes_key;
- unsigned char ivec[32];
- int i;
- unsigned char* ret_ptr;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || (key_bin.size != 16 && key_bin.size != 32)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || ivec_bin.size != 32
- || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)
- || data_bin.size % 16 != 0) {
-
- return enif_make_badarg(env);
- }
-
- if (argv[3] == atom_true) {
- i = AES_ENCRYPT;
- AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key);
- }
- else {
- i = AES_DECRYPT;
- AES_set_decrypt_key(key_bin.data, key_bin.size*8, &aes_key);
- }
-
- ret_ptr = enif_make_new_binary(env, data_bin.size, &ret);
- memcpy(ivec, ivec_bin.data, 32); /* writable copy */
- AES_ige_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, i);
- CONSUME_REDS(env,data_bin);
- return ret;
-#else
- return atom_notsup;
-#endif
-}
-
-
-/* Initializes state for ctr streaming (de)encryption
-*/
-#ifdef HAVE_EVP_AES_CTR
-static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec) */
- ErlNifBinary key_bin, ivec_bin;
- struct evp_cipher_ctx *ctx;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || ivec_bin.size != 16) {
- return enif_make_badarg(env);
- }
-
- switch (key_bin.size)
- {
- case 16: cipher = EVP_aes_128_ctr(); break;
- case 24: cipher = EVP_aes_192_ctr(); break;
- case 32: cipher = EVP_aes_256_ctr(); break;
- default: return enif_make_badarg(env);
- }
-
- ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- ctx->ctx = EVP_CIPHER_CTX_new();
- EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
- key_bin.data, ivec_bin.data, 1);
- EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
- return ret;
-}
-static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Context, Data) */
- struct evp_cipher_ctx *ctx, *new_ctx;
- ErlNifBinary data_bin;
- ERL_NIF_TERM ret, cipher_term;
- unsigned char *out;
- int outl = 0;
-
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx)
- || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
- return enif_make_badarg(env);
- }
- new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- new_ctx->ctx = EVP_CIPHER_CTX_new();
- EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx);
- out = enif_make_new_binary(env, data_bin.size, &cipher_term);
- EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size);
- ASSERT(outl == data_bin.size);
-
- ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
- enif_release_resource(new_ctx);
- CONSUME_REDS(env,data_bin);
- return ret;
-}
-
-#else /* if not HAVE_EVP_AES_CTR */
-
-static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IVec) */
- ErlNifBinary key_bin, ivec_bin;
- ERL_NIF_TERM ecount_bin;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || !(key_bin.size == 16 || key_bin.size == 24 || key_bin.size ==32)
- || ivec_bin.size != 16) {
- return enif_make_badarg(env);
- }
-
- memset(enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin),
- 0, AES_BLOCK_SIZE);
- return enif_make_tuple4(env, argv[0], argv[1], ecount_bin, enif_make_int(env, 0));
-}
-
-static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* ({Key, IVec, ECount, Num}, Data) */
- ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin;
- AES_KEY aes_key;
- unsigned int num;
- ERL_NIF_TERM ret, num2_term, cipher_term, ivec2_term, ecount2_term, new_state_term;
- int state_arity;
- const ERL_NIF_TERM *state_term;
- unsigned char * ivec2_buf;
- unsigned char * ecount2_buf;
-
- if (!enif_get_tuple(env, argv[0], &state_arity, &state_term)
- || state_arity != 4
- || !enif_inspect_iolist_as_binary(env, state_term[0], &key_bin)
- || AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key) != 0
- || !enif_inspect_binary(env, state_term[1], &ivec_bin) || ivec_bin.size != 16
- || !enif_inspect_binary(env, state_term[2], &ecount_bin) || ecount_bin.size != AES_BLOCK_SIZE
- || !enif_get_uint(env, state_term[3], &num)
- || !enif_inspect_iolist_as_binary(env, argv[1], &text_bin)) {
- return enif_make_badarg(env);
- }
-
- ivec2_buf = enif_make_new_binary(env, ivec_bin.size, &ivec2_term);
- ecount2_buf = enif_make_new_binary(env, ecount_bin.size, &ecount2_term);
-
- memcpy(ivec2_buf, ivec_bin.data, 16);
- memcpy(ecount2_buf, ecount_bin.data, ecount_bin.size);
-
- AES_ctr128_encrypt((unsigned char *) text_bin.data,
- enif_make_new_binary(env, text_bin.size, &cipher_term),
- text_bin.size, &aes_key, ivec2_buf, ecount2_buf, &num);
-
- num2_term = enif_make_uint(env, num);
- new_state_term = enif_make_tuple4(env, state_term[0], ivec2_term, ecount2_term, num2_term);
- ret = enif_make_tuple2(env, new_state_term, cipher_term);
- CONSUME_REDS(env,text_bin);
- return ret;
-}
-#endif /* !HAVE_EVP_AES_CTR */
-
-static ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type,Key,Iv,AAD,In) */
-#if defined(HAVE_AEAD)
- EVP_CIPHER_CTX *ctx;
- const EVP_CIPHER *cipher = NULL;
- ErlNifBinary key, iv, aad, in;
- unsigned int tag_len;
- unsigned char *outp, *tagp;
- ERL_NIF_TERM type, out, out_tag;
- int len, ctx_ctrl_set_ivlen, ctx_ctrl_get_tag;
-
- type = argv[0];
-
- if (!enif_is_atom(env, type)
- || !enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !enif_inspect_binary(env, argv[2], &iv)
- || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
- || !enif_inspect_iolist_as_binary(env, argv[4], &in)
- || !enif_get_uint(env, argv[5], &tag_len)) {
- return enif_make_badarg(env);
- }
-
- /* Use cipher_type some day. Must check block_encrypt|decrypt first */
-#if defined(HAVE_GCM)
- if (type == atom_aes_gcm) {
- if ((iv.size > 0)
- && (1 <= tag_len && tag_len <= 16)) {
- ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN;
- ctx_ctrl_get_tag = EVP_CTRL_GCM_GET_TAG;
- if (key.size == 16) cipher = EVP_aes_128_gcm();
- else if (key.size == 24) cipher = EVP_aes_192_gcm();
- else if (key.size == 32) cipher = EVP_aes_256_gcm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if ((7 <= iv.size && iv.size <= 13)
- && (4 <= tag_len && tag_len <= 16)
- && ((tag_len & 1) == 0)
- ) {
- ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN;
- ctx_ctrl_get_tag = EVP_CTRL_CCM_GET_TAG;
- if (key.size == 16) cipher = EVP_aes_128_ccm();
- else if (key.size == 24) cipher = EVP_aes_192_ccm();
- else if (key.size == 32) cipher = EVP_aes_256_ccm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CHACHA20_POLY1305)
- if (type == atom_chacha20_poly1305) {
- if ((key.size == 32)
- && (1 <= iv.size && iv.size <= 16)
- && (tag_len == 16)
- ) {
- ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN;
- ctx_ctrl_get_tag = EVP_CTRL_AEAD_GET_TAG,
- cipher = EVP_chacha20_poly1305();
- } else enif_make_badarg(env);
- } else
-#endif
- return enif_raise_exception(env, atom_notsup);
-
- ctx = EVP_CIPHER_CTX_new();
- if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err;
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err;
-
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL) != 1) goto out_err;
- if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
- if (EVP_EncryptUpdate(ctx, NULL, &len, NULL, in.size) != 1) goto out_err;
- } else
-#endif
- if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
-
- if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err;
-
- outp = enif_make_new_binary(env, in.size, &out);
-
- if (EVP_EncryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err;
- if (EVP_EncryptFinal_ex(ctx, outp/*+len*/, &len) != 1) goto out_err;
-
- tagp = enif_make_new_binary(env, tag_len, &out_tag);
-
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_get_tag, tag_len, tagp) != 1) goto out_err;
-
- EVP_CIPHER_CTX_free(ctx);
- CONSUME_REDS(env, in);
- return enif_make_tuple2(env, out, out_tag);
-
-out_err:
- EVP_CIPHER_CTX_free(ctx);
- return atom_error;
-
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-static ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type,Key,Iv,AAD,In,Tag) */
-#if defined(HAVE_AEAD)
- EVP_CIPHER_CTX *ctx;
- const EVP_CIPHER *cipher = NULL;
- ErlNifBinary key, iv, aad, in, tag;
- unsigned char *outp;
- ERL_NIF_TERM type, out;
- int len, ctx_ctrl_set_ivlen, ctx_ctrl_set_tag;
-
- type = argv[0];
-#if defined(HAVE_GCM_EVP_DECRYPT_BUG)
- if (type == atom_aes_gcm)
- return aes_gcm_decrypt_NO_EVP(env, argc, argv);
-#endif
-
- if (!enif_is_atom(env, type)
- || !enif_inspect_iolist_as_binary(env, argv[1], &key)
- || !enif_inspect_binary(env, argv[2], &iv)
- || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
- || !enif_inspect_iolist_as_binary(env, argv[4], &in)
- || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) {
- return enif_make_badarg(env);
- }
-
- /* Use cipher_type some day. Must check block_encrypt|decrypt first */
-#if defined(HAVE_GCM)
- if (type == atom_aes_gcm) {
- if (iv.size > 0) {
- ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN;
- ctx_ctrl_set_tag = EVP_CTRL_GCM_SET_TAG;
- if (key.size == 16) cipher = EVP_aes_128_gcm();
- else if (key.size == 24) cipher = EVP_aes_192_gcm();
- else if (key.size == 32) cipher = EVP_aes_256_gcm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (iv.size > 0) {
- ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN;
- if (key.size == 16) cipher = EVP_aes_128_ccm();
- else if (key.size == 24) cipher = EVP_aes_192_ccm();
- else if (key.size == 32) cipher = EVP_aes_256_ccm();
- else enif_make_badarg(env);
- } else
- enif_make_badarg(env);
- } else
-#endif
-#if defined(HAVE_CHACHA20_POLY1305)
- if (type == atom_chacha20_poly1305) {
- if ((key.size == 32)
- && (1 <= iv.size && iv.size <= 16)
- && tag.size == 16
- ) {
- ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN;
- ctx_ctrl_set_tag = EVP_CTRL_AEAD_SET_TAG;
- cipher = EVP_chacha20_poly1305();
- } else enif_make_badarg(env);
- } else
-#endif
- return enif_raise_exception(env, atom_notsup);
-
- outp = enif_make_new_binary(env, in.size, &out);
-
- ctx = EVP_CIPHER_CTX_new();
- if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err;
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err;
-
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag.size, tag.data) != 1) goto out_err;
- }
-#endif
-
- if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err;
-
-#if defined(HAVE_CCM)
- if (type == atom_aes_ccm) {
- if (1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, in.size)) goto out_err;
- }
-#endif
-
- if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err;
- if (EVP_DecryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err;
-
-#if defined(HAVE_GCM) || defined(HAVE_CHACHA20_POLY1305)
- if (type == atom_aes_gcm) {
- if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, tag.size, tag.data) != 1) goto out_err;
- if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1) goto out_err;
- }
-#endif
- EVP_CIPHER_CTX_free(ctx);
-
- CONSUME_REDS(env, in);
- return out;
-
-out_err:
- EVP_CIPHER_CTX_free(ctx);
- return atom_error;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-#ifdef HAVE_GCM_EVP_DECRYPT_BUG
-static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Type,Key,Iv,AAD,In,Tag) */
- GCM128_CONTEXT *ctx;
- ErlNifBinary key, iv, aad, in, tag;
- AES_KEY aes_key;
- unsigned char *outp;
- ERL_NIF_TERM out;
-
- if (!enif_inspect_iolist_as_binary(env, argv[1], &key)
- || AES_set_encrypt_key(key.data, key.size*8, &aes_key) != 0
- || !enif_inspect_binary(env, argv[2], &iv) || iv.size == 0
- || !enif_inspect_iolist_as_binary(env, argv[3], &aad)
- || !enif_inspect_iolist_as_binary(env, argv[4], &in)
- || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) {
- return enif_make_badarg(env);
- }
-
- if (!(ctx = CRYPTO_gcm128_new(&aes_key, (block128_f)AES_encrypt)))
- return atom_error;
-
- CRYPTO_gcm128_setiv(ctx, iv.data, iv.size);
-
- if (CRYPTO_gcm128_aad(ctx, aad.data, aad.size))
- goto out_err;
-
- outp = enif_make_new_binary(env, in.size, &out);
-
- /* decrypt */
- if (CRYPTO_gcm128_decrypt(ctx, in.data, outp, in.size))
- goto out_err;
-
- /* calculate and check the tag */
- if (CRYPTO_gcm128_finish(ctx, tag.data, tag.size))
- goto out_err;
-
- CRYPTO_gcm128_release(ctx);
- CONSUME_REDS(env, in);
-
- return out;
-
-out_err:
- CRYPTO_gcm128_release(ctx);
- return atom_error;
-}
-#endif /* HAVE_GCM_EVP_DECRYPT_BUG */
-
-
-static ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key, IV) */
-#if defined(HAVE_CHACHA20)
- ErlNifBinary key_bin, ivec_bin;
- struct evp_cipher_ctx *ctx;
- const EVP_CIPHER *cipher;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
- || !enif_inspect_binary(env, argv[1], &ivec_bin)
- || key_bin.size != 32
- || ivec_bin.size != 16) {
- return enif_make_badarg(env);
- }
-
- cipher = EVP_chacha20();
-
- ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- ctx->ctx = EVP_CIPHER_CTX_new();
-
-
- EVP_CipherInit_ex(ctx->ctx, cipher, NULL,
- key_bin.data, ivec_bin.data, 1);
- EVP_CIPHER_CTX_set_padding(ctx->ctx, 0);
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
- return ret;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-};
-
-static ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (State, Data) */
-#if defined(HAVE_CHACHA20)
- struct evp_cipher_ctx *ctx, *new_ctx;
- ErlNifBinary data_bin;
- ERL_NIF_TERM ret, cipher_term;
- unsigned char *out;
- int outl = 0;
-
- if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx)
- || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
- return enif_make_badarg(env);
- }
- new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx));
- new_ctx->ctx = EVP_CIPHER_CTX_new();
- EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx);
- out = enif_make_new_binary(env, data_bin.size, &cipher_term);
- EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size);
- ASSERT(outl == data_bin.size);
-
- ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term);
- enif_release_resource(new_ctx);
- CONSUME_REDS(env,data_bin);
- return ret;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-};
-
-
-static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Bytes) */
- unsigned bytes;
- unsigned char* data;
- ERL_NIF_TERM ret;
-
- if (!enif_get_uint(env, argv[0], &bytes)) {
- return enif_make_badarg(env);
- }
- data = enif_make_new_binary(env, bytes, &ret);
- if ( RAND_bytes(data, bytes) != 1) {
- return atom_false;
- }
- ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes);
- return ret;
-}
-
-
-static int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
-{
- ErlNifBinary bin;
- int sz;
- if (!enif_inspect_binary(env,term,&bin)) {
- return 0;
- }
- ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
- sz = bin.size - 4;
- if (sz < 0 || get_int32(bin.data) != sz) {
- return 0;
- }
- *bnp = BN_bin2bn(bin.data+4, sz, NULL);
- return 1;
-}
-
-static int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
-{
- ErlNifBinary bin;
- if (!enif_inspect_binary(env,term,&bin)) {
- return 0;
- }
- ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
- *bnp = BN_bin2bn(bin.data, bin.size, NULL);
- return 1;
-}
-
-static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn)
-{
- int bn_len;
- unsigned char *bin_ptr;
- ERL_NIF_TERM term;
-
- /* Copy the bignum into an erlang binary. */
- bn_len = BN_num_bytes(bn);
- bin_ptr = enif_make_new_binary(env, bn_len, &term);
- BN_bn2bin(bn, bin_ptr);
-
- return term;
-}
-
-static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Range) */
- BIGNUM *bn_range, *bn_rand;
- ERL_NIF_TERM ret;
-
- if(!get_bn_from_bin(env, argv[0], &bn_range)) {
- return enif_make_badarg(env);
- }
-
- bn_rand = BN_new();
- if (BN_rand_range(bn_rand, bn_range) != 1) {
- ret = atom_false;
- }
- else {
- ret = bin_from_bn(env, bn_rand);
- }
- BN_free(bn_rand);
- BN_free(bn_range);
- return ret;
-}
-
-static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Lo,Hi) */
- BIGNUM *bn_from = NULL, *bn_to, *bn_rand;
- unsigned char* data;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- if (!get_bn_from_mpint(env, argv[0], &bn_from)
- || !get_bn_from_mpint(env, argv[1], &bn_rand)) {
- if (bn_from) BN_free(bn_from);
- return enif_make_badarg(env);
- }
-
- bn_to = BN_new();
- BN_sub(bn_to, bn_rand, bn_from);
- BN_pseudo_rand_range(bn_rand, bn_to);
- BN_add(bn_rand, bn_rand, bn_from);
- dlen = BN_num_bytes(bn_rand);
- data = enif_make_new_binary(env, dlen+4, &ret);
- put_int32(data, dlen);
- BN_bn2bin(bn_rand, data+4);
- ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen);
- BN_free(bn_rand);
- BN_free(bn_from);
- BN_free(bn_to);
- return ret;
-}
-
-static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Base,Exponent,Modulo,bin_hdr) */
- BIGNUM *bn_base=NULL, *bn_exponent=NULL, *bn_modulo=NULL, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- unsigned bin_hdr; /* return type: 0=plain binary, 4: mpint */
- unsigned extra_byte;
- ERL_NIF_TERM ret;
-
- if (!get_bn_from_bin(env, argv[0], &bn_base)
- || !get_bn_from_bin(env, argv[1], &bn_exponent)
- || !get_bn_from_bin(env, argv[2], &bn_modulo)
- || !enif_get_uint(env,argv[3],&bin_hdr) || (bin_hdr & ~4)) {
-
- if (bn_base) BN_free(bn_base);
- if (bn_exponent) BN_free(bn_exponent);
- if (bn_modulo) BN_free(bn_modulo);
- return enif_make_badarg(env);
- }
- bn_result = BN_new();
- bn_ctx = BN_CTX_new();
- BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx);
- dlen = BN_num_bytes(bn_result);
- extra_byte = bin_hdr && BN_is_bit_set(bn_result, dlen*8-1);
- ptr = enif_make_new_binary(env, bin_hdr+extra_byte+dlen, &ret);
- if (bin_hdr) {
- put_int32(ptr, extra_byte+dlen);
- ptr[4] = 0; /* extra zeroed byte to ensure a positive mpint */
- ptr += bin_hdr + extra_byte;
- }
- BN_bn2bin(bn_result, ptr);
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
- BN_free(bn_modulo);
- BN_free(bn_exponent);
- BN_free(bn_base);
- return ret;
-}
-
-static void init_digest_types(ErlNifEnv* env)
-{
- struct digest_type_t* p = digest_types;
-
- for (p = digest_types; p->type.str; p++) {
- p->type.atom = enif_make_atom(env, p->type.str);
- if (p->md.funcp)
- p->md.p = p->md.funcp();
- }
- p->type.atom = atom_false; /* end marker */
-}
-
-static void init_cipher_types(ErlNifEnv* env)
-{
- struct cipher_type_t* p = cipher_types;
-
- for (p = cipher_types; p->type.str; p++) {
- p->type.atom = enif_make_atom(env, p->type.str);
- if (p->cipher.funcp)
- p->cipher.p = p->cipher.funcp();
- }
- p->type.atom = atom_false; /* end marker */
-}
-
-static struct digest_type_t* get_digest_type(ERL_NIF_TERM type)
-{
- struct digest_type_t* p = NULL;
- for (p = digest_types; p->type.atom != atom_false; p++) {
- if (type == p->type.atom) {
- return p;
- }
- }
- return NULL;
-}
-
-static struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len)
-{
- struct cipher_type_t* p = NULL;
- for (p = cipher_types; p->type.atom != atom_false; p++) {
- if (type == p->type.atom && (!p->key_len || key_len == p->key_len)) {
- return p;
- }
- }
- return NULL;
-}
-
-
-static ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Data1, Data2) */
- ErlNifBinary d1, d2;
- unsigned char* ret_ptr;
- int i;
- ERL_NIF_TERM ret;
-
- if (!enif_inspect_iolist_as_binary(env,argv[0], &d1)
- || !enif_inspect_iolist_as_binary(env,argv[1], &d2)
- || d1.size != d2.size) {
- return enif_make_badarg(env);
- }
- ret_ptr = enif_make_new_binary(env, d1.size, &ret);
-
- for (i=0; i<d1.size; i++) {
- ret_ptr[i] = d1.data[i] ^ d2.data[i];
- }
- CONSUME_REDS(env,d1);
- return ret;
-}
-
-static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Key) */
-#ifndef OPENSSL_NO_RC4
- ErlNifBinary key;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env,argv[0], &key)) {
- return enif_make_badarg(env);
- }
- RC4_set_key((RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret),
- key.size, key.data);
- return ret;
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (State, Data) */
-#ifndef OPENSSL_NO_RC4
- ErlNifBinary state, data;
- RC4_KEY* rc4_key;
- ERL_NIF_TERM new_state, new_data;
-
- CHECK_NO_FIPS_MODE();
-
- if (!enif_inspect_iolist_as_binary(env,argv[0], &state)
- || state.size != sizeof(RC4_KEY)
- || !enif_inspect_iolist_as_binary(env,argv[1], &data)) {
- return enif_make_badarg(env);
- }
- rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state);
- memcpy(rc4_key, state.data, sizeof(RC4_KEY));
- RC4(rc4_key, data.size, data.data,
- enif_make_new_binary(env, data.size, &new_data));
- CONSUME_REDS(env,data);
- return enif_make_tuple2(env,new_state,new_data);
-#else
- return enif_raise_exception(env, atom_notsup);
-#endif
-}
-
-static int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
-{
- /* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */
- ERL_NIF_TERM head, tail;
- BIGNUM *e, *n, *d;
- BIGNUM *p, *q;
- BIGNUM *dmp1, *dmq1, *iqmp;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &e)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &n)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &d)) {
- return 0;
- }
- (void) RSA_set0_key(rsa, n, e, d);
- if (enif_is_empty_list(env, tail)) {
- return 1;
- }
- if (!enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &q)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dmp1)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dmq1)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &iqmp)
- || !enif_is_empty_list(env, tail)) {
- return 0;
- }
- (void) RSA_set0_factors(rsa, p, q);
- (void) RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
- return 1;
-}
-
-
-static int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
-{
- /* key=[E,N] */
- ERL_NIF_TERM head, tail;
- BIGNUM *e, *n;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &e)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &n)
- || !enif_is_empty_list(env, tail)) {
- return 0;
- }
-
- (void) RSA_set0_key(rsa, n, e, NULL);
- return 1;
-}
-
-#ifdef HAVE_EDDSA
- static int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey)
-{
- /* key=[K] */
- ERL_NIF_TERM head, tail, tail2, algo;
- ErlNifBinary bin;
- int type;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !enif_inspect_binary(env, head, &bin)
- || !enif_get_list_cell(env, tail, &algo, &tail2)
- || !enif_is_empty_list(env, tail2)) {
- return 0;
- }
- if (algo == atom_ed25519) type = EVP_PKEY_ED25519;
- else if (algo == atom_ed448) type = EVP_PKEY_ED448;
- else
- return 0;
-
- if (public)
- *pkey = EVP_PKEY_new_raw_public_key(type, NULL, bin.data, bin.size);
- else
- *pkey = EVP_PKEY_new_raw_private_key(type, NULL, bin.data, bin.size);
-
- if (!pkey)
- return 0;
- return 1;
-}
-#endif
-
-static int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
-{
- /* key=[P,Q,G,KEY] */
- ERL_NIF_TERM head, tail;
- BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL;
- BIGNUM *dummy_pub_key, *priv_key = NULL;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_q)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_g)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &priv_key)
- || !enif_is_empty_list(env,tail)) {
- if (dsa_p) BN_free(dsa_p);
- if (dsa_q) BN_free(dsa_q);
- if (dsa_g) BN_free(dsa_g);
- if (priv_key) BN_free(priv_key);
- return 0;
- }
-
- /* Note: DSA_set0_key() does not allow setting only the
- * private key, although DSA_sign() does not use the
- * public key. Work around this limitation by setting
- * the public key to a copy of the private key.
- */
- dummy_pub_key = BN_dup(priv_key);
-
- DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g);
- DSA_set0_key(dsa, dummy_pub_key, priv_key);
- return 1;
-}
-
-
-static int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
-{
- /* key=[P, Q, G, Y] */
- ERL_NIF_TERM head, tail;
- BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_y = NULL;
-
- if (!enif_get_list_cell(env, key, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_q)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_g)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dsa_y)
- || !enif_is_empty_list(env,tail)) {
- if (dsa_p) BN_free(dsa_p);
- if (dsa_q) BN_free(dsa_q);
- if (dsa_g) BN_free(dsa_g);
- if (dsa_y) BN_free(dsa_y);
- return 0;
- }
-
- DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g);
- DSA_set0_key(dsa, dsa_y, NULL);
- return 1;
-}
-
-/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */
-static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa)
-{
- ERL_NIF_TERM result[8];
- const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
-
- /* Return at least [E,N,D] */
- n = NULL; e = NULL; d = NULL;
- RSA_get0_key(rsa, &n, &e, &d);
-
- result[0] = bin_from_bn(env, e); // Exponent E
- result[1] = bin_from_bn(env, n); // Modulus N = p*q
- result[2] = bin_from_bn(env, d); // Exponent D
-
- /* Check whether the optional additional parameters are available */
- p = NULL; q = NULL;
- RSA_get0_factors(rsa, &p, &q);
- dmp1 = NULL; dmq1 = NULL; iqmp = NULL;
- RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
-
- if (p && q && dmp1 && dmq1 && iqmp) {
- result[3] = bin_from_bn(env, p); // Factor p
- result[4] = bin_from_bn(env, q); // Factor q
- result[5] = bin_from_bn(env, dmp1); // D mod (p-1)
- result[6] = bin_from_bn(env, dmq1); // D mod (q-1)
- result[7] = bin_from_bn(env, iqmp); // (1/q) mod p
-
- return enif_make_list_from_array(env, result, 8);
- } else {
- return enif_make_list_from_array(env, result, 3);
- }
-}
-
-static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt)
-{
- ErlNifEnv *env = BN_GENCB_get_arg(ctxt);
-
- if (!enif_is_current_process_alive(env)) {
- return 0;
- } else {
- return 1;
- }
-}
-
-static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (ModulusSize, PublicExponent) */
- int modulus_bits;
- BIGNUM *pub_exp, *three;
- RSA *rsa;
- int success;
- ERL_NIF_TERM result;
- BN_GENCB *intr_cb;
-#ifndef HAVE_OPAQUE_BN_GENCB
- BN_GENCB intr_cb_buf;
-#endif
-
- if (!enif_get_int(env, argv[0], &modulus_bits) || modulus_bits < 256) {
- return enif_make_badarg(env);
- }
-
- if (!get_bn_from_bin(env, argv[1], &pub_exp)) {
- return enif_make_badarg(env);
- }
-
- /* Make sure the public exponent is large enough (at least 3).
- * Without this, RSA_generate_key_ex() can run forever. */
- three = BN_new();
- BN_set_word(three, 3);
- success = BN_cmp(pub_exp, three);
- BN_free(three);
- if (success < 0) {
- BN_free(pub_exp);
- return enif_make_badarg(env);
- }
-
- /* For large keys, prime generation can take many seconds. Set up
- * the callback which we use to test whether the process has been
- * interrupted. */
-#ifdef HAVE_OPAQUE_BN_GENCB
- intr_cb = BN_GENCB_new();
-#else
- intr_cb = &intr_cb_buf;
-#endif
- BN_GENCB_set(intr_cb, check_erlang_interrupt, env);
-
- rsa = RSA_new();
- success = RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb);
- BN_free(pub_exp);
-
-#ifdef HAVE_OPAQUE_BN_GENCB
- BN_GENCB_free(intr_cb);
-#endif
-
- if (!success) {
- RSA_free(rsa);
- return atom_error;
- }
-
- result = put_rsa_private_key(env, rsa);
- RSA_free(rsa);
-
- return result;
-}
-
-static ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- /* RSA key generation can take a long time (>1 sec for a large
- * modulus), so schedule it as a CPU-bound operation. */
- return enif_schedule_nif(env, "rsa_generate_key",
- ERL_NIF_DIRTY_JOB_CPU_BOUND,
- rsa_generate_key, argc, argv);
-}
-
-static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (PrivKey|undefined, DHParams=[P,G], Mpint, Len|0) */
- DH *dh_params = NULL;
- int mpint; /* 0 or 4 */
-
- {
- ERL_NIF_TERM head, tail;
- BIGNUM
- *dh_p = NULL,
- *dh_g = NULL,
- *priv_key_in = NULL;
- unsigned long
- len = 0;
-
- if (!(get_bn_from_bin(env, argv[0], &priv_key_in)
- || argv[0] == atom_undefined)
- || !enif_get_list_cell(env, argv[1], &head, &tail)
- || !get_bn_from_bin(env, head, &dh_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dh_g)
- || !enif_is_empty_list(env, tail)
- || !enif_get_int(env, argv[2], &mpint) || (mpint & ~4)
- || !enif_get_ulong(env, argv[3], &len)
-
- /* Load dh_params with values to use by the generator.
- Mem mgmnt transfered from dh_p etc to dh_params */
- || !(dh_params = DH_new())
- || (priv_key_in && !DH_set0_key(dh_params, NULL, priv_key_in))
- || !DH_set0_pqg(dh_params, dh_p, NULL, dh_g)
- ) {
- if (priv_key_in) BN_free(priv_key_in);
- if (dh_p) BN_free(dh_p);
- if (dh_g) BN_free(dh_g);
- if (dh_params) DH_free(dh_params);
- return enif_make_badarg(env);
- }
-
- if (len) {
- if (len < BN_num_bits(dh_p))
- DH_set_length(dh_params, len);
- else {
- if (priv_key_in) BN_free(priv_key_in);
- if (dh_p) BN_free(dh_p);
- if (dh_g) BN_free(dh_g);
- if (dh_params) DH_free(dh_params);
- return enif_make_badarg(env);
- }
- }
- }
-
-#ifdef HAS_EVP_PKEY_CTX
- {
- EVP_PKEY_CTX *ctx;
- EVP_PKEY *dhkey, *params;
- int success;
-
- params = EVP_PKEY_new();
- success = EVP_PKEY_set1_DH(params, dh_params); /* set the key referenced by params to dh_params... */
- DH_free(dh_params); /* ...dh_params (and params) must be freed */
- if (!success) return atom_error;
-
- ctx = EVP_PKEY_CTX_new(params, NULL);
- EVP_PKEY_free(params);
- if (!ctx) {
- return atom_error;
- }
-
- if (!EVP_PKEY_keygen_init(ctx)) {
- /* EVP_PKEY_CTX_free(ctx); */
- return atom_error;
- }
-
- dhkey = EVP_PKEY_new();
- if (!EVP_PKEY_keygen(ctx, &dhkey)) { /* "performs a key generation operation, the ... */
- /*... generated key is written to ppkey." (=last arg) */
- /* EVP_PKEY_CTX_free(ctx); */
- /* EVP_PKEY_free(dhkey); */
- return atom_error;
- }
-
- dh_params = EVP_PKEY_get1_DH(dhkey); /* return the referenced key. dh_params and dhkey must be freed */
- EVP_PKEY_free(dhkey);
- if (!dh_params) {
- /* EVP_PKEY_CTX_free(ctx); */
- return atom_error;
- }
- EVP_PKEY_CTX_free(ctx);
- }
-#else
- if (!DH_generate_key(dh_params)) return atom_error;
-#endif
- {
- unsigned char *pub_ptr, *prv_ptr;
- int pub_len, prv_len;
- ERL_NIF_TERM ret_pub, ret_prv;
- const BIGNUM *pub_key_gen, *priv_key_gen;
-
- DH_get0_key(dh_params,
- &pub_key_gen, &priv_key_gen); /* Get pub_key_gen and priv_key_gen.
- "The values point to the internal representation of
- the public key and private key values. This memory
- should not be freed directly." says man */
- pub_len = BN_num_bytes(pub_key_gen);
- prv_len = BN_num_bytes(priv_key_gen);
- pub_ptr = enif_make_new_binary(env, pub_len+mpint, &ret_pub);
- prv_ptr = enif_make_new_binary(env, prv_len+mpint, &ret_prv);
- if (mpint) {
- put_int32(pub_ptr, pub_len); pub_ptr += 4;
- put_int32(prv_ptr, prv_len); prv_ptr += 4;
- }
- BN_bn2bin(pub_key_gen, pub_ptr);
- BN_bn2bin(priv_key_gen, prv_ptr);
- ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr, pub_len);
- ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr, prv_len);
-
- DH_free(dh_params);
-
- return enif_make_tuple2(env, ret_pub, ret_prv);
- }
-}
-
-static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */
- BIGNUM *other_pub_key = NULL,
- *dh_p = NULL,
- *dh_g = NULL;
- DH *dh_priv = DH_new();
-
- /* Check the arguments and get
- my private key (dh_priv),
- the peer's public key (other_pub_key),
- the parameters p & q
- */
-
- {
- BIGNUM *dummy_pub_key = NULL,
- *priv_key = NULL;
- ERL_NIF_TERM head, tail;
-
- if (!get_bn_from_bin(env, argv[0], &other_pub_key)
- || !get_bn_from_bin(env, argv[1], &priv_key)
- || !enif_get_list_cell(env, argv[2], &head, &tail)
- || !get_bn_from_bin(env, head, &dh_p)
- || !enif_get_list_cell(env, tail, &head, &tail)
- || !get_bn_from_bin(env, head, &dh_g)
- || !enif_is_empty_list(env, tail)
-
- /* Note: DH_set0_key() does not allow setting only the
- * private key, although DH_compute_key() does not use the
- * public key. Work around this limitation by setting
- * the public key to a copy of the private key.
- */
- || !(dummy_pub_key = BN_dup(priv_key))
- || !DH_set0_key(dh_priv, dummy_pub_key, priv_key)
- || !DH_set0_pqg(dh_priv, dh_p, NULL, dh_g)
- ) {
- if (dh_p) BN_free(dh_p);
- if (dh_g) BN_free(dh_g);
- if (other_pub_key) BN_free(other_pub_key);
- if (dummy_pub_key) BN_free(dummy_pub_key);
- if (priv_key) BN_free(priv_key);
- return enif_make_badarg(env);
- }
- }
- {
- ErlNifBinary ret_bin;
- int size;
-
- enif_alloc_binary(DH_size(dh_priv), &ret_bin);
- size = DH_compute_key(ret_bin.data, other_pub_key, dh_priv);
- BN_free(other_pub_key);
- DH_free(dh_priv);
- if (size<=0) {
- enif_release_binary(&ret_bin);
- return atom_error;
- }
-
- if (size != ret_bin.size) enif_realloc_binary(&ret_bin, size);
- return enif_make_binary(env, &ret_bin);
- }
-}
-
-
-static ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Multiplier, Verifier, Generator, Exponent, Prime) */
- BIGNUM *bn_verifier = NULL;
- BIGNUM *bn_exponent = NULL, *bn_generator = NULL, *bn_prime = NULL, *bn_multiplier = NULL, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!get_bn_from_bin(env, argv[0], &bn_multiplier)
- || !get_bn_from_bin(env, argv[1], &bn_verifier)
- || !get_bn_from_bin(env, argv[2], &bn_generator)
- || !get_bn_from_bin(env, argv[3], &bn_exponent)
- || !get_bn_from_bin(env, argv[4], &bn_prime)) {
- if (bn_multiplier) BN_free(bn_multiplier);
- if (bn_verifier) BN_free(bn_verifier);
- if (bn_generator) BN_free(bn_generator);
- if (bn_exponent) BN_free(bn_exponent);
- if (bn_prime) BN_free(bn_prime);
- return enif_make_badarg(env);
- }
-
- bn_result = BN_new();
- bn_ctx = BN_CTX_new();
-
- /* B = k*v + g^b % N */
-
- /* k * v */
- BN_mod_mul(bn_multiplier, bn_multiplier, bn_verifier, bn_prime, bn_ctx);
-
- /* g^b % N */
- BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx);
-
- /* k*v + g^b % N */
- BN_mod_add(bn_result, bn_result, bn_multiplier, bn_prime, bn_ctx);
-
- /* check that B % N != 0, reuse bn_multiplier */
- BN_nnmod(bn_multiplier, bn_result, bn_prime, bn_ctx);
- if (BN_is_zero(bn_multiplier)) {
- ret = atom_error;
- } else {
- dlen = BN_num_bytes(bn_result);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn_result, ptr);
- }
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
- BN_free(bn_prime);
- BN_free(bn_generator);
- BN_free(bn_multiplier);
- BN_free(bn_exponent);
- BN_free(bn_verifier);
- return ret;
-}
-
-static ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (a, u, B, Multiplier, Prime, Exponent, Generator) */
-/*
- <premaster secret> = (B - (k * g^x)) ^ (a + (u * x)) % N
-*/
- BIGNUM *bn_exponent = NULL, *bn_a = NULL;
- BIGNUM *bn_u = NULL, *bn_multiplier = NULL, *bn_exp2,
- *bn_base, *bn_prime = NULL, *bn_generator = NULL,
- *bn_B = NULL, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!get_bn_from_bin(env, argv[0], &bn_a)
- || !get_bn_from_bin(env, argv[1], &bn_u)
- || !get_bn_from_bin(env, argv[2], &bn_B)
- || !get_bn_from_bin(env, argv[3], &bn_multiplier)
- || !get_bn_from_bin(env, argv[4], &bn_generator)
- || !get_bn_from_bin(env, argv[5], &bn_exponent)
- || !get_bn_from_bin(env, argv[6], &bn_prime))
- {
- if (bn_exponent) BN_free(bn_exponent);
- if (bn_a) BN_free(bn_a);
- if (bn_u) BN_free(bn_u);
- if (bn_B) BN_free(bn_B);
- if (bn_multiplier) BN_free(bn_multiplier);
- if (bn_generator) BN_free(bn_generator);
- if (bn_prime) BN_free(bn_prime);
- return enif_make_badarg(env);
- }
-
- bn_ctx = BN_CTX_new();
- bn_result = BN_new();
-
- /* check that B % N != 0 */
- BN_nnmod(bn_result, bn_B, bn_prime, bn_ctx);
- if (BN_is_zero(bn_result)) {
- BN_free(bn_exponent);
- BN_free(bn_a);
- BN_free(bn_generator);
- BN_free(bn_prime);
- BN_free(bn_u);
- BN_free(bn_B);
- BN_CTX_free(bn_ctx);
-
- return atom_error;
- }
-
- /* (B - (k * g^x)) */
- bn_base = BN_new();
- BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx);
- BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx);
- BN_mod_sub(bn_base, bn_B, bn_result, bn_prime, bn_ctx);
-
- /* a + (u * x) */
- bn_exp2 = BN_new();
- BN_mul(bn_result, bn_u, bn_exponent, bn_ctx);
- BN_add(bn_exp2, bn_a, bn_result);
-
- /* (B - (k * g^x)) ^ (a + (u * x)) % N */
- BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx);
-
- dlen = BN_num_bytes(bn_result);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn_result, ptr);
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
-
- BN_free(bn_multiplier);
- BN_free(bn_exp2);
- BN_free(bn_u);
- BN_free(bn_exponent);
- BN_free(bn_a);
- BN_free(bn_B);
- BN_free(bn_base);
- BN_free(bn_generator);
- BN_free(bn_prime);
- return ret;
-}
-
-static ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Verifier, b, u, A, Prime) */
-/*
- <premaster secret> = (A * v^u) ^ b % N
-*/
- BIGNUM *bn_b = NULL, *bn_verifier = NULL;
- BIGNUM *bn_prime = NULL, *bn_A = NULL, *bn_u = NULL, *bn_base, *bn_result;
- BN_CTX *bn_ctx;
- unsigned char* ptr;
- unsigned dlen;
- ERL_NIF_TERM ret;
-
- CHECK_NO_FIPS_MODE();
-
- if (!get_bn_from_bin(env, argv[0], &bn_verifier)
- || !get_bn_from_bin(env, argv[1], &bn_b)
- || !get_bn_from_bin(env, argv[2], &bn_u)
- || !get_bn_from_bin(env, argv[3], &bn_A)
- || !get_bn_from_bin(env, argv[4], &bn_prime))
- {
- if (bn_verifier) BN_free(bn_verifier);
- if (bn_b) BN_free(bn_b);
- if (bn_u) BN_free(bn_u);
- if (bn_A) BN_free(bn_A);
- if (bn_prime) BN_free(bn_prime);
- return enif_make_badarg(env);
- }
-
- bn_ctx = BN_CTX_new();
- bn_result = BN_new();
-
- /* check that A % N != 0 */
- BN_nnmod(bn_result, bn_A, bn_prime, bn_ctx);
- if (BN_is_zero(bn_result)) {
- BN_free(bn_b);
- BN_free(bn_verifier);
- BN_free(bn_prime);
- BN_free(bn_A);
- BN_CTX_free(bn_ctx);
-
- return atom_error;
- }
-
- /* (A * v^u) */
- bn_base = BN_new();
- BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx);
- BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx);
-
- /* (A * v^u) ^ b % N */
- BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx);
-
- dlen = BN_num_bytes(bn_result);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn_result, ptr);
- BN_free(bn_result);
- BN_CTX_free(bn_ctx);
-
- BN_free(bn_u);
- BN_free(bn_base);
- BN_free(bn_verifier);
- BN_free(bn_prime);
- BN_free(bn_A);
- BN_free(bn_b);
- return ret;
-}
-
-#if defined(HAVE_EC)
-static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg)
-{
- EC_KEY *key = NULL;
- int c_arity = -1;
- const ERL_NIF_TERM* curve;
- ErlNifBinary seed;
- BIGNUM *p = NULL;
- BIGNUM *a = NULL;
- BIGNUM *b = NULL;
- BIGNUM *bn_order = NULL;
- BIGNUM *cofactor = NULL;
- EC_GROUP *group = NULL;
- EC_POINT *point = NULL;
-
- /* {Field, Prime, Point, Order, CoFactor} = Curve */
- if (enif_get_tuple(env,curve_arg,&c_arity,&curve)
- && c_arity == 5
- && get_bn_from_bin(env, curve[3], &bn_order)
- && (curve[4] != atom_none && get_bn_from_bin(env, curve[4], &cofactor))) {
-
- int f_arity = -1;
- const ERL_NIF_TERM* field;
- int p_arity = -1;
- const ERL_NIF_TERM* prime;
-
- long field_bits;
-
- /* {A, B, Seed} = Prime */
- if (!enif_get_tuple(env,curve[1],&p_arity,&prime)
- || !get_bn_from_bin(env, prime[0], &a)
- || !get_bn_from_bin(env, prime[1], &b))
- goto out_err;
-
- if (!enif_get_tuple(env,curve[0],&f_arity,&field))
- goto out_err;
-
- if (f_arity == 2 && field[0] == atom_prime_field) {
- /* {prime_field, Prime} */
-
- if (!get_bn_from_bin(env, field[1], &p))
- goto out_err;
-
- if (BN_is_negative(p) || BN_is_zero(p))
- goto out_err;
-
- field_bits = BN_num_bits(p);
- if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
- goto out_err;
-
- /* create the EC_GROUP structure */
- group = EC_GROUP_new_curve_GFp(p, a, b, NULL);
-
- } else if (f_arity == 3 && field[0] == atom_characteristic_two_field) {
-#if defined(OPENSSL_NO_EC2M)
- enif_raise_exception(env, atom_notsup);
- goto out_err;
-#else
- /* {characteristic_two_field, M, Basis} */
-
- int b_arity = -1;
- const ERL_NIF_TERM* basis;
- unsigned int k1, k2, k3;
-
- if ((p = BN_new()) == NULL)
- goto out_err;
-
- if (!enif_get_long(env, field[1], &field_bits)
- || field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
- goto out_err;
-
- if (enif_get_tuple(env,field[2],&b_arity,&basis)) {
- if (b_arity == 2
- && basis[0] == atom_tpbasis
- && enif_get_uint(env, basis[1], &k1)) {
- /* {tpbasis, k} = Basis */
-
- if (!(field_bits > k1 && k1 > 0))
- goto out_err;
-
- /* create the polynomial */
- if (!BN_set_bit(p, (int)field_bits)
- || !BN_set_bit(p, (int)k1)
- || !BN_set_bit(p, 0))
- goto out_err;
-
- } else if (b_arity == 4
- && basis[0] == atom_ppbasis
- && enif_get_uint(env, basis[1], &k1)
- && enif_get_uint(env, basis[2], &k2)
- && enif_get_uint(env, basis[3], &k3)) {
- /* {ppbasis, k1, k2, k3} = Basis */
-
- if (!(field_bits > k3 && k3 > k2 && k2 > k1 && k1 > 0))
- goto out_err;
-
- /* create the polynomial */
- if (!BN_set_bit(p, (int)field_bits)
- || !BN_set_bit(p, (int)k1)
- || !BN_set_bit(p, (int)k2)
- || !BN_set_bit(p, (int)k3)
- || !BN_set_bit(p, 0))
- goto out_err;
-
- } else
- goto out_err;
- } else if (field[2] == atom_onbasis) {
- /* onbasis = Basis */
- /* no parameters */
- goto out_err;
-
- } else
- goto out_err;
-
- group = EC_GROUP_new_curve_GF2m(p, a, b, NULL);
-#endif
- } else
- goto out_err;
-
- if (!group)
- goto out_err;
-
- if (enif_inspect_binary(env, prime[2], &seed)) {
- EC_GROUP_set_seed(group, seed.data, seed.size);
- }
-
- if (!term2point(env, curve[2], group, &point))
- goto out_err;
-
- if (BN_is_negative(bn_order)
- || BN_is_zero(bn_order)
- || BN_num_bits(bn_order) > (int)field_bits + 1)
- goto out_err;
-
- if (!EC_GROUP_set_generator(group, point, bn_order, cofactor))
- goto out_err;
-
- EC_GROUP_set_asn1_flag(group, 0x0);
-
- key = EC_KEY_new();
- if (!key)
- goto out_err;
- EC_KEY_set_group(key, group);
- }
- else {
- goto out_err;
- }
-
-
- goto out;
-
-out_err:
- if (key) EC_KEY_free(key);
- key = NULL;
-
-out:
- /* some OpenSSL structures are mem-dup'ed into the key,
- so we have to free our copies here */
- if (p) BN_free(p);
- if (a) BN_free(a);
- if (b) BN_free(b);
- if (bn_order) BN_free(bn_order);
- if (cofactor) BN_free(cofactor);
- if (group) EC_GROUP_free(group);
- if (point) EC_POINT_free(point);
-
- return key;
-}
-
-
-static ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn)
-{
- unsigned dlen;
- unsigned char* ptr;
- ERL_NIF_TERM ret;
-
- if (!bn)
- return atom_undefined;
-
- dlen = BN_num_bytes(bn);
- ptr = enif_make_new_binary(env, dlen, &ret);
- BN_bn2bin(bn, ptr);
- ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen);
- return ret;
-}
-
-static ERL_NIF_TERM point2term(ErlNifEnv* env,
- const EC_GROUP *group,
- const EC_POINT *point,
- point_conversion_form_t form)
-{
- unsigned dlen;
- ErlNifBinary bin;
-
- dlen = EC_POINT_point2oct(group, point, form, NULL, 0, NULL);
- if (dlen == 0)
- return atom_undefined;
-
- if (!enif_alloc_binary(dlen, &bin))
- return enif_make_badarg(env);
-
- if (!EC_POINT_point2oct(group, point, form, bin.data, bin.size, NULL)) {
- enif_release_binary(&bin);
- return enif_make_badarg(env);
- }
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin.data, bin.size);
- return enif_make_binary(env, &bin);
-}
-
-static int term2point(ErlNifEnv* env, ERL_NIF_TERM term,
- EC_GROUP *group, EC_POINT **pptr)
-{
- int ret = 0;
- ErlNifBinary bin;
- EC_POINT *point;
-
- if (!enif_inspect_binary(env,term,&bin)) {
- return 0;
- }
-
- if ((*pptr = point = EC_POINT_new(group)) == NULL) {
- return 0;
- }
-
- /* set the point conversion form */
- EC_GROUP_set_point_conversion_form(group, (point_conversion_form_t)(bin.data[0] & ~0x01));
-
- /* extract the ec point */
- if (!EC_POINT_oct2point(group, point, bin.data, bin.size, NULL)) {
- EC_POINT_free(point);
- *pptr = NULL;
- } else
- ret = 1;
-
- return ret;
-}
-
-static int get_ec_key(ErlNifEnv* env,
- ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub,
- EC_KEY** res)
-{
- EC_KEY *key = NULL;
- BIGNUM *priv_key = NULL;
- EC_POINT *pub_key = NULL;
- EC_GROUP *group = NULL;
-
- if (!(priv == atom_undefined || get_bn_from_bin(env, priv, &priv_key))
- || !(pub == atom_undefined || enif_is_binary(env, pub))) {
- goto out_err;
- }
-
- key = ec_key_new(env, curve);
-
- if (!key) {
- goto out_err;
- }
-
- if (!group)
- group = EC_GROUP_dup(EC_KEY_get0_group(key));
-
- if (term2point(env, pub, group, &pub_key)) {
- if (!EC_KEY_set_public_key(key, pub_key)) {
- goto out_err;
- }
- }
- if (priv != atom_undefined
- && !BN_is_zero(priv_key)) {
- if (!EC_KEY_set_private_key(key, priv_key))
- goto out_err;
-
- /* calculate public key (if necessary) */
- if (EC_KEY_get0_public_key(key) == NULL)
- {
- /* the public key was not included in the SEC1 private
- * key => calculate the public key */
- pub_key = EC_POINT_new(group);
- if (pub_key == NULL
- || !EC_POINT_copy(pub_key, EC_GROUP_get0_generator(group))
- || !EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL)
- || !EC_KEY_set_public_key(key, pub_key))
- goto out_err;
- }
- }
-
- goto out;
-
-out_err:
- if (key) EC_KEY_free(key);
- key = NULL;
-
-out:
- /* some OpenSSL structures are mem-dup'ed into the key,
- so we have to free our copies here */
- if (priv_key) BN_clear_free(priv_key);
- if (pub_key) EC_POINT_free(pub_key);
- if (group) EC_GROUP_free(group);
- if (!key)
- return 0;
- *res = key;
- return 1;
-}
-#endif /* HAVE_EC */
-
-static ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
-#if defined(HAVE_EC)
- EC_KEY *key = NULL;
- const EC_GROUP *group;
- const EC_POINT *public_key;
- ERL_NIF_TERM priv_key;
- ERL_NIF_TERM pub_key = atom_undefined;
-
- if (!get_ec_key(env, argv[0], argv[1], atom_undefined, &key))
- goto badarg;
-
- if (argv[1] == atom_undefined) {
- if (!EC_KEY_generate_key(key))
- goto badarg;
- }
-
- group = EC_KEY_get0_group(key);
- public_key = EC_KEY_get0_public_key(key);
-
- if (group && public_key) {
- pub_key = point2term(env, group, public_key,
- EC_KEY_get_conv_form(key));
- }
- priv_key = bn2term(env, EC_KEY_get0_private_key(key));
- EC_KEY_free(key);
- return enif_make_tuple2(env, pub_key, priv_key);
-
-badarg:
- if (key)
- EC_KEY_free(key);
- return make_badarg_maybe(env);
-#else
- return atom_notsup;
-#endif
-}
-
-/*
- (_OthersPublicKey, _MyPrivateKey)
- (_OthersPublicKey, _MyEC_Point)
-*/
-static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-/* (OtherPublicKey, Curve, My) */
-{
-#if defined(HAVE_EC)
- ERL_NIF_TERM ret;
- unsigned char *p;
- EC_KEY* key = NULL;
- int field_size = 0;
- int i;
- EC_GROUP *group;
- const BIGNUM *priv_key;
- EC_POINT *my_ecpoint = NULL;
- EC_KEY *other_ecdh = NULL;
-
- if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key))
- return make_badarg_maybe(env);
-
- group = EC_GROUP_dup(EC_KEY_get0_group(key));
- priv_key = EC_KEY_get0_private_key(key);
-
- if (!term2point(env, argv[0], group, &my_ecpoint)) {
- goto out_err;
- }
-
- if ((other_ecdh = EC_KEY_new()) == NULL
- || !EC_KEY_set_group(other_ecdh, group)
- || !EC_KEY_set_private_key(other_ecdh, priv_key))
- goto out_err;
-
- field_size = EC_GROUP_get_degree(group);
- if (field_size <= 0)
- goto out_err;
-
- p = enif_make_new_binary(env, (field_size+7)/8, &ret);
- i = ECDH_compute_key(p, (field_size+7)/8, my_ecpoint, other_ecdh, NULL);
-
- if (i < 0)
- goto out_err;
-out:
- if (group) EC_GROUP_free(group);
- if (my_ecpoint) EC_POINT_free(my_ecpoint);
- if (other_ecdh) EC_KEY_free(other_ecdh);
- if (key) EC_KEY_free(key);
-
- return ret;
-
-out_err:
- ret = enif_make_badarg(env);
- goto out;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
- /* (Curve, PeerBin, MyBin) */
-{
-#ifdef HAVE_ED_CURVE_DH
- int type;
- EVP_PKEY_CTX *ctx;
- ErlNifBinary peer_bin, my_bin, key_bin;
- EVP_PKEY *peer_key, *my_key;
- size_t max_size;
-
- if (argv[0] == atom_x25519) type = EVP_PKEY_X25519;
- else if (argv[0] == atom_x448) type = EVP_PKEY_X448;
- else return enif_make_badarg(env);
-
- if (!enif_inspect_binary(env, argv[1], &peer_bin) ||
- !enif_inspect_binary(env, argv[2], &my_bin)) {
- return enif_make_badarg(env);
- }
-
- if (!(my_key = EVP_PKEY_new_raw_private_key(type, NULL, my_bin.data, my_bin.size)) ||
- !(ctx = EVP_PKEY_CTX_new(my_key, NULL))) {
- return enif_make_badarg(env);
- }
-
- if (!EVP_PKEY_derive_init(ctx)) {
- return enif_make_badarg(env);
- }
-
- if (!(peer_key = EVP_PKEY_new_raw_public_key(type, NULL, peer_bin.data, peer_bin.size)) ||
- !EVP_PKEY_derive_set_peer(ctx, peer_key)) {
- return enif_make_badarg(env);
- }
-
- if (!EVP_PKEY_derive(ctx, NULL, &max_size)) {
- return enif_make_badarg(env);
- }
-
- if (!enif_alloc_binary(max_size, &key_bin) ||
- !EVP_PKEY_derive(ctx, key_bin.data, &key_bin.size)) {
- return enif_make_badarg(env);
- }
-
- if (key_bin.size < max_size) {
- size_t actual_size = key_bin.size;
- if (!enif_realloc_binary(&key_bin, actual_size)) {
- return enif_make_badarg(env);
- }
- }
-
- return enif_make_binary(env, &key_bin);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-/* (Curve) */
-{
-#ifdef HAVE_ED_CURVE_DH
- int type;
- EVP_PKEY_CTX *ctx;
- EVP_PKEY *pkey = NULL;
- ERL_NIF_TERM ret_pub, ret_prv;
- size_t key_len;
-
- if (argv[0] == atom_x25519) type = EVP_PKEY_X25519;
- else if (argv[0] == atom_x448) type = EVP_PKEY_X448;
- else return enif_make_badarg(env);
-
- if (!(ctx = EVP_PKEY_CTX_new_id(type, NULL))) return enif_make_badarg(env);
-
- if (!EVP_PKEY_keygen_init(ctx)) return atom_error;
- if (!EVP_PKEY_keygen(ctx, &pkey)) return atom_error;
-
- if (!EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len)) return atom_error;
- if (!EVP_PKEY_get_raw_public_key(pkey,
- enif_make_new_binary(env, key_len, &ret_pub),
- &key_len))
- return atom_error;
-
- if (!EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len)) return atom_error;
- if (!EVP_PKEY_get_raw_private_key(pkey,
- enif_make_new_binary(env, key_len, &ret_prv),
- &key_len))
- return atom_error;
-
- return enif_make_tuple2(env, ret_pub, ret_prv);
-#else
- return atom_notsup;
-#endif
-}
-
-/*================================================================*/
-#define PKEY_BADARG -1
-#define PKEY_NOTSUP 0
-#define PKEY_OK 1
-
-typedef struct PKeyCryptOptions {
- const EVP_MD *rsa_mgf1_md;
- ErlNifBinary rsa_oaep_label;
- const EVP_MD *rsa_oaep_md;
- int rsa_padding;
- const EVP_MD *signature_md;
-} PKeyCryptOptions;
-
-typedef struct PKeySignOptions {
- const EVP_MD *rsa_mgf1_md;
- int rsa_padding;
- int rsa_pss_saltlen;
-} PKeySignOptions;
-
-static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
- const EVP_MD **md)
-{
- struct digest_type_t *digp = NULL;
- *md = NULL;
-
- if (type == atom_none && algorithm == atom_rsa) return PKEY_OK;
-#ifdef HAVE_EDDSA
- if (algorithm == atom_eddsa) return PKEY_OK;
-#endif
- digp = get_digest_type(type);
- if (!digp) return PKEY_BADARG;
- if (!digp->md.p) return PKEY_NOTSUP;
-
- *md = digp->md.p;
- return PKEY_OK;
-}
-
-
-static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
- ERL_NIF_TERM type, ERL_NIF_TERM data,
- unsigned char *md_value, const EVP_MD **mdp,
- unsigned char **tbsp, size_t *tbslenp)
-{
- int i;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
- ErlNifBinary tbs_bin;
- EVP_MD_CTX *mdctx;
- const EVP_MD *md = *mdp;
- unsigned char *tbs = *tbsp;
- size_t tbslen = *tbslenp;
- unsigned int tbsleni;
-
- if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK) {
- return i;
- }
- if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) {
- if (tpl_arity != 2 || tpl_terms[0] != atom_digest
- || !enif_inspect_binary(env, tpl_terms[1], &tbs_bin)
- || (md != NULL && tbs_bin.size != EVP_MD_size(md))) {
- return PKEY_BADARG;
- }
- /* We have a digest (= hashed text) in tbs_bin */
- tbs = tbs_bin.data;
- tbslen = tbs_bin.size;
- } else if (md == NULL) {
- if (!enif_inspect_binary(env, data, &tbs_bin)) {
- return PKEY_BADARG;
- }
- /* md == NULL, that is no hashing because DigestType argument was atom_none */
- tbs = tbs_bin.data;
- tbslen = tbs_bin.size;
- } else {
- if (!enif_inspect_binary(env, data, &tbs_bin)) {
- return PKEY_BADARG;
- }
- /* We have the cleartext in tbs_bin and the hash algo info in md */
- tbs = md_value;
- mdctx = EVP_MD_CTX_create();
- if (!mdctx) {
- return PKEY_BADARG;
- }
- /* Looks well, now hash the plain text into a digest according to md */
- if (EVP_DigestInit_ex(mdctx, md, NULL) <= 0) {
- EVP_MD_CTX_destroy(mdctx);
- return PKEY_BADARG;
- }
- if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) <= 0) {
- EVP_MD_CTX_destroy(mdctx);
- return PKEY_BADARG;
- }
- if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) <= 0) {
- EVP_MD_CTX_destroy(mdctx);
- return PKEY_BADARG;
- }
- tbslen = (size_t)(tbsleni);
- EVP_MD_CTX_destroy(mdctx);
- }
-
- *mdp = md;
- *tbsp = tbs;
- *tbslenp = tbslen;
-
- return PKEY_OK;
-}
-
-
-static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
- const EVP_MD *md, PKeySignOptions *opt)
-{
- ERL_NIF_TERM head, tail;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
- const EVP_MD *opt_md;
- int i;
-
- if (!enif_is_list(env, options)) {
- return PKEY_BADARG;
- }
-
- /* defaults */
- if (algorithm == atom_rsa) {
- opt->rsa_mgf1_md = NULL;
- opt->rsa_padding = RSA_PKCS1_PADDING;
- opt->rsa_pss_saltlen = -2;
- }
-
- if (enif_is_empty_list(env, options)) {
- return PKEY_OK;
- }
-
- if (algorithm == atom_rsa) {
- tail = options;
- while (enif_get_list_cell(env, tail, &head, &tail)) {
- if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) {
- if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->rsa_mgf1_md = opt_md;
- } else if (tpl_terms[0] == atom_rsa_padding) {
- if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
- opt->rsa_padding = RSA_PKCS1_PADDING;
- } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) {
-#ifdef HAVE_RSA_PKCS1_PSS_PADDING
- opt->rsa_padding = RSA_PKCS1_PSS_PADDING;
- if (opt->rsa_mgf1_md == NULL) {
- opt->rsa_mgf1_md = md;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (tpl_terms[1] == atom_rsa_x931_padding) {
- opt->rsa_padding = RSA_X931_PADDING;
- } else if (tpl_terms[1] == atom_rsa_no_padding) {
- opt->rsa_padding = RSA_NO_PADDING;
- } else {
- return PKEY_BADARG;
- }
- } else if (tpl_terms[0] == atom_rsa_pss_saltlen) {
- if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen))
- || opt->rsa_pss_saltlen < -2) {
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-
-#ifdef HAS_ENGINE_SUPPORT
-static int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e)
-{
- ERL_NIF_TERM engine_res, key_id_term;
- struct engine_ctx *ctx;
- ErlNifBinary key_id_bin;
-
- if (!enif_get_map_value(env, key, atom_engine, &engine_res) ||
- !enif_get_resource(env, engine_res, engine_ctx_rtype, (void**)&ctx) ||
- !enif_get_map_value(env, key, atom_key_id, &key_id_term) ||
- !enif_inspect_binary(env, key_id_term, &key_id_bin)) {
- return 0;
- }
- else {
- *e = ctx->engine;
- return zero_terminate(key_id_bin, id);
- }
-}
-
-
-static char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key) {
- ERL_NIF_TERM tmp_term;
- ErlNifBinary pwd_bin;
- char *pwd;
- if (enif_get_map_value(env, key, atom_password, &tmp_term) &&
- enif_inspect_binary(env, tmp_term, &pwd_bin) &&
- zero_terminate(pwd_bin, &pwd)
- ) return pwd;
-
- return NULL;
-}
-
-static int zero_terminate(ErlNifBinary bin, char **buf) {
- *buf = enif_alloc(bin.size+1);
- if (!*buf)
- return 0;
- memcpy(*buf, bin.data, bin.size);
- *(*buf+bin.size) = 0;
- return 1;
-}
-#endif
-
-static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey)
-{
- if (enif_is_map(env, key)) {
-#ifdef HAS_ENGINE_SUPPORT
- /* Use key stored in engine */
- ENGINE *e;
- char *id;
- char *password;
-
- if (!get_engine_and_key_id(env, key, &id, &e))
- return PKEY_BADARG;
- password = get_key_password(env, key);
- *pkey = ENGINE_load_private_key(e, id, NULL, password);
- if (!*pkey)
- return PKEY_BADARG;
- enif_free(id);
-#else
- return PKEY_BADARG;
-#endif
- }
- else if (algorithm == atom_rsa) {
- RSA *rsa = RSA_new();
-
- if (!get_rsa_private_key(env, key, rsa)) {
- RSA_free(rsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_RSA(*pkey, rsa)) {
- EVP_PKEY_free(*pkey);
- RSA_free(rsa);
- return PKEY_BADARG;
- }
- } else if (algorithm == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = NULL;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
-
- if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2
- && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1])
- && get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec)) {
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) {
- EVP_PKEY_free(*pkey);
- EC_KEY_free(ec);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_eddsa) {
-#if defined(HAVE_EDDSA)
- if (!get_eddsa_key(env, 0, key, pkey)) {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_dss) {
- DSA *dsa = DSA_new();
-
- if (!get_dss_private_key(env, key, dsa)) {
- DSA_free(dsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_DSA(*pkey, dsa)) {
- EVP_PKEY_free(*pkey);
- DSA_free(dsa);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-
-static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
- EVP_PKEY **pkey)
-{
- if (enif_is_map(env, key)) {
-#ifdef HAS_ENGINE_SUPPORT
- /* Use key stored in engine */
- ENGINE *e;
- char *id;
- char *password;
-
- if (!get_engine_and_key_id(env, key, &id, &e))
- return PKEY_BADARG;
- password = get_key_password(env, key);
- *pkey = ENGINE_load_public_key(e, id, NULL, password);
- if (!pkey)
- return PKEY_BADARG;
- enif_free(id);
-#else
- return PKEY_BADARG;
-#endif
- } else if (algorithm == atom_rsa) {
- RSA *rsa = RSA_new();
-
- if (!get_rsa_public_key(env, key, rsa)) {
- RSA_free(rsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_RSA(*pkey, rsa)) {
- EVP_PKEY_free(*pkey);
- RSA_free(rsa);
- return PKEY_BADARG;
- }
- } else if (algorithm == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = NULL;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
-
- if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2
- && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1])
- && get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec)) {
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) {
- EVP_PKEY_free(*pkey);
- EC_KEY_free(ec);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_eddsa) {
-#if defined(HAVE_EDDSA)
- if (!get_eddsa_key(env, 1, key, pkey)) {
- return PKEY_BADARG;
- }
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (algorithm == atom_dss) {
- DSA *dsa = DSA_new();
-
- if (!get_dss_public_key(env, key, dsa)) {
- DSA_free(dsa);
- return PKEY_BADARG;
- }
-
- *pkey = EVP_PKEY_new();
- if (!EVP_PKEY_assign_DSA(*pkey, dsa)) {
- EVP_PKEY_free(*pkey);
- DSA_free(dsa);
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-static ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
-{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */
- int i;
- const EVP_MD *md = NULL;
- unsigned char md_value[EVP_MAX_MD_SIZE];
- EVP_PKEY *pkey;
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX *ctx;
- size_t siglen;
-#else
- unsigned len, siglen;
-#endif
- PKeySignOptions sig_opt;
- ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
-/*char buf[1024];
-enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf);
-enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf);
-printf("\r\n");
-*/
-
-#ifndef HAS_ENGINE_SUPPORT
- if (enif_is_map(env, argv[3])) {
- return atom_notsup;
- }
-#endif
-
- i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
-
-#ifdef HAS_EVP_PKEY_CTX
- ctx = EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx) goto badarg;
-
- if (argv[0] != atom_eddsa) {
- if (EVP_PKEY_sign_init(ctx) <= 0) goto badarg;
- if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg;
- }
-
- if (argv[0] == atom_rsa) {
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg;
-# ifdef HAVE_RSA_PKCS1_PSS_PADDING
- if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
- if (sig_opt.rsa_mgf1_md != NULL) {
-# ifdef HAVE_RSA_MGF1_MD
- if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg;
-# else
- EVP_PKEY_CTX_free(ctx);
- EVP_PKEY_free(pkey);
- return atom_notsup;
-# endif
- }
- if (sig_opt.rsa_pss_saltlen > -2
- && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0)
- goto badarg;
- }
-#endif
- }
-
- if (argv[0] == atom_eddsa) {
-#ifdef HAVE_EDDSA
- EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
- if (!EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey)) {
- if (mdctx) EVP_MD_CTX_free(mdctx);
- goto badarg;
- }
-
- if (!EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen)) {
- EVP_MD_CTX_free(mdctx);
- goto badarg;
- }
- enif_alloc_binary(siglen, &sig_bin);
-
- if (!EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen)) {
- EVP_MD_CTX_free(mdctx);
- goto badarg;
- }
-#else
- goto badarg;
-#endif
- }
- else
- {
- if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) <= 0) goto badarg;
- enif_alloc_binary(siglen, &sig_bin);
-
- if (md != NULL) {
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
- }
- i = EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen);
- }
-
- EVP_PKEY_CTX_free(ctx);
-#else
-/*printf("Old interface\r\n");
- */
- if (argv[0] == atom_rsa) {
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- enif_alloc_binary(RSA_size(rsa), &sig_bin);
- len = EVP_MD_size(md);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
- i = RSA_sign(md->type, tbs, len, sig_bin.data, &siglen, rsa);
- RSA_free(rsa);
- } else if (argv[0] == atom_dss) {
- DSA *dsa = EVP_PKEY_get1_DSA(pkey);
- enif_alloc_binary(DSA_size(dsa), &sig_bin);
- len = EVP_MD_size(md);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
- i = DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa);
- DSA_free(dsa);
- } else if (argv[0] == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- enif_alloc_binary(ECDSA_size(ec), &sig_bin);
- len = EVP_MD_size(md);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
- i = ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec);
- EC_KEY_free(ec);
-#else
- EVP_PKEY_free(pkey);
- return atom_notsup;
-#endif
- } else {
- goto badarg;
- }
-#endif
-
- EVP_PKEY_free(pkey);
- if (i == 1) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen);
- if (siglen != sig_bin.size) {
- enif_realloc_binary(&sig_bin, siglen);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen);
- }
- return enif_make_binary(env, &sig_bin);
- } else {
- enif_release_binary(&sig_bin);
- return atom_error;
- }
-
- badarg:
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#endif
- EVP_PKEY_free(pkey);
- return enif_make_badarg(env);
-}
-
-
-static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
-{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */
- int i;
- const EVP_MD *md = NULL;
- unsigned char md_value[EVP_MAX_MD_SIZE];
- EVP_PKEY *pkey;
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX *ctx;
-#else
-#endif
- PKeySignOptions sig_opt;
- ErlNifBinary sig_bin; /* signature */
- unsigned char *tbs; /* data to be signed */
- size_t tbslen;
-
-#ifndef HAS_ENGINE_SUPPORT
- if (enif_is_map(env, argv[4])) {
- return atom_notsup;
- }
-#endif
-
- if (!enif_inspect_binary(env, argv[3], &sig_bin)) {
- return enif_make_badarg(env);
- }
-
- i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
-
-#ifdef HAS_EVP_PKEY_CTX
-/* printf("EVP interface\r\n");
- */
- ctx = EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx) goto badarg;
-
- if (argv[0] != atom_eddsa) {
- if (EVP_PKEY_verify_init(ctx) <= 0) goto badarg;
- if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg;
- }
-
- if (argv[0] == atom_rsa) {
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg;
- if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
- if (sig_opt.rsa_mgf1_md != NULL) {
-# ifdef HAVE_RSA_MGF1_MD
- if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg;
-# else
- EVP_PKEY_CTX_free(ctx);
- EVP_PKEY_free(pkey);
- return atom_notsup;
-# endif
- }
- if (sig_opt.rsa_pss_saltlen > -2
- && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0)
- goto badarg;
- }
- }
-
- if (argv[0] == atom_eddsa) {
-#ifdef HAVE_EDDSA
- EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
-
- if (!EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey)) {
- if (mdctx) EVP_MD_CTX_destroy(mdctx);
- goto badarg;
- }
-
- i = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen);
- EVP_MD_CTX_destroy(mdctx);
-#else
- goto badarg;
-#endif
- }
- else
- {
- if (md != NULL) {
- ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
- }
- i = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen);
- }
-
- EVP_PKEY_CTX_free(ctx);
-#else
-/*printf("Old interface\r\n");
-*/
- if (argv[0] == atom_rsa) {
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- i = RSA_verify(md->type, tbs, tbslen, sig_bin.data, sig_bin.size, rsa);
- RSA_free(rsa);
- } else if (argv[0] == atom_dss) {
- DSA *dsa = EVP_PKEY_get1_DSA(pkey);
- i = DSA_verify(0, tbs, tbslen, sig_bin.data, sig_bin.size, dsa);
- DSA_free(dsa);
- } else if (argv[0] == atom_ecdsa) {
-#if defined(HAVE_EC)
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- i = ECDSA_verify(EVP_MD_type(md), tbs, tbslen, sig_bin.data, sig_bin.size, ec);
- EC_KEY_free(ec);
-#else
- EVP_PKEY_free(pkey);
- return atom_notsup;
-#endif
- } else {
- goto badarg;
- }
-#endif
-
- EVP_PKEY_free(pkey);
- if (i == 1) {
- return atom_true;
- } else {
- return atom_false;
- }
-
- badarg:
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#endif
- EVP_PKEY_free(pkey);
- return enif_make_badarg(env);
-}
-
-
-/*--------------------------------*/
-
-static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
- PKeyCryptOptions *opt)
-{
- ERL_NIF_TERM head, tail;
- const ERL_NIF_TERM *tpl_terms;
- int tpl_arity;
- const EVP_MD *opt_md;
- int i;
-
- if (!enif_is_list(env, options)) {
- return PKEY_BADARG;
- }
-
- /* defaults */
- if (algorithm == atom_rsa) {
- opt->rsa_mgf1_md = NULL;
- opt->rsa_oaep_label.data = NULL;
- opt->rsa_oaep_label.size = 0;
- opt->rsa_oaep_md = NULL;
- opt->rsa_padding = RSA_PKCS1_PADDING;
- opt->signature_md = NULL;
- }
-
- if (enif_is_empty_list(env, options)) {
- return PKEY_OK;
- }
-
- if (algorithm == atom_rsa) {
- tail = options;
- while (enif_get_list_cell(env, tail, &head, &tail)) {
- if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) {
- if (tpl_terms[0] == atom_rsa_padding
- || tpl_terms[0] == atom_rsa_pad /* Compatibility */
- ) {
- if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
- opt->rsa_padding = RSA_PKCS1_PADDING;
-#ifdef HAVE_RSA_OAEP_PADDING
- } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) {
- opt->rsa_padding = RSA_PKCS1_OAEP_PADDING;
-#endif
-#ifdef HAVE_RSA_SSLV23_PADDING
- } else if (tpl_terms[1] == atom_rsa_sslv23_padding) {
- opt->rsa_padding = RSA_SSLV23_PADDING;
-#endif
- } else if (tpl_terms[1] == atom_rsa_x931_padding) {
- opt->rsa_padding = RSA_X931_PADDING;
- } else if (tpl_terms[1] == atom_rsa_no_padding) {
- opt->rsa_padding = RSA_NO_PADDING;
- } else {
- return PKEY_BADARG;
- }
- } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) {
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->signature_md = opt_md;
- } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
-#ifndef HAVE_RSA_MGF1_MD
- if (tpl_terms[1] != atom_sha)
- return PKEY_NOTSUP;
-#endif
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->rsa_mgf1_md = opt_md;
- } else if (tpl_terms[0] == atom_rsa_oaep_label
- && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) {
-#ifdef HAVE_RSA_OAEP_MD
- continue;
-#else
- return PKEY_NOTSUP;
-#endif
- } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) {
-#ifndef HAVE_RSA_OAEP_MD
- if (tpl_terms[1] != atom_sha)
- return PKEY_NOTSUP;
-#endif
- i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
- if (i != PKEY_OK) {
- return i;
- }
- opt->rsa_oaep_md = opt_md;
- } else {
- return PKEY_BADARG;
- }
- } else {
- return PKEY_BADARG;
- }
- }
- } else {
- return PKEY_BADARG;
- }
-
- return PKEY_OK;
-}
-
-static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
-{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */
- int i;
- EVP_PKEY *pkey;
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX *ctx;
-#else
- RSA *rsa;
-#endif
- PKeyCryptOptions crypt_opt;
- ErlNifBinary in_bin, out_bin, tmp_bin;
- size_t outlen;
-#ifdef HAVE_RSA_SSLV23_PADDING
- size_t tmplen;
-#endif
- int is_private = (argv[4] == atom_true),
- is_encrypt = (argv[5] == atom_true);
- int algo_init = 0;
-
-/* char algo[1024]; */
-
-#ifndef HAS_ENGINE_SUPPORT
- if (enif_is_map(env, argv[2])) {
- return atom_notsup;
- }
-#endif
-
- if (!enif_inspect_binary(env, argv[1], &in_bin)) {
- return enif_make_badarg(env);
- }
-
- i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt);
- if (i != PKEY_OK) {
- if (i == PKEY_NOTSUP)
- return atom_notsup;
- else
- return enif_make_badarg(env);
- }
-
- if (is_private) {
- if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
- } else {
- if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
- }
-
- out_bin.data = NULL;
- out_bin.size = 0;
- tmp_bin.data = NULL;
- tmp_bin.size = 0;
-
-#ifdef HAS_EVP_PKEY_CTX
- ctx = EVP_PKEY_CTX_new(pkey, NULL);
- if (!ctx) goto badarg;
-
-/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1); */
-
- if (is_private) {
- if (is_encrypt) {
- /* private encrypt */
- if ((algo_init=EVP_PKEY_sign_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s private encrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */
- goto badarg;
- }
- } else {
- /* private decrypt */
- if ((algo_init=EVP_PKEY_decrypt_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s private decrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */
- goto badarg;
- }
- }
- } else {
- if (is_encrypt) {
- /* public encrypt */
- if ((algo_init=EVP_PKEY_encrypt_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s public encrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */
- goto badarg;
- }
- } else {
- /* public decrypt */
- if ((algo_init=EVP_PKEY_verify_recover_init(ctx)) <= 0) {
- /* fprintf(stderr,"BADARG %s public decrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */
- goto badarg;
- }
- }
- }
-
- if (argv[0] == atom_rsa) {
- if (crypt_opt.signature_md != NULL
- && EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) <= 0)
- goto badarg;
-#ifdef HAVE_RSA_SSLV23_PADDING
- if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
- if (is_encrypt) {
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- if (rsa == NULL) goto badarg;
- tmplen = RSA_size(rsa);
- if (!enif_alloc_binary(tmplen, &tmp_bin)) goto badarg;
- if (RSA_padding_add_SSLv23(tmp_bin.data, tmplen, in_bin.data, in_bin.size) <= 0)
- goto badarg;
- in_bin = tmp_bin;
- }
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) goto badarg;
- } else
-#endif
- {
- if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) <= 0) goto badarg;
- }
-#ifdef HAVE_RSA_OAEP_MD
- if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) {
- if (crypt_opt.rsa_oaep_md != NULL
- && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) <= 0)
- goto badarg;
- if (crypt_opt.rsa_mgf1_md != NULL
- && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) <= 0) goto badarg;
- if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) {
- unsigned char *label_copy;
- label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size);
- if (label_copy == NULL) goto badarg;
- memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data),
- crypt_opt.rsa_oaep_label.size);
- if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy,
- crypt_opt.rsa_oaep_label.size) <= 0) {
- OPENSSL_free(label_copy);
- label_copy = NULL;
- goto badarg;
- }
- }
- }
-#endif
- }
-
- if (is_private) {
- if (is_encrypt) {
- /* private_encrypt */
- i = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- } else {
- /* private_decrypt */
- i = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- }
- } else {
- if (is_encrypt) {
- /* public_encrypt */
- i = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- } else {
- /* public_decrypt */
- i = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size);
- }
- }
- /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */
-
- if (i != 1) goto badarg;
-
- enif_alloc_binary(outlen, &out_bin);
-
- if (is_private) {
- if (is_encrypt) {
- /* private_encrypt */
- i = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- } else {
- /* private_decrypt */
- i = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- }
- } else {
- if (is_encrypt) {
- /* public_encrypt */
- i = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- } else {
- /* public_decrypt */
- i = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
- }
- }
-
-#else
- /* Non-EVP cryptolib. Only support RSA */
-
- if (argv[0] != atom_rsa) {
- algo_init = -2; /* exitcode: notsup */
- goto badarg;
- }
- rsa = EVP_PKEY_get1_RSA(pkey);
- enif_alloc_binary(RSA_size(rsa), &out_bin);
-
- if (is_private) {
- if (is_encrypt) {
- /* non-evp rsa private encrypt */
- ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
- i = RSA_private_encrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- }
- } else {
- /* non-evp rsa private decrypt */
- i = RSA_private_decrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- enif_realloc_binary(&out_bin, i);
- }
- }
- } else {
- if (is_encrypt) {
- /* non-evp rsa public encrypt */
- ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
- i = RSA_public_encrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- }
- } else {
- /* non-evp rsa public decrypt */
- i = RSA_public_decrypt(in_bin.size, in_bin.data,
- out_bin.data, rsa, crypt_opt.rsa_padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
- enif_realloc_binary(&out_bin, i);
- }
- }
- }
-
- outlen = i;
- RSA_free(rsa);
-#endif
-
- if ((i > 0) && argv[0] == atom_rsa && !is_encrypt) {
-#ifdef HAVE_RSA_SSLV23_PADDING
- if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- unsigned char *p;
- if (rsa == NULL) goto badarg;
- tmplen = RSA_size(rsa);
- if (!enif_alloc_binary(tmplen, &tmp_bin)) {
- RSA_free(rsa);
- goto badarg;
- }
- p = out_bin.data;
- p++;
- i = RSA_padding_check_SSLv23(tmp_bin.data, tmplen, p, out_bin.size - 1, tmplen);
- if (i >= 0) {
- outlen = i;
- in_bin = out_bin;
- out_bin = tmp_bin;
- tmp_bin = in_bin;
- i = 1;
- }
- RSA_free(rsa);
- }
-#endif
- }
-
- if (tmp_bin.data != NULL) {
- enif_release_binary(&tmp_bin);
- }
-
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#else
-#endif
- EVP_PKEY_free(pkey);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen);
- if (outlen != out_bin.size) {
- enif_realloc_binary(&out_bin, outlen);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen);
- }
- return enif_make_binary(env, &out_bin);
- } else {
- enif_release_binary(&out_bin);
- return atom_error;
- }
-
- badarg:
- if (out_bin.data != NULL) {
- enif_release_binary(&out_bin);
- }
- if (tmp_bin.data != NULL) {
- enif_release_binary(&tmp_bin);
- }
-#ifdef HAS_EVP_PKEY_CTX
- EVP_PKEY_CTX_free(ctx);
-#else
-#endif
- EVP_PKEY_free(pkey);
- if (algo_init == -2)
- return atom_notsup;
- else
- return enif_make_badarg(env);
-}
-
-
-
-/*--------------------------------*/
-static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{ /* (Algorithm, PrivKey | KeyMap) */
- EVP_PKEY *pkey;
- ERL_NIF_TERM alg = argv[0];
- ERL_NIF_TERM result[8];
- if (get_pkey_private_key(env, alg, argv[1], &pkey) != PKEY_OK) {
- return enif_make_badarg(env);
- }
-
- if (alg == atom_rsa) {
- const BIGNUM *n = NULL, *e = NULL, *d = NULL;
- RSA *rsa = EVP_PKEY_get1_RSA(pkey);
- if (rsa) {
- RSA_get0_key(rsa, &n, &e, &d);
- result[0] = bin_from_bn(env, e); // Exponent E
- result[1] = bin_from_bn(env, n); // Modulus N = p*q
- EVP_PKEY_free(pkey);
- return enif_make_list_from_array(env, result, 2);
- }
-
- } else if (argv[0] == atom_dss) {
- const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL;
- DSA *dsa = EVP_PKEY_get1_DSA(pkey);
- if (dsa) {
- DSA_get0_pqg(dsa, &p, &q, &g);
- DSA_get0_key(dsa, &pub_key, NULL);
- result[0] = bin_from_bn(env, p);
- result[1] = bin_from_bn(env, q);
- result[2] = bin_from_bn(env, g);
- result[3] = bin_from_bn(env, pub_key);
- EVP_PKEY_free(pkey);
- return enif_make_list_from_array(env, result, 4);
- }
-
- } else if (argv[0] == atom_ecdsa) {
-#if defined(HAVE_EC)
- /* not yet implemented
- EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
- if (ec) {
- / * Example of result:
- {
- Curve = {Field, Prime, Point, Order, CoFactor} =
- {
- Field = {prime_field,<<255,...,255>>},
- Prime = {<<255,...,252>>,
- <<90,...,75>>,
- <<196,...,144>>
- },
- Point = <<4,...,245>>,
- Order = <<255,...,81>>,
- CoFactor = <<1>>
- },
- Key = <<151,...,62>>
- }
- or
- {
- Curve =
- {characteristic_two_field,
- M,
- Basis = {tpbasis, _}
- | {ppbasis, k1, k2, k3}
- },
- Key
- }
- * /
- EVP_PKEY_free(pkey);
- return enif_make_list_from_array(env, ..., ...);
- */
-#endif
- }
-
- if (pkey) EVP_PKEY_free(pkey);
- return enif_make_badarg(env);
-}
-
-/*================================================================*/
-
-static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{
- ErlNifBinary seed_bin;
-
- if (!enif_inspect_binary(env, argv[0], &seed_bin))
- return enif_make_badarg(env);
- RAND_seed(seed_bin.data,seed_bin.size);
- return atom_ok;
-}
-
-/*================================================================*/
-/* Engine */
-/*================================================================*/
-static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (EngineId) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret;
- ErlNifBinary engine_id_bin;
- char *engine_id;
- ENGINE *engine;
- struct engine_ctx *ctx;
-
- // Get Engine Id
- if(!enif_inspect_binary(env, argv[0], &engine_id_bin)) {
- PRINTF_ERR0("engine_by_id_nif Leaved: badarg");
- return enif_make_badarg(env);
- } else {
- engine_id = enif_alloc(engine_id_bin.size+1);
- (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size);
- engine_id[engine_id_bin.size] = '\0';
- }
-
- engine = ENGINE_by_id(engine_id);
- if(!engine) {
- enif_free(engine_id);
- PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}");
- return enif_make_tuple2(env, atom_error, atom_bad_engine_id);
- }
-
- ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
- ctx->engine = engine;
- ctx->id = engine_id;
-
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
-
- return enif_make_tuple2(env, atom_ok, ret);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret = atom_ok;
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_init_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- if (!ENGINE_init(ctx->engine)) {
- //ERR_print_errors_fp(stderr);
- PRINTF_ERR0("engine_init_nif Leaved: {error, engine_init_failed}");
- return enif_make_tuple2(env, atom_error, atom_engine_init_failed);
- }
-
- return ret;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_free_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- ENGINE_free(ctx->engine);
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_finish_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- ENGINE_finish(ctx->engine);
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* () */
-#ifdef HAS_ENGINE_SUPPORT
- ENGINE_load_dynamic();
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine, Commands) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret = atom_ok;
- unsigned int cmds_len = 0;
- char **cmds = NULL;
- struct engine_ctx *ctx;
- int i, optional = 0;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine));
-
- // Get Command List
- if(!enif_get_list_length(env, argv[1], &cmds_len)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Bad Command List");
- return enif_make_badarg(env);
- } else {
- cmds_len *= 2; // Key-Value list from erlang
- cmds = enif_alloc((cmds_len+1)*sizeof(char*));
- if(get_engine_load_cmd_list(env, argv[1], cmds, 0)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Couldn't read Command List");
- ret = enif_make_badarg(env);
- goto error;
- }
- }
-
- if(!enif_get_int(env, argv[2], &optional)) {
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter optional not an integer");
- return enif_make_badarg(env);
- }
-
- for(i = 0; i < cmds_len; i+=2) {
- PRINTF_ERR2("Cmd: %s:%s\r\n",
- cmds[i] ? cmds[i] : "(NULL)",
- cmds[i+1] ? cmds[i+1] : "(NULL)");
- if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) {
- PRINTF_ERR2("Command failed: %s:%s\r\n",
- cmds[i] ? cmds[i] : "(NULL)",
- cmds[i+1] ? cmds[i+1] : "(NULL)");
- //ENGINE_free(ctx->engine);
- ret = enif_make_tuple2(env, atom_error, atom_ctrl_cmd_failed);
- PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: {error, ctrl_cmd_failed}");
- goto error;
- }
- }
-
- error:
- for(i = 0; cmds != NULL && cmds[i] != NULL; i++)
- enif_free(cmds[i]);
- enif_free(cmds);
- return ret;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_add_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- if (!ENGINE_add(ctx->engine)) {
- PRINTF_ERR0("engine_add_nif Leaved: {error, add_engine_failed}");
- return enif_make_tuple2(env, atom_error, atom_add_engine_failed);
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_remove_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- if (!ENGINE_remove(ctx->engine)) {
- PRINTF_ERR0("engine_remove_nif Leaved: {error, remove_engine_failed}");
- return enif_make_tuple2(env, atom_error, atom_remove_engine_failed);
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine, EngineMethod) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
- unsigned int method;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_register_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- // Get Method
- if (!enif_get_uint(env, argv[1], &method)) {
- PRINTF_ERR0("engine_register_nif Leaved: Parameter Method not an uint");
- return enif_make_badarg(env);
- }
-
- switch(method)
- {
-#ifdef ENGINE_METHOD_RSA
- case ENGINE_METHOD_RSA:
- if (!ENGINE_register_RSA(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_DSA
- case ENGINE_METHOD_DSA:
- if (!ENGINE_register_DSA(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_DH
- case ENGINE_METHOD_DH:
- if (!ENGINE_register_DH(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_RAND
- case ENGINE_METHOD_RAND:
- if (!ENGINE_register_RAND(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDH
- case ENGINE_METHOD_ECDH:
- if (!ENGINE_register_ECDH(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDSA
- case ENGINE_METHOD_ECDSA:
- if (!ENGINE_register_ECDSA(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_STORE
- case ENGINE_METHOD_STORE:
- if (!ENGINE_register_STORE(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_CIPHERS
- case ENGINE_METHOD_CIPHERS:
- if (!ENGINE_register_ciphers(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_DIGESTS
- case ENGINE_METHOD_DIGESTS:
- if (!ENGINE_register_digests(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_METHS
- case ENGINE_METHOD_PKEY_METHS:
- if (!ENGINE_register_pkey_meths(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
- case ENGINE_METHOD_PKEY_ASN1_METHS:
- if (!ENGINE_register_pkey_asn1_meths(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
-#ifdef ENGINE_METHOD_EC
- case ENGINE_METHOD_EC:
- if (!ENGINE_register_EC(ctx->engine))
- return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
- break;
-#endif
- default:
- return enif_make_tuple2(env, atom_error, atom_engine_method_not_supported);
- break;
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine, EngineMethod) */
-#ifdef HAS_ENGINE_SUPPORT
- struct engine_ctx *ctx;
- unsigned int method;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_unregister_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- // Get Method
- if (!enif_get_uint(env, argv[1], &method)) {
- PRINTF_ERR0("engine_unregister_nif Leaved: Parameter Method not an uint");
- return enif_make_badarg(env);
- }
-
- switch(method)
- {
-#ifdef ENGINE_METHOD_RSA
- case ENGINE_METHOD_RSA:
- ENGINE_unregister_RSA(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_DSA
- case ENGINE_METHOD_DSA:
- ENGINE_unregister_DSA(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_DH
- case ENGINE_METHOD_DH:
- ENGINE_unregister_DH(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_RAND
- case ENGINE_METHOD_RAND:
- ENGINE_unregister_RAND(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDH
- case ENGINE_METHOD_ECDH:
- ENGINE_unregister_ECDH(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_ECDSA
- case ENGINE_METHOD_ECDSA:
- ENGINE_unregister_ECDSA(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_STORE
- case ENGINE_METHOD_STORE:
- ENGINE_unregister_STORE(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_CIPHERS
- case ENGINE_METHOD_CIPHERS:
- ENGINE_unregister_ciphers(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_DIGESTS
- case ENGINE_METHOD_DIGESTS:
- ENGINE_unregister_digests(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_METHS
- case ENGINE_METHOD_PKEY_METHS:
- ENGINE_unregister_pkey_meths(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
- case ENGINE_METHOD_PKEY_ASN1_METHS:
- ENGINE_unregister_pkey_asn1_meths(ctx->engine);
- break;
-#endif
-#ifdef ENGINE_METHOD_EC
- case ENGINE_METHOD_EC:
- ENGINE_unregister_EC(ctx->engine);
- break;
-#endif
- default:
- break;
- }
- return atom_ok;
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret;
- ENGINE *engine;
- ErlNifBinary engine_bin;
- struct engine_ctx *ctx;
-
- engine = ENGINE_get_first();
- if(!engine) {
- enif_alloc_binary(0, &engine_bin);
- engine_bin.size = 0;
- return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
- }
-
- ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
- ctx->engine = engine;
- ctx->id = NULL;
-
- ret = enif_make_resource(env, ctx);
- enif_release_resource(ctx);
-
- return enif_make_tuple2(env, atom_ok, ret);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM ret;
- ENGINE *engine;
- ErlNifBinary engine_bin;
- struct engine_ctx *ctx, *next_ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_get_next_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
- engine = ENGINE_get_next(ctx->engine);
- if (!engine) {
- enif_alloc_binary(0, &engine_bin);
- engine_bin.size = 0;
- return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
- }
-
- next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
- next_ctx->engine = engine;
- next_ctx->id = NULL;
-
- ret = enif_make_resource(env, next_ctx);
- enif_release_resource(next_ctx);
-
- return enif_make_tuple2(env, atom_ok, ret);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ErlNifBinary engine_id_bin;
- const char *engine_id;
- int size;
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- engine_id = ENGINE_get_id(ctx->engine);
- if (!engine_id) {
- enif_alloc_binary(0, &engine_id_bin);
- engine_id_bin.size = 0;
- return enif_make_binary(env, &engine_id_bin);
- }
-
- size = strlen(engine_id);
- enif_alloc_binary(size, &engine_id_bin);
- engine_id_bin.size = size;
- memcpy(engine_id_bin.data, engine_id, size);
-
- return enif_make_binary(env, &engine_id_bin);
-#else
- return atom_notsup;
-#endif
-}
-
-static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* (Engine) */
-#ifdef HAS_ENGINE_SUPPORT
- ErlNifBinary engine_name_bin;
- const char *engine_name;
- int size;
- struct engine_ctx *ctx;
-
- // Get Engine
- if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
- PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object");
- return enif_make_badarg(env);
- }
-
- engine_name = ENGINE_get_name(ctx->engine);
- if (!engine_name) {
- enif_alloc_binary(0, &engine_name_bin);
- engine_name_bin.size = 0;
- return enif_make_binary(env, &engine_name_bin);
- }
-
- size = strlen(engine_name);
- enif_alloc_binary(size, &engine_name_bin);
- engine_name_bin.size = size;
- memcpy(engine_name_bin.data, engine_name, size);
-
- return enif_make_binary(env, &engine_name_bin);
-#else
- return atom_notsup;
-#endif
-}
-
-#ifdef HAS_ENGINE_SUPPORT
-static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i)
-{
- ERL_NIF_TERM head, tail;
- const ERL_NIF_TERM *tmp_tuple;
- ErlNifBinary tmpbin;
- int arity;
- char* tmpstr;
-
- if(!enif_is_empty_list(env, term)) {
- if(!enif_get_list_cell(env, term, &head, &tail)) {
- cmds[i] = NULL;
- return -1;
- } else {
- if(!enif_get_tuple(env, head, &arity, &tmp_tuple) || arity != 2) {
- cmds[i] = NULL;
- return -1;
- } else {
- if(!enif_inspect_binary(env, tmp_tuple[0], &tmpbin)) {
- cmds[i] = NULL;
- return -1;
- } else {
- tmpstr = enif_alloc(tmpbin.size+1);
- (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
- tmpstr[tmpbin.size] = '\0';
- cmds[i++] = tmpstr;
- }
- if(!enif_inspect_binary(env, tmp_tuple[1], &tmpbin)) {
- cmds[i] = NULL;
- return -1;
- } else {
- if(tmpbin.size == 0)
- cmds[i++] = NULL;
- else {
- tmpstr = enif_alloc(tmpbin.size+1);
- (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
- tmpstr[tmpbin.size] = '\0';
- cmds[i++] = tmpstr;
- }
- }
- return get_engine_load_cmd_list(env, tail, cmds, i);
- }
- }
- } else {
- cmds[i] = NULL;
- return 0;
- }
-}
-#endif
-
-static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
-{/* () */
-#ifdef HAS_ENGINE_SUPPORT
- ERL_NIF_TERM method_array[12];
- int i = 0;
-
-#ifdef ENGINE_METHOD_RSA
- method_array[i++] = atom_engine_method_rsa;
-#endif
-#ifdef ENGINE_METHOD_DSA
- method_array[i++] = atom_engine_method_dsa;
-#endif
-#ifdef ENGINE_METHOD_DH
- method_array[i++] = atom_engine_method_dh;
-#endif
-#ifdef ENGINE_METHOD_RAND
- method_array[i++] = atom_engine_method_rand;
-#endif
-#ifdef ENGINE_METHOD_ECDH
- method_array[i++] = atom_engine_method_ecdh;
-#endif
-#ifdef ENGINE_METHOD_ECDSA
- method_array[i++] = atom_engine_method_ecdsa;
-#endif
-#ifdef ENGINE_METHOD_STORE
- method_array[i++] = atom_engine_method_store;
-#endif
-#ifdef ENGINE_METHOD_CIPHERS
- method_array[i++] = atom_engine_method_ciphers;
-#endif
-#ifdef ENGINE_METHOD_DIGESTS
- method_array[i++] = atom_engine_method_digests;
-#endif
-#ifdef ENGINE_METHOD_PKEY_METHS
- method_array[i++] = atom_engine_method_pkey_meths;
-#endif
-#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
- method_array[i++] = atom_engine_method_pkey_asn1_meths;
-#endif
-#ifdef ENGINE_METHOD_EC
- method_array[i++] = atom_engine_method_ec;
-#endif
-
- return enif_make_list_from_array(env, method_array, i);
-#else
- return atom_notsup;
-#endif
-}
diff --git a/lib/crypto/c_src/dh.c b/lib/crypto/c_src/dh.c
new file mode 100644
index 0000000000..0c18ad7a3f
--- /dev/null
+++ b/lib/crypto/c_src/dh.c
@@ -0,0 +1,204 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "dh.h"
+#include "bn.h"
+
+ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (PrivKey|undefined, DHParams=[P,G], Mpint, Len|0) */
+ DH *dh_params = NULL;
+ int mpint; /* 0 or 4 */
+
+ {
+ ERL_NIF_TERM head, tail;
+ BIGNUM
+ *dh_p = NULL,
+ *dh_g = NULL,
+ *priv_key_in = NULL;
+ unsigned long
+ len = 0;
+
+ if (!(get_bn_from_bin(env, argv[0], &priv_key_in)
+ || argv[0] == atom_undefined)
+ || !enif_get_list_cell(env, argv[1], &head, &tail)
+ || !get_bn_from_bin(env, head, &dh_p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dh_g)
+ || !enif_is_empty_list(env, tail)
+ || !enif_get_int(env, argv[2], &mpint) || (mpint & ~4)
+ || !enif_get_ulong(env, argv[3], &len)
+
+ /* Load dh_params with values to use by the generator.
+ Mem mgmnt transfered from dh_p etc to dh_params */
+ || !(dh_params = DH_new())
+ || (priv_key_in && !DH_set0_key(dh_params, NULL, priv_key_in))
+ || !DH_set0_pqg(dh_params, dh_p, NULL, dh_g)
+ ) {
+ if (priv_key_in) BN_free(priv_key_in);
+ if (dh_p) BN_free(dh_p);
+ if (dh_g) BN_free(dh_g);
+ if (dh_params) DH_free(dh_params);
+ return enif_make_badarg(env);
+ }
+
+ if (len) {
+ if (len < BN_num_bits(dh_p))
+ DH_set_length(dh_params, len);
+ else {
+ if (priv_key_in) BN_free(priv_key_in);
+ if (dh_p) BN_free(dh_p);
+ if (dh_g) BN_free(dh_g);
+ if (dh_params) DH_free(dh_params);
+ return enif_make_badarg(env);
+ }
+ }
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+ {
+ EVP_PKEY_CTX *ctx;
+ EVP_PKEY *dhkey, *params;
+ int success;
+
+ params = EVP_PKEY_new();
+ success = EVP_PKEY_set1_DH(params, dh_params); /* set the key referenced by params to dh_params... */
+ DH_free(dh_params); /* ...dh_params (and params) must be freed */
+ if (!success) return atom_error;
+
+ ctx = EVP_PKEY_CTX_new(params, NULL);
+ EVP_PKEY_free(params);
+ if (!ctx) {
+ return atom_error;
+ }
+
+ if (!EVP_PKEY_keygen_init(ctx)) {
+ /* EVP_PKEY_CTX_free(ctx); */
+ return atom_error;
+ }
+
+ dhkey = EVP_PKEY_new();
+ if (!EVP_PKEY_keygen(ctx, &dhkey)) { /* "performs a key generation operation, the ... */
+ /*... generated key is written to ppkey." (=last arg) */
+ /* EVP_PKEY_CTX_free(ctx); */
+ /* EVP_PKEY_free(dhkey); */
+ return atom_error;
+ }
+
+ dh_params = EVP_PKEY_get1_DH(dhkey); /* return the referenced key. dh_params and dhkey must be freed */
+ EVP_PKEY_free(dhkey);
+ if (!dh_params) {
+ /* EVP_PKEY_CTX_free(ctx); */
+ return atom_error;
+ }
+ EVP_PKEY_CTX_free(ctx);
+ }
+#else
+ if (!DH_generate_key(dh_params)) return atom_error;
+#endif
+ {
+ unsigned char *pub_ptr, *prv_ptr;
+ int pub_len, prv_len;
+ ERL_NIF_TERM ret_pub, ret_prv;
+ const BIGNUM *pub_key_gen, *priv_key_gen;
+
+ DH_get0_key(dh_params,
+ &pub_key_gen, &priv_key_gen); /* Get pub_key_gen and priv_key_gen.
+ "The values point to the internal representation of
+ the public key and private key values. This memory
+ should not be freed directly." says man */
+ pub_len = BN_num_bytes(pub_key_gen);
+ prv_len = BN_num_bytes(priv_key_gen);
+ pub_ptr = enif_make_new_binary(env, pub_len+mpint, &ret_pub);
+ prv_ptr = enif_make_new_binary(env, prv_len+mpint, &ret_prv);
+ if (mpint) {
+ put_int32(pub_ptr, pub_len); pub_ptr += 4;
+ put_int32(prv_ptr, prv_len); prv_ptr += 4;
+ }
+ BN_bn2bin(pub_key_gen, pub_ptr);
+ BN_bn2bin(priv_key_gen, prv_ptr);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr, pub_len);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr, prv_len);
+
+ DH_free(dh_params);
+
+ return enif_make_tuple2(env, ret_pub, ret_prv);
+ }
+}
+
+ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */
+ BIGNUM *other_pub_key = NULL,
+ *dh_p = NULL,
+ *dh_g = NULL;
+ DH *dh_priv = DH_new();
+
+ /* Check the arguments and get
+ my private key (dh_priv),
+ the peer's public key (other_pub_key),
+ the parameters p & q
+ */
+
+ {
+ BIGNUM *dummy_pub_key = NULL,
+ *priv_key = NULL;
+ ERL_NIF_TERM head, tail;
+
+ if (!get_bn_from_bin(env, argv[0], &other_pub_key)
+ || !get_bn_from_bin(env, argv[1], &priv_key)
+ || !enif_get_list_cell(env, argv[2], &head, &tail)
+ || !get_bn_from_bin(env, head, &dh_p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dh_g)
+ || !enif_is_empty_list(env, tail)
+
+ /* Note: DH_set0_key() does not allow setting only the
+ * private key, although DH_compute_key() does not use the
+ * public key. Work around this limitation by setting
+ * the public key to a copy of the private key.
+ */
+ || !(dummy_pub_key = BN_dup(priv_key))
+ || !DH_set0_key(dh_priv, dummy_pub_key, priv_key)
+ || !DH_set0_pqg(dh_priv, dh_p, NULL, dh_g)
+ ) {
+ if (dh_p) BN_free(dh_p);
+ if (dh_g) BN_free(dh_g);
+ if (other_pub_key) BN_free(other_pub_key);
+ if (dummy_pub_key) BN_free(dummy_pub_key);
+ if (priv_key) BN_free(priv_key);
+ return enif_make_badarg(env);
+ }
+ }
+ {
+ ErlNifBinary ret_bin;
+ int size;
+
+ enif_alloc_binary(DH_size(dh_priv), &ret_bin);
+ size = DH_compute_key(ret_bin.data, other_pub_key, dh_priv);
+ BN_free(other_pub_key);
+ DH_free(dh_priv);
+ if (size<=0) {
+ enif_release_binary(&ret_bin);
+ return atom_error;
+ }
+
+ if (size != ret_bin.size) enif_realloc_binary(&ret_bin, size);
+ return enif_make_binary(env, &ret_bin);
+ }
+}
diff --git a/lib/crypto/c_src/dh.h b/lib/crypto/c_src/dh.h
new file mode 100644
index 0000000000..a996b0ea28
--- /dev/null
+++ b/lib/crypto/c_src/dh.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_DH_H__
+#define E_DH_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_DH_H__ */
diff --git a/lib/crypto/c_src/digest.c b/lib/crypto/c_src/digest.c
new file mode 100644
index 0000000000..9e6199030d
--- /dev/null
+++ b/lib/crypto/c_src/digest.c
@@ -0,0 +1,111 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "digest.h"
+
+static struct digest_type_t digest_types[] =
+{
+ {{"md4"}, {&EVP_md4}},
+ {{"md5"}, {&EVP_md5}},
+ {{"ripemd160"}, {&EVP_ripemd160}},
+ {{"sha"}, {&EVP_sha1}},
+ {{"sha224"},
+#ifdef HAVE_SHA224
+ {&EVP_sha224}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha256"},
+#ifdef HAVE_SHA256
+ {&EVP_sha256}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha384"},
+#ifdef HAVE_SHA384
+ {&EVP_sha384}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha512"},
+#ifdef HAVE_SHA512
+ {&EVP_sha512}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_224"},
+#ifdef HAVE_SHA3_224
+ {&EVP_sha3_224}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_256"},
+#ifdef HAVE_SHA3_256
+ {&EVP_sha3_256}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_384"},
+#ifdef HAVE_SHA3_384
+ {&EVP_sha3_384}
+#else
+ {NULL}
+#endif
+ },
+ {{"sha3_512"},
+#ifdef HAVE_SHA3_512
+ {&EVP_sha3_512}
+#else
+ {NULL}
+#endif
+ },
+
+ {{NULL}}
+};
+
+void init_digest_types(ErlNifEnv* env)
+{
+ struct digest_type_t* p = digest_types;
+
+ for (p = digest_types; p->type.str; p++) {
+ p->type.atom = enif_make_atom(env, p->type.str);
+ if (p->md.funcp)
+ p->md.p = p->md.funcp();
+ }
+ p->type.atom = atom_false; /* end marker */
+}
+
+struct digest_type_t* get_digest_type(ERL_NIF_TERM type)
+{
+ struct digest_type_t* p = NULL;
+ for (p = digest_types; p->type.atom != atom_false; p++) {
+ if (type == p->type.atom) {
+ return p;
+ }
+ }
+ return NULL;
+}
+
diff --git a/lib/crypto/c_src/digest.h b/lib/crypto/c_src/digest.h
new file mode 100644
index 0000000000..06852416cf
--- /dev/null
+++ b/lib/crypto/c_src/digest.h
@@ -0,0 +1,40 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_DIGEST_H__
+#define E_DIGEST_H__ 1
+
+#include "common.h"
+
+struct digest_type_t {
+ union {
+ const char* str; /* before init, NULL for end-of-table */
+ ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */
+ }type;
+ union {
+ const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */
+ const EVP_MD* p; /* after init, NULL if notsup */
+ }md;
+};
+
+void init_digest_types(ErlNifEnv* env);
+struct digest_type_t* get_digest_type(ERL_NIF_TERM type);
+
+#endif /* E_DIGEST_H__ */
diff --git a/lib/crypto/c_src/dss.c b/lib/crypto/c_src/dss.c
new file mode 100644
index 0000000000..9d39241382
--- /dev/null
+++ b/lib/crypto/c_src/dss.c
@@ -0,0 +1,85 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "dss.h"
+#include "bn.h"
+
+int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
+{
+ /* key=[P,Q,G,KEY] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL;
+ BIGNUM *dummy_pub_key, *priv_key = NULL;
+
+ if (!enif_get_list_cell(env, key, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_q)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_g)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &priv_key)
+ || !enif_is_empty_list(env,tail)) {
+ if (dsa_p) BN_free(dsa_p);
+ if (dsa_q) BN_free(dsa_q);
+ if (dsa_g) BN_free(dsa_g);
+ if (priv_key) BN_free(priv_key);
+ return 0;
+ }
+
+ /* Note: DSA_set0_key() does not allow setting only the
+ * private key, although DSA_sign() does not use the
+ * public key. Work around this limitation by setting
+ * the public key to a copy of the private key.
+ */
+ dummy_pub_key = BN_dup(priv_key);
+
+ DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g);
+ DSA_set0_key(dsa, dummy_pub_key, priv_key);
+ return 1;
+}
+
+
+int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa)
+{
+ /* key=[P, Q, G, Y] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_y = NULL;
+
+ if (!enif_get_list_cell(env, key, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_q)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_g)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dsa_y)
+ || !enif_is_empty_list(env,tail)) {
+ if (dsa_p) BN_free(dsa_p);
+ if (dsa_q) BN_free(dsa_q);
+ if (dsa_g) BN_free(dsa_g);
+ if (dsa_y) BN_free(dsa_y);
+ return 0;
+ }
+
+ DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g);
+ DSA_set0_key(dsa, dsa_y, NULL);
+ return 1;
+}
diff --git a/lib/crypto/c_src/dss.h b/lib/crypto/c_src/dss.h
new file mode 100644
index 0000000000..3275657e98
--- /dev/null
+++ b/lib/crypto/c_src/dss.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_DSS_H__
+#define E_DSS_H__ 1
+
+#include "common.h"
+
+int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa);
+int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa);
+
+#endif /* E_DSS_H__ */
diff --git a/lib/crypto/c_src/ec.c b/lib/crypto/c_src/ec.c
new file mode 100644
index 0000000000..6d831ec9d2
--- /dev/null
+++ b/lib/crypto/c_src/ec.c
@@ -0,0 +1,360 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "ec.h"
+#include "bn.h"
+
+#ifdef HAVE_EC
+static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg);
+static ERL_NIF_TERM point2term(ErlNifEnv* env,
+ const EC_GROUP *group,
+ const EC_POINT *point,
+ point_conversion_form_t form);
+
+ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env)
+{
+ ERL_NIF_TERM reason;
+ if (enif_has_pending_exception(env, &reason))
+ return reason; /* dummy return value ignored */
+ else
+ return enif_make_badarg(env);
+}
+
+static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg)
+{
+ EC_KEY *key = NULL;
+ int c_arity = -1;
+ const ERL_NIF_TERM* curve;
+ ErlNifBinary seed;
+ BIGNUM *p = NULL;
+ BIGNUM *a = NULL;
+ BIGNUM *b = NULL;
+ BIGNUM *bn_order = NULL;
+ BIGNUM *cofactor = NULL;
+ EC_GROUP *group = NULL;
+ EC_POINT *point = NULL;
+
+ /* {Field, Prime, Point, Order, CoFactor} = Curve */
+ if (enif_get_tuple(env,curve_arg,&c_arity,&curve)
+ && c_arity == 5
+ && get_bn_from_bin(env, curve[3], &bn_order)
+ && (curve[4] != atom_none && get_bn_from_bin(env, curve[4], &cofactor))) {
+
+ int f_arity = -1;
+ const ERL_NIF_TERM* field;
+ int p_arity = -1;
+ const ERL_NIF_TERM* prime;
+
+ long field_bits;
+
+ /* {A, B, Seed} = Prime */
+ if (!enif_get_tuple(env,curve[1],&p_arity,&prime)
+ || !get_bn_from_bin(env, prime[0], &a)
+ || !get_bn_from_bin(env, prime[1], &b))
+ goto out_err;
+
+ if (!enif_get_tuple(env,curve[0],&f_arity,&field))
+ goto out_err;
+
+ if (f_arity == 2 && field[0] == atom_prime_field) {
+ /* {prime_field, Prime} */
+
+ if (!get_bn_from_bin(env, field[1], &p))
+ goto out_err;
+
+ if (BN_is_negative(p) || BN_is_zero(p))
+ goto out_err;
+
+ field_bits = BN_num_bits(p);
+ if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
+ goto out_err;
+
+ /* create the EC_GROUP structure */
+ group = EC_GROUP_new_curve_GFp(p, a, b, NULL);
+
+ } else if (f_arity == 3 && field[0] == atom_characteristic_two_field) {
+#if defined(OPENSSL_NO_EC2M)
+ enif_raise_exception(env, atom_notsup);
+ goto out_err;
+#else
+ /* {characteristic_two_field, M, Basis} */
+
+ int b_arity = -1;
+ const ERL_NIF_TERM* basis;
+ unsigned int k1, k2, k3;
+
+ if ((p = BN_new()) == NULL)
+ goto out_err;
+
+ if (!enif_get_long(env, field[1], &field_bits)
+ || field_bits > OPENSSL_ECC_MAX_FIELD_BITS)
+ goto out_err;
+
+ if (enif_get_tuple(env,field[2],&b_arity,&basis)) {
+ if (b_arity == 2
+ && basis[0] == atom_tpbasis
+ && enif_get_uint(env, basis[1], &k1)) {
+ /* {tpbasis, k} = Basis */
+
+ if (!(field_bits > k1 && k1 > 0))
+ goto out_err;
+
+ /* create the polynomial */
+ if (!BN_set_bit(p, (int)field_bits)
+ || !BN_set_bit(p, (int)k1)
+ || !BN_set_bit(p, 0))
+ goto out_err;
+
+ } else if (b_arity == 4
+ && basis[0] == atom_ppbasis
+ && enif_get_uint(env, basis[1], &k1)
+ && enif_get_uint(env, basis[2], &k2)
+ && enif_get_uint(env, basis[3], &k3)) {
+ /* {ppbasis, k1, k2, k3} = Basis */
+
+ if (!(field_bits > k3 && k3 > k2 && k2 > k1 && k1 > 0))
+ goto out_err;
+
+ /* create the polynomial */
+ if (!BN_set_bit(p, (int)field_bits)
+ || !BN_set_bit(p, (int)k1)
+ || !BN_set_bit(p, (int)k2)
+ || !BN_set_bit(p, (int)k3)
+ || !BN_set_bit(p, 0))
+ goto out_err;
+
+ } else
+ goto out_err;
+ } else if (field[2] == atom_onbasis) {
+ /* onbasis = Basis */
+ /* no parameters */
+ goto out_err;
+
+ } else
+ goto out_err;
+
+ group = EC_GROUP_new_curve_GF2m(p, a, b, NULL);
+#endif
+ } else
+ goto out_err;
+
+ if (!group)
+ goto out_err;
+
+ if (enif_inspect_binary(env, prime[2], &seed)) {
+ EC_GROUP_set_seed(group, seed.data, seed.size);
+ }
+
+ if (!term2point(env, curve[2], group, &point))
+ goto out_err;
+
+ if (BN_is_negative(bn_order)
+ || BN_is_zero(bn_order)
+ || BN_num_bits(bn_order) > (int)field_bits + 1)
+ goto out_err;
+
+ if (!EC_GROUP_set_generator(group, point, bn_order, cofactor))
+ goto out_err;
+
+ EC_GROUP_set_asn1_flag(group, 0x0);
+
+ key = EC_KEY_new();
+ if (!key)
+ goto out_err;
+ EC_KEY_set_group(key, group);
+ }
+ else {
+ goto out_err;
+ }
+
+
+ goto out;
+
+out_err:
+ if (key) EC_KEY_free(key);
+ key = NULL;
+
+out:
+ /* some OpenSSL structures are mem-dup'ed into the key,
+ so we have to free our copies here */
+ if (p) BN_free(p);
+ if (a) BN_free(a);
+ if (b) BN_free(b);
+ if (bn_order) BN_free(bn_order);
+ if (cofactor) BN_free(cofactor);
+ if (group) EC_GROUP_free(group);
+ if (point) EC_POINT_free(point);
+
+ return key;
+}
+
+static ERL_NIF_TERM point2term(ErlNifEnv* env,
+ const EC_GROUP *group,
+ const EC_POINT *point,
+ point_conversion_form_t form)
+{
+ unsigned dlen;
+ ErlNifBinary bin;
+
+ dlen = EC_POINT_point2oct(group, point, form, NULL, 0, NULL);
+ if (dlen == 0)
+ return atom_undefined;
+
+ if (!enif_alloc_binary(dlen, &bin))
+ return enif_make_badarg(env);
+
+ if (!EC_POINT_point2oct(group, point, form, bin.data, bin.size, NULL)) {
+ enif_release_binary(&bin);
+ return enif_make_badarg(env);
+ }
+ ERL_VALGRIND_MAKE_MEM_DEFINED(bin.data, bin.size);
+ return enif_make_binary(env, &bin);
+}
+
+int term2point(ErlNifEnv* env, ERL_NIF_TERM term, EC_GROUP *group, EC_POINT **pptr)
+{
+ int ret = 0;
+ ErlNifBinary bin;
+ EC_POINT *point;
+
+ if (!enif_inspect_binary(env,term,&bin)) {
+ return 0;
+ }
+
+ if ((*pptr = point = EC_POINT_new(group)) == NULL) {
+ return 0;
+ }
+
+ /* set the point conversion form */
+ EC_GROUP_set_point_conversion_form(group, (point_conversion_form_t)(bin.data[0] & ~0x01));
+
+ /* extract the ec point */
+ if (!EC_POINT_oct2point(group, point, bin.data, bin.size, NULL)) {
+ EC_POINT_free(point);
+ *pptr = NULL;
+ } else
+ ret = 1;
+
+ return ret;
+}
+
+int get_ec_key(ErlNifEnv* env,
+ ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub,
+ EC_KEY** res)
+{
+ EC_KEY *key = NULL;
+ BIGNUM *priv_key = NULL;
+ EC_POINT *pub_key = NULL;
+ EC_GROUP *group = NULL;
+
+ if (!(priv == atom_undefined || get_bn_from_bin(env, priv, &priv_key))
+ || !(pub == atom_undefined || enif_is_binary(env, pub))) {
+ goto out_err;
+ }
+
+ key = ec_key_new(env, curve);
+
+ if (!key) {
+ goto out_err;
+ }
+
+ if (!group)
+ group = EC_GROUP_dup(EC_KEY_get0_group(key));
+
+ if (term2point(env, pub, group, &pub_key)) {
+ if (!EC_KEY_set_public_key(key, pub_key)) {
+ goto out_err;
+ }
+ }
+ if (priv != atom_undefined
+ && !BN_is_zero(priv_key)) {
+ if (!EC_KEY_set_private_key(key, priv_key))
+ goto out_err;
+
+ /* calculate public key (if necessary) */
+ if (EC_KEY_get0_public_key(key) == NULL)
+ {
+ /* the public key was not included in the SEC1 private
+ * key => calculate the public key */
+ pub_key = EC_POINT_new(group);
+ if (pub_key == NULL
+ || !EC_POINT_copy(pub_key, EC_GROUP_get0_generator(group))
+ || !EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL)
+ || !EC_KEY_set_public_key(key, pub_key))
+ goto out_err;
+ }
+ }
+
+ goto out;
+
+out_err:
+ if (key) EC_KEY_free(key);
+ key = NULL;
+
+out:
+ /* some OpenSSL structures are mem-dup'ed into the key,
+ so we have to free our copies here */
+ if (priv_key) BN_clear_free(priv_key);
+ if (pub_key) EC_POINT_free(pub_key);
+ if (group) EC_GROUP_free(group);
+ if (!key)
+ return 0;
+ *res = key;
+ return 1;
+}
+
+#endif /* HAVE_EC */
+
+ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#if defined(HAVE_EC)
+ EC_KEY *key = NULL;
+ const EC_GROUP *group;
+ const EC_POINT *public_key;
+ ERL_NIF_TERM priv_key;
+ ERL_NIF_TERM pub_key = atom_undefined;
+
+ if (!get_ec_key(env, argv[0], argv[1], atom_undefined, &key))
+ goto badarg;
+
+ if (argv[1] == atom_undefined) {
+ if (!EC_KEY_generate_key(key))
+ goto badarg;
+ }
+
+ group = EC_KEY_get0_group(key);
+ public_key = EC_KEY_get0_public_key(key);
+
+ if (group && public_key) {
+ pub_key = point2term(env, group, public_key,
+ EC_KEY_get_conv_form(key));
+ }
+ priv_key = bn2term(env, EC_KEY_get0_private_key(key));
+ EC_KEY_free(key);
+ return enif_make_tuple2(env, pub_key, priv_key);
+
+badarg:
+ if (key)
+ EC_KEY_free(key);
+ return make_badarg_maybe(env);
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/ec.h b/lib/crypto/c_src/ec.h
new file mode 100644
index 0000000000..b7e1cc5a46
--- /dev/null
+++ b/lib/crypto/c_src/ec.h
@@ -0,0 +1,35 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_EC_H__
+#define E_EC_H__ 1
+
+#include "common.h"
+
+#if defined(HAVE_EC)
+int get_ec_key(ErlNifEnv* env, ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub,
+ EC_KEY** res);
+int term2point(ErlNifEnv* env, ERL_NIF_TERM term, EC_GROUP *group, EC_POINT **pptr);
+ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env);
+#endif
+
+ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_EC_H__ */
diff --git a/lib/crypto/c_src/ecdh.c b/lib/crypto/c_src/ecdh.c
new file mode 100644
index 0000000000..d458f3c48e
--- /dev/null
+++ b/lib/crypto/c_src/ecdh.c
@@ -0,0 +1,80 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "ecdh.h"
+#include "ec.h"
+
+/*
+ (_OthersPublicKey, _MyPrivateKey)
+ (_OthersPublicKey, _MyEC_Point)
+*/
+ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+/* (OtherPublicKey, Curve, My) */
+{
+#if defined(HAVE_EC)
+ ERL_NIF_TERM ret;
+ unsigned char *p;
+ EC_KEY* key = NULL;
+ int field_size = 0;
+ int i;
+ EC_GROUP *group;
+ const BIGNUM *priv_key;
+ EC_POINT *my_ecpoint = NULL;
+ EC_KEY *other_ecdh = NULL;
+
+ if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key))
+ return make_badarg_maybe(env);
+
+ group = EC_GROUP_dup(EC_KEY_get0_group(key));
+ priv_key = EC_KEY_get0_private_key(key);
+
+ if (!term2point(env, argv[0], group, &my_ecpoint)) {
+ goto out_err;
+ }
+
+ if ((other_ecdh = EC_KEY_new()) == NULL
+ || !EC_KEY_set_group(other_ecdh, group)
+ || !EC_KEY_set_private_key(other_ecdh, priv_key))
+ goto out_err;
+
+ field_size = EC_GROUP_get_degree(group);
+ if (field_size <= 0)
+ goto out_err;
+
+ p = enif_make_new_binary(env, (field_size+7)/8, &ret);
+ i = ECDH_compute_key(p, (field_size+7)/8, my_ecpoint, other_ecdh, NULL);
+
+ if (i < 0)
+ goto out_err;
+out:
+ if (group) EC_GROUP_free(group);
+ if (my_ecpoint) EC_POINT_free(my_ecpoint);
+ if (other_ecdh) EC_KEY_free(other_ecdh);
+ if (key) EC_KEY_free(key);
+
+ return ret;
+
+out_err:
+ ret = enif_make_badarg(env);
+ goto out;
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/ecdh.h b/lib/crypto/c_src/ecdh.h
new file mode 100644
index 0000000000..5ed331e676
--- /dev/null
+++ b/lib/crypto/c_src/ecdh.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_ECDH_H__
+#define E_ECDH_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_ECDH_H__ */
diff --git a/lib/crypto/c_src/eddsa.c b/lib/crypto/c_src/eddsa.c
new file mode 100644
index 0000000000..0fdada9677
--- /dev/null
+++ b/lib/crypto/c_src/eddsa.c
@@ -0,0 +1,51 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "eddsa.h"
+
+#ifdef HAVE_EDDSA
+int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey)
+{
+ /* key=[K] */
+ ERL_NIF_TERM head, tail, tail2, algo;
+ ErlNifBinary bin;
+ int type;
+
+ if (!enif_get_list_cell(env, key, &head, &tail)
+ || !enif_inspect_binary(env, head, &bin)
+ || !enif_get_list_cell(env, tail, &algo, &tail2)
+ || !enif_is_empty_list(env, tail2)) {
+ return 0;
+ }
+ if (algo == atom_ed25519) type = EVP_PKEY_ED25519;
+ else if (algo == atom_ed448) type = EVP_PKEY_ED448;
+ else
+ return 0;
+
+ if (public)
+ *pkey = EVP_PKEY_new_raw_public_key(type, NULL, bin.data, bin.size);
+ else
+ *pkey = EVP_PKEY_new_raw_private_key(type, NULL, bin.data, bin.size);
+
+ if (!pkey)
+ return 0;
+ return 1;
+}
+#endif
diff --git a/lib/crypto/c_src/eddsa.h b/lib/crypto/c_src/eddsa.h
new file mode 100644
index 0000000000..4b30247cab
--- /dev/null
+++ b/lib/crypto/c_src/eddsa.h
@@ -0,0 +1,30 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_EDDSA_H__
+#define E_EDDSA_H__ 1
+
+#include "common.h"
+
+#ifdef HAVE_EDDSA
+int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey);
+#endif
+
+#endif /* E_EDDSA_H__ */
diff --git a/lib/crypto/c_src/engine.c b/lib/crypto/c_src/engine.c
new file mode 100644
index 0000000000..dc8e1828ce
--- /dev/null
+++ b/lib/crypto/c_src/engine.c
@@ -0,0 +1,720 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "engine.h"
+
+#ifdef HAS_ENGINE_SUPPORT
+struct engine_ctx {
+ ENGINE *engine;
+ char *id;
+};
+
+static ErlNifResourceType* engine_ctx_rtype;
+
+static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i);
+static int zero_terminate(ErlNifBinary bin, char **buf);
+
+static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) {
+ PRINTF_ERR0("engine_ctx_dtor");
+ if(ctx->id) {
+ PRINTF_ERR1(" non empty ctx->id=%s", ctx->id);
+ enif_free(ctx->id);
+ } else
+ PRINTF_ERR0(" empty ctx->id=NULL");
+}
+
+int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e)
+{
+ ERL_NIF_TERM engine_res, key_id_term;
+ struct engine_ctx *ctx;
+ ErlNifBinary key_id_bin;
+
+ if (!enif_get_map_value(env, key, atom_engine, &engine_res) ||
+ !enif_get_resource(env, engine_res, engine_ctx_rtype, (void**)&ctx) ||
+ !enif_get_map_value(env, key, atom_key_id, &key_id_term) ||
+ !enif_inspect_binary(env, key_id_term, &key_id_bin)) {
+ return 0;
+ }
+ else {
+ *e = ctx->engine;
+ return zero_terminate(key_id_bin, id);
+ }
+}
+
+char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key) {
+ ERL_NIF_TERM tmp_term;
+ ErlNifBinary pwd_bin;
+ char *pwd = NULL;
+ if (enif_get_map_value(env, key, atom_password, &tmp_term) &&
+ enif_inspect_binary(env, tmp_term, &pwd_bin) &&
+ zero_terminate(pwd_bin, &pwd)
+ ) return pwd;
+
+ return NULL;
+}
+
+static int zero_terminate(ErlNifBinary bin, char **buf) {
+ *buf = enif_alloc(bin.size+1);
+ if (!*buf)
+ return 0;
+ memcpy(*buf, bin.data, bin.size);
+ *(*buf+bin.size) = 0;
+ return 1;
+}
+#endif /* HAS_ENGINE_SUPPORT */
+
+int init_engine_ctx(ErlNifEnv *env) {
+#ifdef HAS_ENGINE_SUPPORT
+ engine_ctx_rtype = enif_open_resource_type(env, NULL, "ENGINE_CTX",
+ (ErlNifResourceDtor*) engine_ctx_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (engine_ctx_rtype == NULL) {
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'ENGINE_CTX'");
+ return 0;
+ }
+#endif
+
+ return 1;
+}
+
+ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (EngineId) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret;
+ ErlNifBinary engine_id_bin;
+ char *engine_id;
+ ENGINE *engine;
+ struct engine_ctx *ctx;
+
+ // Get Engine Id
+ if(!enif_inspect_binary(env, argv[0], &engine_id_bin)) {
+ PRINTF_ERR0("engine_by_id_nif Leaved: badarg");
+ return enif_make_badarg(env);
+ } else {
+ engine_id = enif_alloc(engine_id_bin.size+1);
+ (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size);
+ engine_id[engine_id_bin.size] = '\0';
+ }
+
+ engine = ENGINE_by_id(engine_id);
+ if(!engine) {
+ enif_free(engine_id);
+ PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}");
+ return enif_make_tuple2(env, atom_error, atom_bad_engine_id);
+ }
+
+ ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
+ ctx->engine = engine;
+ ctx->id = engine_id;
+
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+
+ return enif_make_tuple2(env, atom_ok, ret);
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret = atom_ok;
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_init_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+ if (!ENGINE_init(ctx->engine)) {
+ //ERR_print_errors_fp(stderr);
+ PRINTF_ERR0("engine_init_nif Leaved: {error, engine_init_failed}");
+ return enif_make_tuple2(env, atom_error, atom_engine_init_failed);
+ }
+
+ return ret;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_free_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ ENGINE_free(ctx->engine);
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_finish_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ ENGINE_finish(ctx->engine);
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+#ifdef HAS_ENGINE_SUPPORT
+ ENGINE_load_dynamic();
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine, Commands) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret = atom_ok;
+ unsigned int cmds_len = 0;
+ char **cmds = NULL;
+ struct engine_ctx *ctx;
+ int i, optional = 0;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine));
+
+ // Get Command List
+ if(!enif_get_list_length(env, argv[1], &cmds_len)) {
+ PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Bad Command List");
+ return enif_make_badarg(env);
+ } else {
+ cmds_len *= 2; // Key-Value list from erlang
+ cmds = enif_alloc((cmds_len+1)*sizeof(char*));
+ if(get_engine_load_cmd_list(env, argv[1], cmds, 0)) {
+ PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Couldn't read Command List");
+ ret = enif_make_badarg(env);
+ goto error;
+ }
+ }
+
+ if(!enif_get_int(env, argv[2], &optional)) {
+ PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter optional not an integer");
+ return enif_make_badarg(env);
+ }
+
+ for(i = 0; i < cmds_len; i+=2) {
+ PRINTF_ERR2("Cmd: %s:%s\r\n",
+ cmds[i] ? cmds[i] : "(NULL)",
+ cmds[i+1] ? cmds[i+1] : "(NULL)");
+ if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) {
+ PRINTF_ERR2("Command failed: %s:%s\r\n",
+ cmds[i] ? cmds[i] : "(NULL)",
+ cmds[i+1] ? cmds[i+1] : "(NULL)");
+ //ENGINE_free(ctx->engine);
+ ret = enif_make_tuple2(env, atom_error, atom_ctrl_cmd_failed);
+ PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: {error, ctrl_cmd_failed}");
+ goto error;
+ }
+ }
+
+ error:
+ for(i = 0; cmds != NULL && cmds[i] != NULL; i++)
+ enif_free(cmds[i]);
+ enif_free(cmds);
+ return ret;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_add_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ if (!ENGINE_add(ctx->engine)) {
+ PRINTF_ERR0("engine_add_nif Leaved: {error, add_engine_failed}");
+ return enif_make_tuple2(env, atom_error, atom_add_engine_failed);
+ }
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_remove_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ if (!ENGINE_remove(ctx->engine)) {
+ PRINTF_ERR0("engine_remove_nif Leaved: {error, remove_engine_failed}");
+ return enif_make_tuple2(env, atom_error, atom_remove_engine_failed);
+ }
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine, EngineMethod) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+ unsigned int method;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_register_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+ // Get Method
+ if (!enif_get_uint(env, argv[1], &method)) {
+ PRINTF_ERR0("engine_register_nif Leaved: Parameter Method not an uint");
+ return enif_make_badarg(env);
+ }
+
+ switch(method)
+ {
+#ifdef ENGINE_METHOD_RSA
+ case ENGINE_METHOD_RSA:
+ if (!ENGINE_register_RSA(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DSA
+ case ENGINE_METHOD_DSA:
+ if (!ENGINE_register_DSA(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DH
+ case ENGINE_METHOD_DH:
+ if (!ENGINE_register_DH(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_RAND
+ case ENGINE_METHOD_RAND:
+ if (!ENGINE_register_RAND(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDH
+ case ENGINE_METHOD_ECDH:
+ if (!ENGINE_register_ECDH(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDSA
+ case ENGINE_METHOD_ECDSA:
+ if (!ENGINE_register_ECDSA(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_STORE
+ case ENGINE_METHOD_STORE:
+ if (!ENGINE_register_STORE(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_CIPHERS
+ case ENGINE_METHOD_CIPHERS:
+ if (!ENGINE_register_ciphers(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DIGESTS
+ case ENGINE_METHOD_DIGESTS:
+ if (!ENGINE_register_digests(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_METHS
+ case ENGINE_METHOD_PKEY_METHS:
+ if (!ENGINE_register_pkey_meths(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
+ case ENGINE_METHOD_PKEY_ASN1_METHS:
+ if (!ENGINE_register_pkey_asn1_meths(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+#ifdef ENGINE_METHOD_EC
+ case ENGINE_METHOD_EC:
+ if (!ENGINE_register_EC(ctx->engine))
+ return enif_make_tuple2(env, atom_error, atom_register_engine_failed);
+ break;
+#endif
+ default:
+ return enif_make_tuple2(env, atom_error, atom_engine_method_not_supported);
+ break;
+ }
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine, EngineMethod) */
+#ifdef HAS_ENGINE_SUPPORT
+ struct engine_ctx *ctx;
+ unsigned int method;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_unregister_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+ // Get Method
+ if (!enif_get_uint(env, argv[1], &method)) {
+ PRINTF_ERR0("engine_unregister_nif Leaved: Parameter Method not an uint");
+ return enif_make_badarg(env);
+ }
+
+ switch(method)
+ {
+#ifdef ENGINE_METHOD_RSA
+ case ENGINE_METHOD_RSA:
+ ENGINE_unregister_RSA(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DSA
+ case ENGINE_METHOD_DSA:
+ ENGINE_unregister_DSA(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DH
+ case ENGINE_METHOD_DH:
+ ENGINE_unregister_DH(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_RAND
+ case ENGINE_METHOD_RAND:
+ ENGINE_unregister_RAND(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDH
+ case ENGINE_METHOD_ECDH:
+ ENGINE_unregister_ECDH(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_ECDSA
+ case ENGINE_METHOD_ECDSA:
+ ENGINE_unregister_ECDSA(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_STORE
+ case ENGINE_METHOD_STORE:
+ ENGINE_unregister_STORE(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_CIPHERS
+ case ENGINE_METHOD_CIPHERS:
+ ENGINE_unregister_ciphers(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_DIGESTS
+ case ENGINE_METHOD_DIGESTS:
+ ENGINE_unregister_digests(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_METHS
+ case ENGINE_METHOD_PKEY_METHS:
+ ENGINE_unregister_pkey_meths(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
+ case ENGINE_METHOD_PKEY_ASN1_METHS:
+ ENGINE_unregister_pkey_asn1_meths(ctx->engine);
+ break;
+#endif
+#ifdef ENGINE_METHOD_EC
+ case ENGINE_METHOD_EC:
+ ENGINE_unregister_EC(ctx->engine);
+ break;
+#endif
+ default:
+ break;
+ }
+ return atom_ok;
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret;
+ ENGINE *engine;
+ ErlNifBinary engine_bin;
+ struct engine_ctx *ctx;
+
+ engine = ENGINE_get_first();
+ if(!engine) {
+ enif_alloc_binary(0, &engine_bin);
+ engine_bin.size = 0;
+ return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
+ }
+
+ ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
+ ctx->engine = engine;
+ ctx->id = NULL;
+
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+
+ return enif_make_tuple2(env, atom_ok, ret);
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM ret;
+ ENGINE *engine;
+ ErlNifBinary engine_bin;
+ struct engine_ctx *ctx, *next_ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_get_next_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+ engine = ENGINE_get_next(ctx->engine);
+ if (!engine) {
+ enif_alloc_binary(0, &engine_bin);
+ engine_bin.size = 0;
+ return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin));
+ }
+
+ next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx));
+ next_ctx->engine = engine;
+ next_ctx->id = NULL;
+
+ ret = enif_make_resource(env, next_ctx);
+ enif_release_resource(next_ctx);
+
+ return enif_make_tuple2(env, atom_ok, ret);
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ErlNifBinary engine_id_bin;
+ const char *engine_id;
+ int size;
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ engine_id = ENGINE_get_id(ctx->engine);
+ if (!engine_id) {
+ enif_alloc_binary(0, &engine_id_bin);
+ engine_id_bin.size = 0;
+ return enif_make_binary(env, &engine_id_bin);
+ }
+
+ size = strlen(engine_id);
+ enif_alloc_binary(size, &engine_id_bin);
+ engine_id_bin.size = size;
+ memcpy(engine_id_bin.data, engine_id, size);
+
+ return enif_make_binary(env, &engine_id_bin);
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Engine) */
+#ifdef HAS_ENGINE_SUPPORT
+ ErlNifBinary engine_name_bin;
+ const char *engine_name;
+ int size;
+ struct engine_ctx *ctx;
+
+ // Get Engine
+ if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) {
+ PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object");
+ return enif_make_badarg(env);
+ }
+
+ engine_name = ENGINE_get_name(ctx->engine);
+ if (!engine_name) {
+ enif_alloc_binary(0, &engine_name_bin);
+ engine_name_bin.size = 0;
+ return enif_make_binary(env, &engine_name_bin);
+ }
+
+ size = strlen(engine_name);
+ enif_alloc_binary(size, &engine_name_bin);
+ engine_name_bin.size = size;
+ memcpy(engine_name_bin.data, engine_name, size);
+
+ return enif_make_binary(env, &engine_name_bin);
+#else
+ return atom_notsup;
+#endif
+}
+
+#ifdef HAS_ENGINE_SUPPORT
+static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i)
+{
+ ERL_NIF_TERM head, tail;
+ const ERL_NIF_TERM *tmp_tuple;
+ ErlNifBinary tmpbin;
+ int arity;
+ char* tmpstr;
+
+ if(!enif_is_empty_list(env, term)) {
+ if(!enif_get_list_cell(env, term, &head, &tail)) {
+ cmds[i] = NULL;
+ return -1;
+ } else {
+ if(!enif_get_tuple(env, head, &arity, &tmp_tuple) || arity != 2) {
+ cmds[i] = NULL;
+ return -1;
+ } else {
+ if(!enif_inspect_binary(env, tmp_tuple[0], &tmpbin)) {
+ cmds[i] = NULL;
+ return -1;
+ } else {
+ tmpstr = enif_alloc(tmpbin.size+1);
+ (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
+ tmpstr[tmpbin.size] = '\0';
+ cmds[i++] = tmpstr;
+ }
+ if(!enif_inspect_binary(env, tmp_tuple[1], &tmpbin)) {
+ cmds[i] = NULL;
+ return -1;
+ } else {
+ if(tmpbin.size == 0)
+ cmds[i++] = NULL;
+ else {
+ tmpstr = enif_alloc(tmpbin.size+1);
+ (void) memcpy(tmpstr, tmpbin.data, tmpbin.size);
+ tmpstr[tmpbin.size] = '\0';
+ cmds[i++] = tmpstr;
+ }
+ }
+ return get_engine_load_cmd_list(env, tail, cmds, i);
+ }
+ }
+ } else {
+ cmds[i] = NULL;
+ return 0;
+ }
+}
+#endif /* HAS_ENGINE_SUPPORT */
+
+ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+#ifdef HAS_ENGINE_SUPPORT
+ ERL_NIF_TERM method_array[12];
+ int i = 0;
+
+#ifdef ENGINE_METHOD_RSA
+ method_array[i++] = atom_engine_method_rsa;
+#endif
+#ifdef ENGINE_METHOD_DSA
+ method_array[i++] = atom_engine_method_dsa;
+#endif
+#ifdef ENGINE_METHOD_DH
+ method_array[i++] = atom_engine_method_dh;
+#endif
+#ifdef ENGINE_METHOD_RAND
+ method_array[i++] = atom_engine_method_rand;
+#endif
+#ifdef ENGINE_METHOD_ECDH
+ method_array[i++] = atom_engine_method_ecdh;
+#endif
+#ifdef ENGINE_METHOD_ECDSA
+ method_array[i++] = atom_engine_method_ecdsa;
+#endif
+#ifdef ENGINE_METHOD_STORE
+ method_array[i++] = atom_engine_method_store;
+#endif
+#ifdef ENGINE_METHOD_CIPHERS
+ method_array[i++] = atom_engine_method_ciphers;
+#endif
+#ifdef ENGINE_METHOD_DIGESTS
+ method_array[i++] = atom_engine_method_digests;
+#endif
+#ifdef ENGINE_METHOD_PKEY_METHS
+ method_array[i++] = atom_engine_method_pkey_meths;
+#endif
+#ifdef ENGINE_METHOD_PKEY_ASN1_METHS
+ method_array[i++] = atom_engine_method_pkey_asn1_meths;
+#endif
+#ifdef ENGINE_METHOD_EC
+ method_array[i++] = atom_engine_method_ec;
+#endif
+
+ return enif_make_list_from_array(env, method_array, i);
+#else
+ return atom_notsup;
+#endif
+}
diff --git a/lib/crypto/c_src/engine.h b/lib/crypto/c_src/engine.h
new file mode 100644
index 0000000000..4a2eed9672
--- /dev/null
+++ b/lib/crypto/c_src/engine.h
@@ -0,0 +1,49 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_ENGINE_H__
+#define E_ENGINE_H__ 1
+
+#include "common.h"
+
+#ifdef HAS_ENGINE_SUPPORT
+int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e);
+char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key);
+#endif /* HAS_ENGINE_SUPPORT */
+
+int init_engine_ctx(ErlNifEnv *env);
+
+ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_ENGINE_H__ */
diff --git a/lib/crypto/c_src/evp.c b/lib/crypto/c_src/evp.c
new file mode 100644
index 0000000000..3c55ab630b
--- /dev/null
+++ b/lib/crypto/c_src/evp.c
@@ -0,0 +1,124 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "evp.h"
+
+ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+ /* (Curve, PeerBin, MyBin) */
+{
+#ifdef HAVE_ED_CURVE_DH
+ int type;
+ EVP_PKEY_CTX *ctx = NULL;
+ ErlNifBinary peer_bin, my_bin, key_bin;
+ EVP_PKEY *peer_key = NULL, *my_key = NULL;
+ size_t max_size;
+
+ if (argv[0] == atom_x25519) type = EVP_PKEY_X25519;
+ else if (argv[0] == atom_x448) type = EVP_PKEY_X448;
+ else return enif_make_badarg(env);
+
+ if (!enif_inspect_binary(env, argv[1], &peer_bin) ||
+ !enif_inspect_binary(env, argv[2], &my_bin))
+ goto return_badarg;
+
+ if (!(my_key = EVP_PKEY_new_raw_private_key(type, NULL, my_bin.data, my_bin.size)) ||
+ !(ctx = EVP_PKEY_CTX_new(my_key, NULL)))
+ goto return_badarg;
+
+ if (!EVP_PKEY_derive_init(ctx))
+ goto return_badarg;
+
+ if (!(peer_key = EVP_PKEY_new_raw_public_key(type, NULL, peer_bin.data, peer_bin.size)) ||
+ !EVP_PKEY_derive_set_peer(ctx, peer_key))
+ goto return_badarg;
+
+ if (!EVP_PKEY_derive(ctx, NULL, &max_size))
+ goto return_badarg;
+
+ if (!enif_alloc_binary(max_size, &key_bin) ||
+ !EVP_PKEY_derive(ctx, key_bin.data, &key_bin.size))
+ goto return_badarg;
+
+ if (key_bin.size < max_size) {
+ size_t actual_size = key_bin.size;
+ if (!enif_realloc_binary(&key_bin, actual_size))
+ goto return_badarg;
+ }
+
+ EVP_PKEY_free(my_key);
+ EVP_PKEY_free(peer_key);
+ EVP_PKEY_CTX_free(ctx);
+ return enif_make_binary(env, &key_bin);
+
+return_badarg:
+ if (my_key) EVP_PKEY_free(my_key);
+ if (peer_key) EVP_PKEY_free(peer_key);
+ if (ctx) EVP_PKEY_CTX_free(ctx);
+ return enif_make_badarg(env);
+#else
+ return atom_notsup;
+#endif
+}
+
+ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+/* (Curve) */
+{
+#ifdef HAVE_ED_CURVE_DH
+ int type;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *pkey = NULL;
+ ERL_NIF_TERM ret_pub, ret_prv;
+ size_t key_len;
+
+ if (argv[0] == atom_x25519) type = EVP_PKEY_X25519;
+ else if (argv[0] == atom_x448) type = EVP_PKEY_X448;
+ else return enif_make_badarg(env);
+
+ if (!(ctx = EVP_PKEY_CTX_new_id(type, NULL))) return enif_make_badarg(env);
+
+ if (!EVP_PKEY_keygen_init(ctx)) goto return_error;
+ if (!EVP_PKEY_keygen(ctx, &pkey)) goto return_error;
+
+ if (!EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len)) goto return_error;
+ if (!EVP_PKEY_get_raw_public_key(pkey,
+ enif_make_new_binary(env, key_len, &ret_pub),
+ &key_len))
+ goto return_error;
+
+ if (!EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len)) goto return_error;
+ if (!EVP_PKEY_get_raw_private_key(pkey,
+ enif_make_new_binary(env, key_len, &ret_prv),
+ &key_len))
+ goto return_error;
+
+ EVP_PKEY_free(pkey);
+ EVP_PKEY_CTX_free(ctx);
+ return enif_make_tuple2(env, ret_pub, ret_prv);
+
+return_error:
+ if (pkey) EVP_PKEY_free(pkey);
+ if (ctx) EVP_PKEY_CTX_free(ctx);
+ return atom_error;
+
+#else
+ return atom_notsup;
+#endif
+}
+
diff --git a/lib/crypto/c_src/evp.h b/lib/crypto/c_src/evp.h
new file mode 100644
index 0000000000..d767260262
--- /dev/null
+++ b/lib/crypto/c_src/evp.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_EVP_H__
+#define E_EVP_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_EVP_H__ */
diff --git a/lib/crypto/c_src/evp_compat.h b/lib/crypto/c_src/evp_compat.h
new file mode 100644
index 0000000000..98c861c45e
--- /dev/null
+++ b/lib/crypto/c_src/evp_compat.h
@@ -0,0 +1,196 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_EVP_COMPAT_H__
+#define E_EVP_COMPAT_H__ 1
+
+/*
+ * In OpenSSL 1.1.0, most structs are opaque. That means that
+ * the structs cannot be allocated as automatic variables on the
+ * C stack (because the size is unknown) and that it is necessary
+ * to use access functions.
+ *
+ * For backward compatibility to previous versions of OpenSSL, define
+ * on our versions of the new functions defined in 1.1.0 here, so that
+ * we don't have to sprinkle ifdefs throughout the code.
+ */
+
+static INLINE HMAC_CTX *HMAC_CTX_new(void);
+static INLINE void HMAC_CTX_free(HMAC_CTX *ctx);
+
+static INLINE HMAC_CTX *HMAC_CTX_new()
+{
+ HMAC_CTX *ctx = CRYPTO_malloc(sizeof(HMAC_CTX), __FILE__, __LINE__);
+ HMAC_CTX_init(ctx);
+ return ctx;
+}
+
+static INLINE void HMAC_CTX_free(HMAC_CTX *ctx)
+{
+ HMAC_CTX_cleanup(ctx);
+ CRYPTO_free(ctx);
+}
+
+#define EVP_MD_CTX_new() EVP_MD_CTX_create()
+#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx)
+
+static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb);
+
+static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb)
+{
+ return cb->arg;
+}
+
+static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d);
+static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d);
+static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q);
+static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q);
+static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp);
+static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp);
+
+static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
+{
+ r->n = n;
+ r->e = e;
+ r->d = d;
+ return 1;
+}
+
+static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
+{
+ *n = r->n;
+ *e = r->e;
+ *d = r->d;
+}
+
+static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q)
+{
+ r->p = p;
+ r->q = q;
+ return 1;
+}
+
+static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
+{
+ *p = r->p;
+ *q = r->q;
+}
+
+static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
+{
+ r->dmp1 = dmp1;
+ r->dmq1 = dmq1;
+ r->iqmp = iqmp;
+ return 1;
+}
+
+static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp)
+{
+ *dmp1 = r->dmp1;
+ *dmq1 = r->dmq1;
+ *iqmp = r->iqmp;
+}
+
+static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key);
+static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g);
+static INLINE void DSA_get0_pqg(const DSA *dsa,
+ const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
+static INLINE void DSA_get0_key(const DSA *dsa,
+ const BIGNUM **pub_key, const BIGNUM **priv_key);
+
+static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key)
+{
+ d->pub_key = pub_key;
+ d->priv_key = priv_key;
+ return 1;
+}
+
+static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+{
+ d->p = p;
+ d->q = q;
+ d->g = g;
+ return 1;
+}
+
+static INLINE void
+DSA_get0_pqg(const DSA *dsa, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
+{
+ *p = dsa->p;
+ *q = dsa->q;
+ *g = dsa->g;
+}
+
+static INLINE void
+DSA_get0_key(const DSA *dsa, const BIGNUM **pub_key, const BIGNUM **priv_key)
+{
+ if (pub_key) *pub_key = dsa->pub_key;
+ if (priv_key) *priv_key = dsa->priv_key;
+}
+
+
+
+static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key);
+static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g);
+static INLINE int DH_set_length(DH *dh, long length);
+static INLINE void DH_get0_pqg(const DH *dh,
+ const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
+static INLINE void DH_get0_key(const DH *dh,
+ const BIGNUM **pub_key, const BIGNUM **priv_key);
+
+static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key)
+{
+ dh->pub_key = pub_key;
+ dh->priv_key = priv_key;
+ return 1;
+}
+
+static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g)
+{
+ dh->p = p;
+ dh->q = q;
+ dh->g = g;
+ return 1;
+}
+
+static INLINE int DH_set_length(DH *dh, long length)
+{
+ dh->length = length;
+ return 1;
+}
+
+
+
+static INLINE void
+DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g)
+{
+ *p = dh->p;
+ *q = dh->q;
+ *g = dh->g;
+}
+
+static INLINE void
+DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key)
+{
+ if (pub_key) *pub_key = dh->pub_key;
+ if (priv_key) *priv_key = dh->priv_key;
+}
+
+#endif /* E_EVP_COMPAT_H__ */
diff --git a/lib/crypto/c_src/fips.c b/lib/crypto/c_src/fips.c
new file mode 100644
index 0000000000..b2d892d00b
--- /dev/null
+++ b/lib/crypto/c_src/fips.c
@@ -0,0 +1,52 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "fips.h"
+
+ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef FIPS_SUPPORT
+ return FIPS_mode() ? atom_enabled : atom_not_enabled;
+#else
+ return atom_not_supported;
+#endif
+}
+
+ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Boolean) */
+ if (argv[0] == atom_true) {
+#ifdef FIPS_SUPPORT
+ if (FIPS_mode_set(1)) {
+ return atom_true;
+ }
+#endif
+ PRINTF_ERR0("CRYPTO: Could not setup FIPS mode");
+ return atom_false;
+ } else if (argv[0] == atom_false) {
+#ifdef FIPS_SUPPORT
+ if (!FIPS_mode_set(0)) {
+ return atom_false;
+ }
+#endif
+ return atom_true;
+ } else {
+ return enif_make_badarg(env);
+ }
+}
diff --git a/lib/crypto/c_src/fips.h b/lib/crypto/c_src/fips.h
new file mode 100644
index 0000000000..9a436bd202
--- /dev/null
+++ b/lib/crypto/c_src/fips.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_FIPS_H__
+#define E_FIPS_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_FIPS_H__ */
diff --git a/lib/crypto/c_src/hash.c b/lib/crypto/c_src/hash.c
new file mode 100644
index 0000000000..52748dc933
--- /dev/null
+++ b/lib/crypto/c_src/hash.c
@@ -0,0 +1,405 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "hash.h"
+#include "digest.h"
+
+#define MD5_CTX_LEN (sizeof(MD5_CTX))
+#define MD4_CTX_LEN (sizeof(MD4_CTX))
+#define RIPEMD160_CTX_LEN (sizeof(RIPEMD160_CTX))
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+struct evp_md_ctx {
+ EVP_MD_CTX* ctx;
+};
+
+/* Define resource types for OpenSSL context structures. */
+static ErlNifResourceType* evp_md_ctx_rtype;
+
+static void evp_md_ctx_dtor(ErlNifEnv* env, struct evp_md_ctx *ctx) {
+ EVP_MD_CTX_free(ctx->ctx);
+}
+#endif
+
+int init_hash_ctx(ErlNifEnv* env) {
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ evp_md_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_MD_CTX",
+ (ErlNifResourceDtor*) evp_md_ctx_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (evp_md_ctx_rtype == NULL) {
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_MD_CTX'");
+ return 0;
+ }
+#endif
+
+ return 1;
+}
+
+ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Data) */
+ struct digest_type_t *digp = NULL;
+ const EVP_MD *md;
+ ErlNifBinary data;
+ ERL_NIF_TERM ret;
+ unsigned ret_size;
+
+ digp = get_digest_type(argv[0]);
+ if (!digp ||
+ !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+ md = digp->md.p;
+ if (!md) {
+ return atom_notsup;
+ }
+
+ ret_size = (unsigned)EVP_MD_size(md);
+ ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
+ if (!EVP_Digest(data.data, data.size,
+ enif_make_new_binary(env, ret_size, &ret), &ret_size,
+ md, NULL)) {
+ return atom_notsup;
+ }
+ ASSERT(ret_size == (unsigned)EVP_MD_size(md));
+
+ CONSUME_REDS(env, data);
+ return ret;
+}
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+
+ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+ struct digest_type_t *digp = NULL;
+ struct evp_md_ctx *ctx;
+ ERL_NIF_TERM ret;
+
+ digp = get_digest_type(argv[0]);
+ if (!digp) {
+ return enif_make_badarg(env);
+ }
+ if (!digp->md.p) {
+ return atom_notsup;
+ }
+
+ ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx));
+ ctx->ctx = EVP_MD_CTX_new();
+ if (!EVP_DigestInit(ctx->ctx, digp->md.p)) {
+ enif_release_resource(ctx);
+ return atom_notsup;
+ }
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
+}
+
+ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ struct evp_md_ctx *ctx, *new_ctx;
+ ErlNifBinary data;
+ ERL_NIF_TERM ret;
+
+ if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx) ||
+ !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+
+ new_ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx));
+ new_ctx->ctx = EVP_MD_CTX_new();
+ if (!EVP_MD_CTX_copy(new_ctx->ctx, ctx->ctx) ||
+ !EVP_DigestUpdate(new_ctx->ctx, data.data, data.size)) {
+ enif_release_resource(new_ctx);
+ return atom_notsup;
+ }
+
+ ret = enif_make_resource(env, new_ctx);
+ enif_release_resource(new_ctx);
+ CONSUME_REDS(env, data);
+ return ret;
+}
+
+ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) */
+ struct evp_md_ctx *ctx;
+ EVP_MD_CTX *new_ctx;
+ ERL_NIF_TERM ret;
+ unsigned ret_size;
+
+ if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx)) {
+ return enif_make_badarg(env);
+ }
+
+ ret_size = (unsigned)EVP_MD_CTX_size(ctx->ctx);
+ ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE);
+
+ new_ctx = EVP_MD_CTX_new();
+ if (!EVP_MD_CTX_copy(new_ctx, ctx->ctx) ||
+ !EVP_DigestFinal(new_ctx,
+ enif_make_new_binary(env, ret_size, &ret),
+ &ret_size)) {
+ EVP_MD_CTX_free(new_ctx);
+ return atom_notsup;
+ }
+ EVP_MD_CTX_free(new_ctx);
+ ASSERT(ret_size == (unsigned)EVP_MD_CTX_size(ctx->ctx));
+
+ return ret;
+}
+
+#else /* if OPENSSL_VERSION_NUMBER < 1.0 */
+
+ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type) */
+ typedef int (*init_fun)(unsigned char*);
+ struct digest_type_t *digp = NULL;
+ ERL_NIF_TERM ctx;
+ size_t ctx_size = 0;
+ init_fun ctx_init = 0;
+
+ digp = get_digest_type(argv[0]);
+ if (!digp) {
+ return enif_make_badarg(env);
+ }
+ if (!digp->md.p) {
+ return atom_notsup;
+ }
+
+ switch (EVP_MD_type(digp->md.p))
+ {
+ case NID_md4:
+ ctx_size = MD4_CTX_LEN;
+ ctx_init = (init_fun)(&MD4_Init);
+ break;
+ case NID_md5:
+ ctx_size = MD5_CTX_LEN;
+ ctx_init = (init_fun)(&MD5_Init);
+ break;
+ case NID_ripemd160:
+ ctx_size = RIPEMD160_CTX_LEN;
+ ctx_init = (init_fun)(&RIPEMD160_Init);
+ break;
+ case NID_sha1:
+ ctx_size = sizeof(SHA_CTX);
+ ctx_init = (init_fun)(&SHA1_Init);
+ break;
+#ifdef HAVE_SHA224
+ case NID_sha224:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_init = (init_fun)(&SHA224_Init);
+ break;
+#endif
+#ifdef HAVE_SHA256
+ case NID_sha256:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_init = (init_fun)(&SHA256_Init);
+ break;
+#endif
+#ifdef HAVE_SHA384
+ case NID_sha384:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_init = (init_fun)(&SHA384_Init);
+ break;
+#endif
+#ifdef HAVE_SHA512
+ case NID_sha512:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_init = (init_fun)(&SHA512_Init);
+ break;
+#endif
+ default:
+ return atom_notsup;
+ }
+ ASSERT(ctx_size);
+ ASSERT(ctx_init);
+
+ ctx_init(enif_make_new_binary(env, ctx_size, &ctx));
+ return enif_make_tuple2(env, argv[0], ctx);
+}
+
+ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* ({Type, Context}, Data) */
+ typedef int (*update_fun)(unsigned char*, const unsigned char*, size_t);
+ ERL_NIF_TERM new_ctx;
+ ErlNifBinary ctx, data;
+ const ERL_NIF_TERM *tuple;
+ int arity;
+ struct digest_type_t *digp = NULL;
+ unsigned char *ctx_buff;
+ size_t ctx_size = 0;
+ update_fun ctx_update = 0;
+
+ if (!enif_get_tuple(env, argv[0], &arity, &tuple) ||
+ arity != 2 ||
+ !(digp = get_digest_type(tuple[0])) ||
+ !enif_inspect_binary(env, tuple[1], &ctx) ||
+ !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+ if (!digp->md.p) {
+ return atom_notsup;
+ }
+
+ switch (EVP_MD_type(digp->md.p))
+ {
+ case NID_md4:
+ ctx_size = MD4_CTX_LEN;
+ ctx_update = (update_fun)(&MD4_Update);
+ break;
+ case NID_md5:
+ ctx_size = MD5_CTX_LEN;
+ ctx_update = (update_fun)(&MD5_Update);
+ break;
+ case NID_ripemd160:
+ ctx_size = RIPEMD160_CTX_LEN;
+ ctx_update = (update_fun)(&RIPEMD160_Update);
+ break;
+ case NID_sha1:
+ ctx_size = sizeof(SHA_CTX);
+ ctx_update = (update_fun)(&SHA1_Update);
+ break;
+#ifdef HAVE_SHA224
+ case NID_sha224:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_update = (update_fun)(&SHA224_Update);
+ break;
+#endif
+#ifdef HAVE_SHA256
+ case NID_sha256:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_update = (update_fun)(&SHA256_Update);
+ break;
+#endif
+#ifdef HAVE_SHA384
+ case NID_sha384:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_update = (update_fun)(&SHA384_Update);
+ break;
+#endif
+#ifdef HAVE_SHA512
+ case NID_sha512:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_update = (update_fun)(&SHA512_Update);
+ break;
+#endif
+ default:
+ return atom_notsup;
+ }
+ ASSERT(ctx_size);
+ ASSERT(ctx_update);
+
+ if (ctx.size != ctx_size) {
+ return enif_make_badarg(env);
+ }
+
+ ctx_buff = enif_make_new_binary(env, ctx_size, &new_ctx);
+ memcpy(ctx_buff, ctx.data, ctx_size);
+ ctx_update(ctx_buff, data.data, data.size);
+
+ CONSUME_REDS(env, data);
+ return enif_make_tuple2(env, tuple[0], new_ctx);
+}
+
+ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* ({Type, Context}) */
+ typedef int (*final_fun)(unsigned char*, void*);
+ ERL_NIF_TERM ret;
+ ErlNifBinary ctx;
+ const ERL_NIF_TERM *tuple;
+ int arity;
+ struct digest_type_t *digp = NULL;
+ const EVP_MD *md;
+ void *new_ctx;
+ size_t ctx_size = 0;
+ final_fun ctx_final = 0;
+
+ if (!enif_get_tuple(env, argv[0], &arity, &tuple) ||
+ arity != 2 ||
+ !(digp = get_digest_type(tuple[0])) ||
+ !enif_inspect_binary(env, tuple[1], &ctx)) {
+ return enif_make_badarg(env);
+ }
+ md = digp->md.p;
+ if (!md) {
+ return atom_notsup;
+ }
+
+ switch (EVP_MD_type(md))
+ {
+ case NID_md4:
+ ctx_size = MD4_CTX_LEN;
+ ctx_final = (final_fun)(&MD4_Final);
+ break;
+ case NID_md5:
+ ctx_size = MD5_CTX_LEN;
+ ctx_final = (final_fun)(&MD5_Final);
+ break;
+ case NID_ripemd160:
+ ctx_size = RIPEMD160_CTX_LEN;
+ ctx_final = (final_fun)(&RIPEMD160_Final);
+ break;
+ case NID_sha1:
+ ctx_size = sizeof(SHA_CTX);
+ ctx_final = (final_fun)(&SHA1_Final);
+ break;
+#ifdef HAVE_SHA224
+ case NID_sha224:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_final = (final_fun)(&SHA224_Final);
+ break;
+#endif
+#ifdef HAVE_SHA256
+ case NID_sha256:
+ ctx_size = sizeof(SHA256_CTX);
+ ctx_final = (final_fun)(&SHA256_Final);
+ break;
+#endif
+#ifdef HAVE_SHA384
+ case NID_sha384:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_final = (final_fun)(&SHA384_Final);
+ break;
+#endif
+#ifdef HAVE_SHA512
+ case NID_sha512:
+ ctx_size = sizeof(SHA512_CTX);
+ ctx_final = (final_fun)(&SHA512_Final);
+ break;
+#endif
+ default:
+ return atom_notsup;
+ }
+ ASSERT(ctx_size);
+ ASSERT(ctx_final);
+
+ if (ctx.size != ctx_size) {
+ return enif_make_badarg(env);
+ }
+
+ new_ctx = enif_alloc(ctx_size);
+ memcpy(new_ctx, ctx.data, ctx_size);
+ ctx_final(enif_make_new_binary(env, (size_t)EVP_MD_size(md), &ret),
+ new_ctx);
+ enif_free(new_ctx);
+
+ return ret;
+}
+
+#endif /* OPENSSL_VERSION_NUMBER < 1.0 */
diff --git a/lib/crypto/c_src/hash.h b/lib/crypto/c_src/hash.h
new file mode 100644
index 0000000000..8bae07f39a
--- /dev/null
+++ b/lib/crypto/c_src/hash.h
@@ -0,0 +1,33 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_HASH_H__
+#define E_HASH_H__ 1
+
+#include "common.h"
+
+int init_hash_ctx(ErlNifEnv *env);
+
+ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_HASH_H__ */
diff --git a/lib/crypto/c_src/hmac.c b/lib/crypto/c_src/hmac.c
new file mode 100644
index 0000000000..143cde90e1
--- /dev/null
+++ b/lib/crypto/c_src/hmac.c
@@ -0,0 +1,185 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "hmac.h"
+#include "digest.h"
+
+struct hmac_context
+{
+ ErlNifMutex* mtx;
+ int alive;
+ HMAC_CTX* ctx;
+};
+
+static ErlNifResourceType* hmac_context_rtype;
+
+static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*);
+
+int init_hmac_ctx(ErlNifEnv *env) {
+ hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context",
+ (ErlNifResourceDtor*) hmac_context_dtor,
+ ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER,
+ NULL);
+ if (hmac_context_rtype == NULL) {
+ PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'");
+ return 0;
+ }
+ return 1;
+}
+
+ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */
+ struct digest_type_t *digp = NULL;
+ ErlNifBinary key, data;
+ unsigned char buff[EVP_MAX_MD_SIZE];
+ unsigned size = 0, req_size = 0;
+ ERL_NIF_TERM ret;
+
+ digp = get_digest_type(argv[0]);
+ if (!digp ||
+ !enif_inspect_iolist_as_binary(env, argv[1], &key) ||
+ !enif_inspect_iolist_as_binary(env, argv[2], &data) ||
+ (argc == 4 && !enif_get_uint(env, argv[3], &req_size))) {
+ return enif_make_badarg(env);
+ }
+
+ if (!digp->md.p ||
+ !HMAC(digp->md.p,
+ key.data, key.size,
+ data.data, data.size,
+ buff, &size)) {
+ return atom_notsup;
+ }
+ ASSERT(0 < size && size <= EVP_MAX_MD_SIZE);
+ CONSUME_REDS(env, data);
+
+ if (argc == 4) {
+ if (req_size <= size) {
+ size = req_size;
+ }
+ else {
+ return enif_make_badarg(env);
+ }
+ }
+ memcpy(enif_make_new_binary(env, size, &ret), buff, size);
+ return ret;
+}
+
+static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj)
+{
+ if (obj->alive) {
+ HMAC_CTX_free(obj->ctx);
+ obj->alive = 0;
+ }
+ enif_mutex_destroy(obj->mtx);
+}
+
+ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Key) */
+ struct digest_type_t *digp = NULL;
+ ErlNifBinary key;
+ ERL_NIF_TERM ret;
+ struct hmac_context *obj;
+
+ digp = get_digest_type(argv[0]);
+ if (!digp ||
+ !enif_inspect_iolist_as_binary(env, argv[1], &key)) {
+ return enif_make_badarg(env);
+ }
+ if (!digp->md.p) {
+ return atom_notsup;
+ }
+
+ obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context));
+ obj->mtx = enif_mutex_create("crypto.hmac");
+ obj->alive = 1;
+ obj->ctx = HMAC_CTX_new();
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ // Check the return value of HMAC_Init: it may fail in FIPS mode
+ // for disabled algorithms
+ if (!HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL)) {
+ enif_release_resource(obj);
+ return atom_notsup;
+ }
+#else
+ HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL);
+#endif
+
+ ret = enif_make_resource(env, obj);
+ enif_release_resource(obj);
+ return ret;
+}
+
+ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ ErlNifBinary data;
+ struct hmac_context* obj;
+
+ if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+ enif_mutex_lock(obj->mtx);
+ if (!obj->alive) {
+ enif_mutex_unlock(obj->mtx);
+ return enif_make_badarg(env);
+ }
+ HMAC_Update(obj->ctx, data.data, data.size);
+ enif_mutex_unlock(obj->mtx);
+
+ CONSUME_REDS(env,data);
+ return argv[0];
+}
+
+ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) or (Context, HashLen) */
+ ERL_NIF_TERM ret;
+ struct hmac_context* obj;
+ unsigned char mac_buf[EVP_MAX_MD_SIZE];
+ unsigned char * mac_bin;
+ unsigned int req_len = 0;
+ unsigned int mac_len;
+
+ if (!enif_get_resource(env,argv[0],hmac_context_rtype, (void**)&obj)
+ || (argc == 2 && !enif_get_uint(env, argv[1], &req_len))) {
+ return enif_make_badarg(env);
+ }
+
+ enif_mutex_lock(obj->mtx);
+ if (!obj->alive) {
+ enif_mutex_unlock(obj->mtx);
+ return enif_make_badarg(env);
+ }
+
+ HMAC_Final(obj->ctx, mac_buf, &mac_len);
+ HMAC_CTX_free(obj->ctx);
+ obj->alive = 0;
+ enif_mutex_unlock(obj->mtx);
+
+ if (argc == 2 && req_len < mac_len) {
+ /* Only truncate to req_len bytes if asked. */
+ mac_len = req_len;
+ }
+ mac_bin = enif_make_new_binary(env, mac_len, &ret);
+ memcpy(mac_bin, mac_buf, mac_len);
+
+ return ret;
+}
+
diff --git a/lib/crypto/c_src/hmac.h b/lib/crypto/c_src/hmac.h
new file mode 100644
index 0000000000..1f0e0ca632
--- /dev/null
+++ b/lib/crypto/c_src/hmac.h
@@ -0,0 +1,33 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_HMAC_H__
+#define E_HMAC_H__ 1
+
+#include "common.h"
+
+int init_hmac_ctx(ErlNifEnv *env);
+
+ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_HMAC_H__ */
diff --git a/lib/crypto/c_src/info.c b/lib/crypto/c_src/info.c
new file mode 100644
index 0000000000..3f3194081d
--- /dev/null
+++ b/lib/crypto/c_src/info.c
@@ -0,0 +1,81 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "info.h"
+
+#ifdef HAVE_DYNAMIC_CRYPTO_LIB
+
+# if defined(DEBUG)
+char *crypto_callback_name = "crypto_callback.debug";
+# elif defined(VALGRIND)
+char *crypto_callback_name = "crypto_callback.valgrind";
+# else
+char *crypto_callback_name = "crypto_callback";
+# endif
+
+int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile)
+{
+ int i;
+
+ for (i = bin->size; i > 0; i--) {
+ if (bin->data[i-1] == '/')
+ break;
+ }
+ if (i + strlen(newfile) >= bufsz) {
+ PRINTF_ERR0("CRYPTO: lib name too long");
+ return 0;
+ }
+ memcpy(buf, bin->data, i);
+ strcpy(buf+i, newfile);
+ return 1;
+}
+
+void error_handler(void* null, const char* errstr)
+{
+ PRINTF_ERR1("CRYPTO LOADING ERROR: '%s'", errstr);
+}
+#endif /* HAVE_DYNAMIC_CRYPTO_LIB */
+
+ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */
+
+ static const char libname[] = "OpenSSL";
+ unsigned name_sz = strlen(libname);
+ const char* ver = SSLeay_version(SSLEAY_VERSION);
+ unsigned ver_sz = strlen(ver);
+ ERL_NIF_TERM name_term, ver_term;
+ int ver_num = OPENSSL_VERSION_NUMBER;
+ /* R16:
+ * Ignore library version number from SSLeay() and instead show header
+ * version. Otherwise user might try to call a function that is implemented
+ * by a newer library but not supported by the headers used at compile time.
+ * Example: DES_ede3_cfb_encrypt in 0.9.7i but not in 0.9.7d.
+ *
+ * Version string is still from library though.
+ */
+
+ memcpy(enif_make_new_binary(env, name_sz, &name_term), libname, name_sz);
+ memcpy(enif_make_new_binary(env, ver_sz, &ver_term), ver, ver_sz);
+
+ return enif_make_list1(env, enif_make_tuple3(env, name_term,
+ enif_make_int(env, ver_num),
+ ver_term));
+}
diff --git a/lib/crypto/c_src/info.h b/lib/crypto/c_src/info.h
new file mode 100644
index 0000000000..4f8822ddd7
--- /dev/null
+++ b/lib/crypto/c_src/info.h
@@ -0,0 +1,35 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_INFO_H__
+#define E_INFO_H__ 1
+
+#include "common.h"
+
+#ifdef HAVE_DYNAMIC_CRYPTO_LIB
+extern char *crypto_callback_name;
+
+int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile);
+void error_handler(void* null, const char* errstr);
+#endif
+
+ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_INFO_H__ */
diff --git a/lib/crypto/c_src/math.c b/lib/crypto/c_src/math.c
new file mode 100644
index 0000000000..7d7d146ca9
--- /dev/null
+++ b/lib/crypto/c_src/math.c
@@ -0,0 +1,43 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "math.h"
+
+ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data1, Data2) */
+ ErlNifBinary d1, d2;
+ unsigned char* ret_ptr;
+ int i;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &d1)
+ || !enif_inspect_iolist_as_binary(env,argv[1], &d2)
+ || d1.size != d2.size) {
+ return enif_make_badarg(env);
+ }
+ ret_ptr = enif_make_new_binary(env, d1.size, &ret);
+
+ for (i=0; i<d1.size; i++) {
+ ret_ptr[i] = d1.data[i] ^ d2.data[i];
+ }
+ CONSUME_REDS(env,d1);
+ return ret;
+}
+
diff --git a/lib/crypto/c_src/math.h b/lib/crypto/c_src/math.h
new file mode 100644
index 0000000000..b8d68ea654
--- /dev/null
+++ b/lib/crypto/c_src/math.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_MATH_H__
+#define E_MATH_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_MATH_H__ */
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
new file mode 100644
index 0000000000..2e5f5b22c1
--- /dev/null
+++ b/lib/crypto/c_src/openssl_config.h
@@ -0,0 +1,337 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_OPENSSL_CONFIG_H__
+#define E_OPENSSL_CONFIG_H__ 1
+
+#define OPENSSL_THREAD_DEFINES
+#include <openssl/opensslconf.h>
+
+#include <openssl/crypto.h>
+#ifndef OPENSSL_NO_DES
+#include <openssl/des.h>
+#endif /* #ifndef OPENSSL_NO_DES */
+/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
+#include <openssl/dsa.h>
+#include <openssl/rsa.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+#include <openssl/md4.h>
+#include <openssl/sha.h>
+#include <openssl/ripemd.h>
+#include <openssl/bn.h>
+#include <openssl/objects.h>
+#ifndef OPENSSL_NO_RC4
+ #include <openssl/rc4.h>
+#endif /* OPENSSL_NO_RC4 */
+#ifndef OPENSSL_NO_RC2
+ #include <openssl/rc2.h>
+#endif
+#include <openssl/blowfish.h>
+#include <openssl/rand.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/err.h>
+
+/* Helper macro to construct a OPENSSL_VERSION_NUMBER.
+ * See openssl/opensslv.h
+ */
+#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \
+ ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf)
+
+#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \
+ PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1))
+
+
+/* LibreSSL was cloned from OpenSSL 1.0.1g and claims to be API and BPI compatible
+ * with 1.0.1.
+ *
+ * LibreSSL has the same names on include files and symbols as OpenSSL, but defines
+ * the OPENSSL_VERSION_NUMBER to be >= 2.0.0
+ *
+ * Therefor works tests like this as intendend:
+ * OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+ * (The test is for example "2.4.2" >= "1.0.0" although the test
+ * with the cloned OpenSSL test would be "1.0.1" >= "1.0.0")
+ *
+ * But tests like this gives wrong result:
+ * OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+ * (The test is false since "2.4.2" < "1.1.0". It should have been
+ * true because the LibreSSL API version is "1.0.1")
+ *
+ */
+
+#ifdef LIBRESSL_VERSION_NUMBER
+/* A macro to test on in this file */
+#define HAS_LIBRESSL
+#endif
+
+#ifdef HAS_LIBRESSL
+/* LibreSSL dislikes FIPS */
+# ifdef FIPS_SUPPORT
+# undef FIPS_SUPPORT
+# endif
+
+# if LIBRESSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(2,7,0)
+/* LibreSSL wants the 1.0.1 API */
+# define NEED_EVP_COMPATIBILITY_FUNCTIONS
+# endif
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+# define NEED_EVP_COMPATIBILITY_FUNCTIONS
+#endif
+
+
+#ifndef HAS_LIBRESSL
+# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+# define HAS_EVP_PKEY_CTX
+# endif
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+#include <openssl/modes.h>
+#endif
+
+#include "crypto_callback.h"
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA224) && defined(NID_sha224) \
+ && !defined(OPENSSL_NO_SHA256) /* disabled like this in my sha.h (?) */
+# define HAVE_SHA224
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA256) && defined(NID_sha256)
+# define HAVE_SHA256
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA384) && defined(NID_sha384)\
+ && !defined(OPENSSL_NO_SHA512) /* disabled like this in my sha.h (?) */
+# define HAVE_SHA384
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \
+ && !defined(OPENSSL_NO_SHA512) && defined(NID_sha512)
+# define HAVE_SHA512
+#endif
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,7,'e')
+# define HAVE_DES_ede3_cfb_encrypt
+#endif
+
+// SHA3:
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1)
+// An error in beta releases of 1.1.1 fixed in production release
+# ifdef NID_sha3_224
+# define HAVE_SHA3_224
+# endif
+# ifdef NID_sha3_256
+# define HAVE_SHA3_256
+# endif
+#endif
+# ifdef NID_sha3_384
+# define HAVE_SHA3_384
+# endif
+# ifdef NID_sha3_512
+# define HAVE_SHA3_512
+# endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \
+ && !defined(OPENSSL_NO_EC) \
+ && !defined(OPENSSL_NO_ECDH) \
+ && !defined(OPENSSL_NO_ECDSA)
+# define HAVE_EC
+#endif
+
+// (test for >= 1.1.1pre8)
+#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \
+ && !defined(HAS_LIBRESSL) \
+ && defined(HAVE_EC)
+# define HAVE_ED_CURVE_DH
+# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1))
+# define HAVE_EDDSA
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'c')
+# define HAVE_AES_IGE
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1)
+# define HAVE_EVP_AES_CTR
+# define HAVE_AEAD
+# define HAVE_GCM
+# define HAVE_CCM
+# define HAVE_CMAC
+# if defined(RSA_PKCS1_OAEP_PADDING)
+# define HAVE_RSA_OAEP_PADDING
+# endif
+# define HAVE_RSA_MGF1_MD
+# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d')
+# define HAVE_GCM_EVP_DECRYPT_BUG
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0)
+# ifndef HAS_LIBRESSL
+# define HAVE_CHACHA20
+# define HAVE_CHACHA20_POLY1305
+# define HAVE_RSA_OAEP_MD
+# endif
+#endif
+
+// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8
+#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7)
+# ifndef HAS_LIBRESSL
+# define HAVE_POLY1305
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l')
+# define HAVE_ECB_IVEC_BUG
+#endif
+
+#ifndef HAS_LIBRESSL
+# ifdef RSA_SSLV23_PADDING
+# define HAVE_RSA_SSLV23_PADDING
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0)
+# ifdef RSA_PKCS1_PSS_PADDING
+# define HAVE_RSA_PKCS1_PSS_PADDING
+# endif
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'h') \
+ && defined(HAVE_EC)
+/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h
+ So if EC is disabled, you can't use Engine either....
+*/
+# define HAS_ENGINE_SUPPORT
+#endif
+
+
+#if defined(HAS_ENGINE_SUPPORT)
+# include <openssl/engine.h>
+#endif
+
+#if defined(HAVE_CMAC)
+#include <openssl/cmac.h>
+#endif
+
+#if defined(HAVE_EC)
+#include <openssl/ec.h>
+#include <openssl/ecdh.h>
+#include <openssl/ecdsa.h>
+#endif
+
+#ifdef VALGRIND
+ # include <valgrind/memcheck.h>
+
+/* libcrypto mixes supplied buffer contents into its entropy pool,
+ which makes valgrind complain about the use of uninitialized data.
+ We use this valgrind "request" to make sure that no such seemingly
+ undefined data is returned.
+*/
+ # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \
+ VALGRIND_MAKE_MEM_DEFINED(ptr,size)
+
+ # define ERL_VALGRIND_ASSERT_MEM_DEFINED(Ptr,Size) \
+ do { \
+ int __erl_valgrind_mem_defined = VALGRIND_CHECK_MEM_IS_DEFINED((Ptr),(Size)); \
+ if (__erl_valgrind_mem_defined != 0) { \
+ fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%ld) failed at %s:%d\r\n", \
+ (Ptr),(long)(Size), __FILE__, __LINE__); \
+ abort(); \
+ } \
+ } while (0)
+
+#else
+ # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size)
+ # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size)
+#endif
+
+#ifdef DEBUG
+ # define ASSERT(e) \
+ ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
+ #e, __FILE__, __LINE__), abort(), 0)))
+#else
+ # define ASSERT(e) ((void) 1)
+#endif
+
+#ifdef __GNUC__
+ # define INLINE __inline__
+#elif defined(__WIN32__)
+ # define INLINE __forceinline
+#else
+ # define INLINE
+#endif
+
+
+#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \
+ (((unsigned char*) (s))[1] << 16) | \
+ (((unsigned char*) (s))[2] << 8) | \
+ (((unsigned char*) (s))[3]))
+
+#define put_int32(s,i) \
+{ (s)[0] = (char)(((i) >> 24) & 0xff);\
+ (s)[1] = (char)(((i) >> 16) & 0xff);\
+ (s)[2] = (char)(((i) >> 8) & 0xff);\
+ (s)[3] = (char)((i) & 0xff);\
+}
+
+/* This shall correspond to the similar macro in crypto.erl */
+/* Current value is: erlang:system_info(context_reductions) * 10 */
+#define MAX_BYTES_TO_NIF 20000
+
+#define CONSUME_REDS(NifEnv, Ibin) \
+do { \
+ int _cost = ((Ibin).size * 100) / MAX_BYTES_TO_NIF;\
+ if (_cost) { \
+ (void) enif_consume_timeslice((NifEnv), \
+ (_cost > 100) ? 100 : _cost); \
+ } \
+ } while (0)
+
+#ifdef NEED_EVP_COMPATIBILITY_FUNCTIONS
+# include "evp_compat.h"
+#else
+# define HAVE_OPAQUE_BN_GENCB
+#endif
+
+/*
+#define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n")
+#define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1)
+#define PRINTF_ERR2(FMT, A1, A2) enif_fprintf(stderr, FMT "\n", A1, A2)
+*/
+
+#define PRINTF_ERR0(FMT)
+#define PRINTF_ERR1(FMT,A1)
+#define PRINTF_ERR2(FMT,A1,A2)
+
+#ifdef FIPS_SUPPORT
+/* In FIPS mode non-FIPS algorithms are disabled and return badarg. */
+#define CHECK_NO_FIPS_MODE() { if (FIPS_mode()) return atom_notsup; }
+#else
+#define CHECK_NO_FIPS_MODE()
+#endif
+
+#endif /* E_OPENSSL_CONFIG_H__ */
diff --git a/lib/crypto/c_src/otp_test_engine.c b/lib/crypto/c_src/otp_test_engine.c
index 34c825059f..2c8cce094e 100644
--- a/lib/crypto/c_src/otp_test_engine.c
+++ b/lib/crypto/c_src/otp_test_engine.c
@@ -35,7 +35,12 @@
#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) \
|| defined(LIBRESSL_VERSION_NUMBER)
-#define OLD
+# define OLD
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0) \
+ && !defined(LIBRESSL_VERSION_NUMBER)
+# define FAKE_RSA_IMPL
#endif
#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \
@@ -56,13 +61,41 @@
static const char *test_engine_id = "MD5";
static const char *test_engine_name = "MD5 test engine";
-/* The callback that does the job of fetching keys on demand by the Engine */
-EVP_PKEY* test_key_load(ENGINE *er, const char *id, UI_METHOD *ui_method, void *callback_data);
+#if defined(FAKE_RSA_IMPL)
+/*-------- test of private/public keys and RSA in engine ---------*/
+static RSA_METHOD *test_rsa_method = NULL;
+
+/* Our on "RSA" implementation */
+static int test_rsa_sign(int dtype, const unsigned char *m,
+ unsigned int m_len, unsigned char *sigret,
+ unsigned int *siglen, const RSA *rsa);
+static int test_rsa_verify(int dtype, const unsigned char *m,
+ unsigned int m_len, const unsigned char *sigret,
+ unsigned int siglen, const RSA *rsa);
+static int test_rsa_free(RSA *rsa);
+#endif /* if defined(FAKE_RSA_IMPL) */
+
+/* The callbacks that does the job of fetching keys on demand by the Engine */
+EVP_PKEY* test_privkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data);
+EVP_PKEY* test_pubkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data);
+EVP_PKEY* test_key_load(ENGINE *er, const char *id, UI_METHOD *ui_method, void *callback_data, int priv);
+
+/*----------------------------------------------------------------*/
static int test_init(ENGINE *e) {
printf("OTP Test Engine Initializatzion!\r\n");
+#if defined(FAKE_RSA_IMPL)
+ if ( !RSA_meth_set_finish(test_rsa_method, test_rsa_free)
+ || !RSA_meth_set_sign(test_rsa_method, test_rsa_sign)
+ || !RSA_meth_set_verify(test_rsa_method, test_rsa_verify)
+ ) {
+ fprintf(stderr, "Setup RSA_METHOD failed\r\n");
+ return 0;
+ }
+#endif /* if defined(FAKE_RSA_IMPL) */
+
/* Load all digest and cipher algorithms. Needed for password protected private keys */
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
@@ -79,6 +112,19 @@ static void add_test_data(unsigned char *md, unsigned int len)
}
}
+#if defined(FAKE_RSA_IMPL)
+static int chk_test_data(const unsigned char *md, unsigned int len)
+{
+ unsigned int i;
+
+ for (i=0; i<len; i++) {
+ if (md[i] != (unsigned char)(i & 0xff))
+ return 0;
+ }
+ return 1;
+}
+#endif /* if defined(FAKE_RSA_IMPL) */
+
/* MD5 part */
#undef data
#ifdef OLD
@@ -184,19 +230,34 @@ static int test_engine_digest_selector(ENGINE *e, const EVP_MD **digest,
return ok;
}
-
static int bind_helper(ENGINE * e, const char *id)
{
- if (!ENGINE_set_id(e, test_engine_id) ||
- !ENGINE_set_name(e, test_engine_name) ||
- !ENGINE_set_init_function(e, test_init) ||
- !ENGINE_set_digests(e, &test_engine_digest_selector) ||
+#if defined(FAKE_RSA_IMPL)
+ test_rsa_method = RSA_meth_new("OTP test RSA method", 0);
+ if (test_rsa_method == NULL) {
+ fprintf(stderr, "RSA_meth_new failed\r\n");
+ return 0;
+ }
+#endif /* if defined(FAKE_RSA_IMPL) */
+
+ if (!ENGINE_set_id(e, test_engine_id)
+ || !ENGINE_set_name(e, test_engine_name)
+ || !ENGINE_set_init_function(e, test_init)
+ || !ENGINE_set_digests(e, &test_engine_digest_selector)
/* For testing of key storage in an Engine: */
- !ENGINE_set_load_privkey_function(e, &test_key_load) ||
- !ENGINE_set_load_pubkey_function(e, &test_key_load)
- )
+ || !ENGINE_set_load_privkey_function(e, &test_privkey_load)
+ || !ENGINE_set_load_pubkey_function(e, &test_pubkey_load)
+ )
return 0;
+#if defined(FAKE_RSA_IMPL)
+ if ( !ENGINE_set_RSA(e, test_rsa_method) ) {
+ RSA_meth_free(test_rsa_method);
+ test_rsa_method = NULL;
+ return 0;
+ }
+#endif /* if defined(FAKE_RSA_IMPL) */
+
return 1;
}
@@ -211,24 +272,29 @@ IMPLEMENT_DYNAMIC_BIND_FN(bind_helper);
*/
int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password);
-EVP_PKEY* test_key_load(ENGINE *er, const char *id, UI_METHOD *ui_method, void *callback_data)
+EVP_PKEY* test_privkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data) {
+ return test_key_load(eng, id, ui_method, callback_data, 1);
+}
+
+EVP_PKEY* test_pubkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data) {
+ return test_key_load(eng, id, ui_method, callback_data, 0);
+}
+
+EVP_PKEY* test_key_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data, int priv)
{
EVP_PKEY *pkey = NULL;
FILE *f = fopen(id, "r");
if (!f) {
- fprintf(stderr, "%s:%d fopen(%s) failed\r\n", __FILE__,__LINE__,id);
- return NULL;
+ fprintf(stderr, "%s:%d fopen(%s) failed\r\n", __FILE__,__LINE__,id);
+ return NULL;
}
- /* First try to read as a private key. If that fails, try to read as a public key: */
- pkey = PEM_read_PrivateKey(f, NULL, pem_passwd_cb_fun, callback_data);
- if (!pkey) {
- /* ERR_print_errors_fp (stderr); */
- fclose(f);
- f = fopen(id, "r");
- pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL);
- }
+ pkey =
+ priv
+ ? PEM_read_PrivateKey(f, NULL, pem_passwd_cb_fun, callback_data)
+ : PEM_read_PUBKEY(f, NULL, NULL, NULL);
+
fclose(f);
if (!pkey) {
@@ -278,3 +344,71 @@ int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password)
}
#endif
+
+#if defined(FAKE_RSA_IMPL)
+/* RSA sign. This returns a fixed string so the test case can test that it was called
+ instead of the cryptolib default RSA sign */
+
+unsigned char fake_flag[] = {255,3,124,180,35,10,180,151,101,247,62,59,80,122,220,
+ 142,24,180,191,34,51,150,112,27,43,142,195,60,245,213,80,179};
+
+int test_rsa_sign(int dtype,
+ /* The digest to sign */
+ const unsigned char *m, unsigned int m_len,
+ /* The allocated buffer to fill with the signature */
+ unsigned char *sigret, unsigned int *siglen,
+ /* The key */
+ const RSA *rsa)
+{
+ int slen;
+ fprintf(stderr, "test_rsa_sign (dtype=%i) called m_len=%u *siglen=%u\r\n", dtype, m_len, *siglen);
+ if (!sigret) {
+ fprintf(stderr, "sigret = NULL\r\n");
+ return -1;
+ }
+
+ /* {int i;
+ fprintf(stderr, "Digest =\r\n");
+ for(i=0; i<m_len; i++)
+ fprintf(stderr, "%i,", m[i]);
+ fprintf(stderr, "\r\n");
+ } */
+
+ if ((sizeof(fake_flag) == m_len)
+ && bcmp(m,fake_flag,m_len) == 0) {
+ printf("To be faked\r\n");
+ /* To be faked */
+ slen = RSA_size(rsa);
+ add_test_data(sigret, slen); /* The signature is 0,1,2...255,0,1... */
+ *siglen = slen; /* Must set this. Why? */
+ return 1; /* 1 = success */
+ }
+ return 0;
+}
+
+int test_rsa_verify(int dtype,
+ /* The digest to verify */
+ const unsigned char *m, unsigned int m_len,
+ /* The signature */
+ const unsigned char *sigret, unsigned int siglen,
+ /* The key */
+ const RSA *rsa)
+{
+ printf("test_rsa_verify (dtype=%i) called m_len=%u siglen=%u\r\n", dtype, m_len, siglen);
+
+ if ((sizeof(fake_flag) == m_len)
+ && bcmp(m,fake_flag,m_len) == 0) {
+ printf("To be faked\r\n");
+ return (siglen == RSA_size(rsa))
+ && chk_test_data(sigret, siglen);
+ }
+ return 0;
+}
+
+static int test_rsa_free(RSA *rsa)
+{
+ printf("test_rsa_free called\r\n");
+ return 1;
+}
+
+#endif /* if defined(FAKE_RSA_IMPL) */
diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c
new file mode 100644
index 0000000000..bd56b2d977
--- /dev/null
+++ b/lib/crypto/c_src/pkey.c
@@ -0,0 +1,1170 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "pkey.h"
+#include "bn.h"
+#include "digest.h"
+#include "dss.h"
+#include "ec.h"
+#include "eddsa.h"
+#include "engine.h"
+#include "rsa.h"
+
+#define PKEY_BADARG -1
+#define PKEY_NOTSUP 0
+#define PKEY_OK 1
+
+typedef struct PKeyCryptOptions {
+ const EVP_MD *rsa_mgf1_md;
+ ErlNifBinary rsa_oaep_label;
+ const EVP_MD *rsa_oaep_md;
+ int rsa_padding;
+ const EVP_MD *signature_md;
+} PKeyCryptOptions;
+
+typedef struct PKeySignOptions {
+ const EVP_MD *rsa_mgf1_md;
+ int rsa_padding;
+ int rsa_pss_saltlen;
+} PKeySignOptions;
+
+
+static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
+ const EVP_MD **md);
+static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
+ ERL_NIF_TERM type, ERL_NIF_TERM data,
+ unsigned char *md_value, const EVP_MD **mdp,
+ unsigned char **tbsp, size_t *tbslenp);
+static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ const EVP_MD *md, PKeySignOptions *opt);
+static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey);
+static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
+ EVP_PKEY **pkey);
+static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ PKeyCryptOptions *opt);
+static size_t size_of_RSA(EVP_PKEY *pkey);
+
+
+static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type,
+ const EVP_MD **md)
+{
+ struct digest_type_t *digp = NULL;
+ *md = NULL;
+
+ if (type == atom_none && algorithm == atom_rsa) return PKEY_OK;
+#ifdef HAVE_EDDSA
+ if (algorithm == atom_eddsa) return PKEY_OK;
+#endif
+ digp = get_digest_type(type);
+ if (!digp) return PKEY_BADARG;
+ if (!digp->md.p) return PKEY_NOTSUP;
+
+ *md = digp->md.p;
+ return PKEY_OK;
+}
+
+static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm,
+ ERL_NIF_TERM type, ERL_NIF_TERM data,
+ unsigned char *md_value, const EVP_MD **mdp,
+ unsigned char **tbsp, size_t *tbslenp)
+{
+ int i;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+ ErlNifBinary tbs_bin;
+ EVP_MD_CTX *mdctx;
+ const EVP_MD *md = *mdp;
+ unsigned char *tbs = *tbsp;
+ size_t tbslen = *tbslenp;
+ unsigned int tbsleni;
+
+ if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK) {
+ return i;
+ }
+ if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) {
+ if (tpl_arity != 2 || tpl_terms[0] != atom_digest
+ || !enif_inspect_binary(env, tpl_terms[1], &tbs_bin)
+ || (md != NULL && tbs_bin.size != EVP_MD_size(md))) {
+ return PKEY_BADARG;
+ }
+ /* We have a digest (= hashed text) in tbs_bin */
+ tbs = tbs_bin.data;
+ tbslen = tbs_bin.size;
+ } else if (md == NULL) {
+ if (!enif_inspect_binary(env, data, &tbs_bin)) {
+ return PKEY_BADARG;
+ }
+ /* md == NULL, that is no hashing because DigestType argument was atom_none */
+ tbs = tbs_bin.data;
+ tbslen = tbs_bin.size;
+ } else {
+ if (!enif_inspect_binary(env, data, &tbs_bin)) {
+ return PKEY_BADARG;
+ }
+ /* We have the cleartext in tbs_bin and the hash algo info in md */
+ tbs = md_value;
+ mdctx = EVP_MD_CTX_create();
+ if (!mdctx) {
+ return PKEY_BADARG;
+ }
+ /* Looks well, now hash the plain text into a digest according to md */
+ if (EVP_DigestInit_ex(mdctx, md, NULL) <= 0) {
+ EVP_MD_CTX_destroy(mdctx);
+ return PKEY_BADARG;
+ }
+ if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) <= 0) {
+ EVP_MD_CTX_destroy(mdctx);
+ return PKEY_BADARG;
+ }
+ if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) <= 0) {
+ EVP_MD_CTX_destroy(mdctx);
+ return PKEY_BADARG;
+ }
+ tbslen = (size_t)(tbsleni);
+ EVP_MD_CTX_destroy(mdctx);
+ }
+
+ *mdp = md;
+ *tbsp = tbs;
+ *tbslenp = tbslen;
+
+ return PKEY_OK;
+}
+
+static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ const EVP_MD *md, PKeySignOptions *opt)
+{
+ ERL_NIF_TERM head, tail;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+ const EVP_MD *opt_md;
+ int i;
+
+ if (!enif_is_list(env, options)) {
+ return PKEY_BADARG;
+ }
+
+ /* defaults */
+ if (algorithm == atom_rsa) {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+ opt->rsa_pss_saltlen = -2;
+ }
+
+ if (enif_is_empty_list(env, options)) {
+ return PKEY_OK;
+ }
+
+ if (algorithm == atom_rsa) {
+ tail = options;
+ while (enif_get_list_cell(env, tail, &head, &tail)) {
+ if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) {
+ if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->rsa_mgf1_md = opt_md;
+ } else if (tpl_terms[0] == atom_rsa_padding) {
+ if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+ } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) {
+#ifdef HAVE_RSA_PKCS1_PSS_PADDING
+ opt->rsa_padding = RSA_PKCS1_PSS_PADDING;
+ if (opt->rsa_mgf1_md == NULL) {
+ opt->rsa_mgf1_md = md;
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (tpl_terms[1] == atom_rsa_x931_padding) {
+ opt->rsa_padding = RSA_X931_PADDING;
+ } else if (tpl_terms[1] == atom_rsa_no_padding) {
+ opt->rsa_padding = RSA_NO_PADDING;
+ } else {
+ return PKEY_BADARG;
+ }
+ } else if (tpl_terms[0] == atom_rsa_pss_saltlen) {
+ if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen))
+ || opt->rsa_pss_saltlen < -2) {
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+
+ return PKEY_OK;
+}
+
+static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey)
+{
+ if (enif_is_map(env, key)) {
+#ifdef HAS_ENGINE_SUPPORT
+ /* Use key stored in engine */
+ ENGINE *e;
+ char *id = NULL;
+ char *password;
+
+ if (!get_engine_and_key_id(env, key, &id, &e))
+ return PKEY_BADARG;
+ password = get_key_password(env, key);
+ *pkey = ENGINE_load_private_key(e, id, NULL, password);
+ if (password) enif_free(password);
+ enif_free(id);
+ if (!*pkey)
+ return PKEY_BADARG;
+#else
+ return PKEY_BADARG;
+#endif
+ }
+ else if (algorithm == atom_rsa) {
+ RSA *rsa = RSA_new();
+
+ if (!get_rsa_private_key(env, key, rsa)) {
+ RSA_free(rsa);
+ return PKEY_BADARG;
+ }
+
+ *pkey = EVP_PKEY_new();
+ if (!EVP_PKEY_assign_RSA(*pkey, rsa)) {
+ EVP_PKEY_free(*pkey);
+ RSA_free(rsa);
+ return PKEY_BADARG;
+ }
+ } else if (algorithm == atom_ecdsa) {
+#if defined(HAVE_EC)
+ EC_KEY *ec = NULL;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+
+ if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2
+ && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1])
+ && get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec)) {
+
+ *pkey = EVP_PKEY_new();
+ if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) {
+ EVP_PKEY_free(*pkey);
+ EC_KEY_free(ec);
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_eddsa) {
+#if defined(HAVE_EDDSA)
+ if (!get_eddsa_key(env, 0, key, pkey)) {
+ return PKEY_BADARG;
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_dss) {
+ DSA *dsa = DSA_new();
+
+ if (!get_dss_private_key(env, key, dsa)) {
+ DSA_free(dsa);
+ return PKEY_BADARG;
+ }
+
+ *pkey = EVP_PKEY_new();
+ if (!EVP_PKEY_assign_DSA(*pkey, dsa)) {
+ EVP_PKEY_free(*pkey);
+ DSA_free(dsa);
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+
+ return PKEY_OK;
+}
+
+static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key,
+ EVP_PKEY **pkey)
+{
+ if (enif_is_map(env, key)) {
+#ifdef HAS_ENGINE_SUPPORT
+ /* Use key stored in engine */
+ ENGINE *e;
+ char *id = NULL;
+ char *password;
+
+ if (!get_engine_and_key_id(env, key, &id, &e))
+ return PKEY_BADARG;
+ password = get_key_password(env, key);
+ *pkey = ENGINE_load_public_key(e, id, NULL, password);
+ if (password) enif_free(password);
+ enif_free(id);
+ if (!pkey)
+ return PKEY_BADARG;
+#else
+ return PKEY_BADARG;
+#endif
+ } else if (algorithm == atom_rsa) {
+ RSA *rsa = RSA_new();
+
+ if (!get_rsa_public_key(env, key, rsa)) {
+ RSA_free(rsa);
+ return PKEY_BADARG;
+ }
+
+ *pkey = EVP_PKEY_new();
+ if (!EVP_PKEY_assign_RSA(*pkey, rsa)) {
+ EVP_PKEY_free(*pkey);
+ RSA_free(rsa);
+ return PKEY_BADARG;
+ }
+ } else if (algorithm == atom_ecdsa) {
+#if defined(HAVE_EC)
+ EC_KEY *ec = NULL;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+
+ if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2
+ && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1])
+ && get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec)) {
+
+ *pkey = EVP_PKEY_new();
+ if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) {
+ EVP_PKEY_free(*pkey);
+ EC_KEY_free(ec);
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_eddsa) {
+#if defined(HAVE_EDDSA)
+ if (!get_eddsa_key(env, 1, key, pkey)) {
+ return PKEY_BADARG;
+ }
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (algorithm == atom_dss) {
+ DSA *dsa = DSA_new();
+
+ if (!get_dss_public_key(env, key, dsa)) {
+ DSA_free(dsa);
+ return PKEY_BADARG;
+ }
+
+ *pkey = EVP_PKEY_new();
+ if (!EVP_PKEY_assign_DSA(*pkey, dsa)) {
+ EVP_PKEY_free(*pkey);
+ DSA_free(dsa);
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+
+ return PKEY_OK;
+}
+
+ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */
+ int i;
+ const EVP_MD *md = NULL;
+ unsigned char md_value[EVP_MAX_MD_SIZE];
+ EVP_PKEY *pkey;
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx;
+ size_t siglen;
+#else
+ unsigned len, siglen;
+#endif
+ PKeySignOptions sig_opt;
+ ErlNifBinary sig_bin; /* signature */
+ unsigned char *tbs; /* data to be signed */
+ size_t tbslen;
+/*char buf[1024];
+enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf);
+enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf);
+printf("\r\n");
+*/
+
+#ifndef HAS_ENGINE_SUPPORT
+ if (enif_is_map(env, argv[3])) {
+ return atom_notsup;
+ }
+#endif
+
+ i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
+ if (i != PKEY_OK) {
+ if (i == PKEY_NOTSUP)
+ return atom_notsup;
+ else
+ return enif_make_badarg(env);
+ }
+
+ i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt);
+ if (i != PKEY_OK) {
+ if (i == PKEY_NOTSUP)
+ return atom_notsup;
+ else
+ return enif_make_badarg(env);
+ }
+
+ if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK) {
+ return enif_make_badarg(env);
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx) goto badarg;
+
+ if (argv[0] != atom_eddsa) {
+ if (EVP_PKEY_sign_init(ctx) <= 0) goto badarg;
+ if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg;
+ }
+
+ if (argv[0] == atom_rsa) {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg;
+# ifdef HAVE_RSA_PKCS1_PSS_PADDING
+ if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
+ if (sig_opt.rsa_mgf1_md != NULL) {
+# ifdef HAVE_RSA_MGF1_MD
+ if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg;
+# else
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return atom_notsup;
+# endif
+ }
+ if (sig_opt.rsa_pss_saltlen > -2
+ && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0)
+ goto badarg;
+ }
+#endif
+ }
+
+ if (argv[0] == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
+ if (!EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey)) {
+ if (mdctx) EVP_MD_CTX_free(mdctx);
+ goto badarg;
+ }
+
+ if (!EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen)) {
+ EVP_MD_CTX_free(mdctx);
+ goto badarg;
+ }
+ enif_alloc_binary(siglen, &sig_bin);
+
+ if (!EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen)) {
+ EVP_MD_CTX_free(mdctx);
+ goto badarg;
+ }
+ EVP_MD_CTX_free(mdctx);
+#else
+ goto badarg;
+#endif
+ }
+ else
+ {
+ if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) <= 0) goto badarg;
+ enif_alloc_binary(siglen, &sig_bin);
+
+ if (md != NULL) {
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
+ }
+ i = EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen);
+ }
+
+ EVP_PKEY_CTX_free(ctx);
+#else
+/*printf("Old interface\r\n");
+ */
+ if (argv[0] == atom_rsa) {
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey);
+ enif_alloc_binary(RSA_size(rsa), &sig_bin);
+ len = EVP_MD_size(md);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
+ i = RSA_sign(md->type, tbs, len, sig_bin.data, &siglen, rsa);
+ RSA_free(rsa);
+ } else if (argv[0] == atom_dss) {
+ DSA *dsa = EVP_PKEY_get1_DSA(pkey);
+ enif_alloc_binary(DSA_size(dsa), &sig_bin);
+ len = EVP_MD_size(md);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
+ i = DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa);
+ DSA_free(dsa);
+ } else if (argv[0] == atom_ecdsa) {
+#if defined(HAVE_EC)
+ EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
+ enif_alloc_binary(ECDSA_size(ec), &sig_bin);
+ len = EVP_MD_size(md);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len);
+ i = ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec);
+ EC_KEY_free(ec);
+#else
+ EVP_PKEY_free(pkey);
+ return atom_notsup;
+#endif
+ } else {
+ goto badarg;
+ }
+#endif
+
+ EVP_PKEY_free(pkey);
+ if (i == 1) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen);
+ if (siglen != sig_bin.size) {
+ enif_realloc_binary(&sig_bin, siglen);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen);
+ }
+ return enif_make_binary(env, &sig_bin);
+ } else {
+ enif_release_binary(&sig_bin);
+ return atom_error;
+ }
+
+ badarg:
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX_free(ctx);
+#endif
+ EVP_PKEY_free(pkey);
+ return enif_make_badarg(env);
+}
+
+ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */
+ int i;
+ const EVP_MD *md = NULL;
+ unsigned char md_value[EVP_MAX_MD_SIZE];
+ EVP_PKEY *pkey;
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx;
+#else
+#endif
+ PKeySignOptions sig_opt;
+ ErlNifBinary sig_bin; /* signature */
+ unsigned char *tbs; /* data to be signed */
+ size_t tbslen;
+
+#ifndef HAS_ENGINE_SUPPORT
+ if (enif_is_map(env, argv[4])) {
+ return atom_notsup;
+ }
+#endif
+
+ if (!enif_inspect_binary(env, argv[3], &sig_bin)) {
+ return enif_make_badarg(env);
+ }
+
+ i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen);
+ if (i != PKEY_OK) {
+ if (i == PKEY_NOTSUP)
+ return atom_notsup;
+ else
+ return enif_make_badarg(env);
+ }
+
+ i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt);
+ if (i != PKEY_OK) {
+ if (i == PKEY_NOTSUP)
+ return atom_notsup;
+ else
+ return enif_make_badarg(env);
+ }
+
+ if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) {
+ return enif_make_badarg(env);
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+/* printf("EVP interface\r\n");
+ */
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx) goto badarg;
+
+ if (argv[0] != atom_eddsa) {
+ if (EVP_PKEY_verify_init(ctx) <= 0) goto badarg;
+ if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg;
+ }
+
+ if (argv[0] == atom_rsa) {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg;
+ if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) {
+ if (sig_opt.rsa_mgf1_md != NULL) {
+# ifdef HAVE_RSA_MGF1_MD
+ if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg;
+# else
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return atom_notsup;
+# endif
+ }
+ if (sig_opt.rsa_pss_saltlen > -2
+ && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0)
+ goto badarg;
+ }
+ }
+
+ if (argv[0] == atom_eddsa) {
+#ifdef HAVE_EDDSA
+ EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
+
+ if (!EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey)) {
+ if (mdctx) EVP_MD_CTX_destroy(mdctx);
+ goto badarg;
+ }
+
+ i = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen);
+ EVP_MD_CTX_destroy(mdctx);
+#else
+ goto badarg;
+#endif
+ }
+ else
+ {
+ if (md != NULL) {
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md));
+ }
+ i = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen);
+ }
+
+ EVP_PKEY_CTX_free(ctx);
+#else
+/*printf("Old interface\r\n");
+*/
+ if (argv[0] == atom_rsa) {
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey);
+ i = RSA_verify(md->type, tbs, tbslen, sig_bin.data, sig_bin.size, rsa);
+ RSA_free(rsa);
+ } else if (argv[0] == atom_dss) {
+ DSA *dsa = EVP_PKEY_get1_DSA(pkey);
+ i = DSA_verify(0, tbs, tbslen, sig_bin.data, sig_bin.size, dsa);
+ DSA_free(dsa);
+ } else if (argv[0] == atom_ecdsa) {
+#if defined(HAVE_EC)
+ EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
+ i = ECDSA_verify(EVP_MD_type(md), tbs, tbslen, sig_bin.data, sig_bin.size, ec);
+ EC_KEY_free(ec);
+#else
+ EVP_PKEY_free(pkey);
+ return atom_notsup;
+#endif
+ } else {
+ goto badarg;
+ }
+#endif
+
+ EVP_PKEY_free(pkey);
+ if (i == 1) {
+ return atom_true;
+ } else {
+ return atom_false;
+ }
+
+ badarg:
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX_free(ctx);
+#endif
+ EVP_PKEY_free(pkey);
+ return enif_make_badarg(env);
+}
+
+static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options,
+ PKeyCryptOptions *opt)
+{
+ ERL_NIF_TERM head, tail;
+ const ERL_NIF_TERM *tpl_terms;
+ int tpl_arity;
+ const EVP_MD *opt_md;
+ int i;
+
+ if (!enif_is_list(env, options)) {
+ return PKEY_BADARG;
+ }
+
+ /* defaults */
+ if (algorithm == atom_rsa) {
+ opt->rsa_mgf1_md = NULL;
+ opt->rsa_oaep_label.data = NULL;
+ opt->rsa_oaep_label.size = 0;
+ opt->rsa_oaep_md = NULL;
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+ opt->signature_md = NULL;
+ }
+
+ if (enif_is_empty_list(env, options)) {
+ return PKEY_OK;
+ }
+
+ if (algorithm == atom_rsa) {
+ tail = options;
+ while (enif_get_list_cell(env, tail, &head, &tail)) {
+ if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) {
+ if (tpl_terms[0] == atom_rsa_padding
+ || tpl_terms[0] == atom_rsa_pad /* Compatibility */
+ ) {
+ if (tpl_terms[1] == atom_rsa_pkcs1_padding) {
+ opt->rsa_padding = RSA_PKCS1_PADDING;
+#ifdef HAVE_RSA_OAEP_PADDING
+ } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) {
+ opt->rsa_padding = RSA_PKCS1_OAEP_PADDING;
+#endif
+#ifdef HAVE_RSA_SSLV23_PADDING
+ } else if (tpl_terms[1] == atom_rsa_sslv23_padding) {
+ opt->rsa_padding = RSA_SSLV23_PADDING;
+#endif
+ } else if (tpl_terms[1] == atom_rsa_x931_padding) {
+ opt->rsa_padding = RSA_X931_PADDING;
+ } else if (tpl_terms[1] == atom_rsa_no_padding) {
+ opt->rsa_padding = RSA_NO_PADDING;
+ } else {
+ return PKEY_BADARG;
+ }
+ } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) {
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->signature_md = opt_md;
+ } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) {
+#ifndef HAVE_RSA_MGF1_MD
+ if (tpl_terms[1] != atom_sha)
+ return PKEY_NOTSUP;
+#endif
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->rsa_mgf1_md = opt_md;
+ } else if (tpl_terms[0] == atom_rsa_oaep_label
+ && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) {
+#ifdef HAVE_RSA_OAEP_MD
+ continue;
+#else
+ return PKEY_NOTSUP;
+#endif
+ } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) {
+#ifndef HAVE_RSA_OAEP_MD
+ if (tpl_terms[1] != atom_sha)
+ return PKEY_NOTSUP;
+#endif
+ i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md);
+ if (i != PKEY_OK) {
+ return i;
+ }
+ opt->rsa_oaep_md = opt_md;
+ } else {
+ return PKEY_BADARG;
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+ }
+ } else {
+ return PKEY_BADARG;
+ }
+
+ return PKEY_OK;
+}
+
+static size_t size_of_RSA(EVP_PKEY *pkey) {
+ size_t tmplen;
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) return 0;
+ tmplen = RSA_size(rsa);
+ RSA_free(rsa);
+ return tmplen;
+}
+
+ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */
+ int i;
+ EVP_PKEY *pkey;
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX *ctx;
+#else
+ RSA *rsa;
+#endif
+ PKeyCryptOptions crypt_opt;
+ ErlNifBinary in_bin, out_bin, tmp_bin;
+ size_t outlen;
+#ifdef HAVE_RSA_SSLV23_PADDING
+ size_t tmplen;
+#endif
+ int is_private = (argv[4] == atom_true),
+ is_encrypt = (argv[5] == atom_true);
+ int algo_init = 0;
+
+/* char algo[1024]; */
+
+#ifndef HAS_ENGINE_SUPPORT
+ if (enif_is_map(env, argv[2])) {
+ return atom_notsup;
+ }
+#endif
+
+ if (!enif_inspect_binary(env, argv[1], &in_bin)) {
+ return enif_make_badarg(env);
+ }
+
+ i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt);
+ if (i != PKEY_OK) {
+ if (i == PKEY_NOTSUP)
+ return atom_notsup;
+ else
+ return enif_make_badarg(env);
+ }
+
+ if (is_private) {
+ if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK) {
+ return enif_make_badarg(env);
+ }
+ } else {
+ if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK) {
+ return enif_make_badarg(env);
+ }
+ }
+
+ out_bin.data = NULL;
+ out_bin.size = 0;
+ tmp_bin.data = NULL;
+ tmp_bin.size = 0;
+
+#ifdef HAS_EVP_PKEY_CTX
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx) goto badarg;
+
+/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1); */
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* private encrypt */
+ if ((algo_init=EVP_PKEY_sign_init(ctx)) <= 0) {
+ /* fprintf(stderr,"BADARG %s private encrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */
+ goto badarg;
+ }
+ } else {
+ /* private decrypt */
+ if ((algo_init=EVP_PKEY_decrypt_init(ctx)) <= 0) {
+ /* fprintf(stderr,"BADARG %s private decrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */
+ goto badarg;
+ }
+ }
+ } else {
+ if (is_encrypt) {
+ /* public encrypt */
+ if ((algo_init=EVP_PKEY_encrypt_init(ctx)) <= 0) {
+ /* fprintf(stderr,"BADARG %s public encrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */
+ goto badarg;
+ }
+ } else {
+ /* public decrypt */
+ if ((algo_init=EVP_PKEY_verify_recover_init(ctx)) <= 0) {
+ /* fprintf(stderr,"BADARG %s public decrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */
+ goto badarg;
+ }
+ }
+ }
+
+ if (argv[0] == atom_rsa) {
+ if (crypt_opt.signature_md != NULL
+ && EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) <= 0)
+ goto badarg;
+#ifdef HAVE_RSA_SSLV23_PADDING
+ if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
+ if (is_encrypt) {
+ tmplen = size_of_RSA(pkey);
+ if (tmplen == 0) goto badarg;
+ if (!enif_alloc_binary(tmplen, &tmp_bin)) goto badarg;
+ if (RSA_padding_add_SSLv23(tmp_bin.data, tmplen, in_bin.data, in_bin.size) <= 0)
+ goto badarg;
+ in_bin = tmp_bin;
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) goto badarg;
+ } else
+#endif
+ {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) <= 0) goto badarg;
+ }
+#ifdef HAVE_RSA_OAEP_MD
+ if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) {
+ if (crypt_opt.rsa_oaep_md != NULL
+ && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) <= 0)
+ goto badarg;
+ if (crypt_opt.rsa_mgf1_md != NULL
+ && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) <= 0) goto badarg;
+ if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) {
+ unsigned char *label_copy = NULL;
+ label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size);
+ if (label_copy == NULL) goto badarg;
+ memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data),
+ crypt_opt.rsa_oaep_label.size);
+ if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy,
+ crypt_opt.rsa_oaep_label.size) <= 0) {
+ OPENSSL_free(label_copy);
+ label_copy = NULL;
+ goto badarg;
+ }
+ }
+ }
+#endif
+ }
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* private_encrypt */
+ i = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* private_decrypt */
+ i = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ }
+ } else {
+ if (is_encrypt) {
+ /* public_encrypt */
+ i = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* public_decrypt */
+ i = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size);
+ }
+ }
+ /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */
+
+ if (i != 1) goto badarg;
+
+ enif_alloc_binary(outlen, &out_bin);
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* private_encrypt */
+ i = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* private_decrypt */
+ i = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ }
+ } else {
+ if (is_encrypt) {
+ /* public_encrypt */
+ i = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ } else {
+ /* public_decrypt */
+ i = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size);
+ }
+ }
+
+#else
+ /* Non-EVP cryptolib. Only support RSA */
+
+ if (argv[0] != atom_rsa) {
+ algo_init = -2; /* exitcode: notsup */
+ goto badarg;
+ }
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ enif_alloc_binary(RSA_size(rsa), &out_bin);
+
+ if (is_private) {
+ if (is_encrypt) {
+ /* non-evp rsa private encrypt */
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
+ i = RSA_private_encrypt(in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
+ }
+ } else {
+ /* non-evp rsa private decrypt */
+ i = RSA_private_decrypt(in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
+ enif_realloc_binary(&out_bin, i);
+ }
+ }
+ } else {
+ if (is_encrypt) {
+ /* non-evp rsa public encrypt */
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size);
+ i = RSA_public_encrypt(in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
+ }
+ } else {
+ /* non-evp rsa public decrypt */
+ i = RSA_public_decrypt(in_bin.size, in_bin.data,
+ out_bin.data, rsa, crypt_opt.rsa_padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i);
+ enif_realloc_binary(&out_bin, i);
+ }
+ }
+ }
+
+ outlen = i;
+ RSA_free(rsa);
+#endif
+
+ if ((i > 0) && argv[0] == atom_rsa && !is_encrypt) {
+#ifdef HAVE_RSA_SSLV23_PADDING
+ if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) {
+ unsigned char *p;
+ tmplen = size_of_RSA(pkey);
+ if (tmplen == 0) goto badarg;
+ if (!enif_alloc_binary(tmplen, &tmp_bin))
+ goto badarg;
+ p = out_bin.data;
+ p++;
+ i = RSA_padding_check_SSLv23(tmp_bin.data, tmplen, p, out_bin.size - 1, tmplen);
+ if (i >= 0) {
+ outlen = i;
+ in_bin = out_bin;
+ out_bin = tmp_bin;
+ tmp_bin = in_bin;
+ i = 1;
+ }
+ }
+#endif
+ }
+
+ if (tmp_bin.data != NULL) {
+ enif_release_binary(&tmp_bin);
+ }
+
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX_free(ctx);
+#else
+#endif
+ EVP_PKEY_free(pkey);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen);
+ if (outlen != out_bin.size) {
+ enif_realloc_binary(&out_bin, outlen);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen);
+ }
+ return enif_make_binary(env, &out_bin);
+ } else {
+ enif_release_binary(&out_bin);
+ return atom_error;
+ }
+
+ badarg:
+ if (out_bin.data != NULL) {
+ enif_release_binary(&out_bin);
+ }
+ if (tmp_bin.data != NULL) {
+ enif_release_binary(&tmp_bin);
+ }
+#ifdef HAS_EVP_PKEY_CTX
+ EVP_PKEY_CTX_free(ctx);
+#else
+#endif
+ EVP_PKEY_free(pkey);
+ if (algo_init == -2)
+ return atom_notsup;
+ else
+ return enif_make_badarg(env);
+}
+
+ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{ /* (Algorithm, PrivKey | KeyMap) */
+ EVP_PKEY *pkey;
+ ERL_NIF_TERM alg = argv[0];
+ ERL_NIF_TERM result[8];
+ if (get_pkey_private_key(env, alg, argv[1], &pkey) != PKEY_OK) {
+ return enif_make_badarg(env);
+ }
+
+ if (alg == atom_rsa) {
+ const BIGNUM *n = NULL, *e = NULL, *d = NULL;
+ RSA *rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa) {
+ RSA_get0_key(rsa, &n, &e, &d);
+ result[0] = bin_from_bn(env, e); // Exponent E
+ result[1] = bin_from_bn(env, n); // Modulus N = p*q
+ RSA_free(rsa);
+ EVP_PKEY_free(pkey);
+ return enif_make_list_from_array(env, result, 2);
+ }
+
+ } else if (argv[0] == atom_dss) {
+ const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL;
+ DSA *dsa = EVP_PKEY_get1_DSA(pkey);
+ if (dsa) {
+ DSA_get0_pqg(dsa, &p, &q, &g);
+ DSA_get0_key(dsa, &pub_key, NULL);
+ result[0] = bin_from_bn(env, p);
+ result[1] = bin_from_bn(env, q);
+ result[2] = bin_from_bn(env, g);
+ result[3] = bin_from_bn(env, pub_key);
+ DSA_free(dsa);
+ EVP_PKEY_free(pkey);
+ return enif_make_list_from_array(env, result, 4);
+ }
+
+ } else if (argv[0] == atom_ecdsa) {
+#if defined(HAVE_EC)
+ /* not yet implemented
+ EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey);
+ if (ec) {
+ / * Example of result:
+ {
+ Curve = {Field, Prime, Point, Order, CoFactor} =
+ {
+ Field = {prime_field,<<255,...,255>>},
+ Prime = {<<255,...,252>>,
+ <<90,...,75>>,
+ <<196,...,144>>
+ },
+ Point = <<4,...,245>>,
+ Order = <<255,...,81>>,
+ CoFactor = <<1>>
+ },
+ Key = <<151,...,62>>
+ }
+ or
+ {
+ Curve =
+ {characteristic_two_field,
+ M,
+ Basis = {tpbasis, _}
+ | {ppbasis, k1, k2, k3}
+ },
+ Key
+ }
+ * /
+ EVP_PKEY_free(pkey);
+ return enif_make_list_from_array(env, ..., ...);
+ */
+#endif
+ }
+
+ if (pkey) EVP_PKEY_free(pkey);
+ return enif_make_badarg(env);
+}
diff --git a/lib/crypto/c_src/pkey.h b/lib/crypto/c_src/pkey.h
new file mode 100644
index 0000000000..f647a4a160
--- /dev/null
+++ b/lib/crypto/c_src/pkey.h
@@ -0,0 +1,31 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_PKEY_H__
+#define E_PKEY_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_PKEY_H__ */
diff --git a/lib/crypto/c_src/poly1305.c b/lib/crypto/c_src/poly1305.c
new file mode 100644
index 0000000000..3e2bcfa60e
--- /dev/null
+++ b/lib/crypto/c_src/poly1305.c
@@ -0,0 +1,78 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "poly1305.h"
+
+/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */
+ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Text) */
+#ifdef HAVE_POLY1305
+ ErlNifBinary key_bin, text, ret_bin;
+ ERL_NIF_TERM ret = atom_error;
+ EVP_PKEY *key = NULL;
+ EVP_MD_CTX *mctx = NULL;
+ EVP_PKEY_CTX *pctx = NULL;
+ const EVP_MD *md = NULL;
+ size_t size;
+ int type;
+
+ type = EVP_PKEY_POLY1305;
+
+ if (!enif_inspect_binary(env, argv[0], &key_bin) ||
+ !(key_bin.size == 32) ) {
+ return enif_make_badarg(env);
+ }
+
+ if (!enif_inspect_binary(env, argv[1], &text) ) {
+ return enif_make_badarg(env);
+ }
+
+ key = EVP_PKEY_new_raw_private_key(type, /*engine*/ NULL, key_bin.data, key_bin.size);
+
+ if (!key ||
+ !(mctx = EVP_MD_CTX_new()) ||
+ !EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) ||
+ !EVP_DigestSignUpdate(mctx, text.data, text.size)) {
+ goto err;
+ }
+
+ if (!EVP_DigestSignFinal(mctx, NULL, &size) ||
+ !enif_alloc_binary(size, &ret_bin) ||
+ !EVP_DigestSignFinal(mctx, ret_bin.data, &size)) {
+ goto err;
+ }
+
+ if ((size != ret_bin.size) &&
+ !enif_realloc_binary(&ret_bin, size)) {
+ goto err;
+ }
+
+ ret = enif_make_binary(env, &ret_bin);
+
+ err:
+ EVP_MD_CTX_free(mctx);
+ EVP_PKEY_free(key);
+ return ret;
+
+#else
+ return atom_notsup;
+#endif
+}
+
diff --git a/lib/crypto/c_src/poly1305.h b/lib/crypto/c_src/poly1305.h
new file mode 100644
index 0000000000..4bf45e6218
--- /dev/null
+++ b/lib/crypto/c_src/poly1305.h
@@ -0,0 +1,28 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_POLY1305_H__
+#define E_POLY1305_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_POLY1305_H__ */
diff --git a/lib/crypto/c_src/rand.c b/lib/crypto/c_src/rand.c
new file mode 100644
index 0000000000..e71e202f36
--- /dev/null
+++ b/lib/crypto/c_src/rand.c
@@ -0,0 +1,99 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "rand.h"
+#include "bn.h"
+
+ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Bytes) */
+ unsigned bytes;
+ unsigned char* data;
+ ERL_NIF_TERM ret;
+
+ if (!enif_get_uint(env, argv[0], &bytes)) {
+ return enif_make_badarg(env);
+ }
+ data = enif_make_new_binary(env, bytes, &ret);
+ if ( RAND_bytes(data, bytes) != 1) {
+ return atom_false;
+ }
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes);
+ return ret;
+}
+
+ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Range) */
+ BIGNUM *bn_range, *bn_rand;
+ ERL_NIF_TERM ret;
+
+ if(!get_bn_from_bin(env, argv[0], &bn_range)) {
+ return enif_make_badarg(env);
+ }
+
+ bn_rand = BN_new();
+ if (BN_rand_range(bn_rand, bn_range) != 1) {
+ ret = atom_false;
+ }
+ else {
+ ret = bin_from_bn(env, bn_rand);
+ }
+ BN_free(bn_rand);
+ BN_free(bn_range);
+ return ret;
+}
+
+ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Lo,Hi) */
+ BIGNUM *bn_from = NULL, *bn_to, *bn_rand;
+ unsigned char* data;
+ unsigned dlen;
+ ERL_NIF_TERM ret;
+
+ if (!get_bn_from_mpint(env, argv[0], &bn_from)
+ || !get_bn_from_mpint(env, argv[1], &bn_rand)) {
+ if (bn_from) BN_free(bn_from);
+ return enif_make_badarg(env);
+ }
+
+ bn_to = BN_new();
+ BN_sub(bn_to, bn_rand, bn_from);
+ BN_pseudo_rand_range(bn_rand, bn_to);
+ BN_add(bn_rand, bn_rand, bn_from);
+ dlen = BN_num_bytes(bn_rand);
+ data = enif_make_new_binary(env, dlen+4, &ret);
+ put_int32(data, dlen);
+ BN_bn2bin(bn_rand, data+4);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen);
+ BN_free(bn_rand);
+ BN_free(bn_from);
+ BN_free(bn_to);
+ return ret;
+}
+
+ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifBinary seed_bin;
+
+ if (!enif_inspect_binary(env, argv[0], &seed_bin))
+ return enif_make_badarg(env);
+ RAND_seed(seed_bin.data,seed_bin.size);
+ return atom_ok;
+}
+
diff --git a/lib/crypto/c_src/rand.h b/lib/crypto/c_src/rand.h
new file mode 100644
index 0000000000..9c23d343ec
--- /dev/null
+++ b/lib/crypto/c_src/rand.h
@@ -0,0 +1,31 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_RAND_H__
+#define E_RAND_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_RAND_H__ */
diff --git a/lib/crypto/c_src/rc4.c b/lib/crypto/c_src/rc4.c
new file mode 100644
index 0000000000..483c87b04b
--- /dev/null
+++ b/lib/crypto/c_src/rc4.c
@@ -0,0 +1,66 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "rc4.h"
+
+ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key) */
+#ifndef OPENSSL_NO_RC4
+ ErlNifBinary key;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &key)) {
+ return enif_make_badarg(env);
+ }
+ RC4_set_key((RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret),
+ key.size, key.data);
+ return ret;
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
+ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (State, Data) */
+#ifndef OPENSSL_NO_RC4
+ ErlNifBinary state, data;
+ RC4_KEY* rc4_key;
+ ERL_NIF_TERM new_state, new_data;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &state)
+ || state.size != sizeof(RC4_KEY)
+ || !enif_inspect_iolist_as_binary(env,argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+ rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state);
+ memcpy(rc4_key, state.data, sizeof(RC4_KEY));
+ RC4(rc4_key, data.size, data.data,
+ enif_make_new_binary(env, data.size, &new_data));
+ CONSUME_REDS(env,data);
+ return enif_make_tuple2(env,new_state,new_data);
+#else
+ return enif_raise_exception(env, atom_notsup);
+#endif
+}
+
diff --git a/lib/crypto/c_src/rc4.h b/lib/crypto/c_src/rc4.h
new file mode 100644
index 0000000000..28bf674253
--- /dev/null
+++ b/lib/crypto/c_src/rc4.h
@@ -0,0 +1,29 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_RC4_H__
+#define E_RC4_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_RC4_H__ */
diff --git a/lib/crypto/c_src/rsa.c b/lib/crypto/c_src/rsa.c
new file mode 100644
index 0000000000..92867671fb
--- /dev/null
+++ b/lib/crypto/c_src/rsa.c
@@ -0,0 +1,195 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "rsa.h"
+#include "bn.h"
+
+static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa);
+static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt);
+
+int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
+{
+ /* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *e, *n, *d;
+ BIGNUM *p, *q;
+ BIGNUM *dmp1, *dmq1, *iqmp;
+
+ if (!enif_get_list_cell(env, key, &head, &tail)
+ || !get_bn_from_bin(env, head, &e)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &n)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &d)) {
+ return 0;
+ }
+ (void) RSA_set0_key(rsa, n, e, d);
+ if (enif_is_empty_list(env, tail)) {
+ return 1;
+ }
+ if (!enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &q)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dmp1)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &dmq1)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &iqmp)
+ || !enif_is_empty_list(env, tail)) {
+ return 0;
+ }
+ (void) RSA_set0_factors(rsa, p, q);
+ (void) RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);
+ return 1;
+}
+
+int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa)
+{
+ /* key=[E,N] */
+ ERL_NIF_TERM head, tail;
+ BIGNUM *e, *n;
+
+ if (!enif_get_list_cell(env, key, &head, &tail)
+ || !get_bn_from_bin(env, head, &e)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_bin(env, head, &n)
+ || !enif_is_empty_list(env, tail)) {
+ return 0;
+ }
+
+ (void) RSA_set0_key(rsa, n, e, NULL);
+ return 1;
+}
+
+/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */
+static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa)
+{
+ ERL_NIF_TERM result[8];
+ const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp;
+
+ /* Return at least [E,N,D] */
+ n = NULL; e = NULL; d = NULL;
+ RSA_get0_key(rsa, &n, &e, &d);
+
+ result[0] = bin_from_bn(env, e); // Exponent E
+ result[1] = bin_from_bn(env, n); // Modulus N = p*q
+ result[2] = bin_from_bn(env, d); // Exponent D
+
+ /* Check whether the optional additional parameters are available */
+ p = NULL; q = NULL;
+ RSA_get0_factors(rsa, &p, &q);
+ dmp1 = NULL; dmq1 = NULL; iqmp = NULL;
+ RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);
+
+ if (p && q && dmp1 && dmq1 && iqmp) {
+ result[3] = bin_from_bn(env, p); // Factor p
+ result[4] = bin_from_bn(env, q); // Factor q
+ result[5] = bin_from_bn(env, dmp1); // D mod (p-1)
+ result[6] = bin_from_bn(env, dmq1); // D mod (q-1)
+ result[7] = bin_from_bn(env, iqmp); // (1/q) mod p
+
+ return enif_make_list_from_array(env, result, 8);
+ } else {
+ return enif_make_list_from_array(env, result, 3);
+ }
+}
+
+static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt)
+{
+ ErlNifEnv *env = BN_GENCB_get_arg(ctxt);
+
+ if (!enif_is_current_process_alive(env)) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (ModulusSize, PublicExponent) */
+ int modulus_bits;
+ BIGNUM *pub_exp, *three;
+ RSA *rsa;
+ int success;
+ ERL_NIF_TERM result;
+ BN_GENCB *intr_cb;
+#ifndef HAVE_OPAQUE_BN_GENCB
+ BN_GENCB intr_cb_buf;
+#endif
+
+ if (!enif_get_int(env, argv[0], &modulus_bits) || modulus_bits < 256) {
+ return enif_make_badarg(env);
+ }
+
+ if (!get_bn_from_bin(env, argv[1], &pub_exp)) {
+ return enif_make_badarg(env);
+ }
+
+ /* Make sure the public exponent is large enough (at least 3).
+ * Without this, RSA_generate_key_ex() can run forever. */
+ three = BN_new();
+ BN_set_word(three, 3);
+ success = BN_cmp(pub_exp, three);
+ BN_free(three);
+ if (success < 0) {
+ BN_free(pub_exp);
+ return enif_make_badarg(env);
+ }
+
+ /* For large keys, prime generation can take many seconds. Set up
+ * the callback which we use to test whether the process has been
+ * interrupted. */
+#ifdef HAVE_OPAQUE_BN_GENCB
+ intr_cb = BN_GENCB_new();
+#else
+ intr_cb = &intr_cb_buf;
+#endif
+ BN_GENCB_set(intr_cb, check_erlang_interrupt, env);
+
+ rsa = RSA_new();
+ success = RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb);
+ BN_free(pub_exp);
+
+#ifdef HAVE_OPAQUE_BN_GENCB
+ BN_GENCB_free(intr_cb);
+#endif
+
+ if (!success) {
+ RSA_free(rsa);
+ return atom_error;
+ }
+
+ result = put_rsa_private_key(env, rsa);
+ RSA_free(rsa);
+
+ return result;
+}
+
+ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ /* RSA key generation can take a long time (>1 sec for a large
+ * modulus), so schedule it as a CPU-bound operation. */
+ return enif_schedule_nif(env, "rsa_generate_key",
+ ERL_NIF_DIRTY_JOB_CPU_BOUND,
+ rsa_generate_key, argc, argv);
+}
diff --git a/lib/crypto/c_src/rsa.h b/lib/crypto/c_src/rsa.h
new file mode 100644
index 0000000000..69c02aa2cb
--- /dev/null
+++ b/lib/crypto/c_src/rsa.h
@@ -0,0 +1,31 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_RSA_H__
+#define E_RSA_H__ 1
+
+#include "common.h"
+
+int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa);
+int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa);
+
+ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_RSA_H__ */
diff --git a/lib/crypto/c_src/srp.c b/lib/crypto/c_src/srp.c
new file mode 100644
index 0000000000..1552bc8cc1
--- /dev/null
+++ b/lib/crypto/c_src/srp.c
@@ -0,0 +1,229 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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%
+ */
+
+#include "srp.h"
+#include "bn.h"
+
+ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Multiplier, Verifier, Generator, Exponent, Prime) */
+ BIGNUM *bn_verifier = NULL;
+ BIGNUM *bn_exponent = NULL, *bn_generator = NULL, *bn_prime = NULL, *bn_multiplier = NULL, *bn_result;
+ BN_CTX *bn_ctx;
+ unsigned char* ptr;
+ unsigned dlen;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!get_bn_from_bin(env, argv[0], &bn_multiplier)
+ || !get_bn_from_bin(env, argv[1], &bn_verifier)
+ || !get_bn_from_bin(env, argv[2], &bn_generator)
+ || !get_bn_from_bin(env, argv[3], &bn_exponent)
+ || !get_bn_from_bin(env, argv[4], &bn_prime)) {
+ if (bn_multiplier) BN_free(bn_multiplier);
+ if (bn_verifier) BN_free(bn_verifier);
+ if (bn_generator) BN_free(bn_generator);
+ if (bn_exponent) BN_free(bn_exponent);
+ if (bn_prime) BN_free(bn_prime);
+ return enif_make_badarg(env);
+ }
+
+ bn_result = BN_new();
+ bn_ctx = BN_CTX_new();
+
+ /* B = k*v + g^b % N */
+
+ /* k * v */
+ BN_mod_mul(bn_multiplier, bn_multiplier, bn_verifier, bn_prime, bn_ctx);
+
+ /* g^b % N */
+ BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx);
+
+ /* k*v + g^b % N */
+ BN_mod_add(bn_result, bn_result, bn_multiplier, bn_prime, bn_ctx);
+
+ /* check that B % N != 0, reuse bn_multiplier */
+ BN_nnmod(bn_multiplier, bn_result, bn_prime, bn_ctx);
+ if (BN_is_zero(bn_multiplier)) {
+ ret = atom_error;
+ } else {
+ dlen = BN_num_bytes(bn_result);
+ ptr = enif_make_new_binary(env, dlen, &ret);
+ BN_bn2bin(bn_result, ptr);
+ }
+ BN_free(bn_result);
+ BN_CTX_free(bn_ctx);
+ BN_free(bn_prime);
+ BN_free(bn_generator);
+ BN_free(bn_multiplier);
+ BN_free(bn_exponent);
+ BN_free(bn_verifier);
+ return ret;
+}
+
+ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (a, u, B, Multiplier, Prime, Exponent, Generator) */
+/*
+ <premaster secret> = (B - (k * g^x)) ^ (a + (u * x)) % N
+*/
+ BIGNUM *bn_exponent = NULL, *bn_a = NULL;
+ BIGNUM *bn_u = NULL, *bn_multiplier = NULL, *bn_exp2,
+ *bn_base, *bn_prime = NULL, *bn_generator = NULL,
+ *bn_B = NULL, *bn_result;
+ BN_CTX *bn_ctx;
+ unsigned char* ptr;
+ unsigned dlen;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!get_bn_from_bin(env, argv[0], &bn_a)
+ || !get_bn_from_bin(env, argv[1], &bn_u)
+ || !get_bn_from_bin(env, argv[2], &bn_B)
+ || !get_bn_from_bin(env, argv[3], &bn_multiplier)
+ || !get_bn_from_bin(env, argv[4], &bn_generator)
+ || !get_bn_from_bin(env, argv[5], &bn_exponent)
+ || !get_bn_from_bin(env, argv[6], &bn_prime))
+ {
+ if (bn_exponent) BN_free(bn_exponent);
+ if (bn_a) BN_free(bn_a);
+ if (bn_u) BN_free(bn_u);
+ if (bn_B) BN_free(bn_B);
+ if (bn_multiplier) BN_free(bn_multiplier);
+ if (bn_generator) BN_free(bn_generator);
+ if (bn_prime) BN_free(bn_prime);
+ return enif_make_badarg(env);
+ }
+
+ bn_ctx = BN_CTX_new();
+ bn_result = BN_new();
+
+ /* check that B % N != 0 */
+ BN_nnmod(bn_result, bn_B, bn_prime, bn_ctx);
+ if (BN_is_zero(bn_result)) {
+ BN_free(bn_exponent);
+ BN_free(bn_a);
+ BN_free(bn_generator);
+ BN_free(bn_prime);
+ BN_free(bn_u);
+ BN_free(bn_B);
+ BN_CTX_free(bn_ctx);
+
+ return atom_error;
+ }
+
+ /* (B - (k * g^x)) */
+ bn_base = BN_new();
+ BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx);
+ BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx);
+ BN_mod_sub(bn_base, bn_B, bn_result, bn_prime, bn_ctx);
+
+ /* a + (u * x) */
+ bn_exp2 = BN_new();
+ BN_mul(bn_result, bn_u, bn_exponent, bn_ctx);
+ BN_add(bn_exp2, bn_a, bn_result);
+
+ /* (B - (k * g^x)) ^ (a + (u * x)) % N */
+ BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx);
+
+ dlen = BN_num_bytes(bn_result);
+ ptr = enif_make_new_binary(env, dlen, &ret);
+ BN_bn2bin(bn_result, ptr);
+ BN_free(bn_result);
+ BN_CTX_free(bn_ctx);
+
+ BN_free(bn_multiplier);
+ BN_free(bn_exp2);
+ BN_free(bn_u);
+ BN_free(bn_exponent);
+ BN_free(bn_a);
+ BN_free(bn_B);
+ BN_free(bn_base);
+ BN_free(bn_generator);
+ BN_free(bn_prime);
+ return ret;
+}
+
+ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Verifier, b, u, A, Prime) */
+/*
+ <premaster secret> = (A * v^u) ^ b % N
+*/
+ BIGNUM *bn_b = NULL, *bn_verifier = NULL;
+ BIGNUM *bn_prime = NULL, *bn_A = NULL, *bn_u = NULL, *bn_base, *bn_result;
+ BN_CTX *bn_ctx;
+ unsigned char* ptr;
+ unsigned dlen;
+ ERL_NIF_TERM ret;
+
+ CHECK_NO_FIPS_MODE();
+
+ if (!get_bn_from_bin(env, argv[0], &bn_verifier)
+ || !get_bn_from_bin(env, argv[1], &bn_b)
+ || !get_bn_from_bin(env, argv[2], &bn_u)
+ || !get_bn_from_bin(env, argv[3], &bn_A)
+ || !get_bn_from_bin(env, argv[4], &bn_prime))
+ {
+ if (bn_verifier) BN_free(bn_verifier);
+ if (bn_b) BN_free(bn_b);
+ if (bn_u) BN_free(bn_u);
+ if (bn_A) BN_free(bn_A);
+ if (bn_prime) BN_free(bn_prime);
+ return enif_make_badarg(env);
+ }
+
+ bn_ctx = BN_CTX_new();
+ bn_result = BN_new();
+
+ /* check that A % N != 0 */
+ BN_nnmod(bn_result, bn_A, bn_prime, bn_ctx);
+ if (BN_is_zero(bn_result)) {
+ BN_free(bn_b);
+ BN_free(bn_verifier);
+ BN_free(bn_prime);
+ BN_free(bn_A);
+ BN_CTX_free(bn_ctx);
+
+ return atom_error;
+ }
+
+ /* (A * v^u) */
+ bn_base = BN_new();
+ BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx);
+ BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx);
+
+ /* (A * v^u) ^ b % N */
+ BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx);
+
+ dlen = BN_num_bytes(bn_result);
+ ptr = enif_make_new_binary(env, dlen, &ret);
+ BN_bn2bin(bn_result, ptr);
+ BN_free(bn_result);
+ BN_CTX_free(bn_ctx);
+
+ BN_free(bn_u);
+ BN_free(bn_base);
+ BN_free(bn_verifier);
+ BN_free(bn_prime);
+ BN_free(bn_A);
+ BN_free(bn_b);
+ return ret;
+}
+
diff --git a/lib/crypto/c_src/srp.h b/lib/crypto/c_src/srp.h
new file mode 100644
index 0000000000..c356690470
--- /dev/null
+++ b/lib/crypto/c_src/srp.h
@@ -0,0 +1,30 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2010-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 E_SRP_H__
+#define E_SRP_H__ 1
+
+#include "common.h"
+
+ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+#endif /* E_SRP_H__ */
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index 96fda34860..3306fe3d16 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -22,7 +22,7 @@
<title>crypto</title>
</header>
- <module>crypto</module>
+ <module since="">crypto</module>
<modulesummary>Crypto Functions</modulesummary>
<description>
<p>This module provides a set of cryptographic functions.
@@ -523,7 +523,7 @@
<!--================ FUNCTIONS ================-->
<funcs>
<func>
- <name name="block_encrypt" arity="3"/>
+ <name name="block_encrypt" arity="3" since="OTP 18.0"/>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<desc>
<p>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher.</p>
@@ -536,7 +536,7 @@
</func>
<func>
- <name name="block_decrypt" arity="3"/>
+ <name name="block_decrypt" arity="3" since="OTP 18.0"/>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<desc>
<p>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher.</p>
@@ -549,9 +549,9 @@
</func>
<func>
- <name>block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name>
- <name>block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name>
- <name>block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag}</name>
+ <name since="OTP R16B01">block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name>
+ <name since="OTP R16B01">block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name>
+ <name since="OTP R16B01">block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag}</name>
<fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary>
<type>
<v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v>
@@ -576,8 +576,8 @@
</func>
<func>
- <name>block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name>
- <name>block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name>
+ <name since="OTP R16B01">block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name>
+ <name since="OTP R16B01">block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name>
<fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary>
<type>
<v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v>
@@ -602,7 +602,7 @@
</func>
<func>
- <name name="bytes_to_integer" arity="1"/>
+ <name name="bytes_to_integer" arity="1" since="OTP R16B01"/>
<fsummary>Convert binary representation, of an integer, to an Erlang integer.</fsummary>
<desc>
<p>Convert binary representation, of an integer, to an Erlang integer.
@@ -611,7 +611,7 @@
</func>
<func>
- <name name="compute_key" arity="4"/>
+ <name name="compute_key" arity="4" since="OTP R16B01"/>
<fsummary>Computes the shared secret</fsummary>
<desc>
<p>Computes the shared secret from the private key and the other party's public key.
@@ -621,7 +621,7 @@
</func>
<func>
- <name name="exor" arity="2"/>
+ <name name="exor" arity="2" since=""/>
<fsummary>XOR data</fsummary>
<desc>
<p>Performs bit-wise XOR (exclusive or) on the data supplied.</p>
@@ -630,8 +630,8 @@
<func>
- <name name="generate_key" arity="2"/>
- <name name="generate_key" arity="3"/>
+ <name name="generate_key" arity="2" since="OTP R16B01"/>
+ <name name="generate_key" arity="3" since="OTP R16B01"/>
<fsummary>Generates a public key of type <c>Type</c></fsummary>
<desc>
<p>Generates a public key of type <c>Type</c>.
@@ -652,7 +652,7 @@
</func>
<func>
- <name name="hash" arity="2"/>
+ <name name="hash" arity="2" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Computes a message digest of type <c>Type</c> from <c>Data</c>.</p>
@@ -662,7 +662,7 @@
</func>
<func>
- <name name="hash_init" arity="1"/>
+ <name name="hash_init" arity="1" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Initializes the context for streaming hash operations. <c>Type</c> determines
@@ -674,7 +674,7 @@
</func>
<func>
- <name name="hash_update" arity="2"/>
+ <name name="hash_update" arity="2" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Updates the digest represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c>
@@ -686,7 +686,7 @@
</func>
<func>
- <name name="hash_final" arity="1"/>
+ <name name="hash_final" arity="1" since="OTP R15B02"/>
<fsummary></fsummary>
<desc>
<p>Finalizes the hash operation referenced by <c>Context</c> returned
@@ -697,8 +697,8 @@
</func>
<func>
- <name name="hmac" arity="3"/>
- <name name="hmac" arity="4"/>
+ <name name="hmac" arity="3" since="OTP R16B"/>
+ <name name="hmac" arity="4" since="OTP R16B"/>
<fsummary></fsummary>
<desc>
<p>Computes a HMAC of type <c>Type</c> from <c>Data</c> using
@@ -708,7 +708,7 @@
</func>
<func>
- <name name="hmac_init" arity="2"/>
+ <name name="hmac_init" arity="2" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Initializes the context for streaming HMAC operations. <c>Type</c> determines
@@ -718,7 +718,7 @@
</func>
<func>
- <name name="hmac_update" arity="2"/>
+ <name name="hmac_update" arity="2" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Updates the HMAC represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c>
@@ -737,7 +737,7 @@
</func>
<func>
- <name name="hmac_final" arity="1"/>
+ <name name="hmac_final" arity="1" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Finalizes the HMAC operation referenced by <c>Context</c>. The size of the resultant MAC is
@@ -746,7 +746,7 @@
</func>
<func>
- <name name="hmac_final_n" arity="2"/>
+ <name name="hmac_final_n" arity="2" since="OTP R14B03"/>
<fsummary></fsummary>
<desc>
<p>Finalizes the HMAC operation referenced by <c>Context</c>. <c>HashLen</c> must be greater than
@@ -755,8 +755,8 @@
</func>
<func>
- <name name="cmac" arity="3"/>
- <name name="cmac" arity="4"/>
+ <name name="cmac" arity="3" since="OTP 20.0"/>
+ <name name="cmac" arity="4" since="OTP 20.0"/>
<fsummary>Calculates the Cipher-based Message Authentication Code.</fsummary>
<desc>
<p>Computes a CMAC of type <c>Type</c> from <c>Data</c> using
@@ -766,7 +766,7 @@
</func>
<func>
- <name name="info_fips" arity="0"/>
+ <name name="info_fips" arity="0" since="OTP 20.0"/>
<fsummary>Provides information about the FIPS operating status.</fsummary>
<desc>
<p>Provides information about the FIPS operating status of
@@ -789,7 +789,7 @@
</func>
<func>
- <name name="enable_fips_mode" arity="1"/>
+ <name name="enable_fips_mode" arity="1" since="OTP 21.1"/>
<fsummary>Change FIPS mode.</fsummary>
<desc>
<p>Enables (<c>Enable = true</c>) or disables (<c>Enable = false</c>) FIPS mode. Returns <c>true</c> if
@@ -804,7 +804,7 @@
</func>
<func>
- <name name="info_lib" arity="0"/>
+ <name name="info_lib" arity="0" since=""/>
<fsummary>Provides information about the libraries used by crypto.</fsummary>
<desc>
<p>Provides the name and version of the libraries used by crypto.</p>
@@ -825,7 +825,7 @@
</func>
<func>
- <name name="mod_pow" arity="3"/>
+ <name name="mod_pow" arity="3" since="OTP R16B01"/>
<fsummary>Computes the function: N^P mod M</fsummary>
<desc>
<p>Computes the function <c>N^P mod M</c>.</p>
@@ -833,8 +833,8 @@
</func>
<func>
- <name name="next_iv" arity="2"/>
- <name name="next_iv" arity="3"/>
+ <name name="next_iv" arity="2" since="OTP R16B01"/>
+ <name name="next_iv" arity="3" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Returns the initialization vector to be used in the next
@@ -846,7 +846,7 @@
</func>
<func>
- <name name="poly1305" arity="2"/>
+ <name name="poly1305" arity="2" since="OTP 21.1"/>
<fsummary></fsummary>
<desc>
<p>Computes a POLY1305 message authentication code (<c>Mac</c>) from <c>Data</c> using
@@ -855,7 +855,7 @@
</func>
<func>
- <name name="private_decrypt" arity="4"/>
+ <name name="private_decrypt" arity="4" since="OTP R16B01"/>
<fsummary>Decrypts CipherText using the private Key.</fsummary>
<desc>
<p>Decrypts the <c>CipherText</c>, encrypted with
@@ -869,7 +869,7 @@
</func>
<func>
- <name name="private_encrypt" arity="4"/>
+ <name name="private_encrypt" arity="4" since="OTP R16B01"/>
<fsummary>Encrypts PlainText using the private Key.</fsummary>
<desc>
<p>Encrypts the <c>PlainText</c> using the <c>PrivateKey</c>
@@ -882,7 +882,7 @@
</func>
<func>
- <name name="public_decrypt" arity="4"/>
+ <name name="public_decrypt" arity="4" since="OTP R16B01"/>
<fsummary>Decrypts CipherText using the public Key.</fsummary>
<desc>
<p>Decrypts the <c>CipherText</c>, encrypted with
@@ -896,7 +896,7 @@
</func>
<func>
- <name name="public_encrypt" arity="4"/>
+ <name name="public_encrypt" arity="4" since="OTP R16B01"/>
<fsummary>Encrypts PlainText using the public Key.</fsummary>
<desc>
<p>Encrypts the <c>PlainText</c> (message digest) using the <c>PublicKey</c>
@@ -908,7 +908,7 @@
</func>
<func>
- <name name="rand_seed" arity="1"/>
+ <name name="rand_seed" arity="1" since="OTP 17.0"/>
<fsummary>Set the seed for random bytes generation</fsummary>
<desc>
<p>Set the seed for PRNG to the given binary. This calls the
@@ -921,7 +921,7 @@
</func>
<func>
- <name>rand_uniform(Lo, Hi) -> N</name>
+ <name since="">rand_uniform(Lo, Hi) -> N</name>
<fsummary>Generate a random number</fsummary>
<type>
<v>Lo, Hi, N = integer()</v>
@@ -934,7 +934,7 @@
</func>
<func>
- <name name="start" arity="0"/>
+ <name name="start" arity="0" since=""/>
<fsummary> Equivalent to application:start(crypto). </fsummary>
<desc>
<p> Equivalent to application:start(crypto).</p>
@@ -942,7 +942,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary> Equivalent to application:stop(crypto).</fsummary>
<desc>
<p> Equivalent to application:stop(crypto).</p>
@@ -950,7 +950,7 @@
</func>
<func>
- <name name="strong_rand_bytes" arity="1"/>
+ <name name="strong_rand_bytes" arity="1" since="OTP R14B03"/>
<fsummary>Generate a binary of random bytes</fsummary>
<desc>
<p>Generates N bytes randomly uniform 0..255, and returns the
@@ -963,7 +963,7 @@
</func>
<func>
- <name name="rand_seed" arity="0"/>
+ <name name="rand_seed" arity="0" since="OTP 20.0"/>
<fsummary>Strong random number generation plugin state</fsummary>
<desc>
<p>
@@ -991,7 +991,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name name="rand_seed_s" arity="0"/>
+ <name name="rand_seed_s" arity="0" since="OTP 20.0"/>
<fsummary>Strong random number generation plugin state</fsummary>
<desc>
<p>
@@ -1026,7 +1026,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name>rand_seed_alg(Alg) -> rand:state()</name>
+ <name since="OTP 21.0">rand_seed_alg(Alg) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto | crypto_cache</v>
@@ -1057,7 +1057,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre>
</func>
<func>
- <name>rand_seed_alg(Alg, Seed) -> rand:state()</name>
+ <name since="OTP-22.0">rand_seed_alg(Alg, Seed) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto_aes</v>
@@ -1085,7 +1085,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name>rand_seed_alg_s(Alg) -> rand:state()</name>
+ <name since="OTP 21.0">rand_seed_alg_s(Alg) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto | crypto_cache</v>
@@ -1149,7 +1149,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name>rand_seed_alg_s(Alg, Seed) -> rand:state()</name>
+ <name since="OTP 22.0">rand_seed_alg_s(Alg, Seed) -> rand:state()</name>
<fsummary>Strong random number generation plugin state</fsummary>
<type>
<v>Alg = crypto_aes</v>
@@ -1215,7 +1215,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="stream_init" arity="2"/>
+ <name name="stream_init" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Initializes the state for use in RC4 stream encryption
@@ -1228,7 +1228,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="stream_init" arity="3"/>
+ <name name="stream_init" arity="3" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Initializes the state for use in streaming AES encryption using Counter mode (CTR).
@@ -1243,7 +1243,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="stream_encrypt" arity="2"/>
+ <name name="stream_encrypt" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Encrypts <c>PlainText</c> according to the stream cipher <c>Type</c> specified in stream_init/3.
@@ -1254,7 +1254,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="stream_decrypt" arity="2"/>
+ <name name="stream_decrypt" arity="2" since="OTP R16B01"/>
<fsummary></fsummary>
<desc>
<p>Decrypts <c>CipherText</c> according to the stream cipher <c>Type</c> specified in stream_init/3.
@@ -1265,7 +1265,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="supports" arity="0"/>
+ <name name="supports" arity="0" since="OTP R16B01"/>
<fsummary>Provide a list of available crypto algorithms.</fsummary>
<desc>
<p> Can be used to determine which crypto algorithms that are supported
@@ -1277,7 +1277,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="ec_curves" arity="0"/>
+ <name name="ec_curves" arity="0" since="OTP 17.0"/>
<fsummary>Provide a list of available named elliptic curves.</fsummary>
<desc>
<p>Can be used to determine which named elliptic curves are supported.</p>
@@ -1285,7 +1285,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="ec_curve" arity="1"/>
+ <name name="ec_curve" arity="1" since="OTP 17.0"/>
<fsummary>Get the defining parameters of a elliptic curve.</fsummary>
<desc>
<p>Return the defining parameters of a elliptic curve.</p>
@@ -1293,8 +1293,8 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="sign" arity="4"/>
- <name name="sign" arity="5"/>
+ <name name="sign" arity="4" since="OTP R16B01"/>
+ <name name="sign" arity="5" since="OTP 20.1"/>
<fsummary> Create digital signature.</fsummary>
<desc>
<p>Creates a digital signature.</p>
@@ -1308,8 +1308,8 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="verify" arity="5"/>
- <name name="verify" arity="6"/>
+ <name name="verify" arity="5" since="OTP R16B01"/>
+ <name name="verify" arity="6" since="OTP 20.1"/>
<fsummary>Verifies a digital signature.</fsummary>
<desc>
<p>Verifies a digital signature</p>
@@ -1325,7 +1325,7 @@ FloatValue = rand:uniform(). % again
<!-- Engine functions -->
<func>
- <name name="privkey_to_pubkey" arity="2"/>
+ <name name="privkey_to_pubkey" arity="2" since="OTP 20.2"/>
<fsummary>Fetches a public key from an Engine stored private key.</fsummary>
<desc>
<p>Fetches the corresponding public key from a private key stored in an Engine.
@@ -1335,7 +1335,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_get_all_methods" arity="0"/>
+ <name name="engine_get_all_methods" arity="0" since="OTP 20.2"/>
<fsummary>Return list of all possible engine methods</fsummary>
<desc>
<p>
@@ -1353,7 +1353,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_load" arity="3"/>
+ <name name="engine_load" arity="3" since="OTP 20.2"/>
<fsummary>Dynamical load an encryption engine</fsummary>
<desc>
<p>
@@ -1375,7 +1375,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_load" arity="4"/>
+ <name name="engine_load" arity="4" since="OTP 20.2"/>
<fsummary>Dynamical load an encryption engine</fsummary>
<desc>
<p>
@@ -1395,7 +1395,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_unload" arity="1"/>
+ <name name="engine_unload" arity="1" since="OTP 20.2"/>
<fsummary>Dynamical load an encryption engine</fsummary>
<desc>
<p>
@@ -1415,7 +1415,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_by_id" arity="1"/>
+ <name name="engine_by_id" arity="1" since="OTP 21.0.6"/>
<fsummary>Get a reference to an already loaded engine</fsummary>
<desc>
<p>
@@ -1435,7 +1435,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_ctrl_cmd_string" arity="3"/>
+ <name name="engine_ctrl_cmd_string" arity="3" since="OTP 20.2"/>
<fsummary>Sends ctrl commands to an OpenSSL engine</fsummary>
<desc>
<p>
@@ -1452,7 +1452,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_ctrl_cmd_string" arity="4"/>
+ <name name="engine_ctrl_cmd_string" arity="4" since="OTP 20.2"/>
<fsummary>Sends ctrl commands to an OpenSSL engine</fsummary>
<desc>
<p>
@@ -1473,7 +1473,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_add" arity="1"/>
+ <name name="engine_add" arity="1" since="OTP 21.0.6"/>
<fsummary>Add engine to OpenSSL internal list</fsummary>
<desc>
<p>Add the engine to OpenSSL's internal list.</p>
@@ -1486,7 +1486,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_remove" arity="1"/>
+ <name name="engine_remove" arity="1" since="OTP 21.0.6"/>
<fsummary>Remove engine to OpenSSL internal list</fsummary>
<desc>
<p>Remove the engine from OpenSSL's internal list.</p>
@@ -1499,7 +1499,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_get_id" arity="1"/>
+ <name name="engine_get_id" arity="1" since="OTP 21.0.6"/>
<fsummary>Fetch engine ID</fsummary>
<desc>
<p>Return the ID for the engine, or an empty binary if there is no id set.</p>
@@ -1512,7 +1512,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_get_name" arity="1"/>
+ <name name="engine_get_name" arity="1" since="OTP 21.0.6"/>
<fsummary>Fetch engine name</fsummary>
<desc>
<p>Return the name (eg a description) for the engine, or an empty binary if there is no name set.</p>
@@ -1525,7 +1525,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="engine_list" arity="0"/>
+ <name name="engine_list" arity="0" since="OTP 20.2"/>
<fsummary>List the known engine ids</fsummary>
<desc>
<p>List the id's of all engines in OpenSSL's internal list.</p>
@@ -1545,7 +1545,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="ensure_engine_loaded" arity="2"/>
+ <name name="ensure_engine_loaded" arity="2" since="OTP 21.0.6"/>
<fsummary>Ensure encryption engine just loaded once</fsummary>
<desc>
<p>
@@ -1567,7 +1567,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="ensure_engine_loaded" arity="3"/>
+ <name name="ensure_engine_loaded" arity="3" since="OTP 21.0.6"/>
<fsummary>Ensure encryption engine just loaded once</fsummary>
<desc>
<p>
@@ -1590,7 +1590,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="ensure_engine_unloaded" arity="1"/>
+ <name name="ensure_engine_unloaded" arity="1" since="OTP 21.0.6"/>
<fsummary>Unload an engine loaded with the ensure function</fsummary>
<desc>
<p>
@@ -1613,7 +1613,7 @@ FloatValue = rand:uniform(). % again
</func>
<func>
- <name name="ensure_engine_unloaded" arity="2"/>
+ <name name="ensure_engine_unloaded" arity="2" since="OTP 21.0.6"/>
<fsummary>Unload an engine loaded with the ensure function</fsummary>
<desc>
<p>
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index d81a8ddd87..0a3f68ade2 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,43 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Updated the RSA options part in the crypto application's
+ C-code, documentation and tests.</p>
+ <p>
+ Own Id: OTP-15302</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added ed25519 and ed448 sign/verify.</p>
+ <p>
+ Requires OpenSSL 1.1.1 or higher as cryptolib under the
+ OTP application <c>crypto</c>.</p>
+ <p>
+ Own Id: OTP-15419 Aux Id: OTP-15094 </p>
+ </item>
+ <item>
+ <p>
+ Fixed valgrind warnings.</p>
+ <p>
+ Own Id: OTP-15467</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.3.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index feb02d0f5a..6836e30a1b 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -1351,7 +1351,11 @@ engine_load_1(Engine, PreCmds, PostCmds, EngineMethods) ->
throw:Error ->
%% The engine couldn't initialise, release the structural reference
ok = engine_free_nif(Engine),
- throw(Error)
+ throw(Error);
+ error:badarg ->
+ %% For example bad argument list, release the structural reference
+ ok = engine_free_nif(Engine),
+ error(badarg)
end.
engine_load_2(Engine, PostCmds, EngineMethods) ->
diff --git a/lib/crypto/test/Makefile b/lib/crypto/test/Makefile
index e046a25338..8b320e01a9 100644
--- a/lib/crypto/test/Makefile
+++ b/lib/crypto/test/Makefile
@@ -6,6 +6,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
MODULES = \
+ crypto_bench_SUITE \
blowfish_SUITE \
crypto_SUITE \
engine_SUITE
@@ -77,7 +78,7 @@ release_spec:
release_tests_spec: $(TEST_TARGET)
$(INSTALL_DIR) "$(RELSYSDIR)"
- $(INSTALL_DATA) crypto.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)"
+ $(INSTALL_DATA) crypto.spec crypto_bench.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)"
@tar cfh - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -)
chmod -R u+w "$(RELSYSDIR)"
diff --git a/lib/crypto/test/crypto.spec b/lib/crypto/test/crypto.spec
index cc09970cb3..4a95275687 100644
--- a/lib/crypto/test/crypto.spec
+++ b/lib/crypto/test/crypto.spec
@@ -1 +1,6 @@
{suites,"../crypto_test",all}.
+
+{skip_suites, "../crypto_test", [crypto_bench_SUITE
+ ],
+ "Benchmarks run separately"}.
+
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 8e20304dc9..003e0c58b1 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -38,6 +38,7 @@ all() ->
mod_pow,
exor,
rand_uniform,
+ rand_threads,
rand_plugin,
rand_plugin_s
].
@@ -155,7 +156,7 @@ groups() ->
]},
{dh, [], [generate_compute,
compute_bug]},
- {ecdh, [], [generate_all_supported, compute, generate]},
+ {ecdh, [], [use_all_elliptic_curves, compute, generate]},
{srp, [], [generate_compute]},
{des_cbc, [], [block]},
{des_cfb, [], [block]},
@@ -562,32 +563,43 @@ compute(Config) when is_list(Config) ->
Gen = proplists:get_value(compute, Config),
lists:foreach(fun do_compute/1, Gen).
%%--------------------------------------------------------------------
-generate_all_supported() ->
- [{doc, " Test that all curves from crypto:ec_curves/0 returns two binaries"}].
-generate_all_supported(_Config) ->
+use_all_elliptic_curves() ->
+ [{doc, " Test that all curves from crypto:ec_curves/0"}].
+use_all_elliptic_curves(_Config) ->
+ Msg = <<"hello world!">>,
+ Sups = crypto:supports(),
+ Curves = proplists:get_value(curves, Sups),
+ Hashs = proplists:get_value(hashs, Sups),
+ ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~nHashs: ~p", [crypto:info_lib(),
+ crypto:info_fips(),
+ Curves,
+ Hashs]),
Results =
- [try
- crypto:generate_key(ecdh, C)
- of
- {B1,B2} when is_binary(B1) and is_binary(B2) ->
- %% That is, seems like it works as expected.
- {ok,C};
- Err ->
- ct:log("ERROR: Curve ~p generated ~p", [C,Err]),
- {error,{C,Err}}
- catch
- Cls:Err:Stack ->
- ct:log("ERROR: Curve ~p exception ~p:~p~n~p", [C,Cls,Err,Stack]),
- {error,{C,{Cls,Err}}}
- end
- || C <- crypto:ec_curves(),
- not lists:member(C, [ed25519, ed448])
+ [{{Curve,Hash},
+ try
+ {Pub,Priv} = crypto:generate_key(ecdh, Curve),
+ true = is_binary(Pub),
+ true = is_binary(Priv),
+ Sig = crypto:sign(ecdsa, Hash, Msg, [Priv, Curve]),
+ crypto:verify(ecdsa, Hash, Msg, Sig, [Pub, Curve])
+ catch
+ C:E ->
+ {C,E}
+ end}
+ || Curve <- Curves -- [ed25519, ed448, x25519, x448, ipsec3, ipsec4],
+ Hash <- Hashs -- [md4, md5, ripemd160, sha3_224, sha3_256, sha3_384, sha3_512]
],
- OK = [C || {ok,C} <- Results],
- ct:log("Ok (len=~p): ~p", [length(OK), OK]),
- false = lists:any(fun({error,_}) -> true;
- (_) -> false
- end, Results).
+ Fails =
+ lists:filter(fun({_,true}) -> false;
+ (_) -> true
+ end, Results),
+ case Fails of
+ [] ->
+ ok;
+ _ ->
+ ct:log("Fails:~n~p",[Fails]),
+ ct:fail("Bad curve(s)",[])
+ end.
%%--------------------------------------------------------------------
generate() ->
@@ -614,6 +626,25 @@ rand_uniform(Config) when is_list(Config) ->
10 = byte_size(crypto:strong_rand_bytes(10)).
%%--------------------------------------------------------------------
+rand_threads() ->
+ [{doc, "strong_rand_bytes in parallel threads"}].
+rand_threads(Config) when is_list(Config) ->
+ %% This will crash the emulator on at least one version of libcrypto
+ %% with buggy multithreading in RAND_bytes().
+ %% The test needs to run at least a few minutes...
+ NofThreads = 4,
+ Fun = fun F() -> crypto:strong_rand_bytes(16), F() end,
+ PidRefs = [spawn_monitor(Fun) || _ <- lists:seq(1, NofThreads)],
+%%% The test case takes too much time to run.
+%%% Keep it around for reference by setting it down to just 10 seconds.
+%%% receive after 10 * 60 * 1000 -> ok end, % 10 minutes
+ receive after 10 * 1000 -> ok end, % 10 seconds
+ spawn_link(fun () -> receive after 5000 -> exit(timeout) end end),
+ [exit(Pid, stop) || {Pid,_Ref} <- PidRefs],
+ [receive {'DOWN',Ref,_,_,stop} -> ok end || {_Pid,Ref} <- PidRefs],
+ ok.
+
+%%--------------------------------------------------------------------
rand_plugin() ->
[{doc, "crypto rand plugin testing (implicit state / process dictionary)"}].
rand_plugin(Config) when is_list(Config) ->
diff --git a/lib/crypto/test/crypto_bench.spec b/lib/crypto/test/crypto_bench.spec
new file mode 100644
index 0000000000..b9a26d94db
--- /dev/null
+++ b/lib/crypto/test/crypto_bench.spec
@@ -0,0 +1,3 @@
+{suites, "../crypto_test", [
+ crypto_bench_SUITE
+ ]}.
diff --git a/lib/crypto/test/crypto_bench_SUITE.erl b/lib/crypto/test/crypto_bench_SUITE.erl
new file mode 100644
index 0000000000..e1fd0a63e5
--- /dev/null
+++ b/lib/crypto/test/crypto_bench_SUITE.erl
@@ -0,0 +1,387 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-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%
+%%
+
+%%
+-module(crypto_bench_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [%%{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]},
+ {timetrap,{minutes,2}}
+ ].
+
+all() ->
+ [
+ {group, ciphers_128}
+ ].
+
+groups() ->
+ [
+ {ciphers_128, [{repeat, 3}], [{group,textblock_256}
+ ]},
+
+ {textblock_256, [{repeat,2}], [
+ block,
+ stream
+ ]}
+ ].
+
+%%%----------------------------------------------------------------
+%%%
+init_per_suite(Config) ->
+ try crypto:start() of
+ _ ->
+ [{_,_,Info}] = crypto:info_lib(),
+ ct:comment("~s",[Info]),
+ ct:pal("Crypto version: ~p~n~n~p",[Info,crypto:supports()]),
+ [{sec_goal,5} | Config]
+ catch _:_ ->
+ {fail, "Crypto did not start"}
+ end.
+
+end_per_suite(_Config) ->
+ application:stop(crypto).
+
+%%%----------------------------------------------------------------
+%%%
+init_per_group(Group, Config0) ->
+ ct:pal("~p(~p,..)",[?FUNCTION_NAME,Group]),
+
+ Config = calibrate(Config0),
+ case atom_to_list(Group) of
+ "ciphers_"++KeySizeStr ->
+ KeySize = list_to_integer(KeySizeStr),
+ [{key_size,KeySize}
+ | measure_openssl_aes_cbc(KeySize, Config)];
+
+ "textblock_"++BlockSizeStr ->
+ BlockSize = list_to_integer(BlockSizeStr),
+ [{block_size,BlockSize} | Config];
+
+ _ ->
+ Config
+ end.
+
+end_per_group(_Group, Config) ->
+ Config.
+
+
+measure_openssl_aes_cbc(KeySize, Config) ->
+ BLno_acc = [baseline(aes_cbc, KeySize, false)],
+ ct:pal("Non-accelerated baseline encryption time [µs/block]:~n~p", [BLno_acc]),
+ BLacc = [baseline(aes_cbc, KeySize, true)],
+ ct:pal("Possibly accelerated baseline encryption time [µs/block]:~n~p", [BLacc]),
+ [{acc,BLacc},
+ {no_acc,BLno_acc} | Config].
+
+calibrate(Config) ->
+ Secs = proplists:get_value(sec_goal, Config, 5),
+ {_,Empty} = data(empty, 0, 0),
+ {Ne,Te} = run1(Secs*2000, Empty),
+ [{overhead,Te/Ne} | Config].
+
+%%%================================================================
+%%%
+%%%
+block(Config) ->
+ run_cryptos([aes_cbc, aes_gcm, aes_ccm, chacha20_poly1305],
+ Config).
+
+stream(Config) ->
+ run_cryptos([aes_ctr, chacha20],
+ Config).
+
+%%%================================================================
+%%%
+%%%
+
+run_cryptos(Cryptos, Config) ->
+ run_cryptos(Cryptos, 1, Config).
+
+run_cryptos(Cryptos, Factor, Config) ->
+ KeySize = proplists:get_value(key_size, Config),
+ BlockSize = proplists:get_value(block_size, Config),
+ MilliSecGoal = 1000*proplists:get_value(sec_goal,Config),
+ OverHead = proplists:get_value(overhead, Config, 0),
+ [try
+ Factor*run(Crypto,KeySize,BlockSize,MilliSecGoal) - OverHead
+ of
+ TimePerOp -> % µs
+ %% First, Report speed of encrypting blocks of 1000. [blocks/sec]
+ ReportUnit = 1000,
+ Label = [fmt(Crypto)," key:",KeySize," block:",BlockSize],
+ report(Label,
+ (BlockSize/ReportUnit)*1000000/TimePerOp
+ ),
+
+ EffCrypto = case Crypto of
+ X -> X
+ end,
+ %% Percent of accelerated speed
+ case find_value([acc,{EffCrypto,KeySize},BlockSize], Config) of
+ undefined ->
+ ok;
+ TimePerOpBaseAcc ->
+ report(["Percent of acc OpenSSL "|Label],
+ 100*TimePerOpBaseAcc/TimePerOp % Percent of base *speed*
+ )
+ end,
+
+ %% Percent of non-accelerated speed
+ case find_value([no_acc,{EffCrypto,KeySize},BlockSize], Config) of
+ undefined ->
+ ok;
+ TimePerOpBaseNoAcc ->
+ report(["Percent of noacc OpenSSL "|Label],
+ 100*TimePerOpBaseNoAcc/TimePerOp % Percent of base *speed*
+ )
+ end
+ catch
+ _:_ ->
+ ct:pal("~p unsupported",[{Crypto,KeySize,BlockSize}])
+ end
+ || Crypto <- Cryptos,
+ supported(Crypto)
+ ].
+
+
+run(Crypto, KeySize, BlockSize, MilliSecGoal) ->
+ {_Type, Funs} = data(Crypto, KeySize, BlockSize),
+ {Nc,Tc} = run1(MilliSecGoal, Funs),
+ Tc/Nc.
+
+fmt(X) -> X.
+
+
+find_value(KeyPath, PropList, Default) ->
+ try find_value(KeyPath, PropList)
+ of
+ undefined -> Default
+ catch
+ error:function_clause -> Default
+ end.
+
+find_value(KeyPath, PropList) ->
+ lists:foldl(fun(K, L) when is_list(L) -> proplists:get_value(K,L);
+ (_, _) -> undefined
+ end, PropList, KeyPath).
+
+%%%================================================================
+%%%
+%%%
+funs({block, {Type, Key, IV, Block}}) ->
+ {fun() -> ok end,
+ fun(_) -> crypto:block_encrypt(Type, Key, IV, Block) end,
+ fun(_) -> ok end};
+
+funs({stream, {Type, Key, IV, Block}}) ->
+ {fun() -> {crypto:stream_init(Type, Key, IV),ok} end,
+ fun({Ctx,_}) -> crypto:stream_encrypt(Ctx, Block) end,
+ fun(_) -> ok end}.
+
+
+data(aes_cbc, KeySize, BlockSize) ->
+ Type = case KeySize of
+ 128 -> aes_cbc128;
+ 256 -> aes_cbc256
+ end,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ Block = mk_bin(BlockSize),
+ {Type, funs({block, {Type, Key, IV, Block}})};
+
+data(aes_gcm, KeySize, BlockSize) ->
+ Type = aes_gcm,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(12),
+ Block = mk_bin(BlockSize),
+ AAD = <<01,02,03,04>>,
+ {Type, funs({block, {Type, Key, IV, {AAD,Block,16}}})};
+
+data(aes_ccm, KeySize, BlockSize) ->
+ Type = aes_ccm,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(12),
+ Block = mk_bin(BlockSize),
+ AAD = <<01,02,03,04>>,
+ {Type, funs({block, {Type, Key, IV, {AAD,Block,12}}})};
+
+data(aes_ctr, KeySize, BlockSize) ->
+ Type = aes_ctr,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ Block = mk_bin(BlockSize),
+ {Type, funs({stream, {Type, Key, IV, Block}})};
+
+data(chacha20_poly1305, 256=KeySize, BlockSize) ->
+ Type = chacha20_poly1305,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ AAD = <<01,02,03,04>>,
+ Block = mk_bin(BlockSize),
+ {Type, funs({block, {Type, Key, IV, {AAD,Block}}})};
+
+data(chacha20, 256=KeySize, BlockSize) ->
+ Type = chacha20,
+ Key = mk_bin(KeySize div 8),
+ IV = mk_bin(16),
+ Block = mk_bin(BlockSize),
+ {Type, funs({stream, {Type, Key, IV, Block}})};
+
+data(empty, 0, 0) ->
+ {undefined,
+ {fun() -> ok end,
+ fun(X) -> X end,
+ fun(_) -> ok end}}.
+
+%%%================================================================
+%%%
+%%%
+run1(MilliSecGoal, Funs) ->
+ Parent = self(),
+ Pid = spawn(fun() ->
+ {Fi,Fu,Ff} = Funs,
+ Ctx0 = Fi(),
+ T0 = start_time(),
+ {N,Ctx} = loop(Fu, Ctx0, 0),
+ T = elapsed_time(T0),
+ Ff(Ctx),
+ Parent ! {result,N,microseconds(T)}
+ end),
+ Pid ! go,
+ receive
+ after MilliSecGoal ->
+ Pid ! stop
+ end,
+ receive
+ {result,N,MicroSecs} ->
+ {N,MicroSecs}
+ end.
+
+
+loop(F, Ctx, N) ->
+ receive
+ stop ->
+ {N, Ctx}
+ after 0 ->
+ loop(F, F(Ctx), N+1)
+ end.
+
+%%%----------------------------------------------------------------
+report(LabelList, Value) ->
+ Label = report_chars(lists:concat(LabelList)),
+ ct:pal("ct_event:notify ~p: ~p", [Label, Value]),
+ ct_event:notify(
+ #event{name = benchmark_data,
+ data = [{name, Label},
+ {value,Value}]}).
+
+report_chars(Cs) ->
+ [case C of
+ $- -> $_;
+ _ -> C
+ end || C <- Cs].
+
+%%%----------------------------------------------------------------
+supported(Algorithm) ->
+ lists:member(Algorithm,
+ [A || {_,As} <- crypto:supports(), A <- As]
+ ).
+
+%%%----------------------------------------------------------------
+start_time() ->
+ erlang:system_time().
+
+elapsed_time(StartTime) ->
+ erlang:system_time() - StartTime.
+
+microseconds(Time) ->
+ erlang:convert_time_unit(Time, native, microsecond).
+
+%%%----------------------------------------------------------------
+
+%% Example output:
+%% +DT:aes-128-cbc:3:16
+%% +R:135704772:aes-128-cbc:2.980000
+%% +DT:aes-128-cbc:3:64
+%% +R:36835089:aes-128-cbc:3.000000
+%% +DT:aes-128-cbc:3:256
+%% +R:9398616:aes-128-cbc:3.000000
+%% +DT:aes-128-cbc:3:1024
+%% +R:2355683:aes-128-cbc:2.990000
+%% +DT:aes-128-cbc:3:8192
+%% +R:294508:aes-128-cbc:2.990000
+%% +H:16:64:256:1024:8192
+%% +F:22:aes-128-cbc:728616225.50:785815232.00:802015232.00:806762338.46:806892821.40
+
+baseline(Crypto, KeySize, EVP) ->
+ Spec=
+ case {Crypto,KeySize} of
+ {aes_cbc, 128} -> "aes-128-cbc";
+ {aes_cbc, 256} -> "aes-256-cbc"
+ end,
+ {{Crypto,KeySize}, baseline(Spec, EVP)}.
+
+baseline(Spec, EVP) ->
+ Cmd =
+ case EVP of
+ true -> "openssl speed -mr -evp " ++ Spec;
+ false-> "openssl speed -mr " ++ Spec
+ end,
+ get_base_values(string:tokens(os:cmd(Cmd),"\n"), Spec, []).
+
+
+get_base_values(["+DT:"++Sdt,
+ "+R:"++Sr
+ |T], Crypto, Acc) ->
+ [Crypto0,_GoalSecs0,BlockSize0] = string:tokens(Sdt, ":"),
+ [Nblocks0,Crypto0,RealSecs0] = string:tokens(Sr, ":"),
+ Crypto = fix_possible_space_bug(Crypto0),
+ RealSecs = list_to_float(RealSecs0),
+ BlockSize = list_to_integer(BlockSize0),
+ Nblocks = list_to_integer(Nblocks0),
+ get_base_values(T, Crypto, [{BlockSize, 1000000*RealSecs/Nblocks} | Acc]);
+
+get_base_values([_|T], Crypto, Acc) ->
+ get_base_values(T, Crypto, Acc);
+
+get_base_values([], _, Acc) ->
+ lists:sort(Acc).
+
+fix_possible_space_bug(S) -> lists:concat(lists:join("-",string:tokens(S,"- "))).
+
+%%%----------------------------------------------------------------
+mk_bin(Size) when Size =< 256 ->
+ list_to_binary(lists:seq(0,Size-1));
+
+mk_bin(Size) when 1024 =< Size ->
+ B = mk_bin(Size div 4),
+ Brest = mk_bin(Size rem 4),
+ <<B/binary, B/binary, B/binary, B/binary, Brest/binary>>;
+
+mk_bin(Size) when 256 < Size ->
+ B = mk_bin(Size div 2),
+ Brest = mk_bin(Size rem 2),
+ <<B/binary, B/binary, Brest/binary>>.
+
diff --git a/lib/crypto/test/engine_SUITE.erl b/lib/crypto/test/engine_SUITE.erl
index 3f26493cd3..2a89fa71a3 100644
--- a/lib/crypto/test/engine_SUITE.erl
+++ b/lib/crypto/test/engine_SUITE.erl
@@ -51,12 +51,14 @@ all() ->
ctrl_cmd_string,
ctrl_cmd_string_optional,
ensure_load,
- {group, engine_stored_key}
+ {group, engine_stored_key},
+ {group, engine_fakes_rsa}
].
groups() ->
[{engine_stored_key, [],
- [sign_verify_rsa,
+ [
+ sign_verify_rsa,
sign_verify_dsa,
sign_verify_ecdsa,
sign_verify_rsa_pwd,
@@ -71,7 +73,10 @@ groups() ->
get_pub_from_priv_key_rsa_pwd_bad_pwd,
get_pub_from_priv_key_dsa,
get_pub_from_priv_key_ecdsa
- ]}].
+ ]},
+ {engine_fakes_rsa, [], [sign_verify_rsa_fake
+ ]}
+ ].
init_per_suite(Config) ->
@@ -102,7 +107,20 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(engine_stored_key, Config) ->
- case load_storage_engine(Config) of
+ group_load_engine(Config, [engine_method_rsa]);
+init_per_group(engine_fakes_rsa, Config) ->
+ case crypto:info_lib() of
+ [{<<"OpenSSL">>,LibVer,_}] when is_integer(LibVer), LibVer >= 16#10100000 ->
+ group_load_engine(Config, []);
+ _ ->
+ {skip, "Too low OpenSSL cryptolib version"}
+ end;
+init_per_group(_Group, Config0) ->
+ Config0.
+
+
+group_load_engine(Config, ExcludeMthds) ->
+ case load_storage_engine(Config, ExcludeMthds) of
{ok, E} ->
KeyDir = key_dir(Config),
[{storage_engine,E}, {storage_dir,KeyDir} | Config];
@@ -115,19 +133,19 @@ init_per_group(engine_stored_key, Config) ->
Other ->
ct:log("Engine load failed: ~p",[Other]),
{fail, "Engine load failed"}
- end;
-init_per_group(_Group, Config0) ->
- Config0.
+ end.
+
+
-end_per_group(engine_stored_key, Config) ->
+
+
+end_per_group(_, Config) ->
case proplists:get_value(storage_engine, Config) of
undefined ->
ok;
E ->
ok = crypto:engine_unload(E)
- end;
-end_per_group(_, _) ->
- ok.
+ end.
%%--------------------------------------------------------------------
init_per_testcase(_Case, Config) ->
@@ -421,6 +439,9 @@ bad_arguments(Config) when is_list(Config) ->
try
try
crypto:engine_load(fail_engine, [], [])
+ of
+ X1 ->
+ ct:fail("1 Got ~p",[X1])
catch
error:badarg ->
ok
@@ -432,6 +453,11 @@ bad_arguments(Config) when is_list(Config) ->
{<<"ID">>, <<"MD5">>},
<<"LOAD">>],
[])
+ of
+ {error,bad_engine_id} ->
+ throw(dynamic_engine_unsupported);
+ X2 ->
+ ct:fail("2 Got ~p",[X2])
catch
error:badarg ->
ok
@@ -442,13 +468,20 @@ bad_arguments(Config) when is_list(Config) ->
{'ID', <<"MD5">>},
<<"LOAD">>],
[])
+ of
+ {error,bad_engine_id} -> % should have happend in the previous try...catch end!
+ throw(dynamic_engine_unsupported);
+ X3 ->
+ ct:fail("3 Got ~p",[X3])
catch
error:badarg ->
ok
end
catch
error:notsup ->
- {skip, "Engine not supported on this SSL version"}
+ {skip, "Engine not supported on this SSL version"};
+ throw:dynamic_engine_unsupported ->
+ {skip, "Dynamic Engine not supported"}
end
end.
@@ -554,11 +587,11 @@ ctrl_cmd_string(Config) when is_list(Config) ->
{ok, E} ->
case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>) of
ok ->
+ ok = crypto:engine_unload(E),
ct:fail(fail_ctrl_cmd_should_fail);
{error,ctrl_cmd_failed} ->
- ok
- end,
- ok = crypto:engine_unload(E);
+ ok = crypto:engine_unload(E)
+ end;
{error, bad_engine_id} ->
{skip, "Dynamic Engine not supported"}
end
@@ -584,11 +617,12 @@ ctrl_cmd_string_optional(Config) when is_list(Config) ->
{ok, E} ->
case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>, true) of
ok ->
- ok;
- _ ->
+ ok = crypto:engine_unload(E);
+ Err ->
+ ct:log("Error: ~p",[Err]),
+ ok = crypto:engine_unload(E),
ct:fail(fail_ctrl_cmd_string)
- end,
- ok = crypto:engine_unload(E);
+ end;
{error, bad_engine_id} ->
{skip, "Dynamic Engine not supported"}
end
@@ -650,6 +684,14 @@ sign_verify_rsa(Config) ->
key_id => key_id(Config, "rsa_public_key.pem")},
sign_verify(rsa, sha, Priv, Pub).
+sign_verify_rsa_fake(Config) ->
+ %% Use fake engine rsa implementation
+ Priv = #{engine => engine_ref(Config),
+ key_id => key_id(Config, "rsa_private_key.pem")},
+ Pub = #{engine => engine_ref(Config),
+ key_id => key_id(Config, "rsa_public_key.pem")},
+ sign_verify_fake(rsa, sha256, Priv, Pub).
+
sign_verify_dsa(Config) ->
Priv = #{engine => engine_ref(Config),
key_id => key_id(Config, "dsa_private_key.pem")},
@@ -809,13 +851,18 @@ get_pub_from_priv_key_ecdsa(Config) ->
%%%================================================================
%%% Help for engine_stored_pub_priv_keys* test cases
%%%
-load_storage_engine(_Config) ->
+load_storage_engine(Config) ->
+ load_storage_engine(Config, []).
+
+load_storage_engine(_Config, ExcludeMthds) ->
case crypto:get_test_engine() of
{ok, Engine} ->
try crypto:engine_load(<<"dynamic">>,
[{<<"SO_PATH">>, Engine},
<<"LOAD">>],
- [])
+ [],
+ crypto:engine_get_all_methods() -- ExcludeMthds
+ )
catch
error:notsup ->
{error, notsup}
@@ -873,10 +920,47 @@ sign_verify(Alg, Sha, KeySign, KeyVerify) ->
true ->
PlainText = <<"Hej på dig">>,
Signature = crypto:sign(Alg, Sha, PlainText, KeySign),
- case crypto:verify(Alg, Sha, PlainText, Signature, KeyVerify) of
- true -> ok;
- _ -> {fail, "Sign-verify error"}
+ case is_fake(Signature) of
+ true ->
+ ct:pal("SIG ~p ~p size ~p~n~p",[Alg,Sha,size(Signature),Signature]),
+ {fail, "Faked RSA impl used!!"};
+ false ->
+ case crypto:verify(Alg, Sha, PlainText, Signature, KeyVerify) of
+ true -> ok;
+ _ -> {fail, "Sign-verify error"}
+ end
+ end;
+ false ->
+ {skip, lists:concat([Alg," is not supported by cryptolib"])}
+ end.
+
+
+%%% Use fake engine rsa implementation
+sign_verify_fake(Alg, Sha, KeySign, KeyVerify) ->
+ case pubkey_alg_supported(Alg) of
+ true ->
+ PlainText = <<"Fake me!">>,
+ Signature = crypto:sign(Alg, Sha, PlainText, KeySign),
+ case is_fake(Signature) of
+ true ->
+ case crypto:verify(Alg, Sha, PlainText, Signature, KeyVerify) of
+ true -> ok;
+ _ -> {fail, "Sign-verify error"}
+ end;
+ false ->
+ ct:pal("SIG ~p ~p size ~p~n~p",[Alg,Sha,size(Signature),Signature]),
+ {fail, "Faked impl not used"}
end;
false ->
{skip, lists:concat([Alg," is not supported by cryptolib"])}
end.
+
+
+is_fake(Sig) -> is_fake(Sig, 0).
+
+is_fake(<<>>, _) -> true;
+is_fake(<<B,Rest/binary>>, B) -> is_fake(Rest, B+1);
+is_fake(_, _) -> false.
+
+
+
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 64d593f64a..6a91244715 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.3.3
+CRYPTO_VSN = 4.4
diff --git a/lib/debugger/doc/src/debugger.xml b/lib/debugger/doc/src/debugger.xml
index 1ecdbcd064..77285095e7 100644
--- a/lib/debugger/doc/src/debugger.xml
+++ b/lib/debugger/doc/src/debugger.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>debugger</module>
+ <module since="">debugger</module>
<modulesummary>Erlang Debugger.</modulesummary>
<description>
<p>Erlang Debugger for debugging and testing of Erlang programs.</p>
@@ -36,10 +36,10 @@
<funcs>
<func>
- <name>start()</name>
- <name>start(File)</name>
- <name>start(Mode)</name>
- <name>start(Mode, File)</name>
+ <name since="">start()</name>
+ <name since="">start(File)</name>
+ <name since="">start(Mode)</name>
+ <name since="">start(Mode, File)</name>
<fsummary>Start Debugger.</fsummary>
<type>
<v>Mode = local | global</v>
@@ -60,7 +60,7 @@
</func>
<func>
- <name>quick(Module, Name, Args)</name>
+ <name since="">quick(Module, Name, Args)</name>
<fsummary>Debug a process.</fsummary>
<type>
<v>Module = Name = atom()</v>
diff --git a/lib/debugger/doc/src/i.xml b/lib/debugger/doc/src/i.xml
index 628b91e9e4..06b0eb876a 100644
--- a/lib/debugger/doc/src/i.xml
+++ b/lib/debugger/doc/src/i.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>i</module>
+ <module since="">i</module>
<modulesummary>Debugger/Interpreter Interface.</modulesummary>
<description>
<p>The <c>i</c> module provides short forms for some of
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name>im() -> pid()</name>
+ <name since="">im() -> pid()</name>
<fsummary>Start a graphical monitor.</fsummary>
<desc>
<p>Starts a new graphical monitor. This is the Monitor window,
@@ -63,10 +63,10 @@
</func>
<func>
- <name>ii(AbsModules) -> ok</name>
- <name>ii(AbsModule) -> {module, Module} | error</name>
- <name>ini(AbsModules) -> ok</name>
- <name>ini(AbsModule) -> {module, Module} | error</name>
+ <name since="">ii(AbsModules) -> ok</name>
+ <name since="">ii(AbsModule) -> {module, Module} | error</name>
+ <name since="">ini(AbsModules) -> ok</name>
+ <name since="">ini(AbsModule) -> {module, Module} | error</name>
<fsummary>Interpret a module.</fsummary>
<type>
<v>AbsModules = [AbsModule]</v>
@@ -84,8 +84,8 @@
</func>
<func>
- <name>iq(AbsModule) -> ok</name>
- <name>inq(AbsModule) -> ok</name>
+ <name since="">iq(AbsModule) -> ok</name>
+ <name since="">inq(AbsModule) -> ok</name>
<fsummary>Stop interpreting a module.</fsummary>
<type>
<v>AbsModule = Module | File</v>
@@ -100,7 +100,7 @@
</func>
<func>
- <name>il() -> ok</name>
+ <name since="">il() -> ok</name>
<fsummary>Make a printout of all interpreted modules</fsummary>
<desc>
<p>Makes a printout of all interpreted modules.
@@ -110,7 +110,7 @@
</func>
<func>
- <name>ip() -> ok</name>
+ <name since="">ip() -> ok</name>
<fsummary>Print the current status of all interpreted
processes.</fsummary>
<desc>
@@ -119,7 +119,7 @@
</func>
<func>
- <name>ic() -> ok</name>
+ <name since="">ic() -> ok</name>
<fsummary>Clear information about processes executing interpreted
code.</fsummary>
<desc>
@@ -129,8 +129,8 @@
</func>
<func>
- <name>iaa(Flags) -> true</name>
- <name>iaa(Flags, Function) -> true</name>
+ <name since="">iaa(Flags) -> true</name>
+ <name since="">iaa(Flags, Function) -> true</name>
<fsummary>Set when and how to attach to a process.</fsummary>
<type>
<v>Flags = [init | break | exit]</v>
@@ -148,7 +148,7 @@
</func>
<func>
- <name>ist(Flag) -> true</name>
+ <name since="">ist(Flag) -> true</name>
<fsummary>Set how to save call frames.</fsummary>
<type>
<v>Flag = all | no_tail | false</v>
@@ -160,7 +160,7 @@
</func>
<func>
- <name>ia(Pid) -> ok | no_proc</name>
+ <name since="">ia(Pid) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -172,7 +172,7 @@
</func>
<func>
- <name>ia(X,Y,Z) -> ok | no_proc</name>
+ <name since="">ia(X,Y,Z) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>X = Y = Z = int()</v>
@@ -184,7 +184,7 @@
</func>
<func>
- <name>ia(Pid, Function) -> ok | no_proc</name>
+ <name since="">ia(Pid, Function) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -199,7 +199,7 @@
</func>
<func>
- <name>ia(X,Y,Z, Function) -> ok | no_proc</name>
+ <name since="">ia(X,Y,Z, Function) -> ok | no_proc</name>
<fsummary>Attache to a process.</fsummary>
<type>
<v>X = Y = Z = int()</v>
@@ -217,7 +217,7 @@
</func>
<func>
- <name>ib(Module, Line) -> ok | {error, break_exists}</name>
+ <name since="">ib(Module, Line) -> ok | {error, break_exists}</name>
<fsummary>Create a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -229,7 +229,7 @@
</func>
<func>
- <name>ib(Module, Name, Arity) -> ok | {error, function_not_found}
+ <name since="">ib(Module, Name, Arity) -> ok | {error, function_not_found}
</name>
<fsummary>Create breakpoints in the specified function.</fsummary>
<type>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>ir() -> ok</name>
+ <name since="">ir() -> ok</name>
<fsummary>Delete all breakpoints.</fsummary>
<desc>
<p>Deletes all breakpoints.</p>
@@ -251,7 +251,7 @@
</func>
<func>
- <name>ir(Module) -> ok</name>
+ <name since="">ir(Module) -> ok</name>
<fsummary>Delete all breakpoints in a module.</fsummary>
<type>
<v>Module = atom()</v>
@@ -262,7 +262,7 @@
</func>
<func>
- <name>ir(Module, Line) -> ok</name>
+ <name since="">ir(Module, Line) -> ok</name>
<fsummary>Delete a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -274,7 +274,7 @@
</func>
<func>
- <name>ir(Module, Name, Arity) -> ok | {error, function_not_found}
+ <name since="">ir(Module, Name, Arity) -> ok | {error, function_not_found}
</name>
<fsummary>Delete breakpoints from the specified function.</fsummary>
<type>
@@ -288,7 +288,7 @@
</func>
<func>
- <name>ibd(Module, Line) -> ok</name>
+ <name since="">ibd(Module, Line) -> ok</name>
<fsummary>Make a breakpoint inactive.</fsummary>
<type>
<v>Module = atom()</v>
@@ -300,7 +300,7 @@
</func>
<func>
- <name>ibe(Module, Line) -> ok</name>
+ <name since="">ibe(Module, Line) -> ok</name>
<fsummary>Make a breakpoint active.</fsummary>
<type>
<v>Module = atom()</v>
@@ -312,7 +312,7 @@
</func>
<func>
- <name>iba(Module, Line, Action) -> ok</name>
+ <name since="">iba(Module, Line, Action) -> ok</name>
<fsummary>Set the trigger action of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -326,7 +326,7 @@
</func>
<func>
- <name>ibc(Module, Line, Function) -> ok</name>
+ <name since="">ibc(Module, Line, Function) -> ok</name>
<fsummary>Set the conditional test of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -348,7 +348,7 @@
</func>
<func>
- <name>ipb() -> ok</name>
+ <name since="">ipb() -> ok</name>
<fsummary>Print all existing breakpoints.</fsummary>
<desc>
<p>Prints all existing breakpoints.</p>
@@ -356,7 +356,7 @@
</func>
<func>
- <name>ipb(Module) -> ok</name>
+ <name since="">ipb(Module) -> ok</name>
<fsummary>Print all existing breakpoints in a module.</fsummary>
<type>
<v>Module = atom()</v>
@@ -367,7 +367,7 @@
</func>
<func>
- <name>iv() -> atom()</name>
+ <name since="">iv() -> atom()</name>
<fsummary>Return the current version number of the interpreter.
</fsummary>
<desc>
@@ -377,7 +377,7 @@
</func>
<func>
- <name>help() -> ok</name>
+ <name since="">help() -> ok</name>
<fsummary>Print help text.</fsummary>
<desc>
<p>Prints help text.</p>
diff --git a/lib/debugger/doc/src/int.xml b/lib/debugger/doc/src/int.xml
index 31e9dfe923..a0078714e6 100644
--- a/lib/debugger/doc/src/int.xml
+++ b/lib/debugger/doc/src/int.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>int</module>
+ <module since="">int</module>
<modulesummary>Interpreter Interface.</modulesummary>
<description>
<p>The Erlang interpreter provides mechanisms for breakpoints and
@@ -94,10 +94,10 @@
<funcs>
<func>
- <name>i(AbsModule) -> {module,Module} | error</name>
- <name>i(AbsModules) -> ok</name>
- <name>ni(AbsModule) -> {module,Module} | error</name>
- <name>ni(AbsModules) -> ok</name>
+ <name since="">i(AbsModule) -> {module,Module} | error</name>
+ <name since="">i(AbsModules) -> ok</name>
+ <name since="">ni(AbsModule) -> {module,Module} | error</name>
+ <name since="">ni(AbsModules) -> ok</name>
<fsummary>Interpret a module.</fsummary>
<type>
<v>AbsModules = [AbsModule]</v>
@@ -144,8 +144,8 @@
</func>
<func>
- <name>n(AbsModule) -> ok</name>
- <name>nn(AbsModule) -> ok</name>
+ <name since="">n(AbsModule) -> ok</name>
+ <name since="">nn(AbsModule) -> ok</name>
<fsummary>Stop interpreting a module.</fsummary>
<type>
<v>AbsModule = Module | File | [Module | File]</v>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>interpreted() -> [Module]</name>
+ <name since="">interpreted() -> [Module]</name>
<fsummary>Get all interpreted modules.</fsummary>
<type>
<v>Module = atom()</v>
@@ -174,7 +174,7 @@
</func>
<func>
- <name>file(Module) -> File | {error,not_loaded}</name>
+ <name since="">file(Module) -> File | {error,not_loaded}</name>
<fsummary>Get the filename for an interpreted module.</fsummary>
<type>
<v>Module = atom()</v>
@@ -187,7 +187,7 @@
</func>
<func>
- <name>interpretable(AbsModule) -> true | {error,Reason}</name>
+ <name since="">interpretable(AbsModule) -> true | {error,Reason}</name>
<fsummary>Check if a module can be interpreted.</fsummary>
<type>
<v>AbsModule = Module | File</v>
@@ -255,9 +255,9 @@
</func>
<func>
- <name>auto_attach() -> false | {Flags,Function}</name>
- <name>auto_attach(false)</name>
- <name>auto_attach(Flags, Function)</name>
+ <name since="">auto_attach() -> false | {Flags,Function}</name>
+ <name since="">auto_attach(false)</name>
+ <name since="">auto_attach(Flags, Function)</name>
<fsummary>Get and set when and how to attach to a process.</fsummary>
<type>
<v>Flags = [init | break | exit]</v>
@@ -290,8 +290,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>stack_trace() -> Flag</name>
- <name>stack_trace(Flag)</name>
+ <name since="">stack_trace() -> Flag</name>
+ <name since="">stack_trace(Flag)</name>
<fsummary>Get and set if and how to save call frames.</fsummary>
<type>
<v>Flag = all | no_tail | false</v>
@@ -322,7 +322,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>break(Module, Line) -> ok | {error,break_exists}</name>
+ <name since="">break(Module, Line) -> ok | {error,break_exists}</name>
<fsummary>Create a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -334,7 +334,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>delete_break(Module, Line) -> ok</name>
+ <name since="">delete_break(Module, Line) -> ok</name>
<fsummary>Delete a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -346,7 +346,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>break_in(Module, Name, Arity) -> ok
+ <name since="">break_in(Module, Name, Arity) -> ok
| {error,function_not_found}</name>
<fsummary>Create breakpoints in the specified function.</fsummary>
<type>
@@ -360,7 +360,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>del_break_in(Module, Name, Arity) -> ok
+ <name since="">del_break_in(Module, Name, Arity) -> ok
| {error,function_not_found}</name>
<fsummary>Delete breakpoints from the specified function.</fsummary>
<type>
@@ -374,8 +374,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>no_break() -> ok</name>
- <name>no_break(Module) -> ok</name>
+ <name since="">no_break() -> ok</name>
+ <name since="">no_break(Module) -> ok</name>
<fsummary>Delete all breakpoints.</fsummary>
<desc>
<p>Deletes all breakpoints, or all breakpoints in <c>Module</c>.</p>
@@ -383,7 +383,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>disable_break(Module, Line) -> ok</name>
+ <name since="">disable_break(Module, Line) -> ok</name>
<fsummary>Make a breakpoint inactive.</fsummary>
<type>
<v>Module = atom()</v>
@@ -395,7 +395,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>enable_break(Module, Line) -> ok</name>
+ <name since="">enable_break(Module, Line) -> ok</name>
<fsummary>Make a breakpoint active.</fsummary>
<type>
<v>Module = atom()</v>
@@ -407,7 +407,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>action_at_break(Module, Line, Action) -> ok</name>
+ <name since="">action_at_break(Module, Line, Action) -> ok</name>
<fsummary>Set the trigger action of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -421,7 +421,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>test_at_break(Module, Line, Function) -> ok</name>
+ <name since="">test_at_break(Module, Line, Function) -> ok</name>
<fsummary>Set the conditional test of a breakpoint.</fsummary>
<type>
<v>Module = atom()</v>
@@ -438,7 +438,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>get_binding(Var, Bindings) -> {value,Value} | unbound</name>
+ <name since="">get_binding(Var, Bindings) -> {value,Value} | unbound</name>
<fsummary>Retrieve a variable binding.</fsummary>
<type>
<v>Var = atom()</v>
@@ -453,8 +453,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>all_breaks() -> [Break]</name>
- <name>all_breaks(Module) -> [Break]</name>
+ <name since="">all_breaks() -> [Break]</name>
+ <name since="">all_breaks(Module) -> [Break]</name>
<fsummary>Get all breakpoints.</fsummary>
<type>
<v>Break = {Point,Options}</v>
@@ -474,7 +474,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>snapshot() -> [Snapshot]</name>
+ <name since="">snapshot() -> [Snapshot]</name>
<fsummary>Get information about all processes executing interpreted
code.</fsummary>
<type>
@@ -519,7 +519,7 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>clear() -> ok</name>
+ <name since="">clear() -> ok</name>
<fsummary>Clear information about processes executing interpreted
code.</fsummary>
<desc>
@@ -529,8 +529,8 @@ spawn(Module, Name, [Pid | Args])</pre>
</func>
<func>
- <name>continue(Pid) -> ok | {error,not_interpreted}</name>
- <name>continue(X,Y,Z) -> ok | {error,not_interpreted}</name>
+ <name since="">continue(Pid) -> ok | {error,not_interpreted}</name>
+ <name since="">continue(X,Y,Z) -> ok | {error,not_interpreted}</name>
<fsummary>Resume process execution.</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index e34ffd6def..f5e8337eb1 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>dialyzer.xml</file>
</header>
- <module>dialyzer</module>
+ <module since="">dialyzer</module>
<modulesummary>Dialyzer, a DIscrepancy AnaLYZer for ERlang programs.
</modulesummary>
<description>
@@ -472,7 +472,7 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<funcs>
<func>
- <name>format_warning(Msg) -> string()</name>
+ <name since="">format_warning(Msg) -> string()</name>
<fsummary>Get the string version of a warning message.</fsummary>
<type>
<v>Msg = {Tag, Id, msg()}</v>
@@ -485,8 +485,8 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
</func>
<func>
- <name>gui() -> ok | {error, Msg}</name>
- <name>gui(OptList) -> ok | {error, Msg}</name>
+ <name since="">gui() -> ok | {error, Msg}</name>
+ <name since="">gui(OptList) -> ok | {error, Msg}</name>
<fsummary>Dialyzer GUI version.</fsummary>
<type>
<v>OptList</v>
@@ -539,7 +539,7 @@ WarnOpts :: error_handling
</func>
<func>
- <name>plt_info(string()) -> {'ok', [{atom(), any()}]} | {'error', atom()}</name>
+ <name since="">plt_info(string()) -> {'ok', [{atom(), any()}]} | {'error', atom()}</name>
<fsummary>Return information about the specified PLT.</fsummary>
<desc>
<p>Returns information about the specified PLT.</p>
@@ -547,7 +547,7 @@ WarnOpts :: error_handling
</func>
<func>
- <name>run(OptList) -> Warnings</name>
+ <name since="">run(OptList) -> Warnings</name>
<fsummary>Dialyzer command-line version.</fsummary>
<type>
<v>OptList</v>
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index dfa4c803ed..0a0194af2d 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -55,7 +55,7 @@ limitations under the License.
<!-- ===================================================================== -->
<!-- ===================================================================== -->
-<module>diameter</module>
+<module since="OTP R14B03">diameter</module>
<modulesummary>Main API of the diameter application.</modulesummary>
<description>
@@ -1574,7 +1574,7 @@ identifies the configuration.</p>
<!-- ===================================================================== -->
<func>
-<name>add_transport(SvcName, {connect|listen, [Opt]})
+<name since="OTP R14B03">add_transport(SvcName, {connect|listen, [Opt]})
-> {ok, Ref} | {error, Reason}</name>
<fsummary>Add transport capability to a service.</fsummary>
<type>
@@ -1624,7 +1624,7 @@ its transports.</p>
<!-- ===================================================================== -->
<func>
-<name>call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name>
+<name since="OTP R14B03">call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name>
<fsummary>Send a Diameter request message.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -1730,7 +1730,7 @@ transport connection.</p>
<!-- ===================================================================== -->
<func>
-<name>origin_state_id() -> &dict_Unsigned32;</name>
+<name since="OTP R14B03">origin_state_id() -> &dict_Unsigned32;</name>
<fsummary>Returns a reasonable Origin-State-Id.</fsummary>
<desc>
<p>
@@ -1748,7 +1748,7 @@ at the time the diameter application was started.</p>
<!-- ===================================================================== -->
<func>
-<name>remove_transport(SvcName, Pred) -> ok | {error, Reason}</name>
+<name since="OTP R14B03">remove_transport(SvcName, Pred) -> ok | {error, Reason}</name>
<fsummary>Remove previously added transports.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -1795,7 +1795,7 @@ configured on the transport.</p>
<!-- ===================================================================== -->
<func>
-<name>service_info(SvcName, Info) -> term()</name>
+<name since="OTP R14B03">service_info(SvcName, Info) -> term()</name>
<fsummary>Return information about a started service.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2114,7 +2114,7 @@ For example:</p>
<!-- ===================================================================== -->
<func>
-<name>services() -> [SvcName]</name>
+<name since="OTP R14B03">services() -> [SvcName]</name>
<fsummary>Return the list of started services.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2129,7 +2129,7 @@ Return the list of started services.</p>
<!-- ===================================================================== -->
<func>
-<name>session_id(Ident) -> &dict_OctetString;</name>
+<name since="OTP R14B03">session_id(Ident) -> &dict_OctetString;</name>
<fsummary>Return a value for a Session-Id AVP.</fsummary>
<type>
<v>Ident = &dict_DiameterIdentity;</v>
@@ -2148,7 +2148,7 @@ the message containing the returned value will be sent.</p>
<!-- ===================================================================== -->
<func>
-<name>start() -> ok | {error, Reason}</name>
+<name since="OTP R14B03">start() -> ok | {error, Reason}</name>
<fsummary>Start the diameter application.</fsummary>
<desc>
<p>
@@ -2164,7 +2164,7 @@ file, not by calling <c>start/0</c> explicitly.</p>
<!-- ===================================================================== -->
<func>
-<name>start_service(SvcName, Options) -> ok | {error, Reason}</name>
+<name since="OTP R14B03">start_service(SvcName, Options) -> ok | {error, Reason}</name>
<fsummary>Start a Diameter service.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2194,7 +2194,7 @@ necessarily the case.</p>
<!-- ===================================================================== -->
<func>
-<name>stop() -> ok | {error, Reason}</name>
+<name since="OTP R14B03">stop() -> ok | {error, Reason}</name>
<fsummary>Stop the diameter application.</fsummary>
<desc>
<p>
@@ -2208,7 +2208,7 @@ Stop the diameter application.</p>
<!-- ===================================================================== -->
<func>
-<name>stop_service(SvcName) -> ok | {error, Reason}</name>
+<name since="OTP R14B03">stop_service(SvcName) -> ok | {error, Reason}</name>
<fsummary>Stop a Diameter service.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2236,7 +2236,7 @@ be called to remove transport configuration.</p>
<!-- ===================================================================== -->
<func>
-<name>subscribe(SvcName) -> true</name>
+<name since="OTP R14B03">subscribe(SvcName) -> true</name>
<fsummary>Subscribe to event messages.</fsummary>
<type>
<v>SvcName = &service_name;</v>
@@ -2258,7 +2258,7 @@ reception of all transport-related events.</p>
<!-- ===================================================================== -->
<func>
-<name>unsubscribe(SvcName) -> true</name>
+<name since="OTP R14B03">unsubscribe(SvcName) -> true</name>
<fsummary>Unsubscribe to event messages.</fsummary>
<type>
<v>SvcName = &service_name;</v>
diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml
index aa334beb21..82e3d449ef 100644
--- a/lib/diameter/doc/src/diameter_app.xml
+++ b/lib/diameter/doc/src/diameter_app.xml
@@ -44,7 +44,7 @@ limitations under the License.
</header>
-<module>diameter_app</module>
+<module since="OTP R14B03">diameter_app</module>
<modulesummary>
Callback module of a Diameter application.</modulesummary>
@@ -180,7 +180,7 @@ process.</p>
<funcs>
<func>
-<name>Mod:peer_up(SvcName, Peer, State) -> NewState</name>
+<name since="OTP R14B03">Mod:peer_up(SvcName, Peer, State) -> NewState</name>
<fsummary>Invoked when a transport connection has been established</fsummary>
<type>
<v>SvcName = &mod_service_name;</v>
@@ -215,7 +215,7 @@ handled independently of &peer_up; and &peer_down;.</p>
</func>
<func>
-<name>Mod:peer_down(SvcName, Peer, State) -> NewState</name>
+<name since="OTP R14B03">Mod:peer_down(SvcName, Peer, State) -> NewState</name>
<fsummary>Invoked when a transport connection has been lost.</fsummary>
<type>
<v>SvcName = &mod_service_name;</v>
@@ -234,7 +234,7 @@ candidate in &pick_peer; callbacks.</p>
</func>
<func>
-<name>Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State)
+<name since="OTP R14B03">Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State)
-> Selection | false</name>
<fsummary>Select a target peer for an outgoing request.</fsummary>
<type>
@@ -311,7 +311,7 @@ or &peer_down; callback.</p>
</func>
<func>
-<name>Mod:prepare_request(Packet, SvcName, Peer) -> Action</name>
+<name since="OTP R14B03">Mod:prepare_request(Packet, SvcName, Peer) -> Action</name>
<fsummary>Return a request for encoding and transport.</fsummary>
<type>
<v>Packet = &packet;</v>
@@ -363,7 +363,7 @@ discarded}</c>.</p>
</func>
<func>
-<name>Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name>
+<name since="OTP R14B03">Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name>
<fsummary>Return a request for encoding and retransmission.</fsummary>
<type>
<v>Packet = &packet;</v>
@@ -393,7 +393,7 @@ discarded}</c>.</p>
</func>
<func>
-<name>Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name>
+<name since="OTP R14B03">Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name>
<fsummary>Receive an answer message from a peer.</fsummary>
<type>
<v>Packet = &packet;</v>
@@ -437,7 +437,7 @@ The &mod_application_opt;
</func>
<func>
-<name>Mod:handle_error(Reason, Request, SvcName, Peer) -> Result</name>
+<name since="OTP R14B03">Mod:handle_error(Reason, Request, SvcName, Peer) -> Result</name>
<fsummary>Return an error from a outgoing request.</fsummary>
<type>
<v>Reason = timeout | failover | term()</v>
@@ -465,7 +465,7 @@ not selected.</p>
</func>
<func>
-<name>Mod:handle_request(Packet, SvcName, Peer) -> Action</name>
+<name since="OTP R14B03">Mod:handle_request(Packet, SvcName, Peer) -> Action</name>
<fsummary>Receive an incoming request.</fsummary>
<type>
<v>Packet = &packet;</v>
diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml
index 0a34dd7ec7..0384ad2913 100644
--- a/lib/diameter/doc/src/diameter_codec.xml
+++ b/lib/diameter/doc/src/diameter_codec.xml
@@ -46,7 +46,7 @@ limitations under the License.
<file>diameter_codec.xml</file>
</header>
-<module>diameter_codec</module>
+<module since="OTP R15B03">diameter_codec</module>
<modulesummary>Decode and encode of Diameter messages.</modulesummary>
<description>
@@ -346,7 +346,7 @@ question, as documented in &man_transport;.</p>
<funcs>
<func>
-<name>decode(Mod, Bin) -> Pkt</name>
+<name since="OTP R15B03">decode(Mod, Bin) -> Pkt</name>
<fsummary>Decode a Diameter message.</fsummary>
<type>
<v>Mod = &dictionary;</v>
@@ -362,7 +362,7 @@ Decode a Diameter message.</p>
</func>
<func>
-<name>encode(Mod, Msg) -> Pkt</name>
+<name since="OTP R15B03">encode(Mod, Msg) -> Pkt</name>
<fsummary>Encode a Diameter message.</fsummary>
<type>
<v>Mod = &dictionary;</v>
diff --git a/lib/diameter/doc/src/diameter_make.xml b/lib/diameter/doc/src/diameter_make.xml
index 112355816f..57e83bbca1 100644
--- a/lib/diameter/doc/src/diameter_make.xml
+++ b/lib/diameter/doc/src/diameter_make.xml
@@ -45,7 +45,7 @@ limitations under the License.
<file>diameter_make.xml</file>
</header>
-<module>diameter_make</module>
+<module since="OTP R14B03">diameter_make</module>
<modulesummary>Diameter dictionary compilation.</modulesummary>
<description>
@@ -67,7 +67,7 @@ interface.</p>
<funcs>
<func>
-<name>codec(File :: iolist() | binary(), [Opt]) -> ok
+<name since="OTP R15B">codec(File :: iolist() | binary(), [Opt]) -> ok
| {ok, [Out]}
| {error, Reason}</name>
<fsummary>Compile a dictionary file into Erlang source.</fsummary>
@@ -186,7 +186,7 @@ A returned error reason can be converted into a readable string using
<!-- ===================================================================== -->
<func>
-<name>format(Parsed) -> iolist()</name>
+<name since="OTP R16B03">format(Parsed) -> iolist()</name>
<fsummary>Format a parsed dictionary.</fsummary>
<desc>
<p>
@@ -198,7 +198,7 @@ dictionary format.</p>
<!-- ===================================================================== -->
<func>
-<name>flatten(Parsed) -> term()</name>
+<name since="OTP R16B03">flatten(Parsed) -> term()</name>
<fsummary>Flatten a parsed dictionary.</fsummary>
<desc>
@@ -214,7 +214,7 @@ The return value is also a parsed dictionary.</p>
<!-- ===================================================================== -->
<func>
-<name>format_error(Reason) -> string()</name>
+<name since="OTP 17.0">format_error(Reason) -> string()</name>
<fsummary>Turn an error reason into a readable string.</fsummary>
<desc>
diff --git a/lib/diameter/doc/src/diameter_sctp.xml b/lib/diameter/doc/src/diameter_sctp.xml
index 62e958870e..2cf924ee6b 100644
--- a/lib/diameter/doc/src/diameter_sctp.xml
+++ b/lib/diameter/doc/src/diameter_sctp.xml
@@ -47,7 +47,7 @@ limitations under the License.
<file>diameter_sctp.xml</file>
</header>
-<module>diameter_sctp</module>
+<module since="OTP R14B03">diameter_sctp</module>
<modulesummary>Diameter transport over SCTP.</modulesummary>
<description>
@@ -67,7 +67,7 @@ and implements the behaviour documented in
<funcs>
<func>
-<name>start({Type, Ref}, Svc, [Opt])
+<name since="OTP R14B03">start({Type, Ref}, Svc, [Opt])
-> {ok, Pid, [LAddr]} | {error, Reason}</name>
<fsummary>Start a transport process.</fsummary>
<type>
diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml
index 9f84eeb9fd..fe0cb2d067 100644
--- a/lib/diameter/doc/src/diameter_tcp.xml
+++ b/lib/diameter/doc/src/diameter_tcp.xml
@@ -57,7 +57,7 @@ limitations under the License.
<file>diameter_tcp.xml</file>
</header>
-<module>diameter_tcp</module>
+<module since="OTP R14B03">diameter_tcp</module>
<modulesummary>Diameter transport over TCP.</modulesummary>
<description>
@@ -83,7 +83,7 @@ before configuring TLS capability on diameter transports.</p>
<funcs>
<func>
-<name>start({Type, Ref}, Svc, [Opt])
+<name since="OTP R14B03">start({Type, Ref}, Svc, [Opt])
-> {ok, Pid}
| {ok, Pid, [LAddr]}
| {error, Reason}</name>
diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml
index 294e8a8864..67fd54bc56 100644
--- a/lib/diameter/doc/src/diameter_transport.xml
+++ b/lib/diameter/doc/src/diameter_transport.xml
@@ -43,7 +43,7 @@ limitations under the License.
<file>diameter_transport.xml</file>
</header>
-<module>diameter_transport</module>
+<module since="OTP R14B03">diameter_transport</module>
<modulesummary>Diameter transport interface.</modulesummary>
<description>
@@ -94,7 +94,7 @@ and has the binary() to send in its <c>bin</c> field.</p>
<funcs>
<func>
-<name>Mod:start({Type, Ref}, Svc, Config)
+<name since="OTP R14B03">Mod:start({Type, Ref}, Svc, Config)
-> {ok, Pid}
| {ok, Pid, LAddrs}
| {error, Reason}</name>
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml
index f2c7889e58..790a2f4e26 100644
--- a/lib/eldap/doc/src/eldap.xml
+++ b/lib/eldap/doc/src/eldap.xml
@@ -28,7 +28,7 @@
<date>2000-06-20</date>
<rev>B</rev>
</header>
- <module>eldap</module>
+ <module since="OTP R15B01">eldap</module>
<modulesummary>LDAP Client</modulesummary>
<description>
<p>This module provides a client api to the Lightweight Directory Access Protocol (LDAP).
@@ -103,7 +103,7 @@
<funcs>
<func>
- <name>open([Host]) -> {ok, Handle} | {error, Reason}</name>
+ <name since="OTP R15B01">open([Host]) -> {ok, Handle} | {error, Reason}</name>
<fsummary>Open a connection to an LDAP server.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -113,7 +113,7 @@
</desc>
</func>
<func>
- <name>open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name>
+ <name since="OTP R15B01">open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name>
<fsummary>Open a connection to an LDAP server.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -129,7 +129,7 @@
</desc>
</func>
<func>
- <name>close(Handle) -> ok</name>
+ <name since="OTP R15B01">close(Handle) -> ok</name>
<fsummary>Shutdown the connection.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -140,14 +140,14 @@
</desc>
</func>
<func>
- <name>start_tls(Handle, Options) -> return_value()</name>
+ <name since="OTP R16B03">start_tls(Handle, Options) -> return_value()</name>
<fsummary>Upgrade a connection to TLS.</fsummary>
<desc>
<p>Same as start_tls(Handle, Options, infinity)</p>
</desc>
</func>
<func>
- <name>start_tls(Handle, Options, Timeout) -> return_value()</name>
+ <name since="OTP R16B03">start_tls(Handle, Options, Timeout) -> return_value()</name>
<fsummary>Upgrade a connection to TLS.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -176,7 +176,7 @@
</desc>
</func>
<func>
- <name>simple_bind(Handle, Dn, Password) -> return_value()</name>
+ <name since="OTP R15B01">simple_bind(Handle, Dn, Password) -> return_value()</name>
<fsummary>Authenticate the connection.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -188,7 +188,7 @@
</desc>
</func>
<func>
- <name>add(Handle, Dn, [Attribute]) -> return_value()</name>
+ <name since="OTP R15B01">add(Handle, Dn, [Attribute]) -> return_value()</name>
<fsummary>Add an entry.</fsummary>
<type>
<v>Handle = handle()</v>
@@ -209,7 +209,7 @@
</desc>
</func>
<func>
- <name>delete(Handle, Dn) -> return_value()</name>
+ <name since="OTP R15B01">delete(Handle, Dn) -> return_value()</name>
<fsummary>Delete an entry.</fsummary>
<type>
<v>Dn = string()</v>
@@ -223,7 +223,7 @@
</func>
<func>
- <name>mod_add(Type, [Value]) -> modify_op()</name>
+ <name since="OTP R15B01">mod_add(Type, [Value]) -> modify_op()</name>
<fsummary>Create a modification operation.</fsummary>
<type>
<v>Type = string()</v>
@@ -232,7 +232,7 @@
<desc> <p> Create an add modification operation.</p> </desc>
</func>
<func>
- <name>mod_delete(Type, [Value]) -> modify_op()</name>
+ <name since="OTP R15B01">mod_delete(Type, [Value]) -> modify_op()</name>
<fsummary>Create a modification operation.</fsummary>
<type>
<v>Type = string()</v>
@@ -241,7 +241,7 @@
<desc> <p> Create a delete modification operation.</p> </desc>
</func>
<func>
- <name>mod_replace(Type, [Value]) -> modify_op()</name>
+ <name since="OTP R15B01">mod_replace(Type, [Value]) -> modify_op()</name>
<fsummary>Create a modification operation.</fsummary>
<type>
<v>Type = string()</v>
@@ -251,7 +251,7 @@
</func>
<func>
- <name>modify(Handle, Dn, [ModifyOp]) -> return_value()</name>
+ <name since="OTP R15B01">modify(Handle, Dn, [ModifyOp]) -> return_value()</name>
<fsummary>Modify an entry.</fsummary>
<type>
<v>Dn = string()</v>
@@ -267,7 +267,7 @@
</desc>
</func>
<func>
- <name>modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name>
+ <name since="OTP 18.0">modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name>
<fsummary>Modify the password of a user.</fsummary>
<type>
<v>Dn = string()</v>
@@ -278,7 +278,7 @@
</desc>
</func>
<func>
- <name>modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name>
+ <name since="OTP 18.0">modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name>
<fsummary>Modify the password of a user.</fsummary>
<type>
<v>Dn = string()</v>
@@ -307,7 +307,7 @@
</desc>
</func>
<func>
- <name>modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name>
+ <name since="OTP R15B01">modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name>
<fsummary>Modify the DN of an entry.</fsummary>
<type>
<v>Dn = string()</v>
@@ -327,7 +327,7 @@
</desc>
</func>
<func>
- <name>search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name>
+ <name since="OTP R15B01">search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name>
<fsummary>Search the Directory</fsummary>
<type>
<v>SearchOptions = #eldap_search{} | [SearchOption]</v>
@@ -354,44 +354,44 @@
</func>
<func>
- <name>baseObject() -> scope()</name>
+ <name since="OTP R15B01">baseObject() -> scope()</name>
<fsummary>Create search scope.</fsummary>
<desc> <p> Search baseobject only.</p> </desc>
</func>
<func>
- <name>singleLevel() -> scope()</name>
+ <name since="OTP R15B01">singleLevel() -> scope()</name>
<fsummary>Create search scope.</fsummary>
<desc> <p> Search the specified level only, i.e. do not recurse.</p> </desc>
</func>
<func>
- <name>wholeSubtree() -> scope()</name>
+ <name since="OTP R15B01">wholeSubtree() -> scope()</name>
<fsummary>Create search scope.</fsummary>
<desc> <p> Search the entire subtree.</p> </desc>
</func>
<func>
- <name>neverDerefAliases() -> dereference()</name>
+ <name since="OTP R15B01">neverDerefAliases() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Never derefrence aliases, treat aliases as entries.</p> </desc>
</func>
<func>
- <name>derefAlways() -> dereference()</name>
+ <name since="OTP R15B01">derefAlways() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Always derefrence aliases.</p> </desc>
</func>
<func>
- <name>derefInSearching() -> dereference()</name>
+ <name since="OTP R15B01">derefInSearching() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Derefrence aliases only when searching.</p> </desc>
</func>
<func>
- <name>derefFindingBaseObj() -> dereference()</name>
+ <name since="OTP R15B01">derefFindingBaseObj() -> dereference()</name>
<fsummary>Create search option.</fsummary>
<desc> <p>Derefrence aliases only in finding the base.</p> </desc>
</func>
<func>
- <name>present(Type) -> filter()</name>
+ <name since="OTP R15B01">present(Type) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -399,7 +399,7 @@
<desc> <p>Create a filter which filters on attribute type presence.</p> </desc>
</func>
<func>
- <name>substrings(Type, [SubString]) -> filter()</name>
+ <name since="OTP R15B01">substrings(Type, [SubString]) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -409,7 +409,7 @@
<desc> <p>Create a filter which filters on substrings.</p> </desc>
</func>
<func>
- <name>equalityMatch(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">equalityMatch(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -418,7 +418,7 @@
<desc> <p>Create a equality filter.</p> </desc>
</func>
<func>
- <name>greaterOrEqual(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">greaterOrEqual(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -427,7 +427,7 @@
<desc> <p>Create a greater or equal filter.</p> </desc>
</func>
<func>
- <name>lessOrEqual(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">lessOrEqual(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -436,7 +436,7 @@
<desc> <p>Create a less or equal filter.</p> </desc>
</func>
<func>
- <name>approxMatch(Type, Value) -> filter()</name>
+ <name since="OTP R15B01">approxMatch(Type, Value) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Type = string()</v>
@@ -445,7 +445,7 @@
<desc> <p>Create a approximation match filter.</p> </desc>
</func>
<func>
- <name>extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name>
+ <name since="OTP 17.4">extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>MatchValue = string()</v>
@@ -459,7 +459,7 @@
<p>creates a filter which performs a <c>caseExactMatch</c> on the attribute <c>sn</c> and matches with the value <c>"Bar"</c>. The default value of <c>dnAttributes</c> is <c>false</c>.</p> </desc>
</func>
<func>
- <name>'and'([Filter]) -> filter()</name>
+ <name since="OTP R15B01">'and'([Filter]) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Filter = filter()</v>
@@ -467,7 +467,7 @@
<desc> <p>Creates a filter where all <c>Filter</c> must be true.</p> </desc>
</func>
<func>
- <name>'or'([Filter]) -> filter()</name>
+ <name since="OTP R15B01">'or'([Filter]) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Filter = filter()</v>
@@ -475,7 +475,7 @@
<desc> <p>Create a filter where at least one of the <c>Filter</c> must be true.</p> </desc>
</func>
<func>
- <name>'not'(Filter) -> filter()</name>
+ <name since="OTP R15B01">'not'(Filter) -> filter()</name>
<fsummary>Create search filter option.</fsummary>
<type>
<v>Filter = filter()</v>
diff --git a/lib/erl_docgen/doc/src/docgen_xml_check.xml b/lib/erl_docgen/doc/src/docgen_xml_check.xml
index 68253edef7..8d6dceef43 100644
--- a/lib/erl_docgen/doc/src/docgen_xml_check.xml
+++ b/lib/erl_docgen/doc/src/docgen_xml_check.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>docgen_xml_check</module>
+ <module since="OTP R15B">docgen_xml_check</module>
<modulesummary>Validate XML documentation source code</modulesummary>
<description>
<p><c>docgen_xml_check</c> contains functions for validating XML
@@ -39,7 +39,7 @@
<funcs>
<func>
- <name>validate(File) -> ok | error | {error, badfile}</name>
+ <name since="OTP R15B">validate(File) -> ok | error | {error, badfile}</name>
<fsummary>Validate XML source code.</fsummary>
<type>
<v>File = string()</v>
diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css
index 34c6befb0e..89b278215c 100644
--- a/lib/erl_docgen/priv/css/otp_doc.css
+++ b/lib/erl_docgen/priv/css/otp_doc.css
@@ -71,6 +71,33 @@ a:visited { color: #1b6ec2; text-decoration: none }
}
.bold_code { font-family: mono, Courier, monospace; font-weight: bold }
+
+/* Invisible table for function specs,
+ * just to get since-version out in right margin */
+.func-table, .func-tr, .func-td, .func-since-td {
+ width: 200%;
+ border: 0;
+ padding: 0;
+ margin: 0;
+}
+
+.func-tr:nth-child(n) {
+ background: inherit /* turn off zebra striped rows */
+}
+
+.func-td {
+ width: 50%
+}
+
+.func-since-td {
+ width: 50%;
+ padding-left: 1em
+}
+
+.func-td:hover {
+ background-color: #f5f5f5;
+}
+
.code {
font-family: mono, Courier, monospace;
font-weight: normal;
@@ -283,3 +310,9 @@ a > .code {
.func-types-title{
font-size: 1em;
}
+
+.since{
+ color: gray;
+ font-weight: normal;
+ font-size: small;
+} \ No newline at end of file
diff --git a/lib/erl_docgen/priv/dtd/cref.dtd b/lib/erl_docgen/priv/dtd/cref.dtd
index 5ccd98ed89..d392081807 100644
--- a/lib/erl_docgen/priv/dtd/cref.dtd
+++ b/lib/erl_docgen/priv/dtd/cref.dtd
@@ -30,6 +30,8 @@
<!-- `name' is used in common.refs.dtd and must therefore
be defined in each *ref. dtd -->
<!ELEMENT name (ret,nametext) >
+<!ATTLIST name since CDATA #IMPLIED>
+
<!ELEMENT ret (#PCDATA) >
<!ELEMENT nametext (#PCDATA) >
diff --git a/lib/erl_docgen/priv/dtd/erlref.dtd b/lib/erl_docgen/priv/dtd/erlref.dtd
index 78d6771f52..8202ea5a4d 100644
--- a/lib/erl_docgen/priv/dtd/erlref.dtd
+++ b/lib/erl_docgen/priv/dtd/erlref.dtd
@@ -25,6 +25,7 @@
<!ELEMENT erlref (header,module,modulesummary,description,
(section|funcs|datatypes)*,authors?) >
<!ELEMENT module (#PCDATA) >
+<!ATTLIST module since CDATA #IMPLIED>
<!ELEMENT modulesummary (#PCDATA) >
<!-- `name' is used in common.refs.dtd and must therefore
@@ -34,4 +35,5 @@
arity CDATA #IMPLIED
clause_i CDATA #IMPLIED
anchor CDATA #IMPLIED
+ since CDATA #IMPLIED
n_vars CDATA #IMPLIED>
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index a0a922216b..c5150d447c 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -191,6 +191,7 @@
<xsl:variable name="name" select="@name"/>
<xsl:variable name="arity" select="@arity"/>
<xsl:variable name="anchor" select="@anchor"/>
+ <xsl:variable name="since" select="@since"/>
<xsl:variable name="spec0">
<xsl:call-template name="find_spec"/>
</xsl:variable>
@@ -225,11 +226,12 @@
<xsl:variable name="global_types" select="ancestor::erlref/datatypes"/>
<xsl:variable name="local_types"
select="../type[string-length(@name) > 0]"/>
- <xsl:apply-templates select="$spec/contract/clause/head">
+ <xsl:apply-templates select="$spec/contract/clause/head">
<xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/>
<xsl:with-param name="local_types" select="$local_types"/>
<xsl:with-param name="global_types" select="$global_types"/>
- </xsl:apply-templates>
+ <xsl:with-param name="since" select="$since"/>
+ </xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
@@ -238,19 +240,32 @@
<xsl:param name="ghlink"/>
<xsl:param name="local_types"/>
<xsl:param name="global_types"/>
+ <xsl:param name="since"/>
<xsl:variable name="id" select="concat(concat(concat(concat(../../../name,'-'),../../../arity),'-'),generate-id(.))"/>
- <div class="bold_code func-head"
- onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
- onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
- <xsl:call-template name="ghlink">
- <xsl:with-param name="ghlink" select="$ghlink"/>
- <xsl:with-param name="id" select="$id"/>
- </xsl:call-template>
- <xsl:apply-templates mode="local_type">
- <xsl:with-param name="local_types" select="$local_types"/>
- <xsl:with-param name="global_types" select="$global_types"/>
- </xsl:apply-templates>
- </div>
+ <table class="func-table">
+ <tr class="func-tr">
+ <td class="func-td">
+ <div class="bold_code func-head"
+ onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';"
+ onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';">
+ <xsl:call-template name="ghlink">
+ <xsl:with-param name="ghlink" select="$ghlink"/>
+ <xsl:with-param name="id" select="$id"/>
+ </xsl:call-template>
+ <xsl:apply-templates mode="local_type">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
+ </div>
+ </td>
+ <td class="func-since-td">
+ <xsl:if test="string-length($since) > 0">
+ <span class="since"><xsl:value-of select="$since"/>
+ </span>
+ </xsl:if>
+ </td>
+ </tr>
+ </table>
</xsl:template>
<!-- The *last* <name name="..." arity=".."/> -->
@@ -1884,6 +1899,16 @@
<xsl:with-param name="partnum" select="$partnum"/>
</xsl:apply-templates>
</div>
+ <!-- Since -->
+ <xsl:if test="string-length(../module/@since) > 0">
+ <xsl:call-template name="h3_title_link">
+ <xsl:with-param name="title">Since</xsl:with-param>
+ </xsl:call-template>
+ <div class="REFBODY module-since">
+ Module <xsl:value-of select="../module"/> was introduced in
+ <xsl:value-of select="../module/@since"/>.
+ </div>
+ </xsl:if>
</xsl:template>
<!-- Lib -->
@@ -2030,11 +2055,10 @@
<xsl:template match="func">
<xsl:param name="partnum"/>
- <p><xsl:apply-templates select="name"/>
- <xsl:apply-templates
- select="name[string-length(@arity) > 0 and position()=last()]"
- mode="types"/>
- </p>
+ <xsl:apply-templates select="name"/>
+ <xsl:apply-templates
+ select="name[string-length(@arity) > 0 and position()=last()]"
+ mode="types"/>
<xsl:apply-templates select="fsummary|type|desc">
<xsl:with-param name="partnum" select="$partnum"/>
@@ -2093,19 +2117,29 @@
<xsl:choose>
<xsl:when test="ancestor::cref">
- <span class="bold_code bc-7">
- <xsl:call-template name="title_link">
- <xsl:with-param name="link" select="substring-before(nametext, '(')"/>
- <xsl:with-param name="title">
- <xsl:value-of select="ret"/>
- <xsl:call-template name="maybe-space-after-ret">
- <xsl:with-param name="s" select="ret"/>
- </xsl:call-template>
- <xsl:value-of select="nametext"/>
- </xsl:with-param>
- </xsl:call-template>
- </span>
- <br/>
+ <table class="func-table">
+ <tr class="func-tr">
+ <td class="func-td">
+ <span class="bold_code bc-7">
+ <xsl:call-template name="title_link">
+ <xsl:with-param name="link" select="substring-before(nametext, '(')"/>
+ <xsl:with-param name="title">
+ <xsl:value-of select="ret"/>
+ <xsl:call-template name="maybe-space-after-ret">
+ <xsl:with-param name="s" select="ret"/>
+ </xsl:call-template>
+ <xsl:value-of select="nametext"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </span>
+ </td>
+ <td class="func-since-td">
+ <xsl:if test="string-length(@since) > 0">
+ <span class="since"><xsl:value-of select="@since"/></span>
+ </xsl:if>
+ </td>
+ </tr>
+ </table>
</xsl:when>
<xsl:when test="ancestor::erlref">
<xsl:variable name="fname">
@@ -2136,14 +2170,25 @@
</div>
</xsl:when>
<xsl:otherwise>
- <div class="bold_code fun-type">
- <xsl:call-template name="title_link">
- <xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/>
- <xsl:with-param name="title">
- <xsl:apply-templates/>
- </xsl:with-param>
- </xsl:call-template>
- </div>
+ <table class="func-table">
+ <tr class="func-tr">
+ <td class="func-td">
+ <div class="bold_code fun-type">
+ <xsl:call-template name="title_link">
+ <xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/>
+ <xsl:with-param name="title">
+ <xsl:apply-templates/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </div>
+ </td>
+ <td class="func-since-td">
+ <xsl:if test="string-length(@since) > 0">
+ <span class="since"><xsl:value-of select="@since"/></span>
+ </xsl:if>
+ </td>
+ </tr>
+ </table>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
diff --git a/lib/erl_interface/configure.in b/lib/erl_interface/configure.in
index 46dd995289..14f06f946f 100644
--- a/lib/erl_interface/configure.in
+++ b/lib/erl_interface/configure.in
@@ -77,6 +77,15 @@ AC_ARG_ENABLE(threads,
esac ],
[ threads_disabled=maybe ])
+AC_ARG_ENABLE(mask-real-errno,
+[ --disable-mask-real-errno do not mask real 'errno'],
+[ case "$enableval" in
+ no) mask_real_errno=no ;;
+ *) mask_real_errno=yes ;;
+ esac ],
+[ mask_real_errno=yes ])
+
+
dnl ----------------------------------------------------------------------
dnl Checks for programs
dnl ----------------------------------------------------------------------
@@ -95,6 +104,10 @@ AC_CHECK_SIZEOF(long)
AC_CHECK_SIZEOF(void *)
AC_CHECK_SIZEOF(long long)
+if test $mask_real_errno = yes; then
+ AC_DEFINE(EI_HIDE_REAL_ERRNO, 1, [Define if 'errno' should not be exposed as is in 'erl_errno'])
+fi
+
dnl We set EI_64BIT mode when long is 8 bytes, this makes things
dnl work on windows and unix correctly
if test $ac_cv_sizeof_long = 8; then
@@ -153,7 +166,7 @@ AC_CHECK_LIB([socket], [getpeername])
# Checks for header files.
AC_HEADER_STDC
AC_HEADER_SYS_WAIT
-AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/param.h sys/socket.h sys/select.h sys/time.h unistd.h sys/types.h])
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/param.h sys/socket.h sys/select.h sys/time.h unistd.h sys/types.h sys/uio.h])
# Checks for typedefs, structures, and compiler characteristics.
# fixme AC_C_CONST & AC_C_VOLATILE needed for Windows?
@@ -188,7 +201,7 @@ AC_CHECK_FUNCS([dup2 gethostbyaddr gethostbyname \
gethostbyaddr_r \
gethostbyname_r gethostname writev \
gethrtime gettimeofday inet_ntoa memchr memmove memset select \
- socket strchr strerror strrchr strstr uname])
+ socket strchr strerror strrchr strstr uname sysconf])
AC_CHECK_FUNC(res_gethostbyname, [],
AC_CHECK_LIB(resolv, res_gethostbyname)
)
@@ -250,6 +263,7 @@ AC_SUBST(EI_THREADS)
case "$threads_disabled" in
no|maybe)
LM_CHECK_THR_LIB
+ ETHR_CHK_GCC_ATOMIC_OPS([])
case "$THR_LIB_NAME" in
"")
@@ -263,7 +277,7 @@ case "$threads_disabled" in
EI_THREADS="true"
THR_DEFS="$THR_DEFS -D_WIN32_WINNT=0x0600 -DWINVER=0x0600"
;;
- pthread)
+ pthread)
EI_THREADS="true"
;;
*)
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 9502fb1ee7..16f4e18637 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -124,7 +124,7 @@ typedef enum {
<funcs>
<func>
- <name><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode an atom.</fsummary>
<desc>
<p>Decodes an atom from the binary format. The <c>NULL</c>-terminated
@@ -134,7 +134,7 @@ typedef enum {
</func>
<func>
- <name><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>
+ <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>
<desc>
<p>Decodes an atom from the binary format. The <c>NULL</c>-terminated
@@ -158,7 +158,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_bignum(const char *buf, int *index, mpz_t obj)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_bignum(const char *buf, int *index, mpz_t obj)</nametext></name>
<fsummary>Decode a GMP arbitrary precision integer.</fsummary>
<desc>
<p>Decodes an integer in the binary format to a GMP
@@ -168,7 +168,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_binary(const char *buf, int *index, void *p, long *len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_binary(const char *buf, int *index, void *p, long *len)</nametext></name>
<fsummary>Decode a binary.</fsummary>
<desc>
<p>Decodes a binary from the binary format. Parameter
@@ -180,7 +180,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_boolean(const char *buf, int *index, int *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_boolean(const char *buf, int *index, int *p)</nametext></name>
<fsummary>Decode a boolean.</fsummary>
<desc>
<p>Decodes a boolean value from the binary format.
@@ -190,7 +190,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_char(const char *buf, int *index, char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_char(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode an 8-bit integer between 0-255.</fsummary>
<desc>
<p>Decodes a char (8-bit) integer between 0-255 from the binary format.
@@ -203,7 +203,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_double(const char *buf, int *index, double *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_double(const char *buf, int *index, double *p)</nametext></name>
<fsummary>Decode a double.</fsummary>
<desc>
<p>Decodes a double-precision (64-bit) floating
@@ -212,7 +212,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name>
+ <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>
<desc>
<p>Decodes any term, or at least tries to. If the term
@@ -233,8 +233,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name>
- <name><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name>
+ <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>
<desc>
<p>Decodes a fun from the binary format. Parameter
@@ -248,7 +248,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name>
+ <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>
<p>Decodes a list header from the binary
@@ -265,7 +265,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_long(const char *buf, int *index, long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_long(const char *buf, int *index, long *p)</nametext></name>
<fsummary>Decode integer.</fsummary>
<desc>
<p>Decodes a long integer from the binary format.
@@ -275,7 +275,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_longlong(const char *buf, int *index, long long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_longlong(const char *buf, int *index, long long *p)</nametext></name>
<fsummary>Decode integer.</fsummary>
<desc>
<p>Decodes a GCC <c>long long</c> or Visual C++
@@ -286,7 +286,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_map_header(const char *buf, int *index, int *arity)</nametext></name>
+ <name since="OTP 17.0"><ret>int</ret><nametext>ei_decode_map_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a map.</fsummary>
<desc>
<p>Decodes a map header from the binary
@@ -299,7 +299,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name>
+ <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>
<desc>
<p>Decodes a process identifier (pid) from the binary format.</p>
@@ -307,7 +307,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name>
<fsummary>Decode a port.</fsummary>
<desc>
<p>Decodes a port identifier from the binary format.</p>
@@ -315,7 +315,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name>
<fsummary>Decode a reference.</fsummary>
<desc>
<p>Decodes a reference from the binary format.</p>
@@ -323,7 +323,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_string(const char *buf, int *index, char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_string(const char *buf, int *index, char *p)</nametext></name>
<fsummary>Decode a string.</fsummary>
<desc>
<p>Decodes a string from the binary format. A
@@ -338,7 +338,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name>
<fsummary>Decode a <c>ETERM</c>.</fsummary>
<desc>
<p>Decodes a term from the binary format. The term
@@ -352,7 +352,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name>
+ <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>
<desc>
<p>Decodes an Erlang trace token from the binary format.</p>
@@ -360,7 +360,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_tuple_header(const char *buf, int *index, int *arity)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_tuple_header(const char *buf, int *index, int *arity)</nametext></name>
<fsummary>Decode a tuple.</fsummary>
<desc>
<p>Decodes a tuple header, the number of elements
@@ -370,7 +370,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ulong(const char *buf, int *index, unsigned long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ulong(const char *buf, int *index, unsigned long *p)</nametext></name>
<fsummary>Decode unsigned integer.</fsummary>
<desc>
<p>Decodes an unsigned long integer from the binary format.
@@ -380,7 +380,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_ulonglong(const char *buf, int *index, unsigned long long *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_ulonglong(const char *buf, int *index, unsigned long long *p)</nametext></name>
<fsummary>Decode unsigned integer.</fsummary>
<desc>
<p>Decodes a GCC <c>unsigned long long</c> or Visual C++
@@ -390,7 +390,7 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_decode_version(const char *buf, int *index, int *version)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_decode_version(const char *buf, int *index, int *version)</nametext></name>
<fsummary>Decode an empty list (<c>nil</c>).</fsummary>
<desc>
<p>Decodes the version magic number for the
@@ -400,10 +400,10 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_atom(char *buf, int *index, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_encode_atom_len(char *buf, int *index, const char *p, int len)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_atom(char *buf, int *index, const char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_atom_len(char *buf, int *index, const char *p, int len)</nametext></name>
+ <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>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c>
@@ -415,10 +415,10 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_atom_as(char *buf, int *index, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
- <name><ret>int</ret><nametext>ei_encode_atom_len_as(char *buf, int *index, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
- <name><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><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>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_encode_atom_as(char *buf, int *index, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
+ <name since="OTP R16B"><ret>int</ret><nametext>ei_encode_atom_len_as(char *buf, int *index, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name>
+ <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>
<desc>
<p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom with
@@ -435,8 +435,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name>
+ <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>
<desc>
<p>Encodes a GMP <c>mpz_t</c> integer to binary format.
@@ -446,8 +446,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name>
+ <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>
<desc>
<p>Encodes a binary in the binary format. The data is at
@@ -456,8 +456,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name>
+ <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>
<desc>
<p>Encodes a boolean value as the atom <c>true</c> if
@@ -467,8 +467,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name>
+ <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>
<desc>
<p>Encodes a char (8-bit) as an integer between 0-255 in the binary
@@ -481,8 +481,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name>
+ <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>
<desc>
<p>Encodes a double-precision (64-bit) floating point number in
@@ -493,8 +493,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name>
+ <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>
<desc>
<p>Encodes an empty list. It is often used at the tail of a list.</p>
@@ -502,8 +502,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name>
+ <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>
<desc>
<p>Encodes a fun in the binary format. Parameter <c>p</c>
@@ -515,8 +515,8 @@ typedef enum {
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name>
+ <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>
<desc>
<p>Encodes a list header, with a specified
@@ -552,8 +552,8 @@ ei_x_encode_empty_list(&amp;x);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name>
+ <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>
<desc>
<p>Encodes a long integer in the binary format.
@@ -563,8 +563,8 @@ ei_x_encode_empty_list(&amp;x);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name>
+ <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>
<desc>
<p>Encodes a GCC <c>long long</c> or Visual C++
@@ -574,8 +574,8 @@ ei_x_encode_empty_list(&amp;x);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name>
+ <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>
<desc>
<p>Encodes a map header, with a specified arity. The next
@@ -595,8 +595,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name>
+ <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>
<desc>
<p>Encodes an Erlang process identifier (pid) in the binary
@@ -607,8 +607,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name>
+ <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>
<desc>
<p>Encodes an Erlang port in the binary format. Parameter
@@ -619,8 +619,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name>
+ <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>
<desc>
<p>Encodes an Erlang reference in the binary format. Parameter
@@ -631,10 +631,10 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_string(char *buf, int *index, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_encode_string_len(char *buf, int *index, const char *p, int len)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_string(char *buf, int *index, const char *p)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_string_len(char *buf, int *index, const char *p, int len)</nametext></name>
+ <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>
<desc>
<p>Encodes a string in the binary format. (A string in Erlang
@@ -645,8 +645,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name>
<fsummary>Encode an <c>erl_interface</c> term.</fsummary>
<desc>
<p>Encodes an <c>ETERM</c>, as obtained from
@@ -656,8 +656,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</desc>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name>
+ <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>
<desc>
<p>Encodes an Erlang trace token in the binary format.
@@ -668,8 +668,8 @@ ei_x_encode_string(&amp;x, "Banana");</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name>
+ <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>
<desc>
<p>Encodes a tuple header, with a specified
@@ -687,8 +687,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name>
+ <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>
<desc>
<p>Encodes an unsigned long integer in the binary format.
@@ -698,8 +698,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name>
+ <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>
<desc>
<p>Encodes a GCC <c>unsigned long long</c> or Visual C++
@@ -709,8 +709,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name>
+ <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>
<desc>
<p>Encodes a version magic number for the binary format. Must
@@ -719,7 +719,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_get_type(const char *buf, const int *index, int *type, int *size)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_get_type(const char *buf, const int *index, int *type, int *size)</nametext></name>
<fsummary>Fetch the type and size of an encoded term.</fsummary>
<desc>
<p>Returns the type in <c>type</c> and size in
@@ -733,8 +733,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_print_term(FILE* fp, const char* buf, int* index)</nametext></name>
- <name><ret>int</ret><nametext>ei_s_print_term(char** s, const char* buf, int* index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_print_term(FILE* fp, const char* buf, int* index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_s_print_term(char** s, const char* buf, int* index)</nametext></name>
<fsummary>Print a term in clear text.</fsummary>
<desc>
<p>Prints a term, in clear text, to the file
@@ -759,7 +759,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name>
+ <name since=""><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name>
<fsummary>Set the ei library in compatibility mode.</fsummary>
<type>
<v>unsigned release_number;</v>
@@ -794,7 +794,7 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_skip_term(const char* buf, int* index)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_skip_term(const char* buf, int* index)</nametext></name>
<fsummary>Skip a term.</fsummary>
<desc>
<p>Skips a term in the specified buffer;
@@ -815,8 +815,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name>
+ <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>
<desc>
<p>Appends data at the end of buffer <c>x</c>.</p>
@@ -824,8 +824,8 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name>
+ <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>
<desc>
<p>Formats a term, given as a string, to a buffer.
@@ -853,7 +853,7 @@ encodes the tuple {numbers,12,3.14159}</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name>
<fsummary>Free a buffer.</fsummary>
<desc>
<p>Frees an <c>ei_x_buff</c> buffer.
@@ -862,8 +862,8 @@ encodes the tuple {numbers,12,3.14159}</pre>
</func>
<func>
- <name><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name>
- <name><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name>
+ <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>
<desc>
<p>Allocates a new <c>ei_x_buff</c> buffer. The
diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml
index 607a7cbff4..e318dd6664 100644
--- a/lib/erl_interface/doc/src/ei_connect.xml
+++ b/lib/erl_interface/doc/src/ei_connect.xml
@@ -85,20 +85,288 @@
the <c>_tmo</c> suffix.</p>
</section>
+ <section>
+ <marker id="ussi"/>
+ <title>User Supplied Socket Implementation</title>
+ <p>By default <c>ei</c> supplies a TCP/IPv4 socket interface
+ that is used when communicating. The user can however plug in
+ his/her own IPv4 socket implementation. This, for example, in order
+ to communicate over TLS. A user supplied socket implementation
+ is plugged in by passing a
+ <seealso marker="#ei_socket_callbacks">callback structure</seealso>
+ to either
+ <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso>
+ or
+ <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>.</p>
+
+ <p>All callbacks in the <c>ei_socket_callbacks</c> structure
+ <em>should</em> return zero on success; and a posix error
+ code on failure.</p>
+
+ <p>The <c>addr</c> argument of the <c>listen</c>, <c>accept</c>,
+ and <c>connect</c> callbacks refer to appropriate address
+ structure for currently used protocol. Currently <c>ei</c>
+ 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>
+
+ <taglist>
+
+ <tag><c>flags</c></tag>
+ <item>
+ <p>Flags informing <c>ei</c> about the behaviour of the
+ callbacks. Flags should be bitwise or:ed together. If no flag,
+ is set, the <c>flags</c> field should contain <c>0</c>. Currently,
+ supported flags:</p>
+ <taglist>
+ <tag><c>EI_SCLBK_FLG_FULL_IMPL</c></tag>
+ <item>
+ <p>
+ If set, the <c>accept()</c>, <c>connect()</c>,
+ <c>writev()</c>, <c>write()</c>, and <c>read()</c> callbacks
+ implements timeouts. The timeout is passed in the <c>tmo</c>
+ argument and is given in milli seconds. Note that the
+ <c>tmo</c> argument to these callbacks differ from the
+ timeout arguments in the <c>ei</c> API. Zero means a zero
+ timeout. That is, poll and timeout immediately unless the
+ operation is successful. <c>EI_SCLBK_INF_TMO</c>
+ (max <c>unsigned</c>) means infinite timeout. The file
+ descriptor is in blocking mode when a callback is called,
+ and it must be in blocking mode when the callback returns.
+ </p>
+ <p>
+ If not set, <c>ei</c> will implement the timeout using
+ <c>select()</c> in order to determine when to call the
+ callbacks and when to time out. The <c>tmo</c> arguments
+ of the <c>accept()</c>, <c>connect()</c>, <c>writev()</c>,
+ <c>write()</c>, and <c>read()</c> callbacks should be
+ ignored. The callbacks may be called in non-blocking mode.
+ The callbacks are not allowed to change between blocking
+ and non-blocking mode. In order for this to work,
+ <c>select()</c> needs to interact with the socket primitives
+ used the same way as it interacts with the ordinary socket
+ primitives. If this is not the case, the callbacks
+ <em>need</em> to implement timeouts and this flag should
+ be set.
+ </p>
+ </item>
+ </taglist>
+ <p>More flags may be introduced in the future.</p>
+ </item>
+
+ <tag><c>int (*socket)(void **ctx, void *setup_ctx)</c></tag>
+ <item>
+ <p>Create a socket and a context for the socket.</p>
+
+ <p>On success it should set <c>*ctx</c> to point to a context for
+ the created socket. This context will be passed to all other
+ socket callbacks. This function will be passed the same
+ <c>setup_context</c> as passed to the preceeding
+ <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso>
+ or
+ <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>
+ call.</p>
+
+ <note><p>During the lifetime of a socket, the pointer <c>*ctx</c>
+ <em>has</em> to remain the same. That is, it cannot later be
+ relocated.</p></note>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*close)(void *ctx)</c></tag>
+ <item>
+ <p>Close the socket identified by <c>ctx</c> and destroy the context.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*listen)(void *ctx, void *addr, int *len, int backlog)</c></tag>
+ <item>
+ <p>Bind the socket identified by <c>ctx</c> to a local interface
+ and then listen on it.</p>
+
+ <p>The <c>addr</c> and <c>len</c> arguments are both input and output
+ arguments. When called <c>addr</c> points to an address structure of
+ lenght <c>*len</c> containing information on how to bind the socket.
+ Uppon return this callback should have updated the structure referred
+ by <c>addr</c> with information on how the socket actually was bound.
+ <c>*len</c> should be updated to reflect the size of <c>*addr</c>
+ updated. <c>backlog</c> identifies the size of the backlog for the
+ listen socket.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*accept)(void **ctx, void *addr, int *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Accept connections on the listen socket identified by
+ <c>*ctx</c>.</p>
+
+ <p>When a connection is accepted, a new context for the accepted
+ connection should be created and <c>*ctx</c> should be updated
+ to point to the new context for the accepted connection. When
+ called <c>addr</c> points to an uninitialized address structure
+ of lenght <c>*len</c>. Uppon return this callback should have
+ updated this structure with information about the client address.
+ <c>*len</c> should be updated to reflect the size of <c>*addr</c>
+ updated.
+ </p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <note><p>During the lifetime of a socket, the pointer <c>*ctx</c>
+ <em>has</em> to remain the same. That is, it cannot later be
+ relocated.</p></note>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*connect)(void *ctx, void *addr, int len, unsigned tmo)</c></tag>
+ <item>
+ <p>Connect the socket identified by <c>ctx</c> to the address
+ identified by <c>addr</c>.</p>
+
+ <p>When called <c>addr</c> points to an address structure of
+ lenght <c>len</c> containing information on where to connect.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*writev)(void *ctx, const void *iov, long iovcnt, ssize_t *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Write data on the connected socket identified by <c>ctx</c>.</p>
+
+ <p><c>iov</c> points to an array of <c>struct iovec</c> structures of
+ length <c>iovcnt</c> containing data to write to the socket. On success,
+ this callback should set <c>*len</c> to the amount of bytes successfully
+ written on the socket.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is optional. Set the <c>writev</c> field
+ in the the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not
+ implemented.</p>
+ </item>
+
+ <tag><c>int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Write data on the connected socket identified by <c>ctx</c>.</p>
+
+ <p>When called <c>buf</c> points to a buffer of length <c>*len</c>
+ containing the data to write on the socket. On success, this callback
+ should set <c>*len</c> to the amount of bytes successfully written on
+ the socket.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo)</c></tag>
+ <item>
+ <p>Read data on the connected socket identified by <c>ctx</c>.</p>
+
+ <p><c>buf</c> points to a buffer of length <c>*len</c> where the
+ read data should be placed. On success, this callback should update
+ <c>*len</c> to the amount of bytes successfully read on the socket.</p>
+
+ <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set,
+ <c>tmo</c> contains timeout time in milliseconds.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*handshake_packet_header_size)(void *ctx, int *sz)</c></tag>
+ <item>
+ <p>Inform about handshake packet header size to use during the Erlang
+ distribution handshake.</p>
+
+ <p>On success, <c>*sz</c> should be set to the handshake packet header
+ size to use. Valid values are <c>2</c> and <c>4</c>. Erlang TCP
+ distribution use a handshake packet size of <c>2</c> and Erlang TLS
+ distribution use a handshake packet size of <c>4</c>.</p>
+
+ <p>This callback is mandatory.</p>
+ </item>
+
+ <tag><c>int (*connect_handshake_complete)(void *ctx)</c></tag>
+ <item>
+ <p>Called when a locally started handshake has completed successfully.</p>
+
+ <p>This callback is optional. Set the <c>connect_handshake_complete</c> field
+ in the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not implemented.</p>
+ </item>
+
+ <tag><c>int (*accept_handshake_complete)(void *ctx)</c></tag>
+ <item>
+ <p>Called when a remotely started handshake has completed successfully.</p>
+
+ <p>This callback is optional. Set the <c>accept_handshake_complete</c> field in
+ the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not implemented.</p>
+ </item>
+
+ <tag><c>int (*get_fd)(void *ctx, int *fd)</c></tag>
+ <item>
+ <p>Inform about file descriptor used by the socket which is identified
+ by <c>ctx</c>.</p>
+
+ <note><p>During the lifetime of a socket, the file descriptor
+ <em>has</em> to remain the same. That is, repeated calls to this
+ callback with the same context <c>should</c> always report the same
+ file descriptor.</p>
+ <p>The file descriptor <em>has</em> to be a real file descriptor.
+ That is, no other operation should be able to get the same file
+ descriptor until it has been released by the <c>close()</c>
+ callback.</p>
+ </note>
+
+ <p>This callback is mandatory.</p>
+ </item>
+ </taglist>
+ </section>
<funcs>
<func>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyaddr_r(const char *addr, int length, int type, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyname(const char *name)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*ei_gethostbyname_r(const char *name, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr_r(const char *addr, int length, int type, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyname(const char *name)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyname_r(const char *name, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name>
<fsummary>Name lookup functions.</fsummary>
<desc>
<p>Convenience functions for some common name lookup functions.</p>
</desc>
</func>
+
<func>
- <name><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name>
+ <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>
<desc>
<p>Used by a server process to accept a
@@ -130,7 +398,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
@@ -141,8 +409,16 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name>
- <name><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name>
+ <fsummary>Close a connection.</fsummary>
+ <desc>
+ <p>Closes a previously opened connection or listen socket.</p>
+ </desc>
+ </func>
+
+ <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>
<fsummary>Establish a connection to an Erlang node.</fsummary>
<desc>
<p>Sets up a connection to an Erlang node.</p>
@@ -192,8 +468,10 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
</func>
<func>
- <name><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name>
- <name><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=""><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name>
+ <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 @OTP-15442@"><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>
<desc>
<p>Initializes the <c>ec</c> structure, to
@@ -236,6 +514,21 @@ fd = ei_xconnect(&ec, &addr, ALIVE);
<item>
<p><c>thispaddr</c> if the IP address of the host.</p>
</item>
+ <item>
+ <p><c>cbs</c> is a pointer to a
+ <seealso marker="#ei_socket_callbacks">callback structure</seealso>
+ implementing and alternative socket interface.</p>
+ </item>
+ <item>
+ <p><c>cbs_sz</c> is the size of the structure
+ pointed to by <c>cbs</c>.</p>
+ </item>
+ <item>
+ <p><c>setup_context</c> is a pointer to a structure that
+ will be passed as second argument to the <c>socket</c> callback
+ in the <c>cbs</c> structure.</p>
+ </item>
+
</list>
<p>A C-node acting as a server is assigned a creation
number when it calls <c>ei_publish()</c>.</p>
@@ -273,8 +566,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name>
- <name><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name>
+ <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>
<fsummary>Establish a connection to an Erlang node with optional
time-out.</fsummary>
<desc>
@@ -286,8 +579,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_get_tracelevel(void)</nametext></name>
- <name><ret>void</ret><nametext>ei_set_tracelevel(int level)</nametext></name>
+ <name since="OTP R13B04"><ret>int</ret><nametext>ei_get_tracelevel(void)</nametext></name>
+ <name since="OTP R13B04"><ret>void</ret><nametext>ei_set_tracelevel(int level)</nametext></name>
<fsummary>Get and set functions for tracing.</fsummary>
<desc>
<p>Used to set tracing on the distribution. The levels are different
@@ -299,7 +592,46 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name>
+ <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name>
+ <fsummary>Create a listen socket.</fsummary>
+ <desc>
+ <p>Used by a server process to setup a listen socket which
+ later can be used for accepting connections from client processes.
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>ec</c> is the C-node structure.</p>
+ </item>
+ <item>
+ <p><c>adr</c> is local interface to bind to.</p>
+ </item>
+ <item>
+ <p><c>port</c> is a pointer to an integer containing the
+ port number to bind to. If <c>*port</c> equals <c>0</c>
+ when calling <c>ei_listen()</c>, the socket will be bound to
+ an ephemeral port. On success, <c>ei_listen()</c> will update
+ the value of <c>*port</c> to the port actually bound to.
+ </p>
+ </item>
+ <item>
+ <p><c>backlog</c> is maximum backlog of pending connections.</p>
+ </item>
+ </list>
+ <p><c>ei_listen</c> will create a socket, bind to a port on the
+ local interface identified by <c>adr</c> (or all local interfaces if
+ <c>ei_listen()</c> is called), and mark the socket as a passive socket
+ (that is, a socket that will be used for accepting incoming connections).
+ </p>
+ <p>
+ On success, a file descriptor is returned which can be used in a call to
+ <c>ei_accept()</c>. On failure, <c>ERL_ERROR</c> is returned and
+ <c>erl_errno</c> is set to <c>EIO</c>.</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>
<desc>
<p>Used by a server process to register
@@ -336,7 +668,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
<p>Equivalent to
@@ -346,7 +678,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive(int fd, unsigned char* bufp, int bufsize)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive(int fd, unsigned char* bufp, int bufsize)</nametext></name>
<fsummary>Receive a message.</fsummary>
<desc>
<p>Receives a message consisting of a sequence
@@ -387,7 +719,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name>
+ <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>
<desc>
<p>This function is retained for compatibility with code
@@ -417,7 +749,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
@@ -428,8 +760,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
- <name><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name>
+ <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>
<desc>
<p>Receives a message to the buffer in <c>x</c>.
@@ -493,8 +825,8 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name>
- <name><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
<p>Equivalent to <c>ei_receive_msg</c> and <c>ei_xreceive_msg</c>
@@ -504,7 +836,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_receive_tmo(int fd, unsigned char* bufp, int bufsize, unsigned timeout_ms)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_receive_tmo(int fd, unsigned char* bufp, int bufsize, unsigned timeout_ms)</nametext></name>
<fsummary>Receive a message with optional time-out.</fsummary>
<desc>
<p>Equivalent to
@@ -514,7 +846,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name>
+ <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>
<desc>
<p>Sends an Erlang term to a registered process.</p>
@@ -546,7 +878,7 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
</func>
<func>
- <name><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>
+ <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>
<desc>
@@ -557,9 +889,9 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0)
</func>
<func>
- <name><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name>
- <name><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name>
- <name><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name>
+ <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>
<desc>
<p>Supports calling Erlang functions on remote nodes.
@@ -658,7 +990,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name>
<fsummary>Retrieve the pid of the C-node.</fsummary>
<desc>
<p>Retrieves the pid of the C-node. Every C-node
@@ -671,7 +1003,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
<fsummary>Send a message.</fsummary>
<desc>
<p>Sends an Erlang term to a process.</p>
@@ -692,7 +1024,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name>
+ <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>
<desc>
<p>Works exactly as <c>ei_send</c>, the alternative name is retained for
@@ -702,7 +1034,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
@@ -713,7 +1045,7 @@ if (ei_decode_version(result.buff, &index) < 0
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name>
+ <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>
<desc>
@@ -741,7 +1073,7 @@ self->num = fd;
</func>
<func>
- <name><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>
+ <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>
<desc>
@@ -752,7 +1084,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
<p>Equivalent to
@@ -762,9 +1094,9 @@ self->num = fd;
</func>
<func>
- <name><ret>const char *</ret><nametext>ei_thisnodename(ei_cnode *ec)</nametext></name>
- <name><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name>
- <name><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>ei_thisnodename(ei_cnode *ec)</nametext></name>
+ <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>
<desc>
<p>Can be used to retrieve information about
@@ -779,7 +1111,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name>
<fsummary>Forcefully unpublish a node name.</fsummary>
<desc>
<p>Can be called by a process to unregister a
@@ -802,7 +1134,7 @@ self->num = fd;
</func>
<func>
- <name><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name>
+ <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>
<desc>
<p>Equivalent to
diff --git a/lib/erl_interface/doc/src/erl_connect.xml b/lib/erl_interface/doc/src/erl_connect.xml
index 76ef6588c2..139ac9e2f0 100644
--- a/lib/erl_interface/doc/src/erl_connect.xml
+++ b/lib/erl_interface/doc/src/erl_connect.xml
@@ -49,7 +49,7 @@
<funcs>
<func>
- <name><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name>
<fsummary>Accept a connection.</fsummary>
<type>
<v>int listensock;</v>
@@ -78,7 +78,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name>
<fsummary>Close a connection to an Erlang node.</fsummary>
<type>
<v>int fd;</v>
@@ -95,8 +95,8 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_connect(node)</nametext></name>
- <name><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_connect(node)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name>
<fsummary>Establish a connection to an Erlang node.</fsummary>
<type>
<v>char *node, *alive;</v>
@@ -149,8 +149,8 @@ erl_xconnect( &addr , ALIVE );
</func>
<func>
- <name><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name>
- <name><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name>
<fsummary>Initialize communication.</fsummary>
<type>
<v>int number;</v>
@@ -246,7 +246,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0))
</func>
<func>
- <name><ret>int</ret><nametext>erl_publish(port)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_publish(port)</nametext></name>
<fsummary>Publish a node name.</fsummary>
<type>
<v>int port;</v>
@@ -277,7 +277,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0))
</func>
<func>
- <name><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name>
<fsummary>Receive a message.</fsummary>
<type>
<v>int fd;</v>
@@ -316,7 +316,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0))
</func>
<func>
- <name><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name>
<fsummary>Receive and decode a message.</fsummary>
<type>
<v>int fd;</v>
@@ -411,7 +411,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name>
<fsummary>Send a message to a registered name.</fsummary>
<type>
<v>int fd;</v>
@@ -439,9 +439,9 @@ typedef struct {
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name>
- <name><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name>
- <name><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name>
<fsummary>Remote Procedure Call.</fsummary>
<type>
<v>int fd, timeout;</v>
@@ -511,7 +511,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name>
<fsummary>Send a message.</fsummary>
<type>
<v>int fd;</v>
@@ -541,11 +541,11 @@ typedef struct {
</func>
<func>
- <name><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name>
- <name><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name>
- <name><ret>short</ret><nametext>erl_thiscreation()</nametext></name>
- <name><ret>const char *</ret><nametext>erl_thishostname()</nametext></name>
- <name><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name>
+ <name since=""><ret>short</ret><nametext>erl_thiscreation()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thishostname()</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name>
<fsummary>Retrieve some values.</fsummary>
<desc>
<p>Retrieves information about
@@ -556,7 +556,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name>
<fsummary>Forcefully unpublish a node name.</fsummary>
<type>
<v>char *alive;</v>
@@ -583,7 +583,7 @@ typedef struct {
</func>
<func>
- <name><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name>
<fsummary>Receive and decode a message.</fsummary>
<type>
<v>int fd;</v>
@@ -616,10 +616,10 @@ typedef struct {
</func>
<func>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyaddr(addr, length, type)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyname(name)</nametext></name>
- <name><ret>struct hostent</ret><nametext>*erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr(addr, length, type)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname(name)</nametext></name>
+ <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name>
<fsummary>Name lookup functions.</fsummary>
<type>
diff --git a/lib/erl_interface/doc/src/erl_error.xml b/lib/erl_interface/doc/src/erl_error.xml
index 8139c9b343..6fac94e442 100644
--- a/lib/erl_interface/doc/src/erl_error.xml
+++ b/lib/erl_interface/doc/src/erl_error.xml
@@ -47,7 +47,7 @@
<funcs>
<func>
- <name><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name>
<fsummary>Non-fatal error, and not system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -59,7 +59,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name>
<fsummary>Fatal error, but not system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -73,7 +73,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name>
<fsummary>Non-fatal system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -86,7 +86,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name>
<fsummary>Fatal system call error.</fsummary>
<type>
<v>const char *FormatStr;</v>
@@ -113,7 +113,7 @@
<funcs>
<func>
- <name><ret>volatile int</ret><nametext>erl_errno</nametext></name>
+ <name since=""><ret>volatile int</ret><nametext>erl_errno</nametext></name>
<fsummary>Variable <c>erl_errno</c> contains the
Erl_Interface error number. You can change the value if you wish.
</fsummary>
diff --git a/lib/erl_interface/doc/src/erl_eterm.xml b/lib/erl_interface/doc/src/erl_eterm.xml
index 9a05196a70..070ed30dfe 100644
--- a/lib/erl_interface/doc/src/erl_eterm.xml
+++ b/lib/erl_interface/doc/src/erl_eterm.xml
@@ -142,7 +142,7 @@
<funcs>
<func>
- <name><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name>
<fsummary>Prepend a term to the head of a list.</fsummary>
<type>
<v>ETERM *head;</v>
@@ -181,7 +181,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name>
<fsummary>Create a copy of an Erlang term.</fsummary>
<type>
<v>ETERM *term;</v>
@@ -193,7 +193,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name>
<fsummary>Extract an element from an Erlang tuple.</fsummary>
<type>
<v>int position;</v>
@@ -215,7 +215,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name>
<fsummary>Extract the first element from a list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -230,7 +230,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name>
<fsummary>Initialization routine.</fsummary>
<type>
<v>void *NULL;</v>
@@ -245,7 +245,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name>
<fsummary>Return the length of an I/O list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -262,7 +262,7 @@ erl_free_compound(list);
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name>
<fsummary>Convert an I/O list to a binary.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -289,7 +289,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name>
+ <name since=""><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name>
<fsummary>Convert an I/O list to a <c>NULL</c>-terminated string.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -312,7 +312,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>int</ret><nametext>erl_length(list)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_length(list)</nametext></name>
<fsummary>Determine the length of a list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -328,7 +328,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name>
<fsummary>Create an atom.</fsummary>
<type>
<v>const char *string;</v>
@@ -355,7 +355,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name>
<fsummary>Create a binary object.</fsummary>
<type>
<v>char *bptr;</v>
@@ -378,7 +378,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name>
<fsummary>Create an empty Erlang list.</fsummary>
<desc>
<p>Creates and returns an empty Erlang list.
@@ -388,7 +388,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name>
<fsummary>Create an Erlang string.</fsummary>
<type>
<v>char *string;</v>
@@ -408,7 +408,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name>
<fsummary>Create an Erlang float.</fsummary>
<type>
<v>double f;</v>
@@ -426,7 +426,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name>
<fsummary>Create an Erlang integer.</fsummary>
<type>
<v>int n;</v>
@@ -443,7 +443,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name>
<fsummary>Create a list from an array.</fsummary>
<type>
<v>ETERM **array;</v>
@@ -465,7 +465,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name>
<fsummary>Create an Erlang reference.</fsummary>
<type>
<v>const char *node;</v>
@@ -495,7 +495,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name>
<fsummary>Create a process identifier.</fsummary>
<type>
<v>const char *node;</v>
@@ -525,7 +525,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name>
<fsummary>Create a port identifier.</fsummary>
<type>
<v>const char *node;</v>
@@ -550,7 +550,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name>
<fsummary>Create an old Erlang reference.</fsummary>
<type>
<v>const char *node;</v>
@@ -578,7 +578,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name>
<fsummary>Create a string.</fsummary>
<type>
<v>char *string;</v>
@@ -593,7 +593,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name>
<fsummary>Create an Erlang tuple from an array.</fsummary>
<type>
<v>ETERM **array;</v>
@@ -621,7 +621,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name>
<fsummary>Create an unsigned integer.</fsummary>
<type>
<v>unsigned int n;</v>
@@ -638,7 +638,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name>
<fsummary>Create an Erlang variable.</fsummary>
<type>
<v>char *name;</v>
@@ -653,7 +653,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name>
<fsummary>Print an Erlang term.</fsummary>
<type>
<v>FILE *stream;</v>
@@ -672,7 +672,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name>
<fsummary>Set the Erl_Interface library in compatibility mode.</fsummary>
<type>
<v>unsigned release_number;</v>
@@ -706,7 +706,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>int</ret><nametext>erl_size(term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_size(term)</nametext></name>
<fsummary>Return the arity of a tuple or binary.</fsummary>
<type>
<v>ETERM *term;</v>
@@ -723,7 +723,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name>
<fsummary>Extract the tail from a list.</fsummary>
<type>
<v>ETERM *list;</v>
@@ -738,7 +738,7 @@ iohead ::= Binary
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name>
<fsummary>Extract the content of a variable.</fsummary>
<type>
<v>ETERM *term;</v>
diff --git a/lib/erl_interface/doc/src/erl_format.xml b/lib/erl_interface/doc/src/erl_format.xml
index 5b8b7b5e78..b5e895c720 100644
--- a/lib/erl_interface/doc/src/erl_format.xml
+++ b/lib/erl_interface/doc/src/erl_format.xml
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name>
<fsummary>Create an Erlang term.</fsummary>
<type>
<v>char *FormatStr;</v>
@@ -81,7 +81,7 @@ erl_format("[{name,~a},{age,~i},{data,~w}]",
</func>
<func>
- <name><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name>
<fsummary>Perform pattern matching.</fsummary>
<type>
<v>ETERM *Pattern,*Term;</v>
diff --git a/lib/erl_interface/doc/src/erl_global.xml b/lib/erl_interface/doc/src/erl_global.xml
index 2fa0045adf..72d43e81d5 100644
--- a/lib/erl_interface/doc/src/erl_global.xml
+++ b/lib/erl_interface/doc/src/erl_global.xml
@@ -48,7 +48,7 @@
<funcs>
<func>
- <name><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name>
+ <name since=""><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name>
<fsummary>Obtain list of global names.</fsummary>
<type>
<v>int fd;</v>
@@ -79,7 +79,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name>
<fsummary>Register a name in global.</fsummary>
<type>
<v>int fd;</v>
@@ -103,7 +103,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name>
<fsummary>Unregister a name from global.</fsummary>
<type>
<v>int fd;</v>
@@ -122,7 +122,7 @@
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name>
<fsummary>Look up a name in global.</fsummary>
<type>
<v>int fd;</v>
diff --git a/lib/erl_interface/doc/src/erl_malloc.xml b/lib/erl_interface/doc/src/erl_malloc.xml
index c0eebc29e9..aae3b7e078 100644
--- a/lib/erl_interface/doc/src/erl_malloc.xml
+++ b/lib/erl_interface/doc/src/erl_malloc.xml
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name>
<fsummary>Allocate an ETERM structure.</fsummary>
<type>
<v>unsigned char etype;</v>
@@ -89,7 +89,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name>
<fsummary>Clear the ETERM freelist.</fsummary>
<desc>
<p>Clears the freelist, where blocks are placed when they are
@@ -99,7 +99,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name>
<fsummary>Report term allocation statistics.</fsummary>
<type>
<v>long *allocated;</v>
@@ -127,7 +127,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free(ptr)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free(ptr)</nametext></name>
<fsummary>Free some memory.</fsummary>
<type>
<v>void *ptr;</v>
@@ -139,7 +139,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name>
<fsummary>Free an array of ETERM structures.</fsummary>
<type>
<v>ETERM **array;</v>
@@ -156,7 +156,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free_compound(t)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free_compound(t)</nametext></name>
<fsummary>Free an array of ETERM structures.</fsummary>
<type>
<v>ETERM *t;</v>
@@ -179,7 +179,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_free_term(t)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_free_term(t)</nametext></name>
<fsummary>Free an ETERM structure.</fsummary>
<type>
<v>ETERM *t;</v>
@@ -190,7 +190,7 @@
</func>
<func>
- <name><ret>void</ret><nametext>erl_malloc(size)</nametext></name>
+ <name since=""><ret>void</ret><nametext>erl_malloc(size)</nametext></name>
<fsummary>Allocate some memory.</fsummary>
<type>
<v>long size;</v>
diff --git a/lib/erl_interface/doc/src/erl_marshal.xml b/lib/erl_interface/doc/src/erl_marshal.xml
index 2ad658f78b..1a6d3bb43c 100644
--- a/lib/erl_interface/doc/src/erl_marshal.xml
+++ b/lib/erl_interface/doc/src/erl_marshal.xml
@@ -42,7 +42,7 @@
<funcs>
<func>
- <name><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name>
<fsummary>Compare encoded byte sequences.</fsummary>
<type>
<v>unsigned char *bufp1,*bufp2;</v>
@@ -62,8 +62,8 @@
</func>
<func>
- <name><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name>
- <name><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name>
+ <name since=""><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name>
<fsummary>Convert a term from Erlang external format.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -102,8 +102,8 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name>
- <name><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name>
<fsummary>Convert a term into Erlang external format.</fsummary>
<type>
<v>ETERM *term;</v>
@@ -179,7 +179,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name>
<fsummary>Count elements in encoded term.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -190,7 +190,7 @@
</func>
<func>
- <name><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name>
+ <name since=""><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name>
<fsummary>Determine type of an encoded byte sequence.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -228,7 +228,7 @@
</func>
<func>
- <name><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name>
+ <name since=""><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name>
<fsummary>Step over encoded term.</fsummary>
<type>
<v>unsigned char *bufp;</v>
@@ -252,7 +252,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>erl_term_len(t)</nametext></name>
+ <name since=""><ret>int</ret><nametext>erl_term_len(t)</nametext></name>
<fsummary>Determine encoded size of term.</fsummary>
<type>
<v>ETERM *t;</v>
diff --git a/lib/erl_interface/doc/src/registry.xml b/lib/erl_interface/doc/src/registry.xml
index 6d70fb3475..1c90c5c9dd 100644
--- a/lib/erl_interface/doc/src/registry.xml
+++ b/lib/erl_interface/doc/src/registry.xml
@@ -44,7 +44,7 @@
<funcs>
<func>
- <name><ret>int</ret><nametext>ei_reg_close(reg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_close(reg)</nametext></name>
<fsummary>Close a registry.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -59,7 +59,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_delete(reg,key)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_delete(reg,key)</nametext></name>
<fsummary>Delete an object from the registry.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -85,7 +85,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_dump(fd,reg,mntab,flags)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_dump(fd,reg,mntab,flags)</nametext></name>
<fsummary>Back up a registry to Mnesia.</fsummary>
<type>
<v>int fd;</v>
@@ -125,7 +125,7 @@
</func>
<func>
- <name><ret>double</ret><nametext>ei_reg_getfval(reg,key)</nametext></name>
+ <name since=""><ret>double</ret><nametext>ei_reg_getfval(reg,key)</nametext></name>
<fsummary>Get a floating point object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_getival(reg,key)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_getival(reg,key)</nametext></name>
<fsummary>Get an integer object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -177,7 +177,7 @@
</func>
<func>
- <name><ret>const void *</ret><nametext>ei_reg_getpval(reg,key,size)</nametext></name>
+ <name since=""><ret>const void *</ret><nametext>ei_reg_getpval(reg,key,size)</nametext></name>
<fsummary>Get a binary object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -207,7 +207,7 @@
</func>
<func>
- <name><ret>const char *</ret><nametext>ei_reg_getsval(reg,key)</nametext></name>
+ <name since=""><ret>const char *</ret><nametext>ei_reg_getsval(reg,key)</nametext></name>
<fsummary>Get a string object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -232,7 +232,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_getval(reg,key,flags,v,...)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_getval(reg,key,flags,v,...)</nametext></name>
<fsummary>Get any object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -278,7 +278,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_markdirty(reg,key)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_markdirty(reg,key)</nametext></name>
<fsummary>Mark an object as dirty.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -305,7 +305,7 @@
</func>
<func>
- <name><ret>ei_reg *</ret><nametext>ei_reg_open(size)</nametext></name>
+ <name since=""><ret>ei_reg *</ret><nametext>ei_reg_open(size)</nametext></name>
<fsummary>Create and open a registry.</fsummary>
<type>
<v>int size;</v>
@@ -326,7 +326,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_purge(reg)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_purge(reg)</nametext></name>
<fsummary>Remove deleted objects.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -346,7 +346,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_resize(reg,newsize)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_resize(reg,newsize)</nametext></name>
<fsummary>Resize a registry.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -363,7 +363,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_restore(fd,reg,mntab)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_restore(fd,reg,mntab)</nametext></name>
<fsummary>Restore a registry from Mnesia.</fsummary>
<type>
<v>int fd;</v>
@@ -399,7 +399,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setfval(reg,key,f)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setfval(reg,key,f)</nametext></name>
<fsummary>Assign a floating point object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -424,7 +424,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setival(reg,key,i)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setival(reg,key,i)</nametext></name>
<fsummary>Assign an integer object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -448,7 +448,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setpval(reg,key,p,size)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setpval(reg,key,p,size)</nametext></name>
<fsummary>Assign a binary object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -479,7 +479,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setsval(reg,key,s)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setsval(reg,key,s)</nametext></name>
<fsummary>Assign a string object.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -507,7 +507,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_setval(reg,key,flags,v,...)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_setval(reg,key,flags,v,...)</nametext></name>
<fsummary>Assign a value to any object type.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -552,7 +552,7 @@
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_stat(reg,key,obuf)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_stat(reg,key,obuf)</nametext></name>
<fsummary>Get object information.</fsummary>
<type>
<v>ei_reg *reg;</v>
@@ -590,7 +590,7 @@ struct ei_reg_stat {
</func>
<func>
- <name><ret>int</ret><nametext>ei_reg_tabstat(reg,obuf)</nametext></name>
+ <name since=""><ret>int</ret><nametext>ei_reg_tabstat(reg,obuf)</nametext></name>
<fsummary>Get registry information.</fsummary>
<type>
<v>ei_reg *reg;</v>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index 948f89be85..92674571e2 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -35,6 +35,7 @@
#include <winsock2.h>
#include <windows.h>
#include <winbase.h>
+typedef LONG_PTR ssize_t; /* Sigh... */
#endif
#include <stdio.h> /* Need type FILE */
@@ -286,6 +287,31 @@ typedef struct {
char nodename[MAXNODELEN+1];
} ErlConnect;
+#define EI_SCLBK_INF_TMO (~((unsigned) 0))
+
+#define EI_SCLBK_FLG_FULL_IMPL (1 << 0)
+
+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);
+
+ /* end of version 1 */
+
+} ei_socket_callbacks;
+
typedef struct ei_cnode_s {
char thishostname[EI_MAXHOSTNAMELEN+1];
char thisnodename[MAXNODELEN+1];
@@ -295,6 +321,8 @@ typedef struct ei_cnode_s {
char ei_connect_cookie[EI_MAX_COOKIE_SIZE+1];
short creation;
erlang_pid self;
+ ei_socket_callbacks *cbs;
+ void *setup_context;
} ei_cnode;
typedef struct in_addr *Erl_IpAddr;
@@ -308,7 +336,6 @@ typedef struct ei_x_buff_TAG {
int index;
} ei_x_buff;
-
/* -------------------------------------------------------------------- */
/* Function definitions (listed in same order as documentation) */
/* -------------------------------------------------------------------- */
@@ -322,6 +349,16 @@ int ei_connect_xinit (ei_cnode* ec, const char *thishostname,
Erl_IpAddr thisipaddr, const char *cookie,
const short creation);
+int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
+ const char *cookie, short creation,
+ ei_socket_callbacks *cbs, int cbs_sz,
+ void *setup_context);
+int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
+ const char *thisalivename, const char *thisnodename,
+ Erl_IpAddr thisipaddr, const char *cookie,
+ const short creation, ei_socket_callbacks *cbs,
+ int cbs_sz, void *setup_context);
+
int ei_connect(ei_cnode* ec, char *nodename);
int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms);
int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename);
@@ -348,11 +385,15 @@ int ei_rpc_from(ei_cnode* ec, int fd, int timeout, erlang_msg* msg,
int ei_publish(ei_cnode* ec, int port);
int ei_publish_tmo(ei_cnode* ec, int port, unsigned ms);
+int ei_listen(ei_cnode *ec, int *port, int backlog);
+int ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog);
int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp);
int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms);
int ei_unpublish(ei_cnode* ec);
int ei_unpublish_tmo(const char *alive, unsigned ms);
+int ei_close_connection(int fd);
+
const char *ei_thisnodename(const ei_cnode* ec);
const char *ei_thishostname(const ei_cnode* ec);
const char *ei_thisalivename(const ei_cnode* ec);
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index 9df4fa3b6c..1132c9fc23 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -21,8 +21,6 @@
* Purpose: Connect to any node at any host. (EI version)
*/
-#include "eidef.h"
-
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
@@ -84,7 +82,9 @@
#include <string.h>
#include <errno.h>
#include <ctype.h>
+#include <stddef.h>
+#include "eidef.h"
#include "eiext.h"
#include "ei_portio.h"
#include "ei_internal.h"
@@ -103,6 +103,10 @@ int ei_tracelevel = 0;
#define COOKIE_FILE "/.erlang.cookie"
#define EI_MAX_HOME_PATH 1024
+#define EI_SOCKET_CALLBACKS_SZ_V1 \
+ (offsetof(ei_socket_callbacks, get_fd) \
+ + sizeof(int (*)(void *)))
+
/* FIXME why not macro? */
static char *null_cookie = "";
@@ -113,35 +117,51 @@ static int get_home(char *buf, int size);
static unsigned gen_challenge(void);
static void gen_digest(unsigned challenge, char cookie[],
unsigned char digest[16]);
-static int send_status(int fd, char *status, unsigned ms);
-static int recv_status(int fd, unsigned ms);
-static int send_challenge(int fd, char *nodename,
- unsigned challenge, unsigned version, unsigned ms);
-static int recv_challenge(int fd, unsigned *challenge,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms);
-static int send_challenge_reply(int fd, unsigned char digest[16],
+static int send_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, char *status, unsigned ms);
+static int recv_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned ms);
+static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned challenge,
+ unsigned version, unsigned ms);
+static int recv_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ unsigned *challenge, unsigned *version,
+ unsigned *flags, char *namebuf, unsigned ms);
+static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned char digest[16],
unsigned challenge, unsigned ms);
-static int recv_challenge_reply(int fd,
- unsigned our_challenge,
+static int recv_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned our_challenge,
char cookie[],
unsigned *her_challenge, unsigned ms);
-static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms);
-static int recv_challenge_ack(int fd,
- unsigned our_challenge,
+static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned char digest[16],
+ unsigned ms);
+static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned our_challenge,
char cookie[], unsigned ms);
-static int send_name(int fd, char *nodename,
- unsigned version, unsigned ms);
+static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned version, unsigned ms);
-/* Common for both handshake types */
-static int recv_name(int fd,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms);
+static int recv_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ unsigned *version, unsigned *flags, char *namebuf,
+ unsigned ms);
static struct hostent*
dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p,
int buflen, int *h_errnop);
+static void abort_connection(ei_socket_callbacks *cbs, void *ctx);
+static int close_connection(ei_socket_callbacks *cbs, void *ctx, int fd);
+
+static char *
+estr(int e)
+{
+ char *str = strerror(e);
+ if (!str)
+ return "unknown error";
+ return str;
+}
/***************************************************************************
@@ -154,25 +174,206 @@ dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p,
typedef struct ei_socket_info_s {
int socket;
+ ei_socket_callbacks *cbs;
+ void *ctx;
int dist_version;
ei_cnode cnode; /* A copy, not a pointer. We don't know when freed */
char cookie[EI_MAX_COOKIE_SIZE+1];
} ei_socket_info;
+/***************************************************************************
+ *
+ * XXX
+ *
+ ***************************************************************************/
+
+#ifndef ETHR_HAVE___atomic_compare_exchange_n
+# define ETHR_HAVE___atomic_compare_exchange_n 0
+#endif
+#ifndef ETHR_HAVE___atomic_load_n
+# define ETHR_HAVE___atomic_load_n 0
+#endif
+#ifndef ETHR_HAVE___atomic_store_n
+# define ETHR_HAVE___atomic_store_n 0
+#endif
+
+#if defined(_REENTRANT) \
+ && (!(ETHR_HAVE___atomic_compare_exchange_n & SIZEOF_VOID_P) \
+ || !(ETHR_HAVE___atomic_load_n & SIZEOF_VOID_P) \
+ || !(ETHR_HAVE___atomic_store_n & SIZEOF_VOID_P))
+# undef EI_DISABLE_SEQ_SOCKET_INFO
+# define EI_DISABLE_SEQ_SOCKET_INFO
+#endif
+
+#ifdef __WIN32__
+# undef EI_DISABLE_SEQ_SOCKET_INFO
+# define EI_DISABLE_SEQ_SOCKET_INFO
+#endif
+
+#ifndef EI_DISABLE_SEQ_SOCKET_INFO
+
+#ifdef _REENTRANT
+
+#define EI_ATOMIC_CMPXCHG_ACQ_REL(VARP, XCHGP, NEW) \
+ __atomic_compare_exchange_n((VARP), (XCHGP), (NEW), 0, \
+ __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)
+#define EI_ATOMIC_LOAD_ACQ(VARP) \
+ __atomic_load_n((VARP), __ATOMIC_ACQUIRE)
+#define EI_ATOMIC_STORE_REL(VARP, NEW) \
+ __atomic_store_n((VARP), (NEW), __ATOMIC_RELEASE)
+
+#else /* ! _REENTRANT */
+
+#define EI_ATOMIC_CMPXCHG_ACQ_REL(VARP, XCHGP, NEW) \
+ (*(VARP) == *(XCHGP) \
+ ? ((*(VARP) = (NEW)), !0) \
+ : ((*(XCHGP) = *(VARP)), 0))
+#define EI_ATOMIC_LOAD_ACQ(VARP) (*(VARP))
+#define EI_ATOMIC_STORE_REL(VARP, NEW) (*(VARP) = (NEW))
+
+#endif /* ! _REENTRANT */
+
+#define EI_SOCKET_INFO_SEG_BITS 5
+#define EI_SOCKET_INFO_SEG_SIZE (1 << EI_SOCKET_INFO_SEG_BITS)
+#define EI_SOCKET_INFO_SEG_MASK (EI_SOCKET_INFO_SEG_SIZE - 1)
+
+typedef struct {
+ int max_fds;
+ ei_socket_info *segments[1]; /* Larger in reality... */
+} ei_socket_info_data__;
+
+static ei_socket_info_data__ *socket_info_data = NULL;
+
+static int init_socket_info(void)
+{
+ int max_fds;
+ int i;
+ size_t segments_len;
+ ei_socket_info_data__ *info_data, *xchg;
+
+ if (EI_ATOMIC_LOAD_ACQ(&socket_info_data) != NULL)
+ return !0; /* Already initialized... */
+
+#if defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
+ max_fds = sysconf(_SC_OPEN_MAX);
+#else
+ max_fds = 1024;
+#endif
+
+ if (max_fds < 0)
+ return 0;
+
+ segments_len = ((max_fds-1)/EI_SOCKET_INFO_SEG_SIZE + 1);
+
+ info_data = malloc(sizeof(ei_socket_info_data__)
+ + (sizeof(ei_socket_info *)*(segments_len-1)));
+ if (!info_data)
+ return 0;
+
+ info_data->max_fds = max_fds;
+ for (i = 0; i < segments_len; i++)
+ info_data->segments[i] = NULL;
+
+ xchg = NULL;
+ if (!EI_ATOMIC_CMPXCHG_ACQ_REL(&socket_info_data, &xchg, info_data))
+ free(info_data); /* Already initialized... */
+
+ return !0;
+}
+
+static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec,
+ ei_socket_callbacks *cbs, void *ctx)
+{
+ int six;
+ ei_socket_info *seg, *si;
+ int socket;
+
+ if (fd < 0 || socket_info_data->max_fds <= fd)
+ return -1;
+
+ socket = fd;
+ six = fd >> EI_SOCKET_INFO_SEG_BITS;
+ seg = EI_ATOMIC_LOAD_ACQ(&socket_info_data->segments[six]);
+
+ if (!seg) {
+ ei_socket_info *xchg;
+ int i;
+ seg = malloc(sizeof(ei_socket_info)*EI_SOCKET_INFO_SEG_SIZE);
+ if (!seg)
+ return -1;
+ for (i = 0; i < EI_SOCKET_INFO_SEG_SIZE; i++) {
+ seg[i].socket = -1;
+ }
+
+ xchg = NULL;
+ if (!EI_ATOMIC_CMPXCHG_ACQ_REL(&socket_info_data->segments[six], &xchg, seg)) {
+ free(seg);
+ seg = xchg;
+ }
+ }
+
+ si = &seg[fd & EI_SOCKET_INFO_SEG_MASK];
+
+ if (dist_version < 0) {
+ socket = -1;
+ si->cbs = NULL;
+ si->ctx = NULL;
+ }
+ else {
+ si->dist_version = dist_version;
+ si->cnode = *ec;
+ si->cbs = cbs;
+ si->ctx = ctx;
+ strcpy(si->cookie, cookie);
+ }
+
+ EI_ATOMIC_STORE_REL(&si->socket, socket);
+
+ return 0;
+}
+
+static ei_socket_info* get_ei_socket_info(int fd)
+{
+ int six, socket;
+ ei_socket_info *seg, *si;
+
+ if (fd < 0 || socket_info_data->max_fds <= fd)
+ return NULL;
+
+ six = fd >> EI_SOCKET_INFO_SEG_BITS;
+ seg = EI_ATOMIC_LOAD_ACQ(&socket_info_data->segments[six]);
+
+ if (!seg)
+ return NULL;
+
+ si = &seg[fd & EI_SOCKET_INFO_SEG_MASK];
+ socket = EI_ATOMIC_LOAD_ACQ(&si->socket);
+ if (socket != fd)
+ return NULL;
+ return si;
+}
+
+#else /* EI_DISABLE_SEQ_SOCKET_INFO */
+
int ei_n_sockets = 0, ei_sz_sockets = 0;
ei_socket_info *ei_sockets = NULL;
+
#ifdef _REENTRANT
ei_mutex_t* ei_sockets_lock = NULL;
#endif /* _REENTRANT */
+static int init_socket_info(void)
+{
+#ifdef _REENTRANT
+ if (ei_sockets_lock == NULL) {
+ ei_sockets_lock = ei_mutex_create();
+ }
+#endif /* _REENTRANT */
+ return !0;
+}
-/***************************************************************************
- *
- * XXX
- *
- ***************************************************************************/
-
-static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec)
+static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec,
+ ei_socket_callbacks *cbs, void *ctx)
{
int i;
@@ -182,11 +383,13 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *
for (i = 0; i < ei_n_sockets; ++i) {
if (ei_sockets[i].socket == fd) {
if (dist_version == -1) {
- memmove(&ei_sockets[i], &ei_sockets[i+1],
+ memmove(&ei_sockets[i], &ei_sockets[i+1],
sizeof(ei_sockets[0])*(ei_n_sockets-i-1));
} else {
ei_sockets[i].dist_version = dist_version;
/* Copy the content, see ei_socket_info */
+ ei_sockets[i].cbs = cbs;
+ ei_sockets[i].ctx = ctx;
ei_sockets[i].cnode = *ec;
strcpy(ei_sockets[i].cookie, cookie);
}
@@ -209,7 +412,9 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *
}
ei_sockets[ei_n_sockets].socket = fd;
ei_sockets[ei_n_sockets].dist_version = dist_version;
- ei_sockets[i].cnode = *ec;
+ ei_sockets[ei_n_sockets].cnode = *ec;
+ ei_sockets[ei_n_sockets].cbs = cbs;
+ ei_sockets[ei_n_sockets].ctx = ctx;
strcpy(ei_sockets[ei_n_sockets].cookie, cookie);
++ei_n_sockets;
}
@@ -219,14 +424,6 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *
return 0;
}
-#if 0
-/* FIXME not used ?! */
-static int remove_ei_socket_info(int fd, int dist_version, char* cookie)
-{
- return put_ei_socket_info(fd, -1, NULL);
-}
-#endif
-
static ei_socket_info* get_ei_socket_info(int fd)
{
int i;
@@ -248,6 +445,13 @@ static ei_socket_info* get_ei_socket_info(int fd)
return NULL;
}
+#endif /* EI_DISABLE_SEQ_SOCKET_INFO */
+
+static int remove_ei_socket_info(int fd)
+{
+ return put_ei_socket_info(fd, -1, NULL, NULL, NULL, NULL);
+}
+
ei_cnode *ei_fd_to_cnode(int fd)
{
ei_socket_info *sockinfo = get_ei_socket_info(fd);
@@ -255,6 +459,19 @@ ei_cnode *ei_fd_to_cnode(int fd)
return &sockinfo->cnode;
}
+int ei_get_cbs_ctx__(ei_socket_callbacks **cbs, void **ctx, int fd)
+{
+ ei_socket_info *sockinfo = get_ei_socket_info(fd);
+ if (sockinfo) {
+ *cbs = sockinfo->cbs;
+ *ctx = sockinfo->ctx;
+ return 0;
+ }
+
+ *cbs = NULL;
+ *ctx = NULL;
+ return EBADF;
+}
/***************************************************************************
* Get/Set tracelevel
@@ -333,21 +550,6 @@ const char *ei_getfdcookie(int fd)
return r;
}
-/* call with cookie to set value to use on descriptor fd,
-* or specify NULL to use default
-*/
-/* FIXME why defined but not used? */
-#if 0
-static int ei_setfdcookie(ei_cnode* ec, int fd, char *cookie)
-{
- int dist_version = ei_distversion(fd);
-
- if (cookie == NULL)
- cookie = ec->ei_connect_cookie;
- return put_ei_socket_info(fd, dist_version, cookie);
-}
-#endif
-
static int get_int32(unsigned char *s)
{
return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] ));
@@ -405,12 +607,16 @@ static int initWinSock(void)
* Initailize by setting:
* thishostname, thisalivename, thisnodename and thisipaddr
*/
-int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
- const char *thisalivename, const char *thisnodename,
- Erl_IpAddr thisipaddr, const char *cookie,
- const short creation)
+int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname,
+ const char *thisalivename, const char *thisnodename,
+ Erl_IpAddr thisipaddr, const char *cookie,
+ const short creation, ei_socket_callbacks *cbs,
+ int cbs_sz, void *setup_context)
{
char *dbglevel;
+
+ if (cbs != &ei_default_socket_callbacks)
+ EI_SET_HAVE_PLUGIN_SOCKET_IMPL__;
/* FIXME this code was enabled for 'erl'_connect_xinit(), why not here? */
#if 0
@@ -422,12 +628,16 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
#endif
#endif
-#ifdef _REENTRANT
- if (ei_sockets_lock == NULL) {
- ei_sockets_lock = ei_mutex_create();
+ if (!init_socket_info()) {
+ EI_TRACE_ERR0("ei_connect_xinit","can't initiate socket info");
+ return ERL_ERROR;
}
-#endif /* _REENTRANT */
+ if (cbs_sz < EI_SOCKET_CALLBACKS_SZ_V1) {
+ EI_TRACE_ERR0("ei_connect_xinit","invalid size of ei_socket_callbacks struct");
+ return ERL_ERROR;
+ }
+
ec->creation = creation & 0x3; /* 2 bits */
if (cookie) {
@@ -469,6 +679,9 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
ec->self.serial = 0;
ec->self.creation = creation & 0x3; /* 2 bits */
+ ec->cbs = cbs;
+ ec->setup_context = setup_context;
+
if ((dbglevel = getenv("EI_TRACELEVEL")) != NULL ||
(dbglevel = getenv("ERL_DEBUG_DIST")) != NULL)
ei_tracelevel = atoi(dbglevel);
@@ -476,14 +689,27 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
return 0;
}
+int ei_connect_xinit(ei_cnode* ec, const char *thishostname,
+ const char *thisalivename, const char *thisnodename,
+ Erl_IpAddr thisipaddr, const char *cookie,
+ const short creation)
+{
+ return ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
+ thisipaddr, cookie, creation,
+ &ei_default_socket_callbacks,
+ sizeof(ei_default_socket_callbacks),
+ NULL);
+}
/*
* Initialize by set: thishostname, thisalivename,
* thisnodename and thisipaddr. At success return 0,
* otherwise return -1.
*/
-int ei_connect_init(ei_cnode* ec, const char* this_node_name,
- const char *cookie, short creation)
+int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
+ const char *cookie, short creation,
+ ei_socket_callbacks *cbs, int cbs_sz,
+ void *setup_context)
{
char thishostname[EI_MAXHOSTNAMELEN+1];
char thisnodename[MAXNODELEN+1];
@@ -500,12 +726,7 @@ int ei_connect_init(ei_cnode* ec, const char* this_node_name,
return ERL_ERROR;
}
#endif /* win32 */
-#ifdef _REENTRANT
- if (ei_sockets_lock == NULL) {
- ei_sockets_lock = ei_mutex_create();
- }
-#endif /* _REENTRANT */
-
+
/* gethostname requires len to be max(hostname) + 1 */
if (gethostname(thishostname, EI_MAXHOSTNAMELEN+1) == -1) {
#ifdef __WIN32__
@@ -561,43 +782,22 @@ int ei_connect_init(ei_cnode* ec, const char* this_node_name,
sprintf(thisnodename, "%s@%s", this_node_name, hp->h_name);
}
}
- res = ei_connect_xinit(ec, thishostname, thisalivename, thisnodename,
- (struct in_addr *)*hp->h_addr_list, cookie, creation);
+ res = ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
+ (struct in_addr *)*hp->h_addr_list, cookie, creation,
+ cbs, cbs_sz, setup_context);
if (buf != buffer)
free(buf);
return res;
}
-
-/* connects to port at ip-address ip_addr
-* and returns fd to socket
-* port has to be in host byte order
-*/
-static int cnct(uint16 port, struct in_addr *ip_addr, int addr_len, unsigned ms)
+int ei_connect_init(ei_cnode* ec, const char* this_node_name,
+ const char *cookie, short creation)
{
- int s, res;
- struct sockaddr_in iserv_addr;
-
- if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- erl_errno = errno;
- return ERL_ERROR;
- }
-
- memset((char*)&iserv_addr, 0, sizeof(struct sockaddr_in));
- memcpy((char*)&iserv_addr.sin_addr, (char*)ip_addr, addr_len);
- iserv_addr.sin_family = AF_INET;
- iserv_addr.sin_port = htons(port);
-
- if ((res = ei_connect_t(s, (struct sockaddr*)&iserv_addr,
- sizeof(iserv_addr),ms)) < 0) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- closesocket(s);
- return ERL_ERROR;
- }
-
- return s;
-} /* cnct */
-
+ return ei_connect_init_ussi(ec, this_node_name, cookie, creation,
+ &ei_default_socket_callbacks,
+ sizeof(ei_default_socket_callbacks),
+ NULL);
+}
/*
* Same as ei_gethostbyname_r, but also handles ERANGE error
@@ -758,91 +958,218 @@ int ei_connect(ei_cnode* ec, char *nodename)
* the node through epmd at that host
*
*/
-int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned ms)
+int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned ms)
{
- struct in_addr *ip_addr=(struct in_addr *) adr;
+ ei_socket_callbacks *cbs = ec->cbs;
+ void *ctx;
int rport = 0; /*uint16 rport = 0;*/
int sockd;
- int one = 1;
int dist = 0;
- ErlConnect her_name;
unsigned her_flags, her_version;
-
+ unsigned our_challenge, her_challenge;
+ unsigned char our_digest[16];
+ int err;
+ int pkt_sz;
+ struct sockaddr_in addr;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
erl_errno = EIO; /* Default error code */
EI_TRACE_CONN1("ei_xconnect","-> CONNECT attempt to connect to %s",
alivename);
- if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, ms)) < 0) {
+ if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, tmo)) < 0) {
EI_TRACE_ERR0("ei_xconnect","-> CONNECT can't get remote port");
/* ei_epmd_port_tmo() has set erl_errno */
return ERL_NO_PORT;
}
-
- /* we now have port number to enode, try to connect */
- if((sockd = cnct((uint16)rport, ip_addr, sizeof(struct in_addr),ms)) < 0) {
- EI_TRACE_ERR0("ei_xconnect","-> CONNECT socket connect failed");
- /* cnct() has set erl_errno */
- return ERL_CONNECT_FAIL;
- }
-
- EI_TRACE_CONN0("ei_xconnect","-> CONNECT connected to remote");
- /* FIXME why connect before checking 'dist' output from ei_epmd_port() ?! */
if (dist <= 4) {
EI_TRACE_ERR0("ei_xconnect","-> CONNECT remote version not compatible");
- goto error;
+ return ERL_ERROR;
}
- else {
- unsigned our_challenge, her_challenge;
- unsigned char our_digest[16];
-
- if (send_name(sockd, ec->thisnodename, (unsigned) dist, ms))
- goto error;
- if (recv_status(sockd, ms))
- goto error;
- if (recv_challenge(sockd, &her_challenge, &her_version,
- &her_flags, &her_name, ms))
- goto error;
- our_challenge = gen_challenge();
- gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
- if (send_challenge_reply(sockd, our_digest, our_challenge, ms))
- goto error;
- if (recv_challenge_ack(sockd, our_challenge,
- ec->ei_connect_cookie, ms))
- goto error;
- put_ei_socket_info(sockd, dist, null_cookie, ec); /* FIXME check == 0 */
+
+ err = ei_socket_ctx__(cbs, &ctx, ec->setup_context);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> SOCKET failed: %s (%d)",
+ estr(err), err);
+ erl_errno = err;
+ return ERL_CONNECT_FAIL;
+ }
+
+ memset((void *) &addr, 0, sizeof(struct sockaddr_in));
+ memcpy((void *) &addr.sin_addr, (void *) ip_addr, sizeof(addr.sin_addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(rport);
+
+ err = ei_connect_ctx_t__(cbs, ctx, (void *) &addr, sizeof(addr), tmo);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> CONNECT socket connect failed: %s (%d)",
+ estr(err), err);
+ abort_connection(cbs, ctx);
+ erl_errno = err;
+ return ERL_CONNECT_FAIL;
}
- setsockopt(sockd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
- setsockopt(sockd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one));
+ EI_TRACE_CONN0("ei_xconnect","-> CONNECT connected to remote");
- EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename);
+ err = EI_GET_FD__(cbs, ctx, &sockd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ goto error;
+ }
+
+ err = cbs->handshake_packet_header_size(ctx, &pkt_sz);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ goto error;
+ }
+
+ if (send_name(cbs, ctx, pkt_sz, ec->thisnodename, (unsigned) dist, tmo))
+ goto error;
+ if (recv_status(cbs, ctx, pkt_sz, tmo))
+ goto error;
+ if (recv_challenge(cbs, ctx, pkt_sz, &her_challenge,
+ &her_version, &her_flags, NULL, tmo))
+ goto error;
+ our_challenge = gen_challenge();
+ gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
+ if (send_challenge_reply(cbs, ctx, pkt_sz, our_digest, our_challenge, tmo))
+ goto error;
+ if (recv_challenge_ack(cbs, ctx, pkt_sz, our_challenge,
+ ec->ei_connect_cookie, tmo))
+ goto error;
+ if (put_ei_socket_info(sockd, dist, null_cookie, ec, cbs, ctx) != 0)
+ goto error;
+
+ if (cbs->connect_handshake_complete) {
+ err = cbs->connect_handshake_complete(ctx);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> CONNECT failed: %s (%d)",
+ estr(err), err);
+ close_connection(cbs, ctx, sockd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+ }
+ EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename);
+
erl_errno = 0;
return sockd;
error:
EI_TRACE_ERR0("ei_xconnect","-> CONNECT failed");
- closesocket(sockd);
+ abort_connection(cbs, ctx);
return ERL_ERROR;
} /* ei_xconnect */
-int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)
+int ei_xconnect(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename)
{
- return ei_xconnect_tmo(ec, adr, alivename, 0);
+ return ei_xconnect_tmo(ec, ip_addr, alivename, 0);
}
+int ei_listen(ei_cnode *ec, int *port, int backlog)
+{
+ struct in_addr ip_addr;
+ ip_addr.s_addr = htonl(INADDR_ANY);
+ return ei_xlisten(ec, &ip_addr, port, backlog);
+}
+
+int ei_xlisten(ei_cnode *ec, struct in_addr *ip_addr, int *port, int backlog)
+{
+ ei_socket_callbacks *cbs = ec->cbs;
+ struct sockaddr_in sock_addr;
+ void *ctx;
+ int fd, err, len;
+
+ err = ei_socket_ctx__(cbs, &ctx, ec->setup_context);
+ if (err) {
+ EI_TRACE_ERR2("ei_xlisten","-> SOCKET failed: %s (%d)",
+ estr(err), err);
+ erl_errno = err;
+ return ERL_ERROR;
+ }
+
+ memset((void *) &sock_addr, 0, sizeof(struct sockaddr_in));
+ memcpy((void *) &sock_addr.sin_addr, (void *) ip_addr, sizeof(*ip_addr));
+ sock_addr.sin_family = AF_INET;
+ sock_addr.sin_port = htons((short) *port);
+
+ len = sizeof(sock_addr);
+ err = ei_listen_ctx__(cbs, ctx, (void *) &sock_addr, &len, backlog);
+ if (err) {
+ EI_TRACE_ERR2("ei_xlisten","-> listen failed: %s (%d)",
+ estr(err), err);
+ erl_errno = err;
+ goto error;
+ }
+
+ if (len != sizeof(sock_addr)) {
+ if (len < offsetof(struct sockaddr_in, sin_addr) + sizeof(sock_addr.sin_addr)
+ || len < offsetof(struct sockaddr_in, sin_port) + sizeof(sock_addr.sin_port)) {
+ erl_errno = EIO;
+ EI_TRACE_ERR0("ei_xlisten","-> get info failed");
+ goto error;
+ }
+ }
+
+ memcpy((void *) ip_addr, (void *) &sock_addr.sin_addr, sizeof(*ip_addr));
+ *port = (int) ntohs(sock_addr.sin_port);
+
+ err = EI_GET_FD__(cbs, ctx, &fd);
+ if (err) {
+ erl_errno = err;
+ goto error;
+ }
+
+ if (put_ei_socket_info(fd, 0, null_cookie, ec, cbs, ctx) != 0) {
+ EI_TRACE_ERR0("ei_xlisten","-> save socket info failed");
+ erl_errno = EIO;
+ goto error;
+ }
+
+ erl_errno = 0;
+
+ return fd;
+
+error:
+ abort_connection(cbs, ctx);
+ return ERL_ERROR;
+}
+
+static int close_connection(ei_socket_callbacks *cbs, void *ctx, int fd)
+{
+ int err;
+ remove_ei_socket_info(fd);
+ err = ei_close_ctx__(cbs, ctx);
+ if (err) {
+ erl_errno = err;
+ return ERL_ERROR;
+ }
+ return 0;
+}
- /*
- * For symmetry reasons
-*/
-#if 0
int ei_close_connection(int fd)
{
- return closesocket(fd);
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ int err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err)
+ erl_errno = err;
+ else {
+ if (close_connection(cbs, ctx, fd) == 0)
+ return 0;
+ }
+ EI_TRACE_ERR2("ei_close_connection","<- CLOSE socket close failed: %s (%d)",
+ estr(erl_errno), erl_errno);
+ return ERL_ERROR;
} /* ei_close_connection */
-#endif
+
+static void abort_connection(ei_socket_callbacks *cbs, void *ctx)
+{
+ (void) ei_close_ctx__(cbs, ctx);
+}
/*
* Accept and initiate a connection from another
@@ -857,25 +1184,71 @@ int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp)
int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
{
int fd;
- struct sockaddr_in cli_addr;
- int cli_addr_len=sizeof(struct sockaddr_in);
unsigned her_version, her_flags;
- ErlConnect her_name;
+ char tmp_nodename[MAXNODELEN+1];
+ char *her_name;
+ int pkt_sz, err;
+ struct sockaddr_in addr;
+ int addr_len = sizeof(struct sockaddr_in);
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
erl_errno = EIO; /* Default error code */
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, lfd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+
EI_TRACE_CONN0("ei_accept","<- ACCEPT waiting for connection");
+
+ if (conp) {
+ her_name = &conp->nodename[0];
+ }
+ else {
+ her_name = &tmp_nodename[0];
+ }
- if ((fd = ei_accept_t(lfd, (struct sockaddr*) &cli_addr,
- &cli_addr_len, ms )) < 0) {
- EI_TRACE_ERR0("ei_accept","<- ACCEPT socket accept failed");
- erl_errno = (fd == -2) ? ETIMEDOUT : EIO;
- goto error;
+ /*
+ * ei_accept_ctx_t__() replaces the pointer to the listen context
+ * with a pointer to the accepted connection context on success.
+ */
+ err = ei_accept_ctx_t__(cbs, &ctx, (void *) &addr, &addr_len, tmo);
+ if (err) {
+ EI_TRACE_ERR2("ei_accept","<- ACCEPT socket accept failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+
+ err = EI_GET_FD__(cbs, ctx, &fd);
+ if (err) {
+ EI_TRACE_ERR2("ei_accept","<- ACCEPT get fd failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
+ }
+
+ if (addr_len != sizeof(struct sockaddr_in)) {
+ if (addr_len < (offsetof(struct sockaddr_in, sin_addr)
+ + sizeof(addr.sin_addr))) {
+ EI_TRACE_ERR0("ei_accept","<- ACCEPT get addr failed");
+ goto error;
+ }
+ }
+
+ err = cbs->handshake_packet_header_size(ctx, &pkt_sz);
+ if (err) {
+ EI_TRACE_ERR2("ei_accept","<- ACCEPT get packet size failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
}
EI_TRACE_CONN0("ei_accept","<- ACCEPT connected to remote");
- if (recv_name(fd, &her_version, &her_flags, &her_name, ms)) {
+ if (recv_name(cbs, ctx, pkt_sz, &her_version, &her_flags, her_name, tmo)) {
EI_TRACE_ERR0("ei_accept","<- ACCEPT initial ident failed");
goto error;
}
@@ -888,34 +1261,46 @@ int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms)
unsigned our_challenge;
unsigned her_challenge;
unsigned char our_digest[16];
-
- if (send_status(fd,"ok", ms))
+
+ if (send_status(cbs, ctx, pkt_sz, "ok", tmo))
goto error;
our_challenge = gen_challenge();
- if (send_challenge(fd, ec->thisnodename,
- our_challenge, her_version, ms))
+ if (send_challenge(cbs, ctx, pkt_sz, ec->thisnodename,
+ our_challenge, her_version, tmo))
goto error;
- if (recv_challenge_reply(fd, our_challenge,
- ec->ei_connect_cookie,
- &her_challenge, ms))
+ if (recv_challenge_reply(cbs, ctx, pkt_sz, our_challenge,
+ ec->ei_connect_cookie, &her_challenge, tmo))
goto error;
gen_digest(her_challenge, ec->ei_connect_cookie, our_digest);
- if (send_challenge_ack(fd, our_digest, ms))
+ if (send_challenge_ack(cbs, ctx, pkt_sz, our_digest, tmo))
goto error;
- put_ei_socket_info(fd, her_version, null_cookie, ec);
+ if (put_ei_socket_info(fd, her_version, null_cookie, ec, cbs, ctx) != 0)
+ goto error;
+ }
+ if (conp) {
+ memcpy((void *) conp->ipadr, (void *) &addr.sin_addr, sizeof(conp->ipadr));
+ strcpy(&conp->nodename[0], her_name);
+ }
+
+ if (cbs->accept_handshake_complete) {
+ err = cbs->accept_handshake_complete(ctx);
+ if (err) {
+ EI_TRACE_ERR2("ei_xconnect","-> ACCEPT handshake failed: %s (%d)",
+ estr(err), err);
+ close_connection(cbs, ctx, fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
}
- if (conp)
- *conp = her_name;
- EI_TRACE_CONN1("ei_accept","<- ACCEPT (ok) remote = %s",her_name.nodename);
+ EI_TRACE_CONN1("ei_accept","<- ACCEPT (ok) remote = %s",her_name);
erl_errno = 0; /* No error */
return fd;
error:
EI_TRACE_ERR0("ei_accept","<- ACCEPT failed");
- if (fd>=0)
- closesocket(fd);
+ abort_connection(cbs, ctx);
return ERL_ERROR;
} /* ei_accept */
@@ -927,36 +1312,57 @@ error:
*/
int ei_receive_tmo(int fd, unsigned char *bufp, int bufsize, unsigned ms)
{
- int len;
+ ssize_t len;
unsigned char fourbyte[4]={0,0,0,0};
- int res;
-
- if ((res = ei_read_fill_t(fd, (char *) bufp, 4, ms)) != 4) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ int err;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+
+ len = (ssize_t) 4;
+ err = ei_read_fill_ctx_t__(cbs, ctx, (char *) bufp, &len, tmo);
+ if (!err && len != (ssize_t) 4)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return ERL_ERROR;
}
/* Tick handling */
- if ((len = get_int32(bufp)) == ERL_TICK)
- {
- ei_write_fill_t(fd, (char *) fourbyte, 4, ms);
+ len = get_int32(bufp);
+ if (len == ERL_TICK) {
+ len = 4;
+ ei_write_fill_ctx_t__(cbs, ctx, (char *) fourbyte, &len, tmo);
/* FIXME ok to ignore error or timeout? */
erl_errno = EAGAIN;
return ERL_TICK;
}
- else if (len > bufsize)
- {
+
+ if (len > bufsize) {
/* FIXME: We should drain the message. */
erl_errno = EMSGSIZE;
return ERL_ERROR;
}
- else if ((res = ei_read_fill_t(fd, (char *) bufp, len, ms)) != len)
- {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return ERL_ERROR;
+ else {
+ ssize_t need = len;
+ err = ei_read_fill_ctx_t__(cbs, ctx, (char *) bufp, &len, tmo);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
+ if (len != need) {
+ erl_errno = EIO;
+ return ERL_ERROR;
+ }
}
- return len;
+ return (int) len;
}
@@ -1112,36 +1518,11 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun,
int ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg,
ei_x_buff *x)
{
- fd_set readmask;
- struct timeval tv;
- struct timeval *t = NULL;
-
- if (timeout >= 0) {
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
- t = &tv;
- }
-
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
-
- switch (select(fd+1, &readmask, NULL, NULL, t)) {
- case -1:
- erl_errno = EIO;
- return ERL_ERROR;
-
- case 0:
- erl_errno = ETIMEDOUT;
- return ERL_TIMEOUT;
-
- default:
- if (FD_ISSET(fd, &readmask)) {
- return ei_xreceive_msg(fd, msg, x);
- } else {
- erl_errno = EIO;
- return ERL_ERROR;
- }
- }
+ unsigned tmo = timeout < 0 ? EI_SCLBK_INF_TMO : (unsigned) timeout;
+ int res = ei_xreceive_msg_tmo(fd, msg, x, tmo);
+ if (res < 0 && erl_errno == ETIMEDOUT)
+ return ERL_TIMEOUT;
+ return res;
} /* rpc_from */
/*
@@ -1295,19 +1676,34 @@ static char *hex(char digest[16], char buff[33])
return buff;
}
-static int read_2byte_package(int fd, char **buf, int *buflen,
- int *is_static, unsigned ms)
+static int read_hs_package(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, char **buf, int *buflen,
+ int *is_static, unsigned ms)
{
- unsigned char nbuf[2];
+ unsigned char nbuf[4];
unsigned char *x = nbuf;
- unsigned len;
- int res;
-
- if((res = ei_read_fill_t(fd, (char *)nbuf, 2, ms)) != 2) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ ssize_t len, need;
+ int err;
+
+ len = (ssize_t) pkt_sz;
+ err = ei_read_fill_ctx_t__(cbs, ctx, (char *)nbuf, &len, ms);
+ if (!err && len != (ssize_t) pkt_sz)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
- len = get16be(x);
+
+ switch (pkt_sz) {
+ case 2:
+ len = get16be(x);
+ break;
+ case 4:
+ len = get32be(x);
+ break;
+ default:
+ return -1;
+ }
if (len > *buflen) {
if (*is_static) {
@@ -1329,20 +1725,26 @@ static int read_2byte_package(int fd, char **buf, int *buflen,
*buflen = len;
}
}
- if ((res = ei_read_fill_t(fd, *buf, len, ms)) != len) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ need = len;
+ err = ei_read_fill_ctx_t__(cbs, ctx, *buf, &len, ms);
+ if (!err && len != need)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
return len;
}
-static int send_status(int fd, char *status, unsigned ms)
+static int send_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, char *status, unsigned ms)
{
char *buf, *s;
char dbuf[DEFBUF_SIZ];
- int siz = strlen(status) + 1 + 2;
- int res;
+ int siz = strlen(status) + 1 + pkt_sz;
+ int err;
+ ssize_t len;
buf = (siz > DEFBUF_SIZ) ? malloc(siz) : dbuf;
if (!buf) {
@@ -1350,14 +1752,28 @@ static int send_status(int fd, char *status, unsigned ms)
return -1;
}
s = buf;
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 's');
memcpy(s, status, strlen(status));
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
- EI_TRACE_ERR0("send_status","-> SEND_STATUS socket write failed");
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR2("send_status","-> SEND_STATUS socket write failed: %s (%d)",
+ estr(err), err);
if (buf != dbuf)
- free(buf);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ free(buf);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
EI_TRACE_CONN1("send_status","-> SEND_STATUS (%s)",status);
@@ -1367,7 +1783,8 @@ static int send_status(int fd, char *status, unsigned ms)
return 0;
}
-static int recv_status(int fd, unsigned ms)
+static int recv_status(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1375,7 +1792,8 @@ static int recv_status(int fd, unsigned ms)
int buflen = DEFBUF_SIZ;
int rlen;
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz,
+ &buf, &buflen, &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_status",
"<- RECV_STATUS socket read failed (%d)", rlen);
goto error;
@@ -1396,7 +1814,10 @@ error:
return -1;
}
-static int send_name_or_challenge(int fd, char *nodename,
+static int send_name_or_challenge(ei_socket_callbacks *cbs,
+ void *ctx,
+ int pkt_sz,
+ char *nodename,
int f_chall,
unsigned challenge,
unsigned version,
@@ -1405,9 +1826,10 @@ static int send_name_or_challenge(int fd, char *nodename,
char *buf;
unsigned char *s;
char dbuf[DEFBUF_SIZ];
- int siz = 2 + 1 + 2 + 4 + strlen(nodename);
+ int siz = pkt_sz + 1 + 2 + 4 + strlen(nodename);
const char* function[] = {"SEND_NAME", "SEND_CHALLENGE"};
- int res;
+ int err;
+ ssize_t len;
if (f_chall)
siz += 4;
@@ -1417,7 +1839,16 @@ static int send_name_or_challenge(int fd, char *nodename,
return -1;
}
s = (unsigned char *)buf;
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 'n');
put16be(s, version);
put32be(s, (DFLAG_EXTENDED_REFERENCES
@@ -1433,13 +1864,16 @@ static int send_name_or_challenge(int fd, char *nodename,
if (f_chall)
put32be(s, challenge);
memcpy(s, nodename, strlen(nodename));
-
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
EI_TRACE_ERR1("send_name_or_challenge",
"-> %s socket write failed", function[f_chall]);
if (buf != dbuf)
free(buf);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -1448,9 +1882,9 @@ static int send_name_or_challenge(int fd, char *nodename,
return 0;
}
-static int recv_challenge(int fd, unsigned *challenge,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms)
+static int recv_challenge(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned *challenge, unsigned *version,
+ unsigned *flags, char *namebuf, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1458,13 +1892,13 @@ static int recv_challenge(int fd, unsigned *challenge,
int buflen = DEFBUF_SIZ;
int rlen;
char *s;
- struct sockaddr_in sin;
- socklen_t sin_len = sizeof(sin);
char tag;
-
+ char tmp_nodename[MAXNODELEN+1];
+
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen,
+ &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_challenge",
"<- RECV_CHALLENGE socket read failed (%d)",rlen);
goto error;
@@ -1505,22 +1939,19 @@ static int recv_challenge(int fd, unsigned *challenge,
goto error;
}
- if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) {
- EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE can't get peername");
- erl_errno = errno;
- goto error;
- }
- memcpy(namebuf->ipadr, &(sin.sin_addr.s_addr),
- sizeof(sin.sin_addr.s_addr));
- memcpy(namebuf->nodename, s, rlen - 11);
- namebuf->nodename[rlen - 11] = '\0';
+ if (!namebuf)
+ namebuf = &tmp_nodename[0];
+
+ memcpy(namebuf, s, rlen - 11);
+ namebuf[rlen - 11] = '\0';
+
if (!is_static)
free(buf);
EI_TRACE_CONN4("recv_challenge","<- RECV_CHALLENGE (ok) node = %s, "
"version = %u, "
"flags = %u, "
"challenge = %d",
- namebuf->nodename,
+ namebuf,
*version,
*flags,
*challenge
@@ -1533,24 +1964,40 @@ error:
return -1;
}
-static int send_challenge_reply(int fd, unsigned char digest[16],
+static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned char digest[16],
unsigned challenge, unsigned ms)
{
char *s;
char buf[DEFBUF_SIZ];
- int siz = 2 + 1 + 4 + 16;
- int res;
+ int siz = pkt_sz + 1 + 4 + 16;
+ int err;
+ ssize_t len;
s = buf;
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 'r');
put32be(s, challenge);
memcpy(s, digest, 16);
-
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
- EI_TRACE_ERR0("send_challenge_reply",
- "-> SEND_CHALLENGE_REPLY socket write failed");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR2("send_challenge_reply",
+ "-> SEND_CHALLENGE_REPLY socket write failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -1563,11 +2010,13 @@ static int send_challenge_reply(int fd, unsigned char digest[16],
return 0;
}
-static int recv_challenge_reply (int fd,
- unsigned our_challenge,
- char cookie[],
- unsigned *her_challenge,
- unsigned ms)
+static int recv_challenge_reply(ei_socket_callbacks *cbs,
+ void *ctx,
+ int pkt_sz,
+ unsigned our_challenge,
+ char cookie[],
+ unsigned *her_challenge,
+ unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1580,7 +2029,7 @@ static int recv_challenge_reply (int fd,
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) != 21) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 21) {
EI_TRACE_ERR1("recv_challenge_reply",
"<- RECV_CHALLENGE_REPLY socket read failed (%d)",rlen);
goto error;
@@ -1620,23 +2069,38 @@ error:
return -1;
}
-static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms)
+static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ unsigned char digest[16], unsigned ms)
{
char *s;
char buf[DEFBUF_SIZ];
- int siz = 2 + 1 + 16;
- int res;
+ int siz = pkt_sz + 1 + 16;
+ int err;
+ ssize_t len;
s = buf;
-
- put16be(s,siz - 2);
+ switch (pkt_sz) {
+ case 2:
+ put16be(s,siz - 2);
+ break;
+ case 4:
+ put32be(s,siz - 4);
+ break;
+ default:
+ return -1;
+ }
put8(s, 'a');
memcpy(s, digest, 16);
- if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) {
- EI_TRACE_ERR0("recv_challenge_reply",
- "-> SEND_CHALLENGE_ACK socket write failed");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ len = (ssize_t) siz;
+ err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms);
+ if (!err && len != (ssize_t) siz)
+ err = EIO;
+ if (err) {
+ EI_TRACE_ERR2("recv_challenge_reply",
+ "-> SEND_CHALLENGE_ACK socket write failed: %s (%d)",
+ estr(err), err);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -1649,8 +2113,8 @@ static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms)
return 0;
}
-static int recv_challenge_ack(int fd,
- unsigned our_challenge,
+static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned our_challenge,
char cookie[], unsigned ms)
{
char dbuf[DEFBUF_SIZ];
@@ -1664,7 +2128,7 @@ static int recv_challenge_ack(int fd,
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) != 17) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 17) {
EI_TRACE_ERR1("recv_challenge_ack",
"<- RECV_CHALLENGE_ACK socket read failed (%d)",rlen);
goto error;
@@ -1701,20 +2165,24 @@ error:
return -1;
}
-static int send_name(int fd, char *nodename, unsigned version, unsigned ms)
+static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned version, unsigned ms)
{
- return send_name_or_challenge(fd, nodename, 0, 0, version, ms);
+ return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 0,
+ 0, version, ms);
}
-static int send_challenge(int fd, char *nodename,
- unsigned challenge, unsigned version, unsigned ms)
+static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz,
+ char *nodename, unsigned challenge, unsigned version,
+ unsigned ms)
{
- return send_name_or_challenge(fd, nodename, 1, challenge, version, ms);
+ return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 1,
+ challenge, version, ms);
}
-static int recv_name(int fd,
- unsigned *version,
- unsigned *flags, ErlConnect *namebuf, unsigned ms)
+static int recv_name(ei_socket_callbacks *cbs, void *ctx,
+ int pkt_sz, unsigned *version,
+ unsigned *flags, char *namebuf, unsigned ms)
{
char dbuf[DEFBUF_SIZ];
char *buf = dbuf;
@@ -1722,13 +2190,13 @@ static int recv_name(int fd,
int buflen = DEFBUF_SIZ;
int rlen;
char *s;
- struct sockaddr_in sin;
- socklen_t sin_len = sizeof(sin);
+ char tmp_nodename[MAXNODELEN+1];
char tag;
erl_errno = EIO; /* Default */
- if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) {
+ if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen,
+ &is_static, ms)) <= 0) {
EI_TRACE_ERR1("recv_name","<- RECV_NAME socket read failed (%d)",rlen);
goto error;
}
@@ -1759,21 +2227,18 @@ static int recv_name(int fd,
erl_errno = EIO;
goto error;
}
-
- if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) {
- EI_TRACE_ERR0("recv_name","<- RECV_NAME can't get peername");
- erl_errno = errno;
- goto error;
- }
- memcpy(namebuf->ipadr, &(sin.sin_addr.s_addr),
- sizeof(sin.sin_addr.s_addr));
- memcpy(namebuf->nodename, s, rlen - 7);
- namebuf->nodename[rlen - 7] = '\0';
+
+ if (!namebuf)
+ namebuf = &tmp_nodename[0];
+
+ memcpy(namebuf, s, rlen - 7);
+ namebuf[rlen - 7] = '\0';
+
if (!is_static)
free(buf);
EI_TRACE_CONN3("recv_name",
"<- RECV_NAME (ok) node = %s, version = %u, flags = %u",
- namebuf->nodename,*version,*flags);
+ namebuf,*version,*flags);
erl_errno = 0;
return 0;
@@ -1867,3 +2332,4 @@ static int get_cookie(char *buf, int bufsize)
return 1; /* Success! */
}
+
diff --git a/lib/erl_interface/src/connect/eirecv.c b/lib/erl_interface/src/connect/eirecv.c
index 7b9dbfc387..47eea06ced 100644
--- a/lib/erl_interface/src/connect/eirecv.c
+++ b/lib/erl_interface/src/connect/eirecv.c
@@ -60,22 +60,36 @@ ei_recv_internal (int fd,
int arity;
int version;
int index = 0;
- int i = 0;
- int res;
+ int err;
int show_this_msg = 0;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ ssize_t rlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
/* get length field */
- if ((res = ei_read_fill_t(fd, header, 4, ms)) != 4)
- {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
+ rlen = 4;
+ err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
+ if (!err && rlen != 4)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
+
len = get32be(s);
/* got tick - respond and return */
if (!len) {
char tock[] = {0,0,0,0};
- ei_write_fill_t(fd, tock, sizeof(tock), ms); /* Failure no problem */
+ 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] */
}
@@ -86,9 +100,12 @@ ei_recv_internal (int fd,
ei_trace(-1,NULL);
/* read enough to get at least entire header */
- bytesread = (len > EIRECVBUF ? EIRECVBUF : len);
- if ((i = ei_read_fill_t(fd,header,bytesread,ms)) != bytesread) {
- erl_errno = (i == -2) ? ETIMEDOUT : EIO;
+ rlen = bytesread = (len > EIRECVBUF ? EIRECVBUF : len);
+ err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
+ if (!err && rlen != bytesread)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
@@ -212,12 +229,17 @@ ei_recv_internal (int fd,
*/
if (msglen > *bufsz) {
if (staticbufp) {
- int sz = EIRECVBUF;
/* flush in rest of packet */
while (remain > 0) {
- if (remain < sz) sz = remain;
- if ((i=ei_read_fill_t(fd,header,sz,ms)) <= 0) break;
- remain -= i;
+ rlen = remain > EIRECVBUF ? EIRECVBUF : remain;
+ err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+ if (rlen == 0)
+ break;
+ remain -= rlen;
}
erl_errno = EMSGSIZE;
return -1;
@@ -247,11 +269,15 @@ ei_recv_internal (int fd,
/* read the rest of the message into callers buffer */
if (remain > 0) {
- if ((i = ei_read_fill_t(fd,mbuf+bytesread-index,remain,ms)) != remain) {
- *msglenp = bytesread-index+1; /* actual bytes in users buffer */
- erl_errno = (i == -2) ? ETIMEDOUT : EIO;
- return -1;
- }
+ rlen = remain;
+ err = ei_read_fill_ctx_t__(cbs, ctx, mbuf+bytesread-index, &rlen, tmo);
+ if (!err && rlen != remain)
+ err = EIO;
+ if (err) {
+ *msglenp = bytesread-index+1; /* actual bytes in users buffer */
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
}
if (show_this_msg)
diff --git a/lib/erl_interface/src/connect/send.c b/lib/erl_interface/src/connect/send.c
index 37d7db6d68..d97532d123 100644
--- a/lib/erl_interface/src/connect/send.c
+++ b/lib/erl_interface/src/connect/send.c
@@ -58,10 +58,17 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
char *s, header[1200]; /* see size calculation below */
erlang_trace *token = NULL;
int index = 5; /* reserve 5 bytes for control message */
- int res;
-#ifdef HAVE_WRITEV
- struct iovec v[2];
-#endif
+ int err;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ ssize_t len, tot_len;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
/* are we tracing? */
/* check that he can receive trace tokens first */
@@ -91,30 +98,47 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to,
if (ei_tracelevel >= 4)
ei_show_sendmsg(stderr,header,msg);
-#ifdef HAVE_WRITEV
-
- v[0].iov_base = (char *)header;
- v[0].iov_len = index;
- v[1].iov_base = (char *)msg;
- v[1].iov_len = msglen;
-
- if ((res = ei_writev_fill_t(fd,v,2,ms)) != index+msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
- }
-
-#else /* !HAVE_WRITEV */
-
- if ((res = ei_write_fill_t(fd,header,index,ms)) != index) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+
+#ifdef EI_HAVE_STRUCT_IOVEC__
+ if (ei_socket_callbacks_have_writev__(cbs)) {
+ struct iovec v[2];
+
+ v[0].iov_base = (char *)header;
+ v[0].iov_len = index;
+ v[1].iov_base = (char *)msg;
+ v[1].iov_len = msglen;
+
+ len = tot_len = (ssize_t) index+msglen;
+ err = ei_writev_fill_ctx_t__(cbs, ctx, v, 2, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+
+ return 0;
}
- if ((res = ei_write_fill_t(fd,msg,msglen,ms)) != msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+#endif /* EI_HAVE_STRUCT_IOVEC__ */
+
+ /* no writev() */
+ len = tot_len = (ssize_t) index;
+ err = ei_write_fill_ctx_t__(cbs, ctx, header, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
-#endif /* !HAVE_WRITEV */
+ len = tot_len = (ssize_t) msglen;
+ err = ei_write_fill_ctx_t__(cbs, ctx, msg, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_exit.c b/lib/erl_interface/src/connect/send_exit.c
index 2e298e3221..b4f7e14c7f 100644
--- a/lib/erl_interface/src/connect/send_exit.c
+++ b/lib/erl_interface/src/connect/send_exit.c
@@ -55,6 +55,17 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
char *s;
int index = 0;
int len = strlen(reason) + 1080; /* see below */
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ int err;
+ ssize_t wlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
if (len > EISMALLBUF)
if (!(dbuf = malloc(len)))
@@ -92,10 +103,16 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to,
if (ei_tracelevel >= 4)
ei_show_sendmsg(stderr,msgbuf,NULL);
- ei_write_fill_t(fd,msgbuf,index,ms);
- /* FIXME ignore timeout etc? erl_errno?! */
-
- if (dbuf) free(dbuf);
+ wlen = (ssize_t) index;
+ err = ei_write_fill_ctx_t__(cbs, ctx, msgbuf, &wlen, tmo);
+ if (!err && wlen != (ssize_t) index)
+ err = EIO;
+ if (dbuf)
+ free(dbuf);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
return 0;
}
diff --git a/lib/erl_interface/src/connect/send_reg.c b/lib/erl_interface/src/connect/send_reg.c
index 62478f042d..80d61e57b5 100644
--- a/lib/erl_interface/src/connect/send_reg.c
+++ b/lib/erl_interface/src/connect/send_reg.c
@@ -51,11 +51,17 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
char *s, header[1400]; /* see size calculation below */
erlang_trace *token = NULL;
int index = 5; /* reserve 5 bytes for control message */
- int res;
+ int err;
+ ei_socket_callbacks *cbs;
+ void *ctx;
+ ssize_t len, tot_len;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
-#ifdef HAVE_WRITEV
- struct iovec v[2];
-#endif
+ err = EI_GET_CBS_CTX__(&cbs, &ctx, fd);
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return ERL_ERROR;
+ }
/* are we tracing? */
/* check that he can receive trace tokens first */
@@ -86,29 +92,45 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from,
if (ei_tracelevel >= 4)
ei_show_sendmsg(stderr,header,msg);
-#ifdef HAVE_WRITEV
+#ifdef EI_HAVE_STRUCT_IOVEC__
+ if (ei_socket_callbacks_have_writev__(cbs)) {
+ struct iovec v[2];
- v[0].iov_base = (char *)header;
- v[0].iov_len = index;
- v[1].iov_base = (char *)msg;
- v[1].iov_len = msglen;
+ v[0].iov_base = (char *)header;
+ v[0].iov_len = index;
+ v[1].iov_base = (char *)msg;
+ v[1].iov_len = msglen;
- if ((res = ei_writev_fill_t(fd,v,2,ms)) != index+msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ len = tot_len = (ssize_t) index+msglen;
+ err = ei_writev_fill_ctx_t__(cbs, ctx, v, 2, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
+ }
+ return 0;
}
-#else
-
+#endif /* EI_HAVE_STRUCT_IOVEC__ */
+
/* no writev() */
- if ((res = ei_write_fill_t(fd,header,index,ms)) != index) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ len = tot_len = (ssize_t) index;
+ err = ei_write_fill_ctx_t__(cbs, ctx, header, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
- if ((res = ei_write_fill_t(fd,msg,msglen,ms)) != msglen) {
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+
+ len = tot_len = (ssize_t) msglen;
+ err = ei_write_fill_ctx_t__(cbs, ctx, msg, &len, tmo);
+ if (!err && len != tot_len)
+ err = EIO;
+ if (err) {
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
-#endif
return 0;
}
diff --git a/lib/erl_interface/src/epmd/epmd_port.c b/lib/erl_interface/src/epmd/epmd_port.c
index 2ec418b24a..492c3fb3aa 100644
--- a/lib/erl_interface/src/epmd/epmd_port.c
+++ b/lib/erl_interface/src/epmd/epmd_port.c
@@ -62,31 +62,38 @@
int ei_epmd_connect_tmo(struct in_addr *inaddr, unsigned ms)
{
static unsigned int epmd_port = 0;
- struct sockaddr_in saddr;
- int sd;
- int res;
+ int port, sd, err;
+ struct in_addr ip_addr;
+ struct sockaddr_in addr;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
+ err = ei_socket__(&sd);
+ if (err) {
+ erl_errno = err;
+ return -1;
+ }
if (epmd_port == 0) {
char* port_str = getenv("ERL_EPMD_PORT");
epmd_port = (port_str != NULL) ? atoi(port_str) : EPMD_PORT;
}
- memset(&saddr, 0, sizeof(saddr));
- saddr.sin_port = htons(epmd_port);
- saddr.sin_family = AF_INET;
- if (!inaddr) saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- else memmove(&saddr.sin_addr,inaddr,sizeof(saddr.sin_addr));
+ port = (int) epmd_port;
- if (((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0))
- {
- erl_errno = errno;
- return -1;
+ if (!inaddr) {
+ ip_addr.s_addr = htonl(INADDR_LOOPBACK);
+ inaddr = &ip_addr;
}
+
+ memset((void *) &addr, 0, sizeof(struct sockaddr_in));
+ memcpy((void *) &addr.sin_addr, (void *) inaddr, sizeof(addr.sin_addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
- if ((res = ei_connect_t(sd,(struct sockaddr *)&saddr,sizeof(saddr),ms)) < 0)
- {
- erl_errno = (res == -2) ? ETIMEDOUT : errno;
- closesocket(sd);
+ err = ei_connect_t__(sd, (void *) &addr, sizeof(addr), tmo);
+ if (err) {
+ erl_errno = err;
+ ei_close__(sd);
return -1;
}
@@ -104,6 +111,9 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
int port;
int dist_high, dist_low, proto;
int res;
+ int err;
+ ssize_t dlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
#if defined(VXWORKS)
char ntoabuf[32];
#endif
@@ -124,10 +134,14 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
return -1;
}
- if ((res = ei_write_fill_t(fd, buf, len+2, ms)) != len+2) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ dlen = len + 2;
+ err = ei_write_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) len + 2)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
#ifdef VXWORKS
@@ -142,12 +156,15 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
"-> PORT2_REQ alive=%s ip=%s",alive,inet_ntoa(*addr));
#endif
- /* read first two bytes (response type, response) */
- if ((res = ei_read_fill_t(fd, buf, 2, ms)) != 2) {
- EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- closesocket(fd);
- return -2; /* version mismatch */
+ dlen = (ssize_t) 2;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) 2)
+ erl_errno = EIO;
+ if (err) {
+ EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE");
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -2;
}
s = buf;
@@ -156,7 +173,7 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
if (res != EI_EPMD_PORT2_RESP) { /* response type */
EI_TRACE_ERR1("ei_epmd_r4_port","<- unknown (%d)",res);
EI_TRACE_ERR0("ei_epmd_r4_port","-> CLOSE");
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
@@ -167,7 +184,7 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
if ((res = get8(s))) {
/* got negative response */
EI_TRACE_ERR1("ei_epmd_r4_port","<- PORT2_RESP result=%d (failure)",res);
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
@@ -175,14 +192,18 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive,
EI_TRACE_CONN1("ei_epmd_r4_port","<- PORT2_RESP result=%d (ok)",res);
/* expecting remaining 8 bytes */
- if ((res = ei_read_fill_t(fd,buf,8,ms)) != 8) {
+ dlen = (ssize_t) 8;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) 8)
+ err = EIO;
+ if (err) {
EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE");
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- closesocket(fd);
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
return -1;
}
- closesocket(fd);
+ ei_close__(fd);
s = buf;
port = get16be(s);
diff --git a/lib/erl_interface/src/epmd/epmd_publish.c b/lib/erl_interface/src/epmd/epmd_publish.c
index 47d68a6db0..20b8e867e8 100644
--- a/lib/erl_interface/src/epmd/epmd_publish.c
+++ b/lib/erl_interface/src/epmd/epmd_publish.c
@@ -68,8 +68,10 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
int nlen = strlen(alive);
int len = elen + nlen + 13; /* hard coded: be careful! */
int n;
- int res, creation;
-
+ int err, res, creation;
+ ssize_t dlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
+
if (len > sizeof(buf)-2)
{
erl_errno = ERANGE;
@@ -93,29 +95,39 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
if ((fd = ei_epmd_connect_tmo(NULL,ms)) < 0) return fd;
- if ((res = ei_write_fill_t(fd, buf, len+2, ms)) != len+2) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ dlen = (ssize_t) len+2;
+ err = ei_write_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) len + 2)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
EI_TRACE_CONN6("ei_epmd_r4_publish",
"-> ALIVE2_REQ alive=%s port=%d ntype=%d "
"proto=%d dist-high=%d dist-low=%d",
alive,port,'H',EI_MYPROTO,EI_DIST_HIGH,EI_DIST_LOW);
-
- if ((n = ei_read_fill_t(fd, buf, 4, ms)) != 4) {
+
+ dlen = (ssize_t) 4;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ n = (int) dlen;
+ if (!err && n != 4)
+ err = EIO;
+ if (err) {
EI_TRACE_ERR0("ei_epmd_r4_publish","<- CLOSE");
- closesocket(fd);
- erl_errno = (n == -2) ? ETIMEDOUT : EIO;
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
return -2; /* version mismatch */
}
+
/* Don't close fd here! It keeps us registered with epmd */
s = buf;
if (((res=get8(s)) != EI_EPMD_ALIVE2_RESP)) { /* response */
EI_TRACE_ERR1("ei_epmd_r4_publish","<- unknown (%d)",res);
EI_TRACE_ERR0("ei_epmd_r4_publish","-> CLOSE");
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
@@ -124,7 +136,7 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms)
if (((res=get8(s)) != 0)) { /* 0 == success */
EI_TRACE_ERR1("ei_epmd_r4_publish"," result=%d (fail)",res);
- closesocket(fd);
+ ei_close__(fd);
erl_errno = EIO;
return -1;
}
diff --git a/lib/erl_interface/src/epmd/epmd_unpublish.c b/lib/erl_interface/src/epmd/epmd_unpublish.c
index 255d0ffb59..c112f74147 100644
--- a/lib/erl_interface/src/epmd/epmd_unpublish.c
+++ b/lib/erl_interface/src/epmd/epmd_unpublish.c
@@ -58,7 +58,9 @@ int ei_unpublish_tmo(const char *alive, unsigned ms)
char buf[EPMDBUF];
char *s = (char*)buf;
int len = 1 + strlen(alive);
- int fd, res;
+ int fd, err;
+ ssize_t dlen;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
if (len > sizeof(buf)-3) {
erl_errno = ERANGE;
@@ -72,20 +74,29 @@ int ei_unpublish_tmo(const char *alive, unsigned ms)
/* FIXME can't connect, return success?! At least commen whats up */
if ((fd = ei_epmd_connect_tmo(NULL,ms)) < 0) return fd;
- if ((res = ei_write_fill_t(fd, buf, len+2,ms)) != len+2) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+ dlen = (ssize_t) len+2;
+ err = ei_write_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) len + 2)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
EI_TRACE_CONN1("ei_unpublish_tmo","-> STOP %s",alive);
-
- if ((res = ei_read_fill_t(fd, buf, 7, ms)) != 7) {
- closesocket(fd);
- erl_errno = (res == -2) ? ETIMEDOUT : EIO;
- return -1;
+
+ dlen = (ssize_t) 7;
+ err = ei_read_fill_t__(fd, buf, &dlen, tmo);
+ if (!err && dlen != (ssize_t) 7)
+ erl_errno = EIO;
+ if (err) {
+ ei_close__(fd);
+ EI_CONN_SAVE_ERRNO__(err);
+ return -1;
}
- closesocket(fd);
+
+ ei_close__(fd);
buf[7]=(char)0; /* terminate the string */
if (!strcmp("STOPPED",(char *)buf)) {
diff --git a/lib/erl_interface/src/legacy/erl_connect.c b/lib/erl_interface/src/legacy/erl_connect.c
index 7ffd545d3e..e2fd4611c0 100644
--- a/lib/erl_interface/src/legacy/erl_connect.c
+++ b/lib/erl_interface/src/legacy/erl_connect.c
@@ -179,15 +179,13 @@ int erl_xconnect(Erl_IpAddr addr, char *alivename)
*
* API: erl_close_connection()
*
- * Close a connection. FIXME call ei_close_connection() later.
- *
* Returns 0 on success and -1 on failure.
*
***************************************************************************/
int erl_close_connection(int fd)
{
- return closesocket(fd);
+ return ei_close_connection(fd);
}
/*
@@ -220,7 +218,10 @@ int erl_reg_send(int fd, char *server_name, ETERM *msg)
ei_x_buff x;
int r;
- ei_x_new_with_version(&x);
+ if (ei_x_new_with_version(&x) < 0) {
+ erl_errno = ENOMEM;
+ return 0;
+ }
if (ei_x_encode_term(&x, msg) < 0) {
erl_errno = EINVAL;
r = 0;
diff --git a/lib/erl_interface/src/misc/ei_internal.h b/lib/erl_interface/src/misc/ei_internal.h
index aa6aacd703..0c58245c0a 100644
--- a/lib/erl_interface/src/misc/ei_internal.h
+++ b/lib/erl_interface/src/misc/ei_internal.h
@@ -22,19 +22,20 @@
#ifndef _EI_INTERNAL_H
#define _EI_INTERNAL_H
+#ifdef EI_HIDE_REAL_ERRNO
+# define EI_CONN_SAVE_ERRNO__(E) \
+ ((E) == ETIMEDOUT ? (erl_errno = ETIMEDOUT) : (erl_errno = EIO))
+#else
+# define EI_CONN_SAVE_ERRNO__(E) \
+ (erl_errno = (E))
+#endif
+
/*
* Some useful stuff not to be exported to users.
*/
#ifdef __WIN32__
#define MAXPATHLEN 256
-#define writesocket(sock,buf,nbyte) send(sock,buf,nbyte,0)
-#define readsocket(sock,buf,nbyte) recv(sock,buf,nbyte,0)
-#else /* not __WIN32__ */
-#define writesocket write
-#define readsocket read
-#define closesocket close
-#define ioctlsocket ioctl
#endif
/*
@@ -155,4 +156,7 @@ extern int ei_tracelevel;
void ei_trace_printf(const char *name, int level, const char *format, ...);
int ei_internal_use_r9_pids_ports(void);
+
+int ei_get_cbs_ctx__(ei_socket_callbacks **cbs, void **ctx, int fd);
+
#endif /* _EI_INTERNAL_H */
diff --git a/lib/erl_interface/src/misc/ei_portio.c b/lib/erl_interface/src/misc/ei_portio.c
index 8cd35bf2e5..726b1af82d 100644
--- a/lib/erl_interface/src/misc/ei_portio.c
+++ b/lib/erl_interface/src/misc/ei_portio.c
@@ -22,6 +22,7 @@
#ifdef __WIN32__
#include <winsock2.h>
#include <windows.h>
+#include <winbase.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
@@ -35,10 +36,6 @@ static unsigned long param_one = 1;
#define SET_BLOCKING(Sock) ioctlsocket((Sock),FIONBIO,&param_zero)
#define SET_NONBLOCKING(Sock) ioctlsocket((Sock),FIONBIO,&param_one)
-#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
-#define ERROR_TIMEDOUT WSAETIMEDOUT
-#define ERROR_INPROGRESS WSAEINPROGRESS
-#define GET_SOCKET_ERROR() WSAGetLastError()
#define MEANS_SOCKET_ERROR(Ret) ((Ret == SOCKET_ERROR))
#define IS_INVALID_SOCKET(Sock) ((Sock) == INVALID_SOCKET)
@@ -53,15 +50,16 @@ static unsigned long param_one = 1;
#include <sys/types.h>
#include <ioLib.h>
#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <timers.h>
static unsigned long param_zero = 0;
static unsigned long param_one = 1;
#define SET_BLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_zero)
#define SET_NONBLOCKING(Sock) ioctl((Sock),FIONBIO,(int)&param_one)
-#define ERROR_WOULDBLOCK EWOULDBLOCK
-#define ERROR_TIMEDOUT ETIMEDOUT
-#define ERROR_INPROGRESS EINPROGRESS
-#define GET_SOCKET_ERROR() (errno)
#define MEANS_SOCKET_ERROR(Ret) ((Ret) == ERROR)
#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)
@@ -69,106 +67,394 @@ static unsigned long param_one = 1;
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
-#ifndef EWOULDBLOCK
-#define ERROR_WOULDBLOCK EAGAIN
-#else
-#define ERROR_WOULDBLOCK EWOULDBLOCK
-#endif
#define SET_BLOCKING(fd) fcntl((fd), F_SETFL, \
fcntl((fd), F_GETFL, 0) & ~O_NONBLOCK)
#define SET_NONBLOCKING(fd) fcntl((fd), F_SETFL, \
fcntl((fd), F_GETFL, 0) | O_NONBLOCK)
-#define ERROR_TIMEDOUT ETIMEDOUT
-#define ERROR_INPROGRESS EINPROGRESS
-#define GET_SOCKET_ERROR() (errno)
#define MEANS_SOCKET_ERROR(Ret) ((Ret) < 0)
#define IS_INVALID_SOCKET(Sock) ((Sock) < 0)
#endif
/* common includes */
-#include "eidef.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "ei_portio.h"
-#include "ei_internal.h"
-
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
+#include "eidef.h"
+#include "ei_portio.h"
+#include "ei_internal.h"
+
+#ifdef __WIN32__
+
+#define writesocket(sock,buf,nbyte) send(sock,buf,nbyte,0)
+#define readsocket(sock,buf,nbyte) recv(sock,buf,nbyte,0)
-#ifdef HAVE_WRITEV
-static int ei_writev_t(int fd, struct iovec *iov, int iovcnt, unsigned ms)
+static int get_error(void)
{
- int res;
- if (ms != 0) {
- fd_set writemask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&writemask);
- FD_SET(fd,&writemask);
- switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &writemask)) {
- return -1; /* Other error */
- }
- }
+ switch (WSAGetLastError()) {
+ case WSAEWOULDBLOCK: return EWOULDBLOCK;
+ case WSAETIMEDOUT: return ETIMEDOUT;
+ case WSAEINPROGRESS: return EINPROGRESS;
+ case WSA_NOT_ENOUGH_MEMORY: return ENOMEM;
+ case WSA_INVALID_PARAMETER: return EINVAL;
+ case WSAEBADF: return EBADF;
+ case WSAEINVAL: return EINVAL;
+ case WSAEADDRINUSE: return EADDRINUSE;
+ case WSAENETUNREACH: return ENETUNREACH;
+ case WSAECONNABORTED: return ECONNABORTED;
+ case WSAECONNRESET: return ECONNRESET;
+ case WSAECONNREFUSED: return ECONNREFUSED;
+ case WSAEHOSTUNREACH: return EHOSTUNREACH;
+ case WSAEMFILE: return EMFILE;
+ case WSAEALREADY: return EALREADY;
+ default: return EIO;
}
+}
+
+#else /* not __WIN32__ */
+
+#define writesocket write
+#define readsocket read
+#define closesocket close
+#define ioctlsocket ioctl
+
+static int get_error(void)
+{
+ int err = errno;
+ if (err == 0)
+ return EIO; /* Make sure never to return 0 as error code... */
+ return err;
+}
+
+#endif
+
+int ei_plugin_socket_impl__ = 0;
+
+/*
+ * Callbacks for communication over TCP/IPv4
+ */
+
+static int tcp_get_fd(void *ctx, int *fd)
+{
+ return EI_DFLT_CTX_TO_FD__(ctx, fd);
+}
+
+static int tcp_hs_packet_header_size(void *ctx, int *sz)
+{
+ int fd;
+ *sz = 2;
+ return EI_DFLT_CTX_TO_FD__(ctx, &fd);
+}
+
+static int tcp_handshake_complete(void *ctx)
+{
+ int res, fd, one = 1;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one));
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+static int tcp_socket(void **ctx, void *setup_ctx)
+{
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (MEANS_SOCKET_ERROR(fd))
+ return get_error();
+
+ *ctx = EI_FD_AS_CTX__(fd);
+ return 0;
+}
+
+static int tcp_close(void *ctx)
+{
+ int fd, res;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = closesocket(fd);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+static int tcp_listen(void *ctx, void *addr, int *len, int backlog)
+{
+ int res, fd;
+ socklen_t sz = (socklen_t) *len;
+ int on = 1;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ res = bind(fd, (struct sockaddr *) addr, sz);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ res = getsockname(fd, (struct sockaddr *) addr, (socklen_t *) &sz);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = (int) sz;
+
+ res = listen(fd, backlog);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+static int tcp_accept(void **ctx, void *addr, int *len, unsigned unused)
+{
+ int fd, res;
+ socklen_t addr_len = (socklen_t) *len;
+
+ if (!ctx)
+ return EINVAL;
+
+ res = EI_DFLT_CTX_TO_FD__(*ctx, &fd);
+ if (res)
+ return res;
+
+ res = accept(fd, (struct sockaddr*) &addr, &addr_len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ *len = (int) addr_len;
+
+ *ctx = EI_FD_AS_CTX__(res);
+ return 0;
+}
+
+static int tcp_connect(void *ctx, void *addr, int len, unsigned unused)
+{
+ int res, fd;
+
+ res = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (res)
+ return res;
+
+ res = connect(fd, (struct sockaddr *) addr, len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+
+ return 0;
+}
+
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+
+static int tcp_writev(void *ctx, const void *viov, int iovcnt, ssize_t *len, unsigned unused)
+{
+ const struct iovec *iov = (const struct iovec *) viov;
+ int fd, error;
+ ssize_t res;
+
+ error = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (error)
+ return error;
+
res = writev(fd, iov, iovcnt);
- return (res < 0) ? -1 : res;
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = res;
+ return 0;
+}
+
+#endif
+
+static int tcp_write(void *ctx, const char* buf, ssize_t *len, unsigned unused)
+{
+ int error, fd;
+ ssize_t res;
+
+ error = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (error)
+ return error;
+
+ res = writesocket(fd, buf, *len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = res;
+ return 0;
}
-int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned ms)
+static int tcp_read(void *ctx, char* buf, ssize_t *len, unsigned unused)
{
- int i;
- int done;
+ int error, fd;
+ ssize_t res;
+
+ error = EI_DFLT_CTX_TO_FD__(ctx, &fd);
+ if (error)
+ return error;
+
+ res = readsocket(fd, buf, *len);
+ if (MEANS_SOCKET_ERROR(res))
+ return get_error();
+ *len = res;
+ return 0;
+}
+
+ei_socket_callbacks ei_default_socket_callbacks = {
+ 0, /* flags */
+ tcp_socket,
+ tcp_close,
+ tcp_listen,
+ tcp_accept,
+ tcp_connect,
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+ tcp_writev,
+#else
+ NULL,
+#endif
+ tcp_write,
+ tcp_read,
+
+ tcp_hs_packet_header_size,
+ tcp_handshake_complete,
+ tcp_handshake_complete,
+ tcp_get_fd
+
+};
+
+
+/*
+ *
+ */
+
+#if defined(EI_HAVE_STRUCT_IOVEC__)
+
+int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs)
+{
+ return !!cbs->writev;
+}
+
+static int writev_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ const struct iovec *iov, int iovcnt,
+ ssize_t *len,
+ unsigned ms)
+{
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set writemask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&writemask);
+ FD_SET(fd,&writemask);
+ switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &writemask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
+ }
+ do {
+ error = cbs->writev(ctx, (const void *) iov, iovcnt, len, ms);
+ } while (error == EINTR);
+ return error;
+}
+
+int ei_writev_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ const struct iovec *iov, int iovcnt,
+ ssize_t *len,
+ unsigned ms)
+{
+ ssize_t i, done, sum;
struct iovec *iov_base = NULL;
struct iovec *current_iov;
int current_iovcnt;
- int sum;
+ int fd, error;
+ int basic;
+
+ if (!cbs->writev)
+ return ENOTSUP;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+ basic = !(cbs->flags & EI_SCLBK_FLG_FULL_IMPL);
+
for (sum = 0, i = 0; i < iovcnt; ++i) {
sum += iov[i].iov_len;
}
- if (ms != 0U) {
+ if (basic && ms != 0U) {
SET_NONBLOCKING(fd);
}
current_iovcnt = iovcnt;
current_iov = (struct iovec *) iov;
done = 0;
for (;;) {
- i = ei_writev_t(fd, current_iov, current_iovcnt, ms);
- if (i <= 0) { /* ei_writev_t should always return at least 1 */
+
+ error = writev_ctx_t__(cbs, ctx, current_iov, current_iovcnt, &i, ms);
+ if (error) {
+ *len = done;
if (ms != 0U) {
SET_BLOCKING(fd);
}
if (iov_base != NULL) {
free(iov_base);
}
- return (i);
- }
+ return error;
+ }
done += i;
if (done < sum) {
if (iov_base == NULL) {
iov_base = malloc(sizeof(struct iovec) * iovcnt);
if (iov_base == NULL) {
- return -1;
+ *len = done;
+ return ENOMEM;
}
memcpy(iov_base, iov, sizeof(struct iovec) * iovcnt);
current_iov = iov_base;
@@ -189,195 +475,383 @@ int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned
break;
}
}
- if (ms != 0U) {
+ if (basic && ms != 0U) {
SET_BLOCKING(fd);
}
if (iov_base != NULL) {
free(iov_base);
}
- return (sum);
+ *len = done;
+ return 0;
}
+#endif /* defined(EI_HAVE_STRUCT_IOVEC__) */
-#endif
-
-int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms)
+int ei_socket_ctx__(ei_socket_callbacks *cbs, void **ctx, void *setup_ctx)
{
int res;
- int error;
- int s_res;
- struct timeval tv;
- fd_set writefds;
- fd_set exceptfds;
-
- if (ms == 0) {
- res = connect(fd, sinp, sin_siz);
- return (res < 0) ? -1 : res;
- } else {
- SET_NONBLOCKING(fd);
- res = connect(fd, sinp, sin_siz);
- error = GET_SOCKET_ERROR();
- SET_BLOCKING(fd);
- if (!MEANS_SOCKET_ERROR(res)) {
- return (res < 0) ? -1 : res;
- } else {
- if (error != ERROR_WOULDBLOCK &&
- error != ERROR_INPROGRESS) {
- return -1;
- } else {
- tv.tv_sec = (long) (ms/1000U);
- ms %= 1000U;
- tv.tv_usec = (long) (ms * 1000U);
- FD_ZERO(&writefds);
- FD_SET(fd,&writefds);
- FD_ZERO(&exceptfds);
- FD_SET(fd,&exceptfds);
- s_res = select(fd + 1, NULL, &writefds, &exceptfds, &tv);
- switch (s_res) {
- case 0:
- return -2;
- case 1:
- if (FD_ISSET(fd, &exceptfds)) {
- return -1;
- } else {
- return 0; /* Connect completed */
- }
- default:
- return -1;
- }
- }
- }
- }
+
+ do {
+ res = cbs->socket(ctx, setup_ctx);
+ } while (res == EINTR);
+
+ return res;
}
-int ei_accept_t(int fd, void *addr, void *addrlen, unsigned ms)
+int ei_close_ctx__(ei_socket_callbacks *cbs, void *ctx)
{
- int res;
- if (ms != 0) {
- fd_set readmask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
- switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &readmask)) {
- return -1; /* Other error */
- }
- }
- }
- res = (int) accept(fd,addr,addrlen);
- return (res < 0) ? -1 : res;
+ return cbs->close(ctx);
}
+
+int ei_connect_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ void *addr, int len, unsigned ms)
+{
+ int res, fd;
+
+ if ((cbs->flags & EI_SCLBK_FLG_FULL_IMPL) || ms == EI_SCLBK_INF_TMO) {
+ do {
+ res = cbs->connect(ctx, addr, len, ms);
+ } while (res == EINTR);
+ return res;
+ }
+
+ res = EI_GET_FD__(cbs, ctx, &fd);
+ if (res)
+ return res;
+ SET_NONBLOCKING(fd);
+ do {
+ res = cbs->connect(ctx, addr, len, 0);
+ } while (res == EINTR);
+ SET_BLOCKING(fd);
+
+ switch (res) {
+ case EINPROGRESS:
+ case EAGAIN:
+#ifdef EWOULDBLOCK
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+#endif
+ break;
+ default:
+ return res;
+ }
+ while (1) {
+ struct timeval tv;
+ fd_set writefds;
+ fd_set exceptfds;
+
+ tv.tv_sec = (long) (ms/1000U);
+ ms %= 1000U;
+ tv.tv_usec = (long) (ms * 1000U);
+ FD_ZERO(&writefds);
+ FD_SET(fd,&writefds);
+ FD_ZERO(&exceptfds);
+ FD_SET(fd,&exceptfds);
+ res = select(fd + 1, NULL, &writefds, &exceptfds, &tv);
+ switch (res) {
+ case -1:
+ res = get_error();
+ if (res != EINTR)
+ return res;
+ break;
+ case 0:
+ return ETIMEDOUT;
+ case 1:
+ if (!FD_ISSET(fd, &exceptfds))
+ return 0; /* Connect completed */
+ /* fall through... */
+ default:
+ return EIO;
+ }
+ }
+}
-static int ei_read_t(int fd, char* buf, int len, unsigned ms)
+int ei_listen_ctx__(ei_socket_callbacks *cbs, void *ctx,
+ void *adr, int *len, int backlog)
{
int res;
- if (ms != 0) {
- fd_set readmask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&readmask);
- FD_SET(fd,&readmask);
- switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &readmask)) {
- return -1; /* Other error */
- }
- }
+
+ do {
+ res = cbs->listen(ctx, adr, len, backlog);
+ } while (res == EINTR);
+ return res;
+}
+
+int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx,
+ void *addr, int *len, unsigned ms)
+{
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, *ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set readmask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&readmask);
+ FD_SET(fd,&readmask);
+ switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &readmask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
}
- res = readsocket(fd, buf, len);
- return (res < 0) ? -1 : res;
+ do {
+ error = cbs->accept(ctx, addr, len, ms);
+ } while (error == EINTR);
+ return error;
}
-static int ei_write_t(int fd, const char* buf, int len, unsigned ms)
+static int read_ctx_t__(ei_socket_callbacks *cbs, void *ctx,
+ char* buf, ssize_t *len, unsigned ms)
{
- int res;
- if (ms != 0) {
- fd_set writemask;
- struct timeval tv;
- tv.tv_sec = (time_t) (ms / 1000U);
- ms %= 1000U;
- tv.tv_usec = (time_t) (ms * 1000U);
- FD_ZERO(&writemask);
- FD_SET(fd,&writemask);
- switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
- case -1 :
- return -1; /* i/o error */
- case 0:
- return -2; /* timeout */
- default:
- if (!FD_ISSET(fd, &writemask)) {
- return -1; /* Other error */
- }
- }
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set readmask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&readmask);
+ FD_SET(fd,&readmask);
+ switch (select(fd+1, &readmask, NULL, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &readmask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
+ }
+ do {
+ error = cbs->read(ctx, buf, len, ms);
+ } while (error == EINTR);
+ return error;
+}
+
+static int write_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char* buf, ssize_t *len, unsigned ms)
+{
+ int error;
+
+ if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) {
+ int fd;
+
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
+
+ do {
+ fd_set writemask;
+ struct timeval tv;
+
+ tv.tv_sec = (time_t) (ms / 1000U);
+ ms %= 1000U;
+ tv.tv_usec = (time_t) (ms * 1000U);
+ FD_ZERO(&writemask);
+ FD_SET(fd,&writemask);
+ switch (select(fd+1, NULL, &writemask, NULL, &tv)) {
+ case -1 :
+ error = get_error();
+ if (error != EINTR)
+ return error;
+ break;
+ case 0:
+ return ETIMEDOUT; /* timeout */
+ default:
+ if (!FD_ISSET(fd, &writemask)) {
+ return EIO; /* Other error */
+ }
+ error = 0;
+ break;
+ }
+ } while (error == EINTR);
}
- res = writesocket(fd, buf, len);
- return (res < 0) ? -1 : res;
+ do {
+ error = cbs->write(ctx, buf, len, ms);
+ } while (error == EINTR);
+ return error;
}
/*
* Fill buffer, return buffer length, 0 for EOF, < 0 (and sets errno)
* for error. */
-int ei_read_fill_t(int fd, char* buf, int len, unsigned ms)
+int ei_read_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len, unsigned ms)
{
- int i,got=0;
+ ssize_t got = 0;
+ ssize_t want = *len;
do {
- i = ei_read_t(fd, buf+got, len-got, ms);
- if (i <= 0)
- return (i);
- got += i;
- } while (got < len);
- return (len);
-
+ ssize_t read_len = want-got;
+ int error;
+
+ do {
+ error = read_ctx_t__(cbs, ctx, buf+got, &read_len, ms);
+ } while (error == EINTR);
+ if (error)
+ return error;
+ if (read_len == 0) {
+ *len = got;
+ return 0;
+ }
+ got += read_len;
+ } while (got < want);
+
+ *len = got;
+ return 0;
} /* read_fill */
-int ei_read_fill(int fd, char* buf, int len)
+int ei_read_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len)
{
- return ei_read_fill_t(fd, buf, len, 0);
+ return ei_read_fill_ctx_t__(cbs, ctx, buf, len, 0);
}
/* write entire buffer on fd or fail (setting errno)
*/
-int ei_write_fill_t(int fd, const char *buf, int len, unsigned ms)
+int ei_write_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len, unsigned ms)
{
- int i,done=0;
- if (ms != 0U) {
+ ssize_t tot = *len, done = 0;
+ int error, fd = -1, basic = !(cbs->flags & EI_SCLBK_FLG_FULL_IMPL);
+
+ if (basic && ms != 0U) {
+ error = EI_GET_FD__(cbs, ctx, &fd);
+ if (error)
+ return error;
SET_NONBLOCKING(fd);
}
do {
- i = ei_write_t(fd, buf+done, len-done, ms);
- if (i <= 0) {
- if (ms != 0U) {
+ ssize_t write_len = tot-done;
+ error = write_ctx_t__(cbs, ctx, buf+done, &write_len, ms);
+ if (error) {
+ *len = done;
+ if (basic && ms != 0U) {
SET_BLOCKING(fd);
}
- return (i);
+ return error;
}
- done += i;
- } while (done < len);
- if (ms != 0U) {
+ done += write_len;
+ } while (done < tot);
+ if (basic && ms != 0U) {
SET_BLOCKING(fd);
}
- return (len);
+ *len = done;
+ return 0;
+}
+
+int ei_write_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len)
+{
+ return ei_write_fill_ctx_t__(cbs, ctx, buf, len, 0);
+}
+
+/*
+ * Internal API for TCP/IPv4
+ */
+
+int ei_connect_t__(int fd, void *addr, int len, unsigned ms)
+{
+ return ei_connect_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ addr, len, ms);
}
-int ei_write_fill(int fd, const char *buf, int len)
+int ei_socket__(int *fd)
{
- return ei_write_fill_t(fd, buf, len, 0);
+ void *ctx;
+ int error = ei_socket_ctx__(&ei_default_socket_callbacks, &ctx, NULL);
+ if (error)
+ return error;
+ return EI_GET_FD__(&ei_default_socket_callbacks, ctx, fd);
}
+int ei_close__(int fd)
+{
+ return ei_close_ctx__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd));
+}
+
+int ei_listen__(int fd, void *adr, int *len, int backlog)
+{
+ return ei_listen_ctx__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ adr, len, backlog);
+}
+
+int ei_accept_t__(int *fd, void *addr, int *len, unsigned ms)
+{
+ void *ctx = EI_FD_AS_CTX__(*fd);
+ int error = ei_accept_ctx_t__(&ei_default_socket_callbacks, &ctx,
+ addr, len, ms);
+ if (error)
+ return error;
+ return EI_GET_FD__(&ei_default_socket_callbacks, ctx, fd);
+}
+
+int ei_read_fill_t__(int fd, char* buf, ssize_t *len, unsigned ms)
+{
+ return ei_read_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, ms);
+}
+
+int ei_read_fill__(int fd, char* buf, ssize_t *len)
+{
+ return ei_read_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, 0);
+}
+
+int ei_write_fill_t__(int fd, const char *buf, ssize_t *len, unsigned ms)
+{
+ return ei_write_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, ms);
+}
+
+int ei_write_fill__(int fd, const char *buf, ssize_t *len)
+{
+ return ei_write_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ buf, len, 0);
+}
+
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+
+int ei_writev_fill_t__(int fd, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms)
+{
+ return ei_writev_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd),
+ iov, iovcnt, len, ms);
+}
+
+#endif
+
diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h
index bded811a35..a84b5ca09c 100644
--- a/lib/erl_interface/src/misc/ei_portio.h
+++ b/lib/erl_interface/src/misc/ei_portio.h
@@ -21,21 +21,94 @@
*/
#ifndef _EI_PORTIO_H
#define _EI_PORTIO_H
-#if !defined(__WIN32__) && !defined(VXWORKS)
-#ifdef HAVE_WRITEV
+
+#undef EI_HAVE_STRUCT_IOVEC__
+#if !defined(__WIN32__) && !defined(VXWORKS) && defined(HAVE_SYS_UIO_H)
/* Declaration of struct iovec *iov should be visible in this scope. */
-#include <sys/uio.h>
+# include <sys/uio.h>
+# define EI_HAVE_STRUCT_IOVEC__
#endif
+
+/*
+ * Internal API. Should not be used outside of the erl_interface application...
+ */
+
+int ei_socket_ctx__(ei_socket_callbacks *cbs, void **ctx, void *setup);
+int ei_close_ctx__(ei_socket_callbacks *cbs, void *ctx);
+int ei_listen_ctx__(ei_socket_callbacks *cbs, void *ctx, void *adr, int *len, int backlog);
+int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx, void *addr, int *len, unsigned ms);
+int ei_connect_ctx_t__(ei_socket_callbacks *cbs, void *ctx, void *addr, int len, unsigned ms);
+int ei_read_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len);
+int ei_write_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len);
+int ei_read_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len, unsigned ms);
+int ei_write_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len, unsigned ms);
+#if defined(EI_HAVE_STRUCT_IOVEC__)
+int ei_writev_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms);
+int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs);
#endif
-int ei_accept_t(int fd, void *addr, void *addrlen, unsigned ms);
-int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms);
-int ei_read_fill(int fd, char* buf, int len);
-int ei_write_fill(int fd, const char *buf, int len);
-int ei_read_fill_t(int fd, char* buf, int len, unsigned ms);
-int ei_write_fill_t(int fd, const char *buf, int len, unsigned ms);
-#ifdef HAVE_WRITEV
-int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned ms);
+ei_socket_callbacks ei_default_socket_callbacks;
+
+#define EI_FD_AS_CTX__(FD) \
+ ((void *) (long) (FD))
+
+#define EI_DFLT_CTX_TO_FD__(CTX, FD) \
+ ((int) (long) (CTX) < 0 \
+ ? EBADF \
+ : (*(FD) = (int) (long) (CTX), 0))
+
+#define EI_GET_FD__(CBS, CTX, FD) \
+ ((CBS) == &ei_default_socket_callbacks \
+ ? EI_DFLT_CTX_TO_FD__((CTX), FD) \
+ : (CBS)->get_fd((CTX), (FD)))
+
+extern int ei_plugin_socket_impl__;
+
+#if !defined(_REENTRANT)
+
+#define EI_HAVE_PLUGIN_SOCKET_IMPL__ \
+ ei_plugin_socket_impl__
+#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ \
+ ei_plugin_socket_impl__ = 1
+
+#elif ((ETHR_HAVE___atomic_load_n & SIZEOF_INT) \
+ && (ETHR_HAVE___atomic_store_n & SIZEOF_INT))
+
+#define EI_HAVE_PLUGIN_SOCKET_IMPL__ \
+ __atomic_load_n(&ei_plugin_socket_impl__, __ATOMIC_ACQUIRE)
+#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ \
+ __atomic_store_n(&ei_plugin_socket_impl__, 1, __ATOMIC_RELEASE)
+
+#else
+
+/* No gcc atomics; always lookup using ei_get_cbs_ctx()... */
+#define EI_HAVE_PLUGIN_SOCKET_IMPL__ 0
+#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ (void) 0
+
+#endif
+
+#define EI_GET_CBS_CTX__(CBS, CTX, FD) \
+ (EI_HAVE_PLUGIN_SOCKET_IMPL__ \
+ ? ei_get_cbs_ctx__((CBS), (CTX), (FD)) \
+ : ((FD) < 0 \
+ ? EBADF \
+ : (*(CBS) = &ei_default_socket_callbacks, \
+ *(CTX) = EI_FD_AS_CTX__((FD)), \
+ 0)))
+/*
+ * The following uses our own TCP/IPv4 socket implementation...
+ */
+int ei_socket__(int *fd);
+int ei_close__(int fd);
+int ei_listen__(int fd, void *adr, int *len, int backlog);
+int ei_accept_t__(int *fd, void *addr, int *len, unsigned ms);
+int ei_connect_t__(int fd, void *addr, int len, unsigned ms);
+int ei_read_fill__(int fd, char* buf, ssize_t *len);
+int ei_write_fill__(int fd, const char *buf, ssize_t *len);
+int ei_read_fill_t__(int fd, char* buf, ssize_t *len, unsigned ms);
+int ei_write_fill_t__(int fd, const char *buf, ssize_t *len, unsigned ms);
+#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV)
+int ei_writev_fill_t__(int fd, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms);
#endif
#endif /* _EI_PORTIO_H */
diff --git a/lib/erl_interface/src/not_used/send_link.c b/lib/erl_interface/src/not_used/send_link.c
index 7be476fd93..38fae27df4 100644
--- a/lib/erl_interface/src/not_used/send_link.c
+++ b/lib/erl_interface/src/not_used/send_link.c
@@ -50,6 +50,7 @@ static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to,
char *s;
int index = 0;
int n;
+ unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms;
index = 5; /* max sizes: */
ei_encode_version(msgbuf,&index); /* 1 */
@@ -69,7 +70,7 @@ static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to,
if (ei_trace_distribution > 1) ei_show_sendmsg(stderr,msgbuf,NULL);
#endif
- n = ei_write_fill_t(fd,msgbuf,index,ms);
+ n = ei_write_fill_t__(fd,msgbuf,index,tmo);
return (n==index ? 0 : -1);
}
diff --git a/lib/erl_interface/test/ei_accept_SUITE.erl b/lib/erl_interface/test/ei_accept_SUITE.erl
index 78a433d21b..9c9c3f86b6 100644
--- a/lib/erl_interface/test/ei_accept_SUITE.erl
+++ b/lib/erl_interface/test/ei_accept_SUITE.erl
@@ -81,12 +81,10 @@ ei_accept(Config) when is_list(Config) ->
ei_threaded_accept(Config) when is_list(Config) ->
Einode = filename:join(proplists:get_value(data_dir, Config), "eiaccnode"),
- N = 1, % 3,
+ N = 3,
Host = atom_to_list(node()),
- Port = 6767,
- start_einode(Einode, N, Host, Port),
+ start_einode(Einode, N, Host),
io:format("started eiaccnode"),
- %%spawn_link(fun() -> start_einode(Einode, N, Host, Port) end),
TestServerPid = self(),
[spawn_link(fun() -> send_rec_einode(I, TestServerPid) end) || I <- lists:seq(0, N-1)],
[receive I -> ok end || I <- lists:seq(0, N-1) ],
@@ -159,10 +157,9 @@ send_rec_einode(N, TestServerPid) ->
ct:fail(EINode)
end.
-start_einode(Einode, N, Host, Port) ->
+start_einode(Einode, N, Host) ->
Einodecmd = Einode ++ " " ++ atom_to_list(erlang:get_cookie())
- ++ " " ++ integer_to_list(N) ++ " " ++ Host ++ " "
- ++ integer_to_list(Port) ++ " nothreads",
+ ++ " " ++ integer_to_list(N) ++ " " ++ Host,
io:format("Einodecmd ~p ~n", [Einodecmd]),
open_port({spawn, Einodecmd}, []),
ok.
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
index 50df848b69..f41d741609 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c
@@ -125,45 +125,26 @@ static void cmd_ei_connect_init(char* buf, int len)
ei_x_free(&res);
}
-static int my_listen(int port)
-{
- int listen_fd;
- struct sockaddr_in addr;
- const char *on = "1";
-
- if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
- return -1;
-
- setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on));
-
- memset((void*) &addr, 0, (size_t) sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
- return -1;
-
- listen(listen_fd, 5);
- return listen_fd;
-}
-
static void cmd_ei_publish(char* buf, int len)
{
int index = 0;
- int listen, r;
- long port;
+ int iport, lfd, r;
+ long lport;
ei_x_buff x;
int i;
/* get port */
- if (ei_decode_long(buf, &index, &port) < 0)
+ if (ei_decode_long(buf, &index, &lport) < 0)
fail("expected int (port)");
/* Make a listen socket */
- if ((listen = my_listen(port)) <= 0)
+
+ iport = (int) lport;
+ lfd = ei_listen(&ec, &iport, 5);
+ if (lfd < 0)
fail("listen");
+ lport = (long) iport;
- if ((i = ei_publish(&ec, port)) == -1)
+ if ((i = ei_publish(&ec, lport)) == -1)
fail("ei_publish");
#ifdef VXWORKS
save_fd(i);
@@ -171,7 +152,7 @@ static void cmd_ei_publish(char* buf, int len)
/* send listen-fd, result and errno */
ei_x_new_with_version(&x);
ei_x_encode_tuple_header(&x, 3);
- ei_x_encode_long(&x, listen);
+ ei_x_encode_long(&x, (long) lfd);
ei_x_encode_long(&x, i);
ei_x_encode_long(&x, erl_errno);
send_bin_term(&x);
diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
index 308f843530..c850d20f3c 100644
--- a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
+++ b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c
@@ -47,8 +47,6 @@
#define MAIN main
#endif
-static int my_listen(int port);
-
/*
A small einode.
To be called from the test case ei_accept_SUITE:multi_thread
@@ -64,7 +62,6 @@ static int my_listen(int port);
*/
static const char* cookie, * desthost;
-static int port; /* actually base port */
#ifndef SD_SEND
#ifdef SHUTWR
@@ -74,10 +71,6 @@ static int port; /* actually base port */
#endif
#endif
-#ifndef __WIN32__
-#define closesocket(fd) close(fd)
-#endif
-
#ifdef __WIN32__
static DWORD WINAPI
#else
@@ -86,26 +79,32 @@ static void*
einode_thread(void* num)
{
int n = (int)num;
+ int port;
ei_cnode ec;
- char myname[100], destname[100];
+ char myname[100], destname[100], filename[100];
int r, fd, listen;
ErlConnect conn;
erlang_msg msg;
-/* FILE* f;*/
+ FILE* file;
- sprintf(myname, "eiacc%d", n);
- printf("thread %d (%s) listening\n", n, myname, destname);
+ sprintf(filename, "eiacc%d_trace.txt", n);
+ file = fopen(filename, "w");
+
+ sprintf(myname, "eiacc%d", n); fflush(file);
r = ei_connect_init(&ec, myname, cookie, 0);
- if ((listen = my_listen(port+n)) <= 0) {
- printf("listen err\n");
+ port = 0;
+ listen = ei_listen(&ec, &port, 5);
+ if (listen <= 0) {
+ fprintf(file, "listen err\n"); fflush(file);
exit(7);
}
- if (ei_publish(&ec, port + n) == -1) {
- printf("ei_publish port %d\n", port+n);
+ fprintf(file, "thread %d (%s:%s) listening on port %d\n", n, myname, destname, port);
+ if (ei_publish(&ec, port) == -1) {
+ fprintf(file, "ei_publish port %d\n", port+n); fflush(file);
exit(8);
}
fd = ei_accept(&ec, listen, &conn);
- printf("ei_accept %d\n", fd);
+ fprintf(file, "ei_accept %d\n", fd); fflush(file);
if (fd >= 0) {
ei_x_buff x, xs;
int index, version;
@@ -117,37 +116,38 @@ static void*
if (got == ERL_TICK)
continue;
if (got == ERL_ERROR) {
- printf("receive error %d\n", n);
+ fprintf(file, "receive error %d\n", n); fflush(file);
return 0;
}
- printf("received %d\n", got);
+ fprintf(file, "received %d\n", got); fflush(file);
break;
}
index = 0;
if (ei_decode_version(x.buff, &index, &version) != 0) {
- printf("ei_decode_version %d\n", n);
+ fprintf(file, "ei_decode_version %d\n", n); fflush(file);
return 0;
}
if (ei_decode_pid(x.buff, &index, &pid) != 0) {
- printf("ei_decode_pid %d\n", n);
+ fprintf(file, "ei_decode_pid %d\n", n); fflush(file);
return 0;
}
-/* fprintf(f, "got pid from %s \n", pid.node);*/
+ fprintf(file, "got pid from %s \n", pid.node); fflush(file);
ei_x_new_with_version(&xs);
ei_x_encode_tuple_header(&xs, 2);
ei_x_encode_long(&xs, n);
ei_x_encode_pid(&xs, &pid);
r = ei_send(fd, &pid, xs.buff, xs.index);
-/* fprintf(f, "sent %d bytes %d\n", xs.index, r);*/
+ fprintf(file, "sent %d bytes %d\n", xs.index, r); fflush(file);
shutdown(fd, SD_SEND);
- closesocket(fd);
+ ei_close_connection(fd);
ei_x_free(&x);
ei_x_free(&xs);
} else {
- printf("coudn't connect fd %d r %d\n", fd, r);
+ fprintf(file, "coudn't connect fd %d r %d\n", fd, r); fflush(file);
}
- printf("done thread %d\n", n);
-/* fclose(f);*/
+ ei_close_connection(listen);
+ fprintf(file, "done thread %d\n", n);
+ fclose(file);
return 0;
}
@@ -170,12 +170,14 @@ MAIN(int argc, char *argv[])
if (n > 100)
exit(2);
desthost = argv[3];
- port = atoi(argv[4]);
-#ifndef VXWORKS
- no_threads = argv[5] != NULL && strcmp(argv[5], "nothreads") == 0;
-#else
+ if (argc == 3)
+ no_threads = 0;
+ else
+ no_threads = argv[4] != NULL && strcmp(argv[4], "nothreads") == 0;
+#ifdef VXWORKS
no_threads = 1;
#endif
+
for (i = 0; i < n; ++i) {
if (!no_threads) {
#ifndef VXWORKS
@@ -209,27 +211,3 @@ MAIN(int argc, char *argv[])
printf("ok\n");
return 0;
}
-
-static int my_listen(int port)
-{
- int listen_fd;
- struct sockaddr_in addr;
- const char *on = "1";
-
- if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
- return -1;
-
- setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on));
-
- memset((void*) &addr, 0, (size_t) sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
- if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
- return -1;
-
- listen(listen_fd, 5);
- return listen_fd;
-}
-
diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
index bead0f8413..b87feb9dfc 100644
--- a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
+++ b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c
@@ -20,7 +20,7 @@
#include <stdlib.h>
#include <stdio.h>
-
+#include <string.h>
#include "ei.h"
#include "erl_interface.h"
@@ -68,6 +68,7 @@ MAIN(int argc, char **argv)
char host[80];
int number;
ETERM *ref, *ref1, *ref2;
+ FILE *dfile = fopen("cnode_debug_printout", "w");
erl_init(NULL, 0);
@@ -80,28 +81,30 @@ MAIN(int argc, char **argv)
gethostname(host, sizeof(host));
sprintf(node, "c%d@%s", number, host);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
sprintf(server, "test_server@%s", host);
fd = erl_connect(server);
- printf("fd = %d\n", fd);
+ fprintf(dfile, "fd = %d\n", fd);
-/* printf("dist = %d\n", erl_distversion(fd)); */
+/* fprintf(dfile, "dist = %d\n", erl_distversion(fd)); */
#if 1
ref = erl_mk_long_ref(node, 4711, 113, 98, 0);
#else
ref = erl_mk_ref(node, 4711, 0);
#endif
- printf("ref = %d\n", ref);
+ fprintf(dfile, "ref = %p\n", ref); fflush(dfile);
s = erl_reg_send(fd, "mip", ref);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
{
ETERM* emsg;
emsg = SELF(fd);
- erl_reg_send(fd,"mip",emsg);
+ fprintf(dfile, "pid = %p\n", emsg); fflush(dfile);
+ s = erl_reg_send(fd,"mip",emsg);
+ fprintf(dfile, "s2 = %d\n", s); fflush(dfile);
erl_free_term(emsg);
}
@@ -116,28 +119,29 @@ MAIN(int argc, char **argv)
#endif
switch (s) {
case ERL_TICK:
- printf("tick\n");
+ fprintf(dfile, "tick\n");
break;
case ERL_ERROR:
- printf("error\n");
+ fprintf(dfile, "error: %s (%d)\n", strerror(erl_errno), erl_errno);
break;
case ERL_MSG:
- printf("msg %d\n", msgsize);
+ fprintf(dfile, "msg %d\n", msgsize);
break;
default:
- printf("unknown result %d\n", s);
+ fprintf(dfile, "unknown result %d\n", s);
break;
}
+ fflush(dfile);
} while (s == ERL_TICK);
s = erl_reg_send(fd, "mip", msg.msg);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
s = erl_reg_send(fd, "mip", msg.to);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
#if 0
/* from = NULL! */
s = erl_reg_send(fd, "mip", msg.from);
- printf("s = %d\n", s);
+ fprintf(dfile, "s = %d\n", s); fflush(dfile);
#endif
#if 0
@@ -150,17 +154,19 @@ MAIN(int argc, char **argv)
ref1 = erl_mk_long_ref(node, 4711, 113, 98, 0);
ref2 = erl_mk_ref(node, 4711, 0);
s = erl_encode(ref1, buf1);
- printf("enc1 s = %d\n", s);
+ fprintf(dfile, "enc1 s = %d\n", s); fflush(dfile);
s = erl_encode(ref2, buf2);
- printf("enc2 s = %d\n", s);
+ fprintf(dfile, "enc2 s = %d\n", s); fflush(dfile);
s = erl_compare_ext(buf1, buf2);
- printf("comp s = %d\n", s);
+ fprintf(dfile, "comp s = %d\n", s); fflush(dfile);
/* Compare, in another way */
s = erl_match(ref1, ref2);
- printf("match s = %d\n", s);
+ fprintf(dfile, "match s = %d\n", s); fflush(dfile);
#endif
+ fclose(dfile);
+
erl_close_connection(fd);
return 0;
diff --git a/lib/et/doc/src/et.xml b/lib/et/doc/src/et.xml
index 3009b559e1..a362d00b3e 100644
--- a/lib/et/doc/src/et.xml
+++ b/lib/et/doc/src/et.xml
@@ -32,14 +32,14 @@
<rev>%VSN%</rev>
<file>et</file>
</header>
- <module>et</module>
+ <module since="">et</module>
<modulesummary>Main API of the Event Trace (ET) application</modulesummary>
<description>
<p>Interface module for the Event Trace (ET) application</p>
</description>
<funcs>
<func>
- <name>trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
+ <name since="OTP R13B04">trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
<fsummary>A function that is intended to be traced.</fsummary>
<type>
<v>DetailLevel = integer(X) when X =&lt; 0, X >= 100</v>
@@ -70,7 +70,7 @@
</func>
<func>
- <name>trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
+ <name since="OTP R13B04">trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
<fsummary>A function that is intended to be traced.</fsummary>
<desc>
<p>Invokes <c>et:trace_me/5</c> with both <c>From</c> and <c>To</c>
@@ -79,8 +79,8 @@
</func>
<func>
- <name>phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
- <name>phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
+ <name since="">phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
+ <name since="">phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
<fsummary>Send a signal to the outer space</fsummary>
<desc>
<p>These functions sends a signal to the outer space and the
@@ -90,8 +90,8 @@
</desc>
</func>
<func>
- <name>report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
- <name>report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
+ <name since="">report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name>
+ <name since="">report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name>
<fsummary>Deprecated functions</fsummary>
<desc>
<p>Deprecated functions which for the time being are kept for
diff --git a/lib/et/doc/src/et_collector.xml b/lib/et/doc/src/et_collector.xml
index fd90ecfc41..f908612797 100644
--- a/lib/et/doc/src/et_collector.xml
+++ b/lib/et/doc/src/et_collector.xml
@@ -32,14 +32,14 @@
<rev>%VSN%</rev>
<file>et_collector.xml</file>
</header>
- <module>et_collector</module>
+ <module since="">et_collector</module>
<modulesummary>Collect trace events and provide a backing storage appropriate for iteration </modulesummary>
<description>
<p>Interface module for the Event Trace (ET) application</p>
</description>
<funcs>
<func>
- <name>start_link(Options) -> {ok, CollectorPid} | {error, Reason}</name>
+ <name since="">start_link(Options) -> {ok, CollectorPid} | {error, Reason}</name>
<fsummary>Start a collector process</fsummary>
<type>
<v>Options = [option()]</v>
@@ -105,7 +105,7 @@
</desc>
</func>
<func>
- <name>stop(CollectorPid) -> ok</name>
+ <name since="">stop(CollectorPid) -> ok</name>
<fsummary>Stop a collector process</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -115,7 +115,7 @@
</desc>
</func>
<func>
- <name>save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}</name>
+ <name since="">save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}</name>
<fsummary>Save the events to a file</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -139,7 +139,7 @@
</desc>
</func>
<func>
- <name>load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name>
+ <name since="">load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name>
<fsummary>Load the event table from a file</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -152,9 +152,9 @@
</desc>
</func>
<func>
- <name>report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name>
- <name>report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
- <name>report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
+ <name since="">report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name>
+ <name since="">report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
+ <name since="">report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name>
<fsummary>Report an event to the collector</fsummary>
<type>
<v>Handle = Initial | Continuation</v>
@@ -181,7 +181,7 @@
</desc>
</func>
<func>
- <name>make_key(Type, Stuff) -> Key</name>
+ <name since="">make_key(Type, Stuff) -> Key</name>
<fsummary>Make a key out of an event record or an old key</fsummary>
<type>
<v>Type = record(table_handle) | trace_ts | event_ts</v>
@@ -193,7 +193,7 @@
</desc>
</func>
<func>
- <name>get_table_handle(CollectorPid) -> Handle</name>
+ <name since="">get_table_handle(CollectorPid) -> Handle</name>
<fsummary>Return a table handle</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -204,7 +204,7 @@
</desc>
</func>
<func>
- <name>get_global_pid() -> CollectorPid | exit(Reason)</name>
+ <name since="">get_global_pid() -> CollectorPid | exit(Reason)</name>
<fsummary>Return a the identity of the globally registered collector if there is any</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -216,7 +216,7 @@
</desc>
</func>
<func>
- <name>change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}</name>
+ <name since="">change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}</name>
<fsummary>Change active trace pattern globally on all trace nodes</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -232,9 +232,9 @@
</desc>
</func>
<func>
- <name>dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok</name>
- <name>dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok</name>
- <name>dict_insert(CollectorPid, Key, Val) -> ok</name>
+ <name since="">dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok</name>
+ <name since="">dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok</name>
+ <name since="">dict_insert(CollectorPid, Key, Val) -> ok</name>
<fsummary>Insert a dictionary entry and send a {et, {dict_insert, Key, Val}} tuple to all registered subscribers.</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -259,7 +259,7 @@
</desc>
</func>
<func>
- <name>dict_lookup(CollectorPid, Key) -> [Val]</name>
+ <name since="">dict_lookup(CollectorPid, Key) -> [Val]</name>
<fsummary>Lookup a dictionary entry and return zero or one value</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -273,7 +273,7 @@
</desc>
</func>
<func>
- <name>dict_delete(CollectorPid, Key) -> ok</name>
+ <name since="">dict_delete(CollectorPid, Key) -> ok</name>
<fsummary>Delete a dictionary entry and send a {et, {dict_delete, Key}} tuple to all registered subscribers.</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -290,7 +290,7 @@
</desc>
</func>
<func>
- <name>dict_match(CollectorPid, Pattern) -> [Match]</name>
+ <name since="">dict_match(CollectorPid, Pattern) -> [Match]</name>
<fsummary>Match some dictionary entries</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -306,7 +306,7 @@
</desc>
</func>
<func>
- <name>multicast(_CollectorPid, Msg) -> ok</name>
+ <name since="">multicast(_CollectorPid, Msg) -> ok</name>
<fsummary>Sends a message to all registered subscribers</fsummary>
<type>
<v>CollectorPid = pid()</v>
@@ -318,7 +318,7 @@
</desc>
</func>
<func>
- <name>start_trace_client(CollectorPid, Type, Parameters) -> file_loaded | {trace_client_pid, pid()} | exit(Reason)</name>
+ <name since="">start_trace_client(CollectorPid, Type, Parameters) -> file_loaded | {trace_client_pid, pid()} | exit(Reason)</name>
<fsummary>Load raw Erlang trace from a file, port or process.</fsummary>
<type>
<v>Type = dbg_trace_client_type()</v>
@@ -330,14 +330,14 @@
</desc>
</func>
<func>
- <name>iterate(Handle, Prev, Limit) -> NewAcc</name>
+ <name since="">iterate(Handle, Prev, Limit) -> NewAcc</name>
<fsummary>Iterates over the currently stored events</fsummary>
<desc>
<p>Short for iterate(Handle, Prev, Limit, undefined, Prev) -&gt; NewAcc</p>
</desc>
</func>
<func>
- <name>iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc</name>
+ <name since="">iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc</name>
<fsummary>Iterate over the currently stored events</fsummary>
<type>
<v>Handle = collector_pid() | table_handle()</v>
@@ -361,7 +361,7 @@
</desc>
</func>
<func>
- <name>clear_table(Handle) -> ok</name>
+ <name since="">clear_table(Handle) -> ok</name>
<fsummary>Clear the event table</fsummary>
<type>
<v>Handle = collector_pid() | table_handle()</v>
diff --git a/lib/et/doc/src/et_selector.xml b/lib/et/doc/src/et_selector.xml
index 30ca74c872..3c766cafb7 100644
--- a/lib/et/doc/src/et_selector.xml
+++ b/lib/et/doc/src/et_selector.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>et_selector.xml</file>
</header>
- <module>et_selector</module>
+ <module since="">et_selector</module>
<modulesummary>Define event transforms and trace patterns</modulesummary>
<description>
<p></p>
@@ -40,7 +40,7 @@
<funcs>
<func>
- <name>make_pattern(RawPattern) -> TracePattern</name>
+ <name since="">make_pattern(RawPattern) -> TracePattern</name>
<fsummary>Makes a trace pattern suitable to feed change_pattern/1</fsummary>
<type>
@@ -61,7 +61,7 @@
</func>
<func>
- <name>change_pattern(Pattern) -> ok</name>
+ <name since="">change_pattern(Pattern) -> ok</name>
<fsummary>Activates/deactivates tracing by changing the current trace pattern</fsummary>
@@ -85,7 +85,7 @@
</desc>
</func>
<func>
- <name>parse_event(Mod, ValidTraceData) -> false | true | {true, Event}</name>
+ <name since="">parse_event(Mod, ValidTraceData) -> false | true | {true, Event}</name>
<fsummary>Transforms trace data and makes an event record out of it</fsummary>
diff --git a/lib/et/doc/src/et_viewer.xml b/lib/et/doc/src/et_viewer.xml
index e0b39636e9..9d59eef668 100644
--- a/lib/et/doc/src/et_viewer.xml
+++ b/lib/et/doc/src/et_viewer.xml
@@ -32,14 +32,14 @@
<rev>%VSN%</rev>
<file>et_viewer.xml</file>
</header>
- <module>et_viewer</module>
+ <module since="">et_viewer</module>
<modulesummary>Displays a sequence chart for trace events (messages/actions)</modulesummary>
<description>
<p></p>
</description>
<funcs>
<func>
- <name>file(FileName) -> {ok, ViewerPid} | {error, Reason}</name>
+ <name since="">file(FileName) -> {ok, ViewerPid} | {error, Reason}</name>
<fsummary>Start a new event viewer and a corresponding collector and load them with trace events from a trace file.</fsummary>
<type>
<v>FileName() = string()</v>
@@ -52,7 +52,7 @@
</desc>
</func>
<func>
- <name>start() -> ok</name>
+ <name since="">start() -> ok</name>
<fsummary>Simplified start of a sequence chart viewer with global tracing activated.</fsummary>
<desc>
<p>Simplified start of a sequence chart viewer with
@@ -62,7 +62,7 @@
</desc>
</func>
<func>
- <name>start(Options) -> ok</name>
+ <name since="">start(Options) -> ok</name>
<fsummary>Start of a sequence chart viewer without linking to the parent process.</fsummary>
<desc>
<p>Start of a sequence chart viewer without linking
@@ -70,7 +70,7 @@
</desc>
</func>
<func>
- <name>start_link(Options) -> {ok, ViewerPid} | {error, Reason}</name>
+ <name since="">start_link(Options) -> {ok, ViewerPid} | {error, Reason}</name>
<fsummary>Start a sequence chart viewer for trace events (messages/actions)</fsummary>
<type>
<v>Options = [option() | collector_option()]</v>
@@ -125,7 +125,7 @@
</desc>
</func>
<func>
- <name>get_collector_pid(ViewerPid) -> CollectorPid</name>
+ <name since="">get_collector_pid(ViewerPid) -> CollectorPid</name>
<fsummary>Returns the identifier of the collector process</fsummary>
<type>
<v>ViewerPid = pid()</v>
@@ -136,7 +136,7 @@
</desc>
</func>
<func>
- <name>stop(ViewerPid) -> ok</name>
+ <name since="">stop(ViewerPid) -> ok</name>
<fsummary>Stops a viewer</fsummary>
<type>
<v>ViewerPid = pid()</v>
diff --git a/lib/et/doc/src/notes.xml b/lib/et/doc/src/notes.xml
index 110d3b2110..e144defb69 100644
--- a/lib/et/doc/src/notes.xml
+++ b/lib/et/doc/src/notes.xml
@@ -37,6 +37,23 @@
one section in this document. The title of each section is the
version number of <c>Event Tracer (ET)</c>.</p>
+<section><title>ET 1.6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The scroll bar of the et_viewer window could not be
+ dragged all the way to the top of the window. It would
+ always stop at the second event. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15463 Aux Id: ERL-780 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>ET 1.6.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/et/src/et_wx_viewer.erl b/lib/et/src/et_wx_viewer.erl
index 4dd44e7a4c..041527fec4 100644
--- a/lib/et/src/et_wx_viewer.erl
+++ b/lib/et/src/et_wx_viewer.erl
@@ -793,8 +793,8 @@ handle_info(#wx{event = #wxScroll{type = scroll_changed}} = Wx, S) ->
N = round(S#state.n_events * Pos / Range),
Diff =
case N - event_pos(S) of
- D when D < 0 -> D - 1;
- D -> D + 1
+ D when D < 0 -> D;
+ D -> D
end,
S2 = scroll_changed(S, Diff),
noreply(S2);
@@ -1002,7 +1002,7 @@ scroll_changed(S, Expected) ->
scroll_first(S);
last ->
scroll_last(S)
- end;
+ end;
true ->
%% Down
OldPos = event_pos(S),
@@ -1018,19 +1018,24 @@ scroll_changed(S, Expected) ->
end.
jump_up(S, OldKey, OldPos, NewPos) ->
- Try = NewPos - OldPos,
+ Try = NewPos - OldPos -1,
Order = S#state.event_order,
- Fun = fun(Event, #e{pos = P}) when P >= NewPos ->
- Key = et_collector:make_key(Order, Event),
- #e{event = Event, key = Key, pos = P - 1};
- (_, Acc) ->
- Acc
- end,
- PrevE = et_collector:iterate(S#state.collector_pid,
- OldKey,
- Try,
- Fun,
- #e{key = OldKey, pos = OldPos}),
+ PrevE =
+ if NewPos =:= 0 ->
+ first;
+ true ->
+ Fun = fun(Event, #e{pos = P}) when P >= NewPos ->
+ Key = et_collector:make_key(Order, Event),
+ #e{event = Event, key = Key, pos = P - 1};
+ (_E, Acc) ->
+ Acc
+ end,
+ et_collector:iterate(S#state.collector_pid,
+ OldKey,
+ Try,
+ Fun,
+ #e{key = OldKey, pos = OldPos})
+ end,
case collect_more_events(S, PrevE, S#state.events_per_page) of
{_, []} ->
S;
@@ -2013,7 +2018,7 @@ update_scroll_bar(#state{scroll_bar = ScrollBar,
PixelsPerEvent = Range / EventsPerPage,
Share = EventsPerPage / N,
wxScrollBar:setScrollbar(ScrollBar,
- trunc(EventPos * Share * PixelsPerEvent),
+ trunc(EventPos * Share * PixelsPerEvent),
round(Share * Range),
Range,
round(Share * Range),
diff --git a/lib/et/vsn.mk b/lib/et/vsn.mk
index 9563a38000..d5416f1ea9 100644
--- a/lib/et/vsn.mk
+++ b/lib/et/vsn.mk
@@ -1 +1 @@
-ET_VSN = 1.6.3
+ET_VSN = 1.6.4
diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml
index 34e3ff84b0..ead2367925 100644
--- a/lib/ftp/doc/src/ftp.xml
+++ b/lib/ftp/doc/src/ftp.xml
@@ -29,7 +29,7 @@
<rev>B</rev>
<file>ftp.xml</file>
</header>
- <module>ftp</module>
+ <module since="">ftp</module>
<modulesummary>A File Transfer Protocol client.</modulesummary>
<description>
@@ -272,7 +272,7 @@
<funcs>
<func>
- <name>account(Pid, Account) -> ok | {error, Reason}</name>
+ <name since="">account(Pid, Account) -> ok | {error, Reason}</name>
<fsummary>Specifies which account to use.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -289,8 +289,8 @@
</func>
<func>
- <name>append(Pid, LocalFile) -> </name>
- <name>append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">append(Pid, LocalFile) -> </name>
+ <name since="">append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a file to remote server, and appends it to
<c>Remotefile</c>.</fsummary>
<type>
@@ -310,7 +310,7 @@
</func>
<func>
- <name>append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a binary into a remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -328,7 +328,7 @@
</func>
<func>
- <name>append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <name since="">append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
<fsummary>Appends a chunk to the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -348,7 +348,7 @@
</func>
<func>
- <name>append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <name since="">append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
<fsummary>Starts transfer of file chunks for appending to <c>File</c>.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -365,7 +365,7 @@
</func>
<func>
- <name>append_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <name since="">append_chunk_end(Pid) -> ok | {error, Reason}</name>
<fsummary>Stops transfer of chunks for appending.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -381,7 +381,7 @@
</func>
<func>
- <name>cd(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">cd(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Changes remote working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -397,7 +397,7 @@
</func>
<func>
- <name>close(Pid) -> ok</name>
+ <name since="">close(Pid) -> ok</name>
<fsummary>Ends the FTP session.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -411,7 +411,7 @@
</func>
<func>
- <name>delete(Pid, File) -> ok | {error, Reason}</name>
+ <name since="">delete(Pid, File) -> ok | {error, Reason}</name>
<fsummary>Deletes a file at the remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -426,7 +426,7 @@
</func>
<func>
- <name>formaterror(Tag) -> string()</name>
+ <name since="">formaterror(Tag) -> string()</name>
<fsummary>Returns error diagnostics.</fsummary>
<type>
<v>Tag = {error, atom()} | atom()</v>
@@ -440,7 +440,7 @@
</func>
<func>
- <name>lcd(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">lcd(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Changes local working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -455,7 +455,7 @@
</func>
<func>
- <name>lpwd(Pid) -> {ok, Dir}</name>
+ <name since="">lpwd(Pid) -> {ok, Dir}</name>
<fsummary>Gets local current working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -470,8 +470,8 @@
</func>
<func>
- <name>ls(Pid) -> </name>
- <name>ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <name since="">ls(Pid) -> </name>
+ <name since="">ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
<fsummary>List of files.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -493,7 +493,7 @@
</func>
<func>
- <name>mkdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">mkdir(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Creates a remote directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -510,8 +510,8 @@
</func>
<func>
- <name>nlist(Pid) -> </name>
- <name>nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <name since="">nlist(Pid) -> </name>
+ <name since="">nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
<fsummary>List of files.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -535,8 +535,8 @@
</func>
<func>
- <name>open(Host) -> {ok, Pid} | {error, Reason}</name>
- <name>open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">open(Host) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Starts a standalone FTP client.</fsummary>
<type>
<v>Host = string() | ip_address()</v>
@@ -587,7 +587,7 @@
</func>
<func>
- <name>pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
+ <name since="">pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
<fsummary>Gets the remote current working directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -603,8 +603,8 @@
</func>
<func>
- <name>recv(Pid, RemoteFile) -> </name>
- <name>recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
+ <name since="">recv(Pid, RemoteFile) -> </name>
+ <name since="">recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a file from remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -627,7 +627,7 @@
</func>
<func>
- <name>recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
+ <name since="">recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
<fsummary>Transfers a file from remote server as a binary.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -644,7 +644,7 @@
</func>
<func>
- <name>recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Starts chunk-reading of the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -660,7 +660,7 @@
</func>
<func>
- <name>recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
+ <name since="">recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
<fsummary>Receives a chunk of the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -682,7 +682,7 @@
</func>
<func>
- <name>rename(Pid, Old, New) -> ok | {error, Reason}</name>
+ <name since="">rename(Pid, Old, New) -> ok | {error, Reason}</name>
<fsummary>Renames a file at the remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -697,7 +697,7 @@
</func>
<func>
- <name>rmdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <name since="">rmdir(Pid, Dir) -> ok | {error, Reason}</name>
<fsummary>Removes a remote directory.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -714,8 +714,8 @@
</func>
<func>
- <name>send(Pid, LocalFile) -></name>
- <name>send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">send(Pid, LocalFile) -></name>
+ <name since="">send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a file to the remote server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -732,7 +732,7 @@
</func>
<func>
- <name>send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <name since="">send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
<fsummary>Transfers a binary into a remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -749,7 +749,7 @@
</func>
<func>
- <name>send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <name since="">send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
<fsummary>Writes a chunk to the remote file.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -769,7 +769,7 @@
</func>
<func>
- <name>send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <name since="">send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
<fsummary>Starts transfer of file chunks.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -785,7 +785,7 @@
</func>
<func>
- <name>send_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <name since="">send_chunk_end(Pid) -> ok | {error, Reason}</name>
<fsummary>Stops transfer of chunks.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -801,7 +801,7 @@
</func>
<func>
- <name>start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
+ <name since="OTP 21.0">start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Dynamically starts an <c>FTP</c>
session after the <c>ftp</c> application has been started.</fsummary>
<type>
@@ -820,7 +820,7 @@
</func>
<func>
- <name>stop_service(Reference) -> ok | {error, Reason} </name>
+ <name since="OTP 21.0">stop_service(Reference) -> ok | {error, Reason} </name>
<fsummary>Stops an FTP session.</fsummary>
<type>
<v>Reference = pid() | term() - service-specified reference</v>
@@ -832,7 +832,7 @@
</func>
<func>
- <name>type(Pid, Type) -> ok | {error, Reason}</name>
+ <name since="">type(Pid, Type) -> ok | {error, Reason}</name>
<fsummary>Sets transfer type to <c>ascii</c>or <c>binary</c>.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -849,7 +849,7 @@
</func>
<func>
- <name>user(Pid, User, Password) -> ok | {error, Reason}</name>
+ <name since="">user(Pid, User, Password) -> ok | {error, Reason}</name>
<fsummary>User login.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -864,7 +864,7 @@
</func>
<func>
- <name>user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
+ <name since="">user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
<fsummary>User login.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -880,7 +880,7 @@
</func>
<func>
- <name>quote(Pid, Command) -> [FTPLine]</name>
+ <name since="">quote(Pid, Command) -> [FTPLine]</name>
<fsummary>Sends an arbitrary FTP command.</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index d9f58382bc..e9cdf42018 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -31,6 +31,22 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 3.18.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The code was updated to avoid causing a dialyzer
+ warning because of a tightened spec for
+ <c>beam_lib:info/1</c>.</p>
+ <p>
+ Own Id: OTP-15482</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.18.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index b51f17aff0..12d621bf01 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.18.1
+HIPE_VSN = 3.18.2
diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml
index 2dec5acbf9..6d3547f4fe 100644
--- a/lib/inets/doc/src/http_uri.xml
+++ b/lib/inets/doc/src/http_uri.xml
@@ -30,7 +30,7 @@
<rev></rev>
</header>
- <module>http_uri</module>
+ <module since="OTP R15B01">http_uri</module>
<modulesummary>URI utility module</modulesummary>
<description>
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name>decode(HexEncodedURI) -> URI</name>
+ <name since="OTP R15B01">decode(HexEncodedURI) -> URI</name>
<fsummary>Decodes a hexadecimal encoded URI.</fsummary>
<type>
@@ -93,7 +93,7 @@
</desc>
</func>
<func>
- <name>encode(URI) -> HexEncodedURI</name>
+ <name since="OTP R15B01">encode(URI) -> HexEncodedURI</name>
<fsummary>Encodes a hexadecimal encoded URI.</fsummary>
<type>
@@ -109,8 +109,8 @@
</func>
<func>
- <name>parse(URI) -> {ok, Result} | {error, Reason}</name>
- <name>parse(URI, Options) -> {ok, Result} | {error, Reason}</name>
+ <name since="OTP R15B01">parse(URI) -> {ok, Result} | {error, Reason}</name>
+ <name since="OTP R15B01">parse(URI, Options) -> {ok, Result} | {error, Reason}</name>
<fsummary>Parses a URI.</fsummary>
<type>
<v>URI = uri()</v>
@@ -165,7 +165,7 @@ fun(SchemeStr :: string() | binary()) ->
</func>
<func>
- <name>scheme_defaults() -> SchemeDefaults</name>
+ <name since="OTP R15B01">scheme_defaults() -> SchemeDefaults</name>
<fsummary>A list of the scheme and their default ports.</fsummary>
<type>
<v>SchemeDefaults = [{scheme(), default_scheme_port_number()}] </v>
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index a2871f3b95..7451b314ec 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -30,7 +30,7 @@
<rev></rev>
</header>
- <module>httpc</module>
+ <module since="OTP R13B04">httpc</module>
<modulesummary>An HTTP/1.1 client</modulesummary>
<description>
@@ -151,8 +151,8 @@
<funcs>
<func>
- <name>cancel_request(RequestId) -></name>
- <name>cancel_request(RequestId, Profile) -> ok</name>
+ <name since="OTP R13B04">cancel_request(RequestId) -></name>
+ <name since="OTP R13B04">cancel_request(RequestId, Profile) -> ok</name>
<fsummary>Cancels an asynchronous HTTP request.</fsummary>
<type>
<v>RequestId = request_id() - A unique identifier as returned
@@ -169,9 +169,9 @@
</func>
<func>
- <name>cookie_header(Url) -> </name>
- <name>cookie_header(Url, Profile | Opts) -> header() | {error, Reason}</name>
- <name>cookie_header(Url, Opts, Profile) -> header() | {error, Reason}</name>
+ <name since="OTP R13B04">cookie_header(Url) -> </name>
+ <name since="OTP R13B04">cookie_header(Url, Profile | Opts) -> header() | {error, Reason}</name>
+ <name since="OTP R15B">cookie_header(Url, Opts, Profile) -> header() | {error, Reason}</name>
<fsummary>Returns the cookie header that would have been sent when
making a request to URL using the profile <c>Profile</c>.</fsummary>
<type>
@@ -193,8 +193,8 @@
</func>
<func>
- <name>get_options(OptionItems) -> {ok, Values} | {error, Reason}</name>
- <name>get_options(OptionItems, Profile) -> {ok, Values} | {error, Reason}</name>
+ <name since="OTP R15B01">get_options(OptionItems) -> {ok, Values} | {error, Reason}</name>
+ <name since="OTP R15B01">get_options(OptionItems, Profile) -> {ok, Values} | {error, Reason}</name>
<fsummary>Gets the currently used options.</fsummary>
<type>
<v>OptionItems = all | [option_item()]</v>
@@ -223,8 +223,8 @@
</func>
<func>
- <name>info() -> list()</name>
- <name>info(Profile) -> list()</name>
+ <name since="OTP R15B02">info() -> list()</name>
+ <name since="OTP R15B02">info(Profile) -> list()</name>
<fsummary>Produces a list of miscellaneous information.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
@@ -239,8 +239,8 @@
<func>
- <name>reset_cookies() -> void()</name>
- <name>reset_cookies(Profile) -> void()</name>
+ <name since="OTP R13B04">reset_cookies() -> void()</name>
+ <name since="OTP R13B04">reset_cookies(Profile) -> void()</name>
<fsummary>Resets the cookie database.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
@@ -254,8 +254,8 @@
</func>
<func>
- <name>request(Url) -> </name>
- <name>request(Url, Profile) -> {ok, Result} | {error, Reason}</name>
+ <name since="OTP R13B04">request(Url) -> </name>
+ <name since="OTP R13B04">request(Url, Profile) -> {ok, Result} | {error, Reason}</name>
<fsummary>Sends a get HTTP request.</fsummary>
<type>
<v>Url = url()</v>
@@ -272,8 +272,8 @@
</func>
<func>
- <name>request(Method, Request, HTTPOptions, Options) -></name>
- <name>request(Method, Request, HTTPOptions, Options, Profile) -> {ok, Result} | {ok, saved_to_file} | {error, Reason}</name>
+ <name since="OTP R13B04">request(Method, Request, HTTPOptions, Options) -></name>
+ <name since="OTP R13B04">request(Method, Request, HTTPOptions, Options, Profile) -> {ok, Result} | {ok, saved_to_file} | {error, Reason}</name>
<fsummary>Sends an HTTP request.</fsummary>
<type>
@@ -521,8 +521,8 @@
<func>
- <name>set_options(Options) -> </name>
- <name>set_options(Options, Profile) -> ok | {error, Reason}</name>
+ <name since="OTP R13B04">set_options(Options) -> </name>
+ <name since="OTP R13B04">set_options(Options, Profile) -> ok | {error, Reason}</name>
<fsummary>Sets options to be used for subsequent requests.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -639,8 +639,8 @@
</func>
<func>
- <name>store_cookies(SetCookieHeaders, Url) -> </name>
- <name>store_cookies(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name>
+ <name since="OTP R14B02">store_cookies(SetCookieHeaders, Url) -> </name>
+ <name since="OTP R14B02">store_cookies(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name>
<fsummary>Saves the cookies defined in <c>SetCookieHeaders</c> in the
client profile cookie database.</fsummary>
<type>
@@ -658,7 +658,7 @@
</func>
<func>
- <name>stream_next(Pid) -> ok</name>
+ <name since="OTP R13B04">stream_next(Pid) -> ok</name>
<fsummary>Triggers the next message to be streamed, that is,
the same behavior as active one for sockets.
</fsummary>
@@ -676,8 +676,8 @@
</func>
<func>
- <name>which_cookies() -> cookies()</name>
- <name>which_cookies(Profile) -> cookies()</name>
+ <name since="OTP R13B04">which_cookies() -> cookies()</name>
+ <name since="OTP R13B04">which_cookies(Profile) -> cookies()</name>
<fsummary>Dumps the entire cookie database.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
@@ -695,8 +695,8 @@
</func>
<func>
- <name>which_sessions() -> session_info()</name>
- <name>which_sessions(Profile) -> session_info()</name>
+ <name since="OTP R15B02">which_sessions() -> session_info()</name>
+ <name since="OTP R15B02">which_sessions(Profile) -> session_info()</name>
<fsummary>Produces a slightly processed dump of the sessions database.</fsummary>
<type>
<v>Profile = profile() | pid()</v>
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index e5adb4eb20..1750078db0 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>httpd.sgml</file>
</header>
- <module>httpd</module>
+ <module since="">httpd</module>
<modulesummary>
HTTP server API
</modulesummary>
@@ -874,8 +874,8 @@ text/plain asc txt</pre>
<funcs>
<func>
- <name>info(Pid) -></name>
- <name>info(Pid, Properties) -> [{Option, Value}]</name>
+ <name since="">info(Pid) -></name>
+ <name since="">info(Pid, Properties) -> [{Option, Value}]</name>
<fsummary>Fetches information about the HTTP server.</fsummary>
<type>
<v>Properties = [property()]</v>
@@ -899,10 +899,10 @@ text/plain asc txt</pre>
</func>
<func>
- <name>info(Address, Port) -> </name>
- <name>info(Address, Port, Profile) -> </name>
- <name>info(Address, Port, Profile, Properties) -> [{Option, Value}] </name>
- <name>info(Address, Port, Properties) -> [{Option, Value}] </name>
+ <name since="">info(Address, Port) -> </name>
+ <name since="">info(Address, Port, Profile) -> </name>
+ <name since="OTP 18.0">info(Address, Port, Profile, Properties) -> [{Option, Value}] </name>
+ <name since="">info(Address, Port, Properties) -> [{Option, Value}] </name>
<fsummary>Fetches information about the HTTP server.</fsummary>
<type>
<v>Address = ip_address()</v>
@@ -927,7 +927,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>reload_config(Config, Mode) -> ok | {error, Reason}</name>
+ <name since="">reload_config(Config, Mode) -> ok | {error, Reason}</name>
<fsummary>Reloads the HTTP server configuration without
restarting the server.</fsummary>
<type>
@@ -1051,7 +1051,7 @@ text/plain asc txt</pre>
</section>
<funcs>
<func>
- <name>Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name>
+ <name since="">Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name>
<fsummary>Called for each request to the web server.</fsummary>
<type>
<v>OldData = list()</v>
@@ -1105,7 +1105,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name>
+ <name since="">Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name>
<fsummary>Converts a line in an Apache-like config
file to an <c>{Option, Value}</c> tuple.</fsummary>
<type>
@@ -1128,7 +1128,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>Module:remove(ConfigDB) -> ok | {error, Reason} </name>
+ <name since="">Module:remove(ConfigDB) -> ok | {error, Reason} </name>
<fsummary>Callback function that is called when the web server is closed.</fsummary>
<type>
<v>ConfigDB = ets_table()</v>
@@ -1143,7 +1143,7 @@ text/plain asc txt</pre>
</func>
<func>
- <name>Module:store({Option, Value}, Config)-> {ok, {Option, NewValue}} | {error, Reason}</name>
+ <name since="">Module:store({Option, Value}, Config)-> {ok, {Option, NewValue}} | {error, Reason}</name>
<fsummary>Checks the validity of the configuration options.</fsummary>
<type>
<v>Line = string()</v>
@@ -1171,7 +1171,7 @@ text/plain asc txt</pre>
</section>
<funcs>
<func>
- <name>parse_query(QueryString) -> [{Key,Value}]</name>
+ <name since="">parse_query(QueryString) -> [{Key,Value}]</name>
<fsummary>Parses incoming data to <c>erl</c> and <c>eval</c> scripts.</fsummary>
<type>
<v>QueryString = string()</v>
diff --git a/lib/inets/doc/src/httpd_custom_api.xml b/lib/inets/doc/src/httpd_custom_api.xml
index d2e5441895..2c0f92ff83 100644
--- a/lib/inets/doc/src/httpd_custom_api.xml
+++ b/lib/inets/doc/src/httpd_custom_api.xml
@@ -25,7 +25,7 @@
<title>httpd_custom_api</title>
<file>httpd_custom_api.xml</file>
</header>
- <module>httpd_custom_api</module>
+ <module since="OTP 17.5.6">httpd_custom_api</module>
<modulesummary>Behaviour with optional callbacks to customize the inets HTTP server.</modulesummary>
<description>
<p> The module implementing this behaviour shall be supplied to to the servers
@@ -34,7 +34,7 @@
</description>
<funcs>
<func>
- <name>response_default_headers() -> [Header] </name>
+ <name since="OTP 18.1.1">response_default_headers() -> [Header] </name>
<fsummary>Provide default headers for the HTTP servers responses.</fsummary>
<type>
<v>Header = {HeaderName :: string(), HeaderValue::string()}</v>
@@ -48,7 +48,7 @@
</func>
<func>
- <name>response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
+ <name since="OTP 17.5.6">response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
<fsummary>Filter and possible alter HTTP response headers.</fsummary>
<type>
<v>Header = {HeaderName :: string(), HeaderValue::string()}</v>
@@ -61,7 +61,7 @@
</func>
<func>
- <name>request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
+ <name since="OTP 17.5.6">request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name>
<fsummary>Filter and possible alter HTTP request headers.</fsummary>
<type>
<v>Header = {HeaderName :: string(), HeaderValue::string()}</v>
diff --git a/lib/inets/doc/src/httpd_socket.xml b/lib/inets/doc/src/httpd_socket.xml
index d3aa82a540..22ead06f38 100644
--- a/lib/inets/doc/src/httpd_socket.xml
+++ b/lib/inets/doc/src/httpd_socket.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>httpd_socket.sgml</file>
</header>
- <module>httpd_socket</module>
+ <module since="">httpd_socket</module>
<modulesummary>Communication utility functions to be used by the Erlang
web server API programmer.</modulesummary>
<description>
@@ -43,7 +43,7 @@
<funcs>
<func>
- <name>deliver(SocketType, Socket, Data) -> Result</name>
+ <name since="">deliver(SocketType, Socket, Data) -> Result</name>
<fsummary>Sends binary data over socket.</fsummary>
<type>
<v>SocketType = socket_type()</v>
@@ -63,7 +63,7 @@
</func>
<func>
- <name>peername(SocketType,Socket) -> {Port,IPAddress}</name>
+ <name since="">peername(SocketType,Socket) -> {Port,IPAddress}</name>
<fsummary>Returns the port and IP address of the remote socket.</fsummary>
<type>
<v>SocketType = socket_type()</v>
@@ -81,7 +81,7 @@
</func>
<func>
- <name>resolve() -> HostName</name>
+ <name since="">resolve() -> HostName</name>
<fsummary>Returns the official name of the current host.</fsummary>
<type>
<v>HostName = string()</v>
diff --git a/lib/inets/doc/src/httpd_util.xml b/lib/inets/doc/src/httpd_util.xml
index 220a2ede35..e0f947f860 100644
--- a/lib/inets/doc/src/httpd_util.xml
+++ b/lib/inets/doc/src/httpd_util.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>httpd_util.sgml</file>
</header>
- <module>httpd_util</module>
+ <module since="">httpd_util</module>
<modulesummary>Miscellaneous utility functions to be used when implementing
Erlang web server API modules.</modulesummary>
<description>
@@ -41,12 +41,11 @@
<funcs>
<func>
- <name>convert_request_date(DateString) -> ErlDate|bad_date</name>
+ <name since="">convert_request_date(DateString) -> ErlDate|bad_date</name>
<fsummary>Converts the date to the Erlang date format.</fsummary>
<type>
<v>DateString = string()</v>
- <v>ErlDate = {{Year,Month,Date},{Hour,Min,Sec}}</v>
- <v>Year = Month = Date = Hour = Min = Sec = integer()</v>
+ <v>ErlDate = calendar:datetime() </v>
</type>
<desc>
<p><c>convert_request_date/1</c> converts <c>DateString</c> to
@@ -57,7 +56,7 @@
</func>
<func>
- <name>create_etag(FileInfo) -> Etag</name>
+ <name since="">create_etag(FileInfo) -> Etag</name>
<fsummary>Calculates the Etag for a file.</fsummary>
<type>
<v>FileInfo = file_info()</v>
@@ -71,7 +70,7 @@
</func>
<func>
- <name>day(NthDayOfWeek) -> DayOfWeek</name>
+ <name since="">day(NthDayOfWeek) -> DayOfWeek</name>
<fsummary>Converts the day of the week
(integer [1-7]) to an abbreviated string.</fsummary>
<type>
@@ -87,7 +86,7 @@
</func>
<func>
- <name>decode_hex(HexValue) -> DecValue</name>
+ <name since="">decode_hex(HexValue) -> DecValue</name>
<fsummary>Converts a hexadecimal value into its decimal equivalent.</fsummary>
<type>
<v>HexValue = DecValue = string()</v>
@@ -99,7 +98,7 @@
</func>
<func>
- <name>flatlength(NestedList) -> Size</name>
+ <name since="">flatlength(NestedList) -> Size</name>
<fsummary>Computes the size of a possibly nested list.</fsummary>
<type>
<v>NestedList = list()</v>
@@ -112,7 +111,7 @@
</func>
<func>
- <name>hexlist_to_integer(HexString) -> Number</name>
+ <name since="">hexlist_to_integer(HexString) -> Number</name>
<fsummary>Converts a hexadecimal string to an integer.</fsummary>
<type>
<v>Number = integer()</v>
@@ -125,7 +124,7 @@
</func>
<func>
- <name>integer_to_hexlist(Number) -> HexString</name>
+ <name since="">integer_to_hexlist(Number) -> HexString</name>
<fsummary>Converts an integer to a hexadecimal string.</fsummary>
<type>
<v>Number = integer()</v>
@@ -138,8 +137,8 @@
</func>
<func>
- <name>lookup(ETSTable,Key) -> Result</name>
- <name>lookup(ETSTable,Key,Undefined) -> Result</name>
+ <name since="">lookup(ETSTable,Key) -> Result</name>
+ <name since="">lookup(ETSTable,Key,Undefined) -> Result</name>
<fsummary>Extracts the first value associated with a <c>Key</c>
in an ETS table.</fsummary>
<type>
@@ -160,8 +159,8 @@
</func>
<func>
- <name>lookup_mime(ConfigDB,Suffix)</name>
- <name>lookup_mime(ConfigDB,Suffix,Undefined) -> MimeType</name>
+ <name since="">lookup_mime(ConfigDB,Suffix)</name>
+ <name since="">lookup_mime(ConfigDB,Suffix,Undefined) -> MimeType</name>
<fsummary>Returns the MIME type associated with a specific file suffix.</fsummary>
<type>
<v>ConfigDB = ets_table()</v>
@@ -179,8 +178,8 @@
</func>
<func>
- <name>lookup_mime_default(ConfigDB,Suffix)</name>
- <name>lookup_mime_default(ConfigDB,Suffix,Undefined) -> MimeType</name>
+ <name since="">lookup_mime_default(ConfigDB,Suffix)</name>
+ <name since="">lookup_mime_default(ConfigDB,Suffix,Undefined) -> MimeType</name>
<fsummary>Returns the MIME type associated with a specific file suffix
or the value of the DefaultType.</fsummary>
<type>
@@ -201,7 +200,7 @@
</func>
<func>
- <name>message(StatusCode,PhraseArgs,ConfigDB) -> Message</name>
+ <name since="">message(StatusCode,PhraseArgs,ConfigDB) -> Message</name>
<fsummary>Returns an informative HTTP 1.1 status string in HTML.</fsummary>
<type>
<v>StatusCode = 301 | 400 | 403 | 404 | 500 | 501 | 504</v>
@@ -236,7 +235,7 @@
</func>
<func>
- <name>month(NthMonth) -> Month</name>
+ <name since="">month(NthMonth) -> Month</name>
<fsummary>Converts the month as an integer (1-12) to an abbreviated string.</fsummary>
<type>
<v>NthMonth = 1-12</v>
@@ -250,7 +249,7 @@
</func>
<func>
- <name>multi_lookup(ETSTable,Key) -> Result</name>
+ <name since="">multi_lookup(ETSTable,Key) -> Result</name>
<fsummary>Extracts the values associated with a key in an ETS table.</fsummary>
<type>
<v>ETSTable = ets_table()</v>
@@ -265,7 +264,7 @@
</func>
<func>
- <name>reason_phrase(StatusCode) -> Description</name>
+ <name since="">reason_phrase(StatusCode) -> Description</name>
<fsummary>Returns the description of an HTTP 1.1 status code.</fsummary>
<type>
<v>StatusCode = 100| 200 | 201 | 202 | 204 | 205 | 206 | 300 | 301 | 302 | 303 | 304 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 410 411 | 412 | 413 | 414 415 | 416 | 417 | 500 | 501 | 502 | 503 | 504 | 505</v>
@@ -280,11 +279,11 @@
</func>
<func>
- <name>rfc1123_date() -> RFC1123Date</name>
- <name>rfc1123_date({{YYYY,MM,DD},{Hour,Min,Sec}}) -> RFC1123Date</name>
+ <name since="">rfc1123_date() -> RFC1123Date</name>
+ <name since="">rfc1123_date(Date) -> RFC1123Date</name>
<fsummary>Returns the current date in RFC 1123 format.</fsummary>
<type>
- <v>YYYY = MM = DD = Hour = Min = Sec = integer()</v>
+ <v> Date = calendar:datetime()</v>
<v>RFC1123Date = string()</v>
</type>
<desc>
@@ -295,7 +294,7 @@
</func>
<func>
- <name>split(String,RegExp,N) -> SplitRes</name>
+ <name since="">split(String,RegExp,N) -> SplitRes</name>
<fsummary>Splits a string in N chunks using a regular expression.</fsummary>
<type>
<v>String = RegExp = string()</v>
@@ -313,7 +312,7 @@
</func>
<func>
- <name>split_script_path(RequestLine) -> Splitted</name>
+ <name since="">split_script_path(RequestLine) -> Splitted</name>
<fsummary>Splits a <c>RequestLine</c> in a file reference to an executable,
and a <c>QueryString</c> or a <c>PathInfo</c>string.</fsummary>
<type>
@@ -330,7 +329,7 @@
</func>
<func>
- <name>split_path(RequestLine) -> {Path,QueryStringOrPathInfo}</name>
+ <name since="">split_path(RequestLine) -> {Path,QueryStringOrPathInfo}</name>
<fsummary>Splits a <c>RequestLine</c> in a file reference, and a
<c>QueryString</c> or a <c>PathInfo</c> string.</fsummary>
<type>
@@ -356,7 +355,7 @@
</func>
<func>
- <name>strip(String) -> Stripped</name>
+ <name since="">strip(String) -> Stripped</name>
<fsummary>Returns <c>String</c> where the leading and trailing space
tabs are removed.</fsummary>
<type>
@@ -370,7 +369,7 @@
</func>
<func>
- <name>suffix(FileName) -> Suffix</name>
+ <name since="">suffix(FileName) -> Suffix</name>
<fsummary>Extracts the file suffix from a given filename.</fsummary>
<type>
<v>FileName = Suffix = string()</v>
diff --git a/lib/inets/doc/src/inets.xml b/lib/inets/doc/src/inets.xml
index 9b0ffaad5e..176af3137a 100644
--- a/lib/inets/doc/src/inets.xml
+++ b/lib/inets/doc/src/inets.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>inets</module>
+ <module since="">inets</module>
<modulesummary>The Inets services API.</modulesummary>
<description>
<p>This module provides the most basic API to the
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name>services() -> [{Service, Pid}]</name>
+ <name since="">services() -> [{Service, Pid}]</name>
<fsummary>Returns a list of currently running services.</fsummary>
<type>
<v>Service = service()</v>
@@ -68,7 +68,7 @@
</func>
<func>
- <name>services_info() -> [{Service, Pid, Info}]</name>
+ <name since="">services_info() -> [{Service, Pid, Info}]</name>
<fsummary>Returns a list of currently running services where
each service is described by an <c>[{Option, Value}]</c>
list.</fsummary>
@@ -91,7 +91,7 @@
</func>
<func>
- <name>service_names() -> [Service] </name>
+ <name since="">service_names() -> [Service] </name>
<fsummary>Returns a list of available service names.</fsummary>
<type>
<v>Service = service()</v>
@@ -104,8 +104,8 @@
</func>
<func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="">start() -> </name>
+ <name since="">start(Type) -> ok | {error, Reason}</name>
<fsummary>Starts the <c>Inets</c> application.</fsummary>
<type>
<v>Type = permanent | transient | temporary</v>
@@ -120,8 +120,8 @@
</func>
<func>
- <name>start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
- <name>start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Dynamically starts an <c>Inets</c>
service after the <c>Inets</c> application has been started.</fsummary>
<type>
@@ -156,7 +156,7 @@
</func>
<func>
- <name>stop() -> ok </name>
+ <name since="">stop() -> ok </name>
<fsummary>Stops the <c>Inets</c> application.</fsummary>
<desc>
<p>Stops the <c>Inets</c> application. See also
@@ -167,7 +167,7 @@
</func>
<func>
- <name>stop(Service, Reference) -> ok | {error, Reason} </name>
+ <name since="">stop(Service, Reference) -> ok | {error, Reason} </name>
<fsummary>Stops a started service of the <c>Inets</c> application or takes
down a <c>stand_alone </c>service gracefully.</fsummary>
<type>
diff --git a/lib/inets/doc/src/mod_alias.xml b/lib/inets/doc/src/mod_alias.xml
index 6ae19700a5..ff57d49d08 100644
--- a/lib/inets/doc/src/mod_alias.xml
+++ b/lib/inets/doc/src/mod_alias.xml
@@ -29,7 +29,7 @@
<rev>2.2</rev>
<file>mod_alias.sgml</file>
</header>
- <module>mod_alias</module>
+ <module since="">mod_alias</module>
<modulesummary>URL aliasing.</modulesummary>
<description>
<p>Erlang web server internal API for handling of, for example,
@@ -40,7 +40,7 @@
<funcs>
<func>
- <name>default_index(ConfigDB, Path) -> NewPath</name>
+ <name since="">default_index(ConfigDB, Path) -> NewPath</name>
<fsummary>Returns a new path with the default resource or file appended.</fsummary>
<type>
<v>ConfigDB = config_db()</v>
@@ -64,7 +64,7 @@
</func>
<func>
- <name>path(PathData, ConfigDB, RequestURI) -> Path</name>
+ <name since="">path(PathData, ConfigDB, RequestURI) -> Path</name>
<fsummary>Returns the file path to a URL.</fsummary>
<type>
<v>PathData = interaction_data()</v>
@@ -89,7 +89,7 @@
</func>
<func>
- <name>real_name(ConfigDB, RequestURI, Aliases) -> Ret</name>
+ <name since="">real_name(ConfigDB, RequestURI, Aliases) -> Ret</name>
<fsummary>Expands a request URI using <c>Aliases</c> config directives.</fsummary>
<type>
<v>ConfigDB = config_db()</v>
@@ -120,7 +120,7 @@
</func>
<func>
- <name>real_script_name(ConfigDB, RequestURI, ScriptAliases) -> Ret</name>
+ <name since="">real_script_name(ConfigDB, RequestURI, ScriptAliases) -> Ret</name>
<fsummary>Expands a request URI using <c>ScriptAliases</c>
config directives.</fsummary>
<type>
diff --git a/lib/inets/doc/src/mod_auth.xml b/lib/inets/doc/src/mod_auth.xml
index c4f844622b..ad864ca4d1 100644
--- a/lib/inets/doc/src/mod_auth.xml
+++ b/lib/inets/doc/src/mod_auth.xml
@@ -29,7 +29,7 @@
<rev>2.3</rev>
<file>mod_auth.sgml</file>
</header>
- <module>mod_auth</module>
+ <module since="">mod_auth</module>
<modulesummary>User authentication using text files, Dets, or Mnesia database.</modulesummary>
<description>
<p>This module provides for basic user authentication using
@@ -38,9 +38,9 @@
<funcs>
<func>
- <name>add_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
- <name>add_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
- <name>add_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
+ <name since="">add_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Adds a user to a group.</fsummary>
<type>
<v>GroupName = string()</v>
@@ -65,9 +65,9 @@
</func>
<func>
- <name>add_user(UserName, Options) -> true| {error, Reason}</name>
- <name>add_user(UserName, Password, UserData, Port, Dir) -> true | {error, Reason}</name>
- <name>add_user(UserName, Password, UserData, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_user(UserName, Options) -> true| {error, Reason}</name>
+ <name since="">add_user(UserName, Password, UserData, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">add_user(UserName, Password, UserData, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Adds a user to the user database.</fsummary>
<type>
<v>UserName = string()</v>
@@ -92,8 +92,8 @@
</func>
<func>
- <name>delete_group(GroupName, Options) -> true | {error,Reason} &lt;name>delete_group(GroupName, Port, Dir) -> true | {error, Reason}</name>
- <name>delete_group(GroupName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group(GroupName, Options) -> true | {error,Reason} &lt;name>delete_group(GroupName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group(GroupName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Deletes a group.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -115,9 +115,9 @@
</func>
<func>
- <name>delete_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
- <name>delete_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
- <name>delete_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name>
+ <name since="">delete_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Removes a user from a group.</fsummary>
<type>
<v>GroupName = string()</v>
@@ -141,9 +141,9 @@
</func>
<func>
- <name>delete_user(UserName,Options) -> true | {error, Reason}</name>
- <name>delete_user(UserName, Port, Dir) -> true | {error, Reason}</name>
- <name>delete_user(UserName, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_user(UserName,Options) -> true | {error, Reason}</name>
+ <name since="">delete_user(UserName, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">delete_user(UserName, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Deletes a user from the user database.</fsummary>
<type>
<v>UserName = string()</v>
@@ -166,9 +166,9 @@
</func>
<func>
- <name>get_user(UserName,Options) -> {ok, #httpd_user} |{error, Reason}</name>
- <name>get_user(UserName, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
- <name>get_user(UserName, Address, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
+ <name since="">get_user(UserName,Options) -> {ok, #httpd_user} |{error, Reason}</name>
+ <name since="">get_user(UserName, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
+ <name since="">get_user(UserName, Address, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name>
<fsummary>Returns a user from the user database.</fsummary>
<type>
<v>UserName = string()</v>
@@ -190,9 +190,9 @@
</func>
<func>
- <name>list_groups(Options) -> {ok, Groups} | {error, Reason}</name>
- <name>list_groups(Port, Dir) -> {ok, Groups} | {error, Reason}</name>
- <name>list_groups(Address, Port, Dir) -> {ok, Groups} | {error, Reason}</name>
+ <name since="">list_groups(Options) -> {ok, Groups} | {error, Reason}</name>
+ <name since="">list_groups(Port, Dir) -> {ok, Groups} | {error, Reason}</name>
+ <name since="">list_groups(Address, Port, Dir) -> {ok, Groups} | {error, Reason}</name>
<fsummary>Lists all the groups.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -214,9 +214,9 @@
</func>
<func>
- <name>list_group_members(GroupName, Options) -> {ok, Users} | {error, Reason}</name>
- <name>list_group_members(GroupName, Port, Dir) -> {ok, Users} | {error, Reason}</name>
- <name>list_group_members(GroupName, Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_group_members(GroupName, Options) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_group_members(GroupName, Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_group_members(GroupName, Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
<fsummary>Lists the members of a group.</fsummary>
<type>
<v>GroupName = string()</v>
@@ -240,9 +240,9 @@
</func>
<func>
- <name>list_users(Options) -> {ok, Users} | {error, Reason}</name>
- <name>list_users(Port, Dir) -> {ok, Users} | {error, Reason}</name>
- <name>list_users(Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_users(Options) -> {ok, Users} | {error, Reason}</name>
+ <name since="OTP R14B01">list_users(Port, Dir) -> {ok, Users} | {error, Reason}</name>
+ <name since="">list_users(Address, Port, Dir) -> {ok, Users} | {error, Reason}</name>
<fsummary>Lists users in the user database.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -264,8 +264,8 @@
</func>
<func>
- <name>update_password(Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
- <name>update_password(Address,Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
+ <name since="">update_password(Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
+ <name since="">update_password(Address,Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name>
<fsummary>Changes <c>AuthAcessPassword</c>.</fsummary>
<type>
<v>Port = integer()</v>
diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml
index ede7dc8f7d..bc5f98068f 100644
--- a/lib/inets/doc/src/mod_esi.xml
+++ b/lib/inets/doc/src/mod_esi.xml
@@ -25,7 +25,7 @@
<title>mod_esi</title>
<file>mod_esi.sgml</file>
</header>
- <module>mod_esi</module>
+ <module since="">mod_esi</module>
<modulesummary>Erlang Server Interface</modulesummary>
<description>
<p>This module defines the Erlang Server Interface (ESI) API.
@@ -88,7 +88,7 @@
<funcs>
<func>
- <name>deliver(SessionID, Data) -> ok | {error, Reason}</name>
+ <name since="">deliver(SessionID, Data) -> ok | {error, Reason}</name>
<fsummary>Sends <c>Data</c> back to client.</fsummary>
<type>
<v>SessionID = term()</v>
@@ -121,7 +121,7 @@
<funcs>
<func>
- <name>Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name>
+ <name since="">Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name>
<fsummary>Creates a dynamic web page and returns it chunk by chunk
to the server process by calling <c>mod_esi:deliver/2</c>.</fsummary>
<type>
@@ -179,7 +179,7 @@
</func>
<func>
- <name>Module:Function(Env, Input)-> Response </name>
+ <name since="">Module:Function(Env, Input)-> Response </name>
<fsummary>Creates a dynamic web page and returns it as a list.
This function is deprecated and is only kept for backwards compatibility.</fsummary>
<type>
diff --git a/lib/inets/doc/src/mod_security.xml b/lib/inets/doc/src/mod_security.xml
index 6f3f3c048a..c26d7468c2 100644
--- a/lib/inets/doc/src/mod_security.xml
+++ b/lib/inets/doc/src/mod_security.xml
@@ -29,7 +29,7 @@
<rev>1.0</rev>
<file>mod_security.sgml</file>
</header>
- <module>mod_security</module>
+ <module since="">mod_security</module>
<modulesummary>Security Audit and Trailing Functionality</modulesummary>
<description>
<p>Security Audit and Trailing Functionality</p>
@@ -37,8 +37,8 @@
<funcs>
<func>
- <name>block_user(User, Port, Dir, Seconds) -> true | {error, Reason}</name>
- <name>block_user(User, Address, Port, Dir, Seconds) -> true | {error, Reason}</name>
+ <name since="">block_user(User, Port, Dir, Seconds) -> true | {error, Reason}</name>
+ <name since="">block_user(User, Address, Port, Dir, Seconds) -> true | {error, Reason}</name>
<fsummary>Blocks a user from access to a directory for a certain amount of time.</fsummary>
<type>
<v>User = string()</v>
@@ -56,10 +56,10 @@
</func>
<func>
- <name>list_auth_users(Port) -> Users | []</name>
- <name>list_auth_users(Address, Port) -> Users | []</name>
- <name>list_auth_users(Port, Dir) -> Users | []</name>
- <name>list_auth_users(Address, Port, Dir) -> Users | []</name>
+ <name since="">list_auth_users(Port) -> Users | []</name>
+ <name since="">list_auth_users(Address, Port) -> Users | []</name>
+ <name since="">list_auth_users(Port, Dir) -> Users | []</name>
+ <name since="">list_auth_users(Address, Port, Dir) -> Users | []</name>
<fsummary>Lists users that have authenticated within the <c>SecurityAuthTimeout</c>
time for a given address (if specified), port number, and directory
(if specified).</fsummary>
@@ -77,10 +77,10 @@
</desc>
</func>
<func>
- <name>list_blocked_users(Port) -> Users | []</name>
- <name>list_blocked_users(Address, Port) -> Users | []</name>
- <name>list_blocked_users(Port, Dir) -> Users | []</name>
- <name>list_blocked_users(Address, Port, Dir) -> Users | []</name>
+ <name since="">list_blocked_users(Port) -> Users | []</name>
+ <name since="">list_blocked_users(Address, Port) -> Users | []</name>
+ <name since="">list_blocked_users(Port, Dir) -> Users | []</name>
+ <name since="">list_blocked_users(Address, Port, Dir) -> Users | []</name>
<fsummary>Lists users that are currently blocked from access to a
specified port number, for a given address (if specified).</fsummary>
<type>
@@ -97,10 +97,10 @@
</func>
<func>
- <name>unblock_user(User, Port) -> true | {error, Reason}</name>
- <name>unblock_user(User, Address, Port) -> true | {error, Reason}</name>
- <name>unblock_user(User, Port, Dir) -> true | {error, Reason}</name>
- <name>unblock_user(User, Address, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Port) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Address, Port) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Port, Dir) -> true | {error, Reason}</name>
+ <name since="">unblock_user(User, Address, Port, Dir) -> true | {error, Reason}</name>
<fsummary>Removes a blocked user from the block list.</fsummary>
<type>
<v>User = string()</v>
@@ -129,8 +129,8 @@
<funcs>
<func>
- <name>Module:event(What, Port, Dir, Data) -> ignored</name>
- <name>Module:event(What, Address, Port, Dir, Data) -> ignored</name>
+ <name since="OTP 18.1">Module:event(What, Port, Dir, Data) -> ignored</name>
+ <name since="OTP 18.1">Module:event(What, Address, Port, Dir, Data) -> ignored</name>
<fsummary>Called whenever an event occurs in <c>mod_security</c>.</fsummary>
<type>
<v>What = atom()</v>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index e34dd78a2a..dd8c4115a5 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.0.2</title>
+ <section><title>Inets 7.0.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed http client to not send 'content-length' header in
+ chunked encoded requests.</p>
+ <p>
+ Own Id: OTP-15338 Aux Id: ERL-733 </p>
+ </item>
+ <item>
+ <p>
+ Fixed http client to not drop explicit 'Content-Type'
+ header in requests without a body such as requests with
+ the 'Content-Type' of application/x-www-form-urlencoded.</p>
+ <p>
+ Own Id: OTP-15339 Aux Id: ERL-736 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.0.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index 1bf5d25c98..8b356d8026 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -805,11 +805,12 @@ handle_unix_socket_options(#request{unix_socket = UnixSocket},
error({badarg, [{ipfamily, Else}, {unix_socket, UnixSocket}]})
end.
-connect_and_send_first_request(Address, Request, #state{options = Options0} = State) ->
+connect_and_send_first_request(Address, #request{ipv6_host_with_brackets = HasBrackets} = Request,
+ #state{options = Options0} = State) ->
SocketType = socket_type(Request),
ConnTimeout = (Request#request.settings)#http_options.connect_timeout,
Options = handle_unix_socket_options(Request, Options0),
- case connect(SocketType, Address, Options, ConnTimeout) of
+ case connect(SocketType, format_address(Address, HasBrackets), Options, ConnTimeout) of
{ok, Socket} ->
ClientClose =
httpc_request:is_client_closing(
@@ -1739,3 +1740,8 @@ update_session(ProfileName, #session{id = SessionId} = Session, Pos, Value) ->
end.
+format_address({Host, Port}, true) when is_list(Host)->
+ {ok, Address} = inet:parse_address(string:strip(string:strip(Host, right, $]), left, $[)),
+ {Address, Port};
+format_address(HostPort, _) ->
+ HostPort.
diff --git a/lib/inets/test/httpd_bench_SUITE.erl b/lib/inets/test/httpd_bench_SUITE.erl
index 4b549dcb5b..087516f56c 100644
--- a/lib/inets/test/httpd_bench_SUITE.erl
+++ b/lib/inets/test/httpd_bench_SUITE.erl
@@ -37,7 +37,8 @@
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() ->
- [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
+ [{timetrap, {minutes, 1}},
+ {ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
all() ->
[
@@ -499,8 +500,8 @@ start_dummy("http"= Protocol, Config) ->
Conf = [
%%{big, filename:join(DataDir, "1M_file")},
%%{small, filename:join(DataDir, "1k_file")},
- {big, {gen, crypto:rand_bytes(1000000)}},
- {small, {gen, crypto:rand_bytes(1000)}},
+ {big, {gen, crypto:strong_rand_bytes(1000000)}},
+ {small, {gen, crypto:strong_rand_bytes(1000)}},
{http_version, HTTPVersion},
{keep_alive, ?config(keep_alive, Config)}
],
@@ -519,8 +520,8 @@ start_dummy("https" = Protocol, Config) ->
Opts = [{active, true}, {nodelay, true}, {reuseaddr, true} | SSLOpts],
Conf = [%%{big, filename:join(DataDir, "1M_file")},
%%{small, filename:join(DataDir, "1k_file")},
- {big, {gen, crypto:rand_bytes(1000000)}},
- {small, {gen, crypto:rand_bytes(1000)}},
+ {big, {gen, crypto:strong_rand_bytes(1000000)}},
+ {small, {gen, crypto:strong_rand_bytes(1000)}},
{http_version, HTTPVersion},
{keep_alive, ?config(keep_alive, Config)}
],
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 26adb854e1..52c05a7974 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.0.2
+INETS_VSN = 7.0.3
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml
index 38c7b5acf1..4e32c1a3a5 100644
--- a/lib/kernel/doc/src/application.xml
+++ b/lib/kernel/doc/src/application.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>application</module>
+ <module since="">application</module>
<modulesummary>Generic OTP application functions</modulesummary>
<description>
<p>In OTP, <em>application</em> denotes a component implementing
@@ -67,8 +67,8 @@
</datatypes>
<funcs>
<func>
- <name name="ensure_all_started" arity="1"/>
- <name name="ensure_all_started" arity="2"/>
+ <name name="ensure_all_started" arity="1" since="OTP R16B02"/>
+ <name name="ensure_all_started" arity="2" since="OTP R16B02"/>
<fsummary>Load and start an application and its dependencies, recursively.</fsummary>
<desc>
<p>Equivalent to calling
@@ -85,8 +85,8 @@
</desc>
</func>
<func>
- <name name="ensure_started" arity="1"/>
- <name name="ensure_started" arity="2"/>
+ <name name="ensure_started" arity="1" since="OTP R16B01"/>
+ <name name="ensure_started" arity="2" since="OTP R16B01"/>
<fsummary>Load and start an application.</fsummary>
<desc>
<p>Equivalent to
@@ -95,8 +95,8 @@
</desc>
</func>
<func>
- <name name="get_all_env" arity="0"/>
- <name name="get_all_env" arity="1"/>
+ <name name="get_all_env" arity="0" since=""/>
+ <name name="get_all_env" arity="1" since=""/>
<fsummary>Get the configuration parameters for an application.</fsummary>
<desc>
<p>Returns the configuration parameters and their values for
@@ -108,8 +108,8 @@
</desc>
</func>
<func>
- <name name="get_all_key" arity="0"/>
- <name name="get_all_key" arity="1"/>
+ <name name="get_all_key" arity="0" since=""/>
+ <name name="get_all_key" arity="1" since=""/>
<fsummary>Get the application specification keys.</fsummary>
<desc>
<p>Returns the application specification keys and their values
@@ -122,8 +122,8 @@
</desc>
</func>
<func>
- <name name="get_application" arity="0"/>
- <name name="get_application" arity="1"/>
+ <name name="get_application" arity="0" since=""/>
+ <name name="get_application" arity="1" since=""/>
<fsummary>Get the name of an application containing a certain process or module.</fsummary>
<desc>
<p>Returns the name of the application to which the process
@@ -136,8 +136,8 @@
</desc>
</func>
<func>
- <name name="get_env" arity="1"/>
- <name name="get_env" arity="2"/>
+ <name name="get_env" arity="1" since=""/>
+ <name name="get_env" arity="2" since=""/>
<fsummary>Get the value of a configuration parameter.</fsummary>
<desc>
<p>Returns the value of configuration parameter <c><anno>Par</anno></c>
@@ -153,7 +153,7 @@
</desc>
</func>
<func>
- <name name="get_env" arity="3"/>
+ <name name="get_env" arity="3" since="OTP R16B"/>
<fsummary>Get the value of a configuration parameter using a default.</fsummary>
<desc>
<p>Works like <seealso marker="#get_env/2"><c>get_env/2</c></seealso> but returns
@@ -162,8 +162,8 @@
</desc>
</func>
<func>
- <name name="get_key" arity="1"/>
- <name name="get_key" arity="2"/>
+ <name name="get_key" arity="1" since=""/>
+ <name name="get_key" arity="2" since=""/>
<fsummary>Get the value of an application specification key.</fsummary>
<desc>
<p>Returns the value of the application specification key
@@ -180,8 +180,8 @@
</desc>
</func>
<func>
- <name name="load" arity="1"/>
- <name name="load" arity="2"/>
+ <name name="load" arity="1" since=""/>
+ <name name="load" arity="2" since=""/>
<fsummary>Load an application.</fsummary>
<type name="application_spec"/>
<type name="application_opt"/>
@@ -226,7 +226,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="loaded_applications" arity="0"/>
+ <name name="loaded_applications" arity="0" since=""/>
<fsummary>Get the currently loaded applications.</fsummary>
<desc>
<p>Returns a list with information about the applications, and included
@@ -238,7 +238,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="permit" arity="2"/>
+ <name name="permit" arity="2" since=""/>
<fsummary>Change the permission for an application to run at a node.</fsummary>
<desc>
<p>Changes the permission for <c><anno>Application</anno></c> to run at
@@ -271,8 +271,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="set_env" arity="3"/>
- <name name="set_env" arity="4"/>
+ <name name="set_env" arity="3" since=""/>
+ <name name="set_env" arity="4" since=""/>
<fsummary>Set the value of a configuration parameter.</fsummary>
<desc>
<p>Sets the value of configuration parameter <c><anno>Par</anno></c> for
@@ -302,8 +302,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="start" arity="1"/>
- <name name="start" arity="2"/>
+ <name name="start" arity="1" since=""/>
+ <name name="start" arity="2" since=""/>
<fsummary>Load and start an application.</fsummary>
<desc>
<p>Starts <c><anno>Application</anno></c>. If it is not loaded,
@@ -353,7 +353,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="start_type" arity="0"/>
+ <name name="start_type" arity="0" since=""/>
<fsummary>Get the start type of an ongoing application startup.</fsummary>
<desc>
<p>This function is intended to be called by a process belonging
@@ -370,7 +370,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since=""/>
<fsummary>Stop an application.</fsummary>
<desc>
<p>Stops <c><anno>Application</anno></c>. The application master calls
@@ -399,7 +399,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="takeover" arity="2"/>
+ <name name="takeover" arity="2" since=""/>
<fsummary>Take over a distributed application.</fsummary>
<desc>
<p>Takes over the distributed application
@@ -424,7 +424,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="unload" arity="1"/>
+ <name name="unload" arity="1" since=""/>
<fsummary>Unload an application.</fsummary>
<desc>
<p>Unloads the application specification for <c><anno>Application</anno></c>
@@ -435,8 +435,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="unset_env" arity="2"/>
- <name name="unset_env" arity="3"/>
+ <name name="unset_env" arity="2" since=""/>
+ <name name="unset_env" arity="3" since=""/>
<fsummary>Unset the value of a configuration parameter.</fsummary>
<desc>
<p>Removes the configuration parameter <c><anno>Par</anno></c> and its value
@@ -459,8 +459,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name name="which_applications" arity="0"/>
- <name name="which_applications" arity="1"/>
+ <name name="which_applications" arity="0" since=""/>
+ <name name="which_applications" arity="1" since=""/>
<fsummary>Get the currently running applications.</fsummary>
<desc>
<p>Returns a list with information about the applications that
@@ -484,7 +484,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</section>
<funcs>
<func>
- <name>Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name>
+ <name since="">Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name>
<fsummary>Start an application.</fsummary>
<type>
<v>StartType = <seealso marker="#type-start_type"><c>start_type()</c></seealso></v>
@@ -526,7 +526,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name>
+ <name since="">Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name>
<fsummary>Extended start of an application.</fsummary>
<type>
<v>Phase = atom()</v>
@@ -551,7 +551,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:prep_stop(State) -> NewState</name>
+ <name since="">Module:prep_stop(State) -> NewState</name>
<fsummary>Prepare an application for termination.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -569,7 +569,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:stop(State)</name>
+ <name since="">Module:stop(State)</name>
<fsummary>Clean up after termination of an application.</fsummary>
<type>
<v>State = term()</v>
@@ -585,7 +585,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code>
</desc>
</func>
<func>
- <name>Module:config_change(Changed, New, Removed) -> ok</name>
+ <name since="">Module:config_change(Changed, New, Removed) -> ok</name>
<fsummary>Update the configuration parameters for an application.</fsummary>
<type>
<v>Changed = [{Par,Val}]</v>
diff --git a/lib/kernel/doc/src/auth.xml b/lib/kernel/doc/src/auth.xml
index 5901446960..a57da18de9 100644
--- a/lib/kernel/doc/src/auth.xml
+++ b/lib/kernel/doc/src/auth.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>auth</module>
+ <module since="">auth</module>
<modulesummary>Erlang network authentication server.</modulesummary>
<description>
<p>This module is deprecated. For a description of the Magic
@@ -42,7 +42,7 @@
</datatypes>
<funcs>
<func>
- <name name="cookie" arity="0"/>
+ <name name="cookie" arity="0" since=""/>
<fsummary>Magic cookie for local node (deprecated).</fsummary>
<desc>
<p>Use
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name name="cookie" arity="1"/>
+ <name name="cookie" arity="1" since=""/>
<fsummary>Set the magic for the local node (deprecated).</fsummary>
<type_desc variable="TheCookie">
The cookie can also be specified as a list with a single atom element.
@@ -63,7 +63,7 @@
</desc>
</func>
<func>
- <name name="is_auth" arity="1"/>
+ <name name="is_auth" arity="1" since=""/>
<fsummary>Status of communication authorization (deprecated).</fsummary>
<desc>
<p>Returns <c>yes</c> if communication with <c><anno>Node</anno></c> is
@@ -76,7 +76,7 @@
</desc>
</func>
<func>
- <name>node_cookie([Node, Cookie]) -> yes | no</name>
+ <name since="">node_cookie([Node, Cookie]) -> yes | no</name>
<fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary>
<type>
<v>Node = node()</v>
@@ -88,7 +88,7 @@
</desc>
</func>
<func>
- <name name="node_cookie" arity="2"/>
+ <name name="node_cookie" arity="2" since=""/>
<fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary>
<desc>
<p>Sets the magic cookie of <c><anno>Node</anno></c> to
diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml
index 69ce4da61c..4aa9e8b9d2 100644
--- a/lib/kernel/doc/src/code.xml
+++ b/lib/kernel/doc/src/code.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>code</module>
+ <module since="">code</module>
<modulesummary>Erlang code server.</modulesummary>
<description>
<p>This module contains the interface to the Erlang
@@ -322,7 +322,7 @@ zip:create("mnesia-4.4.7.ez",
<funcs>
<func>
- <name name="set_path" arity="1"/>
+ <name name="set_path" arity="1" since=""/>
<fsummary>Set the code server search path.</fsummary>
<desc>
<p>Sets the code path to the list of directories <c><anno>Path</anno></c>.</p>
@@ -336,15 +336,15 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="get_path" arity="0"/>
+ <name name="get_path" arity="0" since=""/>
<fsummary>Return the code server search path.</fsummary>
<desc>
<p>Returns the code path.</p>
</desc>
</func>
<func>
- <name name="add_path" arity="1"/>
- <name name="add_pathz" arity="1"/>
+ <name name="add_path" arity="1" since=""/>
+ <name name="add_pathz" arity="1" since=""/>
<fsummary>Add a directory to the end of the code path.</fsummary>
<type name="add_path_ret"/>
<desc>
@@ -357,7 +357,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="add_patha" arity="1"/>
+ <name name="add_patha" arity="1" since=""/>
<fsummary>Add a directory to the beginning of the code path.</fsummary>
<type name="add_path_ret"/>
<desc>
@@ -370,8 +370,8 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="add_paths" arity="1"/>
- <name name="add_pathsz" arity="1"/>
+ <name name="add_paths" arity="1" since=""/>
+ <name name="add_pathsz" arity="1" since=""/>
<fsummary>Add directories to the end of the code path.</fsummary>
<desc>
<p>Adds the directories in <c><anno>Dirs</anno></c> to the end of the code
@@ -381,7 +381,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="add_pathsa" arity="1"/>
+ <name name="add_pathsa" arity="1" since=""/>
<fsummary>Add directories to the beginning of the code path.</fsummary>
<desc>
<p>Traverses <c><anno>Dirs</anno></c> and adds
@@ -397,7 +397,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="del_path" arity="1"/>
+ <name name="del_path" arity="1" since=""/>
<fsummary>Delete a directory from the code path.</fsummary>
<desc>
<p>Deletes a directory from the code path. The argument can be
@@ -417,7 +417,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="replace_path" arity="2"/>
+ <name name="replace_path" arity="2" since=""/>
<fsummary>Replace a directory with another in the code path.</fsummary>
<desc>
<p>Replaces an old occurrence of a directory
@@ -441,7 +441,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="load_file" arity="1"/>
+ <name name="load_file" arity="1" since=""/>
<fsummary>Load a module.</fsummary>
<type name="load_ret"/>
<desc>
@@ -460,7 +460,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="load_abs" arity="1"/>
+ <name name="load_abs" arity="1" since=""/>
<fsummary>Load a module, residing in a specified file.</fsummary>
<type name="load_ret"/>
<type name="loaded_filename"/>
@@ -477,7 +477,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="ensure_loaded" arity="1"/>
+ <name name="ensure_loaded" arity="1" since=""/>
<fsummary>Ensure that a module is loaded.</fsummary>
<desc>
<p>Tries to load a module in the same way as
@@ -489,7 +489,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="load_binary" arity="3"/>
+ <name name="load_binary" arity="3" since=""/>
<fsummary>Load object code for a module.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -507,7 +507,7 @@ zip:create("mnesia-4.4.7.ez",
</desc>
</func>
<func>
- <name name="atomic_load" arity="1"/>
+ <name name="atomic_load" arity="1" since="OTP 19.0"/>
<fsummary>Load a list of modules atomically</fsummary>
<desc>
<p>Tries to load all of the modules in the list
@@ -566,7 +566,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="prepare_loading" arity="1"/>
+ <name name="prepare_loading" arity="1" since="OTP 19.0"/>
<fsummary>Prepare a list of modules atomically</fsummary>
<desc>
<p>Prepares to load the modules in the list
@@ -598,7 +598,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="finish_loading" arity="1"/>
+ <name name="finish_loading" arity="1" since="OTP 19.0"/>
<fsummary>Finish loading a list of prepared modules atomically</fsummary>
<desc>
<p>Tries to load code for all modules that have been previously
@@ -627,7 +627,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="ensure_modules_loaded" arity="1"/>
+ <name name="ensure_modules_loaded" arity="1" since="OTP 19.0"/>
<fsummary>Ensure that a list of modules is loaded</fsummary>
<desc>
<p>Tries to load any modules not already loaded in the list
@@ -639,7 +639,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Remove current code for a module.</fsummary>
<desc>
<p>Removes the current code for <c><anno>Module</anno></c>, that is,
@@ -652,7 +652,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="purge" arity="1"/>
+ <name name="purge" arity="1" since=""/>
<fsummary>Remove old code for a module.</fsummary>
<desc>
<p>Purges the code for <c><anno>Module</anno></c>, that is, removes code
@@ -668,7 +668,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="soft_purge" arity="1"/>
+ <name name="soft_purge" arity="1" since=""/>
<fsummary>Remove old code for a module, unless no process uses it.</fsummary>
<desc>
<p>Purges the code for <c><anno>Module</anno></c>, that is, removes code
@@ -683,7 +683,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="is_loaded" arity="1"/>
+ <name name="is_loaded" arity="1" since=""/>
<fsummary>Check if a module is loaded.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -702,7 +702,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="all_loaded" arity="0"/>
+ <name name="all_loaded" arity="0" since=""/>
<fsummary>Get all loaded modules.</fsummary>
<type name="loaded_filename"/>
<type name="loaded_ret_atoms"/>
@@ -716,7 +716,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="which" arity="1"/>
+ <name name="which" arity="1" since=""/>
<fsummary>The object code file of a module.</fsummary>
<type name="loaded_ret_atoms"/>
<desc>
@@ -731,7 +731,7 @@ ok = code:finish_loading(Prepared),
</desc>
</func>
<func>
- <name name="get_object_code" arity="1"/>
+ <name name="get_object_code" arity="1" since=""/>
<fsummary>Gets the object code for a module.</fsummary>
<desc>
<p>Searches the code path for the object code of module
@@ -750,7 +750,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="root_dir" arity="0"/>
+ <name name="root_dir" arity="0" since=""/>
<fsummary>Root directory of Erlang/OTP.</fsummary>
<desc>
<p>Returns the root directory of Erlang/OTP, which is
@@ -762,7 +762,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="lib_dir" arity="0"/>
+ <name name="lib_dir" arity="0" since=""/>
<fsummary>Library directory of Erlang/OTP.</fsummary>
<desc>
<p>Returns the library directory, <c>$OTPROOT/lib</c>, where
@@ -774,7 +774,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="lib_dir" arity="1"/>
+ <name name="lib_dir" arity="1" since=""/>
<fsummary>Library directory for an application.</fsummary>
<desc>
<p>Returns the path
@@ -807,7 +807,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="lib_dir" arity="2"/>
+ <name name="lib_dir" arity="2" since=""/>
<fsummary>Subdirectory for an application.</fsummary>
<desc>
<p>Returns the path to a subdirectory directly under the top
@@ -827,7 +827,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="compiler_dir" arity="0"/>
+ <name name="compiler_dir" arity="0" since=""/>
<fsummary>Library directory for the compiler.</fsummary>
<desc>
<p>Returns the compiler library directory. Equivalent to
@@ -835,7 +835,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="priv_dir" arity="1"/>
+ <name name="priv_dir" arity="1" since=""/>
<fsummary>Priv directory for an application.</fsummary>
<desc>
<p>Returns the path to the <c>priv</c> directory in an
@@ -846,7 +846,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="objfile_extension" arity="0"/>
+ <name name="objfile_extension" arity="0" since=""/>
<fsummary>Object code file extension.</fsummary>
<desc>
<p>Returns the object code file extension corresponding to
@@ -854,7 +854,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="stick_dir" arity="1"/>
+ <name name="stick_dir" arity="1" since=""/>
<fsummary>Mark a directory as sticky.</fsummary>
<desc>
<p>Marks <c><anno>Dir</anno></c> as sticky.</p>
@@ -862,7 +862,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="unstick_dir" arity="1"/>
+ <name name="unstick_dir" arity="1" since=""/>
<fsummary>Remove a sticky directory mark.</fsummary>
<desc>
<p>Unsticks a directory that is marked as
@@ -871,7 +871,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="is_sticky" arity="1"/>
+ <name name="is_sticky" arity="1" since=""/>
<fsummary>Test if a module is sticky.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Module</anno></c> is the
@@ -882,7 +882,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="where_is_file" arity="1"/>
+ <name name="where_is_file" arity="1" since=""/>
<fsummary>Full name of a file located in the code path.</fsummary>
<desc>
<p>Searches the code path for <c><anno>Filename</anno></c>, a file of
@@ -893,7 +893,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="clash" arity="0"/>
+ <name name="clash" arity="0" since=""/>
<fsummary>Search for modules with identical names.</fsummary>
<desc>
<p>Searches all directories in the code path for module names with
@@ -901,7 +901,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="module_status" arity="1"/>
+ <name name="module_status" arity="1" since="OTP 20.0"/>
<fsummary>Return the status of the module in relation to object file on disk.</fsummary>
<desc>
<p>Returns:</p>
@@ -934,7 +934,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="modified_modules" arity="0"/>
+ <name name="modified_modules" arity="0" since="OTP 20.0"/>
<fsummary>Return a list of all modules modified on disk.</fsummary>
<desc>
<p>Returns the list of all currently loaded modules for which
@@ -943,7 +943,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</desc>
</func>
<func>
- <name name="is_module_native" arity="1"/>
+ <name name="is_module_native" arity="1" since=""/>
<fsummary>Test if a module has native code.</fsummary>
<desc>
<p>Returns:</p>
@@ -961,7 +961,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]),
</func>
<func>
- <name name="get_mode" arity="0"/>
+ <name name="get_mode" arity="0" since="OTP R16B"/>
<fsummary>The mode of the code server.</fsummary>
<desc>
<p>Returns an atom describing the mode of the code server:
diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml
index 884cb32c0c..e308b06f3c 100644
--- a/lib/kernel/doc/src/disk_log.xml
+++ b/lib/kernel/doc/src/disk_log.xml
@@ -34,7 +34,7 @@
<rev>D</rev>
<file>disk_log.sgml</file>
</header>
- <module>disk_log</module>
+ <module since="">disk_log</module>
<modulesummary>A disk-based term logging facility.</modulesummary>
<description>
<p><c>disk_log</c> is a disk-based term logger that enables
@@ -238,7 +238,7 @@
</datatypes>
<funcs>
<func>
- <name name="accessible_logs" arity="0"/>
+ <name name="accessible_logs" arity="0" since=""/>
<fsummary>Return the accessible disk logs on the current node.</fsummary>
<desc>
<p>Returns the names of the disk logs accessible on the current node.
@@ -248,8 +248,8 @@
</desc>
</func>
<func>
- <name name="alog" arity="2"/>
- <name name="balog" arity="2"/>
+ <name name="alog" arity="2" since=""/>
+ <name name="balog" arity="2" since=""/>
<fsummary>Asynchronously log an item on to a disk log.</fsummary>
<type variable="Log"/>
<type variable="Term" name_i="1"/>
@@ -275,8 +275,8 @@
</desc>
</func>
<func>
- <name name="alog_terms" arity="2"/>
- <name name="balog_terms" arity="2"/>
+ <name name="alog_terms" arity="2" since=""/>
+ <name name="balog_terms" arity="2" since=""/>
<fsummary>Asynchronously log many items on to a disk log.</fsummary>
<type variable="Log"/>
<type variable="TermList" name_i="1"/>
@@ -303,8 +303,8 @@
</desc>
</func>
<func>
- <name name="block" arity="1"/>
- <name name="block" arity="2"/>
+ <name name="block" arity="1" since=""/>
+ <name name="block" arity="2" since=""/>
<fsummary>Block a disk log.</fsummary>
<type name="block_error_rsn"/>
<desc>
@@ -330,21 +330,21 @@
</desc>
</func>
<func>
- <name name="change_header" arity="2"/>
+ <name name="change_header" arity="2" since=""/>
<fsummary>Change option head or head_func for an owner of a disk log.</fsummary>
<desc>
<p>Changes the value of option <c>head</c> or <c>head_func</c> for an owner of a disk log.</p>
</desc>
</func>
<func>
- <name name="change_notify" arity="3"/>
+ <name name="change_notify" arity="3" since=""/>
<fsummary>Change option notify for an owner of a disk log.</fsummary>
<desc>
<p>Changes the value of option <c>notify</c> for an owner of a disk log. </p>
</desc>
</func>
<func>
- <name name="change_size" arity="2"/>
+ <name name="change_size" arity="2" since=""/>
<fsummary>Change the size of an open disk log.</fsummary>
<desc>
<p>Changes the size of an open log.
@@ -384,10 +384,10 @@
</desc>
</func>
<func>
- <name name="chunk" arity="2"/>
- <name name="chunk" arity="3"/>
- <name name="bchunk" arity="2"/>
- <name name="bchunk" arity="3"/>
+ <name name="chunk" arity="2" since=""/>
+ <name name="chunk" arity="3" since=""/>
+ <name name="bchunk" arity="2" since=""/>
+ <name name="bchunk" arity="3" since=""/>
<fsummary>Read a chunk of items written to a disk log.</fsummary>
<type variable="Log"/>
<type variable="Continuation"/>
@@ -447,7 +447,7 @@
</desc>
</func>
<func>
- <name name="chunk_info" arity="1"/>
+ <name name="chunk_info" arity="1" since=""/>
<fsummary>Return information about a chunk continuation of a disk log.</fsummary>
<desc>
<p>Returns the pair <c>{node, <anno>Node</anno>}</c>,
@@ -457,7 +457,7 @@
</desc>
</func>
<func>
- <name name="chunk_step" arity="3"/>
+ <name name="chunk_step" arity="3" since=""/>
<fsummary>Step forward or backward among the wrap log files of a disk log.</fsummary>
<desc>
<p>Can be used with <c>chunk/2,3</c> and <c>bchunk/2,3</c>
@@ -480,7 +480,7 @@
</desc>
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a disk log.</fsummary>
<type name="close_error_rsn"/>
<desc>
@@ -505,7 +505,7 @@
</desc>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return an English description of a disk log error reply.</fsummary>
<desc>
<p>Given the error returned by any function in this module,
@@ -517,7 +517,7 @@
</desc>
</func>
<func>
- <name name="inc_wrap_file" arity="1"/>
+ <name name="inc_wrap_file" arity="1" since=""/>
<fsummary>Change to the next wrap log file of a disk log.</fsummary>
<type name="inc_wrap_error_rsn"/>
<type name="invalid_header"/>
@@ -534,7 +534,7 @@
</desc>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about a disk log.</fsummary>
<type name="dlog_info"/>
<desc>
@@ -685,8 +685,8 @@
</desc>
</func>
<func>
- <name name="lclose" arity="1"/>
- <name name="lclose" arity="2"/>
+ <name name="lclose" arity="1" since=""/>
+ <name name="lclose" arity="2" since=""/>
<fsummary>Close a disk log on one node.</fsummary>
<type name="lclose_error_rsn"/>
<desc>
@@ -704,8 +704,8 @@
</desc>
</func>
<func>
- <name name="log" arity="2"/>
- <name name="blog" arity="2"/>
+ <name name="log" arity="2" since=""/>
+ <name name="blog" arity="2" since=""/>
<fsummary>Log an item onto a disk log.</fsummary>
<type variable="Log"/>
<type variable="Term" name_i="1"/>
@@ -739,8 +739,8 @@
</desc>
</func>
<func>
- <name name="log_terms" arity="2"/>
- <name name="blog_terms" arity="2"/>
+ <name name="log_terms" arity="2" since=""/>
+ <name name="blog_terms" arity="2" since=""/>
<fsummary>Log many items onto a disk log.</fsummary>
<type variable="Log"/>
<type variable="TermList" name_i="1"/>
@@ -768,7 +768,7 @@
</desc>
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since=""/>
<fsummary>Open a disk log file.</fsummary>
<type name="dlog_options"/>
<type name="dlog_option"/>
@@ -1041,7 +1041,7 @@
</desc>
</func>
<func>
- <name name="pid2name" arity="1"/>
+ <name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the disk log handled by a pid.</fsummary>
<desc>
<p>Returns the log name
@@ -1053,9 +1053,9 @@
</desc>
</func>
<func>
- <name name="reopen" arity="2"/>
- <name name="reopen" arity="3"/>
- <name name="breopen" arity="3"/>
+ <name name="reopen" arity="2" since=""/>
+ <name name="reopen" arity="3" since=""/>
+ <name name="breopen" arity="3" since=""/>
<fsummary>Reopen a disk log and save the old log.</fsummary>
<type variable="Log"/>
<type variable="File" name_i="1"/>
@@ -1087,7 +1087,7 @@
</desc>
</func>
<func>
- <name name="sync" arity="1"/>
+ <name name="sync" arity="1" since=""/>
<fsummary>Flush the contents of a disk log to the disk.</fsummary>
<type name="sync_error_rsn"/>
<desc>
@@ -1097,9 +1097,9 @@
</desc>
</func>
<func>
- <name name="truncate" arity="1"/>
- <name name="truncate" arity="2"/>
- <name name="btruncate" arity="2"/>
+ <name name="truncate" arity="1" since=""/>
+ <name name="truncate" arity="2" since=""/>
+ <name name="btruncate" arity="2" since=""/>
<fsummary>Truncate a disk log.</fsummary>
<type variable="Log"/>
<type variable="Head" name_i="2"/>
@@ -1129,7 +1129,7 @@
</desc>
</func>
<func>
- <name name="unblock" arity="1"/>
+ <name name="unblock" arity="1" since=""/>
<fsummary>Unblock a disk log.</fsummary>
<type name="unblock_error_rsn"/>
<desc>
diff --git a/lib/kernel/doc/src/erl_boot_server.xml b/lib/kernel/doc/src/erl_boot_server.xml
index 4109251387..89f9855c49 100644
--- a/lib/kernel/doc/src/erl_boot_server.xml
+++ b/lib/kernel/doc/src/erl_boot_server.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>erl_boot_server</module>
+ <module since="">erl_boot_server</module>
<modulesummary>Boot server for other Erlang machines.</modulesummary>
<description>
<p>This server is used to assist diskless Erlang nodes that fetch
@@ -52,14 +52,14 @@
</description>
<funcs>
<func>
- <name name="add_slave" arity="1"/>
+ <name name="add_slave" arity="1" since=""/>
<fsummary>Add a slave to the list of allowed slaves.</fsummary>
<desc>
<p>Adds a <c><anno>Slave</anno></c> node to the list of allowed slave hosts.</p>
</desc>
</func>
<func>
- <name name="delete_slave" arity="1"/>
+ <name name="delete_slave" arity="1" since=""/>
<fsummary>Delete a slave from the list of allowed slaves.</fsummary>
<desc>
<p>Deletes a <c><anno>Slave</anno></c> node from the list of allowed slave
@@ -67,7 +67,7 @@
</desc>
</func>
<func>
- <name name="start" arity="1"/>
+ <name name="start" arity="1" since=""/>
<fsummary>Start the boot server.</fsummary>
<desc>
<p>Starts the boot server. <c><anno>Slaves</anno></c> is a list of
@@ -76,7 +76,7 @@
</desc>
</func>
<func>
- <name name="start_link" arity="1"/>
+ <name name="start_link" arity="1" since=""/>
<fsummary>Start the boot server and link to the the caller.</fsummary>
<desc>
<p>Starts the boot server and links to the caller. This function
@@ -85,7 +85,7 @@
</desc>
</func>
<func>
- <name name="which_slaves" arity="0"/>
+ <name name="which_slaves" arity="0" since=""/>
<fsummary>Return the current list of allowed slave hosts.</fsummary>
<desc>
<p>Returns the current list of allowed slave hosts.</p>
diff --git a/lib/kernel/doc/src/erl_ddll.xml b/lib/kernel/doc/src/erl_ddll.xml
index 75114e015c..f2d5e1b397 100644
--- a/lib/kernel/doc/src/erl_ddll.xml
+++ b/lib/kernel/doc/src/erl_ddll.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>erl_ddll</module>
+ <module since="">erl_ddll</module>
<modulesummary>Dynamic driver loader and linker.</modulesummary>
<description>
<p>This module provides an interface for loading
@@ -196,7 +196,7 @@
</datatypes>
<funcs>
<func>
- <name name="demonitor" arity="1"/>
+ <name name="demonitor" arity="1" since=""/>
<fsummary>Remove a monitor for a driver.</fsummary>
<desc>
<p>Removes a driver monitor in much the same way as
@@ -212,7 +212,7 @@
</desc>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Takes an <c><anno>ErrorDesc</anno></c> returned by load, unload, or
@@ -229,7 +229,7 @@
</desc>
</func>
<func>
- <name name="info" arity="0"/>
+ <name name="info" arity="0" since=""/>
<fsummary>Retrieve information about all drivers.</fsummary>
<desc>
<p>Returns a list of tuples <c>{<anno>DriverName</anno>, <anno>InfoList</anno>}</c>,
@@ -240,7 +240,7 @@
</desc>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Retrieve information about one driver.</fsummary>
<desc>
<p>Returns a list of tuples <c>{<anno>Tag</anno>, <anno>Value</anno>}</c>,
@@ -266,7 +266,7 @@
</desc>
</func>
<func>
- <name name="info" arity="2"/>
+ <name name="info" arity="2" since=""/>
<fsummary>Retrieve specific information about one driver.</fsummary>
<desc>
<p>Returns specific information about one aspect of a driver.
@@ -328,7 +328,7 @@
</desc>
</func>
<func>
- <name name="load" arity="2"/>
+ <name name="load" arity="2" since=""/>
<fsummary>Load a driver.</fsummary>
<desc>
<p>Loads and links the dynamic driver <c><anno>Name</anno></c>.
@@ -390,7 +390,7 @@
</desc>
</func>
<func>
- <name name="load_driver" arity="2"/>
+ <name name="load_driver" arity="2" since=""/>
<fsummary>Load a driver.</fsummary>
<desc>
<p>Works essentially as <c>load/2</c>, but loads the driver
@@ -413,7 +413,7 @@
</desc>
</func>
<func>
- <name name="loaded_drivers" arity="0"/>
+ <name name="loaded_drivers" arity="0" since=""/>
<fsummary>List loaded drivers.</fsummary>
<desc>
<p>Returns a list of all the available drivers, both
@@ -425,7 +425,7 @@
</desc>
</func>
<func>
- <name name="monitor" arity="2"/>
+ <name name="monitor" arity="2" since=""/>
<fsummary>Create a monitor for a driver.</fsummary>
<desc>
<p>Creates a driver monitor and works in many
@@ -588,7 +588,7 @@
</desc>
</func>
<func>
- <name name="reload" arity="2"/>
+ <name name="reload" arity="2" since=""/>
<fsummary>Replace a driver.</fsummary>
<desc>
<p>Reloads the driver named <c><anno>Name</anno></c> from a possibly
@@ -626,7 +626,7 @@
</desc>
</func>
<func>
- <name name="reload_driver" arity="2"/>
+ <name name="reload_driver" arity="2" since=""/>
<fsummary>Replace a driver.</fsummary>
<desc>
<p>Works exactly as <seealso marker="#reload/2"><c>reload/2</c></seealso>,
@@ -644,7 +644,7 @@
</desc>
</func>
<func>
- <name name="try_load" arity="3"/>
+ <name name="try_load" arity="3" since=""/>
<fsummary>Load a driver.</fsummary>
<desc>
<p>Provides more control than the
@@ -931,7 +931,7 @@
</desc>
</func>
<func>
- <name name="try_unload" arity="2"/>
+ <name name="try_unload" arity="2" since=""/>
<fsummary>Unload a driver.</fsummary>
<desc>
<p>This is the low-level function to unload (or decrement
@@ -1116,7 +1116,7 @@
</desc>
</func>
<func>
- <name name="unload" arity="1"/>
+ <name name="unload" arity="1" since=""/>
<fsummary>Unload a driver.</fsummary>
<desc>
<p>Unloads, or at least dereferences the driver named
@@ -1143,7 +1143,7 @@
</desc>
</func>
<func>
- <name name="unload_driver" arity="1"/>
+ <name name="unload_driver" arity="1" since=""/>
<fsummary>Unload a driver.</fsummary>
<desc>
<p>Unloads, or at least dereferences the driver named
diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml
index 8b076cd2d7..2adbf11a28 100644
--- a/lib/kernel/doc/src/erl_epmd.xml
+++ b/lib/kernel/doc/src/erl_epmd.xml
@@ -28,7 +28,7 @@
<date>2018-02-19</date>
<rev>A</rev>
</header>
- <module>erl_epmd</module>
+ <module since="OTP R14B">erl_epmd</module>
<modulesummary>
Erlang interface towards epmd
</modulesummary>
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name name="start_link" arity="0"/>
+ <name name="start_link" arity="0" since="OTP 21.0"/>
<fsummary>Callback for erl_distribution supervisor.</fsummary>
<desc>
<p>This function is invoked as this module is added as a child of the
@@ -50,8 +50,8 @@
</func>
<func>
- <name name="register_node" arity="2"/>
- <name name="register_node" arity="3"/>
+ <name name="register_node" arity="2" since="OTP 21.0"/>
+ <name name="register_node" arity="3" since="OTP 21.0"/>
<fsummary>Registers the node with <c>epmd</c>.</fsummary>
<desc>
<p>Registers the node with <c>epmd</c> and tells epmd what port will be
@@ -62,8 +62,8 @@
</func>
<func>
- <name name="port_please" arity="2"/>
- <name name="port_please" arity="3"/>
+ <name name="port_please" arity="2" since="OTP 21.0"/>
+ <name name="port_please" arity="3" since="OTP 21.0"/>
<fsummary>Returns the port number for a given node.</fsummary>
<desc>
<p>Requests the distribution port for the given node of an EPMD
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="address_please" arity="3"/>
+ <name name="address_please" arity="3" since="OTP 21.0"/>
<fsummary>Returns address and port.</fsummary>
<desc>
<p>Called by the distribution module. Resolves the <c>Host</c> to an IP
@@ -84,7 +84,7 @@
</func>
<func>
- <name name="names" arity="1"/>
+ <name name="names" arity="1" since="OTP 21.0"/>
<fsummary>Names of Erlang nodes at a host.</fsummary>
<desc>
<p>Called by <seealso marker="net_adm"><c>net_adm:names/0</c></seealso>.
diff --git a/lib/kernel/doc/src/error_handler.xml b/lib/kernel/doc/src/error_handler.xml
index e5639487dc..eb01e87aee 100644
--- a/lib/kernel/doc/src/error_handler.xml
+++ b/lib/kernel/doc/src/error_handler.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>error_handler</module>
+ <module since="">error_handler</module>
<modulesummary>Default system error handler.</modulesummary>
<description>
<p>This module defines what happens when certain types
@@ -38,7 +38,7 @@
</description>
<funcs>
<func>
- <name name="raise_undef_exception" arity="3"/>
+ <name name="raise_undef_exception" arity="3" since="OTP R16B"/>
<fsummary>Raise an undef exception.</fsummary>
<type_desc variable="Args">
A (possibly empty) list of arguments <c>Arg1,..,ArgN</c>
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name name="undefined_function" arity="3"/>
+ <name name="undefined_function" arity="3" since=""/>
<fsummary>Called when an undefined function is encountered.</fsummary>
<type_desc variable="Args">
A (possibly empty) list of arguments <c>Arg1,..,ArgN</c>
@@ -93,7 +93,7 @@
</desc>
</func>
<func>
- <name name="undefined_lambda" arity="3"/>
+ <name name="undefined_lambda" arity="3" since=""/>
<fsummary>Called when an undefined lambda (fun) is encountered.</fsummary>
<type_desc variable="Args">
A (possibly empty) list of arguments <c>Arg1,..,ArgN</c>
diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml
index c3d68fd79f..c170b4fa34 100644
--- a/lib/kernel/doc/src/error_logger.xml
+++ b/lib/kernel/doc/src/error_logger.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>error_logger</module>
+ <module since="">error_logger</module>
<modulesummary>Erlang error logger.</modulesummary>
<description>
@@ -76,8 +76,8 @@
</datatypes>
<funcs>
<func>
- <name name="add_report_handler" arity="1"/>
- <name name="add_report_handler" arity="2"/>
+ <name name="add_report_handler" arity="1" since=""/>
+ <name name="add_report_handler" arity="2" since=""/>
<fsummary>Add an event handler to the error logger.</fsummary>
<desc>
<p>Adds a new event handler to the error logger. The event
@@ -96,7 +96,7 @@
</desc>
</func>
<func>
- <name name="delete_report_handler" arity="1"/>
+ <name name="delete_report_handler" arity="1" since=""/>
<fsummary>Delete an event handler from the error logger.</fsummary>
<desc>
<p>Deletes an event handler from the error logger by calling
@@ -108,9 +108,9 @@
</desc>
</func>
<func>
- <name name="error_msg" arity="1"/>
- <name name="error_msg" arity="2"/>
- <name name="format" arity="2"/>
+ <name name="error_msg" arity="1" since=""/>
+ <name name="error_msg" arity="2" since=""/>
+ <name name="format" arity="2" since=""/>
<fsummary>Log a standard error event.</fsummary>
<desc>
<p>Log a standard error event. The <c><anno>Format</anno></c>
@@ -142,7 +142,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="error_report" arity="1"/>
+ <name name="error_report" arity="1" since=""/>
<fsummary>Log a standard error event.</fsummary>
<desc>
<p>Log a standard error event. Error logger forwards the event
@@ -169,7 +169,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="error_report" arity="2"/>
+ <name name="error_report" arity="2" since=""/>
<fsummary>Log a user-defined error event.</fsummary>
<desc>
<p>Log a user-defined error event. Error logger forwards the
@@ -191,7 +191,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="get_format_depth" arity="0"/>
+ <name name="get_format_depth" arity="0" since="OTP 20.0"/>
<fsummary>Get the value of the Kernel application variable
<c>error_logger_format_depth</c>.</fsummary>
<desc>
@@ -211,8 +211,8 @@ ok</pre>
</desc>
</func>
<func>
- <name name="info_msg" arity="1"/>
- <name name="info_msg" arity="2"/>
+ <name name="info_msg" arity="1" since=""/>
+ <name name="info_msg" arity="2" since=""/>
<fsummary>Log a standard information event.</fsummary>
<desc>
<p>Log a standard information event. The <c><anno>Format</anno></c>
@@ -244,7 +244,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="info_report" arity="1"/>
+ <name name="info_report" arity="1" since=""/>
<fsummary>Log a standard information event.</fsummary>
<desc>
<p>Log a standard information event. Error logger forwards the
@@ -271,7 +271,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="info_report" arity="2"/>
+ <name name="info_report" arity="2" since=""/>
<fsummary>Log a user-defined information event.</fsummary>
<desc>
<p>Log a user-defined information event. Error logger forwards
@@ -294,9 +294,9 @@ ok</pre>
</desc>
</func>
<func>
- <name name="logfile" arity="1" clause_i="1"/>
- <name name="logfile" arity="1" clause_i="2"/>
- <name name="logfile" arity="1" clause_i="3"/>
+ <name name="logfile" arity="1" clause_i="1" since=""/>
+ <name name="logfile" arity="1" clause_i="2" since=""/>
+ <name name="logfile" arity="1" clause_i="3" since=""/>
<fsummary>Enable or disable error printouts to a file.</fsummary>
<type variable="Filename"/>
<type variable="OpenReason" name_i="1"/>
@@ -346,7 +346,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="tty" arity="1"/>
+ <name name="tty" arity="1" since=""/>
<fsummary>Enable or disable printouts to the terminal.</fsummary>
<desc>
<p>Enables (<c><anno>Flag</anno> == true</c>) or disables
@@ -363,7 +363,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_map" arity="0"/>
+ <name name="warning_map" arity="0" since=""/>
<fsummary>Return the current mapping for warning events.</fsummary>
<desc>
<p>Returns the current mapping for warning events. Events sent
@@ -400,8 +400,8 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_msg" arity="1"/>
- <name name="warning_msg" arity="2"/>
+ <name name="warning_msg" arity="1" since=""/>
+ <name name="warning_msg" arity="2" since=""/>
<fsummary>Log a standard warning event.</fsummary>
<desc>
<p>Log a standard warning event. The <c><anno>Format</anno></c>
@@ -429,7 +429,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_report" arity="1"/>
+ <name name="warning_report" arity="1" since=""/>
<fsummary>Log a standard warning event.</fsummary>
<desc>
<p>Log a standard warning event. Error logger forwards the event
@@ -446,7 +446,7 @@ ok</pre>
</desc>
</func>
<func>
- <name name="warning_report" arity="2"/>
+ <name name="warning_report" arity="2" since=""/>
<fsummary>Log a user-defined warning event.</fsummary>
<desc>
<p>Log a user-defined warning event. Error logger forwards the
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 9acaf6b41e..fc25e83d40 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>file</module>
+ <module since="">file</module>
<modulesummary>File interface module.</modulesummary>
<description>
<p>This module provides an interface to the file system.</p>
@@ -186,7 +186,7 @@
<funcs>
<func>
- <name name="advise" arity="4"/>
+ <name name="advise" arity="4" since="OTP R14B"/>
<fsummary>Predeclare an access pattern for file data.</fsummary>
<type name="posix_file_advise"/>
<desc>
@@ -197,7 +197,7 @@
</desc>
</func>
<func>
- <name name="allocate" arity="3"/>
+ <name name="allocate" arity="3" since="OTP R16B"/>
<fsummary>Allocate file space.</fsummary>
<desc>
<p><c>allocate/3</c> can be used to preallocate space for a file.</p>
@@ -209,7 +209,7 @@
</desc>
</func>
<func>
- <name name="change_group" arity="2"/>
+ <name name="change_group" arity="2" since=""/>
<fsummary>Change group of a file.</fsummary>
<desc>
<p>Changes group of a file. See
@@ -217,7 +217,7 @@
</desc>
</func>
<func>
- <name name="change_mode" arity="2"/>
+ <name name="change_mode" arity="2" since="OTP R14B"/>
<fsummary>Change permissions of a file.</fsummary>
<desc>
<p>Changes permissions of a file. See
@@ -225,7 +225,7 @@
</desc>
</func>
<func>
- <name name="change_owner" arity="2"/>
+ <name name="change_owner" arity="2" since=""/>
<fsummary>Change owner of a file.</fsummary>
<desc>
<p>Changes owner of a file. See
@@ -233,7 +233,7 @@
</desc>
</func>
<func>
- <name name="change_owner" arity="3"/>
+ <name name="change_owner" arity="3" since=""/>
<fsummary>Change owner and group of a file.</fsummary>
<desc>
<p>Changes owner and group of a file. See
@@ -241,7 +241,7 @@
</desc>
</func>
<func>
- <name name="change_time" arity="2"/>
+ <name name="change_time" arity="2" since=""/>
<fsummary>Change the modification time of a file.</fsummary>
<desc>
<p>Changes the modification and access times of a file. See
@@ -249,7 +249,7 @@
</desc>
</func>
<func>
- <name name="change_time" arity="3"/>
+ <name name="change_time" arity="3" since=""/>
<fsummary>Change the modification and last access time of a file.</fsummary>
<desc>
<p>Changes the modification and last access times of a file. See
@@ -257,7 +257,7 @@
</desc>
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a file.</fsummary>
<desc>
<p>Closes the file referenced by <c><anno>IoDevice</anno></c>. It mostly
@@ -270,7 +270,7 @@
</desc>
</func>
<func>
- <name name="consult" arity="1"/>
+ <name name="consult" arity="1" since=""/>
<fsummary>Read Erlang terms from a file.</fsummary>
<desc>
<p>Reads Erlang terms, separated by '.', from
@@ -308,8 +308,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="copy" arity="2"/>
- <name name="copy" arity="3"/>
+ <name name="copy" arity="2" since=""/>
+ <name name="copy" arity="3" since=""/>
<fsummary>Copy file contents.</fsummary>
<desc>
<p>Copies <c><anno>ByteCount</anno></c> bytes from
@@ -346,7 +346,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="datasync" arity="1"/>
+ <name name="datasync" arity="1" since="OTP R14B"/>
<fsummary>Synchronize the in-memory data of a file, ignoring most of its metadata, with that on the physical medium.</fsummary>
<desc>
<p>Ensures that any buffers kept by the operating system
@@ -369,7 +369,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="del_dir" arity="1"/>
+ <name name="del_dir" arity="1" since=""/>
<fsummary>Delete a directory.</fsummary>
<desc>
<p>Tries to delete directory <c><anno>Dir</anno></c>.
@@ -405,7 +405,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete a file.</fsummary>
<desc>
<p>Tries to delete file <c><anno>Filename</anno></c>.
@@ -442,7 +442,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="eval" arity="1"/>
+ <name name="eval" arity="1" since=""/>
<fsummary>Evaluate Erlang expressions in a file.</fsummary>
<desc>
<p>Reads and evaluates Erlang expressions, separated by '.' (or
@@ -476,7 +476,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="eval" arity="2"/>
+ <name name="eval" arity="2" since=""/>
<fsummary>Evaluate Erlang expressions in a file.</fsummary>
<desc>
<p>The same as <c>eval/1</c>, but the variable bindings
@@ -486,7 +486,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return a descriptive string for an error reason.</fsummary>
<desc>
<p>Given the error reason returned by any function in this
@@ -494,7 +494,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="get_cwd" arity="0"/>
+ <name name="get_cwd" arity="0" since=""/>
<fsummary>Get the current working directory.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Dir</anno>}</c>, where <c><anno>Dir</anno></c>
@@ -516,7 +516,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="get_cwd" arity="1"/>
+ <name name="get_cwd" arity="1" since=""/>
<fsummary>Get the current working directory for the specified drive.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Dir</anno>}</c> or
@@ -547,7 +547,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="list_dir" arity="1"/>
+ <name name="list_dir" arity="1" since=""/>
<fsummary>List files in a directory.</fsummary>
<desc>
<p>Lists all files in a directory, <em>except</em> files
@@ -578,7 +578,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="list_dir_all" arity="1"/>
+ <name name="list_dir_all" arity="1" since="OTP R16B"/>
<fsummary>List all files in a directory.</fsummary>
<desc>
<p><marker id="list_dir_all"/>Lists all the files in a directory,
@@ -603,7 +603,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="make_dir" arity="1"/>
+ <name name="make_dir" arity="1" since=""/>
<fsummary>Make a directory.</fsummary>
<desc>
<p>Tries to create directory <c><anno>Dir</anno></c>. Missing parent
@@ -637,7 +637,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="make_link" arity="2"/>
+ <name name="make_link" arity="2" since=""/>
<fsummary>Make a hard link to a file.</fsummary>
<desc>
<p>Makes a hard link from <c><anno>Existing</anno></c> to
@@ -666,7 +666,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="make_symlink" arity="2"/>
+ <name name="make_symlink" arity="2" since=""/>
<fsummary>Make a symbolic link to a file or directory.</fsummary>
<desc>
<p>Creates a symbolic link <c><anno>New</anno></c> to
@@ -702,7 +702,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="native_name_encoding" arity="0"/>
+ <name name="native_name_encoding" arity="0" since="OTP R14B01"/>
<fsummary>Return the configured filename encoding of the VM.</fsummary>
<desc>
<p><marker id="native_name_encoding"/>Returns
@@ -714,7 +714,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="open" arity="2"/>
+ <name name="open" arity="2" since=""/>
<fsummary>Open a file.</fsummary>
<desc>
<p>Opens file <c><anno>File</anno></c> in the mode determined
@@ -997,7 +997,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_consult" arity="2"/>
+ <name name="path_consult" arity="2" since=""/>
<fsummary>Read Erlang terms from a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1039,7 +1039,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_eval" arity="2"/>
+ <name name="path_eval" arity="2" since=""/>
<fsummary>Evaluate Erlang expressions in a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1085,7 +1085,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_open" arity="3"/>
+ <name name="path_open" arity="3" since=""/>
<fsummary>Open a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1114,7 +1114,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_script" arity="2"/>
+ <name name="path_script" arity="2" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>Searches the path <c><anno>Path</anno></c> (a list of directory
@@ -1158,7 +1158,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="path_script" arity="3"/>
+ <name name="path_script" arity="3" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>The same as <c>path_script/2</c> but the variable bindings
@@ -1168,7 +1168,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pid2name" arity="1"/>
+ <name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the file handled by a pid.</fsummary>
<desc>
<p>If <c><anno>Pid</anno></c> is an I/O device, that is, a pid returned from
@@ -1193,7 +1193,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="position" arity="2"/>
+ <name name="position" arity="2" since=""/>
<fsummary>Set position in a file.</fsummary>
<desc>
<p>Sets the position of the file referenced by <c><anno>IoDevice</anno></c>
@@ -1245,7 +1245,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pread" arity="2"/>
+ <name name="pread" arity="2" since=""/>
<fsummary>Read from a file at certain positions.</fsummary>
<desc>
<p>Performs a sequence of <c>pread/3</c> in one operation,
@@ -1263,7 +1263,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pread" arity="3"/>
+ <name name="pread" arity="3" since=""/>
<fsummary>Read from a file at a certain position.</fsummary>
<desc>
<p>Combines <c>position/2</c> and <c>read/2</c> in one
@@ -1283,7 +1283,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pwrite" arity="2"/>
+ <name name="pwrite" arity="2" since=""/>
<fsummary>Write to a file at certain positions.</fsummary>
<desc>
<p>Performs a sequence of <c>pwrite/3</c> in one operation,
@@ -1298,7 +1298,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="pwrite" arity="3"/>
+ <name name="pwrite" arity="3" since=""/>
<fsummary>Write to a file at a certain position.</fsummary>
<desc>
<p>Combines <c>position/2</c> and <c>write/2</c> in one
@@ -1317,7 +1317,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read" arity="2"/>
+ <name name="read" arity="2" since=""/>
<fsummary>Read from a file.</fsummary>
<desc>
<p>Reads <c><anno>Number</anno></c> bytes/characters from the file
@@ -1371,7 +1371,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_file" arity="1"/>
+ <name name="read_file" arity="1" since=""/>
<fsummary>Read a file.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Binary</anno>}</c>, where
@@ -1407,8 +1407,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_file_info" arity="1"/>
- <name name="read_file_info" arity="2"/>
+ <name name="read_file_info" arity="1" since=""/>
+ <name name="read_file_info" arity="2" since="OTP R15B"/>
<fsummary>Retrieve information about a file.</fsummary>
<desc>
<p>Retrieves information about a file. Returns
@@ -1562,7 +1562,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_line" arity="1"/>
+ <name name="read_line" arity="1" since=""/>
<fsummary>Read a line from a file.</fsummary>
<desc>
<p>Reads a line of bytes/characters from the file referenced by
@@ -1619,7 +1619,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_link" arity="1"/>
+ <name name="read_link" arity="1" since=""/>
<fsummary>See what a link is pointing to.</fsummary>
<desc>
<p><marker id="read_link_all"/>Returns
@@ -1649,7 +1649,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_link_all" arity="1"/>
+ <name name="read_link_all" arity="1" since="OTP R16B"/>
<fsummary>See what a link is pointing to.</fsummary>
<desc>
<p>Returns <c>{ok, <anno>Filename</anno>}</c> if
@@ -1677,8 +1677,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="read_link_info" arity="1"/>
- <name name="read_link_info" arity="2"/>
+ <name name="read_link_info" arity="1" since=""/>
+ <name name="read_link_info" arity="2" since="OTP R15B"/>
<fsummary>Retrieve information about a link or file.</fsummary>
<desc>
<p>Works like
@@ -1699,7 +1699,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="rename" arity="2"/>
+ <name name="rename" arity="2" since=""/>
<fsummary>Rename a file.</fsummary>
<desc>
<p>Tries to rename the file <c><anno>Source</anno></c> to
@@ -1762,7 +1762,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="script" arity="1"/>
+ <name name="script" arity="1" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>Reads and evaluates Erlang expressions, separated by '.' (or
@@ -1797,7 +1797,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="script" arity="2"/>
+ <name name="script" arity="2" since=""/>
<fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary>
<desc>
<p>The same as <c>script/1</c> but the variable bindings
@@ -1807,7 +1807,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="sendfile" arity="2"/>
+ <name name="sendfile" arity="2" since="OTP R15B"/>
<fsummary>Send a file to a socket.</fsummary>
<desc>
<p>Sends the file <c>Filename</c> to <c>Socket</c>.
@@ -1816,7 +1816,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="sendfile" arity="5"/>
+ <name name="sendfile" arity="5" since="OTP R15B"/>
<fsummary>Send a file to a socket.</fsummary>
<type name="sendfile_option"/>
<desc>
@@ -1843,7 +1843,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="set_cwd" arity="1"/>
+ <name name="set_cwd" arity="1" since=""/>
<fsummary>Set the current working directory.</fsummary>
<desc>
<p>Sets the current working directory of the file server to
@@ -1890,7 +1890,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="sync" arity="1"/>
+ <name name="sync" arity="1" since=""/>
<fsummary>Synchronize the in-memory state of a file with that on the physical medium.</fsummary>
<desc>
<p>Ensures that any buffers kept by the operating system
@@ -1906,7 +1906,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="truncate" arity="1"/>
+ <name name="truncate" arity="1" since=""/>
<fsummary>Truncate a file.</fsummary>
<desc>
<p>Truncates the file referenced by <c><anno>IoDevice</anno></c> at
@@ -1915,7 +1915,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write" arity="2"/>
+ <name name="write" arity="2" since=""/>
<fsummary>Write to a file.</fsummary>
<desc>
<p>Writes <c><anno>Bytes</anno></c> to the file referenced by
@@ -1941,7 +1941,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write_file" arity="2"/>
+ <name name="write_file" arity="2" since=""/>
<fsummary>Write a file.</fsummary>
<desc>
<p>Writes the contents of the <c>iodata</c> term <c><anno>Bytes</anno></c>
@@ -1978,7 +1978,7 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write_file" arity="3"/>
+ <name name="write_file" arity="3" since=""/>
<fsummary>Write a file.</fsummary>
<desc>
<p>Same as <c>write_file/2</c>, but takes a third argument
@@ -1989,8 +1989,8 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
- <name name="write_file_info" arity="2"/>
- <name name="write_file_info" arity="3"/>
+ <name name="write_file_info" arity="2" since=""/>
+ <name name="write_file_info" arity="3" since="OTP R15B"/>
<fsummary>Change file information.</fsummary>
<desc>
<p>Changes file information. Returns <c>ok</c> if successful,
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml
index 1e08b25f66..f70d6c24db 100644
--- a/lib/kernel/doc/src/gen_sctp.xml
+++ b/lib/kernel/doc/src/gen_sctp.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>gen_sctp.xml</file>
</header>
- <module>gen_sctp</module>
+ <module since="">gen_sctp</module>
<modulesummary>Functions for communicating with sockets using the SCTP
protocol.</modulesummary>
<description>
@@ -100,7 +100,7 @@
<funcs>
<func>
- <name name="abort" arity="2"/>
+ <name name="abort" arity="2" since=""/>
<fsummary>Abnormally terminate the association specified by
<c>Assoc</c>, without flushing of unsent data.</fsummary>
<desc>
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close the socket and all associations on it.</fsummary>
<desc>
<p>Closes the socket and all associations on it. The unsent
@@ -128,7 +128,7 @@
</func>
<func>
- <name name="connect" arity="4"/>
+ <name name="connect" arity="4" since=""/>
<fsummary>Same as <c>connect(Socket, Addr, Port, Opts, infinity)</c>.</fsummary>
<desc>
<p>Same as <c>connect(<anno>Socket</anno>, <anno>Addr</anno>,
@@ -137,7 +137,7 @@
</func>
<func>
- <name name="connect" arity="5"/>
+ <name name="connect" arity="5" since=""/>
<fsummary>Establish a new association for socket <c>Socket</c>, with a
peer (SCTP server socket).</fsummary>
<desc>
@@ -213,7 +213,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="connect_init" arity="4"/>
+ <name name="connect_init" arity="4" since="OTP R13B04"/>
<fsummary>Same as <c>connect_init(Socket, Addr, Port, Opts, infinity)</c>..</fsummary>
<desc>
<p>Same as <c>connect_init(<anno>Socket</anno>, <anno>Addr</anno>,
@@ -222,7 +222,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="connect_init" arity="5"/>
+ <name name="connect_init" arity="5" since="OTP R13B04"/>
<fsummary>Initiate a new association for socket <c>Socket</c>, with a
peer (SCTP server socket).</fsummary>
<desc>
@@ -248,7 +248,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="controlling_process" arity="2"/>
+ <name name="controlling_process" arity="2" since=""/>
<fsummary>Assign a new controlling process pid to the socket.</fsummary>
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
@@ -259,7 +259,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="eof" arity="2"/>
+ <name name="eof" arity="2" since=""/>
<fsummary>Gracefully terminate the association specified by <c>Assoc</c>,
with flushing of all unsent data.</fsummary>
<desc>
@@ -272,7 +272,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="error_string" arity="1"/>
+ <name name="error_string" arity="1" since=""/>
<fsummary>Translate an SCTP error number into a string.</fsummary>
<desc>
<p>Translates an SCTP error number from, for example,
@@ -283,8 +283,8 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="listen" arity="2" clause_i="1"/>
- <name name="listen" arity="2" clause_i="2"/>
+ <name name="listen" arity="2" clause_i="1" since=""/>
+ <name name="listen" arity="2" clause_i="2" since="OTP R15B"/>
<fsummary>Set up a socket to listen.</fsummary>
<desc>
<p>Sets up a socket to listen on the IP address and port number
@@ -300,10 +300,10 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="open" arity="0"/>
- <name name="open" arity="1" clause_i="1"/>
- <name name="open" arity="1" clause_i="2"/>
- <name name="open" arity="2"/>
+ <name name="open" arity="0" since=""/>
+ <name name="open" arity="1" clause_i="1" since=""/>
+ <name name="open" arity="1" clause_i="2" since=""/>
+ <name name="open" arity="2" since=""/>
<fsummary>Create an SCTP socket and binds it to local addresses.</fsummary>
<desc>
<p>Creates an SCTP socket and binds it to the local addresses
@@ -366,7 +366,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="peeloff" arity="2"/>
+ <name name="peeloff" arity="2" since="OTP R15B"/>
<fsummary>Peel off a type <c>stream</c> socket from a type
<c>seqpacket</c> one.</fsummary>
<desc>
@@ -387,8 +387,8 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="recv" arity="1"/>
- <name name="recv" arity="2"/>
+ <name name="recv" arity="1" since=""/>
+ <name name="recv" arity="2" since=""/>
<fsummary>Receive a message from a socket.</fsummary>
<desc>
<p>Receives the <c><anno>Data</anno></c> message from any association
@@ -532,7 +532,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="send" arity="3"/>
+ <name name="send" arity="3" since=""/>
<fsummary>Send a message using an <c>#sctp_sndrcvinfo{}</c>record.</fsummary>
<desc>
<p>Sends the <c><anno>Data</anno></c> message with all sending
@@ -547,7 +547,7 @@ connect(Socket, Ip, Port>,
</func>
<func>
- <name name="send" arity="4"/>
+ <name name="send" arity="4" since=""/>
<fsummary>Send a message over an existing association and specified
stream.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml
index 24d63693fd..fc16473393 100644
--- a/lib/kernel/doc/src/gen_tcp.xml
+++ b/lib/kernel/doc/src/gen_tcp.xml
@@ -27,7 +27,7 @@
<date>1997-10-24</date>
<rev>A</rev>
</header>
- <module>gen_tcp</module>
+ <module since="">gen_tcp</module>
<modulesummary>Interface to TCP/IP sockets.</modulesummary>
<description>
<p>This module provides functions for communicating
@@ -116,8 +116,8 @@ do_recv(Sock, Bs) ->
<funcs>
<func>
- <name name="accept" arity="1"/>
- <name name="accept" arity="2"/>
+ <name name="accept" arity="1" since=""/>
+ <name name="accept" arity="2" since=""/>
<fsummary>Accept an incoming connection request on a listening socket.</fsummary>
<type_desc variable="ListenSocket">Returned by
<seealso marker="#listen/2"><c>listen/2</c></seealso>.
@@ -163,7 +163,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a TCP socket.</fsummary>
<desc>
<p>Closes a TCP socket.</p>
@@ -188,8 +188,8 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="connect" arity="3"/>
- <name name="connect" arity="4"/>
+ <name name="connect" arity="3" since=""/>
+ <name name="connect" arity="4" since=""/>
<fsummary>Connect to a TCP port.</fsummary>
<desc>
<p>Connects to a server on TCP port <c><anno>Port</anno></c> on the host
@@ -268,7 +268,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="controlling_process" arity="2"/>
+ <name name="controlling_process" arity="2" since=""/>
<fsummary>Change controlling process of a socket.</fsummary>
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
@@ -292,7 +292,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="listen" arity="2"/>
+ <name name="listen" arity="2" since=""/>
<fsummary>Set up a socket to listen on a port.</fsummary>
<desc>
<p>Sets up a socket to listen on port <c><anno>Port</anno></c> on
@@ -349,8 +349,8 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="recv" arity="2"/>
- <name name="recv" arity="3"/>
+ <name name="recv" arity="2" since=""/>
+ <name name="recv" arity="3" since=""/>
<fsummary>Receive a packet from a passive socket.</fsummary>
<type_desc variable="HttpPacket">See the description of
<c>HttpPacket</c> in
@@ -375,7 +375,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="send" arity="2"/>
+ <name name="send" arity="2" since=""/>
<fsummary>Send a packet.</fsummary>
<desc>
<p>Sends a packet on a socket.</p>
@@ -386,7 +386,7 @@ do_recv(Sock, Bs) ->
</func>
<func>
- <name name="shutdown" arity="2"/>
+ <name name="shutdown" arity="2" since=""/>
<fsummary>Asynchronously close a socket.</fsummary>
<desc>
<p>Closes a socket in one or two directions.</p>
diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml
index 840ca3c188..d20fc1fdfd 100644
--- a/lib/kernel/doc/src/gen_udp.xml
+++ b/lib/kernel/doc/src/gen_udp.xml
@@ -28,7 +28,7 @@
<date>1997-12-03</date>
<rev>A</rev>
</header>
- <module>gen_udp</module>
+ <module since="">gen_udp</module>
<modulesummary>Interface to UDP sockets.</modulesummary>
<description>
<p>This module provides functions for communicating
@@ -53,7 +53,7 @@
<funcs>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a UDP socket.</fsummary>
<desc>
<p>Closes a UDP socket.</p>
@@ -61,7 +61,7 @@
</func>
<func>
- <name name="controlling_process" arity="2"/>
+ <name name="controlling_process" arity="2" since=""/>
<fsummary>Change controlling process of a socket.</fsummary>
<desc>
<p>Assigns a new controlling process <c><anno>Pid</anno></c> to
@@ -77,8 +77,8 @@
</func>
<func>
- <name name="open" arity="1"/>
- <name name="open" arity="2"/>
+ <name name="open" arity="1" since=""/>
+ <name name="open" arity="2" since=""/>
<fsummary>Associate a UDP port number with the process calling it.</fsummary>
<desc>
<p>Associates a UDP port number (<c><anno>Port</anno></c>) with the
@@ -189,8 +189,8 @@
</func>
<func>
- <name name="recv" arity="2"/>
- <name name="recv" arity="3"/>
+ <name name="recv" arity="2" since=""/>
+ <name name="recv" arity="3" since=""/>
<fsummary>Receive a packet from a passive socket.</fsummary>
<desc>
<p>
@@ -213,7 +213,7 @@
</func>
<func>
- <name name="send" arity="4"/>
+ <name name="send" arity="4" since=""/>
<fsummary>Send a packet.</fsummary>
<desc>
<p>
diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml
index 4442741f54..dfe71de5ce 100644
--- a/lib/kernel/doc/src/global.xml
+++ b/lib/kernel/doc/src/global.xml
@@ -28,7 +28,7 @@
<date>1997-11-17</date>
<rev></rev>
</header>
- <module>global</module>
+ <module since="">global</module>
<modulesummary>A global name registration facility.</modulesummary>
<description>
<p>This module consists of the following services:</p>
@@ -100,8 +100,8 @@
<funcs>
<func>
- <name name="del_lock" arity="1"/>
- <name name="del_lock" arity="2"/>
+ <name name="del_lock" arity="1" since=""/>
+ <name name="del_lock" arity="2" since=""/>
<fsummary>Delete a lock.</fsummary>
<desc>
<p>Deletes the lock <c><anno>Id</anno></c> synchronously.</p>
@@ -109,7 +109,7 @@
</func>
<func>
- <name name="notify_all_name" arity="3"/>
+ <name name="notify_all_name" arity="3" since=""/>
<fsummary>Name resolving function that notifies both pids.</fsummary>
<desc>
<p>Can be used as a name resolving function for
@@ -123,7 +123,7 @@
</func>
<func>
- <name name="random_exit_name" arity="3"/>
+ <name name="random_exit_name" arity="3" since=""/>
<fsummary>Name resolving function that kills one pid.</fsummary>
<desc>
<p>Can be used as a name resolving function for
@@ -136,7 +136,7 @@
</func>
<func>
- <name name="random_notify_name" arity="3"/>
+ <name name="random_notify_name" arity="3" since=""/>
<fsummary>Name resolving function that notifies one pid.</fsummary>
<desc>
<p>Can be used as a name resolving function for
@@ -150,8 +150,8 @@
</func>
<func>
- <name name="re_register_name" arity="2"/>
- <name name="re_register_name" arity="3"/>
+ <name name="re_register_name" arity="2" since=""/>
+ <name name="re_register_name" arity="3" since=""/>
<fsummary>Atomically re-register a name.</fsummary>
<type name="method"/>
<type_desc name="method">{<c>Module</c>, <c>Function</c>}
@@ -167,8 +167,8 @@
</func>
<func>
- <name name="register_name" arity="2"/>
- <name name="register_name" arity="3"/>
+ <name name="register_name" arity="2" since=""/>
+ <name name="register_name" arity="3" since=""/>
<fsummary>Globally register a name for a pid.</fsummary>
<type name="method"/>
<type_desc name="method">{<c>Module</c>, <c>Function</c>} is also
@@ -221,7 +221,7 @@
</func>
<func>
- <name name="registered_names" arity="0"/>
+ <name name="registered_names" arity="0" since=""/>
<fsummary>All globally registered names.</fsummary>
<desc>
<p>Returns a list of all globally registered names.</p>
@@ -229,7 +229,7 @@
</func>
<func>
- <name name="send" arity="2"/>
+ <name name="send" arity="2" since=""/>
<fsummary>Send a message to a globally registered pid.</fsummary>
<desc>
<p>Sends message <c><anno>Msg</anno></c> to the pid globally registered
@@ -241,9 +241,9 @@
</func>
<func>
- <name name="set_lock" arity="1"/>
- <name name="set_lock" arity="2"/>
- <name name="set_lock" arity="3"/>
+ <name name="set_lock" arity="1" since=""/>
+ <name name="set_lock" arity="2" since=""/>
+ <name name="set_lock" arity="3" since=""/>
<fsummary>Set a lock on the specified nodes.</fsummary>
<type name="id"/>
<type name="retries"/>
@@ -287,7 +287,7 @@
</func>
<func>
- <name name="sync" arity="0"/>
+ <name name="sync" arity="0" since=""/>
<fsummary>Synchronize the global name server.</fsummary>
<desc>
<p>Synchronizes the global name server with all nodes known to
@@ -302,9 +302,9 @@
</func>
<func>
- <name name="trans" arity="2"/>
- <name name="trans" arity="3"/>
- <name name="trans" arity="4"/>
+ <name name="trans" arity="2" since=""/>
+ <name name="trans" arity="3" since=""/>
+ <name name="trans" arity="4" since=""/>
<fsummary>Micro transaction facility.</fsummary>
<type name="retries"/>
<type name="trans_fun"/>
@@ -322,7 +322,7 @@
</func>
<func>
- <name name="unregister_name" arity="1"/>
+ <name name="unregister_name" arity="1" since=""/>
<fsummary>Remove a globally registered name for a pid.</fsummary>
<desc>
<p>Removes the globally registered name <c><anno>Name</anno></c> from
@@ -331,7 +331,7 @@
</func>
<func>
- <name name="whereis_name" arity="1"/>
+ <name name="whereis_name" arity="1" since=""/>
<fsummary>Get the pid with a specified globally registered name.</fsummary>
<desc>
<p>Returns the pid with the globally registered name
diff --git a/lib/kernel/doc/src/global_group.xml b/lib/kernel/doc/src/global_group.xml
index 8f947b9adf..74d15cd476 100644
--- a/lib/kernel/doc/src/global_group.xml
+++ b/lib/kernel/doc/src/global_group.xml
@@ -28,7 +28,7 @@
<date>1998-12-18</date>
<rev>B</rev>
</header>
- <module>global_group</module>
+ <module since="">global_group</module>
<modulesummary>Grouping nodes to global name registration groups.</modulesummary>
<description>
<p>This module makes it possible to partition the nodes of a
@@ -105,7 +105,7 @@
<funcs>
<func>
- <name name="global_groups" arity="0"/>
+ <name name="global_groups" arity="0" since=""/>
<fsummary>Return the global group names.</fsummary>
<desc>
<p>Returns a tuple containing the name of the global group that
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="info" arity="0"/>
+ <name name="info" arity="0" since=""/>
<fsummary>Information about global groups.</fsummary>
<type name="info_item"/>
<type name="sync_state"/>
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="monitor_nodes" arity="1"/>
+ <name name="monitor_nodes" arity="1" since=""/>
<fsummary>Subscribe to node status changes.</fsummary>
<desc>
<p>Depending on <c><anno>Flag</anno></c>, the calling process
@@ -187,7 +187,7 @@
</func>
<func>
- <name name="own_nodes" arity="0"/>
+ <name name="own_nodes" arity="0" since=""/>
<fsummary>Return the group nodes.</fsummary>
<desc>
<p>Returns the names of all group nodes, regardless of their
@@ -196,7 +196,7 @@
</func>
<func>
- <name name="registered_names" arity="1"/>
+ <name name="registered_names" arity="1" since=""/>
<fsummary>Return globally registered names.</fsummary>
<desc>
<p>Returns a list of all names that are globally registered
@@ -205,8 +205,8 @@
</func>
<func>
- <name name="send" arity="2"/>
- <name name="send" arity="3"/>
+ <name name="send" arity="2" since=""/>
+ <name name="send" arity="3" since=""/>
<fsummary>Send a message to a globally registered pid.</fsummary>
<desc>
<p>Searches for <c><anno>Name</anno></c>, globally registered on
@@ -224,7 +224,7 @@
</func>
<func>
- <name name="sync" arity="0"/>
+ <name name="sync" arity="0" since=""/>
<fsummary>Synchronize the group nodes.</fsummary>
<desc>
<p>Synchronizes the group nodes, that is, the global name
@@ -242,8 +242,8 @@
</func>
<func>
- <name name="whereis_name" arity="1"/>
- <name name="whereis_name" arity="2"/>
+ <name name="whereis_name" arity="1" since=""/>
+ <name name="whereis_name" arity="2" since=""/>
<fsummary>Get the pid with a specified globally registered name.</fsummary>
<desc>
<p>Searches for <c><anno>Name</anno></c>, globally registered on
diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml
index ad1a2ffeb9..4243b1ffe8 100644
--- a/lib/kernel/doc/src/heart.xml
+++ b/lib/kernel/doc/src/heart.xml
@@ -28,7 +28,7 @@
<date>1998-01-28</date>
<rev>A</rev>
</header>
- <module>heart</module>
+ <module since="">heart</module>
<modulesummary>Heartbeat monitoring of an Erlang runtime system.</modulesummary>
<description>
<p>This modules contains the interface to the <c>heart</c> process.
@@ -119,7 +119,7 @@
<funcs>
<func>
- <name name="set_cmd" arity="1"/>
+ <name name="set_cmd" arity="1" since=""/>
<fsummary>Set a temporary reboot command.</fsummary>
<desc>
<p>Sets a temporary reboot command. This command is used if
@@ -136,7 +136,7 @@
</func>
<func>
- <name name="clear_cmd" arity="0"/>
+ <name name="clear_cmd" arity="0" since=""/>
<fsummary>Clear the temporary boot command.</fsummary>
<desc>
<p>Clears the temporary boot command. If the system terminates,
@@ -145,7 +145,7 @@
</func>
<func>
- <name name="get_cmd" arity="0"/>
+ <name name="get_cmd" arity="0" since=""/>
<fsummary>Get the temporary reboot command.</fsummary>
<desc>
<p>Gets the temporary reboot command. If the command is cleared,
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="set_callback" arity="2"/>
+ <name name="set_callback" arity="2" since="OTP 18.3"/>
<fsummary>Set a validation callback</fsummary>
<desc>
<p> This validation callback will be executed before any
@@ -166,14 +166,14 @@
</desc>
</func>
<func>
- <name name="clear_callback" arity="0"/>
+ <name name="clear_callback" arity="0" since="OTP 18.3"/>
<fsummary>Clear the validation callback</fsummary>
<desc>
<p>Removes the validation callback call before heartbeats.</p>
</desc>
</func>
<func>
- <name name="get_callback" arity="0"/>
+ <name name="get_callback" arity="0" since="OTP 18.3"/>
<fsummary>Get the validation callback</fsummary>
<desc>
<p>Get the validation callback. If the callback is cleared, <c>none</c> will be returned.</p>
@@ -181,7 +181,7 @@
</func>
<func>
- <name name="set_options" arity="1"/>
+ <name name="set_options" arity="1" since="OTP 18.3"/>
<fsummary>Set a list of options</fsummary>
<desc>
<p> Valid options <c>set_options</c> are: </p>
@@ -199,7 +199,7 @@
</desc>
</func>
<func>
- <name name="get_options" arity="0"/>
+ <name name="get_options" arity="0" since="OTP 18.3"/>
<fsummary>Get the temporary reboot command</fsummary>
<desc>
<p>Returns <c>{ok, Options}</c> where <c>Options</c> is a list of current options enabled for heart.
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index 87b08e4e36..709ba8e8fd 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -28,7 +28,7 @@
<date>1998-02-04</date>
<rev>A</rev>
</header>
- <module>inet</module>
+ <module since="">inet</module>
<modulesummary>Access to TCP/IP protocols.</modulesummary>
<description>
<p>This module provides access to TCP/IP protocols.</p>
@@ -298,7 +298,7 @@ fe80::204:acff:fe17:bf38
<funcs>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a socket of any type.</fsummary>
<desc>
<p>Closes a socket of any type.</p>
@@ -306,7 +306,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return a descriptive string for an error reason.</fsummary>
<desc>
<p>Returns a diagnostic error string. For possible POSIX values and
@@ -316,7 +316,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="get_rc" arity="0"/>
+ <name name="get_rc" arity="0" since=""/>
<fsummary>Return a list of IP configuration parameters.</fsummary>
<desc>
<p>
@@ -335,7 +335,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getaddr" arity="2"/>
+ <name name="getaddr" arity="2" since=""/>
<fsummary>Return the IP address for a host.</fsummary>
<desc>
<p>Returns the IP address for <c><anno>Host</anno></c> as a tuple of
@@ -345,7 +345,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getaddrs" arity="2"/>
+ <name name="getaddrs" arity="2" since=""/>
<fsummary>Return the IP addresses for a host.</fsummary>
<desc>
<p>Returns a list of all IP addresses for <c><anno>Host</anno></c>.
@@ -355,7 +355,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostbyaddr" arity="1"/>
+ <name name="gethostbyaddr" arity="1" since=""/>
<fsummary>Return a hostent record for the host with the specified
address.</fsummary>
<desc>
@@ -364,7 +364,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostbyname" arity="1"/>
+ <name name="gethostbyname" arity="1" since=""/>
<fsummary>Return a hostent record for the host with the specified name.
</fsummary>
<desc>
@@ -376,7 +376,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostbyname" arity="2"/>
+ <name name="gethostbyname" arity="2" since=""/>
<fsummary>Return a hostent record for the host with the specified name.
</fsummary>
<desc>
@@ -386,7 +386,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="gethostname" arity="0"/>
+ <name name="gethostname" arity="0" since=""/>
<fsummary>Return the local hostname.</fsummary>
<desc>
<p>Returns the local hostname. Never fails.</p>
@@ -394,7 +394,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getifaddrs" arity="0"/>
+ <name name="getifaddrs" arity="0" since="OTP R14B01"/>
<fsummary>Return a list of interfaces and their addresses.</fsummary>
<desc>
<p>
@@ -416,7 +416,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name>getifaddrs(Opts) ->
+ <name since="OTP 21.2">getifaddrs(Opts) ->
{ok, [{Ifname, Ifopts}]} | {error, Posix}
</name>
<fsummary>Return a list of interfaces and their addresses.</fsummary>
@@ -459,7 +459,7 @@ fe80::204:acff:fe17:bf38
</func>
<func>
- <name name="getopts" arity="2"/>
+ <name name="getopts" arity="2" since=""/>
<fsummary>Get one or more options for a socket.</fsummary>
<desc>
<p>Gets one or more options for a socket. For a list of available
@@ -529,8 +529,8 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="getstat" arity="1"/>
- <name name="getstat" arity="2"/>
+ <name name="getstat" arity="1" since=""/>
+ <name name="getstat" arity="2" since=""/>
<fsummary>Get one or more statistic options for a socket.</fsummary>
<type name="stat_option"/>
<desc>
@@ -586,9 +586,9 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="i" arity="0" />
- <name name="i" arity="1" />
- <name name="i" arity="2" />
+ <name name="i" arity="0" since="OTP 21.0"/>
+ <name name="i" arity="1" since="OTP 21.0"/>
+ <name name="i" arity="2" since="OTP 21.0"/>
<fsummary>Displays information and statistics about sockets on the terminal</fsummary>
<desc>
<p>
@@ -641,7 +641,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="ntoa" arity="1" />
+ <name name="ntoa" arity="1" since="OTP R16B02"/>
<fsummary>Convert IPv6/IPV4 address to ASCII.</fsummary>
<desc>
<p>Parses an
@@ -651,7 +651,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_address" arity="1" />
+ <name name="parse_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 or IPv6 address.</fsummary>
<desc>
<p>Parses an IPv4 or IPv6 address string and returns an
@@ -662,7 +662,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv4_address" arity="1" />
+ <name name="parse_ipv4_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 address.</fsummary>
<desc>
<p>Parses an IPv4 address string and returns an
@@ -672,7 +672,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv4strict_address" arity="1" />
+ <name name="parse_ipv4strict_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 address strict.</fsummary>
<desc>
<p>Parses an IPv4 address string containing four fields, that is,
@@ -683,7 +683,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv6_address" arity="1" />
+ <name name="parse_ipv6_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv6 address.</fsummary>
<desc>
<p>Parses an IPv6 address string and returns an
@@ -694,7 +694,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_ipv6strict_address" arity="1" />
+ <name name="parse_ipv6strict_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv6 address strict.</fsummary>
<desc>
<p>Parses an IPv6 address string and returns an
@@ -704,7 +704,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="ipv4_mapped_ipv6_address" arity="1" />
+ <name name="ipv4_mapped_ipv6_address" arity="1" since="OTP 21.0"/>
<fsummary>Convert to and from IPv4-mapped IPv6 address.</fsummary>
<desc>
<p>
@@ -717,7 +717,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="parse_strict_address" arity="1" />
+ <name name="parse_strict_address" arity="1" since="OTP R16B"/>
<fsummary>Parse an IPv4 or IPv6 address strict.</fsummary>
<desc>
<p>Parses an IPv4 or IPv6 address string and returns an
@@ -728,7 +728,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="peername" arity="1"/>
+ <name name="peername" arity="1" since=""/>
<fsummary>Return the address and port for the other end of a connection.
</fsummary>
<desc>
@@ -741,7 +741,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="peernames" arity="1"/>
+ <name name="peernames" arity="1" since="OTP R16B03"/>
<fsummary>Return all address/port numbers for the other end of a
connection.</fsummary>
<desc>
@@ -755,7 +755,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="peernames" arity="2"/>
+ <name name="peernames" arity="2" since="OTP R16B03"/>
<fsummary>Return all address/port numbers for the other end of a
connection.</fsummary>
<desc>
@@ -774,7 +774,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="port" arity="1"/>
+ <name name="port" arity="1" since=""/>
<fsummary>Return the local port number for a socket.</fsummary>
<desc>
<p>Returns the local port number for a socket.</p>
@@ -782,7 +782,7 @@ get_tcpi_sacked(Sock) ->
</func>
<func>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="2" since=""/>
<fsummary>Set one or more options for a socket.</fsummary>
<desc>
<p>Sets one or more options for a socket.</p>
@@ -1007,13 +1007,34 @@ get_tcpi_sacked(Sock) ->
<marker id="option-linger"></marker>
</item>
<tag><c>{linger, {true|false, Seconds}}</c></tag>
- <item>
+ <item>
<p>Determines the time-out, in seconds, for flushing unsent data
- in the <c>close/1</c> socket call. If the first component of
- the value tuple is <c>false</c>, the second is ignored. This
- means that <c>close/1</c> returns immediately, not waiting
- for data to be flushed. Otherwise, the second component is
- the flushing time-out, in seconds.</p>
+ in the <c>close/1</c> socket call. </p>
+ <p>The first component is if linger is enabled, the second component
+ is the flushing time-out, in seconds. There are 3 alternatives:</p>
+ <taglist>
+ <tag><c>{false, _}</c></tag>
+ <item>
+ <p>close/1 or shutdown/2 returns immediately,
+ not waiting for data to be flushed, with closing
+ happening in the background.</p>
+ </item>
+ <tag><c>{true, 0}</c></tag>
+ <item>
+ <p>Aborts the connection when it is closed.
+ Discards any data still remaining in the send buffers
+ and sends RST to the peer.</p>
+ <p>This avoids TCP's TIME_WAIT state, but leaves open
+ the possibility that another "incarnation" of this connection
+ being created.</p>
+ </item>
+ <tag><c>{true, Time} when Time > 0</c></tag>
+ <item>
+ <p>close/1 or shutdown/2 will not return until
+ all queued messages for the socket have been successfully
+ sent or the linger timeout (Time) has been reached.</p>
+ </item>
+ </taglist>
</item>
<tag><c>{low_msgq_watermark, Size}</c></tag>
<item>
@@ -1486,7 +1507,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
</func>
<func>
- <name name="sockname" arity="1"/>
+ <name name="sockname" arity="1" since=""/>
<fsummary>Return the local address and port number for a socket.
</fsummary>
<desc>
@@ -1499,7 +1520,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
</func>
<func>
- <name name="socknames" arity="1"/>
+ <name name="socknames" arity="1" since="OTP R16B03"/>
<fsummary>Return all local address/port numbers for a socket.</fsummary>
<desc>
<p>Equivalent to
@@ -1509,7 +1530,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code>
</func>
<func>
- <name name="socknames" arity="2"/>
+ <name name="socknames" arity="2" since="OTP R16B03"/>
<fsummary>Return all local address/port numbers for a socket.</fsummary>
<desc>
<p>Returns a list of all local address/port number pairs for a socket
diff --git a/lib/kernel/doc/src/inet_res.xml b/lib/kernel/doc/src/inet_res.xml
index 351d86a93a..1904e371f7 100644
--- a/lib/kernel/doc/src/inet_res.xml
+++ b/lib/kernel/doc/src/inet_res.xml
@@ -28,7 +28,7 @@
<date>2009-09-11</date>
<rev>A</rev>
</header>
- <module>inet_res</module>
+ <module since="">inet_res</module>
<modulesummary>A rudimentary DNS client.</modulesummary>
<description>
<p>This module performs DNS name resolving to recursive name servers.</p>
@@ -185,8 +185,8 @@ inet_dns:record_type(_) -> undefined.</pre>
<funcs>
<func>
- <name name="getbyname" arity="2"/>
- <name name="getbyname" arity="3"/>
+ <name name="getbyname" arity="2" since=""/>
+ <name name="getbyname" arity="3" since=""/>
<fsummary>Resolve a DNS record of the specified type for the specified
host.</fsummary>
<desc>
@@ -205,8 +205,8 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="gethostbyaddr" arity="1"/>
- <name name="gethostbyaddr" arity="2"/>
+ <name name="gethostbyaddr" arity="1" since=""/>
+ <name name="gethostbyaddr" arity="2" since=""/>
<fsummary>Return a hostent record for the host with the specified
address.</fsummary>
<desc>
@@ -217,9 +217,9 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="gethostbyname" arity="1"/>
- <name name="gethostbyname" arity="2"/>
- <name name="gethostbyname" arity="3"/>
+ <name name="gethostbyname" arity="1" since=""/>
+ <name name="gethostbyname" arity="2" since=""/>
+ <name name="gethostbyname" arity="3" since=""/>
<fsummary>Return a hostent record for the host with the specified name.
</fsummary>
<desc>
@@ -235,9 +235,9 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="lookup" arity="3"/>
- <name name="lookup" arity="4"/>
- <name name="lookup" arity="5"/>
+ <name name="lookup" arity="3" since=""/>
+ <name name="lookup" arity="4" since=""/>
+ <name name="lookup" arity="5" since=""/>
<fsummary>Resolve the DNS data for the record of the specified type
and class for the specified name.</fsummary>
<desc>
@@ -257,9 +257,9 @@ inet_dns:record_type(_) -> undefined.</pre>
</func>
<func>
- <name name="resolve" arity="3"/>
- <name name="resolve" arity="4"/>
- <name name="resolve" arity="5"/>
+ <name name="resolve" arity="3" since=""/>
+ <name name="resolve" arity="4" since=""/>
+ <name name="resolve" arity="5" since=""/>
<fsummary>Resolve a DNS record of the specified type and class
for the specified name.</fsummary>
<desc>
@@ -326,9 +326,9 @@ example_lookup(Name, Class, Type) ->
<funcs>
<func>
- <name name="nslookup" arity="3"/>
- <name name="nslookup" arity="4" clause_i="1"/>
- <name name="nslookup" arity="4" clause_i="2"/>
+ <name name="nslookup" arity="3" since=""/>
+ <name name="nslookup" arity="4" clause_i="1" since=""/>
+ <name name="nslookup" arity="4" clause_i="2" since=""/>
<fsummary>Resolve a DNS record of the specified type and class for the
specified name.</fsummary>
<type variable="Name"/>
@@ -344,8 +344,8 @@ example_lookup(Name, Class, Type) ->
</func>
<func>
- <name name="nnslookup" arity="4"/>
- <name name="nnslookup" arity="5"/>
+ <name name="nnslookup" arity="4" since=""/>
+ <name name="nnslookup" arity="5" since=""/>
<fsummary>Resolve a DNS record of the specified type and class
for the specified name.</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml
index 2bcf137299..0668676096 100644
--- a/lib/kernel/doc/src/logger.xml
+++ b/lib/kernel/doc/src/logger.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger.xml</file>
</header>
- <module>logger</module>
+ <module since="OTP 21.0">logger</module>
<modulesummary>API module for Logger, the standard logging facility
in Erlang/OTP.</modulesummary>
@@ -245,6 +245,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</desc>
</datatype>
<datatype>
+ <name name="olp_config"/>
+ <desc>
+ <p></p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="primary_config"/>
<desc>
<p>Primary configuration data for Logger. The following
@@ -290,7 +296,10 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
<section>
<title>Macros</title>
- <p>The following macros are defined:</p>
+ <p>The following macros are defined in <c>logger.hrl</c>, which
+ is included in a module with the directive</p>
+ <code>
+ -include_lib("kernel/include/logger.hrl").</code>
<list>
<item><c>?LOG_EMERGENCY(StringOrReport[,Metadata])</c></item>
@@ -331,9 +340,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</section>
<funcs>
<func>
- <name>emergency(StringOrReport[,Metadata])</name>
- <name>emergency(Format,Args[,Metadata])</name>
- <name>emergency(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">emergency(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">emergency(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">emergency(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>emergency</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -342,9 +351,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>alert(StringOrReport[,Metadata])</name>
- <name>alert(Format,Args[,Metadata])</name>
- <name>alert(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">alert(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">alert(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">alert(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>alert</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -353,9 +362,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>critical(StringOrReport[,Metadata])</name>
- <name>critical(Format,Args[,Metadata])</name>
- <name>critical(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">critical(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">critical(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">critical(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>critical</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -364,9 +373,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>error(StringOrReport[,Metadata])</name>
- <name>error(Format,Args[,Metadata])</name>
- <name>error(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">error(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">error(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">error(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>error</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -375,9 +384,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>warning(StringOrReport[,Metadata])</name>
- <name>warning(Format,Args[,Metadata])</name>
- <name>warning(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">warning(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">warning(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">warning(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>warning</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -386,9 +395,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>notice(StringOrReport[,Metadata])</name>
- <name>notice(Format,Args[,Metadata])</name>
- <name>notice(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">notice(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">notice(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">notice(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>notice</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -397,9 +406,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>info(StringOrReport[,Metadata])</name>
- <name>info(Format,Args[,Metadata])</name>
- <name>info(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">info(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">info(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">info(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>info</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -408,9 +417,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name>debug(StringOrReport[,Metadata])</name>
- <name>debug(Format,Args[,Metadata])</name>
- <name>debug(Fun,FunArgs[,Metadata])</name>
+ <name since="OTP 21.0">debug(StringOrReport[,Metadata])</name>
+ <name since="OTP 21.0">debug(Format,Args[,Metadata])</name>
+ <name since="OTP 21.0">debug(Fun,FunArgs[,Metadata])</name>
<fsummary>Logs the given message as level <c>debug</c>.</fsummary>
<desc>
<p>Equivalent to
@@ -419,12 +428,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="log" arity="2"/>
- <name name="log" arity="3" clause_i="1"/>
- <name name="log" arity="3" clause_i="2"/>
- <name name="log" arity="3" clause_i="3"/>
- <name name="log" arity="4" clause_i="1"/>
- <name name="log" arity="4" clause_i="2"/>
+ <name name="log" arity="2" since="OTP 21.0"/>
+ <name name="log" arity="3" clause_i="1" since="OTP 21.0"/>
+ <name name="log" arity="3" clause_i="2" since="OTP 21.0"/>
+ <name name="log" arity="3" clause_i="3" since="OTP 21.0"/>
+ <name name="log" arity="4" clause_i="1" since="OTP 21.0"/>
+ <name name="log" arity="4" clause_i="2" since="OTP 21.0"/>
<fsummary>Logs the given message.</fsummary>
<type variable="Level"/>
<type variable="StringOrReport" name_i="1"/>
@@ -445,7 +454,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</section>
<funcs>
<func>
- <name name="add_handler" arity="3"/>
+ <name name="add_handler" arity="3" since="OTP 21.0"/>
<fsummary>Add a handler with the given configuration.</fsummary>
<desc>
<p>Add a handler with the given configuration.</p>
@@ -456,7 +465,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="add_handler_filter" arity="3"/>
+ <name name="add_handler_filter" arity="3" since="OTP 21.0"/>
<fsummary>Add a filter to the specified handler.</fsummary>
<desc>
<p>Add a filter to the specified handler.</p>
@@ -497,7 +506,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="add_handlers" arity="1" clause_i="1"/>
+ <name name="add_handlers" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Set up log handlers from the application's
configuration parameters.</fsummary>
<desc>
@@ -507,7 +516,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
</func>
<func>
- <name name="add_handlers" arity="1" clause_i="2"/>
+ <name name="add_handlers" arity="1" clause_i="2" since="OTP 21.0"/>
<fsummary>Setup logger handlers.</fsummary>
<type name="config_handler"/>
<desc>
@@ -550,7 +559,7 @@ start(_, []) ->
</func>
<func>
- <name name="add_primary_filter" arity="2"/>
+ <name name="add_primary_filter" arity="2" since="OTP 21.0"/>
<fsummary>Add a primary filter to Logger.</fsummary>
<desc>
<p>Add a primary filter to Logger.</p>
@@ -591,16 +600,16 @@ start(_, []) ->
</func>
<func>
- <name name="get_config" arity="0"/>
+ <name name="get_config" arity="0" since="OTP 21.0"/>
<fsummary>Look up the current Logger configuration</fsummary>
<desc>
- <p>Look up all current Logger configuration, including primary
- and handler configuration, and module level settings.</p>
+ <p>Look up all current Logger configuration, including primary,
+ handler, and proxy configuration, and module level settings.</p>
</desc>
</func>
<func>
- <name name="get_handler_config" arity="0"/>
+ <name name="get_handler_config" arity="0" since="OTP 21.0"/>
<fsummary>Look up the current configuration for all handlers.</fsummary>
<desc>
<p>Look up the current configuration for all handlers.</p>
@@ -608,7 +617,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_handler_config" arity="1"/>
+ <name name="get_handler_config" arity="1" since="OTP 21.0"/>
<fsummary>Look up the current configuration for the given
handler.</fsummary>
<desc>
@@ -617,7 +626,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_handler_ids" arity="0"/>
+ <name name="get_handler_ids" arity="0" since="OTP 21.0"/>
<fsummary>Look up the identities for all installed handlers.</fsummary>
<desc>
<p>Look up the identities for all installed handlers.</p>
@@ -625,7 +634,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_primary_config" arity="0"/>
+ <name name="get_primary_config" arity="0" since="OTP 21.0"/>
<fsummary>Look up the current primary configuration for Logger.</fsummary>
<desc>
<p>Look up the current primary configuration for Logger.</p>
@@ -633,7 +642,18 @@ start(_, []) ->
</func>
<func>
- <name name="get_module_level" arity="0"/>
+ <name name="get_proxy_config" arity="0" since="OTP 21.3"/>
+ <fsummary>Look up the current configuration for the Logger proxy.</fsummary>
+ <desc>
+ <p>Look up the current configuration for the Logger proxy.</p>
+ <p>For more information about the proxy, see
+ section <seealso marker="logger_chapter#proxy">Logger
+ Proxy</seealso> in the Kernel User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="get_module_level" arity="0" since="OTP 21.0"/>
<fsummary>Look up all current module levels.</fsummary>
<desc>
<p>Look up all current module levels. Returns a list
@@ -645,7 +665,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_module_level" arity="1"/>
+ <name name="get_module_level" arity="1" since="OTP 21.0"/>
<fsummary>Look up the current level for the given modules.</fsummary>
<desc>
<p>Look up the current level for the given modules. Returns a
@@ -657,7 +677,7 @@ start(_, []) ->
</func>
<func>
- <name name="get_process_metadata" arity="0"/>
+ <name name="get_process_metadata" arity="0" since="OTP 21.0"/>
<fsummary>Retrieve data set with set_process_metadata/1.</fsummary>
<desc>
<p>Retrieve data set
@@ -669,7 +689,7 @@ start(_, []) ->
</func>
<func>
- <name name="remove_handler" arity="1"/>
+ <name name="remove_handler" arity="1" since="OTP 21.0"/>
<fsummary>Remove the handler with the specified identity.</fsummary>
<desc>
<p>Remove the handler identified by <c><anno>HandlerId</anno></c>.</p>
@@ -677,7 +697,7 @@ start(_, []) ->
</func>
<func>
- <name name="remove_handler_filter" arity="2"/>
+ <name name="remove_handler_filter" arity="2" since="OTP 21.0"/>
<fsummary>Remove a filter from the specified handler.</fsummary>
<desc>
<p>Remove the filter identified
@@ -687,7 +707,7 @@ start(_, []) ->
</func>
<func>
- <name name="remove_primary_filter" arity="1"/>
+ <name name="remove_primary_filter" arity="1" since="OTP 21.0"/>
<fsummary>Remove a primary filter from Logger.</fsummary>
<desc>
<p>Remove the primary filter identified
@@ -696,7 +716,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_application_level" arity="2"/>
+ <name name="set_application_level" arity="2" since="OTP 21.1"/>
<fsummary>Set the log level for all modules in the specified application.</fsummary>
<desc>
<p>Set the log level for all the modules of the specified application.</p>
@@ -707,7 +727,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_handler_config" arity="2"/>
+ <name name="set_handler_config" arity="2" since="OTP 21.0"/>
<fsummary>Set configuration data for the specified handler.</fsummary>
<desc>
<p>Set configuration data for the specified handler. This
@@ -728,11 +748,11 @@ start(_, []) ->
</func>
<func>
- <name name="set_handler_config" arity="3" clause_i="1"/>
- <name name="set_handler_config" arity="3" clause_i="2"/>
- <name name="set_handler_config" arity="3" clause_i="3"/>
- <name name="set_handler_config" arity="3" clause_i="4"/>
- <name name="set_handler_config" arity="3" clause_i="5"/>
+ <name name="set_handler_config" arity="3" clause_i="1" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="2" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="3" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="4" since="OTP 21.0"/>
+ <name name="set_handler_config" arity="3" clause_i="5" since="OTP 21.0"/>
<fsummary>Add or update configuration data for the specified
handler.</fsummary>
<type variable="HandlerId"/>
@@ -764,7 +784,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_primary_config" arity="1"/>
+ <name name="set_primary_config" arity="1" since="OTP 21.0"/>
<fsummary>Set primary configuration data for Logger.</fsummary>
<desc>
<p>Set primary configuration data for Logger. This
@@ -782,9 +802,9 @@ start(_, []) ->
</func>
<func>
- <name name="set_primary_config" arity="2" clause_i="1"/>
- <name name="set_primary_config" arity="2" clause_i="2"/>
- <name name="set_primary_config" arity="2" clause_i="3"/>
+ <name name="set_primary_config" arity="2" clause_i="1" since="OTP 21.0"/>
+ <name name="set_primary_config" arity="2" clause_i="2" since="OTP 21.0"/>
+ <name name="set_primary_config" arity="2" clause_i="3" since="OTP 21.0"/>
<fsummary>Add or update primary configuration data for Logger.</fsummary>
<type variable="Level" name_i="1"/>
<type variable="FilterDefault" name_i="2"/>
@@ -798,7 +818,28 @@ start(_, []) ->
</func>
<func>
- <name name="set_module_level" arity="2"/>
+ <name name="set_proxy_config" arity="1" since="OTP 21.3"/>
+ <fsummary>Set configuration data for the Logger proxy.</fsummary>
+ <desc>
+ <p>Set configuration data for the Logger proxy. This
+ overwrites the current proxy configuration. Keys that are not
+ specified in the <c><anno>Config</anno></c> map gets default
+ values.</p>
+ <p>To modify the existing configuration,
+ use <seealso marker="#update_proxy_config-1">
+ <c>update_proxy_config/1</c></seealso>, or, if a more
+ complex merge is needed, read the current configuration
+ with <seealso marker="#get_proxy_config-0"><c>get_proxy_config/0</c>
+ </seealso>, then do the merge before writing the new
+ configuration back with this function.</p>
+ <p>For more information about the proxy, see
+ section <seealso marker="logger_chapter#proxy">Logger
+ Proxy</seealso> in the Kernel User's Guide.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="set_module_level" arity="2" since="OTP 21.0"/>
<fsummary>Set the log level for the specified modules.</fsummary>
<desc>
<p>Set the log level for the specified modules.</p>
@@ -838,7 +879,7 @@ start(_, []) ->
</func>
<func>
- <name name="set_process_metadata" arity="1"/>
+ <name name="set_process_metadata" arity="1" since="OTP 21.0"/>
<fsummary>Set metadata to use when logging from current process.</fsummary>
<desc>
<p>Set metadata which Logger shall automatically insert in
@@ -857,7 +898,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_application_level" arity="1"/>
+ <name name="unset_application_level" arity="1" since="OTP 21.1"/>
<fsummary>Unset the log level for all modules in the specified application.</fsummary>
<desc>
<p>Unset the log level for all the modules of the specified application.</p>
@@ -868,7 +909,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_module_level" arity="0"/>
+ <name name="unset_module_level" arity="0" since="OTP 21.0"/>
<fsummary>Remove module specific log settings for all modules.</fsummary>
<desc>
<p>Remove module specific log settings. After this, the
@@ -877,7 +918,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_module_level" arity="1"/>
+ <name name="unset_module_level" arity="1" since="OTP 21.0"/>
<fsummary>Remove module specific log settings for the given
modules.</fsummary>
<desc>
@@ -887,7 +928,7 @@ start(_, []) ->
</func>
<func>
- <name name="unset_process_metadata" arity="0"/>
+ <name name="unset_process_metadata" arity="0" since="OTP 21.0"/>
<fsummary>Delete data set with set_process_metadata/1.</fsummary>
<desc>
<p>Delete data set
@@ -899,7 +940,7 @@ start(_, []) ->
</func>
<func>
- <name name="update_formatter_config" arity="2"/>
+ <name name="update_formatter_config" arity="2" since="OTP 21.0"/>
<fsummary>Update the formatter configuration for the specified handler.</fsummary>
<desc>
<p>Update the formatter configuration for the specified handler.</p>
@@ -914,7 +955,7 @@ start(_, []) ->
</func>
<func>
- <name name="update_formatter_config" arity="3"/>
+ <name name="update_formatter_config" arity="3" since="OTP 21.0"/>
<fsummary>Update the formatter configuration for the specified handler.</fsummary>
<desc>
<p>Update the formatter configuration for the specified handler.</p>
@@ -925,7 +966,7 @@ start(_, []) ->
</func>
<func>
- <name name="update_handler_config" arity="2"/>
+ <name name="update_handler_config" arity="2" since="OTP 21.0"/>
<fsummary>Update configuration data for the specified handler.</fsummary>
<desc>
<p>Update configuration data for the specified handler. This function
@@ -941,11 +982,11 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)).
</func>
<func>
- <name name="update_handler_config" arity="3" clause_i="1"/>
- <name name="update_handler_config" arity="3" clause_i="2"/>
- <name name="update_handler_config" arity="3" clause_i="3"/>
- <name name="update_handler_config" arity="3" clause_i="4"/>
- <name name="update_handler_config" arity="3" clause_i="5"/>
+ <name name="update_handler_config" arity="3" clause_i="1" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="2" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="3" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="4" since="OTP 21.2"/>
+ <name name="update_handler_config" arity="3" clause_i="5" since="OTP 21.2"/>
<fsummary>Add or update configuration data for the specified
handler.</fsummary>
<type variable="HandlerId"/>
@@ -977,7 +1018,7 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)).
</func>
<func>
- <name name="update_primary_config" arity="1"/>
+ <name name="update_primary_config" arity="1" since="OTP 21.0"/>
<fsummary>Update primary configuration data for Logger.</fsummary>
<desc>
<p>Update primary configuration data for Logger. This function
@@ -993,7 +1034,7 @@ logger:set_primary_config(maps:merge(Old, Config)).
</func>
<func>
- <name name="update_process_metadata" arity="1"/>
+ <name name="update_process_metadata" arity="1" since="OTP 21.0"/>
<fsummary>Set or update metadata to use when logging from
current process.</fsummary>
<desc>
@@ -1010,6 +1051,25 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</seealso>.</p>
</desc>
</func>
+
+ <func>
+ <name name="update_proxy_config" arity="1" since="OTP 21.3"/>
+ <fsummary>Update configuration data for the Logger proxy.</fsummary>
+ <desc>
+ <p>Update configuration data for the Logger proxy. This function
+ behaves as if it was implemented as follows:</p>
+ <code type="erl">
+Old = logger:get_proxy_config(),
+logger:set_proxy_config(maps:merge(Old, Config)).
+ </code>
+ <p>To overwrite the existing configuration without any merge,
+ use <seealso marker="#set_proxy_config-1"><c>set_proxy_config/1</c>
+ </seealso>.</p>
+ <p>For more information about the proxy, see
+ section <seealso marker="logger_chapter#proxy">Logger
+ Proxy</seealso> in the Kernel User's Guide.</p>
+ </desc>
+ </func>
</funcs>
<section>
@@ -1018,7 +1078,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</section>
<funcs>
<func>
- <name name="compare_levels" arity="2"/>
+ <name name="compare_levels" arity="2" since="OTP 21.0"/>
<fsummary>Compare the severity of two log levels.</fsummary>
<desc>
<p>Compare the severity of two log levels. Returns <c>gt</c>
@@ -1029,7 +1089,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name name="format_report" arity="1"/>
+ <name name="format_report" arity="1" since="OTP 21.0"/>
<fsummary>Convert a log message on report form to {Format, Args}.</fsummary>
<desc>
<p>Convert a log message on report form to <c>{Format,
@@ -1059,7 +1119,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
<funcs>
<func>
- <name>HModule:adding_handler(Config1) -> {ok, Config2} | {error,
+ <name since="OTP 21.0">HModule:adding_handler(Config1) -> {ok, Config2} | {error,
Reason}</name>
<fsummary>An instance of this handler is about to be added.</fsummary>
<type>
@@ -1085,7 +1145,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name>
+ <name since="OTP 21.2">HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name>
<fsummary>The configuration for this handler is about to change.</fsummary>
<type>
<v>SetOrUpdate = set | update</v>
@@ -1123,7 +1183,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:filter_config(Config) -> FilteredConfig</name>
+ <name since="OTP 21.2">HModule:filter_config(Config) -> FilteredConfig</name>
<fsummary>Remove internal data from configuration.</fsummary>
<type>
<v>Config = FilteredConfig =
@@ -1143,7 +1203,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:log(LogEvent, Config) -> void()</name>
+ <name since="OTP 21.0">HModule:log(LogEvent, Config) -> void()</name>
<fsummary>Log the given log event.</fsummary>
<type>
<v>LogEvent =
@@ -1166,7 +1226,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</func>
<func>
- <name>HModule:removing_handler(Config) -> ok</name>
+ <name since="OTP 21.0">HModule:removing_handler(Config) -> ok</name>
<fsummary>The given handler is about to be removed.</fsummary>
<type>
<v>Config =
@@ -1194,7 +1254,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
<funcs>
<func>
- <name>FModule:check_config(FConfig) -> ok | {error, Reason}</name>
+ <name since="OTP 21.0">FModule:check_config(FConfig) -> ok | {error, Reason}</name>
<fsummary>Validate the given formatter configuration.</fsummary>
<type>
<v>FConfig =
@@ -1214,7 +1274,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
<item><seealso marker="logger#set_handler_config-2">
<c>logger:set_handler_config/2,3</c></seealso></item>
<item><seealso marker="logger#update_handler_config-2">
- <c>logger:updata_handler_config/2,3</c></seealso></item>
+ <c>logger:update_handler_config/2,3</c></seealso></item>
<item><seealso marker="logger#update_formatter_config-2">
<c>logger:update_formatter_config/2</c></seealso></item>
</list>
@@ -1225,7 +1285,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)).
</desc>
</func>
<func>
- <name>FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name>
+ <name since="OTP 21.0">FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name>
<fsummary>Format the given log event.</fsummary>
<type>
<v>LogEvent =
diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml
index 8fc52ce0a9..03b9edcf8f 100644
--- a/lib/kernel/doc/src/logger_chapter.xml
+++ b/lib/kernel/doc/src/logger_chapter.xml
@@ -113,7 +113,10 @@
of functions on the form <c>logger:Level/1,2,3</c>, which are
all shortcuts
for <seealso marker="logger#log-2">
- <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p>
+ <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p>
+ <p>The macros are defined in <c>logger.hrl</c>, which is included
+ in a module with the directive</p>
+ <code>-include_lib("kernel/include/logger.hrl").</code>
<p>The difference between using the macros and the exported
functions is that macros add location (originator) information
to the metadata, and performs lazy evaluation by wrapping the
@@ -690,8 +693,10 @@ logger:debug(#{got => connection_request, id => Id, state => State},
with <seealso marker="#logger_sasl_compatible">
<c>logger_sasl_compatible</c></seealso>.</p>
<p>With this parameter, you can modify or disable the default
- handler, add custom handlers and primary logger filters, and
- set log levels per module.</p>
+ handler, add custom handlers and primary logger filters, set
+ log levels per module, and modify
+ the <seealso marker="#proxy">proxy</seealso>
+ configuration.</p>
<p><c>Config</c> is any (zero or more) of the following:</p>
<taglist>
<tag><c>{handler, default, undefined}</c></tag>
@@ -743,6 +748,14 @@ logger:debug(#{got => connection_request, id => Id, state => State},
<p>for each <c>Module</c>.</p>
<p>Multiple entries of this type are allowed.</p>
</item>
+ <tag><c>{proxy, ProxyConfig}</c></tag>
+ <item>
+ <p>Sets the proxy configuration, equivalent to calling</p>
+ <pre><seealso marker="logger#set_proxy_config/1">
+ logger:set_proxy_config(ProxyConfig)
+ </seealso></pre>
+ <p>Only one entry of this type is allowed.</p>
+ </item>
</taglist>
<p>See
section <seealso marker="#config_examples">Configuration
@@ -1331,9 +1344,50 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h,
</section>
<section>
+ <marker id="proxy"/>
+ <title>Logger Proxy</title>
+ <p>The Logger proxy is an Erlang process which is part of the
+ Kernel application's supervision tree. During startup, the proxy
+ process registers itself as the <c>system_logger</c>, meaning
+ that log events produced by the emulator are sent to this
+ process.</p>
+ <p>When a log event is issued on a process which has its group
+ leader on a remote node, Logger automatically forwards the log
+ event to the group leader's node. To achieve this, it first
+ sends the log event as an Erlang message from the original
+ client process to the proxy on the local node, and the proxy in
+ turn forwards the event to the proxy on the remote node.</p>
+ <p>When receiving a log event, either from the emulator or from a
+ remote node, the proxy calls the Logger API to log the event.</p>
+ <p>The proxy process is overload protected in the same way as
+ described in
+ section <seealso marker="#overload_protection">Protecting the
+ Handler from Overload</seealso>, but with the following default
+ values:</p>
+ <code>
+ #{sync_mode_qlen => 500,
+ drop_mode_qlen => 1000,
+ flush_qlen => 5000,
+ burst_limit_enable => false,
+ overload_kill_enable => false}</code>
+ <p>For log events from the emulator, synchronous message passing
+ mode is not applicable, since all messages are passed
+ asynchronously by the emulator. Drop mode is achieved by setting
+ the <c>system_logger</c> to <c>undefined</c>, forcing the
+ emulator to drop events until it is set back to the proxy pid
+ again.</p>
+ <p>The proxy uses <seealso marker="erts:erlang#send_nosuspend/2">
+ <c>erlang:send_nosuspend/2</c></seealso> when sending log
+ events to a remote node. If the message could not be sent
+ without suspending the sender, it is dropped. This is to avoid
+ blocking the proxy process.</p>
+ </section>
+
+ <section>
<title>See Also</title>
<p>
<seealso marker="disk_log"><c>disk_log(3)</c></seealso>,
+ <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>,
<seealso marker="error_logger"><c>error_logger(3)</c></seealso>,
<seealso marker="logger"><c>logger(3)</c></seealso>,
<seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>,
diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml
index d9b941a0a9..5b2374690e 100644
--- a/lib/kernel/doc/src/logger_disk_log_h.xml
+++ b/lib/kernel/doc/src/logger_disk_log_h.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_disk_log_h.xml</file>
</header>
- <module>logger_disk_log_h</module>
+ <module since="OTP 21.0">logger_disk_log_h</module>
<modulesummary>A disk_log based handler for Logger</modulesummary>
<description>
@@ -148,7 +148,7 @@ erl -kernel logger '[{handler,default,logger_disk_log_h,
<funcs>
<func>
- <name name="filesync" arity="1" clause_i="1"/>
+ <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Writes buffered data to disk.</fsummary>
<desc>
<p>Write buffered data to disk.</p>
diff --git a/lib/kernel/doc/src/logger_filters.xml b/lib/kernel/doc/src/logger_filters.xml
index 90f1fcc270..0a02342864 100644
--- a/lib/kernel/doc/src/logger_filters.xml
+++ b/lib/kernel/doc/src/logger_filters.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_filters.xml</file>
</header>
- <module>logger_filters</module>
+ <module since="OTP 21.0">logger_filters</module>
<modulesummary>Filters to use with Logger.</modulesummary>
<description>
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name name="domain" arity="2"/>
+ <name name="domain" arity="2" since="OTP 21.0"/>
<fsummary>Filter log events based on the domain field in
metadata.</fsummary>
<desc>
@@ -152,7 +152,7 @@ ok</code>
</func>
<func>
- <name name="level" arity="2"/>
+ <name name="level" arity="2" since="OTP 21.0"/>
<fsummary>Filter log events based on the log level.</fsummary>
<desc>
<p>This filter provides a way of filtering log events based
@@ -212,7 +212,7 @@ ok</code>
</func>
<func>
- <name name="progress" arity="2"/>
+ <name name="progress" arity="2" since="OTP 21.0"/>
<fsummary>Filter progress reports from supervisor and application_controller.</fsummary>
<desc>
<p>This filter matches all progress reports
@@ -227,7 +227,7 @@ ok</code>
</func>
<func>
- <name name="remote_gl" arity="2"/>
+ <name name="remote_gl" arity="2" since="OTP 21.0"/>
<fsummary>Filter events with group leader on remote node.</fsummary>
<desc>
<p>This filter matches all events originating from a process
diff --git a/lib/kernel/doc/src/logger_formatter.xml b/lib/kernel/doc/src/logger_formatter.xml
index 24772fd6c4..6dc83d24e1 100644
--- a/lib/kernel/doc/src/logger_formatter.xml
+++ b/lib/kernel/doc/src/logger_formatter.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_formatter.xml</file>
</header>
- <module>logger_formatter</module>
+ <module since="OTP 21.0">logger_formatter</module>
<modulesummary>Default formatter for Logger.</modulesummary>
<description>
@@ -289,7 +289,7 @@ exit_reason: "It crashed"</code>
<funcs>
<func>
- <name name="check_config" arity="1"/>
+ <name name="check_config" arity="1" since="OTP 21.0"/>
<fsummary>Validates the given formatter configuration.</fsummary>
<desc>
<p>The function is called by Logger when the formatter
@@ -303,14 +303,14 @@ exit_reason: "It crashed"</code>
<item><seealso marker="logger#set_handler_config-2">
<c>logger:set_handler_config/2,3</c></seealso></item>
<item><seealso marker="logger#update_handler_config-2">
- <c>logger:updata_handler_config/2</c></seealso></item>
+ <c>logger:update_handler_config/2</c></seealso></item>
<item><seealso marker="logger#update_formatter_config-2">
<c>logger:update_formatter_config/2</c></seealso></item>
</list>
</desc>
</func>
<func>
- <name name="format" arity="2"/>
+ <name name="format" arity="2" since="OTP 21.0"/>
<fsummary>Formats the given message.</fsummary>
<desc>
<p>This the formatter callback function to be called from
diff --git a/lib/kernel/doc/src/logger_std_h.xml b/lib/kernel/doc/src/logger_std_h.xml
index e156f5719b..fcd180abd6 100644
--- a/lib/kernel/doc/src/logger_std_h.xml
+++ b/lib/kernel/doc/src/logger_std_h.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>logger_std_h.xml</file>
</header>
- <module>logger_std_h</module>
+ <module since="OTP 21.0">logger_std_h</module>
<modulesummary>Standard handler for Logger.</modulesummary>
<description>
@@ -121,7 +121,7 @@ erl -kernel logger '[{handler,default,logger_std_h,
<funcs>
<func>
- <name name="filesync" arity="1" clause_i="1"/>
+ <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Writes buffered data to disk.</fsummary>
<desc>
<p>Write buffered data to disk.</p>
diff --git a/lib/kernel/doc/src/net_adm.xml b/lib/kernel/doc/src/net_adm.xml
index 6957a3b5e4..c3e1619f1b 100644
--- a/lib/kernel/doc/src/net_adm.xml
+++ b/lib/kernel/doc/src/net_adm.xml
@@ -28,7 +28,7 @@
<date>1996-09-10</date>
<rev>A</rev>
</header>
- <module>net_adm</module>
+ <module since="">net_adm</module>
<modulesummary>Various Erlang net administration routines.</modulesummary>
<description>
<p>This module contains various network utility functions.</p>
@@ -36,7 +36,7 @@
<funcs>
<func>
- <name name="dns_hostname" arity="1"/>
+ <name name="dns_hostname" arity="1" since=""/>
<fsummary>Official name of a host.</fsummary>
<desc>
<p>Returns the official name of <c><anno>Host</anno></c>, or
@@ -46,7 +46,7 @@
</func>
<func>
- <name name="host_file" arity="0"/>
+ <name name="host_file" arity="0" since=""/>
<fsummary>Read file <c>.hosts.erlang</c>.</fsummary>
<desc>
<p>Reads file <c>.hosts.erlang</c>, see section
@@ -58,7 +58,7 @@
</func>
<func>
- <name name="localhost" arity="0"/>
+ <name name="localhost" arity="0" since=""/>
<fsummary>Name of the local host.</fsummary>
<desc>
<p>Returns the name of the local host. If Erlang was started
@@ -68,8 +68,8 @@
</func>
<func>
- <name name="names" arity="0"/>
- <name name="names" arity="1"/>
+ <name name="names" arity="0" since=""/>
+ <name name="names" arity="1" since=""/>
<fsummary>Names of Erlang nodes at a host.</fsummary>
<desc>
<p>Similar to <c>epmd -names</c>, see
@@ -86,7 +86,7 @@
</func>
<func>
- <name name="ping" arity="1"/>
+ <name name="ping" arity="1" since=""/>
<fsummary>Set up a connection to a node.</fsummary>
<desc>
<p>Sets up a connection to <c><anno>Node</anno></c>. Returns
@@ -95,8 +95,8 @@
</func>
<func>
- <name name="world" arity="0"/>
- <name name="world" arity="1"/>
+ <name name="world" arity="0" since=""/>
+ <name name="world" arity="1" since=""/>
<fsummary>Lookup and connect to all nodes at all hosts in
<c>.hosts.erlang</c>.</fsummary>
<type name="verbosity"/>
@@ -117,8 +117,8 @@
</func>
<func>
- <name name="world_list" arity="1"/>
- <name name="world_list" arity="2"/>
+ <name name="world_list" arity="1" since=""/>
+ <name name="world_list" arity="2" since=""/>
<fsummary>Lookup and connect to all nodes at specified hosts.</fsummary>
<type name="verbosity"/>
<desc>
diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml
index bfbe7a6470..419d3cad84 100644
--- a/lib/kernel/doc/src/net_kernel.xml
+++ b/lib/kernel/doc/src/net_kernel.xml
@@ -28,7 +28,7 @@
<date>1996-09-10</date>
<rev>A</rev>
</header>
- <module>net_kernel</module>
+ <module since="">net_kernel</module>
<modulesummary>Erlang networking kernel.</modulesummary>
<description>
<p>The net kernel is a system process, registered as
@@ -81,7 +81,7 @@ $ <input>erl -sname foobar</input></pre>
<funcs>
<func>
- <name name="allow" arity="1"/>
+ <name name="allow" arity="1" since=""/>
<fsummary>Permit access to a specified set of nodes</fsummary>
<desc>
<p>Permits access to the specified set of nodes.</p>
@@ -98,7 +98,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="connect_node" arity="1"/>
+ <name name="connect_node" arity="1" since=""/>
<fsummary>Establish a connection to a node.</fsummary>
<desc>
<p>Establishes a connection to <c><anno>Node</anno></c>. Returns
@@ -110,7 +110,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="get_net_ticktime" arity="0"/>
+ <name name="get_net_ticktime" arity="0" since=""/>
<fsummary>Get <c>net_ticktime</c>.</fsummary>
<desc>
<p>Gets <c>net_ticktime</c> (see
@@ -131,7 +131,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="getopts" arity="2"/>
+ <name name="getopts" arity="2" since="OTP 19.1"/>
<fsummary>Get distribution socket options.</fsummary>
<desc>
<p>Get one or more options for the distribution socket
@@ -146,8 +146,8 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="monitor_nodes" arity="1"/>
- <name name="monitor_nodes" arity="2"/>
+ <name name="monitor_nodes" arity="1" since=""/>
+ <name name="monitor_nodes" arity="2" since=""/>
<fsummary>Subscribe to node status change messages.</fsummary>
<desc>
<p>The calling process subscribes or unsubscribes to node
@@ -267,8 +267,8 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="set_net_ticktime" arity="1"/>
- <name name="set_net_ticktime" arity="2"/>
+ <name name="set_net_ticktime" arity="1" since=""/>
+ <name name="set_net_ticktime" arity="2" since=""/>
<fsummary>Set <c>net_ticktime</c>.</fsummary>
<desc>
<p>Sets <c>net_ticktime</c> (see
@@ -324,7 +324,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="2" since="OTP 19.1"/>
<fsummary>Set distribution socket options.</fsummary>
<desc>
<p>Set one or more options for distribution sockets.
@@ -345,9 +345,9 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name>start([Name]) -> {ok, pid()} | {error, Reason}</name>
- <name>start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name>
- <name>start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start([Name]) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name>
<fsummary>Turn an Erlang runtime system into a distributed node.</fsummary>
<type>
<v>Name = atom()</v>
@@ -364,7 +364,7 @@ $ <input>erl -sname foobar</input></pre>
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Turn a node into a non-distributed Erlang runtime system.</fsummary>
<desc>
<p>Turns a distributed node into a non-distributed node. For
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 8188ede6a2..021ecfa40d 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,123 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 6.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A new function, <c>logger:update_handler_config/3</c> is
+ added, and the handler callback <c>changing_config</c>
+ now has a new argument, <c>SetOrUpdate</c>, which
+ indicates if the configuration change comes from
+ <c>set_handler_config/2,3</c> or
+ <c>update_handler_config/2,3</c>.</p>
+ <p>
+ This allows the handler to consistently merge the new
+ configuration with the old (if the change comes from
+ <c>update_handler_config/2,3</c>) or with the default (if
+ the change comes from <c>set_handler_config/2,3</c>).</p>
+ <p>
+ The built-in handlers <c>logger_std_h</c> and
+ <c>logger_disk_log_h</c> are updated accordingly. A bug
+ which could cause inconsistency between the handlers'
+ internal state and the stored configuration is also
+ corrected.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15364</p>
+ </item>
+ <item>
+ <p>
+ Fix fallback when custom erl_epmd client does not
+ implement address_please.</p>
+ <p>
+ Own Id: OTP-15388 Aux Id: PR-1983 </p>
+ </item>
+ <item>
+ <p>
+ The logger ets table did not have the
+ <c>read_concurrency</c> option. This is now added.</p>
+ <p>
+ Own Id: OTP-15453 Aux Id: ERL-782 </p>
+ </item>
+ <item>
+ <p>
+ During system start, logger has a simple handler which
+ prints to stdout. After the kernel supervision is
+ started, this handler is removed and replaced by the
+ default handler. Due to a bug, logger earlier issued a
+ debug printout saying it received an unexpected message,
+ which was the EXIT message from the simple handler's
+ process. This is now corrected. The simple handler's
+ process now unlinks from the logger process before
+ terminating.</p>
+ <p>
+ Own Id: OTP-15466 Aux Id: ERL-788 </p>
+ </item>
+ <item>
+ <p>
+ The logger handler <c>logger_std_h</c> would not
+ re-create it's log file if it was removed. Due to this it
+ could not be used with tools like 'logrotate'. This is
+ now corrected.</p>
+ <p>
+ Own Id: OTP-15469</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A function <c>inet:getifaddrs/1</c> that takes a list
+ with a namespace option has been added, for platforms
+ that support that feature, for example Linux (only?).</p>
+ <p>
+ Own Id: OTP-15121 Aux Id: ERIERL-189, PR-1974 </p>
+ </item>
+ <item>
+ <p>Added the <c>nopush</c> option for TCP sockets, which
+ corresponds to <c>TCP_NOPUSH</c> on *BSD and
+ <c>TCP_CORK</c> on Linux.</p>
+ <p>This is also used internally in <c>file:sendfile</c>
+ to reduce latency on subsequent send operations.</p>
+ <p>
+ Own Id: OTP-15357 Aux Id: ERL-698 </p>
+ </item>
+ <item>
+ <p>
+ Optimize handling of send_delay for tcp sockes to better
+ work with the new pollthread implementation introduced in
+ OTP-21.</p>
+ <p>
+ Own Id: OTP-15471 Aux Id: ERIERL-229 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 6.1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug causing net_kernel process crash on connection
+ attempt from node with name identical to local node.</p>
+ <p>
+ Own Id: OTP-15438 Aux Id: ERL-781 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml
index c95e615c6b..0500e4cfb3 100644
--- a/lib/kernel/doc/src/os.xml
+++ b/lib/kernel/doc/src/os.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>os</module>
+ <module since="">os</module>
<modulesummary>Operating system-specific functions.</modulesummary>
<description>
<p>The functions in this module are operating system-specific.
@@ -134,8 +134,8 @@
<funcs>
<func>
- <name name="cmd" arity="1"/>
- <name name="cmd" arity="2"/>
+ <name name="cmd" arity="1" since=""/>
+ <name name="cmd" arity="2" since="OTP 20.2.3"/>
<fsummary>Execute a command in a shell of the target OS.</fsummary>
<desc>
<p>Executes <c><anno>Command</anno></c> in a command shell of the
@@ -173,8 +173,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="find_executable" arity="1"/>
- <name name="find_executable" arity="2"/>
+ <name name="find_executable" arity="1" since=""/>
+ <name name="find_executable" arity="2" since=""/>
<fsummary>Absolute filename of a program.</fsummary>
<desc>
<p>These two functions look up an executable program, with the
@@ -190,7 +190,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getenv" arity="0"/>
+ <name name="getenv" arity="0" since=""/>
<fsummary>List all environment variables.</fsummary>
<desc>
<p>Returns a list of all environment variables.
@@ -205,7 +205,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getenv" arity="1"/>
+ <name name="getenv" arity="1" since=""/>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
<p>Returns the <c><anno>Value</anno></c> of the environment variable
@@ -220,7 +220,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getenv" arity="2"/>
+ <name name="getenv" arity="2" since="OTP 18.0"/>
<fsummary>Get the value of an environment variable.</fsummary>
<desc>
<p>Returns the <c><anno>Value</anno></c> of the environment variable
@@ -235,7 +235,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="getpid" arity="0"/>
+ <name name="getpid" arity="0" since=""/>
<fsummary>Return the process identifier of the emulator
process.</fsummary>
<desc>
@@ -251,7 +251,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="putenv" arity="2"/>
+ <name name="putenv" arity="2" since=""/>
<fsummary>Set a new value for an environment variable.</fsummary>
<desc>
<p>Sets a new <c><anno>Value</anno></c> for environment variable
@@ -277,7 +277,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="set_signal" arity="2"/>
+ <name name="set_signal" arity="2" since="OTP 20.0"/>
<fsummary>Enables or disables handling of OS signals.</fsummary>
<desc>
<p>Enables or disables OS signals.</p>
@@ -304,7 +304,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="system_time" arity="0"/>
+ <name name="system_time" arity="0" since="OTP 18.0"/>
<fsummary>Current OS system time.</fsummary>
<desc>
<p>Returns the current
@@ -317,7 +317,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="system_time" arity="1"/>
+ <name name="system_time" arity="1" since="OTP 18.0"/>
<fsummary>Current OS system time.</fsummary>
<desc>
<p>Returns the current
@@ -332,7 +332,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code>
</func>
<func>
- <name name="timestamp" arity="0"/>
+ <name name="timestamp" arity="0" since=""/>
<fsummary>Current OS system time on the <c>erlang:timestamp/0</c> format.</fsummary>
<type_desc variable="Timestamp">Timestamp = {MegaSecs, Secs, MicroSecs}</type_desc>
<desc>
@@ -373,7 +373,7 @@ calendar:now_to_universal_time(TS),
</func>
<func>
- <name name="perf_counter" arity="0"/>
+ <name name="perf_counter" arity="0" since="OTP 19.0"/>
<fsummary>Returns a performance counter</fsummary>
<desc>
<p>Returns the current performance counter value in <c>perf_counter</c>
@@ -383,7 +383,7 @@ calendar:now_to_universal_time(TS),
</desc>
</func>
<func>
- <name name="perf_counter" arity="1"/>
+ <name name="perf_counter" arity="1" since="OTP 19.0"/>
<fsummary>Returns a performance counter</fsummary>
<desc><p>Returns a performance counter that can be used as a very fast and
high resolution timestamp. This counter is read directly from the hardware or operating
@@ -397,7 +397,7 @@ calendar:now_to_universal_time(TS),
</desc>
</func>
<func>
- <name name="type" arity="0"/>
+ <name name="type" arity="0" since=""/>
<fsummary>Return the OS family and, in some cases, the OS name of the
current OS.</fsummary>
<desc>
@@ -417,7 +417,7 @@ calendar:now_to_universal_time(TS),
</func>
<func>
- <name name="unsetenv" arity="1"/>
+ <name name="unsetenv" arity="1" since="OTP R16B03"/>
<fsummary>Delete an environment variable.</fsummary>
<desc>
<p>Deletes the environment variable <c><anno>VarName</anno></c>.</p>
@@ -429,7 +429,7 @@ calendar:now_to_universal_time(TS),
</func>
<func>
- <name name="version" arity="0"/>
+ <name name="version" arity="0" since=""/>
<fsummary>Return the OS versions.</fsummary>
<desc>
<p>Returns the OS version.
diff --git a/lib/kernel/doc/src/pg2.xml b/lib/kernel/doc/src/pg2.xml
index 0631b317b4..058d711756 100644
--- a/lib/kernel/doc/src/pg2.xml
+++ b/lib/kernel/doc/src/pg2.xml
@@ -32,7 +32,7 @@
<rev>A2</rev>
<file>pg2.xml</file>
</header>
- <module>pg2</module>
+ <module since="">pg2</module>
<modulesummary>Distributed named process groups.</modulesummary>
<description>
<p>This module implements process groups. Each message can be sent
@@ -66,7 +66,7 @@
<funcs>
<func>
- <name name="create" arity="1"/>
+ <name name="create" arity="1" since=""/>
<fsummary>Create a new, empty process group.</fsummary>
<desc>
<p>Creates a new, empty process group. The group is globally
@@ -75,7 +75,7 @@
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete a process group.</fsummary>
<desc>
<p>Deletes a process group.</p>
@@ -83,7 +83,7 @@
</func>
<func>
- <name name="get_closest_pid" arity="1"/>
+ <name name="get_closest_pid" arity="1" since=""/>
<fsummary>Common dispatch function.</fsummary>
<desc>
<p>A useful dispatch function that can be used from
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="get_local_members" arity="1"/>
+ <name name="get_local_members" arity="1" since=""/>
<fsummary>Return all local processes in a group.</fsummary>
<desc>
<p>Returns all processes running on the local node in the
@@ -104,7 +104,7 @@
</func>
<func>
- <name name="get_members" arity="1"/>
+ <name name="get_members" arity="1" since=""/>
<fsummary>Return all processes in a group.</fsummary>
<desc>
<p>Returns all processes in the group <c>Name</c>. This
@@ -114,7 +114,7 @@
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join a process to a group.</fsummary>
<desc>
<p>Joins the process <c>Pid</c> to the group <c>Name</c>.
@@ -124,7 +124,7 @@
</func>
<func>
- <name name="leave" arity="2"/>
+ <name name="leave" arity="2" since=""/>
<fsummary>Make a process leave a group.</fsummary>
<desc>
<p>Makes the process <c>Pid</c> leave the group <c>Name</c>.
@@ -134,8 +134,8 @@
</func>
<func>
- <name name="start" arity="0"/>
- <name name="start_link" arity="0"/>
+ <name name="start" arity="0" since=""/>
+ <name name="start_link" arity="0" since=""/>
<fsummary>Start the <c>pg2</c> server.</fsummary>
<desc>
<p>Starts the <c>pg2</c> server. Normally, the server does not need
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="which_groups" arity="0"/>
+ <name name="which_groups" arity="0" since=""/>
<fsummary>Return a list of all known groups.</fsummary>
<desc>
<p>Returns a list of all known groups.</p>
diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml
index fab616e630..c55454506e 100644
--- a/lib/kernel/doc/src/rpc.xml
+++ b/lib/kernel/doc/src/rpc.xml
@@ -28,7 +28,7 @@
<date>1996-09-10</date>
<rev>A</rev>
</header>
- <module>rpc</module>
+ <module since="">rpc</module>
<modulesummary>Remote Procedure Call services.</modulesummary>
<description>
<p>This module contains services similar to Remote
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name name="abcast" arity="2"/>
+ <name name="abcast" arity="2" since=""/>
<fsummary>Broadcast a message asynchronously to a registered process on
all nodes.</fsummary>
<desc>
@@ -61,7 +61,7 @@
</func>
<func>
- <name name="abcast" arity="3"/>
+ <name name="abcast" arity="3" since=""/>
<fsummary>Broadcast a message asynchronously to a registered process on
specific nodes.</fsummary>
<desc>
@@ -72,7 +72,7 @@
</func>
<func>
- <name name="async_call" arity="4"/>
+ <name name="async_call" arity="4" since=""/>
<fsummary>Evaluate a function call on a node, asynchronous
version.</fsummary>
<desc>
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="block_call" arity="4"/>
+ <name name="block_call" arity="4" since=""/>
<fsummary>Evaluate a function call on a node in the RPC server's
context.</fsummary>
<desc>
@@ -115,7 +115,7 @@
</func>
<func>
- <name name="block_call" arity="5"/>
+ <name name="block_call" arity="5" since=""/>
<fsummary>Evaluate a function call on a node in the RPC server's
context.</fsummary>
<desc>
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="call" arity="4"/>
+ <name name="call" arity="4" since=""/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
@@ -138,7 +138,7 @@
</func>
<func>
- <name name="call" arity="5"/>
+ <name name="call" arity="5" since=""/>
<fsummary>Evaluate a function call on a node.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="cast" arity="4"/>
+ <name name="cast" arity="4" since=""/>
<fsummary>Run a function on a node ignoring the result.</fsummary>
<desc>
<p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>,
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="eval_everywhere" arity="3"/>
+ <name name="eval_everywhere" arity="3" since=""/>
<fsummary>Run a function on all nodes, ignoring the result.</fsummary>
<desc>
<p>Equivalent to <c>eval_everywhere([node()|nodes()],
@@ -181,7 +181,7 @@
</func>
<func>
- <name name="eval_everywhere" arity="4"/>
+ <name name="eval_everywhere" arity="4" since=""/>
<fsummary>Run a function on specific nodes, ignoring the
result.</fsummary>
<desc>
@@ -192,7 +192,7 @@
</func>
<func>
- <name name="multi_server_call" arity="2"/>
+ <name name="multi_server_call" arity="2" since=""/>
<fsummary>Interact with the servers on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multi_server_call([node()|nodes()],
@@ -201,7 +201,7 @@
</func>
<func>
- <name name="multi_server_call" arity="3"/>
+ <name name="multi_server_call" arity="3" since=""/>
<fsummary>Interact with the servers on a number of nodes.</fsummary>
<desc>
<p>Can be used when interacting with servers called
@@ -224,7 +224,7 @@
</func>
<func>
- <name name="multicall" arity="3"/>
+ <name name="multicall" arity="3" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>,
@@ -233,7 +233,7 @@
</func>
<func>
- <name name="multicall" arity="4" clause_i="1"/>
+ <name name="multicall" arity="4" clause_i="1" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multicall(<anno>Nodes</anno>, <anno>Module</anno>,
@@ -242,7 +242,7 @@
</func>
<func>
- <name name="multicall" arity="4" clause_i="2"/>
+ <name name="multicall" arity="4" clause_i="2" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>,
@@ -252,7 +252,7 @@
</func>
<func>
- <name name="multicall" arity="5"/>
+ <name name="multicall" arity="5" since=""/>
<fsummary>Evaluate a function call on a number of nodes.</fsummary>
<desc>
<p>In contrast to an RPC, a multicall is an RPC that is sent
@@ -288,7 +288,7 @@
</func>
<func>
- <name name="nb_yield" arity="1"/>
+ <name name="nb_yield" arity="1" since=""/>
<fsummary>Deliver the result of evaluating a function call on a node
(non-blocking).</fsummary>
<desc>
@@ -297,7 +297,7 @@
</func>
<func>
- <name name="nb_yield" arity="2"/>
+ <name name="nb_yield" arity="2" since=""/>
<fsummary>Deliver the result of evaluating a function call on a node
(non-blocking).</fsummary>
<desc>
@@ -315,7 +315,7 @@
</func>
<func>
- <name name="parallel_eval" arity="1"/>
+ <name name="parallel_eval" arity="1" since=""/>
<fsummary>Evaluate many function calls on all nodes in
parallel.</fsummary>
<desc>
@@ -328,7 +328,7 @@
</func>
<func>
- <name name="pinfo" arity="1"/>
+ <name name="pinfo" arity="1" since=""/>
<fsummary>Information about a process.</fsummary>
<desc>
<p>Location transparent version of the BIF
@@ -337,8 +337,8 @@
</func>
<func>
- <name name="pinfo" arity="2" clause_i="1"/>
- <name name="pinfo" arity="2" clause_i="2"/>
+ <name name="pinfo" arity="2" clause_i="1" since=""/>
+ <name name="pinfo" arity="2" clause_i="2" since=""/>
<fsummary>Information about a process.</fsummary>
<desc>
<p>Location transparent version of the BIF
@@ -347,7 +347,7 @@
</func>
<func>
- <name name="pmap" arity="3"/>
+ <name name="pmap" arity="3" since=""/>
<fsummary>Parallel evaluation of mapping a function over a
list.</fsummary>
<desc>
@@ -360,7 +360,7 @@
</func>
<func>
- <name name="sbcast" arity="2"/>
+ <name name="sbcast" arity="2" since=""/>
<fsummary>Broadcast a message synchronously to a registered process on
all nodes.</fsummary>
<desc>
@@ -370,7 +370,7 @@
</func>
<func>
- <name name="sbcast" arity="3"/>
+ <name name="sbcast" arity="3" since=""/>
<fsummary>Broadcast a message synchronously to a registered process on
specific nodes.</fsummary>
<desc>
@@ -391,7 +391,7 @@
</func>
<func>
- <name name="server_call" arity="4"/>
+ <name name="server_call" arity="4" since=""/>
<fsummary>Interact with a server on a node.</fsummary>
<desc>
<p>Can be used when interacting with a server called
@@ -410,7 +410,7 @@
</func>
<func>
- <name name="yield" arity="1"/>
+ <name name="yield" arity="1" since=""/>
<fsummary>Deliver the result of evaluating a function call on a node
(blocking).</fsummary>
<desc>
diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml
index 1a4a74419a..aa29223dd0 100644
--- a/lib/kernel/doc/src/seq_trace.xml
+++ b/lib/kernel/doc/src/seq_trace.xml
@@ -28,7 +28,7 @@
<date>1998-04-16</date>
<rev>A</rev>
</header>
- <module>seq_trace</module>
+ <module since="">seq_trace</module>
<modulesummary>Sequential tracing of messages.</modulesummary>
<description>
<p>Sequential tracing makes it possible to trace all messages
@@ -51,7 +51,7 @@
</datatypes>
<funcs>
<func>
- <name name="set_token" arity="1"/>
+ <name name="set_token" arity="1" since=""/>
<fsummary>Set the trace token</fsummary>
<desc>
<p>Sets the trace token for the calling process to <c><anno>Token</anno></c>.
@@ -71,7 +71,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="set_token" arity="2"/>
+ <name name="set_token" arity="2" since=""/>
<fsummary>Set a component of the trace token</fsummary>
<type name="component"/>
<type name="flag"/>
@@ -158,7 +158,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="get_token" arity="0"/>
+ <name name="get_token" arity="0" since=""/>
<fsummary>Return the value of the trace token</fsummary>
<desc>
<p>Returns the value of the trace token for the calling process.
@@ -169,7 +169,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="get_token" arity="1"/>
+ <name name="get_token" arity="1" since=""/>
<fsummary>Return the value of a trace token component</fsummary>
<type name="component"/>
<type name="flag"/>
@@ -182,7 +182,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="print" arity="1"/>
+ <name name="print" arity="1" since=""/>
<fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary>
<desc>
<p>Puts the Erlang term <c><anno>TraceInfo</anno></c> into the sequential
@@ -192,7 +192,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="print" arity="2"/>
+ <name name="print" arity="2" since=""/>
<fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary>
<desc>
<p>Same as <c>print/1</c> with the additional condition that
@@ -201,7 +201,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="reset_trace" arity="0"/>
+ <name name="reset_trace" arity="0" since=""/>
<fsummary>Stop all sequential tracing on the local node</fsummary>
<desc>
<p>Sets the trace token to empty for all processes on the
@@ -213,7 +213,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="set_system_tracer" arity="1"/>
+ <name name="set_system_tracer" arity="1" since=""/>
<fsummary>Set the system tracer</fsummary>
<type name="tracer"/>
<desc>
@@ -227,7 +227,7 @@ seq_trace:set_token(OldToken), % activate the trace token again
</desc>
</func>
<func>
- <name name="get_system_tracer" arity="0"/>
+ <name name="get_system_tracer" arity="0" since=""/>
<fsummary>Return the pid() or port() of the current system tracer.</fsummary>
<type name="tracer"/>
<desc>
diff --git a/lib/kernel/doc/src/wrap_log_reader.xml b/lib/kernel/doc/src/wrap_log_reader.xml
index 7fb9c1c023..5f37e7ec5f 100644
--- a/lib/kernel/doc/src/wrap_log_reader.xml
+++ b/lib/kernel/doc/src/wrap_log_reader.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>wrap_log_reader.sgml</file>
</header>
- <module>wrap_log_reader</module>
+ <module since="">wrap_log_reader</module>
<modulesummary>A service to read internally formatted wrap disk logs.
</modulesummary>
<description>
@@ -65,8 +65,8 @@
<funcs>
<func>
- <name name="chunk" arity="1"/>
- <name name="chunk" arity="2"/>
+ <name name="chunk" arity="1" since=""/>
+ <name name="chunk" arity="2" since=""/>
<fsummary>Read a chunk of objects written to a wrap log.</fsummary>
<type name="chunk_ret"/>
<desc>
@@ -105,7 +105,7 @@
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a log.</fsummary>
<desc>
<p>Closes a log file properly.</p>
@@ -113,8 +113,8 @@
</func>
<func>
- <name name="open" arity="1"/>
- <name name="open" arity="2"/>
+ <name name="open" arity="1" since=""/>
+ <name name="open" arity="2" since=""/>
<fsummary>Open a log file.</fsummary>
<type name="open_ret"/>
<desc>
diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile
index 57f17defc8..3d1506ea08 100644
--- a/lib/kernel/src/Makefile
+++ b/lib/kernel/src/Makefile
@@ -118,6 +118,8 @@ MODULES = \
logger_h_common \
logger_filters \
logger_formatter \
+ logger_olp \
+ logger_proxy \
logger_server \
logger_simple_h \
logger_sup \
@@ -151,7 +153,7 @@ INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \
inet_dns.hrl inet_res.hrl \
inet_boot.hrl inet_config.hrl inet_int.hrl \
inet_dns_record_adts.hrl \
- logger_internal.hrl logger_h_common.hrl
+ logger_internal.hrl logger_olp.hrl logger_h_common.hrl
ERL_FILES= $(MODULES:%=%.erl)
@@ -279,6 +281,8 @@ $(EBIN)/logger_config.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_disk_log_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl
$(EBIN)/logger_filters.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_formatter.beam: logger_internal.hrl ../include/logger.hrl
+$(EBIN)/logger_olp.beam: logger_olp.hrl logger_internal.hrl
+$(EBIN)/logger_proxy.beam: logger_internal.hrl
$(EBIN)/logger_server.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_simple_h.beam: logger_internal.hrl ../include/logger.hrl
$(EBIN)/logger_std_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl
diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src
index fe073621c8..a1d9e8e215 100644
--- a/lib/kernel/src/kernel.app.src
+++ b/lib/kernel/src/kernel.app.src
@@ -68,6 +68,8 @@
logger_formatter,
logger_h_common,
logger_handler_watcher,
+ logger_olp,
+ logger_proxy,
logger_server,
logger_simple_h,
logger_std_h,
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index 0c0435e051..ccf0a82ced 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -16,15 +16,42 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
+%%
+%% We allow upgrade from, and downgrade to all previous
+%% versions from the following OTP releases:
+%% - OTP 20
+%% - OTP 21
+%%
+%% We also allow upgrade from, and downgrade to all
+%% versions that have branched off from the above
+%% stated previous versions.
+%%
{"%VSN%",
- %% Up from - max one major revision back
- [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0
- {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+
- {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0
- {<<"6\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21.1
- %% Down to - max one major revision back
- [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0
- {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+
- {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0
- {<<"6\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.1
-}.
+ [{<<"^5\\.3$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.3(?:\\.[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]},
+ {<<"^6\\.1$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ [{<<"^5\\.3$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^5\\.4\\.3(?:\\.[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]},
+ {<<"^6\\.1$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl
index 6762998d4f..abdd9a9ceb 100644
--- a/lib/kernel/src/logger.erl
+++ b/lib/kernel/src/logger.erl
@@ -43,11 +43,14 @@
get_module_level/0, get_module_level/1,
set_primary_config/1, set_primary_config/2,
set_handler_config/2, set_handler_config/3,
+ set_proxy_config/1,
update_primary_config/1,
update_handler_config/2, update_handler_config/3,
+ update_proxy_config/1,
update_formatter_config/2, update_formatter_config/3,
get_primary_config/0, get_handler_config/1,
get_handler_config/0, get_handler_ids/0, get_config/0,
+ get_proxy_config/0,
add_handlers/1]).
%% Private configuration
@@ -122,6 +125,18 @@
{filters,log | stop,[{filter_id(),filter()}]} |
{module_level,level(),[module()]}].
+-type olp_config() :: #{sync_mode_qlen => non_neg_integer(),
+ drop_mode_qlen => pos_integer(),
+ flush_qlen => pos_integer(),
+ burst_limit_enable => boolean(),
+ burst_limit_max_count => pos_integer(),
+ burst_limit_window_time => pos_integer(),
+ overload_kill_enable => boolean(),
+ overload_kill_qlen => pos_integer(),
+ overload_kill_mem_size => pos_integer(),
+ overload_kill_restart_after =>
+ non_neg_integer() | infinity}.
+
-export_type([log_event/0,
level/0,
report/0,
@@ -137,7 +152,8 @@
filter_arg/0,
filter_return/0,
config_handler/0,
- formatter_config/0]).
+ formatter_config/0,
+ olp_config/0]).
%%%-----------------------------------------------------------------
%%% API
@@ -390,6 +406,7 @@ set_primary_config(Key,Value) ->
set_primary_config(Config) ->
logger_server:set_config(primary,Config).
+
-spec set_handler_config(HandlerId,level,Level) -> Return when
HandlerId :: handler_id(),
Level :: level() | all | none,
@@ -419,6 +436,11 @@ set_handler_config(HandlerId,Key,Value) ->
set_handler_config(HandlerId,Config) ->
logger_server:set_config(HandlerId,Config).
+-spec set_proxy_config(Config) -> ok | {error,term()} when
+ Config :: olp_config().
+set_proxy_config(Config) ->
+ logger_server:set_config(proxy,Config).
+
-spec update_primary_config(Config) -> ok | {error,term()} when
Config :: primary_config().
update_primary_config(Config) ->
@@ -453,6 +475,11 @@ update_handler_config(HandlerId,Key,Value) ->
update_handler_config(HandlerId,Config) ->
logger_server:update_config(HandlerId,Config).
+-spec update_proxy_config(Config) -> ok | {error,term()} when
+ Config :: olp_config().
+update_proxy_config(Config) ->
+ logger_server:update_config(proxy,Config).
+
-spec get_primary_config() -> Config when
Config :: primary_config().
get_primary_config() ->
@@ -486,6 +513,12 @@ get_handler_ids() ->
{ok,#{handlers:=HandlerIds}} = logger_config:get(?LOGGER_TABLE,primary),
HandlerIds.
+-spec get_proxy_config() -> Config when
+ Config :: olp_config().
+get_proxy_config() ->
+ {ok,Config} = logger_config:get(?LOGGER_TABLE,proxy),
+ Config.
+
-spec update_formatter_config(HandlerId,FormatterConfig) ->
ok | {error,term()} when
HandlerId :: handler_id(),
@@ -606,10 +639,12 @@ unset_process_metadata() ->
-spec get_config() -> #{primary=>primary_config(),
handlers=>[handler_config()],
+ proxy=>olp_config(),
module_levels=>[{module(),level() | all | none}]}.
get_config() ->
#{primary=>get_primary_config(),
handlers=>get_handler_config(),
+ proxy=>get_proxy_config(),
module_levels=>lists:keysort(1,get_module_level())}.
-spec internal_init_logger() -> ok | {error,term()}.
@@ -672,6 +707,17 @@ init_kernel_handlers(Env) ->
%% This function is responsible for resolving the handler config
%% and then starting the correct handlers. This is done after the
%% kernel supervisor tree has been started as it needs the logger_sup.
+add_handlers(kernel) ->
+ Env = get_logger_env(kernel),
+ case get_proxy_opts(Env) of
+ undefined ->
+ add_handlers(kernel,Env);
+ Opts ->
+ case set_proxy_config(Opts) of
+ ok -> add_handlers(kernel,Env);
+ {error, Reason} -> {error,{bad_proxy_config,Reason}}
+ end
+ end;
add_handlers(App) when is_atom(App) ->
add_handlers(App,get_logger_env(App));
add_handlers(HandlerConfig) ->
@@ -729,6 +775,8 @@ check_logger_config(kernel,[{filters,_,_}|Env]) ->
check_logger_config(kernel,Env);
check_logger_config(kernel,[{module_level,_,_}|Env]) ->
check_logger_config(kernel,Env);
+check_logger_config(kernel,[{proxy,_}|Env]) ->
+ check_logger_config(kernel,Env);
check_logger_config(_,Bad) ->
throw(Bad).
@@ -784,6 +832,13 @@ get_primary_filters(Env) ->
_ -> throw({multiple_filters,Env})
end.
+get_proxy_opts(Env) ->
+ case [P || P={proxy,_} <- Env] of
+ [{proxy,Opts}] -> Opts;
+ [] -> undefined;
+ _ -> throw({multiple_proxies,Env})
+ end.
+
%% This function looks at the kernel logger environment
%% and updates it so that the correct logger is configured
init_default_config(Type,Env) when Type==standard_io;
@@ -880,30 +935,30 @@ log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) ->
maps:merge(Location,maps:merge(proc_meta(),Meta0))),
case node(maps:get(gl,Meta)) of
Node when Node=/=node() ->
- log_remote(Node,Level,Msg,Meta),
- do_log_allowed(Level,Msg,Meta);
+ log_remote(Node,Level,Msg,Meta);
_ ->
- do_log_allowed(Level,Msg,Meta)
- end.
+ ok
+ end,
+ do_log_allowed(Level,Msg,Meta,tid()).
-do_log_allowed(Level,{Format,Args}=Msg,Meta)
+do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid)
when ?IS_LEVEL(Level),
is_list(Format),
is_list(Args),
is_map(Meta) ->
- logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},tid());
-do_log_allowed(Level,Report,Meta)
+ logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid);
+do_log_allowed(Level,Report,Meta,Tid)
when ?IS_LEVEL(Level),
?IS_REPORT(Report),
is_map(Meta) ->
logger_backend:log_allowed(#{level=>Level,msg=>{report,Report},meta=>Meta},
- tid());
-do_log_allowed(Level,String,Meta)
+ Tid);
+do_log_allowed(Level,String,Meta,Tid)
when ?IS_LEVEL(Level),
?IS_STRING(String),
is_map(Meta) ->
logger_backend:log_allowed(#{level=>Level,msg=>{string,String},meta=>Meta},
- tid()).
+ Tid).
tid() ->
ets:whereis(?LOGGER_TABLE).
@@ -913,7 +968,7 @@ log_remote(Node,Level,Msg,Meta) ->
log_remote(Node,{log,Level,Msg,Meta}).
log_remote(Node,Request) ->
- {logger,Node} ! Request,
+ logger_proxy:log({remote,Node,Request}),
ok.
add_default_metadata(Meta) ->
diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl
index 6bfe658552..5024d20cfe 100644
--- a/lib/kernel/src/logger_config.erl
+++ b/lib/kernel/src/logger_config.erl
@@ -31,7 +31,9 @@
-include("logger_internal.hrl").
new(Name) ->
- _ = ets:new(Name,[set,protected,named_table,{write_concurrency,true}]),
+ _ = ets:new(Name,[set,protected,named_table,
+ {read_concurrency,true},
+ {write_concurrency,true}]),
ets:whereis(Name).
delete(Tid,Id) ->
@@ -64,6 +66,8 @@ get(Tid,What) ->
case ets:lookup(Tid,table_key(What)) of
[{_,_,Config}] ->
{ok,Config};
+ [{_,Config}] when What=:=proxy ->
+ {ok,Config};
[] ->
{error,{not_found,What}}
end.
@@ -77,10 +81,15 @@ get(Tid,What,Level) ->
[Data] -> {ok,Data}
end.
+create(Tid,proxy,Config) ->
+ ets:insert(Tid,{table_key(proxy),Config});
create(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
ets:insert(Tid,{table_key(What),LevelInt,Config}).
+set(Tid,proxy,Config) ->
+ ets:insert(Tid,{table_key(proxy),Config}),
+ ok;
set(Tid,What,Config) ->
LevelInt = level_to_int(maps:get(level,Config)),
%% Should do this only if the level has actually changed. Possibly
@@ -146,5 +155,6 @@ int_to_level(?LOG_ALL) -> all.
%%%-----------------------------------------------------------------
%%% Internal
+table_key(proxy) -> ?PROXY_KEY;
table_key(primary) -> ?PRIMARY_KEY;
table_key(HandlerId) -> {?HANDLER_KEY,HandlerId}.
diff --git a/lib/kernel/src/logger_disk_log_h.erl b/lib/kernel/src/logger_disk_log_h.erl
index 2a81458ec8..47b39da900 100644
--- a/lib/kernel/src/logger_disk_log_h.erl
+++ b/lib/kernel/src/logger_disk_log_h.erl
@@ -19,124 +19,89 @@
%%
-module(logger_disk_log_h).
--behaviour(gen_server).
-
-include("logger.hrl").
-include("logger_internal.hrl").
-include("logger_h_common.hrl").
%%% API
--export([start_link/3, info/1, filesync/1, reset/1]).
+-export([filesync/1]).
-%% gen_server callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+%% logger_h_common callbacks
+-export([init/2, check_config/4, reset_state/2,
+ filesync/3, write/4, handle_info/3, terminate/3]).
%% logger callbacks
-export([log/2, adding_handler/1, removing_handler/1, changing_config/3,
filter_config/1]).
-%% handler internal
--export([log_handler_info/4]).
-
%%%===================================================================
%%% API
%%%===================================================================
%%%-----------------------------------------------------------------
-%%% Start a disk_log handler process and link to caller.
-%%% This function is called by the kernel supervisor when this
-%%% handler process gets added (as a result of calling add/3).
--spec start_link(Name, Config, HandlerState) -> {ok,Pid} | {error,Reason} when
- Name :: atom(),
- Config :: logger:handler_config(),
- HandlerState :: map(),
- Pid :: pid(),
- Reason :: term().
-
-start_link(Name, Config, HandlerState) ->
- proc_lib:start_link(?MODULE,init,[[Name,Config,HandlerState]]).
-
-%%%-----------------------------------------------------------------
%%%
-spec filesync(Name) -> ok | {error,Reason} when
Name :: atom(),
Reason :: handler_busy | {badarg,term()}.
-filesync(Name) when is_atom(Name) ->
- try
- gen_server:call(?name_to_reg_name(?MODULE,Name),
- disk_log_sync, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
filesync(Name) ->
- {error,{badarg,{filesync,[Name]}}}.
+ logger_h_common:filesync(?MODULE,Name).
+
+%%%===================================================================
+%%% logger callbacks
+%%%===================================================================
%%%-----------------------------------------------------------------
-%%%
--spec info(Name) -> Info | {error,Reason} when
- Name :: atom(),
- Info :: term(),
- Reason :: handler_busy | {badarg,term()}.
+%%% Handler being added
+adding_handler(Config) ->
+ logger_h_common:adding_handler(Config).
-info(Name) when is_atom(Name) ->
- try
- gen_server:call(?name_to_reg_name(?MODULE,Name),
- info, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
-info(Name) ->
- {error,{badarg,{info,[Name]}}}.
+%%%-----------------------------------------------------------------
+%%% Updating handler config
+changing_config(SetOrUpdate, OldConfig, NewConfig) ->
+ logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig).
%%%-----------------------------------------------------------------
-%%%
--spec reset(Name) -> ok | {error,Reason} when
- Name :: atom(),
- Reason :: handler_busy | {badarg,term()}.
+%%% Handler being removed
+removing_handler(Config) ->
+ logger_h_common:removing_handler(Config).
+
+%%%-----------------------------------------------------------------
+%%% Log a string or report
+-spec log(LogEvent, Config) -> ok when
+ LogEvent :: logger:log_event(),
+ Config :: logger:handler_config().
-reset(Name) when is_atom(Name) ->
- try
- gen_server:call(?name_to_reg_name(?MODULE,Name),
- reset, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
-reset(Name) ->
- {error,{badarg,{reset,[Name]}}}.
+log(LogEvent, Config) ->
+ logger_h_common:log(LogEvent, Config).
+%%%-----------------------------------------------------------------
+%%% Remove internal fields from configuration
+filter_config(Config) ->
+ logger_h_common:filter_config(Config).
%%%===================================================================
-%%% logger callbacks
+%%% logger_h_common callbacks
%%%===================================================================
-
-%%%-----------------------------------------------------------------
-%%% Handler being added
-adding_handler(#{id:=Name}=Config) ->
- case check_config(adding, Config) of
- {ok, #{config:=HConfig}=Config1} ->
- %% create initial handler state by merging defaults with config
- HState = maps:merge(get_init_state(), HConfig),
- case logger_h_common:overload_levels_ok(HState) of
- true ->
- start(Name, Config1, HState);
- false ->
- #{sync_mode_qlen := SMQL,
- drop_mode_qlen := DMQL,
- flush_qlen := FQL} = HState,
- {error,{invalid_levels,{SMQL,DMQL,FQL}}}
- end;
+init(Name, #{file:=File,type:=Type,max_no_bytes:=MNB,max_no_files:=MNF}) ->
+ case open_disk_log(Name, File, Type, MNB, MNF) of
+ ok ->
+ {ok,#{log_opts => #{file => File,
+ type => Type,
+ max_no_bytes => MNB,
+ max_no_files => MNF},
+ prev_log_result => ok,
+ prev_sync_result => ok,
+ prev_disk_log_info => undefined}};
Error ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% Updating handler config
-changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) ->
+check_config(Name,set,undefined,HConfig0) ->
+ HConfig=merge_default_logopts(Name,maps:merge(get_default_config(),HConfig0)),
+ check_config(HConfig);
+check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) ->
WriteOnce = maps:with([type,file,max_no_files,max_no_bytes],OldHConfig),
- ReadOnly = maps:with([handler_pid,mode_tab],OldHConfig),
- NewHConfig0 = maps:get(config, NewConfig, #{}),
Default =
case SetOrUpdate of
set ->
@@ -146,66 +111,25 @@ changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) ->
OldHConfig
end,
- %% Allow (accidentially) included read-only fields - just overwrite them
- NewHConfig = maps:merge(maps:merge(Default,NewHConfig0),ReadOnly),
+ NewHConfig = maps:merge(Default,NewHConfig0),
- %% But fail if write-once fields are changed
+ %% Fail if write-once fields are changed
case maps:with([type,file,max_no_files,max_no_bytes],NewHConfig) of
WriteOnce ->
- changing_config1(maps:get(handler_pid,OldHConfig),
- OldConfig,
- NewConfig#{config=>NewHConfig});
+ check_config(NewHConfig);
Other ->
{Old,New} = logger_server:diff_maps(WriteOnce,Other),
- {error,{illegal_config_change,#{config=>Old},#{config=>New}}}
- end.
-
-changing_config1(HPid, OldConfig, NewConfig) ->
- case check_config(changing, NewConfig) of
- Result = {ok,NewConfig1} ->
- try gen_server:call(HPid, {change_config,OldConfig,NewConfig1},
- ?DEFAULT_CALL_TIMEOUT) of
- ok -> Result;
- Error -> Error
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
- Error ->
- Error
+ {error,{illegal_config_change,?MODULE,Old,New}}
end.
-check_config(adding, #{id:=Name}=Config) ->
- %% merge handler specific config data
- HConfig1 = maps:get(config, Config, #{}),
- HConfig2 = maps:merge(get_default_config(), HConfig1),
- HConfig3 = merge_default_logopts(Name, HConfig2),
- case check_h_config(maps:to_list(HConfig3)) of
- ok ->
- {ok,Config#{config=>HConfig3}};
- Error ->
- Error
- end;
-check_config(changing, Config) ->
- HConfig = maps:get(config, Config, #{}),
+check_config(HConfig) ->
case check_h_config(maps:to_list(HConfig)) of
- ok -> {ok,Config};
- Error -> Error
+ ok ->
+ {ok,HConfig};
+ {error,{Key,Value}} ->
+ {error,{invalid_config,?MODULE,#{Key=>Value}}}
end.
-merge_default_logopts(Name, HConfig) ->
- Type = maps:get(type, HConfig, wrap),
- {DefaultNoFiles,DefaultNoBytes} =
- case Type of
- halt -> {undefined,infinity};
- _wrap -> {10,1048576}
- end,
- {ok,Dir} = file:get_cwd(),
- Defaults = #{file => filename:join(Dir,Name),
- max_no_files => DefaultNoFiles,
- max_no_bytes => DefaultNoBytes,
- type => Type},
- maps:merge(Defaults, HConfig).
-
check_h_config([{file,File}|Config]) when is_list(File) ->
check_h_config(Config);
check_h_config([{max_no_files,undefined}|Config]) ->
@@ -218,454 +142,59 @@ check_h_config([{max_no_bytes,N}|Config]) when is_integer(N), N>0 ->
check_h_config(Config);
check_h_config([{type,Type}|Config]) when Type==wrap; Type==halt ->
check_h_config(Config);
-check_h_config([Other | Config]) ->
- case logger_h_common:check_common_config(Other) of
- valid ->
- check_h_config(Config);
- invalid ->
- {error,{invalid_config,?MODULE,Other}}
- end;
+check_h_config([Other | _]) ->
+ {error,Other};
check_h_config([]) ->
ok.
-%%%-----------------------------------------------------------------
-%%% Handler being removed
-removing_handler(#{id:=Name}) ->
- stop(Name).
-
-%%%-----------------------------------------------------------------
-%%% Log a string or report
--spec log(LogEvent, Config) -> ok when
- LogEvent :: logger:log_event(),
- Config :: logger:handler_config().
-
-log(LogEvent, Config = #{id := Name,
- config := #{handler_pid := HPid,
- mode_tab := ModeTab}}) ->
- %% if the handler has crashed, we must drop this event
- %% and hope the handler restarts so we can try again
- true = is_process_alive(HPid),
- Bin = logger_h_common:log_to_binary(LogEvent, Config),
- logger_h_common:call_cast_or_drop(Name, HPid, ModeTab, Bin).
+get_default_config() ->
+ #{}.
-%%%-----------------------------------------------------------------
-%%% Remove internal fields from configuration
-filter_config(#{config:=HConfig}=Config) ->
- Config#{config=>maps:without([handler_pid,mode_tab],HConfig)}.
+merge_default_logopts(Name, HConfig) ->
+ Type = maps:get(type, HConfig, wrap),
+ {DefaultNoFiles,DefaultNoBytes} =
+ case Type of
+ halt -> {undefined,infinity};
+ _wrap -> {10,1048576}
+ end,
+ {ok,Dir} = file:get_cwd(),
+ Defaults = #{file => filename:join(Dir,Name),
+ max_no_files => DefaultNoFiles,
+ max_no_bytes => DefaultNoBytes,
+ type => Type},
+ maps:merge(Defaults, HConfig).
-%%%===================================================================
-%%% gen_server callbacks
-%%%===================================================================
+filesync(Name,_Mode,State) ->
+ Result = ?disk_log_sync(Name),
+ maybe_notify_error(Name, filesync, Result, prev_sync_result, State).
-init([Name,
- Config = #{config := HConfig = #{file:=File,
- type:=Type,
- max_no_bytes:=MNB,
- max_no_files:=MNF}},
- State = #{dl_sync_int := DLSyncInt}]) ->
-
- RegName = ?name_to_reg_name(?MODULE,Name),
- register(RegName, self()),
- process_flag(trap_exit, true),
- process_flag(message_queue_data, off_heap),
-
- ?init_test_hooks(),
- ?start_observation(Name),
-
- LogOpts = #{file=>File, type=>Type, max_no_bytes=>MNB, max_no_files=>MNF},
- case open_disk_log(Name, File, Type, MNB, MNF) of
- ok ->
- try ets:new(Name, [public]) of
- ModeTab ->
- ?set_mode(ModeTab, async),
- T0 = ?timestamp(),
- State1 =
- ?merge_with_stats(State#{
- id => Name,
- mode_tab => ModeTab,
- mode => async,
- dl_sync => DLSyncInt,
- log_opts => LogOpts,
- last_qlen => 0,
- last_log_ts => T0,
- burst_win_ts => T0,
- burst_msg_count => 0,
- last_op => sync,
- prev_log_result => ok,
- prev_sync_result => ok,
- prev_disk_log_info => undefined}),
- Config1 =
- Config#{config => HConfig#{handler_pid => self(),
- mode_tab => ModeTab}},
- proc_lib:init_ack({ok,self(),Config1}),
- gen_server:cast(self(), repeated_disk_log_sync),
- case logger_h_common:unset_restart_flag(Name, ?MODULE) of
- true ->
- %% inform about restart
- gen_server:cast(self(), {log_handler_info,
- "Handler ~p restarted",
- [Name]});
- false ->
- %% initial start
- ok
- end,
- gen_server:enter_loop(?MODULE, [], State1)
- catch
- _:Error ->
- unregister(RegName),
- logger_h_common:error_notify({open_disk_log,Name,Error}),
- proc_lib:init_ack(Error)
- end;
- Error ->
- unregister(RegName),
- logger_h_common:error_notify({open_disk_log,Name,Error}),
- proc_lib:init_ack(Error)
- end.
+write(Name, Mode, Bin, State) ->
+ Result = ?disk_log_write(Name, Mode, Bin),
+ maybe_notify_error(Name, log, Result, prev_log_result, State).
-%% This is the synchronous log event.
-handle_call({log, Bin}, _From, State) ->
- {Result,State1} = do_log(Bin, call, State),
- %% Result == ok | dropped
- {reply, Result, State1};
-
-handle_call(disk_log_sync, _From, State = #{id := Name}) ->
- State1 = #{prev_sync_result := Result} = disk_log_sync(Name, State),
- {reply, Result, State1};
-
-handle_call({change_config,_OldConfig,NewConfig}, _From,
- State = #{filesync_repeat_interval := FSyncInt0}) ->
- HConfig = maps:get(config, NewConfig, #{}),
- State1 = #{sync_mode_qlen := SMQL,
- drop_mode_qlen := DMQL,
- flush_qlen := FQL} = maps:merge(State, HConfig),
- case logger_h_common:overload_levels_ok(State1) of
- true ->
- _ =
- case maps:get(filesync_repeat_interval, HConfig, undefined) of
- undefined ->
- ok;
- no_repeat ->
- _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref,
- State,
- undefined));
- FSyncInt0 ->
- ok;
- _FSyncInt1 ->
- _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref,
- State,
- undefined)),
- _ = gen_server:cast(self(), repeated_disk_log_sync)
- end,
- {reply, ok, State1};
- false ->
- {reply, {error,{invalid_levels,{SMQL,DMQL,FQL}}}, State}
- end;
-
-handle_call(info, _From, State) ->
- {reply, State, State};
-
-handle_call(reset, _From, State) ->
- State1 = ?merge_with_stats(State),
- {reply, ok, State1#{last_qlen => 0,
- last_log_ts => ?timestamp(),
- prev_log_result => ok,
- prev_sync_result => ok,
- prev_disk_log_info => undefined}};
-
-handle_call(stop, _From, State) ->
- {stop, {shutdown,stopped}, ok, State}.
-
-
-%% This is the asynchronous log event.
-handle_cast({log, Bin}, State) ->
- {_,State1} = do_log(Bin, cast, State),
- {noreply, State1};
-
-handle_cast({log_handler_info, Format, Args}, State = #{id:=Name}) ->
- log_handler_info(Name, Format, Args, State),
- {noreply, State};
-
-%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this
-%% clause gets called repeatedly by the handler. In order to
-%% guarantee that a filesync *always* happens after the last log
-%% event, the repeat operation must be active!
-handle_cast(repeated_disk_log_sync,
- State = #{id := Name,
- filesync_repeat_interval := FSyncInt,
- last_op := LastOp}) ->
- State1 =
- if is_integer(FSyncInt) ->
- %% only do filesync if something has been
- %% written since last time we checked
- NewState = if LastOp == sync ->
- State;
- true ->
- disk_log_sync(Name, State)
- end,
- {ok,TRef} =
- timer:apply_after(FSyncInt, gen_server,cast,
- [self(),repeated_disk_log_sync]),
- NewState#{rep_sync_tref => TRef, last_op => sync};
- true ->
- State
- end,
- {noreply,State1}.
+reset_state(_Name, State) ->
+ State#{prev_log_result => ok,
+ prev_sync_result => ok,
+ prev_disk_log_info => undefined}.
%% The disk log owner must handle status messages from disk_log.
-handle_info({disk_log, _Node, _Log, {wrap,_NoLostItems}}, State) ->
- {noreply, State};
-handle_info({disk_log, _Node, Log, Info = {truncated,_NoLostItems}},
- State = #{id := Name, prev_disk_log_info := PrevInfo}) ->
- error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}),
- {noreply, State#{prev_disk_log_info => Info}};
-handle_info({disk_log, _Node, Log, Info = {blocked_log,_Items}},
- State = #{id := Name, prev_disk_log_info := PrevInfo}) ->
- error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}),
- {noreply, State#{prev_disk_log_info => Info}};
-handle_info({disk_log, _Node, Log, full},
- State = #{id := Name, prev_disk_log_info := PrevInfo}) ->
- error_notify_new(full, PrevInfo, {disk_log,Name,Log,full}),
- {noreply, State#{prev_disk_log_info => full}};
-handle_info({disk_log, _Node, Log, Info = {error_status,_Status}},
- State = #{id := Name, prev_disk_log_info := PrevInfo}) ->
- error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}),
- {noreply, State#{prev_disk_log_info => Info}};
-
-handle_info({'EXIT',_Pid,_Why}, State = #{id := _Name}) ->
- {noreply, State};
-
-handle_info(_, State) ->
- {noreply, State}.
-
-terminate(Reason, State = #{id := Name}) ->
- _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, State,
- undefined)),
+handle_info(Name, {disk_log, _Node, Log, Info={truncated,_NoLostItems}}, State) ->
+ maybe_notify_status(Name, Log, Info, prev_disk_log_info, State);
+handle_info(Name, {disk_log, _Node, Log, Info = {blocked_log,_Items}}, State) ->
+ maybe_notify_status(Name, Log, Info, prev_disk_log_info, State);
+handle_info(Name, {disk_log, _Node, Log, Info = full}, State) ->
+ maybe_notify_status(Name, Log, Info, prev_disk_log_info, State);
+handle_info(Name, {disk_log, _Node, Log, Info = {error_status,_Status}}, State) ->
+ maybe_notify_status(Name, Log, Info, prev_disk_log_info, State);
+handle_info(_, _, State) ->
+ State.
+
+terminate(Name, _Reason, _State) ->
_ = close_disk_log(Name, normal),
- ok = logger_h_common:stop_or_restart(Name, Reason, State),
- unregister(?name_to_reg_name(?MODULE, Name)),
ok.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
%%%-----------------------------------------------------------------
%%% Internal functions
-
-%%%-----------------------------------------------------------------
-%%%
-get_default_config() ->
- #{sync_mode_qlen => ?SYNC_MODE_QLEN,
- drop_mode_qlen => ?DROP_MODE_QLEN,
- flush_qlen => ?FLUSH_QLEN,
- burst_limit_enable => ?BURST_LIMIT_ENABLE,
- burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT,
- burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME,
- overload_kill_enable => ?OVERLOAD_KILL_ENABLE,
- overload_kill_qlen => ?OVERLOAD_KILL_QLEN,
- overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE,
- overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}.
-
-get_init_state() ->
- #{dl_sync_int => ?CONTROLLER_SYNC_INTERVAL,
- filesync_ok_qlen => ?FILESYNC_OK_QLEN}.
-
-%%%-----------------------------------------------------------------
-%%% Add a disk_log handler to the logger.
-%%% This starts a dedicated handler process which should always
-%%% exist if the handler is registered with logger (and should not
-%%% exist if the handler is not registered).
-%%%
-%%% Config is the logger:handler_config() map. Handler specific parameters
-%%% should be provided with a sub map associated with a key named
-%%% 'config', e.g:
-%%%
-%%% Config = #{config => #{sync_mode_qlen => 50}
-%%%
-%%% The 'config' sub map will also contain parameters for configuring
-%%% the disk_log:
-%%%
-%%% Config = #{config => #{file => file:filename(),
-%%% max_no_bytes => integer(),
-%%% max_no_files => integer(),
-%%% type => wrap | halt}}.
-%%%
-%%% If type == halt, then max_no_files is ignored.
-%%%
-%%% The disk_log handler process is linked to logger_sup, which is
-%%% part of the kernel application's supervision tree.
-start(Name, Config, HandlerState) ->
- LoggerDLH =
- #{id => Name,
- start => {?MODULE, start_link, [Name,Config,HandlerState]},
- restart => temporary,
- shutdown => 2000,
- type => worker,
- modules => [?MODULE]},
- case supervisor:start_child(logger_sup, LoggerDLH) of
- {ok,Pid,Config1} ->
- ok = logger_handler_watcher:register_handler(Name,Pid),
- {ok,Config1};
- Error ->
- Error
- end.
-
-%%%-----------------------------------------------------------------
-%%% Stop and remove the handler.
-stop(Name) ->
- case whereis(?name_to_reg_name(?MODULE,Name)) of
- undefined ->
- ok;
- Pid ->
- %% We don't want to do supervisor:terminate_child here
- %% since we need to distinguish this explicit stop from a
- %% system termination in order to avoid circular attempts
- %% at removing the handler (implying deadlocks and
- %% timeouts).
- %% And we don't need to do supervisor:delete_child, since
- %% the restart type is temporary, which means that the
- %% child specification is automatically removed from the
- %% supervisor when the process dies.
- _ = gen_server:call(Pid, stop),
- ok
- end.
-
-%%%-----------------------------------------------------------------
-%%% Logging and overload control.
--define(update_dl_sync(C, Interval),
- if C == 0 -> Interval;
- true -> C-1 end).
-
-%% check for overload between every event (and set Mode to async,
-%% sync or drop accordingly), but never flush the whole mailbox
-%% before LogWindowSize events have been handled
-do_log(Bin, CallOrCast, State = #{id:=Name, mode := Mode0}) ->
- T1 = ?timestamp(),
-
- %% check if the handler is getting overloaded, or if it's
- %% recovering from overload (the check must be done for each
- %% event to react quickly to large bursts of events and
- %% to ensure that the handler can never end up in drop mode
- %% with an empty mailbox, which would stop operation)
- {Mode1,QLen,Mem,State1} = logger_h_common:check_load(State),
-
- if (Mode1 == drop) andalso (Mode0 =/= drop) ->
- log_handler_info(Name, "Handler ~p switched to drop mode",
- [Name], State);
- (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) ->
- log_handler_info(Name, "Handler ~p switched to ~w mode",
- [Name,Mode1], State);
- true ->
- ok
- end,
-
- %% kill the handler if it can't keep up with the load
- logger_h_common:kill_if_choked(Name, QLen, Mem, ?MODULE, State),
-
- if Mode1 == flush ->
- flush(Name, QLen, T1, State1);
- true ->
- write(Name, Mode1, T1, Bin, CallOrCast, State1)
- end.
-
-%% this function is called by do_log/3 after an overload check
-%% has been performed, where QLen > FlushQLen
-flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) ->
- %% flush messages in the mailbox (a limited number in
- %% order to not cause long delays)
- NewFlushed = logger_h_common:flush_log_events(?FLUSH_MAX_N),
-
- %% write info in log about flushed messages
- log_handler_info(Name, "Handler ~p flushed ~w log events",
- [Name,NewFlushed], State),
-
- %% because of the receive loop when flushing messages, the
- %% handler will be scheduled out often and the mailbox could
- %% grow very large, so we'd better check the queue again here
- {_,_QLen1} = process_info(self(), message_queue_len),
- ?observe(Name,{max_qlen,_QLen1}),
-
- %% Add 1 for the current log event
- ?observe(Name,{flushed,NewFlushed+1}),
-
- State1 = ?update_max_time(?diff_time(T1,_T0),State),
- {dropped,?update_other(flushed,FLUSHED,NewFlushed,
- State1#{mode => ?set_mode(ModeTab,async),
- last_qlen => 0,
- last_log_ts => T1})}.
-
-%% this function is called to write to disk_log
-write(Name, Mode, T1, Bin, _CallOrCast,
- State = #{mode_tab := ModeTab,
- dl_sync := DLSync,
- dl_sync_int := DLSyncInt,
- last_qlen := LastQLen,
- last_log_ts := T0}) ->
- %% check if we need to limit the number of writes
- %% during a burst of log events
- {DoWrite,BurstWinT,BurstMsgCount} = logger_h_common:limit_burst(State),
-
- %% only send a synhrounous event to the disk_log process
- %% every DLSyncInt time, to give the handler time between
- %% writes so it can keep up with incoming messages
- {Status,LastQLen1,State1} =
- if DoWrite, DLSync == 0 ->
- ?observe(Name,{_CallOrCast,1}),
- NewState = disk_log_write(Name, Bin, State),
- {ok, element(2,process_info(self(),message_queue_len)),
- NewState};
- DoWrite ->
- ?observe(Name,{_CallOrCast,1}),
- NewState = disk_log_write(Name, Bin, State),
- {ok, LastQLen, NewState};
- not DoWrite ->
- ?observe(Name,{flushed,1}),
- {dropped, LastQLen, State}
- end,
-
- %% Check if the time since the previous log event is long enough -
- %% and the queue length small enough - to assume the mailbox has
- %% been emptied, and if so, do filesync operation and reset mode to
- %% async. Note that this is the best we can do to detect an idle
- %% handler without setting a timer after each log call/cast. If the
- %% time between two consecutive log events is fast and no new
- %% event comes in after the last one, idle state won't be detected!
- Time = ?diff_time(T1,T0),
- {Mode1,BurstMsgCount1,State2} =
- if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso
- (Time > ?IDLE_DETECT_TIME_USEC) ->
- {?change_mode(ModeTab,Mode,async), 0, disk_log_sync(Name,State1)};
- true ->
- {Mode, BurstMsgCount,State1}
- end,
-
- State3 =
- ?update_calls_or_casts(_CallOrCast,1,State2),
- State4 =
- ?update_max_time(Time,
- State3#{mode => Mode1,
- last_qlen := LastQLen1,
- last_log_ts => T1,
- burst_win_ts => BurstWinT,
- burst_msg_count => BurstMsgCount1,
- dl_sync => ?update_dl_sync(DLSync,DLSyncInt)}),
- {Status,State4}.
-
-
-log_handler_info(Name, Format, Args, State) ->
- Config =
- case logger:get_handler_config(Name) of
- {ok,Conf} -> Conf;
- _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}}
- end,
- Meta = #{time=>erlang:system_time(microsecond)},
- Bin = logger_h_common:log_to_binary(#{level => notice,
- msg => {Format,Args},
- meta => Meta}, Config),
- _ = disk_log_write(Name, Bin, State),
- ok.
-
-
open_disk_log(Name, File, Type, MaxNoBytes, MaxNoFiles) ->
case filelib:ensure_dir(File) of
ok ->
@@ -698,43 +227,26 @@ close_disk_log(Name, _) ->
_ = disk_log:lclose(Name),
ok.
-disk_log_write(Name, Bin, State) ->
- case ?disk_log_blog(Name, Bin) of
- ok ->
- State#{prev_log_result => ok, last_op => write};
- LogError ->
- _ = case maps:get(prev_log_result, State) of
- LogError ->
- %% don't report same error twice
- ok;
- _ ->
- LogOpts = maps:get(log_opts, State),
- logger_h_common:error_notify({Name,log,
- LogOpts,
- LogError})
- end,
- State#{prev_log_result => LogError}
- end.
-
-disk_log_sync(Name, State) ->
- case ?disk_log_sync(Name) of
- ok ->
- State#{prev_sync_result => ok, last_op => sync};
- SyncError ->
- _ = case maps:get(prev_sync_result, State) of
- SyncError ->
- %% don't report same error twice
- ok;
- _ ->
- LogOpts = maps:get(log_opts, State),
- logger_h_common:error_notify({Name,filesync,
- LogOpts,
- SyncError})
- end,
- State#{prev_sync_result => SyncError}
- end.
+disk_log_write(Name, sync, Bin) ->
+ disk_log:blog(Name, Bin);
+disk_log_write(Name, async, Bin) ->
+ disk_log:balog(Name, Bin).
+
+%%%-----------------------------------------------------------------
+%%% Print error messages, but don't repeat the same message
+maybe_notify_error(Name, Op, Result, Key, #{log_opts:=LogOpts}=State) ->
+ {Result,error_notify_new({Name, Op, LogOpts, Result}, Result, Key, State)}.
+
+maybe_notify_status(Name, Log, Info, Key, State) ->
+ error_notify_new({disk_log, Name, Log, Info}, Info, Key, State).
-error_notify_new(Info,Info, _Term) ->
+error_notify_new(Term, What, Key, State) ->
+ error_notify_new(What, maps:get(Key,State), Term),
+ State#{Key => What}.
+
+error_notify_new(ok,_Prev,_Term) ->
+ ok;
+error_notify_new(Same,Same,_Term) ->
ok;
-error_notify_new(_Info0,_Info1, Term) ->
+error_notify_new(_New,_Prev,Term) ->
logger_h_common:error_notify(Term).
diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl
index 94c640cb92..e69f6de38d 100644
--- a/lib/kernel/src/logger_h_common.erl
+++ b/lib/kernel/src/logger_h_common.erl
@@ -18,26 +18,345 @@
%% %CopyrightEnd%
%%
-module(logger_h_common).
+-behaviour(gen_server).
-include("logger_h_common.hrl").
-include("logger_internal.hrl").
--export([log_to_binary/2,
- check_common_config/1,
- call_cast_or_drop/4,
- check_load/1,
- limit_burst/1,
- kill_if_choked/5,
- flush_log_events/0,
- flush_log_events/1,
- handler_exit/2,
- set_restart_flag/2,
- unset_restart_flag/2,
- cancel_timer/1,
- stop_or_restart/3,
- overload_levels_ok/1,
- error_notify/1,
- info_notify/1]).
+%% API
+-export([filesync/2]).
+
+%% logger_olp callbacks
+-export([init/1, handle_load/2, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3, notify/2, reset_state/1]).
+
+%% logger callbacks
+-export([log/2, adding_handler/1, removing_handler/1, changing_config/3,
+ filter_config/1]).
+
+%% Library functions for handlers
+-export([error_notify/1]).
+
+-define(OLP_KEYS,[sync_mode_qlen,
+ drop_mode_qlen,
+ flush_qlen,
+ burst_limit_enable,
+ burst_limit_max_count,
+ burst_limit_window_time,
+ overload_kill_enable,
+ overload_kill_qlen,
+ overload_kill_mem_size,
+ overload_kill_restart_after]).
+
+-define(COMMON_KEYS,[filesync_repeat_interval]).
+
+-define(READ_ONLY_KEYS,[olp]).
+
+%%%-----------------------------------------------------------------
+%%% API
+
+%% This function is called by the logger_sup supervisor
+filesync(Module, Name) ->
+ call(Module, Name, filesync).
+
+%%%-----------------------------------------------------------------
+%%% Handler being added
+adding_handler(#{id:=Name,module:=Module}=Config) ->
+ HConfig0 = maps:get(config, Config, #{}),
+ HandlerConfig0 = maps:without(?OLP_KEYS++?COMMON_KEYS,HConfig0),
+ case Module:check_config(Name,set,undefined,HandlerConfig0) of
+ {ok,HandlerConfig} ->
+ ModifiedCommon = maps:with(?COMMON_KEYS,HandlerConfig),
+ CommonConfig0 = maps:with(?COMMON_KEYS,HConfig0),
+ CommonConfig = maps:merge(
+ maps:merge(get_default_config(), CommonConfig0),
+ ModifiedCommon),
+ case check_config(CommonConfig) of
+ ok ->
+ HConfig = maps:merge(CommonConfig,HandlerConfig),
+ OlpOpts = maps:with(?OLP_KEYS,HConfig0),
+ start(OlpOpts, Config#{config => HConfig});
+ {error,Faulty} ->
+ {error,{invalid_config,Module,Faulty}}
+ end;
+ Error ->
+ Error
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Handler being removed
+removing_handler(#{id:=Name, module:=Module, config:=#{olp:=Olp}}) ->
+ case whereis(?name_to_reg_name(Module,Name)) of
+ undefined ->
+ ok;
+ _Pid ->
+ %% We don't want to do supervisor:terminate_child here
+ %% since we need to distinguish this explicit stop from a
+ %% system termination in order to avoid circular attempts
+ %% at removing the handler (implying deadlocks and
+ %% timeouts).
+ %% And we don't need to do supervisor:delete_child, since
+ %% the restart type is temporary, which means that the
+ %% child specification is automatically removed from the
+ %% supervisor when the process dies.
+ _ = logger_olp:stop(Olp),
+ ok
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Updating handler config
+changing_config(SetOrUpdate,
+ #{id:=Name,config:=OldHConfig,module:=Module},
+ NewConfig0) ->
+ NewHConfig0 = maps:get(config, NewConfig0, #{}),
+ NoHandlerKeys = ?OLP_KEYS++?COMMON_KEYS++?READ_ONLY_KEYS,
+ OldHandlerConfig = maps:without(NoHandlerKeys,OldHConfig),
+ NewHandlerConfig0 = maps:without(NoHandlerKeys,NewHConfig0),
+ case Module:check_config(Name, SetOrUpdate,
+ OldHandlerConfig,NewHandlerConfig0) of
+ {ok, NewHandlerConfig} ->
+ ModifiedCommon = maps:with(?COMMON_KEYS,NewHandlerConfig),
+ NewCommonConfig0 = maps:with(?COMMON_KEYS,NewHConfig0),
+ OldCommonConfig = maps:with(?COMMON_KEYS,OldHConfig),
+ CommonDefault =
+ case SetOrUpdate of
+ set ->
+ get_default_config();
+ update ->
+ OldCommonConfig
+ end,
+ NewCommonConfig = maps:merge(
+ maps:merge(CommonDefault,NewCommonConfig0),
+ ModifiedCommon),
+ case check_config(NewCommonConfig) of
+ ok ->
+ OlpDefault =
+ case SetOrUpdate of
+ set ->
+ logger_olp:get_default_opts();
+ update ->
+ maps:with(?OLP_KEYS,OldHConfig)
+ end,
+ Olp = maps:get(olp,OldHConfig),
+ NewOlpOpts = maps:merge(OlpDefault,
+ maps:with(?OLP_KEYS,NewHConfig0)),
+ case logger_olp:set_opts(Olp,NewOlpOpts) of
+ ok ->
+ maybe_set_repeated_filesync(Olp,OldCommonConfig,
+ NewCommonConfig),
+ ReadOnly = maps:with(?READ_ONLY_KEYS,OldHConfig),
+ NewHConfig =
+ maps:merge(
+ maps:merge(
+ maps:merge(NewCommonConfig,NewHandlerConfig),
+ ReadOnly),
+ NewOlpOpts),
+ NewConfig = NewConfig0#{config=>NewHConfig},
+ {ok,NewConfig};
+ Error ->
+ Error
+ end;
+ {error,Faulty} ->
+ {error,{invalid_config,Module,Faulty}}
+ end;
+ Error ->
+ Error
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Log a string or report
+-spec log(LogEvent, Config) -> ok when
+ LogEvent :: logger:log_event(),
+ Config :: logger:handler_config().
+
+log(LogEvent, Config = #{config := #{olp:=Olp}}) ->
+ %% if the handler has crashed, we must drop this event
+ %% and hope the handler restarts so we can try again
+ true = is_process_alive(logger_olp:get_pid(Olp)),
+ Bin = log_to_binary(LogEvent, Config),
+ logger_olp:load(Olp,Bin).
+
+%%%-----------------------------------------------------------------
+%%% Remove internal fields from configuration
+filter_config(#{config:=HConfig}=Config) ->
+ Config#{config=>maps:without(?READ_ONLY_KEYS,HConfig)}.
+
+%%%-----------------------------------------------------------------
+%%% Start the handler process
+%%%
+%%% The process must always exist if the handler is registered with
+%%% logger (and must not exist if the handler is not registered).
+%%%
+%%% The handler process is linked to logger_sup, which is part of the
+%%% kernel application's supervision tree.
+start(OlpOpts0, #{id := Name, module:=Module, config:=HConfig} = Config0) ->
+ RegName = ?name_to_reg_name(Module,Name),
+ ChildSpec =
+ #{id => Name,
+ start => {logger_olp, start_link, [RegName,?MODULE,
+ Config0, OlpOpts0]},
+ restart => temporary,
+ shutdown => 2000,
+ type => worker,
+ modules => [?MODULE]},
+ case supervisor:start_child(logger_sup, ChildSpec) of
+ {ok,Pid,Olp} ->
+ ok = logger_handler_watcher:register_handler(Name,Pid),
+ OlpOpts = logger_olp:get_opts(Olp),
+ {ok,Config0#{config=>(maps:merge(HConfig,OlpOpts))#{olp=>Olp}}};
+ {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child ->
+ {error,Reason};
+ Error ->
+ Error
+ end.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init(#{id := Name, module := Module, config := HConfig}) ->
+ process_flag(trap_exit, true),
+
+ ?init_test_hooks(),
+
+ case Module:init(Name, HConfig) of
+ {ok,HState} ->
+ %% Storing common config in state to avoid copying
+ %% (sending) the config data for each log message
+ CommonConfig = maps:with(?COMMON_KEYS,HConfig),
+ State = CommonConfig#{id => Name,
+ module => Module,
+ ctrl_sync_count =>
+ ?CONTROLLER_SYNC_INTERVAL,
+ last_op => sync,
+ handler_state => HState},
+ State1 = set_repeated_filesync(State),
+ {ok,State1};
+ Error ->
+ Error
+ end.
+
+%% This is the log event.
+handle_load(Bin, #{id:=Name,
+ module:=Module,
+ handler_state:=HandlerState,
+ ctrl_sync_count := CtrlSync}=State) ->
+ if CtrlSync==0 ->
+ {_,HS1} = Module:write(Name, sync, Bin, HandlerState),
+ State#{handler_state => HS1,
+ ctrl_sync_count => ?CONTROLLER_SYNC_INTERVAL,
+ last_op=>write};
+ true ->
+ {_,HS1} = Module:write(Name, async, Bin, HandlerState),
+ State#{handler_state => HS1,
+ ctrl_sync_count => CtrlSync-1,
+ last_op=>write}
+ end.
+
+handle_call(filesync, _From, State = #{id := Name,
+ module := Module,
+ handler_state := HandlerState}) ->
+ {Result,HandlerState1} = Module:filesync(Name,sync,HandlerState),
+ {reply, Result, State#{handler_state=>HandlerState1, last_op=>sync}}.
+
+%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this
+%% clause gets called repeatedly by the handler. In order to
+%% guarantee that a filesync *always* happens after the last log
+%% event, the repeat operation must be active!
+handle_cast(repeated_filesync,State = #{filesync_repeat_interval := no_repeat}) ->
+ %% This clause handles a race condition which may occur when
+ %% config changes filesync_repeat_interval from an integer value
+ %% to no_repeat.
+ {noreply,State};
+handle_cast(repeated_filesync,
+ State = #{id := Name,
+ module := Module,
+ handler_state := HandlerState,
+ last_op := LastOp}) ->
+ State1 =
+ if LastOp == sync ->
+ State;
+ true ->
+ {_,HS} = Module:filesync(Name, async, HandlerState),
+ State#{handler_state => HS, last_op => sync}
+ end,
+ {noreply,set_repeated_filesync(State1)};
+
+handle_cast({set_repeated_filesync,FSyncInt},State) ->
+ State1 = State#{filesync_repeat_interval=>FSyncInt},
+ State2 = set_repeated_filesync(cancel_repeated_filesync(State1)),
+ {noreply, State2}.
+
+handle_info(Info, #{id := Name, module := Module,
+ handler_state := HandlerState} = State) ->
+ {noreply,State#{handler_state => Module:handle_info(Name,Info,HandlerState)}}.
+
+terminate(overloaded=Reason, #{id:=Name}=State) ->
+ _ = log_handler_info(Name,"Handler ~p overloaded and stopping",[Name],State),
+ do_terminate(Reason,State),
+ ConfigResult = logger:get_handler_config(Name),
+ case ConfigResult of
+ {ok,#{module:=Module}=HConfig0} ->
+ spawn(fun() -> logger:remove_handler(Name) end),
+ HConfig = try Module:filter_config(HConfig0)
+ catch _:_ -> HConfig0
+ end,
+ {ok,fun() -> logger:add_handler(Name,Module,HConfig) end};
+ Error ->
+ error_notify({Name,restart_impossible,Error}),
+ Error
+ end;
+terminate(Reason, State) ->
+ do_terminate(Reason, State).
+
+do_terminate(Reason, State = #{id := Name,
+ module := Module,
+ handler_state := HandlerState}) ->
+ _ = cancel_repeated_filesync(State),
+ _ = Module:terminate(Name, Reason, HandlerState),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+reset_state(#{id:=Name, module:=Module, handler_state:=HandlerState} = State) ->
+ State#{handler_state=>Module:reset_state(Name, HandlerState)}.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+call(Module, Name, Op) when is_atom(Name) ->
+ case logger_olp:call(?name_to_reg_name(Module,Name), Op) of
+ {error,busy} -> {error,handler_busy};
+ Other -> Other
+ end;
+call(_, Name, Op) ->
+ {error,{badarg,{Op,[Name]}}}.
+
+notify({mode_change,Mode0,Mode1},#{id:=Name}=State) ->
+ log_handler_info(Name,"Handler ~p switched from ~p to ~p mode",
+ [Name,Mode0,Mode1], State);
+notify({flushed,Flushed},#{id:=Name}=State) ->
+ log_handler_info(Name, "Handler ~p flushed ~w log events",
+ [Name,Flushed], State);
+notify(restart,#{id:=Name}=State) ->
+ log_handler_info(Name, "Handler ~p restarted", [Name], State);
+notify(idle,#{id:=Name,module:=Module,handler_state:=HandlerState}=State) ->
+ {_,HS} = Module:filesync(Name,async,HandlerState),
+ State#{handler_state=>HS, last_op=>sync}.
+
+log_handler_info(Name, Format, Args, #{module:=Module,
+ handler_state:=HandlerState}=State) ->
+ Config =
+ case logger:get_handler_config(Name) of
+ {ok,Conf} -> Conf;
+ _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}}
+ end,
+ Meta = #{time=>erlang:system_time(microsecond)},
+ Bin = log_to_binary(#{level => notice,
+ msg => {Format,Args},
+ meta => Meta}, Config),
+ {_,HS} = Module:write(Name, async, Bin, HandlerState),
+ State#{handler_state=>HS, last_op=>write}.
%%%-----------------------------------------------------------------
%%% Convert log data on any form to binary
@@ -94,246 +413,44 @@ string_to_binary(String) ->
throw(Error)
end.
-
%%%-----------------------------------------------------------------
%%% Check that the configuration term is valid
-check_common_config({mode_tab,_Tid}) ->
- valid;
-check_common_config({handler_pid,Pid}) when is_pid(Pid) ->
- valid;
-
-check_common_config({sync_mode_qlen,N}) when is_integer(N) ->
- valid;
-check_common_config({drop_mode_qlen,N}) when is_integer(N) ->
- valid;
-check_common_config({flush_qlen,N}) when is_integer(N) ->
- valid;
-
-check_common_config({burst_limit_enable,Bool}) when Bool == true;
- Bool == false ->
- valid;
-check_common_config({burst_limit_max_count,N}) when is_integer(N) ->
- valid;
-check_common_config({burst_limit_window_time,N}) when is_integer(N) ->
- valid;
-
-check_common_config({overload_kill_enable,Bool}) when Bool == true;
- Bool == false ->
- valid;
-check_common_config({overload_kill_qlen,N}) when is_integer(N) ->
- valid;
-check_common_config({overload_kill_mem_size,N}) when is_integer(N) ->
- valid;
-check_common_config({overload_kill_restart_after,NorA}) when is_integer(NorA);
- NorA == infinity ->
- valid;
-
-check_common_config({filesync_repeat_interval,NorA}) when is_integer(NorA);
- NorA == no_repeat ->
- valid;
-check_common_config(_) ->
- invalid.
-
+check_config(Config) when is_map(Config) ->
+ check_common_config(maps:to_list(Config)).
-%%%-----------------------------------------------------------------
-%%% Overload Protection
-call_cast_or_drop(_Name, HandlerPid, ModeTab, Bin) ->
- %% If the handler process is getting overloaded, the log event
- %% will be synchronous instead of asynchronous (slows down the
- %% logging tempo of a process doing lots of logging. If the
- %% handler is choked, drop mode is set and no event will be sent.
- try ?get_mode(ModeTab) of
- async ->
- gen_server:cast(HandlerPid, {log,Bin});
- sync ->
- try gen_server:call(HandlerPid, {log,Bin}, ?DEFAULT_CALL_TIMEOUT) of
- %% if return value from call == dropped, the
- %% message has been flushed by handler and should
- %% therefore not be counted as dropped in stats
- ok -> ok;
- dropped -> ok
- catch
- _:{timeout,_} ->
- ?observe(_Name,{dropped,1})
- end;
- drop ->
- ?observe(_Name,{dropped,1})
- catch
- %% if the ETS table doesn't exist (maybe because of a
- %% handler restart), we can only drop the event
- _:_ -> ?observe(_Name,{dropped,1})
- end,
+check_common_config([{filesync_repeat_interval,NorA}|Config])
+ when is_integer(NorA); NorA == no_repeat ->
+ check_common_config(Config);
+check_common_config([{Key,Value}|_]) ->
+ {error,#{Key=>Value}};
+check_common_config([]) ->
ok.
-handler_exit(_Name, Reason) ->
- exit(Reason).
+get_default_config() ->
+ #{filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}.
-set_restart_flag(Name, Module) ->
- Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
- spawn(fun() ->
- register(Flag, self()),
- timer:sleep(infinity)
- end),
- ok.
+set_repeated_filesync(#{filesync_repeat_interval:=FSyncInt} = State)
+ when is_integer(FSyncInt) ->
+ {ok,TRef} = timer:apply_after(FSyncInt, gen_server, cast,
+ [self(),repeated_filesync]),
+ State#{rep_sync_tref=>TRef};
+set_repeated_filesync(State) ->
+ State.
-unset_restart_flag(Name, Module) ->
- Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
- case whereis(Flag) of
- undefined ->
- false;
- Pid ->
- exit(Pid, kill),
- true
+cancel_repeated_filesync(State) ->
+ case maps:take(rep_sync_tref,State) of
+ {TRef,State1} ->
+ _ = timer:cancel(TRef),
+ State1;
+ error ->
+ State
end.
-
-check_load(State = #{id:=_Name, mode_tab := ModeTab, mode := Mode,
- sync_mode_qlen := SyncModeQLen,
- drop_mode_qlen := DropModeQLen,
- flush_qlen := FlushQLen}) ->
- {_,Mem} = process_info(self(), memory),
- ?observe(_Name,{max_mem,Mem}),
- {_,QLen} = process_info(self(), message_queue_len),
- ?observe(_Name,{max_qlen,QLen}),
- %% When the handler process gets scheduled in, it's impossible
- %% to predict the QLen. We could jump "up" arbitrarily from say
- %% async to sync, async to drop, sync to flush, etc. However, when
- %% the handler process manages the log events (without flushing),
- %% one after the other, we will move "down" from drop to sync and
- %% from sync to async. This way we don't risk getting stuck in
- %% drop or sync mode with an empty mailbox.
- {Mode1,_NewDrops,_NewFlushes} =
- if
- QLen >= FlushQLen ->
- {flush, 0,1};
- QLen >= DropModeQLen ->
- %% Note that drop mode will force log events to
- %% be dropped on the client side (never sent get to
- %% the handler).
- IncDrops = if Mode == drop -> 0; true -> 1 end,
- {?change_mode(ModeTab, Mode, drop), IncDrops,0};
- QLen >= SyncModeQLen ->
- {?change_mode(ModeTab, Mode, sync), 0,0};
- true ->
- {?change_mode(ModeTab, Mode, async), 0,0}
- end,
- State1 = ?update_other(drops,DROPS,_NewDrops,State),
- {Mode1, QLen, Mem,
- ?update_other(flushes,FLUSHES,_NewFlushes,
- State1#{last_qlen => QLen})}.
-
-limit_burst(#{burst_limit_enable := false}) ->
- {true,0,0};
-limit_burst(#{burst_win_ts := BurstWinT0,
- burst_msg_count := BurstMsgCount,
- burst_limit_window_time := BurstLimitWinTime,
- burst_limit_max_count := BurstLimitMaxCnt}) ->
- if (BurstMsgCount >= BurstLimitMaxCnt) ->
- %% the limit for allowed messages has been reached
- BurstWinT1 = ?timestamp(),
- case ?diff_time(BurstWinT1,BurstWinT0) of
- BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) ->
- %% we're still within the burst time frame
- {false,BurstWinT0,BurstMsgCount};
- _BurstCheckTime ->
- %% burst time frame passed, reset counters
- {true,BurstWinT1,0}
- end;
- true ->
- %% the limit for allowed messages not yet reached
- {true,BurstWinT0,BurstMsgCount+1}
- end.
-
-kill_if_choked(Name, QLen, Mem, HandlerMod,
- State = #{overload_kill_enable := KillIfOL,
- overload_kill_qlen := OLKillQLen,
- overload_kill_mem_size := OLKillMem}) ->
- if KillIfOL andalso
- ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) ->
- HandlerMod:log_handler_info(Name,
- "Handler ~p overloaded and stopping",
- [Name], State),
- set_restart_flag(Name, HandlerMod),
- handler_exit(Name, {shutdown,{overloaded,Name,QLen,Mem}});
- true ->
- ok
- end.
-
-flush_log_events() ->
- flush_log_events(-1).
-
-flush_log_events(Limit) ->
- process_flag(priority, high),
- Flushed = flush_log_events(0, Limit),
- process_flag(priority, normal),
- Flushed.
-
-flush_log_events(Limit, Limit) ->
- Limit;
-flush_log_events(N, Limit) ->
- %% flush log events but leave other events, such as
- %% filesync, info and change_config, so that these
- %% have a chance to be processed even under heavy load
- receive
- {'$gen_cast',{log,_}} ->
- flush_log_events(N+1, Limit);
- {'$gen_call',{Pid,MRef},{log,_}} ->
- Pid ! {MRef, dropped},
- flush_log_events(N+1, Limit)
- after
- 0 -> N
- end.
-
-cancel_timer(TRef) when is_atom(TRef) -> ok;
-cancel_timer(TRef) -> timer:cancel(TRef).
-
-
-stop_or_restart(Name, {shutdown,Reason={overloaded,_Name,_QLen,_Mem}},
- #{overload_kill_restart_after := RestartAfter}) ->
- %% If we're terminating because of an overload situation (see
- %% logger_h_common:kill_if_choked/4), we need to remove the handler
- %% and set a restart timer. A separate process must perform this
- %% in order to avoid deadlock.
- HandlerPid = self(),
- ConfigResult = logger:get_handler_config(Name),
- RemoveAndRestart =
- fun() ->
- MRef = erlang:monitor(process, HandlerPid),
- receive
- {'DOWN',MRef,_,_,_} ->
- ok
- after 30000 ->
- error_notify(Reason),
- exit(HandlerPid, kill)
- end,
- case ConfigResult of
- {ok,#{module:=HMod}=HConfig0} when is_integer(RestartAfter) ->
- _ = logger:remove_handler(Name),
- HConfig = try HMod:filter_config(HConfig0)
- catch _:_ -> HConfig0
- end,
- _ = timer:apply_after(RestartAfter, logger, add_handler,
- [Name,HMod,HConfig]);
- {ok,_} ->
- _ = logger:remove_handler(Name);
- {error,CfgReason} when is_integer(RestartAfter) ->
- error_notify({Name,restart_impossible,CfgReason});
- {error,_} ->
- ok
- end
- end,
- spawn(RemoveAndRestart),
- ok;
-stop_or_restart(_Name, _Reason, _State) ->
- ok.
-
-overload_levels_ok(HandlerConfig) ->
- SMQL = maps:get(sync_mode_qlen, HandlerConfig, ?SYNC_MODE_QLEN),
- DMQL = maps:get(drop_mode_qlen, HandlerConfig, ?DROP_MODE_QLEN),
- FQL = maps:get(flush_qlen, HandlerConfig, ?FLUSH_QLEN),
- (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL).
-
error_notify(Term) ->
?internal_log(error, Term).
-info_notify(Term) ->
- ?internal_log(info, Term).
+maybe_set_repeated_filesync(_Olp,
+ #{filesync_repeat_interval:=FSyncInt},
+ #{filesync_repeat_interval:=FSyncInt}) ->
+ ok;
+maybe_set_repeated_filesync(Olp,_,#{filesync_repeat_interval:=FSyncInt}) ->
+ logger_olp:cast(Olp,{set_repeated_filesync,FSyncInt}).
diff --git a/lib/kernel/src/logger_h_common.hrl b/lib/kernel/src/logger_h_common.hrl
index e0a7b6e3ca..004a61d9d9 100644
--- a/lib/kernel/src/logger_h_common.hrl
+++ b/lib/kernel/src/logger_h_common.hrl
@@ -1,50 +1,22 @@
-
-%%%-----------------------------------------------------------------
-%%% Overload protection configuration
-
-%%! *** NOTE ***
-%%! It's important that:
-%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN
-%%! and that DROP_MODE_QLEN >= 2.
-%%! Otherwise the handler could end up in drop mode with no new
-%%! log requests to process. This would cause all future requests
-%%! to be dropped (no switch to async mode would ever take place).
-
-%% This specifies the message_queue_len value where the log
-%% requests switch from asynchronous casts to synchronous calls.
--define(SYNC_MODE_QLEN, 10).
-%% Above this message_queue_len, log requests will be dropped,
-%% i.e. no log requests get sent to the handler process.
--define(DROP_MODE_QLEN, 200).
-%% Above this message_queue_len, the handler process will flush
-%% its mailbox and only leave this number of messages in it.
--define(FLUSH_QLEN, 1000).
-
-%% Never flush more than this number of messages in one go,
-%% or the handler will be unresponsive for seconds (keep this
-%% number as large as possible or the mailbox could grow large).
--define(FLUSH_MAX_N, 5000).
-
-%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed
-%% to be written within a BURST_LIMIT_WINDOW_TIME time frame.
--define(BURST_LIMIT_ENABLE, true).
--define(BURST_LIMIT_MAX_COUNT, 500).
--define(BURST_LIMIT_WINDOW_TIME, 1000).
-
-%% This enables/disables the feature to automatically get the
-%% handler terminated if it gets too loaded (and can't keep up).
--define(OVERLOAD_KILL_ENABLE, false).
-%% If the message_queue_len goes above this size even after
-%% flushing has been performed, the handler is terminated.
--define(OVERLOAD_KILL_QLEN, 20000).
-%% If the memory usage exceeds this level
--define(OVERLOAD_KILL_MEM_SIZE, 3000000).
-
-%% This is the default time that the handler will wait before
-%% restarting and accepting new requests. The value 'infinity'
-%% disables restarts.
--define(OVERLOAD_KILL_RESTART_AFTER, 5000).
-%%-define(OVERLOAD_KILL_RESTART_AFTER, infinity).
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2015. 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%
+%%
%% The handler sends asynchronous write requests to the process
%% controlling the i/o device, but every once in this interval
@@ -65,12 +37,6 @@
-define(FILESYNC_REPEAT_INTERVAL, 5000).
%%-define(FILESYNC_REPEAT_INTERVAL, no_repeat).
-%% This is the time after last message received that we think/hope
-%% that the handler has an empty mailbox (no new log request has
-%% come in).
--define(IDLE_DETECT_TIME_MSEC, 100).
--define(IDLE_DETECT_TIME_USEC, 100000).
-
%% Default disk log option values
-define(DISK_LOG_TYPE, wrap).
-define(DISK_LOG_MAX_NO_FILES, 10).
@@ -83,43 +49,6 @@
list_to_atom(lists:concat([MODULE,"_",Name]))).
%%%-----------------------------------------------------------------
-%%% Overload protection macros
-
--define(timestamp(), erlang:monotonic_time(microsecond)).
-
--define(get_mode(Tid),
- case ets:lookup(Tid, mode) of
- [{mode,M}] -> M;
- _ -> async
- end).
-
--define(set_mode(Tid, M),
- begin ets:insert(Tid, {mode,M}), M end).
-
--define(change_mode(Tid, M0, M1),
- if M0 == M1 ->
- M0;
- true ->
- ets:insert(Tid, {mode,M1}),
- M1
- end).
-
--define(min(X1, X2),
- if X2 == undefined -> X1;
- X2 < X1 -> X2;
- true -> X1
- end).
-
--define(max(X1, X2),
- if
- X2 == undefined -> X1;
- X2 > X1 -> X2;
- true -> X1
- end).
-
--define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0).
-
-%%%-----------------------------------------------------------------
%%% The test hook macros make it possible to observe and manipulate
%%% internal handler functionality. When enabled, these macros will
%%% slow down execution and therefore should not be include in code
@@ -137,7 +66,7 @@
ets:insert(?TEST_HOOKS_TAB, {internal_log,{logger,internal_log}}),
ets:insert(?TEST_HOOKS_TAB, {file_write,ok}),
ets:insert(?TEST_HOOKS_TAB, {file_datasync,ok}),
- ets:insert(?TEST_HOOKS_TAB, {disk_log_blog,ok}),
+ ets:insert(?TEST_HOOKS_TAB, {disk_log_write,ok}),
ets:insert(?TEST_HOOKS_TAB, {disk_log_sync,ok})).
-define(set_internal_log(MOD_FUNC),
@@ -150,7 +79,7 @@
ets:insert(?TEST_HOOKS_TAB, {internal_log,{logger,internal_log}}),
ets:insert(?TEST_HOOKS_TAB, {file_write,ok}),
ets:insert(?TEST_HOOKS_TAB, {file_datasync,ok}),
- ets:insert(?TEST_HOOKS_TAB, {disk_log_blog,ok}),
+ ets:insert(?TEST_HOOKS_TAB, {disk_log_write,ok}),
ets:insert(?TEST_HOOKS_TAB, {disk_log_sync,ok})).
-define(internal_log(TYPE, TERM),
@@ -171,11 +100,11 @@
[{_,ERROR}] -> ERROR
catch _:_ -> file:datasync(DEVICE) end).
- -define(disk_log_blog(LOG, DATA),
- try ets:lookup(?TEST_HOOKS_TAB, disk_log_blog) of
- [{_,ok}] -> disk_log:blog(LOG, DATA);
+ -define(disk_log_write(LOG, MODE, DATA),
+ try ets:lookup(?TEST_HOOKS_TAB, disk_log_write) of
+ [{_,ok}] -> disk_log_write(LOG, MODE, DATA);
[{_,ERROR}] -> ERROR
- catch _:_ -> disk_log:blog(LOG, DATA) end).
+ catch _:_ -> disk_log_write(LOG, MODE, DATA) end).
-define(disk_log_sync(LOG),
try ets:lookup(?TEST_HOOKS_TAB, disk_log_sync) of
@@ -183,7 +112,6 @@
[{_,ERROR}] -> ERROR
catch _:_ -> disk_log:sync(LOG) end).
- -define(DEFAULT_CALL_TIMEOUT, 5000).
-else. % DEFAULTS!
-define(TEST_HOOKS_TAB, undefined).
@@ -194,70 +122,6 @@
-define(internal_log(TYPE, TERM), logger:internal_log(TYPE, TERM)).
-define(file_write(DEVICE, DATA), file:write(DEVICE, DATA)).
-define(file_datasync(DEVICE), file:datasync(DEVICE)).
- -define(disk_log_blog(LOG, DATA), disk_log:blog(LOG, DATA)).
+ -define(disk_log_write(LOG, MODE, DATA), disk_log_write(LOG, MODE, DATA)).
-define(disk_log_sync(LOG), disk_log:sync(LOG)).
- -define(DEFAULT_CALL_TIMEOUT, 10000).
--endif.
-
-%%%-----------------------------------------------------------------
-%%% These macros enable statistics counters in the state of the
-%%% handler which is useful for analysing the overload protection
-%%% behaviour. These counters should not be included in code to be
-%%% officially released (as some counters will grow very large
-%%% over time).
-
-%%-define(SAVE_STATS, true).
--ifdef(SAVE_STATS).
- -define(merge_with_stats(STATE),
- STATE#{flushes => 0, flushed => 0, drops => 0,
- casts => 0, calls => 0,
- max_qlen => 0, max_time => 0}).
-
- -define(update_max_qlen(QLEN, STATE),
- begin #{max_qlen := QLEN0} = STATE,
- STATE#{max_qlen => ?max(QLEN0,QLEN)} end).
-
- -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE),
- case CALL_OR_CAST of
- cast ->
- #{casts := CASTS0} = STATE,
- STATE#{casts => CASTS0+INC};
- call ->
- #{calls := CALLS0} = STATE,
- STATE#{calls => CALLS0+INC}
- end).
-
- -define(update_max_time(TIME, STATE),
- begin #{max_time := TIME0} = STATE,
- STATE#{max_time => ?max(TIME0,TIME)} end).
-
- -define(update_other(OTHER, VAR, INCVAL, STATE),
- begin #{OTHER := VAR} = STATE,
- STATE#{OTHER => VAR+INCVAL} end).
-
--else. % DEFAULT!
- -define(merge_with_stats(STATE), STATE).
- -define(update_max_qlen(_QLEN, STATE), STATE).
- -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE).
- -define(update_max_time(_TIME, STATE), STATE).
- -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE).
--endif.
-
-%%%-----------------------------------------------------------------
-%%% These macros enable callbacks that make it possible to analyse
-%%% the overload protection behaviour from outside the handler
-%%% process (including dropped requests on the client side).
-%%% An external callback module (?OBSERVER_MOD) is required which
-%%% is not part of the kernel application. For this reason, these
-%%% callbacks should not be included in code to be officially released.
-
-%%-define(OBSERVER_MOD, logger_test).
--ifdef(OBSERVER_MOD).
- -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)).
- -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)).
-
--else. % DEFAULT!
- -define(start_observation(_NAME), ok).
- -define(observe(_NAME,_EVENT), ok).
-endif.
-%%! <---
diff --git a/lib/kernel/src/logger_internal.hrl b/lib/kernel/src/logger_internal.hrl
index d96a4ac78b..e53922e5d3 100644
--- a/lib/kernel/src/logger_internal.hrl
+++ b/lib/kernel/src/logger_internal.hrl
@@ -19,6 +19,7 @@
%%
-include_lib("kernel/include/logger.hrl").
-define(LOGGER_TABLE,logger).
+-define(PROXY_KEY,'$proxy_config$').
-define(PRIMARY_KEY,'$primary_config$').
-define(HANDLER_KEY,'$handler_config$').
-define(LOGGER_META_KEY,'$logger_metadata$').
@@ -40,12 +41,14 @@
-define(DEFAULT_LOGGER_CALL_TIMEOUT, infinity).
--define(LOG_INTERNAL(Level,Report),
+-define(LOG_INTERNAL(Level,Report),?DO_LOG_INTERNAL(Level,[Report])).
+-define(LOG_INTERNAL(Level,Format,Args),?DO_LOG_INTERNAL(Level,[Format,Args])).
+-define(DO_LOG_INTERNAL(Level,Data),
case logger:allow(Level,?MODULE) of
true ->
%% Spawn this to avoid deadlocks
- _ = spawn(logger,macro_log,[?LOCATION,Level,Report,
- logger:add_default_metadata(#{})]),
+ _ = spawn(logger,macro_log,[?LOCATION,Level|Data]++
+ [logger:add_default_metadata(#{})]),
ok;
false ->
ok
diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl
new file mode 100644
index 0000000000..009280a9c9
--- /dev/null
+++ b/lib/kernel/src/logger_olp.erl
@@ -0,0 +1,626 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017-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%
+%%
+-module(logger_olp).
+-behaviour(gen_server).
+
+-include("logger_olp.hrl").
+-include("logger_internal.hrl").
+
+%% API
+-export([start_link/4, load/2, info/1, reset/1, stop/1, restart/1,
+ set_opts/2, get_opts/1, get_default_opts/0, get_pid/1,
+ call/2, cast/2, get_ref/0, get_ref/1]).
+
+%% gen_server and proc_lib callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(OPT_KEYS,[sync_mode_qlen,
+ drop_mode_qlen,
+ flush_qlen,
+ burst_limit_enable,
+ burst_limit_max_count,
+ burst_limit_window_time,
+ overload_kill_enable,
+ overload_kill_qlen,
+ overload_kill_mem_size,
+ overload_kill_restart_after]).
+
+-export_type([olp_ref/0, options/0]).
+
+-opaque olp_ref() :: {atom(),pid(),ets:tid()}.
+
+-type options() :: logger:olp_config().
+
+%%%-----------------------------------------------------------------
+%%% API
+
+-spec start_link(Name,Module,Args,Options) -> {ok,Pid,Olp} | {error,Reason} when
+ Name :: atom(),
+ Module :: module(),
+ Args :: term(),
+ Options :: options(),
+ Pid :: pid(),
+ Olp :: olp_ref(),
+ Reason :: term().
+start_link(Name,Module,Args,Options0) when is_map(Options0) ->
+ Options = maps:merge(get_default_opts(),Options0),
+ case check_opts(Options) of
+ ok ->
+ proc_lib:start_link(?MODULE,init,[[Name,Module,Args,Options]]);
+ Error ->
+ Error
+ end.
+
+-spec load(Olp, Msg) -> ok when
+ Olp :: olp_ref(),
+ Msg :: term().
+load({_Name,Pid,ModeRef},Msg) ->
+ %% If the process is getting overloaded, the message will be
+ %% synchronous instead of asynchronous (slows down the tempo of a
+ %% process causing much load). If the process is choked, drop mode
+ %% is set and no message is sent.
+ try ?get_mode(ModeRef) of
+ async ->
+ gen_server:cast(Pid, {'$olp_load',Msg});
+ sync ->
+ case call(Pid, {'$olp_load',Msg}) of
+ ok ->
+ ok;
+ _Other ->
+ %% dropped or {error,busy}
+ ?observe(_Name,{dropped,1}),
+ ok
+ end;
+ drop ->
+ ?observe(_Name,{dropped,1})
+ catch
+ %% if the ETS table doesn't exist (maybe because of a
+ %% process restart), we can only drop the event
+ _:_ -> ?observe(_Name,{dropped,1})
+ end,
+ ok.
+
+-spec info(Olp) -> map() | {error, busy} when
+ Olp :: atom() | pid() | olp_ref().
+info(Olp) ->
+ call(Olp, info).
+
+-spec reset(Olp) -> ok | {error, busy} when
+ Olp :: atom() | pid() | olp_ref().
+reset(Olp) ->
+ call(Olp, reset).
+
+-spec stop(Olp) -> ok when
+ Olp :: atom() | pid() | olp_ref().
+stop({_Name,Pid,_ModRef}) ->
+ stop(Pid);
+stop(Pid) ->
+ _ = gen_server:call(Pid, stop),
+ ok.
+
+-spec set_opts(Olp, Opts) -> ok | {error,term()} | {error, busy} when
+ Olp :: atom() | pid() | olp_ref(),
+ Opts :: options().
+set_opts(Olp, Opts) ->
+ call(Olp, {set_opts,Opts}).
+
+-spec get_opts(Olp) -> options() | {error, busy} when
+ Olp :: atom() | pid() | olp_ref().
+get_opts(Olp) ->
+ call(Olp, get_opts).
+
+-spec get_default_opts() -> options().
+get_default_opts() ->
+ #{sync_mode_qlen => ?SYNC_MODE_QLEN,
+ drop_mode_qlen => ?DROP_MODE_QLEN,
+ flush_qlen => ?FLUSH_QLEN,
+ burst_limit_enable => ?BURST_LIMIT_ENABLE,
+ burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT,
+ burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME,
+ overload_kill_enable => ?OVERLOAD_KILL_ENABLE,
+ overload_kill_qlen => ?OVERLOAD_KILL_QLEN,
+ overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE,
+ overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER}.
+
+-spec restart(fun(() -> any())) -> ok.
+restart(Fun) ->
+ Result =
+ try Fun()
+ catch C:R:S ->
+ {error,{restart_failed,Fun,C,R,S}}
+ end,
+ ?LOG_INTERNAL(debug,[{logger_olp,restart},
+ {result,Result}]),
+ ok.
+
+-spec get_ref() -> olp_ref().
+get_ref() ->
+ get(olp_ref).
+
+-spec get_ref(PidOrName) -> olp_ref() | {error, busy} when
+ PidOrName :: pid() | atom().
+get_ref(PidOrName) ->
+ call(PidOrName,get_ref).
+
+-spec get_pid(olp_ref()) -> pid().
+get_pid({_Name,Pid,_ModeRef}) ->
+ Pid.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init([Name,Module,Args,Options]) ->
+ register(Name, self()),
+ process_flag(message_queue_data, off_heap),
+
+ ?start_observation(Name),
+
+ try ets:new(Name, [public]) of
+ ModeRef ->
+ OlpRef = {Name,self(),ModeRef},
+ put(olp_ref,OlpRef),
+ try Module:init(Args) of
+ {ok,CBState} ->
+ ?set_mode(ModeRef, async),
+ T0 = ?timestamp(),
+ proc_lib:init_ack({ok,self(),OlpRef}),
+ %% Storing options in state to avoid copying
+ %% (sending) the option data with each message
+ State0 = ?merge_with_stats(
+ Options#{id => Name,
+ idle=> true,
+ module => Module,
+ mode_ref => ModeRef,
+ mode => async,
+ last_qlen => 0,
+ last_load_ts => T0,
+ burst_win_ts => T0,
+ burst_msg_count => 0,
+ cb_state => CBState}),
+ State = reset_restart_flag(State0),
+ gen_server:enter_loop(?MODULE, [], State);
+ Error ->
+ _ = ets:delete(ModeRef),
+ unregister(Name),
+ proc_lib:init_ack(Error)
+ catch
+ _:Error ->
+ _ = ets:delete(ModeRef),
+ unregister(Name),
+ proc_lib:init_ack(Error)
+ end
+ catch
+ _:Error ->
+ unregister(Name),
+ proc_lib:init_ack(Error)
+ end.
+
+%% This is the synchronous load event.
+handle_call({'$olp_load', Msg}, _From, State) ->
+ {Result,State1} = do_load(Msg, call, State#{idle=>false}),
+ %% Result == ok | dropped
+ reply_return(Result,State1);
+
+handle_call(get_ref,_From,#{id:=Name,mode_ref:=ModeRef}=State) ->
+ reply_return({Name,self(),ModeRef},State);
+
+handle_call({set_opts,Opts0},_From,State) ->
+ Opts = maps:merge(maps:with(?OPT_KEYS,State),Opts0),
+ case check_opts(Opts) of
+ ok ->
+ reply_return(ok, maps:merge(State,Opts));
+ Error ->
+ reply_return(Error, State)
+ end;
+
+handle_call(get_opts,_From,State) ->
+ reply_return(maps:with(?OPT_KEYS,State), State);
+
+handle_call(info, _From, State) ->
+ reply_return(State, State);
+
+handle_call(reset, _From, #{module:=Module,cb_state:=CBState}=State) ->
+ State1 = ?merge_with_stats(State),
+ CBState1 = try_callback_call(Module,reset_state,[CBState],CBState),
+ reply_return(ok, State1#{idle => true,
+ last_qlen => 0,
+ last_load_ts => ?timestamp(),
+ cb_state => CBState1});
+
+handle_call(stop, _From, State) ->
+ {stop, {shutdown,stopped}, ok, State};
+
+handle_call(Msg, From, #{module:=Module,cb_state:=CBState}=State) ->
+ case try_callback_call(Module,handle_call,[Msg, From, CBState]) of
+ {reply,Reply,CBState1} ->
+ reply_return(Reply,State#{cb_state=>CBState1});
+ {noreply,CBState1} ->
+ noreply_return(State#{cb_state=>CBState1});
+ {stop, Reason, Reply, CBState1} ->
+ {stop, Reason, Reply, State#{cb_state=>CBState1}};
+ {stop, Reason, CBState1} ->
+ {stop, Reason, State#{cb_state=>CBState1}}
+ end.
+
+%% This is the asynchronous load event.
+handle_cast({'$olp_load', Msg}, State) ->
+ {_Result,State1} = do_load(Msg, cast, State#{idle=>false}),
+ noreply_return(State1);
+
+handle_cast(Msg, #{module:=Module, cb_state:=CBState} = State) ->
+ case try_callback_call(Module,handle_cast,[Msg, CBState]) of
+ {noreply,CBState1} ->
+ noreply_return(State#{cb_state=>CBState1});
+ {stop, Reason, CBState1} ->
+ {stop, Reason, State#{cb_state=>CBState1}}
+ end.
+
+handle_info(timeout, #{mode_ref:=_ModeRef, mode:=Mode} = State) ->
+ State1 = notify(idle,State),
+ State2 = maybe_notify_mode_change(async,State1),
+ {noreply, State2#{idle => true,
+ mode => ?change_mode(_ModeRef, Mode, async),
+ burst_msg_count => 0}};
+handle_info(Msg, #{module := Module, cb_state := CBState} = State) ->
+ case try_callback_call(Module,handle_info,[Msg, CBState]) of
+ {noreply,CBState1} ->
+ noreply_return(State#{cb_state=>CBState1});
+ {stop, Reason, CBState1} ->
+ {stop, Reason, State#{cb_state=>CBState1}};
+ {load,CBState1} ->
+ {_,State1} = do_load(Msg, cast, State#{idle=>false,
+ cb_state=>CBState1}),
+ noreply_return(State1)
+ end.
+
+terminate({shutdown,{overloaded,_QLen,_Mem}},
+ #{id:=Name, module := Module, cb_state := CBState,
+ overload_kill_restart_after := RestartAfter} = State) ->
+ %% We're terminating because of an overload situation (see
+ %% kill_if_choked/3).
+ unregister(Name), %%!!!! to avoid error printout of callback crashed on stop
+ case try_callback_call(Module,terminate,[overloaded,CBState],ok) of
+ {ok,Fun} when is_function(Fun,0), is_integer(RestartAfter) ->
+ set_restart_flag(State),
+ _ = timer:apply_after(RestartAfter,?MODULE,restart,[Fun]),
+ ok;
+ _ ->
+ ok
+ end;
+terminate(Reason, #{id:=Name, module:=Module, cb_state:=CBState}) ->
+ _ = try_callback_call(Module,terminate,[Reason,CBState],ok),
+ unregister(Name),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+-spec call(Olp, term()) -> term() | {error,busy} when
+ Olp :: atom() | pid() | olp_ref().
+call({_Name, Pid, _ModeRef},Msg) ->
+ call(Pid, Msg);
+call(Server, Msg) ->
+ try
+ gen_server:call(Server, Msg)
+ catch
+ _:{timeout,_} -> {error,busy}
+ end.
+
+-spec cast(olp_ref(),term()) -> ok.
+cast({_Name, Pid, _ModeRef},Msg) ->
+ gen_server:cast(Pid, Msg).
+
+%% check for overload between every event (and set Mode to async,
+%% sync or drop accordingly), but never flush the whole mailbox
+%% before LogWindowSize events have been handled
+do_load(Msg, CallOrCast, State) ->
+ T1 = ?timestamp(),
+ State1 = ?update_time(T1,State),
+
+ %% check if the process is getting overloaded, or if it's
+ %% recovering from overload (the check must be done for each
+ %% event to react quickly to large bursts of events and
+ %% to ensure that the handler can never end up in drop mode
+ %% with an empty mailbox, which would stop operation)
+ {Mode1,QLen,Mem,State2} = check_load(State1),
+
+ %% kill the handler if it can't keep up with the load
+ kill_if_choked(QLen, Mem, State2),
+
+ if Mode1 == flush ->
+ flush(T1, State2);
+ true ->
+ handle_load(Mode1, T1, Msg, CallOrCast, State2)
+ end.
+
+%% this function is called by do_load/3 after an overload check
+%% has been performed, where QLen > FlushQLen
+flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := ModeRef}) ->
+ %% flush load messages in the mailbox (a limited number in order
+ %% to not cause long delays)
+ NewFlushed = flush_load(?FLUSH_MAX_N),
+
+ %% write info in log about flushed messages
+ State1=notify({flushed,NewFlushed},State),
+
+ %% because of the receive loop when flushing messages, the
+ %% handler will be scheduled out often and the mailbox could
+ %% grow very large, so we'd better check the queue again here
+ {_,QLen1} = process_info(self(), message_queue_len),
+ ?observe(_Name,{max_qlen,QLen1}),
+
+ %% Add 1 for the current log event
+ ?observe(_Name,{flushed,NewFlushed+1}),
+
+ State2 = ?update_max_time(?diff_time(T1,_T0),State1),
+ State3 = ?update_max_qlen(QLen1,State2),
+ State4 = maybe_notify_mode_change(async,State3),
+ {dropped,?update_other(flushed,FLUSHED,NewFlushed,
+ State4#{mode => ?change_mode(ModeRef,Mode,async),
+ last_qlen => QLen1,
+ last_load_ts => T1})}.
+
+%% this function is called to actually handle the message
+handle_load(Mode, T1, Msg, _CallOrCast,
+ State = #{id := _Name,
+ module := Module,
+ cb_state := CBState,
+ last_qlen := LastQLen,
+ last_load_ts := _T0}) ->
+ %% check if we need to limit the number of writes
+ %% during a burst of log events
+ {DoWrite,State1} = limit_burst(State),
+
+ {Result,LastQLen1,CBState1} =
+ if DoWrite ->
+ ?observe(_Name,{_CallOrCast,1}),
+ CBS = try_callback_call(Module,handle_load,[Msg,CBState]),
+ {ok,element(2, process_info(self(), message_queue_len)),CBS};
+ true ->
+ ?observe(_Name,{flushed,1}),
+ {dropped,LastQLen,CBState}
+ end,
+ State2 = State1#{cb_state=>CBState1},
+
+ State3 = State2#{mode => Mode},
+ State4 = ?update_calls_or_casts(_CallOrCast,1,State3),
+ State5 = ?update_max_qlen(LastQLen1,State4),
+ State6 =
+ ?update_max_time(?diff_time(T1,_T0),
+ State5#{last_qlen := LastQLen1,
+ last_load_ts => T1}),
+ State7 = case Result of
+ ok ->
+ S = ?update_freq(T1,State6),
+ ?update_other(writes,WRITES,1,S);
+ _ ->
+ State6
+ end,
+ {Result,State7}.
+
+
+%%%-----------------------------------------------------------------
+%%% Check that the options are valid
+check_opts(Options) when is_map(Options) ->
+ case do_check_opts(maps:to_list(Options)) of
+ ok ->
+ case overload_levels_ok(Options) of
+ true ->
+ ok;
+ false ->
+ Faulty = maps:with([sync_mode_qlen,
+ drop_mode_qlen,
+ flush_qlen],Options),
+ {error,{invalid_olp_levels,Faulty}}
+ end;
+ {error,Key,Value} ->
+ {error,{invalid_olp_config,#{Key=>Value}}}
+ end.
+
+do_check_opts([{sync_mode_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{drop_mode_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{flush_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{burst_limit_enable,Bool}|Options]) when is_boolean(Bool) ->
+ do_check_opts(Options);
+do_check_opts([{burst_limit_max_count,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{burst_limit_window_time,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_enable,Bool}|Options]) when is_boolean(Bool) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_qlen,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_mem_size,N}|Options]) when is_integer(N) ->
+ do_check_opts(Options);
+do_check_opts([{overload_kill_restart_after,NorA}|Options])
+ when is_integer(NorA); NorA == infinity ->
+ do_check_opts(Options);
+do_check_opts([{Key,Value}|_]) ->
+ {error,Key,Value};
+do_check_opts([]) ->
+ ok.
+
+set_restart_flag(#{id := Name, module := Module}) ->
+ Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
+ spawn(fun() ->
+ register(Flag, self()),
+ timer:sleep(infinity)
+ end),
+ ok.
+
+reset_restart_flag(#{id := Name, module := Module} = State) ->
+ Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])),
+ case whereis(Flag) of
+ undefined ->
+ State;
+ Pid ->
+ exit(Pid, kill),
+ notify(restart,State)
+ end.
+
+check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode,
+ sync_mode_qlen := SyncModeQLen,
+ drop_mode_qlen := DropModeQLen,
+ flush_qlen := FlushQLen}) ->
+ {_,Mem} = process_info(self(), memory),
+ ?observe(_Name,{max_mem,Mem}),
+ {_,QLen} = process_info(self(), message_queue_len),
+ ?observe(_Name,{max_qlen,QLen}),
+ %% When the handler process gets scheduled in, it's impossible
+ %% to predict the QLen. We could jump "up" arbitrarily from say
+ %% async to sync, async to drop, sync to flush, etc. However, when
+ %% the handler process manages the log events (without flushing),
+ %% one after the other, we will move "down" from drop to sync and
+ %% from sync to async. This way we don't risk getting stuck in
+ %% drop or sync mode with an empty mailbox.
+ {Mode1,_NewDrops,_NewFlushes} =
+ if
+ QLen >= FlushQLen ->
+ {flush, 0,1};
+ QLen >= DropModeQLen ->
+ %% Note that drop mode will force load messages to
+ %% be dropped on the client side (never sent to
+ %% the olp process).
+ IncDrops = if Mode == drop -> 0; true -> 1 end,
+ {?change_mode(ModeRef, Mode, drop), IncDrops,0};
+ QLen >= SyncModeQLen ->
+ {?change_mode(ModeRef, Mode, sync), 0,0};
+ true ->
+ {?change_mode(ModeRef, Mode, async), 0,0}
+ end,
+ State1 = ?update_other(drops,DROPS,_NewDrops,State),
+ State2 = ?update_max_qlen(QLen,State1),
+ State3 = maybe_notify_mode_change(Mode1,State2),
+ {Mode1, QLen, Mem,
+ ?update_other(flushes,FLUSHES,_NewFlushes,
+ State3#{last_qlen => QLen})}.
+
+limit_burst(#{burst_limit_enable := false}=State) ->
+ {true,State};
+limit_burst(#{burst_win_ts := BurstWinT0,
+ burst_msg_count := BurstMsgCount,
+ burst_limit_window_time := BurstLimitWinTime,
+ burst_limit_max_count := BurstLimitMaxCnt} = State) ->
+ if (BurstMsgCount >= BurstLimitMaxCnt) ->
+ %% the limit for allowed messages has been reached
+ BurstWinT1 = ?timestamp(),
+ case ?diff_time(BurstWinT1,BurstWinT0) of
+ BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) ->
+ %% we're still within the burst time frame
+ {false,?update_other(burst_drops,BURSTS,1,State)};
+ _BurstCheckTime ->
+ %% burst time frame passed, reset counters
+ {true,State#{burst_win_ts => BurstWinT1,
+ burst_msg_count => 0}}
+ end;
+ true ->
+ %% the limit for allowed messages not yet reached
+ {true,State#{burst_win_ts => BurstWinT0,
+ burst_msg_count => BurstMsgCount+1}}
+ end.
+
+kill_if_choked(QLen, Mem, #{overload_kill_enable := KillIfOL,
+ overload_kill_qlen := OLKillQLen,
+ overload_kill_mem_size := OLKillMem}) ->
+ if KillIfOL andalso
+ ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) ->
+ exit({shutdown,{overloaded,QLen,Mem}});
+ true ->
+ ok
+ end.
+
+flush_load(Limit) ->
+ process_flag(priority, high),
+ Flushed = flush_load(0, Limit),
+ process_flag(priority, normal),
+ Flushed.
+
+flush_load(Limit, Limit) ->
+ Limit;
+flush_load(N, Limit) ->
+ %% flush log events but leave other events, such as info, reset
+ %% and stop, so that these have a chance to be processed even
+ %% under heavy load
+ receive
+ {'$gen_cast',{'$olp_load',_}} ->
+ flush_load(N+1, Limit);
+ {'$gen_call',{Pid,MRef},{'$olp_load',_}} ->
+ Pid ! {MRef, dropped},
+ flush_load(N+1, Limit);
+ {log,_,_,_,_} ->
+ flush_load(N+1, Limit);
+ {log,_,_,_} ->
+ flush_load(N+1, Limit)
+ after
+ 0 -> N
+ end.
+
+overload_levels_ok(Options) ->
+ SMQL = maps:get(sync_mode_qlen, Options, ?SYNC_MODE_QLEN),
+ DMQL = maps:get(drop_mode_qlen, Options, ?DROP_MODE_QLEN),
+ FQL = maps:get(flush_qlen, Options, ?FLUSH_QLEN),
+ (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL).
+
+maybe_notify_mode_change(drop,#{mode:=Mode0}=State)
+ when Mode0=/=drop ->
+ notify({mode_change,Mode0,drop},State);
+maybe_notify_mode_change(Mode1,#{mode:=drop}=State)
+ when Mode1==async; Mode1==sync ->
+ notify({mode_change,drop,Mode1},State);
+maybe_notify_mode_change(_,State) ->
+ State.
+
+notify(Note,#{module:=Module,cb_state:=CBState}=State) ->
+ CBState1 = try_callback_call(Module,notify,[Note,CBState],CBState),
+ State#{cb_state=>CBState1}.
+
+try_callback_call(Module, Function, Args) ->
+ try_callback_call(Module, Function, Args, '$no_default_return').
+
+try_callback_call(Module, Function, Args, DefRet) ->
+ try apply(Module, Function, Args)
+ catch
+ throw:R -> R;
+ error:undef:S when DefRet=/='$no_default_return' ->
+ case S of
+ [{Module,Function,Args,_}|_] ->
+ DefRet;
+ _ ->
+ erlang:raise(error,undef,S)
+ end
+ end.
+
+noreply_return(#{idle:=true}=State) ->
+ {noreply,State};
+noreply_return(#{idle:=false}=State) ->
+ {noreply,State,?IDLE_DETECT_TIME}.
+
+reply_return(Reply,#{idle:=true}=State) ->
+ {reply,Reply,State};
+reply_return(Reply,#{idle:=false}=State) ->
+ {reply,Reply,State,?IDLE_DETECT_TIME}.
diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl
new file mode 100644
index 0000000000..9b4f5ebf27
--- /dev/null
+++ b/lib/kernel/src/logger_olp.hrl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-2015. 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%
+%%
+
+%%%-----------------------------------------------------------------
+%%% Overload protection configuration
+
+%%! *** NOTE ***
+%%! It's important that:
+%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN
+%%! and that DROP_MODE_QLEN >= 2.
+%%! Otherwise the process could end up in drop mode with no new
+%%! log requests to process. This would cause all future requests
+%%! to be dropped (no switch to async mode would ever take place).
+
+%% This specifies the message_queue_len value where the log
+%% requests switch from asynchronous casts to synchronous calls.
+-define(SYNC_MODE_QLEN, 10).
+%% Above this message_queue_len, log requests will be dropped,
+%% i.e. no log requests get sent to the process.
+-define(DROP_MODE_QLEN, 200).
+%% Above this message_queue_len, the process will flush its mailbox
+%% and only leave this number of messages in it.
+-define(FLUSH_QLEN, 1000).
+
+%% Never flush more than this number of messages in one go, or the
+%% process will be unresponsive for seconds (keep this number as large
+%% as possible or the mailbox could grow large).
+-define(FLUSH_MAX_N, 5000).
+
+%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed
+%% to be written within a BURST_LIMIT_WINDOW_TIME time frame.
+-define(BURST_LIMIT_ENABLE, true).
+-define(BURST_LIMIT_MAX_COUNT, 500).
+-define(BURST_LIMIT_WINDOW_TIME, 1000).
+
+%% This enables/disables the feature to automatically terminate the
+%% process if it gets too loaded (and can't keep up).
+-define(OVERLOAD_KILL_ENABLE, false).
+%% If the message_queue_len goes above this size even after
+%% flushing has been performed, the process is terminated.
+-define(OVERLOAD_KILL_QLEN, 20000).
+%% If the memory usage exceeds this level, the process is terminated.
+-define(OVERLOAD_KILL_MEM_SIZE, 3000000).
+
+%% This is the default time to wait before restarting and accepting
+%% new requests. The value 'infinity' disables restarts.
+-define(OVERLOAD_KILL_RESTART_AFTER, 5000).
+
+%% This is the time in milliseconds after last load message received
+%% that we notify the callback about being idle.
+-define(IDLE_DETECT_TIME, 100).
+
+%%%-----------------------------------------------------------------
+%%% Overload protection macros
+
+-define(timestamp(), erlang:monotonic_time(microsecond)).
+
+-define(get_mode(Tid),
+ case ets:lookup(Tid, mode) of
+ [{mode,M}] -> M;
+ _ -> async
+ end).
+
+-define(set_mode(Tid, M),
+ begin ets:insert(Tid, {mode,M}), M end).
+
+-define(change_mode(Tid, M0, M1),
+ if M0 == M1 ->
+ M0;
+ true ->
+ ets:insert(Tid, {mode,M1}),
+ M1
+ end).
+
+-define(max(X1, X2),
+ if
+ X2 == undefined -> X1;
+ X2 > X1 -> X2;
+ true -> X1
+ end).
+
+-define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0).
+
+%%%-----------------------------------------------------------------
+%%% These macros enable statistics counters in the state of the
+%%% process, which is useful for analysing the overload protection
+%%% behaviour. These counters should not be included in code to be
+%%% officially released (as some counters will grow very large over
+%%% time).
+
+%% -define(SAVE_STATS, true).
+-ifdef(SAVE_STATS).
+ -define(merge_with_stats(STATE),
+ begin
+ TIME = ?timestamp(),
+ STATE#{start => TIME, time => {TIME,0},
+ flushes => 0, flushed => 0, drops => 0,
+ burst_drops => 0, casts => 0, calls => 0,
+ writes => 0, max_qlen => 0, max_time => 0,
+ freq => {TIME,0,0}} end).
+
+ -define(update_max_qlen(QLEN, STATE),
+ begin #{max_qlen := QLEN0} = STATE,
+ STATE#{max_qlen => ?max(QLEN0,QLEN)} end).
+
+ -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE),
+ case CALL_OR_CAST of
+ cast ->
+ #{casts := CASTS0} = STATE,
+ STATE#{casts => CASTS0+INC};
+ call ->
+ #{calls := CALLS0} = STATE,
+ STATE#{calls => CALLS0+INC}
+ end).
+
+ -define(update_max_time(TIME, STATE),
+ begin #{max_time := TIME0} = STATE,
+ STATE#{max_time => ?max(TIME0,TIME)} end).
+
+ -define(update_other(OTHER, VAR, INCVAL, STATE),
+ begin #{OTHER := VAR} = STATE,
+ STATE#{OTHER => VAR+INCVAL} end).
+
+ -define(update_freq(TIME,STATE),
+ begin
+ case STATE of
+ #{freq := {START, 49, _}} ->
+ STATE#{freq => {TIME, 0, trunc(1000000*50/(?diff_time(TIME,START)))}};
+ #{freq := {START, N, FREQ}} ->
+ STATE#{freq => {START, N+1, FREQ}}
+ end end).
+
+ -define(update_time(TIME,STATE),
+ begin #{start := START} = STATE,
+ STATE#{time => {TIME,trunc((?diff_time(TIME,START))/1000000)}} end).
+
+-else. % DEFAULT!
+ -define(merge_with_stats(STATE), STATE).
+ -define(update_max_qlen(_QLEN, STATE), STATE).
+ -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE).
+ -define(update_max_time(_TIME, STATE), STATE).
+ -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE).
+ -define(update_freq(_TIME, STATE), STATE).
+ -define(update_time(_TIME, STATE), STATE).
+-endif.
+
+%%%-----------------------------------------------------------------
+%%% These macros enable callbacks that make it possible to analyse the
+%%% overload protection behaviour from outside the process (including
+%%% dropped requests on the client side). An external callback module
+%%% (?OBSERVER_MOD) is required which is not part of the kernel
+%%% application. For this reason, these callbacks should not be
+%%% included in code to be officially released.
+
+%%-define(OBSERVER_MOD, logger_test).
+-ifdef(OBSERVER_MOD).
+ -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)).
+ -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)).
+
+-else. % DEFAULT!
+ -define(start_observation(_NAME), ok).
+ -define(observe(_NAME,_EVENT), ok).
+-endif.
diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl
new file mode 100644
index 0000000000..24b293805c
--- /dev/null
+++ b/lib/kernel/src/logger_proxy.erl
@@ -0,0 +1,165 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017-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%
+%%
+-module(logger_proxy).
+
+%% API
+-export([start_link/0, restart/0, log/1, child_spec/0, get_default_config/0]).
+
+%% logger_olp callbacks
+-export([init/1, handle_load/2, handle_info/2, terminate/2,
+ notify/2]).
+
+-include("logger_internal.hrl").
+
+-define(SERVER,?MODULE).
+
+%%%-----------------------------------------------------------------
+%%% API
+-spec log(RemoteLog) -> ok when
+ RemoteLog :: {remote,node(),LogEvent},
+ LogEvent :: {log,Level,Format,Args,Meta} |
+ {log,Level,StringOrReport,Meta},
+ Level :: logger:level(),
+ Format :: io:format(),
+ Args :: list(term()),
+ StringOrReport :: unicode:chardata() | logger:report(),
+ Meta :: logger:metadata().
+log(RemoteLog) ->
+ Olp = persistent_term:get(?MODULE),
+ case logger_olp:get_pid(Olp) =:= self() of
+ true ->
+ %% This happens when the log event comes from the
+ %% emulator, and the group leader is on a remote node.
+ _ = handle_load(RemoteLog, no_state),
+ ok;
+ false ->
+ logger_olp:load(Olp, RemoteLog)
+ end.
+
+%% Called by supervisor
+-spec start_link() -> {ok,pid(),logger_olp:olp_ref()} | {error,term()}.
+start_link() ->
+ %% Notice that sync_mode is only used when logging to remote node,
+ %% i.e. when the log/2 API function is called.
+ %%
+ %% When receiving log events from the emulator or from a remote
+ %% node, the log event is sent as a message to this process, and
+ %% thus received directly in handle_info/2. This means that the
+ %% mode (async/sync/drop) is not read before the message is
+ %% sent. Thus sync mode is never entered, and drop mode is
+ %% implemented by setting the system_logger flag to undefined (see
+ %% notify/2)
+ %%
+ %% Burst limit is disabled, since this is only a proxy and we
+ %% don't want to limit bursts twice (here and in the handler).
+ logger_olp:start_link(?SERVER,?MODULE,[],logger:get_proxy_config()).
+
+%% Fun used for restarting this process after it has been killed due
+%% to overload (must set overload_kill_enable=>true in opts)
+restart() ->
+ case supervisor:start_child(logger_sup, child_spec()) of
+ {ok,_Pid,Olp} ->
+ {ok,Olp};
+ {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child ->
+ {error,Reason};
+ Error ->
+ Error
+ end.
+
+%% Called internally and by logger_sup
+child_spec() ->
+ Name = ?SERVER,
+ #{id => Name,
+ start => {?MODULE, start_link, []},
+ restart => temporary,
+ shutdown => 2000,
+ type => worker,
+ modules => [?MODULE]}.
+
+get_default_config() ->
+ OlpDefault = logger_olp:get_default_opts(),
+ OlpDefault#{sync_mode_qlen=>500,
+ drop_mode_qlen=>1000,
+ flush_qlen=>5000,
+ burst_limit_enable=>false}.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+init([]) ->
+ process_flag(trap_exit, true),
+ _ = erlang:system_flag(system_logger,self()),
+ persistent_term:put(?MODULE,logger_olp:get_ref()),
+ {ok,no_state}.
+
+%% Log event to send to the node where the group leader of it's client resides
+handle_load({remote,Node,Log},State) ->
+ %% If the connection is overloaded (send_nosuspend returns false),
+ %% we drop the message.
+ _ = erlang:send_nosuspend({?SERVER,Node},Log),
+ State;
+%% Log event to log on this node
+handle_load({log,Level,Format,Args,Meta},State) ->
+ try_log([Level,Format,Args,Meta]),
+ State;
+handle_load({log,Level,Report,Meta},State) ->
+ try_log([Level,Report,Meta]),
+ State.
+
+%% Log event sent to this process e.g. from the emulator - it is really load
+handle_info(Log,State) when is_tuple(Log), element(1,Log)==log ->
+ {load,State}.
+
+terminate(overloaded, _State) ->
+ _ = erlang:system_flag(system_logger,undefined),
+ {ok,fun ?MODULE:restart/0};
+terminate(_Reason, _State) ->
+ _ = erlang:system_flag(system_logger,whereis(logger)),
+ ok.
+
+notify({mode_change,Mode0,Mode1},State) ->
+ _ = if Mode1=:=drop -> % entering drop mode
+ erlang:system_flag(system_logger,undefined);
+ Mode0=:=drop -> % leaving drop mode
+ erlang:system_flag(system_logger,self());
+ true ->
+ ok
+ end,
+ ?LOG_INTERNAL(notice,"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]),
+ State;
+notify({flushed,Flushed},State) ->
+ ?LOG_INTERNAL(notice, "~w flushed ~w log events",[?MODULE,Flushed]),
+ State;
+notify(restart,State) ->
+ ?LOG_INTERNAL(notice, "~w restarted", [?MODULE]),
+ State;
+notify(_Note,State) ->
+ State.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+try_log(Args) ->
+ try apply(logger,log,Args)
+ catch C:R:S ->
+ ?LOG_INTERNAL(debug,[{?MODULE,log_failed},
+ {log,Args},
+ {reason,{C,R,S}}])
+ end.
diff --git a/lib/kernel/src/logger_server.erl b/lib/kernel/src/logger_server.erl
index b7735dbcf7..722246e82c 100644
--- a/lib/kernel/src/logger_server.erl
+++ b/lib/kernel/src/logger_server.erl
@@ -22,8 +22,7 @@
-behaviour(gen_server).
%% API
--export([start_link/0,
- add_handler/3, remove_handler/1,
+-export([start_link/0, add_handler/3, remove_handler/1,
add_filter/2, remove_filter/2,
set_module_level/2, unset_module_level/0,
unset_module_level/1, cache_module_level/1,
@@ -43,7 +42,7 @@
-define(SERVER, logger).
-define(LOGGER_SERVER_TAG, '$logger_cb_process').
--record(state, {tid, async_req, async_req_queue}).
+-record(state, {tid, async_req, async_req_queue, remote_logger}).
%%%===================================================================
%%% API
@@ -155,6 +154,8 @@ init([]) ->
process_flag(trap_exit, true),
put(?LOGGER_SERVER_TAG,true),
Tid = logger_config:new(?LOGGER_TABLE),
+ %% Store initial proxy config. logger_proxy reads config from here at startup.
+ logger_config:create(Tid,proxy,logger_proxy:get_default_config()),
PrimaryConfig = maps:merge(default_config(primary),
#{handlers=>[simple]}),
logger_config:create(Tid,primary,PrimaryConfig),
@@ -221,6 +222,24 @@ handle_call({add_filter,Id,Filter}, _From,#state{tid=Tid}=State) ->
handle_call({remove_filter,Id,FilterId}, _From, #state{tid=Tid}=State) ->
Reply = do_remove_filter(Tid,Id,FilterId),
{reply,Reply,State};
+handle_call({change_config,SetOrUpd,proxy,Config0},_From,#state{tid=Tid}=State) ->
+ Default =
+ case SetOrUpd of
+ set ->
+ logger_proxy:get_default_config();
+ update ->
+ {ok,OldConfig} = logger_config:get(Tid,proxy),
+ OldConfig
+ end,
+ Config = maps:merge(Default,Config0),
+ Reply =
+ case logger_olp:set_opts(logger_proxy,Config) of
+ ok ->
+ logger_config:set(Tid,proxy,Config);
+ Error ->
+ Error
+ end,
+ {reply,Reply,State};
handle_call({change_config,SetOrUpd,primary,Config0}, _From,
#state{tid=Tid}=State) ->
{ok,#{handlers:=Handlers}=OldConfig} = logger_config:get(Tid,primary),
@@ -357,7 +376,7 @@ terminate(_Reason, _State) ->
%%%===================================================================
%%% Internal functions
%%%===================================================================
-call(Request) ->
+call(Request) when is_tuple(Request) ->
Action = element(1,Request),
case get(?LOGGER_SERVER_TAG) of
true when
@@ -369,6 +388,7 @@ call(Request) ->
gen_server:call(?SERVER,Request,?DEFAULT_LOGGER_CALL_TIMEOUT)
end.
+
do_add_filter(Tid,Id,{FId,_} = Filter) ->
case logger_config:get(Tid,Id) of
{ok,Config} ->
@@ -413,11 +433,13 @@ default_config(Id,Module) ->
sanity_check(Owner,Key,Value) ->
sanity_check_1(Owner,[{Key,Value}]).
-sanity_check(HandlerId,Config) when is_map(Config) ->
- sanity_check_1(HandlerId,maps:to_list(Config));
+sanity_check(Owner,Config) when is_map(Config) ->
+ sanity_check_1(Owner,maps:to_list(Config));
sanity_check(_,Config) ->
{error,{invalid_config,Config}}.
+sanity_check_1(proxy,_Config) ->
+ ok; % Details are checked by logger_olp:set_opts/2
sanity_check_1(Owner,Config) when is_list(Config) ->
try
Type = get_type(Owner),
diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl
index 8b51dd8569..fe181722f3 100644
--- a/lib/kernel/src/logger_simple_h.erl
+++ b/lib/kernel/src/logger_simple_h.erl
@@ -50,7 +50,6 @@ removing_handler(#{id:=simple}) ->
ok;
Pid ->
Ref = erlang:monitor(process,Pid),
- unlink(Pid),
Pid ! stop,
receive {'DOWN',Ref,process,Pid,_} ->
ok
@@ -99,7 +98,11 @@ loop(Buffer) ->
replay_buffer(Buffer);
_ ->
ok
- end;
+ end,
+ %% Before stopping, we unlink the logger process to avoid
+ %% an unexpected EXIT message
+ unlink(whereis(logger)),
+ ok;
{log,#{msg:=_,meta:=#{time:=_}}=Log} ->
do_log(Log),
loop(update_buffer(Buffer,Log));
diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl
index 42e0f5caf4..0669164bb6 100644
--- a/lib/kernel/src/logger_std_h.erl
+++ b/lib/kernel/src/logger_std_h.erl
@@ -19,8 +19,6 @@
%%
-module(logger_std_h).
--behaviour(gen_server).
-
-include("logger.hrl").
-include("logger_internal.hrl").
-include("logger_h_common.hrl").
@@ -28,117 +26,97 @@
-include_lib("kernel/include/file.hrl").
%% API
--export([start_link/3, info/1, filesync/1, reset/1]).
+-export([filesync/1]).
-%% gen_server and proc_lib callbacks
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
+%% logger_h_common callbacks
+-export([init/2, check_config/4, reset_state/2,
+ filesync/3, write/4, handle_info/3, terminate/3]).
%% logger callbacks
-export([log/2, adding_handler/1, removing_handler/1, changing_config/3,
filter_config/1]).
-%% handler internal
--export([log_handler_info/4]).
+-define(DEFAULT_CALL_TIMEOUT, 5000).
%%%===================================================================
%%% API
%%%===================================================================
%%%-----------------------------------------------------------------
-%%% Start a standard handler process and link to caller.
-%%% This function is called by the kernel supervisor when this
-%%% handler process gets added
--spec start_link(Name, Config, HandlerState) -> {ok,Pid} | {error,Reason} when
+%%%
+-spec filesync(Name) -> ok | {error,Reason} when
Name :: atom(),
+ Reason :: handler_busy | {badarg,term()}.
+
+filesync(Name) ->
+ logger_h_common:filesync(?MODULE,Name).
+
+%%%===================================================================
+%%% logger callbacks - just forward to logger_h_common
+%%%===================================================================
+
+%%%-----------------------------------------------------------------
+%%% Handler being added
+-spec adding_handler(Config) -> {ok,Config} | {error,Reason} when
Config :: logger:handler_config(),
- HandlerState :: map(),
- Pid :: pid(),
Reason :: term().
-start_link(Name, Config, HandlerState) ->
- proc_lib:start_link(?MODULE,init,[[Name,Config,HandlerState]]).
+adding_handler(Config) ->
+ logger_h_common:adding_handler(Config).
%%%-----------------------------------------------------------------
-%%%
--spec filesync(Name) -> ok | {error,Reason} when
- Name :: atom(),
- Reason :: handler_busy | {badarg,term()}.
+%%% Updating handler config
+-spec changing_config(SetOrUpdate, OldConfig, NewConfig) ->
+ {ok,Config} | {error,Reason} when
+ SetOrUpdate :: set | update,
+ OldConfig :: logger:handler_config(),
+ NewConfig :: logger:handler_config(),
+ Config :: logger:handler_config(),
+ Reason :: term().
-filesync(Name) when is_atom(Name) ->
- try
- gen_server:call(?name_to_reg_name(?MODULE,Name),
- filesync, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
-filesync(Name) ->
- {error,{badarg,{filesync,[Name]}}}.
+changing_config(SetOrUpdate, OldConfig, NewConfig) ->
+ logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig).
%%%-----------------------------------------------------------------
-%%%
--spec info(Name) -> Info | {error,Reason} when
- Name :: atom(),
- Info :: term(),
- Reason :: handler_busy | {badarg,term()}.
+%%% Handler being removed
+-spec removing_handler(Config) -> ok when
+ Config :: logger:handler_config().
-info(Name) when is_atom(Name) ->
- try
- gen_server:call(?name_to_reg_name(?MODULE,Name),
- info, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
-info(Name) ->
- {error,{badarg,{info,[Name]}}}.
+removing_handler(Config) ->
+ logger_h_common:removing_handler(Config).
%%%-----------------------------------------------------------------
-%%%
--spec reset(Name) -> ok | {error,Reason} when
- Name :: atom(),
- Reason :: handler_busy | {badarg,term()}.
+%%% Log a string or report
+-spec log(LogEvent, Config) -> ok when
+ LogEvent :: logger:log_event(),
+ Config :: logger:handler_config().
-reset(Name) when is_atom(Name) ->
- try
- gen_server:call(?name_to_reg_name(?MODULE,Name),
- reset, ?DEFAULT_CALL_TIMEOUT)
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
-reset(Name) ->
- {error,{badarg,{reset,[Name]}}}.
+log(LogEvent, Config) ->
+ logger_h_common:log(LogEvent, Config).
+
+%%%-----------------------------------------------------------------
+%%% Remove internal fields from configuration
+-spec filter_config(Config) -> Config when
+ Config :: logger:handler_config().
+filter_config(Config) ->
+ logger_h_common:filter_config(Config).
%%%===================================================================
-%%% logger callbacks
+%%% logger_h_common callbacks
%%%===================================================================
-
-%%%-----------------------------------------------------------------
-%%% Handler being added
-adding_handler(#{id:=Name}=Config) ->
- case check_config(adding, Config) of
- {ok, #{config:=HConfig}=Config1} ->
- %% create initial handler state by merging defaults with config
- HState = maps:merge(get_init_state(), HConfig),
- case logger_h_common:overload_levels_ok(HState) of
- true ->
- start(Name, Config1, HState);
- false ->
- #{sync_mode_qlen := SMQL,
- drop_mode_qlen := DMQL,
- flush_qlen := FQL} = HState,
- {error,{invalid_levels,{SMQL,DMQL,FQL}}}
- end;
+init(Name, #{type := Type}) ->
+ case open_log_file(Name, Type) of
+ {ok,FileCtrlPid} ->
+ {ok,#{type=>Type,file_ctrl_pid=>FileCtrlPid}};
Error ->
Error
end.
-%%%-----------------------------------------------------------------
-%%% Updating handler config
-changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) ->
+check_config(_Name,set,undefined,NewHConfig) ->
+ check_config(maps:merge(get_default_config(),NewHConfig));
+check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) ->
WriteOnce = maps:with([type],OldHConfig),
- ReadOnly = maps:with([handler_pid,mode_tab],OldHConfig),
- NewHConfig0 = maps:get(config, NewConfig, #{}),
Default =
case SetOrUpdate of
set ->
@@ -148,48 +126,24 @@ changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) ->
OldHConfig
end,
- %% Allow (accidentially) included read-only fields - just overwrite them
- NewHConfig = maps:merge(maps:merge(Default, NewHConfig0),ReadOnly),
+ NewHConfig = maps:merge(Default, NewHConfig0),
- %% But fail if write-once fields are changed
+ %% Fail if write-once fields are changed
case maps:with([type],NewHConfig) of
WriteOnce ->
- changing_config1(maps:get(handler_pid,OldHConfig),
- OldConfig,
- NewConfig#{config=>NewHConfig});
+ check_config(NewHConfig);
Other ->
- {error,{illegal_config_change,#{config=>WriteOnce},#{config=>Other}}}
+ {error,{illegal_config_change,?MODULE,WriteOnce,Other}}
end.
-changing_config1(HPid, OldConfig, NewConfig) ->
- case check_config(changing, NewConfig) of
- Result = {ok,NewConfig1} ->
- try gen_server:call(HPid, {change_config,OldConfig,NewConfig1},
- ?DEFAULT_CALL_TIMEOUT) of
- ok -> Result;
- HError -> HError
- catch
- _:{timeout,_} -> {error,handler_busy}
- end;
- Error ->
- Error
- end.
-
-check_config(adding, Config) ->
- %% Merge in defaults on handler level
- HConfig0 = maps:get(config, Config, #{}),
- HConfig = maps:merge(get_default_config(),HConfig0),
+check_config(#{type:=Type}=HConfig) ->
case check_h_config(maps:to_list(HConfig)) of
+ ok when is_atom(Type) ->
+ {ok,HConfig#{filesync_repeat_interval=>no_repeat}};
ok ->
- {ok,Config#{config=>HConfig}};
- Error ->
- Error
- end;
-check_config(changing, Config) ->
- HConfig = maps:get(config, Config, #{}),
- case check_h_config(maps:to_list(HConfig)) of
- ok -> {ok,Config};
- Error -> Error
+ {ok,HConfig};
+ {error,{Key,Value}} ->
+ {error,{invalid_config,?MODULE,#{Key=>Value}}}
end.
check_h_config([{type,Type} | Config]) when Type == standard_io;
@@ -200,219 +154,40 @@ check_h_config([{type,{file,File}} | Config]) when is_list(File) ->
check_h_config([{type,{file,File,Modes}} | Config]) when is_list(File),
is_list(Modes) ->
check_h_config(Config);
-check_h_config([Other | Config]) ->
- case logger_h_common:check_common_config(Other) of
- valid ->
- check_h_config(Config);
- invalid ->
- {error,{invalid_config,?MODULE,Other}}
- end;
+check_h_config([Other | _]) ->
+ {error,Other};
check_h_config([]) ->
ok.
-
-%%%-----------------------------------------------------------------
-%%% Handler being removed
-removing_handler(#{id:=Name}) ->
- stop(Name).
-
-%%%-----------------------------------------------------------------
-%%% Log a string or report
--spec log(LogEvent, Config) -> ok when
- LogEvent :: logger:log_event(),
- Config :: logger:handler_config().
-
-log(LogEvent, Config = #{id := Name,
- config := #{handler_pid := HPid,
- mode_tab := ModeTab}}) ->
- %% if the handler has crashed, we must drop this event
- %% and hope the handler restarts so we can try again
- true = is_process_alive(HPid),
- Bin = logger_h_common:log_to_binary(LogEvent, Config),
- logger_h_common:call_cast_or_drop(Name, HPid, ModeTab, Bin).
-
-%%%-----------------------------------------------------------------
-%%% Remove internal fields from configuration
-filter_config(#{config:=HConfig}=Config) ->
- Config#{config=>maps:without([handler_pid,mode_tab],HConfig)}.
-
-%%%===================================================================
-%%% gen_server callbacks
-%%%===================================================================
-
-init([Name, Config = #{config := HConfig},
- State0 = #{type := Type, file_ctrl_sync_int := FileCtrlSyncInt}]) ->
- RegName = ?name_to_reg_name(?MODULE,Name),
- register(RegName, self()),
- process_flag(trap_exit, true),
- process_flag(message_queue_data, off_heap),
-
- ?init_test_hooks(),
- ?start_observation(Name),
-
- case do_init(Name, Type) of
- {ok,InitState} ->
- try ets:new(Name, [public]) of
- ModeTab ->
- ?set_mode(ModeTab, async),
- State = maps:merge(State0, InitState),
- T0 = ?timestamp(),
- State1 =
- ?merge_with_stats(State#{
- mode_tab => ModeTab,
- mode => async,
- file_ctrl_sync => FileCtrlSyncInt,
- last_qlen => 0,
- last_log_ts => T0,
- last_op => sync,
- burst_win_ts => T0,
- burst_msg_count => 0}),
- Config1 =
- Config#{config => HConfig#{handler_pid => self(),
- mode_tab => ModeTab}},
- proc_lib:init_ack({ok,self(),Config1}),
- gen_server:cast(self(), repeated_filesync),
- gen_server:enter_loop(?MODULE, [], State1)
- catch
- _:Error ->
- unregister(RegName),
- logger_h_common:error_notify({init_handler,Name,Error}),
- proc_lib:init_ack(Error)
- end;
- Error ->
- unregister(RegName),
- logger_h_common:error_notify({init_handler,Name,Error}),
- proc_lib:init_ack(Error)
- end.
-
-do_init(Name, Type) ->
- case open_log_file(Name, Type) of
- {ok,FileCtrlPid} ->
- case logger_h_common:unset_restart_flag(Name, ?MODULE) of
- true ->
- %% inform about restart
- gen_server:cast(self(), {log_handler_info,
- "Handler ~p restarted",
- [Name]});
- false ->
- %% initial start
- ok
- end,
- {ok,#{id=>Name,type=>Type,file_ctrl_pid=>FileCtrlPid}};
- Error ->
- Error
- end.
-
-%% This is the synchronous log event.
-handle_call({log, Bin}, _From, State) ->
- {Result,State1} = do_log(Bin, call, State),
- %% Result == ok | dropped
- {reply,Result, State1};
-
-handle_call(filesync, _From, State = #{type := Type,
- file_ctrl_pid := FileCtrlPid}) ->
- if is_atom(Type) ->
- {reply, ok, State};
- true ->
- {reply, file_ctrl_filesync_sync(FileCtrlPid), State#{last_op=>sync}}
- end;
-
-handle_call({change_config,_OldConfig,NewConfig}, _From,
- State = #{filesync_repeat_interval := FSyncInt0}) ->
- HConfig = maps:get(config, NewConfig, #{}),
- State1 = maps:merge(State, HConfig),
- case logger_h_common:overload_levels_ok(State1) of
- true ->
- _ =
- case maps:get(filesync_repeat_interval, HConfig, undefined) of
- undefined ->
- ok;
- no_repeat ->
- _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref,
- State,
- undefined));
- FSyncInt0 ->
- ok;
- _FSyncInt1 ->
- _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref,
- State,
- undefined)),
- gen_server:cast(self(), repeated_filesync)
- end,
- {reply, ok, State1};
- false ->
- #{sync_mode_qlen := SMQL,
- drop_mode_qlen := DMQL,
- flush_qlen := FQL} = State1,
- {reply, {error,{invalid_levels,{SMQL,DMQL,FQL}}}, State}
- end;
-
-handle_call(info, _From, State) ->
- {reply, State, State};
-
-handle_call(reset, _From, State) ->
- State1 = ?merge_with_stats(State),
- {reply, ok, State1#{last_qlen => 0,
- last_log_ts => ?timestamp()}};
-
-handle_call(stop, _From, State) ->
- {stop, {shutdown,stopped}, ok, State}.
-
-%% This is the asynchronous log event.
-handle_cast({log, Bin}, State) ->
- {_,State1} = do_log(Bin, cast, State),
- {noreply, State1};
-
-handle_cast({log_handler_info, Format, Args}, State = #{id:=Name}) ->
- log_handler_info(Name, Format, Args, State),
- {noreply, State};
-
-%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this
-%% clause gets called repeatedly by the handler. In order to
-%% guarantee that a filesync *always* happens after the last log
-%% event, the repeat operation must be active!
-handle_cast(repeated_filesync,
- State = #{type := Type,
- file_ctrl_pid := FileCtrlPid,
- filesync_repeat_interval := FSyncInt,
- last_op := LastOp}) ->
- State1 =
- if not is_atom(Type), is_integer(FSyncInt) ->
- %% only do filesync if something has been
- %% written since last time we checked
- if LastOp == sync ->
- ok;
- true ->
- file_ctrl_filesync_async(FileCtrlPid)
- end,
- {ok,TRef} =
- timer:apply_after(FSyncInt, gen_server,cast,
- [self(),repeated_filesync]),
- State#{rep_sync_tref => TRef, last_op => sync};
- true ->
- State
- end,
- {noreply,State1}.
-
-handle_info({'EXIT',Pid,Why}, State = #{id := Name, type := FileInfo}) ->
- case maps:get(file_ctrl_pid, State, undefined) of
- Pid ->
- %% file error, terminate handler
- logger_h_common:handler_exit(Name,
- {error,{write_failed,FileInfo,Why}});
- _Other ->
- %% ignore EXIT
- ok
- end,
- {noreply, State};
-
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(Reason, State = #{id:=Name, file_ctrl_pid:=FWPid,
- type:=_FileInfo}) ->
- _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, State,
- undefined)),
+get_default_config() ->
+ #{type => standard_io}.
+
+filesync(_Name, _Mode, #{type := Type}=State) when is_atom(Type) ->
+ {ok,State};
+filesync(_Name, async, #{file_ctrl_pid := FileCtrlPid} = State) ->
+ ok = file_ctrl_filesync_async(FileCtrlPid),
+ {ok,State};
+filesync(_Name, sync, #{file_ctrl_pid := FileCtrlPid} = State) ->
+ Result = file_ctrl_filesync_sync(FileCtrlPid),
+ {Result,State}.
+
+write(_Name, async, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) ->
+ ok = file_write_async(FileCtrlPid, Bin),
+ {ok,State};
+write(_Name, sync, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) ->
+ Result = file_write_sync(FileCtrlPid, Bin),
+ {Result,State}.
+
+reset_state(_Name, State) ->
+ State.
+
+handle_info(_Name, {'EXIT',Pid,Why}, #{type := FileInfo, file_ctrl_pid := Pid}) ->
+ %% file_ctrl_pid died, file error, terminate handler
+ exit({error,{write_failed,FileInfo,Why}});
+handle_info(_, _, State) ->
+ State.
+
+terminate(_Name, _Reason, #{file_ctrl_pid:=FWPid}) ->
case is_process_alive(FWPid) of
true ->
unlink(FWPid),
@@ -423,17 +198,12 @@ terminate(Reason, State = #{id:=Name, file_ctrl_pid:=FWPid,
ok
after
?DEFAULT_CALL_TIMEOUT ->
- exit(FWPid, kill)
+ exit(FWPid, kill),
+ ok
end;
false ->
ok
- end,
- ok = logger_h_common:stop_or_restart(Name, Reason, State),
- unregister(?name_to_reg_name(?MODULE, Name)),
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
+ end.
%%%===================================================================
%%% Internal functions
@@ -441,203 +211,6 @@ code_change(_OldVsn, State, _Extra) ->
%%%-----------------------------------------------------------------
%%%
-get_default_config() ->
- #{type => standard_io,
- sync_mode_qlen => ?SYNC_MODE_QLEN,
- drop_mode_qlen => ?DROP_MODE_QLEN,
- flush_qlen => ?FLUSH_QLEN,
- burst_limit_enable => ?BURST_LIMIT_ENABLE,
- burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT,
- burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME,
- overload_kill_enable => ?OVERLOAD_KILL_ENABLE,
- overload_kill_qlen => ?OVERLOAD_KILL_QLEN,
- overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE,
- overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}.
-
-get_init_state() ->
- #{file_ctrl_sync_int => ?CONTROLLER_SYNC_INTERVAL,
- filesync_ok_qlen => ?FILESYNC_OK_QLEN}.
-
-%%%-----------------------------------------------------------------
-%%% Add a standard handler to the logger.
-%%% This starts a dedicated handler process which should always
-%%% exist if the handler is registered with logger (and should not
-%%% exist if the handler is not registered).
-%%%
-%%% Handler specific config should be provided with a sub map associated
-%%% with a key named 'config', e.g:
-%%%
-%%% Config = #{config => #{sync_mode_qlen => 50}
-%%%
-%%% The standard handler process is linked to logger_sup, which is
-%%% part of the kernel application's supervision tree.
-start(Name, Config, HandlerState) ->
- LoggerStdH =
- #{id => Name,
- start => {?MODULE, start_link, [Name,Config,HandlerState]},
- restart => temporary,
- shutdown => 2000,
- type => worker,
- modules => [?MODULE]},
- case supervisor:start_child(logger_sup, LoggerStdH) of
- {ok,Pid,Config1} ->
- ok = logger_handler_watcher:register_handler(Name,Pid),
- {ok,Config1};
- Error ->
- Error
- end.
-
-%%%-----------------------------------------------------------------
-%%% Stop and remove the handler.
-stop(Name) ->
- case whereis(?name_to_reg_name(?MODULE,Name)) of
- undefined ->
- ok;
- Pid ->
- %% We don't want to do supervisor:terminate_child here
- %% since we need to distinguish this explicit stop from a
- %% system termination in order to avoid circular attempts
- %% at removing the handler (implying deadlocks and
- %% timeouts).
- %% And we don't need to do supervisor:delete_child, since
- %% the restart type is temporary, which means that the
- %% child specification is automatically removed from the
- %% supervisor when the process dies.
- _ = gen_server:call(Pid, stop),
- ok
- end.
-
-%%%-----------------------------------------------------------------
-%%% Logging and overload control.
--define(update_file_ctrl_sync(C, Interval),
- if C == 0 -> Interval;
- true -> C-1 end).
-
-%% check for overload between every event (and set Mode to async,
-%% sync or drop accordingly), but never flush the whole mailbox
-%% before LogWindowSize events have been handled
-do_log(Bin, CallOrCast, State = #{id:=Name, mode:=Mode0}) ->
- T1 = ?timestamp(),
-
- %% check if the handler is getting overloaded, or if it's
- %% recovering from overload (the check must be done for each
- %% event to react quickly to large bursts of events and
- %% to ensure that the handler can never end up in drop mode
- %% with an empty mailbox, which would stop operation)
- {Mode1,QLen,Mem,State1} = logger_h_common:check_load(State),
-
- if (Mode1 == drop) andalso (Mode0 =/= drop) ->
- log_handler_info(Name, "Handler ~p switched to drop mode",
- [Name], State);
- (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) ->
- log_handler_info(Name, "Handler ~p switched to ~w mode",
- [Name,Mode1], State);
- true ->
- ok
- end,
-
- %% kill the handler if it can't keep up with the load
- logger_h_common:kill_if_choked(Name, QLen, Mem, ?MODULE, State),
-
- if Mode1 == flush ->
- flush(Name, QLen, T1, State1);
- true ->
- write(Name, Mode1, T1, Bin, CallOrCast, State1)
- end.
-
-%% this clause is called by do_log/3 after an overload check
-%% has been performed, where QLen > FlushQLen
-flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) ->
- %% flush messages in the mailbox (a limited number in
- %% order to not cause long delays)
- NewFlushed = logger_h_common:flush_log_events(?FLUSH_MAX_N),
-
- %% write info in log about flushed messages
- log_handler_info(Name, "Handler ~p flushed ~w log events",
- [Name,NewFlushed], State),
-
- %% because of the receive loop when flushing messages, the
- %% handler will be scheduled out often and the mailbox could
- %% grow very large, so we'd better check the queue again here
- {_,_QLen1} = process_info(self(), message_queue_len),
- ?observe(Name,{max_qlen,_QLen1}),
-
- %% Add 1 for the current log event
- ?observe(Name,{flushed,NewFlushed+1}),
-
- State1 = ?update_max_time(?diff_time(T1,_T0),State),
- {dropped,?update_other(flushed,FLUSHED,NewFlushed,
- State1#{mode => ?set_mode(ModeTab,async),
- last_qlen => 0,
- last_log_ts => T1})}.
-
-%% this clause is called to write to file
-write(_Name, Mode, T1, Bin, _CallOrCast,
- State = #{mode_tab := ModeTab,
- file_ctrl_pid := FileCtrlPid,
- file_ctrl_sync := FileCtrlSync,
- last_qlen := LastQLen,
- last_log_ts := T0,
- file_ctrl_sync_int := FileCtrlSyncInt}) ->
- %% check if we need to limit the number of writes
- %% during a burst of log events
- {DoWrite,BurstWinT,BurstMsgCount} = logger_h_common:limit_burst(State),
-
- %% only send a synhrounous event to the file controller process
- %% every FileCtrlSyncInt time, to give the handler time between
- %% file writes so it can keep up with incoming messages
- {Result,LastQLen1} =
- if DoWrite, FileCtrlSync == 0 ->
- ?observe(_Name,{_CallOrCast,1}),
- file_write_sync(FileCtrlPid, Bin, false),
- {ok,element(2, process_info(self(), message_queue_len))};
- DoWrite ->
- ?observe(_Name,{_CallOrCast,1}),
- file_write_async(FileCtrlPid, Bin),
- {ok,LastQLen};
- not DoWrite ->
- ?observe(_Name,{flushed,1}),
- {dropped,LastQLen}
- end,
-
- %% Check if the time since the previous log event is long enough -
- %% and the queue length small enough - to assume the mailbox has
- %% been emptied, and if so, do filesync operation and reset mode to
- %% async. Note that this is the best we can do to detect an idle
- %% handler without setting a timer after each log call/cast. If the
- %% time between two consecutive log events is fast and no new
- %% event comes in after the last one, idle state won't be detected!
- Time = ?diff_time(T1,T0),
- {Mode1,BurstMsgCount1} =
- if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso
- (Time > ?IDLE_DETECT_TIME_USEC) ->
- %% do filesync if necessary
- case maps:get(type, State) of
- Std when is_atom(Std) ->
- ok;
- _File ->
- file_ctrl_filesync_async(FileCtrlPid)
- end,
- {?change_mode(ModeTab, Mode, async),0};
- true ->
- {Mode,BurstMsgCount}
- end,
- State1 =
- ?update_calls_or_casts(_CallOrCast,1,State),
- State2 =
- ?update_max_time(Time,
- State1#{mode => Mode1,
- last_qlen := LastQLen1,
- last_log_ts => T1,
- last_op => write,
- burst_win_ts => BurstWinT,
- burst_msg_count => BurstMsgCount1,
- file_ctrl_sync =>
- ?update_file_ctrl_sync(FileCtrlSync,
- FileCtrlSyncInt)}),
- {Result,State2}.
-
open_log_file(HandlerName, FileInfo) ->
case file_ctrl_start(HandlerName, FileInfo) of
OK = {ok,_FileCtrlPid} -> OK;
@@ -669,19 +242,6 @@ close_log_file(Fd) ->
_ = file:close(Fd).
-log_handler_info(Name, Format, Args, #{file_ctrl_pid := FileCtrlPid}) ->
- Config =
- case logger:get_handler_config(Name) of
- {ok,Conf} -> Conf;
- _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}}
- end,
- Meta = #{time=>erlang:system_time(microsecond)},
- Bin = logger_h_common:log_to_binary(#{level => notice,
- msg => {Format,Args},
- meta => Meta}, Config),
- _ = file_write_async(FileCtrlPid, Bin),
- ok.
-
%%%-----------------------------------------------------------------
%%% File control process
@@ -708,24 +268,19 @@ file_write_async(Pid, Bin) ->
Pid ! {log,Bin},
ok.
-file_write_sync(Pid, Bin, FileSync) ->
- case file_ctrl_call(Pid, {log,self(),Bin,FileSync}) of
- {error,Reason} ->
- {error,{write_failed,Bin,Reason}};
- Result ->
- Result
- end.
+file_write_sync(Pid, Bin) ->
+ file_ctrl_call(Pid, {log,Bin}).
file_ctrl_filesync_async(Pid) ->
Pid ! filesync,
ok.
file_ctrl_filesync_sync(Pid) ->
- file_ctrl_call(Pid, {filesync,self()}).
+ file_ctrl_call(Pid, filesync).
file_ctrl_call(Pid, Msg) ->
MRef = monitor(process, Pid),
- Pid ! {Msg,MRef},
+ Pid ! {Msg,{self(),MRef}},
receive
{MRef,Result} ->
demonitor(MRef, [flush]),
@@ -743,74 +298,43 @@ file_ctrl_init(HandlerName, FileInfo, Starter) when is_tuple(FileInfo) ->
case do_open_log_file(FileInfo) of
{ok,Fd} ->
Starter ! {self(),ok},
- file_ctrl_loop(Fd, file, FileName, false, ok, ok, HandlerName);
+ file_ctrl_loop(Fd, FileName, false, ok, ok, HandlerName);
{error,Reason} ->
Starter ! {self(),{error,{open_failed,FileName,Reason}}}
end;
file_ctrl_init(HandlerName, StdDev, Starter) ->
Starter ! {self(),ok},
- file_ctrl_loop(StdDev, standard_io, StdDev, false, ok, ok, HandlerName).
+ file_ctrl_loop(StdDev, StdDev, false, ok, ok, HandlerName).
-file_ctrl_loop(Fd, Type, DevName, Synced,
+file_ctrl_loop(Fd, DevName, Synced,
PrevWriteResult, PrevSyncResult, HandlerName) ->
receive
%% asynchronous event
{log,Bin} ->
- Result = if Type == file ->
- write_to_dev(Fd, Bin, DevName,
- PrevWriteResult, HandlerName);
- true ->
- io:put_chars(Fd, Bin)
- end,
- file_ctrl_loop(Fd, Type, DevName, false,
+ Fd1 = ensure(Fd, DevName),
+ Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName),
+ file_ctrl_loop(Fd1, DevName, false,
Result, PrevSyncResult, HandlerName);
%% synchronous event
- {{log,From,Bin,FileSync},MRef} ->
- if Type == file ->
- %% check that file hasn't been deleted
- CheckFile =
- fun() -> {ok,_} = file:read_file_info(DevName) end,
- spawn_link(CheckFile),
- WResult = write_to_dev(Fd, Bin, DevName,
- PrevWriteResult, HandlerName),
- {Synced1,SResult} =
- if not FileSync ->
- {false,PrevSyncResult};
- true ->
- case sync_dev(Fd, DevName,
- PrevSyncResult, HandlerName) of
- ok -> {true,ok};
- Error -> {false,Error}
- end
- end,
- From ! {MRef,ok},
- file_ctrl_loop(Fd, Type, DevName, Synced1,
- WResult, SResult, HandlerName);
- true ->
- _ = io:put_chars(Fd, Bin),
- From ! {MRef,ok},
- file_ctrl_loop(Fd, Type, DevName, false,
- ok, PrevSyncResult, HandlerName)
- end;
+ {{log,Bin},{From,MRef}} ->
+ Fd1 = ensure(Fd, DevName),
+ Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName),
+ From ! {MRef,ok},
+ file_ctrl_loop(Fd1, DevName, false,
+ Result, PrevSyncResult, HandlerName);
- filesync when not Synced ->
- Result = sync_dev(Fd, DevName, PrevSyncResult, HandlerName),
- file_ctrl_loop(Fd, Type, DevName, true,
+ filesync ->
+ Fd1 = ensure(Fd, DevName),
+ Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName),
+ file_ctrl_loop(Fd1, DevName, true,
PrevWriteResult, Result, HandlerName);
- filesync ->
- file_ctrl_loop(Fd, Type, DevName, true,
- PrevWriteResult, PrevSyncResult, HandlerName);
-
- {{filesync,From},MRef} ->
- Result = if not Synced ->
- sync_dev(Fd, DevName, PrevSyncResult, HandlerName);
- true ->
- ok
- end,
+ {filesync,{From,MRef}} ->
+ Fd1 = ensure(Fd, DevName),
+ Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName),
From ! {MRef,ok},
- file_ctrl_loop(Fd, Type, DevName, true,
+ file_ctrl_loop(Fd1, DevName, true,
PrevWriteResult, Result, HandlerName);
stop ->
@@ -818,26 +342,44 @@ file_ctrl_loop(Fd, Type, DevName, Synced,
stopped
end.
-write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) ->
- case ?file_write(Fd, Bin) of
- ok ->
- ok;
- PrevWriteResult ->
- %% don't report same error twice
- PrevWriteResult;
- Error ->
- logger_h_common:error_notify({HandlerName,write,FileName,Error}),
- Error
+%% In order to play well with tools like logrotate, we need to be able
+%% to re-create the file if it has disappeared (e.g. if rotated by
+%% logrotate)
+ensure(Fd,DevName) when is_atom(DevName) ->
+ Fd;
+ensure(Fd,FileName) ->
+ case file:read_file_info(FileName) of
+ {ok,_} ->
+ Fd;
+ _ ->
+ _ = file:close(Fd),
+ _ = file:close(Fd), % delayed_write cause close not to close
+ case do_open_log_file({file,FileName}) of
+ {ok,Fd1} ->
+ Fd1;
+ Error ->
+ exit({could_not_reopen_file,Error})
+ end
end.
-sync_dev(Fd, DevName, PrevSyncResult, HandlerName) ->
- case ?file_datasync(Fd) of
- ok ->
- ok;
- PrevSyncResult ->
- %% don't report same error twice
- PrevSyncResult;
- Error ->
- logger_h_common:error_notify({HandlerName,filesync,DevName,Error}),
- Error
- end.
+write_to_dev(DevName, Bin, _DevName, _PrevWriteResult, _HandlerName)
+ when is_atom(DevName) ->
+ io:put_chars(DevName, Bin);
+write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) ->
+ Result = ?file_write(Fd, Bin),
+ maybe_notify_error(write,Result,PrevWriteResult,FileName,HandlerName).
+
+sync_dev(_Fd, _FileName, true, PrevSyncResult, _HandlerName) ->
+ PrevSyncResult;
+sync_dev(Fd, FileName, false, PrevSyncResult, HandlerName) ->
+ Result = ?file_datasync(Fd),
+ maybe_notify_error(filesync,Result,PrevSyncResult,FileName,HandlerName).
+
+maybe_notify_error(_Op, ok, _PrevResult, _FileName, _HandlerName) ->
+ ok;
+maybe_notify_error(_Op, PrevResult, PrevResult, _FileName, _HandlerName) ->
+ %% don't report same error twice
+ PrevResult;
+maybe_notify_error(Op, Error, _PrevResult, FileName, HandlerName) ->
+ logger_h_common:error_notify({HandlerName,Op,FileName,Error}),
+ Error.
diff --git a/lib/kernel/src/logger_sup.erl b/lib/kernel/src/logger_sup.erl
index 3d6f482e20..9ea8558a16 100644
--- a/lib/kernel/src/logger_sup.erl
+++ b/lib/kernel/src/logger_sup.erl
@@ -50,7 +50,9 @@ init([]) ->
start => {logger_handler_watcher, start_link, []},
shutdown => brutal_kill},
- {ok, {SupFlags, [Watcher]}}.
+ Proxy = logger_proxy:child_spec(),
+
+ {ok, {SupFlags, [Watcher,Proxy]}}.
%%%===================================================================
%%% Internal functions
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index 01d809b566..4915193196 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -808,7 +808,8 @@ handle_info({AcceptPid, {accept_pending,MyNode,Node,Address,Type}}, State) ->
_:_ ->
error_logger:error_msg("~n** Cannot get connection id for node ~w~n",
[Node]),
- AcceptPid ! {self(),{accept_pending,nok_pending}}
+ AcceptPid ! {self(),{accept_pending,nok_pending}},
+ {noreply, State}
end
end;
diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl
index 14fe21e9de..4f9d7b3e5c 100644
--- a/lib/kernel/src/seq_trace.erl
+++ b/lib/kernel/src/seq_trace.erl
@@ -98,7 +98,7 @@ print(Label, Term) ->
-spec reset_trace() -> 'true'.
reset_trace() ->
- erlang:system_flag(1, 0).
+ erlang:system_flag(reset_seq_trace, true).
%% reset_trace(Pid) -> % this might be a useful function too
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index 4a86265a4a..8a6ffe7e72 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -76,8 +76,11 @@ MODULES= \
logger_filters_SUITE \
logger_formatter_SUITE \
logger_legacy_SUITE \
+ logger_olp_SUITE \
+ logger_proxy_SUITE \
logger_simple_h_SUITE \
logger_std_h_SUITE \
+ logger_stress_SUITE \
logger_test_lib \
os_SUITE \
pg2_SUITE \
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index 04c0c48e3a..52edfaee29 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -53,7 +53,7 @@
active_once_closed/1, send_timeout/1, send_timeout_active/1,
otp_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1,
wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1,
- otp_12242/1]).
+ otp_12242/1, delay_send_error/1]).
%% Internal exports.
-export([sender/3, not_owner/1, passive_sockets_server/2, priority_server/1,
@@ -97,7 +97,7 @@ all() ->
active_once_closed, send_timeout, send_timeout_active, otp_7731,
wrapping_oct,
zombie_sockets, otp_7816, otp_8102, otp_9389,
- otp_12242].
+ otp_12242, delay_send_error].
groups() ->
[].
@@ -1969,20 +1969,20 @@ recvtclass(_Config) ->
{skip,{ipv6_not_supported,IFs}}
end.
-%% These version numbers are the highest noted in daily tests
-%% where the test fails for a plausible reason, so
-%% skip on that platform.
+%% These version numbers are above the highest noted
+%% in daily tests where the test fails for a plausible reason,
+%% so skip on platforms of lower version, i.e they are future
+%% versions where it is possible that it might not fail.
%%
-%% On newer versions it might be fixed, but we'll see about that
-%% when machines with newer versions gets installed...
-%% If the test still fails for a plausible reason these
+%% When machines with newer versions gets installed,
+%% if the test still fails for a plausible reason these
%% version numbers simply should be increased.
%% Or maybe we should change to only test on known good
%% platforms - change {unix,_} to false?
%% pktoptions is not supported for IPv4
recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0});
-recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0});
+recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
%% Using the option returns einval, so it is not implemented.
recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0});
recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
@@ -1994,18 +1994,19 @@ recvtos_ok(_, _) -> false.
%% pktoptions is not supported for IPv4
recvttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0});
-recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0});
+recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
%% Using the option returns einval, so it is not implemented.
recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0});
recvttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
+%% Does not return any value - not implemented for pktoptions
+recvttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,7,0});
%%
-recvttl_ok({unix,linux}, _) -> true;
recvttl_ok({unix,_}, _) -> true;
recvttl_ok(_, _) -> false.
%% pktoptions is not supported for IPv6
recvtclass_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0});
-recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0});
+recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0});
recvtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0});
%% Using the option returns einval, so it is not implemented.
recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0});
@@ -3426,3 +3427,32 @@ otp_12242(Addr) when tuple_size(Addr) =:= 4 ->
wait(Mref) ->
receive {'DOWN',Mref,_,_,Reason} -> Reason end.
+
+%% OTP-15536
+%% Test that send error works correctly for delay_send
+delay_send_error(Config) ->
+ {ok, LS} = gen_tcp:listen(0, [{reuseaddr, true}, {packet, 1}, {active, false}]),
+ {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS),
+ P = spawn_link(
+ fun() ->
+ {ok, S} = gen_tcp:accept(LS),
+ receive die -> gen_tcp:close(S) end
+ end),
+ erlang:monitor(process, P),
+ {ok, S} = gen_tcp:connect("localhost", PortNum,
+ [{packet, 1}, {active, false}, {delay_send, true}]),
+
+ %% Do a couple of sends first to see that it works
+ ok = gen_tcp:send(S, "hello"),
+ ok = gen_tcp:send(S, "hello"),
+ ok = gen_tcp:send(S, "hello"),
+
+ %% Make the receiver close
+ P ! die,
+ receive _Down -> ok end,
+
+ ok = gen_tcp:send(S, "hello"),
+ timer:sleep(500), %% Sleep in order for delay_send to have time to trigger
+
+ %% This used to result in a double free
+ {error, closed} = gen_tcp:send(S, "hello").
diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl
index 7828cc4716..a0154b2694 100644
--- a/lib/kernel/test/init_SUITE.erl
+++ b/lib/kernel/test/init_SUITE.erl
@@ -295,7 +295,7 @@ is_real_system(KernelVsn, StdlibVsn) ->
%% before restart.
%% ------------------------------------------------
many_restarts() ->
- [{timetrap,{minutes,8}}].
+ [{timetrap,{minutes,16}}].
many_restarts(Config) when is_list(Config) ->
{ok, Node} = loose_node:start(init_test, "", ?DEFAULT_TIMEOUT_SEC),
@@ -315,7 +315,7 @@ loop_restart(N,Node,EHPid) ->
loose_node:stop(Node),
ct:fail(not_stopping)
end,
- ok = wait_for(30, Node, EHPid),
+ ok = wait_for(60, Node, EHPid),
loop_restart(N-1,Node,rpc:call(Node,erlang,whereis,[logger])).
wait_for(0,Node,_) ->
@@ -367,7 +367,8 @@ restart(Config) when is_list(Config) ->
SysProcs0 = rpc:call(Node, ?MODULE, find_system_processes, []),
io:format("SysProcs0=~p~n", [SysProcs0]),
[InitPid, PurgerPid, LitCollectorPid,
- DirtySigNPid, DirtySigHPid, DirtySigMPid] = SysProcs0,
+ DirtySigNPid, DirtySigHPid, DirtySigMPid,
+ PrimFilePid] = SysProcs0,
InitPid = rpc:call(Node, erlang, whereis, [init]),
PurgerPid = rpc:call(Node, erlang, whereis, [erts_code_purger]),
Procs = rpc:call(Node, erlang, processes, []),
@@ -385,7 +386,8 @@ restart(Config) when is_list(Config) ->
SysProcs1 = rpc:call(Node, ?MODULE, find_system_processes, []),
io:format("SysProcs1=~p~n", [SysProcs1]),
[InitPid1, PurgerPid1, LitCollectorPid1,
- DirtySigNPid1, DirtySigHPid1, DirtySigMPid1] = SysProcs1,
+ DirtySigNPid1, DirtySigHPid1, DirtySigMPid1,
+ PrimFilePid1] = SysProcs1,
%% Still the same init process!
InitPid1 = rpc:call(Node, erlang, whereis, [init]),
@@ -411,6 +413,10 @@ restart(Config) when is_list(Config) ->
DirtySigMP = pid_to_list(DirtySigMPid),
DirtySigMP = pid_to_list(DirtySigMPid1),
+ %% and same prim_file helper process!
+ PrimFileP = pid_to_list(PrimFilePid),
+ PrimFileP = pid_to_list(PrimFilePid1),
+
NewProcs0 = rpc:call(Node, erlang, processes, []),
NewProcs = NewProcs0 -- SysProcs1,
case check_processes(NewProcs, MaxPid) of
@@ -437,7 +443,8 @@ restart(Config) when is_list(Config) ->
literal_collector,
dirty_sig_handler_normal,
dirty_sig_handler_high,
- dirty_sig_handler_max}).
+ dirty_sig_handler_max,
+ prim_file}).
find_system_processes() ->
find_system_procs(processes(), #sys_procs{}).
@@ -448,7 +455,8 @@ find_system_procs([], SysProcs) ->
SysProcs#sys_procs.literal_collector,
SysProcs#sys_procs.dirty_sig_handler_normal,
SysProcs#sys_procs.dirty_sig_handler_high,
- SysProcs#sys_procs.dirty_sig_handler_max];
+ SysProcs#sys_procs.dirty_sig_handler_max,
+ SysProcs#sys_procs.prim_file];
find_system_procs([P|Ps], SysProcs) ->
case process_info(P, [initial_call, priority]) of
[{initial_call,{erl_init,start,2}},_] ->
@@ -472,6 +480,9 @@ find_system_procs([P|Ps], SysProcs) ->
{priority,max}] ->
undefined = SysProcs#sys_procs.dirty_sig_handler_max,
find_system_procs(Ps, SysProcs#sys_procs{dirty_sig_handler_max = P});
+ [{initial_call,{prim_file,start,0}},_] ->
+ undefined = SysProcs#sys_procs.prim_file,
+ find_system_procs(Ps, SysProcs#sys_procs{prim_file = P});
_ ->
find_system_procs(Ps, SysProcs)
end.
diff --git a/lib/kernel/test/kernel_bench.spec b/lib/kernel/test/kernel_bench.spec
index 4de133f21b..898ceb59e0 100644
--- a/lib/kernel/test/kernel_bench.spec
+++ b/lib/kernel/test/kernel_bench.spec
@@ -1,2 +1,3 @@
{groups,"../kernel_test",zlib_SUITE,[bench]}.
{groups,"../kernel_test",file_SUITE,[bench]}.
+{suites,"../kernel_test",[logger_stress_SUITE]}.
diff --git a/lib/kernel/test/logger.cover b/lib/kernel/test/logger.cover
index 960bc0abff..9691aa295e 100644
--- a/lib/kernel/test/logger.cover
+++ b/lib/kernel/test/logger.cover
@@ -4,9 +4,12 @@
logger_backend,
logger_config,
logger_disk_log_h,
- logger_h_common,
logger_filters,
logger_formatter,
+ logger_handler_watcher,
+ logger_h_common,
+ logger_olp,
+ logger_proxy,
logger_server,
logger_simple_h,
logger_std_h,
diff --git a/lib/kernel/test/logger.spec b/lib/kernel/test/logger.spec
index 1ab90b3e93..3aec37951d 100644
--- a/lib/kernel/test/logger.spec
+++ b/lib/kernel/test/logger.spec
@@ -7,5 +7,7 @@
logger_filters_SUITE,
logger_formatter_SUITE,
logger_legacy_SUITE,
+ logger_olp_SUITE,
+ logger_proxy_SUITE,
logger_simple_h_SUITE,
logger_std_h_SUITE]}.
diff --git a/lib/kernel/test/logger_disk_log_h_SUITE.erl b/lib/kernel/test/logger_disk_log_h_SUITE.erl
index 905c2c52c5..9bbec42de8 100644
--- a/lib/kernel/test/logger_disk_log_h_SUITE.erl
+++ b/lib/kernel/test/logger_disk_log_h_SUITE.erl
@@ -24,6 +24,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/logger.hrl").
-include_lib("kernel/src/logger_internal.hrl").
+-include_lib("kernel/src/logger_olp.hrl").
-include_lib("kernel/src/logger_h_common.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include_lib("kernel/include/file.hrl").
@@ -97,7 +98,6 @@ all() ->
formatter_fail,
config_fail,
bad_input,
- info_and_reset,
reconfig,
sync,
disk_log_full,
@@ -306,9 +306,9 @@ logging(cleanup, _Config) ->
filter_config(_Config) ->
ok = logger:add_handler(?MODULE,logger_disk_log_h,#{}),
{ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE),
- HConfig = maps:without([handler_pid,mode_tab],HConfig),
+ HConfig = maps:without([olp],HConfig),
- FakeFullHConfig = HConfig#{handler_pid=>self(),mode_tab=>erlang:make_ref()},
+ FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}},
#{config:=HConfig} =
logger_disk_log_h:filter_config(Config#{config=>FakeFullHConfig}),
ok.
@@ -332,16 +332,18 @@ errors(Config) ->
%%! Check how bad log_opts are handled!
{error,{illegal_config_change,
- #{config:=#{type:=wrap}},
- #{config:=#{type:=halt}}}} =
+ logger_disk_log_h,
+ #{type:=wrap},
+ #{type:=halt}}} =
logger:update_handler_config(Name1,
config,
#{type=>halt,
file=>LogFile1}),
{error,{illegal_config_change,
- #{config:=#{file:=LogFile1}},
- #{config:=#{file:="newfilename"}}}} =
+ logger_disk_log_h,
+ #{file:=LogFile1},
+ #{file:="newfilename"}}} =
logger:update_handler_config(Name1,
config,
#{file=>"newfilename"}),
@@ -349,9 +351,7 @@ errors(Config) ->
%% Read-only fields may (accidentially) be included in the change,
%% but it won't take effect
{ok,C} = logger:get_handler_config(Name1),
- ok = logger:set_handler_config(Name1,config,
- #{handler_pid=>self(),
- mode_tab=>erlang:make_ref()}),
+ ok = logger:set_handler_config(Name1,config,#{olp=>dummyvalue}),
{ok,C} = logger:get_handler_config(Name1),
@@ -411,20 +411,22 @@ formatter_fail(cleanup,_Config) ->
ok.
config_fail(_Config) ->
- {error,{handler_not_added,{invalid_config,logger_disk_log_h,{bad,bad}}}} =
+ {error,{handler_not_added,{invalid_config,logger_disk_log_h,#{bad:=bad}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{bad => bad},
filter_default=>log,
formatter=>{?MODULE,self()}}),
- {error,{handler_not_added,{invalid_levels,{_,1,_}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{drop_mode_qlen=>1}}),
- {error,{handler_not_added,{invalid_levels,{43,42,_}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43,
+ drop_mode_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{sync_mode_qlen=>43,
drop_mode_qlen=>42}}),
- {error,{handler_not_added,{invalid_levels,{_,43,42}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43,
+ flush_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_disk_log_h,
#{config => #{drop_mode_qlen=>43,
flush_qlen=>42}}),
@@ -433,20 +435,17 @@ config_fail(_Config) ->
#{filter_default=>log,
formatter=>{?MODULE,self()}}),
%% can't change the disk log options for a log already in use
- {error,{illegal_config_change,_,_}} =
+ {error,{illegal_config_change,logger_disk_log_h,_,_}} =
logger:update_handler_config(?MODULE,config,
#{max_no_files=>2}),
- %% can't change name of an existing handler
- {error,{illegal_config_change,_,_}} =
- logger:update_handler_config(?MODULE,id,bad),
%% incorrect values of OP params
{ok,#{config := HConfig}} = logger:get_handler_config(?MODULE),
- {error,{invalid_levels,_}} =
+ {error,{invalid_olp_levels,_}} =
logger:update_handler_config(?MODULE,config,
HConfig#{sync_mode_qlen=>100,
flush_qlen=>99}),
%% invalid name of config parameter
- {error,{invalid_config,logger_disk_log_h,{filesync_rep_int,2000}}} =
+ {error,{invalid_config,logger_disk_log_h,#{filesync_rep_int:=2000}}} =
logger:update_handler_config(?MODULE, config,
HConfig#{filesync_rep_int => 2000}),
ok.
@@ -455,18 +454,7 @@ config_fail(cleanup,_Config) ->
bad_input(_Config) ->
{error,{badarg,{filesync,["BadType"]}}} =
- logger_disk_log_h:filesync("BadType"),
- {error,{badarg,{info,["BadType"]}}} = logger_disk_log_h:info("BadType"),
- {error,{badarg,{reset,["BadType"]}}} = logger_disk_log_h:reset("BadType").
-
-info_and_reset(_Config) ->
- ok = logger:add_handler(?MODULE,logger_disk_log_h,
- #{filter_default=>log,
- formatter=>{?MODULE,self()}}),
- #{id := ?MODULE} = logger_disk_log_h:info(?MODULE),
- ok = logger_disk_log_h:reset(?MODULE).
-info_and_reset(cleanup,_Config) ->
- logger:remove_handler(?MODULE).
+ logger_disk_log_h:filesync("BadType").
reconfig(Config) ->
Dir = ?config(priv_dir,Config),
@@ -475,7 +463,7 @@ reconfig(Config) ->
#{filter_default=>log,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{id := ?MODULE,
+ #{%id := ?MODULE,
sync_mode_qlen := ?SYNC_MODE_QLEN,
drop_mode_qlen := ?DROP_MODE_QLEN,
flush_qlen := ?FLUSH_QLEN,
@@ -486,12 +474,14 @@ reconfig(Config) ->
overload_kill_qlen := ?OVERLOAD_KILL_QLEN,
overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE,
overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL,
- log_opts := #{type := ?DISK_LOG_TYPE,
- max_no_files := ?DISK_LOG_MAX_NO_FILES,
- max_no_bytes := ?DISK_LOG_MAX_NO_BYTES,
- file := DiskLogFile}} =
- logger_disk_log_h:info(?MODULE),
+ cb_state :=
+ #{handler_state :=
+ #{log_opts := #{type := ?DISK_LOG_TYPE,
+ max_no_files := ?DISK_LOG_MAX_NO_FILES,
+ max_no_bytes := ?DISK_LOG_MAX_NO_BYTES,
+ file := DiskLogFile}},
+ filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL}} =
+ logger_olp:info(h_proc_name()),
{ok,#{config :=
#{sync_mode_qlen := ?SYNC_MODE_QLEN,
drop_mode_qlen := ?DROP_MODE_QLEN,
@@ -522,7 +512,7 @@ reconfig(Config) ->
overload_kill_restart_after => infinity,
filesync_repeat_interval => no_repeat},
ok = logger:set_handler_config(?MODULE, config, HConfig1),
- #{id := ?MODULE,
+ #{%id := ?MODULE,
sync_mode_qlen := 1,
drop_mode_qlen := 2,
flush_qlen := 3,
@@ -533,8 +523,8 @@ reconfig(Config) ->
overload_kill_qlen := 100000,
overload_kill_mem_size := 10000000,
overload_kill_restart_after := infinity,
- filesync_repeat_interval := no_repeat} =
- logger_disk_log_h:info(?MODULE),
+ cb_state := #{filesync_repeat_interval := no_repeat}} =
+ logger_olp:info(h_proc_name()),
{ok,#{config:=HConfig1}} = logger:get_handler_config(?MODULE),
ok = logger:update_handler_config(?MODULE, config,
@@ -572,11 +562,13 @@ reconfig(Config) ->
max_no_files => 1,
max_no_bytes => 1024,
file => File}}),
- #{log_opts := #{type := halt,
- max_no_files := 1,
- max_no_bytes := 1024,
- file := File}} =
- logger_disk_log_h:info(?MODULE),
+ #{cb_state :=
+ #{handler_state :=
+ #{log_opts := #{type := halt,
+ max_no_files := 1,
+ max_no_bytes := 1024,
+ file := File}}}} =
+ logger_olp:info(h_proc_name()),
{ok,#{config :=
#{type := halt,
max_no_files := 1,
@@ -596,13 +588,13 @@ reconfig(Config) ->
%% You are not allowed to actively set the write once fields
%% (type, max_no_files, max_no_bytes, file) in runtime.
- {error, {illegal_config_change,_,_}} =
+ {error, {illegal_config_change,_,_,_}} =
logger:set_handler_config(?MODULE,config,#{type=>wrap}),
- {error, {illegal_config_change,_,_}} =
+ {error, {illegal_config_change,_,_,_}} =
logger:set_handler_config(?MODULE,config,#{max_no_files=>2}),
- {error, {illegal_config_change,_,_}} =
+ {error, {illegal_config_change,_,_,_}} =
logger:set_handler_config(?MODULE,config,#{max_no_bytes=>2048}),
- {error, {illegal_config_change,_,_}} =
+ {error, {illegal_config_change,_,_,_}} =
logger:set_handler_config(?MODULE,config,#{file=>"otherfile.log"}),
{ok,C7} = logger:get_handler_config(?MODULE),
ct:log("C7: ~p",[C7]),
@@ -630,56 +622,52 @@ sync(Config) ->
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,nl}}),
- start_tracer([{disk_log,blog,2},
- {logger_disk_log_h,disk_log_sync,2}],
- [{disk_log,blog,<<"first\n">>},
- {logger_disk_log_h,disk_log_sync}]),
+ start_tracer([{logger_disk_log_h,disk_log_write,3},
+ {disk_log,sync,1}],
+ [{logger_disk_log_h,disk_log_write,<<"first\n">>},
+ {disk_log,sync}]),
logger:notice("first", ?domain),
%% wait for automatic disk_log_sync
check_tracer(?FILESYNC_REPEAT_INTERVAL*2),
- %% check that if there's no repeated disk_log_sync active,
+ %% check that if there's no repeated filesync active,
%% a disk_log_sync is still performed when handler goes idle
{ok,#{config := HConfig}} = logger:get_handler_config(?MODULE),
HConfig1 = HConfig#{filesync_repeat_interval => no_repeat},
ok = logger:update_handler_config(?MODULE, config, HConfig1),
-
no_repeat = maps:get(filesync_repeat_interval,
- logger_disk_log_h:info(?MODULE)),
- %% The following timer is to make sure the time from last log
- %% ("first") to next ("second") is long enough, so the a flush is
- %% triggered by the idle timeout between "fourth" and "fifth".
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
-
- start_tracer([{disk_log,blog,2},
- {logger_disk_log_h,disk_log_sync,2}],
- [{disk_log,blog,<<"second\n">>},
- {logger_disk_log_h,disk_log_sync},
- {disk_log,blog,<<"third\n">>},
- {logger_disk_log_h,disk_log_sync}]),
+ maps:get(cb_state,logger_olp:info(h_proc_name()))),
+
+ start_tracer([{logger_disk_log_h,disk_log_write,3},
+ {disk_log,sync,1}],
+ [{logger_disk_log_h,disk_log_write,<<"second\n">>},
+ {disk_log,sync},
+ {logger_disk_log_h,disk_log_write,<<"third\n">>},
+ {disk_log,sync}]),
logger:notice("second", ?domain),
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ timer:sleep(?IDLE_DETECT_TIME*2),
logger:notice("third", ?domain),
%% wait for automatic disk_log_sync
- check_tracer(?IDLE_DETECT_TIME_MSEC*2),
+ check_tracer(?IDLE_DETECT_TIME*2),
try_read_file(Log, {ok,<<"first\nsecond\nthird\n">>}, 1000),
- %% switch repeated disk_log_sync on and verify that the looping works
+ %% switch repeated filesync on and verify that the looping works
SyncInt = 1000,
WaitT = 4500,
- OneSync = {logger_disk_log_h,handle_cast,repeated_disk_log_sync},
- %% receive 1 initial repeated_disk_log_sync, then 1 per sec
- start_tracer([{logger_disk_log_h,handle_cast,2}],
- [OneSync || _ <- lists:seq(1, 1 + trunc(WaitT/SyncInt))]),
+ OneSync = {logger_h_common,handle_cast,repeated_filesync},
+ %% receive 1 repeated_filesync per sec
+ start_tracer([{{logger_h_common,handle_cast,2},
+ [{[repeated_filesync,'_'],[],[{message,{caller}}]}]}],
+ [OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]),
HConfig2 = HConfig#{filesync_repeat_interval => SyncInt},
ok = logger:update_handler_config(?MODULE, config, HConfig2),
SyncInt = maps:get(filesync_repeat_interval,
- logger_disk_log_h:info(?MODULE)),
+ maps:get(cb_state,logger_olp:info(h_proc_name()))),
timer:sleep(WaitT),
HConfig3 = HConfig#{filesync_repeat_interval => no_repeat},
ok = logger:update_handler_config(?MODULE, config, HConfig3),
@@ -716,7 +704,7 @@ disk_log_wrap(Config) ->
end,
{ok,_} = dbg:tracer(process, {TraceFun, Tester}),
{ok,_} = dbg:p(whereis(h_proc_name()), [c]),
- {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []),
+ {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []),
Text = [34 + rand:uniform(126-34) || _ <- lists:seq(1,MaxBytes)],
ct:pal("String = ~p (~w)", [Text, erts_debug:size(Text)]),
@@ -734,7 +722,7 @@ disk_log_wrap(Config) ->
timer:sleep(1000),
dbg:stop_clear(),
Received = lists:flatmap(fun({trace,_M,handle_info,
- [{disk_log,_Node,_Name,What},_]}) ->
+ [_,{disk_log,_Node,_Name,What},_]}) ->
[{trace,What}];
({log,_}) ->
[]
@@ -770,7 +758,7 @@ disk_log_full(Config) ->
end,
{ok,_} = dbg:tracer(process, {TraceFun, Tester}),
{ok,_} = dbg:p(whereis(h_proc_name()), [c]),
- {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []),
+ {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []),
NoOfChars = 5,
Text = [34 + rand:uniform(126-34) || _ <- lists:seq(1,NoOfChars)],
@@ -780,20 +768,24 @@ disk_log_full(Config) ->
timer:sleep(2000),
dbg:stop_clear(),
Received = lists:flatmap(fun({trace,_M,handle_info,
- [{disk_log,_Node,_Name,What},_]}) ->
+ [_,{disk_log,_Node,_Name,What},_]}) ->
[{trace,What}];
({log,_}) ->
[]
end, test_server:messages_get()),
ct:pal("Trace =~n~p", [Received]),
- [{trace,full},
- {trace,{error_status,{error,{full,_}}}}] = Received,
+
+ %% The tail here could be an error_status notification, if the
+ %% last write was synchronous, but in most cases it will not be
+ [{trace,full}|_] = Received,
+ %% [{trace,full},
+ %% {trace,{error_status,{error,{full,_}}}}] = Received,
ok.
disk_log_full(cleanup, _Config) ->
dbg:stop_clear(),
logger:remove_handler(?MODULE).
-disk_log_events(Config) ->
+disk_log_events(_Config) ->
Node = node(),
Log = ?MODULE,
ok = logger:add_handler(?MODULE,
@@ -819,14 +811,14 @@ disk_log_events(Config) ->
end,
{ok,_} = dbg:tracer(process, {TraceFun, Tester}),
{ok,_} = dbg:p(whereis(h_proc_name()), [c]),
- {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []),
+ {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []),
[whereis(h_proc_name()) ! E || E <- Events],
%% wait for trace messages
timer:sleep(2000),
dbg:stop_clear(),
Received = lists:map(fun({trace,_M,handle_info,
- [Got,_]}) -> Got
+ [_,Got,_]}) -> Got
end, test_server:messages_get()),
ct:pal("Trace =~n~p", [Received]),
NoOfEvents = length(Events),
@@ -849,13 +841,17 @@ write_failure(Config) ->
false = (undefined == rpc:call(Node, ets, whereis, [?TEST_HOOKS_TAB])),
rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]),
rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]),
- rpc:call(Node, ?MODULE, set_result, [disk_log_blog,ok]),
- HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]),
- ct:pal("LogOpts = ~p", [LogOpts = maps:get(log_opts, HState)]),
-
+ rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]),
+ HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]),
+ LogOpts = maps:get(log_opts,
+ maps:get(handler_state,
+ maps:get(cb_state,HState))),
+ ct:pal("LogOpts = ~p", [LogOpts]),
+
+ %% ?check and ?check_no_log in this test only check for internal log events
ok = log_on_remote_node(Node, "Logged1"),
rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]),
- ?check_no_log,
+ ?check_no_log, % no internal log when write ok
SyncRepInt = case (fun() -> is_atom(?FILESYNC_REPEAT_INTERVAL) end)() of
true -> 5500;
@@ -864,24 +860,26 @@ write_failure(Config) ->
try_read_file(Log, {ok,<<"Logged1\n">>}, SyncRepInt),
- rpc:call(Node, ?MODULE, set_result, [disk_log_blog,{error,no_such_log}]),
+ rpc:call(Node, ?MODULE, set_result, [disk_log_write,{error,no_such_log}]),
ok = log_on_remote_node(Node, "Cause simple error printout"),
+ %% this should have caused an internal log
?check({error,{?STANDARD_HANDLER,log,LogOpts,{error,no_such_log}}}),
-
+
ok = log_on_remote_node(Node, "No second error printout"),
- ?check_no_log,
+ ?check_no_log, % but don't log same error twice
- rpc:call(Node, ?MODULE, set_result, [disk_log_blog,
+ rpc:call(Node, ?MODULE, set_result, [disk_log_write,
{error,{full,?STANDARD_HANDLER}}]),
ok = log_on_remote_node(Node, "Cause simple error printout"),
+ %% this was a different error, so it should be logged
?check({error,{?STANDARD_HANDLER,log,LogOpts,
{error,{full,?STANDARD_HANDLER}}}}),
- rpc:call(Node, ?MODULE, set_result, [disk_log_blog,ok]),
+ rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]),
ok = log_on_remote_node(Node, "Logged2"),
rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]),
- ?check_no_log,
+ ?check_no_log, % no internal log when write ok
try_read_file(Log, {ok,<<"Logged1\nLogged2\n">>}, SyncRepInt),
ok.
write_failure(cleanup, _Config) ->
@@ -900,15 +898,16 @@ sync_failure(Config) ->
rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]),
rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]),
rpc:call(Node, ?MODULE, set_result, [disk_log_sync,ok]),
- HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]),
- LogOpts = maps:get(log_opts, HState),
+ HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]),
+ LogOpts = maps:get(log_opts, maps:get(handler_state,
+ maps:get(cb_state,HState))),
SyncInt = 500,
ok = rpc:call(Node, logger, update_handler_config,
[?STANDARD_HANDLER, config,
#{filesync_repeat_interval => SyncInt}]),
- Info = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]),
- SyncInt = maps:get(filesync_repeat_interval, Info),
+ Info = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]),
+ SyncInt = maps:get(filesync_repeat_interval, maps:get(cb_state, Info)),
ok = log_on_remote_node(Node, "Logged1"),
?check_no_log,
@@ -1184,7 +1183,7 @@ qlen_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1194,7 +1193,7 @@ qlen_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_disk_log_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1221,7 +1220,7 @@ mem_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1231,7 +1230,7 @@ mem_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_disk_log_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1257,7 +1256,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info1 = logger_std_h:info(?MODULE),
+ Info1 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info1]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1281,7 +1280,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info2 = logger_std_h:info(?MODULE),
+ Info2 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info2]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1302,11 +1301,15 @@ handler_requests_under_load(Config) ->
flush_qlen => 2000,
burst_limit_enable => false}},
ok = logger:update_handler_config(?MODULE, NewHConfig),
- Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]},
- {info,[]},
- {reset,[]},
- {change_config,[]}])
- end),
+ Pid = spawn_link(
+ fun() -> send_requests(1,[{logger_disk_log_h,filesync,[?MODULE],[]},
+ {logger_olp,info,[h_proc_name()],[]},
+ {logger_olp,reset,[h_proc_name()],[]},
+ {logger,update_handler_config,
+ [?MODULE, config,
+ #{overload_kill_enable => false}],
+ []}])
+ end),
Procs = 100,
Sent = Procs * send_burst({n,5000}, {spawn,Procs,10}, {chars,79}, notice),
Pid ! {self(),finish},
@@ -1318,29 +1321,22 @@ handler_requests_under_load(Config) ->
[E || E <- Res,
is_tuple(E) andalso (element(1,E) == error)]
end,
- Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult],
- NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult),
+ Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult],
+ NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end,
+ 0, ReqResult),
ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]),
ok = file_delete(Log).
handler_requests_under_load(cleanup, _Config) ->
ok = stop_handler(?MODULE).
-send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) ->
+send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) ->
receive
{From,finish} ->
From ! {self(),Reqs}
after
TO ->
- Result =
- case Req of
- change_config ->
- logger:update_handler_config(HName, logger_disk_log_h,
- #{overload_kill_enable =>
- false});
- Func ->
- logger_disk_log_h:Func(HName)
- end,
- send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}])
+ Result = apply(Mod,Func,Args),
+ send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}])
end.
%%%-----------------------------------------------------------------
@@ -1458,15 +1454,6 @@ format(Msg,Tag) ->
erlang:display(Error),
exit(Error).
-remove(Handler, LogName) ->
- logger_disk_log_h:remove(Handler, LogName),
- HState = #{log_names := Logs} = logger_disk_log_h:info(),
- false = maps:is_key(LogName, HState),
- false = lists:member(LogName, Logs),
- false = logger_config:exist(?LOGGER_TABLE, LogName),
- {error,no_such_log} = disk_log:info(LogName),
- ok.
-
start_and_add(Name, Config, LogOpts) ->
HConfig = maps:get(config, Config, #{}),
HConfig1 = maps:merge(HConfig, LogOpts),
@@ -1593,7 +1580,9 @@ start_tracer(Trace,Expected) ->
ok.
tpl([{M,F,A}|Trace]) ->
- {ok,Match} = dbg:tpl(M,F,A,c),
+ tpl([{{M,F,A},c}|Trace]);
+tpl([{{M,F,A},MS}|Trace]) ->
+ {ok,Match} = dbg:tpl(M,F,A,MS),
case lists:keyfind(matched,1,Match) of
{_,_,1} ->
ok;
@@ -1606,10 +1595,10 @@ tpl([{M,F,A}|Trace]) ->
tpl([]) ->
ok.
-tracer({trace,_,call,{logger_disk_log_h,handle_cast,[Op|_]},Caller},
+tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]},Caller},
{Pid,[{Mod,Func,Op}|Expected]}) ->
maybe_tracer_done(Pid,Expected,{Mod,Func,Op},Caller);
-tracer({trace,_,call,{Mod=disk_log,Func=blog,[_,Data]},Caller}, {Pid,[{Mod,Func,Data}|Expected]}) ->
+tracer({trace,_,call,{Mod=logger_disk_log_h,Func=disk_log_write,[_,_,Data]},Caller}, {Pid,[{Mod,Func,Data}|Expected]}) ->
maybe_tracer_done(Pid,Expected,{Mod,Func,Data},Caller);
tracer({trace,_,call,{Mod,Func,_},Caller}, {Pid,[{Mod,Func}|Expected]}) ->
maybe_tracer_done(Pid,Expected,{Mod,Func},Caller);
diff --git a/lib/kernel/test/logger_env_var_SUITE.erl b/lib/kernel/test/logger_env_var_SUITE.erl
index e8d1a313dc..9d2ad11be8 100644
--- a/lib/kernel/test/logger_env_var_SUITE.erl
+++ b/lib/kernel/test/logger_env_var_SUITE.erl
@@ -59,7 +59,8 @@ groups() ->
logger_undefined,
logger_many_handlers_default_first,
logger_many_handlers_default_last,
- logger_many_handlers_default_last_broken_filter
+ logger_many_handlers_default_last_broken_filter,
+ logger_proxy
]},
{bad,[],[bad_error_logger,
bad_level,
@@ -541,6 +542,19 @@ logger_many_handlers(Config, Env, LogErr, LogInfo, NumProgress) ->
ok.
+logger_proxy(Config) ->
+ %% assume current node runs with default settings
+ DefOpts = logger_olp:get_opts(logger_proxy),
+ {ok,_,Node} = setup(Config,
+ [{logger,[{proxy,#{sync_mode_qlen=>0,
+ drop_mode_qlen=>2}}]}]),
+ Expected = DefOpts#{sync_mode_qlen:=0,
+ drop_mode_qlen:=2},
+ Expected = rpc:call(Node,logger_olp,get_opts,[logger_proxy]),
+ Expected = rpc:call(Node,logger,get_proxy_config,[]),
+
+ ok.
+
sasl_compatible_false(Config) ->
Log = file(Config,?FUNCTION_NAME),
{ok,_,Node} = setup(Config,
diff --git a/lib/kernel/test/logger_olp_SUITE.erl b/lib/kernel/test/logger_olp_SUITE.erl
new file mode 100644
index 0000000000..ea3eec89f5
--- /dev/null
+++ b/lib/kernel/test/logger_olp_SUITE.erl
@@ -0,0 +1,90 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+-module(logger_olp_SUITE).
+
+-compile(export_all).
+
+-include_lib("kernel/src/logger_olp.hrl").
+
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(Case, Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+groups() ->
+ [].
+
+all() ->
+ [idle_timer].
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+idle_timer(_Config) ->
+ {ok,_Pid,Olp} = logger_olp:start_link(?MODULE,?MODULE,self(),#{}),
+ [logger_olp:load(Olp,{msg,N}) || N<-lists:seq(1,3)],
+ timer:sleep(?IDLE_DETECT_TIME*2),
+ [{load,{msg,1}},
+ {load,{msg,2}},
+ {load,{msg,3}},
+ {notify,idle}] = test_server:messages_get(),
+ logger_olp:cast(Olp,hello),
+ timer:sleep(?IDLE_DETECT_TIME*2),
+ [{cast,hello}] = test_server:messages_get(),
+ ok.
+idle_timer(cleanup,_Config) ->
+ unlink(whereis(?MODULE)),
+ logger_olp:stop(?MODULE),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Olp callbacks
+init(P) ->
+ {ok,P}.
+
+handle_load(M,P) ->
+ P ! {load,M},
+ P.
+
+handle_cast(M,P) ->
+ P ! {cast,M},
+ {noreply,P}.
+
+notify(N,P) ->
+ P ! {notify,N},
+ P.
diff --git a/lib/kernel/test/logger_proxy_SUITE.erl b/lib/kernel/test/logger_proxy_SUITE.erl
new file mode 100644
index 0000000000..777531e4ed
--- /dev/null
+++ b/lib/kernel/test/logger_proxy_SUITE.erl
@@ -0,0 +1,274 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+-module(logger_proxy_SUITE).
+
+-compile(export_all).
+
+%% -include_lib("common_test/include/ct.hrl").
+%% -include_lib("kernel/include/logger.hrl").
+%% -include_lib("kernel/src/logger_internal.hrl").
+
+%% -define(str,"Log from "++atom_to_list(?FUNCTION_NAME)++
+%% ":"++integer_to_list(?LINE)).
+%% -define(map_rep,#{function=>?FUNCTION_NAME, line=>?LINE}).
+%% -define(keyval_rep,[{function,?FUNCTION_NAME}, {line,?LINE}]).
+
+%% -define(MY_LOC(N),#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY},
+%% file=>?FILE, line=>?LINE-N}).
+
+%% -define(TRY(X), my_try(fun() -> X end)).
+
+
+-define(HNAME,list_to_atom(lists:concat([?MODULE,"_",?FUNCTION_NAME]))).
+-define(LOC,#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY},line=>?LINE}).
+-define(ENSURE_TIME,5000).
+
+suite() ->
+ [{timetrap,{seconds,30}},
+ {ct_hooks,[logger_test_lib]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(Case, Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+groups() ->
+ [].
+
+all() ->
+ [basic,
+ emulator,
+ remote,
+ remote_emulator,
+ config,
+ restart_after,
+ terminate].
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+basic(_Config) ->
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ logger_proxy ! {log,notice,"Log from: ~p; ~p",[?FUNCTION_NAME,?LINE],L1=?LOC},
+ ok = ensure(L1),
+ logger_proxy ! {log,notice,[{test_case,?FUNCTION_NAME},{line,?LINE}],L2=?LOC},
+ ok = ensure(L2),
+ logger_proxy:log({remote,node(),{log,notice,
+ "Log from: ~p; ~p",
+ [?FUNCTION_NAME,?LINE],
+ L3=?LOC}}),
+ ok = ensure(L3),
+ logger_proxy:log({remote,node(),{log,notice,
+ [{test_case,?FUNCTION_NAME},
+ {line,?LINE}],
+ L4=?LOC}}),
+ ok = ensure(L4),
+ ok.
+basic(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+emulator(_Config) ->
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ Pid = spawn(fun() -> erlang:error(some_reason) end),
+ ok = ensure(#{pid=>Pid}),
+ ok.
+emulator(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+remote(Config) ->
+ {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]),
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ L1 = ?LOC, spawn(Node,fun() -> logger:notice("Log from ~p; ~p",[?FUNCTION_NAME,?LINE],L1) end),
+ ok = ensure(L1),
+ L2 = ?LOC, spawn(Node,fun() -> logger:notice([{test_case,?FUNCTION_NAME},{line,?LINE}],L2) end),
+ ok = ensure(L2),
+ ok.
+remote(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+remote_emulator(Config) ->
+ {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]),
+ ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}),
+ Pid = spawn(Node,fun() -> erlang:error(some_reason) end),
+ ok = ensure(#{pid=>Pid}),
+ ok.
+remote_emulator(cleanup,_Config) ->
+ ok = logger:remove_handler(?HNAME).
+
+config(_Config) ->
+ C1 = #{sync_mode_qlen:=SQ,
+ drop_mode_qlen:=DQ} = logger:get_proxy_config(),
+ C1 = logger_olp:get_opts(logger_proxy),
+
+ %% Update the existing config with these two values
+ SQ1 = SQ+1,
+ DQ1 = DQ+1,
+ ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ1,
+ drop_mode_qlen=>DQ1}),
+ C2 = logger:get_proxy_config(), % reads from ets table
+ C2 = logger_olp:get_opts(logger_proxy), % ensure consistency with process opts
+ C2 = C1#{sync_mode_qlen:=SQ1,
+ drop_mode_qlen:=DQ1},
+
+ %% Update the existing again with only one value
+ SQ2 = SQ+2,
+ ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ2}),
+ C3 = logger:get_proxy_config(),
+ C3 = logger_olp:get_opts(logger_proxy),
+ C3 = C2#{sync_mode_qlen:=SQ2},
+
+ %% Set the config, i.e. merge with defaults
+ ok = logger:set_proxy_config(#{sync_mode_qlen=>SQ1}),
+ C4 = logger:get_proxy_config(),
+ C4 = logger_olp:get_opts(logger_proxy),
+ C4 = C1#{sync_mode_qlen:=SQ1},
+
+ %% Reset to default
+ ok = logger:set_proxy_config(#{}),
+ C5 = logger:get_proxy_config(),
+ C5 = logger_olp:get_opts(logger_proxy),
+ C5 = logger_proxy:get_default_config(),
+
+ %% Errors
+ {error,{invalid_olp_config,_}} =
+ logger:set_proxy_config(#{faulty_key=>1}),
+ {error,{invalid_olp_config,_}} =
+ logger:set_proxy_config(#{sync_mode_qlen=>infinity}),
+ {error,{invalid_config,[]}} = logger:set_proxy_config([]),
+
+ {error,{invalid_olp_config,_}} =
+ logger:update_proxy_config(#{faulty_key=>1}),
+ {error,{invalid_olp_config,_}} =
+ logger:update_proxy_config(#{sync_mode_qlen=>infinity}),
+ {error,{invalid_config,[]}} = logger:update_proxy_config([]),
+
+ C5 = logger:get_proxy_config(),
+ C5 = logger_olp:get_opts(logger_proxy),
+
+ ok.
+config(cleanup,_Config) ->
+ _ = logger:set_logger_proxy(logger_proxy:get_default_config()),
+ ok.
+
+restart_after(_Config) ->
+ Restart = 3000,
+ ok = logger:update_proxy_config(#{overload_kill_enable => true,
+ overload_kill_qlen => 10,
+ overload_kill_restart_after => Restart}),
+ Proxy = whereis(logger_proxy),
+ Proxy = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ Ref = erlang:monitor(process,Proxy),
+ spawn(fun() ->
+ [logger_proxy ! {log,debug,
+ [{test_case,?FUNCTION_NAME},
+ {line,?LINE}],
+ ?LOC} || _ <- lists:seq(1,100)]
+ end),
+ receive
+ {'DOWN',Ref,_,_,_Reason} ->
+ undefined = erlang:system_info(system_logger),
+ timer:sleep(Restart),
+ poll_restarted(10)
+ after 5000 ->
+ ct:fail(proxy_not_terminated)
+ end,
+
+ Proxy1 = whereis(logger_proxy),
+ Proxy1 = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ ok.
+restart_after(cleanup,_Config) ->
+ _ = logger:set_logger_proxy(logger_proxy:get_default_config()),
+ ok.
+
+%% Test that system_logger flag is set to logger process if
+%% logger_proxy terminates for other reason than overloaded.
+terminate(_Config) ->
+ Logger = whereis(logger),
+ Proxy = whereis(logger_proxy),
+ Proxy = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ Ref = erlang:monitor(process,Proxy),
+ ok = logger_olp:stop(Proxy),
+ receive
+ {'DOWN',Ref,_,_,_Reason} ->
+ Logger = erlang:system_info(system_logger),
+ logger_proxy:restart(),
+ poll_restarted(10)
+ after 5000 ->
+ ct:fail(proxy_not_terminated)
+ end,
+
+ Proxy1 = whereis(logger_proxy),
+ Proxy1 = erlang:system_info(system_logger),
+ ProxyConfig = logger:get_proxy_config(),
+ ProxyConfig = logger_olp:get_opts(logger_proxy),
+
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+
+poll_restarted(0) ->
+ ct:fail(proxy_not_restarted);
+poll_restarted(N) ->
+ timer:sleep(1000),
+ case whereis(logger_proxy) of
+ undefined ->
+ poll_restarted(N-1);
+ _Pid ->
+ ok
+ end.
+
+%% Logger handler callback
+log(#{meta:=Meta},#{config:=Pid}) ->
+ Pid ! {logged,Meta}.
+
+%% Check that the log from the logger callback function log/2 is received
+ensure(Match) ->
+ receive {logged,Meta} ->
+ case maps:with(maps:keys(Match),Meta) of
+ Match -> ok;
+ _NoMatch -> {error,Match,Meta,test_server:messages_get()}
+ end
+ after ?ENSURE_TIME -> {error,Match,test_server:messages_get()}
+ end.
diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl
index b6a09f4980..484d914ec3 100644
--- a/lib/kernel/test/logger_std_h_SUITE.erl
+++ b/lib/kernel/test/logger_std_h_SUITE.erl
@@ -25,10 +25,15 @@
-include_lib("kernel/include/logger.hrl").
-include_lib("kernel/src/logger_internal.hrl").
-include_lib("kernel/src/logger_h_common.hrl").
+-include_lib("kernel/src/logger_olp.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-include_lib("kernel/include/file.hrl").
--define(check_no_log, [] = test_server:messages_get()).
+-define(check_no_log,
+ begin
+ timer:sleep(?IDLE_DETECT_TIME*2),
+ [] = test_server:messages_get()
+ end).
-define(check(Expected),
receive
{log,Expected} ->
@@ -115,7 +120,6 @@ all() ->
crash_std_h_to_file,
crash_std_h_to_disk_log,
bad_input,
- info_and_reset,
reconfig,
file_opts,
sync,
@@ -136,11 +140,12 @@ all() ->
mem_kill_new,
mem_kill_std,
restart_after,
- handler_requests_under_load
+ handler_requests_under_load,
+ recreate_deleted_log
].
add_remove_instance_tty(_Config) ->
- {error,{handler_not_added,{invalid_config,logger_std_h,{type,tty}}}} =
+ {error,{handler_not_added,{invalid_config,logger_std_h,#{type:=tty}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{type => tty},
filter_default=>log,
@@ -208,9 +213,9 @@ default_formatter(_Config) ->
filter_config(_Config) ->
ok = logger:add_handler(?MODULE,logger_std_h,#{}),
{ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE),
- HConfig = maps:without([handler_pid,mode_tab],HConfig),
+ HConfig = maps:without([olp],HConfig),
- FakeFullHConfig = HConfig#{handler_pid=>self(),mode_tab=>erlang:make_ref()},
+ FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}},
#{config:=HConfig} =
logger_std_h:filter_config(Config#{config=>FakeFullHConfig}),
ok.
@@ -234,7 +239,7 @@ errors(Config) ->
{error,
{handler_not_added,
- {invalid_config,logger_std_h,{type,faulty_type}}}} =
+ {invalid_config,logger_std_h,#{type:=faulty_type}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{type => faulty_type}}),
@@ -245,13 +250,13 @@ errors(Config) ->
_ ->
NoDir = lists:concat(["/",?MODULE,"_dir"]),
{error,
- {handler_not_added,{{open_failed,NoDir,eacces},_}}} =
+ {handler_not_added,{open_failed,NoDir,eacces}}} =
logger:add_handler(myh2,logger_std_h,
#{config=>#{type=>{file,NoDir}}})
end,
{error,
- {handler_not_added,{{open_failed,Log,_},_}}} =
+ {handler_not_added,{open_failed,Log,_}}} =
logger:add_handler(myh3,logger_std_h,
#{config=>#{type=>{file,Log,[bad_file_opt]}}}),
@@ -308,25 +313,27 @@ formatter_fail(cleanup,_Config) ->
logger:remove_handler(?MODULE).
config_fail(_Config) ->
- {error,{handler_not_added,{invalid_config,logger_std_h,{bad,bad}}}} =
+ {error,{handler_not_added,{invalid_config,logger_std_h,#{bad:=bad}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{bad => bad},
filter_default=>log,
formatter=>{?MODULE,self()}}),
{error,{handler_not_added,{invalid_config,logger_std_h,
- {restart_type,bad}}}} =
+ #{restart_type:=bad}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{restart_type => bad},
filter_default=>log,
formatter=>{?MODULE,self()}}),
- {error,{handler_not_added,{invalid_levels,{_,1,_}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{drop_mode_qlen=>1}}),
- {error,{handler_not_added,{invalid_levels,{43,42,_}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43,
+ drop_mode_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{sync_mode_qlen=>43,
drop_mode_qlen=>42}}),
- {error,{handler_not_added,{invalid_levels,{_,43,42}}}} =
+ {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43,
+ flush_qlen:=42}}}} =
logger:add_handler(?MODULE,logger_std_h,
#{config => #{drop_mode_qlen=>43,
flush_qlen=>42}}),
@@ -334,24 +341,22 @@ config_fail(_Config) ->
ok = logger:add_handler(?MODULE,logger_std_h,
#{filter_default=>log,
formatter=>{?MODULE,self()}}),
- {error,{illegal_config_change,#{config:=#{type:=_}},#{config:=#{type:=_}}}} =
+ {error,{illegal_config_change,logger_std_h,#{type:=_},#{type:=_}}} =
logger:set_handler_config(?MODULE,config,
#{type=>{file,"file"}}),
- {error,{invalid_levels,_}} =
+ {error,{invalid_olp_levels,_}} =
logger:set_handler_config(?MODULE,config,
#{sync_mode_qlen=>100,
flush_qlen=>99}),
- {error,{invalid_config,logger_std_h,{filesync_rep_int,2000}}} =
+ {error,{invalid_config,logger_std_h,#{filesync_rep_int:=2000}}} =
logger:set_handler_config(?MODULE, config,
#{filesync_rep_int => 2000}),
%% Read-only fields may (accidentially) be included in the change,
%% but it won't take effect
{ok,C} = logger:get_handler_config(?MODULE),
- ok = logger:set_handler_config(?MODULE,config,
- #{handler_pid=>self(),
- mode_tab=>erlang:make_ref()}),
+ ok = logger:set_handler_config(?MODULE,config,#{olp=>dummyvalue}),
{ok,C} = logger:get_handler_config(?MODULE),
ok.
@@ -419,10 +424,13 @@ crash_std_h(Config,Func,Var,Type,Log) ->
%% logger would send the log event to the logger process here instead
%% of logging it itself.
log_on_remote_node(Node,Msg) ->
+ Pid = self(),
_ = spawn_link(Node,
fun() -> erlang:group_leader(whereis(user),self()),
- logger:notice(Msg)
+ logger:notice(Msg),
+ Pid ! done
end),
+ receive done -> ok end,
ok.
@@ -450,14 +458,7 @@ sync_and_read(Node,file,Log) ->
end.
bad_input(_Config) ->
- {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType"),
- {error,{badarg,{info,["BadType"]}}} = logger_std_h:info("BadType"),
- {error,{badarg,{reset,["BadType"]}}} = logger_std_h:reset("BadType").
-
-
-info_and_reset(_Config) ->
- #{id := ?STANDARD_HANDLER} = logger_std_h:info(?STANDARD_HANDLER),
- ok = logger_std_h:reset(?STANDARD_HANDLER).
+ {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType").
reconfig(Config) ->
Dir = ?config(priv_dir,Config),
@@ -467,9 +468,10 @@ reconfig(Config) ->
filter_default=>log,
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{id := ?MODULE,
- type := standard_io,
- file_ctrl_pid := FileCtrlPid,
+ #{%id := ?MODULE,
+ cb_state:=#{handler_state := #{type := standard_io,
+ file_ctrl_pid := FileCtrlPid},
+ filesync_repeat_interval := no_repeat},
sync_mode_qlen := ?SYNC_MODE_QLEN,
drop_mode_qlen := ?DROP_MODE_QLEN,
flush_qlen := ?FLUSH_QLEN,
@@ -479,9 +481,8 @@ reconfig(Config) ->
overload_kill_enable := ?OVERLOAD_KILL_ENABLE,
overload_kill_qlen := ?OVERLOAD_KILL_QLEN,
overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE,
- overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL} = DefaultInfo =
- logger_std_h:info(?MODULE),
+ overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER} =
+ logger_olp:info(h_proc_name()),
{ok,
#{config:=
@@ -496,7 +497,7 @@ reconfig(Config) ->
overload_kill_qlen := ?OVERLOAD_KILL_QLEN,
overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE,
overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER,
- filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL} =
+ filesync_repeat_interval := no_repeat} =
DefaultHConf}}
= logger:get_handler_config(?MODULE),
@@ -511,10 +512,11 @@ reconfig(Config) ->
overload_kill_qlen => 100000,
overload_kill_mem_size => 10000000,
overload_kill_restart_after => infinity,
- filesync_repeat_interval => no_repeat}),
- #{id := ?MODULE,
- type := standard_io,
- file_ctrl_pid := FileCtrlPid,
+ filesync_repeat_interval => 5000}),
+ #{%id := ?MODULE,
+ cb_state := #{handler_state := #{type := standard_io,
+ file_ctrl_pid := FileCtrlPid},
+ filesync_repeat_interval := no_repeat},
sync_mode_qlen := 1,
drop_mode_qlen := 2,
flush_qlen := 3,
@@ -524,8 +526,7 @@ reconfig(Config) ->
overload_kill_enable := true,
overload_kill_qlen := 100000,
overload_kill_mem_size := 10000000,
- overload_kill_restart_after := infinity,
- filesync_repeat_interval := no_repeat} = Info = logger_std_h:info(?MODULE),
+ overload_kill_restart_after := infinity} = logger_olp:info(h_proc_name()),
{ok,#{config :=
#{type := standard_io,
@@ -584,7 +585,7 @@ reconfig(Config) ->
%% You are not allowed to actively set 'type' in runtime, since
%% this is a write once field.
- {error, {illegal_config_change,_,_}} =
+ {error, {illegal_config_change,logger_std_h,_,_}} =
logger:set_handler_config(?MODULE,config,#{type=>standard_io}),
{ok,#{config:=C6}} = logger:get_handler_config(?MODULE),
ct:log("C6: ~p",[C6]),
@@ -607,7 +608,7 @@ file_opts(Config) ->
Log = filename:join(Dir, lists:concat([?FUNCTION_NAME,".log"])),
BadFileOpts = [raw],
BadType = {file,Log,BadFileOpts},
- {error,{handler_not_added,{{open_failed,Log,enoent},_}}} =
+ {error,{handler_not_added,{open_failed,Log,enoent}}} =
logger:add_handler(?MODULE, logger_std_h,
#{config => #{type => BadType}}),
@@ -620,7 +621,9 @@ file_opts(Config) ->
filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]),
formatter=>{?MODULE,self()}}),
- #{type := OkType} = logger_std_h:info(?MODULE),
+ #{cb_state := #{handler_state := #{type := OkType}}} =
+ logger_olp:info(h_proc_name()),
+ {ok,#{config := #{type := OkType}}} = logger:get_handler_config(?MODULE),
logger:notice(M1=?msg,?domain),
?check(M1),
B1 = ?bin(M1),
@@ -643,10 +646,8 @@ sync(Config) ->
%% check repeated filesync happens
start_tracer([{logger_std_h, write_to_dev, 5},
- {logger_std_h, sync_dev, 4},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"first\n">>},
- {logger_std_h, sync_dev},
{file,datasync}]),
logger:notice("first", ?domain),
@@ -655,10 +656,8 @@ sync(Config) ->
%% check that explicit filesync is only done once
start_tracer([{logger_std_h, write_to_dev, 5},
- {logger_std_h, sync_dev, 4},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"second\n">>},
- {logger_std_h, sync_dev},
{file,datasync},
{no_more,500}
]),
@@ -673,38 +672,34 @@ sync(Config) ->
%% a filesync is still performed when handler goes idle
ok = logger:update_handler_config(?MODULE, config,
#{filesync_repeat_interval => no_repeat}),
- no_repeat = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)),
- %% The following timer is to make sure the time from last log
- %% ("second") to next ("third") is long enough, so the a flush is
- %% triggered by the idle timeout between "thrid" and "fourth".
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ no_repeat = maps:get(filesync_repeat_interval,
+ maps:get(cb_state, logger_olp:info(h_proc_name()))),
start_tracer([{logger_std_h, write_to_dev, 5},
- {logger_std_h, sync_dev, 4},
{file, datasync, 1}],
[{logger_std_h, write_to_dev, <<"third\n">>},
- {logger_std_h, sync_dev},
{file,datasync},
{logger_std_h, write_to_dev, <<"fourth\n">>},
- {logger_std_h, sync_dev},
{file,datasync}]),
logger:notice("third", ?domain),
%% wait for automatic filesync
- timer:sleep(?IDLE_DETECT_TIME_MSEC*2),
+ timer:sleep(?IDLE_DETECT_TIME*2),
logger:notice("fourth", ?domain),
%% wait for automatic filesync
- check_tracer(?IDLE_DETECT_TIME_MSEC*2),
+ check_tracer(?IDLE_DETECT_TIME*2),
%% switch repeated filesync on and verify that the looping works
SyncInt = 1000,
WaitT = 4500,
- OneSync = {logger_std_h,handle_cast,repeated_filesync},
- %% receive 1 initial repeated_filesync, then 1 per sec
- start_tracer([{logger_std_h,handle_cast,2}],
- [OneSync || _ <- lists:seq(1, 1 + trunc(WaitT/SyncInt))]),
+ OneSync = {logger_h_common,handle_cast,repeated_filesync},
+ %% receive 1 repeated_filesync per sec
+ start_tracer([{{logger_h_common,handle_cast,2},
+ [{[repeated_filesync,'_'],[],[]}]}],
+ [OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]),
ok = logger:update_handler_config(?MODULE, config,
#{filesync_repeat_interval => SyncInt}),
- SyncInt = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)),
+ SyncInt = maps:get(filesync_repeat_interval,
+ maps:get(cb_state,logger_olp:info(h_proc_name()))),
timer:sleep(WaitT),
ok = logger:update_handler_config(?MODULE, config,
#{filesync_repeat_interval => no_repeat}),
@@ -765,8 +760,6 @@ sync_failure(Config) ->
ok = rpc:call(Node, logger, update_handler_config,
[?STANDARD_HANDLER, config,
#{filesync_repeat_interval => SyncInt}]),
- Info = rpc:call(Node, logger_std_h, info, [?STANDARD_HANDLER]),
- SyncInt = maps:get(filesync_repeat_interval, Info),
ok = log_on_remote_node(Node, "Logged1"),
?check_no_log,
@@ -1096,7 +1089,7 @@ qlen_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1106,7 +1099,7 @@ qlen_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_std_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1147,7 +1140,7 @@ mem_kill_new(Config) ->
receive
{'DOWN', MRef, _, _, Info} ->
case Info of
- {shutdown,{overloaded,?MODULE,QLen,Mem}} ->
+ {shutdown,{overloaded,QLen,Mem}} ->
ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]);
killed ->
ct:pal("Slow shutdown, handler process was killed!", [])
@@ -1157,7 +1150,7 @@ mem_kill_new(Config) ->
ok
after
5000 ->
- Info = logger_std_h:info(?MODULE),
+ Info = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info]),
ct:fail("Handler not dead! It should not have survived this!")
end.
@@ -1188,7 +1181,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info1 = logger_std_h:info(?MODULE),
+ Info1 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info1]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1213,7 +1206,7 @@ restart_after(Config) ->
ok
after
5000 ->
- Info2 = logger_std_h:info(?MODULE),
+ Info2 = logger_olp:info(h_proc_name()),
ct:pal("Handler state = ~p", [Info2]),
ct:fail("Handler not dead! It should not have survived this!")
end,
@@ -1235,11 +1228,15 @@ handler_requests_under_load(Config) ->
flush_qlen => 2000,
burst_limit_enable => false}},
ok = logger:update_handler_config(?MODULE, NewHConfig),
- Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]},
- {info,[]},
- {reset,[]},
- {change_config,[]}])
- end),
+ Pid = spawn_link(
+ fun() -> send_requests(1,[{logger_std_h,filesync,[?MODULE],[]},
+ {logger_olp,info,[h_proc_name()],[]},
+ {logger_olp,reset,[h_proc_name()],[]},
+ {logger,update_handler_config,
+ [?MODULE, config,
+ #{overload_kill_enable => false}],
+ []}])
+ end),
Sent = send_burst({t,10000}, seq, {chars,79}, notice),
Pid ! {self(),finish},
ReqResult = receive {Pid,Result} -> Result end,
@@ -1250,36 +1247,45 @@ handler_requests_under_load(Config) ->
[E || E <- Res,
is_tuple(E) andalso (element(1,E) == error)]
end,
- Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult],
- NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult),
+ Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult],
+ NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end,
+ 0, ReqResult),
ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]),
ok = file_delete(Log).
handler_requests_under_load(cleanup, _Config) ->
ok = stop_handler(?MODULE).
-send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) ->
+recreate_deleted_log(Config) ->
+ {Log,_HConfig,_StdHConfig} =
+ start_handler(?MODULE, ?FUNCTION_NAME, Config),
+ logger:notice("first",?domain),
+ logger_std_h:filesync(?MODULE),
+ ok = file:rename(Log,Log++".old"),
+ logger:notice("second",?domain),
+ logger_std_h:filesync(?MODULE),
+ {ok,<<"first\n">>} = file:read_file(Log++".old"),
+ {ok,<<"second\n">>} = file:read_file(Log),
+ ok.
+recreate_deleted_log(cleanup, _Config) ->
+ ok = stop_handler(?MODULE).
+
+%%%-----------------------------------------------------------------
+%%%
+send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) ->
receive
{From,finish} ->
From ! {self(),Reqs}
after
TO ->
- Result =
- case Req of
- change_config ->
- logger:update_handler_config(HName, config,
- #{overload_kill_enable =>
- false});
- Func ->
- logger_std_h:Func(HName)
- end,
- send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}])
+ Result = apply(Mod,Func,Args),
+ send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}])
end.
%%%-----------------------------------------------------------------
%%%
-start_handler(Name, TTY, Config) when TTY == standard_io;
- TTY == standard_error->
+start_handler(Name, TTY, _Config) when TTY == standard_io;
+ TTY == standard_error->
ok = logger:add_handler(Name,
logger_std_h,
#{config => #{type => TTY},
@@ -1533,7 +1539,7 @@ start_op_trace() ->
{ok,_} = dbg:p(self(), [c]),
MS1 = dbg:fun2ms(fun([_]) -> return_trace() end),
- {ok,_} = dbg:tp(logger_h_common, check_load, 1, MS1),
+ {ok,_} = dbg:tpl(logger_h_common, check_load, 1, MS1),
{ok,_} = dbg:tpl(logger_h_common, flush_log_requests, 2, []),
@@ -1607,7 +1613,10 @@ analyse(Msgs) ->
start_tracer(Trace,Expected) ->
Pid = self(),
- FileCtrlPid = maps:get(file_ctrl_pid, logger_std_h:info(?MODULE)),
+ FileCtrlPid = maps:get(file_ctrl_pid,
+ maps:get(handler_state,
+ maps:get(cb_state,
+ logger_olp:info(h_proc_name())))),
dbg:tracer(process,{fun tracer/2,{Pid,Expected}}),
dbg:p(whereis(h_proc_name()),[c]),
dbg:p(FileCtrlPid,[c]),
@@ -1615,7 +1624,9 @@ start_tracer(Trace,Expected) ->
ok.
tpl([{M,F,A}|Trace]) ->
- {ok,Match} = dbg:tpl(M,F,A,[]),
+ tpl([{{M,F,A},[]}|Trace]);
+tpl([{{M,F,A},MS}|Trace]) ->
+ {ok,Match} = dbg:tpl(M,F,A,MS),
case lists:keyfind(matched,1,Match) of
{_,_,1} ->
ok;
@@ -1628,7 +1639,7 @@ tpl([{M,F,A}|Trace]) ->
tpl([]) ->
ok.
-tracer({trace,_,call,{logger_std_h,handle_cast,[Op|_]}},
+tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]}},
{Pid,[{Mod,Func,Op}|Expected]}) ->
maybe_tracer_done(Pid,Expected,{Mod,Func,Op});
tracer({trace,_,call,{Mod=logger_std_h,Func=write_to_dev,[_,Data,_,_,_]}},
diff --git a/lib/kernel/test/logger_stress_SUITE.erl b/lib/kernel/test/logger_stress_SUITE.erl
new file mode 100644
index 0000000000..4072e8c86a
--- /dev/null
+++ b/lib/kernel/test/logger_stress_SUITE.erl
@@ -0,0 +1,456 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+-module(logger_stress_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("kernel/include/logger.hrl").
+-include_lib("kernel/src/logger_h_common.hrl").
+
+-ifdef(SAVE_STATS).
+ -define(COLLECT_STATS(_All_,_Procs_),
+ ct:pal("~p",[stats(_All_,_Procs_)])).
+-else.
+ -define(COLLECT_STATS(_All_,_Procs__), ok).
+-endif.
+
+-define(TEST_DURATION,120). % seconds
+
+suite() ->
+ [{timetrap,{minutes,3}},
+ {ct_hooks,[logger_test_lib]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_Group, Config) ->
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(Case, Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+groups() ->
+ [].
+
+all() ->
+ [allow_events,
+ reject_events,
+ std_handler,
+ disk_log_handler,
+ emulator_events,
+ remote_events,
+ remote_to_disk_log,
+ remote_emulator_events,
+ remote_emulator_to_disk_log].
+
+%%%-----------------------------------------------------------------
+%%% Test cases
+%%%-----------------------------------------------------------------
+%% Time from log macro call to handler callback
+allow_events(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,?MODULE,#{}}]},
+ {logger_level,notice}]),
+ N = 100000,
+ {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end),
+ IOPS = N * 1000/T, % log events allowed per millisecond
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,IOPS}]}),
+ {comment,io_lib:format("~.2f accepted events pr millisecond",
+ [IOPS])}.
+
+%% Time from log macro call to reject (log level)
+reject_events(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,?MODULE,#{}}]},
+ {logger_level,error}]),
+ N = 1000000,
+ {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end),
+ IOPS = N * 1000/T, % log events rejected per millisecond
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,IOPS}]}),
+ {comment,io_lib:format("~.2f rejected events pr millisecond",
+ [IOPS])}.
+
+%% Cascading failure that produce gen_server and proc_lib reports -
+%% how many of the produced log events are actually written to a log
+%% with logger_std_h file handler.
+std_handler(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,logger_std_h,
+ #{config=>#{type=>{file,"default.log"}}}}]}]),
+
+ cascade({Node,{logger_backend,log_allowed,2},[]},
+ {Node,{logger_std_h,write,4},[{default,logger_std_h_default}]},
+ fun otp_cascading/0).
+std_handler(cleanup,_Config) ->
+ _ = file:delete("default.log"),
+ ok.
+
+%% Cascading failure that produce gen_server and proc_lib reports -
+%% how many of the produced log events are actually written to a log
+%% with logger_disk_log_h wrap file handler.
+disk_log_handler(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,logger_disk_log_h,#{}}]}]),
+ cascade({Node,{logger_backend,log_allowed,2},[]},
+ {Node,{logger_disk_log_h,write,4},
+ [{default,logger_disk_log_h_default}]},
+ fun otp_cascading/0).
+disk_log_handler(cleanup,_Config) ->
+ Files = filelib:wildcard("default.log.*"),
+ [_ = file:delete(F) || F <- Files],
+ ok.
+
+%% Cascading failure that produce log events from the emulator - how
+%% many of the produced log events pass through the proxy.
+emulator_events(Config) ->
+ {ok,_,Node} =
+ logger_test_lib:setup(Config,
+ [{logger,
+ [{handler,default,?MODULE,#{}}]}]),
+ cascade({Node,{?MODULE,producer,0},[]},
+ {Node,{?MODULE,log,2},[{proxy,logger_proxy}]},
+ fun em_cascading/0).
+
+%% Cascading failure that produce gen_server and proc_lib reports on
+%% remote node - how many of the produced log events pass through the
+%% proxy.
+remote_events(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,?MODULE,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]},
+ {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]},
+ fun otp_cascading/0).
+
+%% Cascading failure that produce gen_server and proc_lib reports on
+%% remote node - how many of the produced log events are actually
+%% written to a log with logger_disk_log_h wrap file handler.
+remote_to_disk_log(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,logger_disk_log_h,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]},
+ {Node1,{logger_disk_log_h,write,4},
+ [{local_proxy,logger_proxy},
+ {local_default,logger_disk_log_h_default}]},
+ fun otp_cascading/0).
+remote_to_disk_log(cleanup,_Config) ->
+ Files = filelib:wildcard("default.log.*"),
+ [_ = file:delete(F) || F <- Files],
+ ok.
+
+%% Cascading failure that produce log events from the emulator on
+%% remote node - how many of the produced log events pass through the
+%% proxy.
+remote_emulator_events(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,?MODULE,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]},
+ {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]},
+ fun em_cascading/0).
+
+%% Cascading failure that produce log events from the emulator on
+%% remote node - how many of the produced log events are actually
+%% written to a log with logger_disk_log_h wrap file handler.
+remote_emulator_to_disk_log(Config) ->
+ {ok,_,Node1} =
+ logger_test_lib:setup([{postfix,1}|Config],
+ [{logger,
+ [{handler,default,logger_disk_log_h,#{}}]}]),
+ {ok,_,Node2} =
+ logger_test_lib:setup([{postfix,2}|Config],[]),
+ cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]},
+ {Node1,{logger_disk_log_h,write,4},
+ [{local_proxy,logger_proxy},
+ {local_default,logger_disk_log_h_default}]},
+ fun em_cascading/0).
+remote_emulator_to_disk_log(cleanup,_Config) ->
+ Files = filelib:wildcard("default.log.*"),
+ [_ = file:delete(F) || F <- Files],
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Internal functions
+nlogs(N) ->
+ group_leader(whereis(user),self()),
+ Str = "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "[\\]^_`abcdefghijklmnopqr",
+ [?LOG_NOTICE(Str) || _ <- lists:seq(1,N)],
+ ok.
+
+%% cascade(ProducerInfo,ConsumerInfo,TestFun)
+cascade({PNode,PMFA,_PStatProcs},{CNode,CMFA,_CStatProcs},TestFun) ->
+ Tab = ets:new(counter,[set,public]),
+ ets:insert(Tab,{producer,0}),
+ ets:insert(Tab,{consumer,0}),
+ dbg:tracer(process,{fun tracer/2,{Tab,PNode,CNode}}),
+ dbg:n(PNode),
+ dbg:n(CNode),
+ dbg:cn(node()),
+ dbg:p(all,[call,arity]),
+ dbg:tpl(PMFA,[]),
+ dbg:tpl(CMFA,[]),
+
+ Pid = rpc:call(CNode,?MODULE,wrap_test,[PNode,TestFun]),
+ MRef = erlang:monitor(process,Pid),
+ TO = ?TEST_DURATION*1000,
+ receive {'DOWN',MRef,_,_,Reason} ->
+ ct:fail({remote_pid_down,Reason})
+ after TO ->
+ All = ets:lookup_element(Tab,producer,2),
+ Written = ets:lookup_element(Tab,consumer,2),
+ dbg:stop_clear(),
+ ?COLLECT_STATS(All,
+ [{PNode,P,Id} || {Id,P} <- _PStatProcs] ++
+ [{CNode,P,Id} || {Id,P} <- _CStatProcs]),
+ Ratio = Written/All * 100,
+ ct_event:notify(#event{name = benchmark_data,
+ data = [{value,Ratio}]}),
+ {comment,io_lib:format("~p % (~p written, ~p produced)",
+ [round(Ratio),Written,All])}
+ end.
+
+wrap_test(Fun) ->
+ wrap_test(node(),Fun).
+wrap_test(Node,Fun) ->
+ reset(),
+ group_leader(whereis(user),self()),
+ rpc:call(Node,?MODULE,do_fun,[Fun]).
+
+do_fun(Fun) ->
+ reset(),
+ Fun().
+
+reset() ->
+ reset([logger_std_h_default, logger_disk_log_h_default, logger_proxy]).
+reset([P|Ps]) ->
+ is_pid(whereis(P)) andalso logger_olp:reset(P),
+ reset(Ps);
+reset([]) ->
+ ok.
+
+
+tracer({trace,_,call,{?MODULE,producer,_}},{Tab,_PNode,_CNode}=S) ->
+ ets:update_counter(Tab,producer,1),
+ S;
+tracer({trace,Pid,call,{logger_backend,log_allowed,_}},{Tab,PNode,_CNode}=S) when node(Pid)=:=PNode ->
+ ets:update_counter(Tab,producer,1),
+ S;
+tracer({trace,_,call,{?MODULE,log,_}},{Tab,_PNode,_CNode}=S) ->
+ ets:update_counter(Tab,consumer,1),
+ S;
+tracer({trace,_,call,{_,write,_}},{Tab,_PNode,_CNode}=S) ->
+ ets:update_counter(Tab,consumer,1),
+ S;
+tracer(_,S) ->
+ S.
+
+
+%%%-----------------------------------------------------------------
+%%% Collect statistics
+-define(STAT_KEYS,
+ [burst_drops,
+ calls,
+ casts,
+ drops,
+ flushed,
+ flushes,
+ freq,
+ last_qlen,
+ max_qlen,
+ time,
+ writes]).
+-define(EVENT_KEYS,
+ [calls,casts,flushed]).
+
+stats(All,Procs) ->
+ NI = [{Id,rpc:call(N,logger_olp,info,[P])} || {N,P,Id}<-Procs],
+ [{all,All}|[stats(Id,I,All) || {Id,I} <- NI]].
+
+stats(Id,Info,All) ->
+ S = maps:with(?STAT_KEYS,Info),
+ AllOnProc = lists:sum(maps:values(maps:with(?EVENT_KEYS,S))),
+ if All>0 ->
+ Writes = maps:get(writes,S),
+ {_,ActiveTime} = maps:get(time,S),
+ Rate = round(100*Writes/All),
+ RateOnProc =
+ if AllOnProc>0 ->
+ round(100*Writes/AllOnProc);
+ true ->
+ 0
+ end,
+ AvFreq =
+ if ActiveTime>0 ->
+ round(Writes/ActiveTime);
+ true ->
+ 0
+ end,
+ {Id,
+ {stats,S},
+ {rate,Rate},
+ {rate_on_proc,RateOnProc},
+ {av_freq,AvFreq}};
+ true ->
+ {Id,none}
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Spawn a lot of processes that crash repeatedly, causing a lot of
+%%% error reports from the emulator.
+em_cascading() ->
+ spawn(fun() -> super() end).
+
+super() ->
+ process_flag(trap_exit,true),
+ spawn_link(fun server/0),
+ [spawn_link(fun client/0) || _<-lists:seq(1,10000)],
+ super_loop().
+
+super_loop() ->
+ receive
+ {'EXIT',_,server} ->
+ spawn_link(fun server/0),
+ super_loop();
+ {'EXIT',_,_} ->
+ _L = lists:sum(lists:seq(1,10000)),
+ spawn_link(fun client/0),
+ super_loop()
+ end.
+
+client() ->
+ receive
+ after 1 ->
+ case whereis(server) of
+ Pid when is_pid(Pid) ->
+ ok;
+ undefined ->
+ producer(),
+ erlang:error(some_exception)
+ end
+ end,
+ client().
+
+server() ->
+ register(server,self()),
+ receive
+ after 3000 ->
+ exit(server)
+ end.
+
+
+%%%-----------------------------------------------------------------
+%%% Create a supervisor tree with processes that crash repeatedly,
+%%% causing a lot of supervisor reports and crashreports
+otp_cascading() ->
+ {ok,Pid} = supervisor:start_link({local,otp_super}, ?MODULE, [otp_super]),
+ unlink(Pid),
+ Pid.
+
+otp_server_sup() ->
+ supervisor:start_link({local,otp_server_sup},?MODULE,[otp_server_sup]).
+
+otp_client_sup(N) ->
+ supervisor:start_link({local,otp_client_sup},?MODULE,[otp_client_sup,N]).
+
+otp_server() ->
+ gen_server:start_link({local,otp_server},?MODULE,[otp_server],[]).
+
+otp_client() ->
+ gen_server:start_link(?MODULE,[otp_client],[]).
+
+init([otp_super]) ->
+ {ok, {{one_for_one, 200, 10},
+ [{client_sup,
+ {?MODULE, otp_client_sup, [10000]},
+ permanent, 1000, supervisor, [?MODULE]},
+ {server_sup,
+ {?MODULE, otp_server_sup, []},
+ permanent, 1000, supervisor, [?MODULE]}
+ ]}};
+init([otp_server_sup]) ->
+ {ok, {{one_for_one, 2, 10},
+ [{server,
+ {?MODULE, otp_server, []},
+ permanent, 1000, worker, [?MODULE]}
+ ]}};
+init([otp_client_sup,N]) ->
+ spawn(fun() ->
+ [supervisor:start_child(otp_client_sup,[])
+ || _ <- lists:seq(1,N)]
+ end),
+ {ok, {{simple_one_for_one, N*10, 1},
+ [{client,
+ {?MODULE, otp_client, []},
+ permanent, 1000, worker, [?MODULE]}
+ ]}};
+init([otp_server]) ->
+ {ok, server, 10000};
+init([otp_client]) ->
+ {ok, client,1}.
+
+handle_info(timeout, client) ->
+ true = is_pid(whereis(otp_server)),
+ {noreply,client,1};
+handle_info(timeout, server) ->
+ exit(self(), some_error).
+
+%%%-----------------------------------------------------------------
+%%% Logger callbacks
+log(_LogEvent,_Config) ->
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Function to trace on for counting produced emulator messages
+producer() ->
+ ok.
diff --git a/lib/kernel/test/logger_test_lib.erl b/lib/kernel/test/logger_test_lib.erl
index 81eb9ce5eb..be4bc427fb 100644
--- a/lib/kernel/test/logger_test_lib.erl
+++ b/lib/kernel/test/logger_test_lib.erl
@@ -28,11 +28,17 @@
post_end_per_testcase/5, post_end_per_suite/3]).
setup(Config,Vars) ->
+ Postfix = case proplists:get_value(postfix, Config) of
+ undefined -> "";
+ P -> ["_",P]
+ end,
FuncStr = lists:concat([proplists:get_value(suite, Config), "_",
- proplists:get_value(tc, Config)]),
+ proplists:get_value(tc, Config)|
+ Postfix]),
ConfigFileName = filename:join(proplists:get_value(priv_dir, Config), FuncStr),
file:write_file(ConfigFileName ++ ".config", io_lib:format("[{kernel, ~p}].",[Vars])),
- case test_server:start_node(proplists:get_value(tc, Config), slave,
+ Sname = lists:concat([proplists:get_value(tc,Config)|Postfix]),
+ case test_server:start_node(Sname, slave,
[{args, ["-pa ",filename:dirname(code:which(?MODULE)),
" -boot start_sasl -kernel start_timer true "
"-config ",ConfigFileName]}]) of
diff --git a/lib/kernel/test/sendfile_SUITE.erl b/lib/kernel/test/sendfile_SUITE.erl
index 0c0b1cbcb6..ad060aa05c 100644
--- a/lib/kernel/test/sendfile_SUITE.erl
+++ b/lib/kernel/test/sendfile_SUITE.erl
@@ -341,7 +341,21 @@ t_sendfile_closeduring(Config) ->
-1
end,
- ok = sendfile_send({127,0,0,1}, Send, 0).
+ ok = sendfile_send({127,0,0,1}, Send, 0, [{active,false}]),
+ [] = flush(),
+ ok = sendfile_send({127,0,0,1}, Send, 0, [{active,true}]),
+ [] = flush(),
+ ok.
+
+flush() ->
+ lists:reverse(flush([])).
+
+flush(Acc) ->
+ receive M ->
+ flush([M | Acc])
+ after 0 ->
+ Acc
+ end.
t_sendfile_crashduring(Config) ->
Filename = proplists:get_value(big_file, Config),
@@ -409,12 +423,16 @@ sendfile_send(Send) ->
sendfile_send(Host, Send) ->
sendfile_send(Host, Send, []).
sendfile_send(Host, Send, Orig) ->
+ sendfile_send(Host, Send, Orig, [{active,false}]).
+
+sendfile_send(Host, Send, Orig, SockOpts) ->
+
SFServer = spawn_link(?MODULE, sendfile_server, [self(), Orig]),
receive
{server, Port} ->
- {ok, Sock} = gen_tcp:connect(Host, Port,
- [binary,{packet,0},
- {active,false}]),
+ Opts = [binary,{packet,0}|SockOpts],
+ io:format("connect with opts = ~p\n", [Opts]),
+ {ok, Sock} = gen_tcp:connect(Host, Port, Opts),
Data = case proplists:get_value(arity,erlang:fun_info(Send)) of
1 ->
Send(Sock);
diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl
index 3bbf62e832..663f910751 100644
--- a/lib/kernel/test/seq_trace_SUITE.erl
+++ b/lib/kernel/test/seq_trace_SUITE.erl
@@ -28,7 +28,8 @@
-export([token_set_get/1, tracer_set_get/1, print/1,
send/1, distributed_send/1, recv/1, distributed_recv/1,
trace_exit/1, distributed_exit/1, call/1, port/1,
- match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1]).
+ match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1,
+ send_literal/1]).
%% internal exports
-export([simple_tracer/2, one_time_receiver/0, one_time_receiver/1,
@@ -47,7 +48,7 @@ suite() ->
{timetrap,{minutes,1}}].
all() ->
- [token_set_get, tracer_set_get, print, send,
+ [token_set_get, tracer_set_get, print, send, send_literal,
distributed_send, recv, distributed_recv, trace_exit,
distributed_exit, call, port, match_set_seq_token,
gc_seq_token, label_capability_mismatch].
@@ -161,23 +162,51 @@ do_print(TsType) ->
{0,{print,_,_,[],print3}, Ts1}] = stop_tracer(2),
check_ts(TsType, Ts0),
check_ts(TsType, Ts1).
-
+
send(Config) when is_list(Config) ->
lists:foreach(fun do_send/1, ?TIMESTAMP_MODES).
do_send(TsType) ->
+ do_send(TsType, send).
+
+do_send(TsType, Msg) ->
seq_trace:reset_trace(),
start_tracer(),
Receiver = spawn(?MODULE,one_time_receiver,[]),
Label = make_ref(),
seq_trace:set_token(label,Label),
set_token_flags([send, TsType]),
- Receiver ! send,
+ Receiver ! Msg,
Self = self(),
seq_trace:reset_trace(),
- [{Label,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1),
+ [{Label,{send,_,Self,Receiver,Msg}, Ts}] = stop_tracer(1),
check_ts(TsType, Ts).
+%% This testcase tests that we do not segfault when we have a
+%% literal as the message and the message is copied onto the
+%% heap during a GC.
+send_literal(Config) when is_list(Config) ->
+ lists:foreach(fun do_send_literal/1,
+ [atom, make_ref(), ets:new(hej,[]), 1 bsl 64,
+ "gurka", {tuple,test,with,#{}}, #{}]).
+
+do_send_literal(Msg) ->
+ N = 10000,
+ seq_trace:reset_trace(),
+ start_tracer(),
+ Label = make_ref(),
+ seq_trace:set_token(label,Label),
+ set_token_flags([send, 'receive', no_timestamp]),
+ Receiver = spawn_link(fun() -> receive ok -> ok end end),
+ [Receiver ! Msg || _ <- lists:seq(1, N)],
+ erlang:garbage_collect(Receiver),
+ [Receiver ! Msg || _ <- lists:seq(1, N)],
+ erlang:garbage_collect(Receiver),
+ Self = self(),
+ seq_trace:reset_trace(),
+ [{Label,{send,_,Self,Receiver,Msg}, Ts} | _] = stop_tracer(N),
+ check_ts(no_timestamp, Ts).
+
distributed_send(Config) when is_list(Config) ->
lists:foreach(fun do_distributed_send/1, ?TIMESTAMP_MODES).
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index df95174c9f..4b43c6ae9d 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.1
+KERNEL_VSN = 6.2
diff --git a/lib/megaco/doc/src/megaco.xml b/lib/megaco/doc/src/megaco.xml
index d4a7451bfc..c7bcdfcd6f 100644
--- a/lib/megaco/doc/src/megaco.xml
+++ b/lib/megaco/doc/src/megaco.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco.xml</file>
</header>
- <module>megaco</module>
+ <module since="">megaco</module>
<modulesummary>Main API of the Megaco application</modulesummary>
<description>
<p>Interface module for the Megaco application</p>
@@ -135,7 +135,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
<funcs>
<func>
- <name>start() -> ok | {error, Reason}</name>
+ <name since="">start() -> ok | {error, Reason}</name>
<fsummary>Starts the Megaco application</fsummary>
<type>
<v>Reason = term()</v>
@@ -153,7 +153,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>stop() -> ok | {error, Reason}</name>
+ <name since="">stop() -> ok | {error, Reason}</name>
<fsummary>Stops the Megaco application</fsummary>
<type>
<v>Reason = term()</v>
@@ -166,7 +166,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>start_user(UserMid, Config) -> ok | {error, Reason}</name>
+ <name since="">start_user(UserMid, Config) -> ok | {error, Reason}</name>
<fsummary>Initial configuration of a user</fsummary>
<type>
<v>UserMid = megaco_mid()</v>
@@ -188,7 +188,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>stop_user(UserMid) -> ok | {error, Reason}</name>
+ <name since="">stop_user(UserMid) -> ok | {error, Reason}</name>
<fsummary>Delete the configuration of a user</fsummary>
<type>
<v>UserMid = megaco_mid()</v>
@@ -203,8 +203,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>user_info(UserMid) -> [{Item, Value}]</name>
- <name>user_info(UserMid, Item) -> Value | exit(Reason)</name>
+ <name since="">user_info(UserMid) -> [{Item, Value}]</name>
+ <name since="">user_info(UserMid, Item) -> Value | exit(Reason)</name>
<fsummary>Lookup user information</fsummary>
<type>
<v>Handle = user_info_handle()</v>
@@ -703,7 +703,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>update_user_info(UserMid, Item, Value) -> ok | {error, Reason}</name>
+ <name since="">update_user_info(UserMid, Item, Value) -> ok | {error, Reason}</name>
<fsummary>Update information about a user</fsummary>
<type>
<v>UserMid = megaco_mid() </v>
@@ -721,8 +721,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>conn_info(ConnHandle) -> [{Item, Value}]</name>
- <name>conn_info(ConnHandle, Item) -> Value | exit(Reason)</name>
+ <name since="">conn_info(ConnHandle) -> [{Item, Value}]</name>
+ <name since="">conn_info(ConnHandle, Item) -> Value | exit(Reason)</name>
<fsummary>Lookup information about an active connection</fsummary>
<type>
<v>ConnHandle = #megaco_conn_handle{}</v>
@@ -1222,7 +1222,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>update_conn_info(ConnHandle, Item, Value) -> ok | {error, Reason}</name>
+ <name since="">update_conn_info(ConnHandle, Item, Value) -> ok | {error, Reason}</name>
<fsummary>Update information about an active connection</fsummary>
<type>
<v>ConnHandle = #megaco_conn_handle{}</v>
@@ -1241,8 +1241,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>system_info() -> [{Item, Value}] | exit(Reason)</name>
- <name>system_info(Item) -> Value | exit(Reason)</name>
+ <name since="">system_info() -> [{Item, Value}] | exit(Reason)</name>
+ <name since="">system_info(Item) -> Value | exit(Reason)</name>
<fsummary>Lookup system information</fsummary>
<type>
<v>Item = system_info_item()</v>
@@ -1289,7 +1289,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>info() -> Info</name>
+ <name since="">info() -> Info</name>
<fsummary>All the information of the application</fsummary>
<type>
<v>Info = [{Key, Value}]</v>
@@ -1311,8 +1311,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid) -> {ok, ConnHandle} | {error, Reason}</name>
- <name>connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid, Extra) -> {ok, ConnHandle} | {error, Reason}</name>
+ <name since="">connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid) -> {ok, ConnHandle} | {error, Reason}</name>
+ <name since="">connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid, Extra) -> {ok, ConnHandle} | {error, Reason}</name>
<fsummary>Establish a "virtual" connection</fsummary>
<type>
<v>ReceiveHandle = #megaco_receive_handle{}</v>
@@ -1436,7 +1436,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>disconnect(ConnHandle, DiscoReason) -> ok | {error, ErrReason}</name>
+ <name since="">disconnect(ConnHandle, DiscoReason) -> ok | {error, ErrReason}</name>
<fsummary>Tear down a "virtual" connection</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1454,7 +1454,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>call(ConnHandle, Actions, Options) -> {ProtocolVersion, UserReply}</name>
+ <name since="">call(ConnHandle, Actions, Options) -> {ProtocolVersion, UserReply}</name>
<fsummary>Sends one or more transaction request(s) and waits for the reply</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1545,7 +1545,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>cast(ConnHandle, Actions, Options) -> ok | {error, Reason}</name>
+ <name since="">cast(ConnHandle, Actions, Options) -> ok | {error, Reason}</name>
<fsummary>Sends one or more transaction request(s) but does NOT wait for a reply</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1582,7 +1582,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>encode_actions(ConnHandle, Actions, Options) -> {ok, BinOrBins} | {error, Reason}</name>
+ <name since="">encode_actions(ConnHandle, Actions, Options) -> {ok, BinOrBins} | {error, Reason}</name>
<fsummary>Encode action requests for one or more transaction request(s)</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1607,9 +1607,9 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>token_tag2string(Tag) -> Result</name>
- <name>token_tag2string(Tag, EncoderMod) -> Result</name>
- <name>token_tag2string(Tag, EncoderMod, Version) -> Result</name>
+ <name since="">token_tag2string(Tag) -> Result</name>
+ <name since="">token_tag2string(Tag, EncoderMod) -> Result</name>
+ <name since="">token_tag2string(Tag, EncoderMod, Version) -> Result</name>
<fsummary>Convert a token tag to a string</fsummary>
<type>
<v>Tag = atom()</v>
@@ -1635,7 +1635,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>cancel(ConnHandle, CancelReason) -> ok | {error, ErrReason}</name>
+ <name since="">cancel(ConnHandle, CancelReason) -> ok | {error, ErrReason}</name>
<fsummary>Cancel all outstanding messages for this connection</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -1655,8 +1655,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
- <name>process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
+ <name since="">process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
+ <name since="">process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
<fsummary>Process a received message</fsummary>
<type>
<v>ReceiveHandle = #megaco_receive_handle{}</v>
@@ -1755,8 +1755,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
- <name>receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
+ <name since="">receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name>
+ <name since="">receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name>
<fsummary>Process a received message</fsummary>
<type>
<v>ReceiveHandle = #megaco_receive_handle{}</v>
@@ -1783,7 +1783,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>parse_digit_map(DigitMapBody) -> {ok, ParsedDigitMap} | {error, Reason}</name>
+ <name since="">parse_digit_map(DigitMapBody) -> {ok, ParsedDigitMap} | {error, Reason}</name>
<fsummary>Parses a digit map body</fsummary>
<type>
<v>DigitMapBody = string()</v>
@@ -1802,8 +1802,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>eval_digit_map(DigitMap) -> {ok, MatchResult} | {error, Reason}</name>
- <name>eval_digit_map(DigitMap, Timers) -> {ok, MatchResult} | {error, Reason}</name>
+ <name since="">eval_digit_map(DigitMap) -> {ok, MatchResult} | {error, Reason}</name>
+ <name since="">eval_digit_map(DigitMap, Timers) -> {ok, MatchResult} | {error, Reason}</name>
<fsummary>Collect digit map letters according to the digit map</fsummary>
<type>
<v>DigitMap = #'DigitMapValue'{} | parsed_digit_map()</v>
@@ -1839,7 +1839,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>report_digit_event(DigitMapEvalPid, Events) -> ok | {error, Reason}</name>
+ <name since="">report_digit_event(DigitMapEvalPid, Events) -> ok | {error, Reason}</name>
<fsummary>Send one or more events to the event collector process</fsummary>
<type>
<v>DigitMapEvalPid = pid()</v>
@@ -1866,7 +1866,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>test_digit_event(DigitMap, Events) -> {ok, Kind, Letters} | {error, Reason}</name>
+ <name since="">test_digit_event(DigitMap, Events) -> {ok, Kind, Letters} | {error, Reason}</name>
<fsummary>Feed digit map collector with events and return the result</fsummary>
<type>
<v>DigitMap = #'DigitMapValue'{} | parsed_digit_map()</v>
@@ -1900,7 +1900,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>encode_sdp(SDP) -> {ok, PP} | {error, Reason}</name>
+ <name since="">encode_sdp(SDP) -> {ok, PP} | {error, Reason}</name>
<fsummary>Encode an SDP construct</fsummary>
<type>
<v>SDP = sdp_property_parm() | sdp_property_group() | sdp_property_groups() | asn1_NOVALUE</v>
@@ -1929,7 +1929,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>decode_sdp(PP) -> {ok, SDP} | {error, Reason}</name>
+ <name since="">decode_sdp(PP) -> {ok, SDP} | {error, Reason}</name>
<fsummary>Decode an property parameter construct</fsummary>
<type>
<v>PP = property_parm() | property_group() | property_groups() | asn1_NOVALUE</v>
@@ -1969,7 +1969,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name>
+ <name since="">get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name>
<fsummary>Get all sdp records of a certain type from a property group</fsummary>
<type>
<v>Type = v | c | m | o | a | b | t | r | z | k | s | i | u | e | p</v>
@@ -1986,8 +1986,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>versions1() -> {ok, VersionInfo} | {error, Reason}</name>
- <name>versions2() -> {ok, Info} | {error, Reason}</name>
+ <name since="">versions1() -> {ok, VersionInfo} | {error, Reason}</name>
+ <name since="">versions2() -> {ok, Info} | {error, Reason}</name>
<fsummary>Retreive various system and application info</fsummary>
<type>
<v>VersionInfo = [version_info()]</v>
@@ -2007,8 +2007,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>print_version_info() -> void()</name>
- <name>print_version_info(VersionInfo) -> void()</name>
+ <name since="">print_version_info() -> void()</name>
+ <name since="">print_version_info(VersionInfo) -> void()</name>
<fsummary>Formated print of result of the versions functions</fsummary>
<type>
<v>VersionInfo = [version_info()]</v>
@@ -2029,7 +2029,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>enable_trace(Level, Destination) -> void()</name>
+ <name since="">enable_trace(Level, Destination) -> void()</name>
<fsummary>Start megaco tracing</fsummary>
<type>
<v>Level = max | min | 0 &lt;= integer() &lt;= 100</v>
@@ -2057,7 +2057,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>disable_trace() -> void()</name>
+ <name since="">disable_trace() -> void()</name>
<fsummary>Stop megaco tracing</fsummary>
<desc>
<p>This function is used to stop megaco tracing.</p>
@@ -2065,7 +2065,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</desc>
</func>
<func>
- <name>set_trace(Level) -> void()</name>
+ <name since="">set_trace(Level) -> void()</name>
<fsummary>Change megaco trace level</fsummary>
<type>
<v>Level = max | min | 0 &lt;= integer() &lt;= 100</v>
@@ -2081,10 +2081,10 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name>
- <name>get_stats(GlobalCounter) -> {ok, CounterStats} | {error, Reason}</name>
- <name>get_stats(ConnHandle) -> {ok, ConnHandleStats} | {error, Reason}</name>
- <name>get_stats(ConnHandle, Counter) -> {ok, integer()} | {error, Reason}</name>
+ <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name>
+ <name since="">get_stats(GlobalCounter) -> {ok, CounterStats} | {error, Reason}</name>
+ <name since="">get_stats(ConnHandle) -> {ok, ConnHandleStats} | {error, Reason}</name>
+ <name since="">get_stats(ConnHandle, Counter) -> {ok, integer()} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TotalStats = [total_stats()]</v>
@@ -2110,8 +2110,8 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>reset_stats() -> void()</name>
- <name>reset_stats(ConnHandle) -> void()</name>
+ <name since="">reset_stats() -> void()</name>
+ <name since="">reset_stats(ConnHandle) -> void()</name>
<fsummary></fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -2123,7 +2123,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>test_request(ConnHandle, Version, EncodingMod, EncodingConfig, Actions) -> {MegaMsg, EncodeRes}</name>
+ <name since="">test_request(ConnHandle, Version, EncodingMod, EncodingConfig, Actions) -> {MegaMsg, EncodeRes}</name>
<fsummary>Tests if the Actions argument is correct</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -2150,7 +2150,7 @@ megaco_incr_timer() = #megaco_incr_timer{}
</func>
<func>
- <name>test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> {MegaMsg, EncodeRes}</name>
+ <name since="">test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> {MegaMsg, EncodeRes}</name>
<fsummary>Tests if the Reply argument is correct</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_meas.xml b/lib/megaco/doc/src/megaco_codec_meas.xml
index 13cc3eb834..5184fe392e 100644
--- a/lib/megaco/doc/src/megaco_codec_meas.xml
+++ b/lib/megaco/doc/src/megaco_codec_meas.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_meas.xml</file>
</header>
- <module>megaco_codec_meas</module>
+ <module since="">megaco_codec_meas</module>
<modulesummary>This module implements a simple megaco codec measurement tool.</modulesummary>
<description>
<p>This module implements a simple megaco codec measurement tool.</p>
@@ -43,8 +43,8 @@
<funcs>
<func>
- <name>start() -> void()</name>
- <name>start(MessagePackage) -> void()</name>
+ <name since="">start() -> void()</name>
+ <name since="">start(MessagePackage) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackageRaw = message_package()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_mstone1.xml b/lib/megaco/doc/src/megaco_codec_mstone1.xml
index 2ff959a648..507a790c71 100644
--- a/lib/megaco/doc/src/megaco_codec_mstone1.xml
+++ b/lib/megaco/doc/src/megaco_codec_mstone1.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_mstone1.xml</file>
</header>
- <module>megaco_codec_mstone1</module>
+ <module since="">megaco_codec_mstone1</module>
<modulesummary>This module implements a simple megaco codec-based performance tool.</modulesummary>
<description>
<p>This module implements the <em>mstone1</em> tool,
@@ -44,9 +44,9 @@
<funcs>
<func>
- <name>start() -> void()</name>
- <name>start(MessagePackage) -> void()</name>
- <name>start(MessagePackage, Factor) -> void()</name>
+ <name since="">start() -> void()</name>
+ <name since="">start(MessagePackage) -> void()</name>
+ <name since="">start(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
@@ -63,9 +63,9 @@
</func>
<func>
- <name>start_flex() -> void()</name>
- <name>start_flex(MessagePackage) -> void()</name>
- <name>start_flex(MessagePackage, Factor) -> void()</name>
+ <name since="">start_flex() -> void()</name>
+ <name since="">start_flex(MessagePackage) -> void()</name>
+ <name since="">start_flex(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
@@ -83,9 +83,9 @@
</func>
<func>
- <name>start_only_drv() -> void()</name>
- <name>start_only_drv(MessagePackage) -> void()</name>
- <name>start_only_drv(MessagePackage, Factor) -> void()</name>
+ <name since="">start_only_drv() -> void()</name>
+ <name since="">start_only_drv(MessagePackage) -> void()</name>
+ <name since="">start_only_drv(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
@@ -105,9 +105,9 @@
</func>
<func>
- <name>start_no_drv() -> void()</name>
- <name>start_no_drv(MessagePackage) -> void()</name>
- <name>start_no_drv(MessagePackage, Factor) -> void()</name>
+ <name since="">start_no_drv() -> void()</name>
+ <name since="">start_no_drv(MessagePackage) -> void()</name>
+ <name since="">start_no_drv(MessagePackage, Factor) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_mstone2.xml b/lib/megaco/doc/src/megaco_codec_mstone2.xml
index 3da30d4f99..03990f5c3d 100644
--- a/lib/megaco/doc/src/megaco_codec_mstone2.xml
+++ b/lib/megaco/doc/src/megaco_codec_mstone2.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_mstone2.xml</file>
</header>
- <module>megaco_codec_mstone2</module>
+ <module since="">megaco_codec_mstone2</module>
<modulesummary>This module implements a simple megaco codec-based performance tool.</modulesummary>
<description>
<p>This module implements the <em>mstone2</em> tool,
@@ -44,8 +44,8 @@
<funcs>
<func>
- <name>start() -> void()</name>
- <name>start(MessagePackage) -> void()</name>
+ <name since="">start() -> void()</name>
+ <name since="">start(MessagePackage) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = message_package()</v>
diff --git a/lib/megaco/doc/src/megaco_codec_transform.xml b/lib/megaco/doc/src/megaco_codec_transform.xml
index 26b83c3799..392868fdfa 100644
--- a/lib/megaco/doc/src/megaco_codec_transform.xml
+++ b/lib/megaco/doc/src/megaco_codec_transform.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_codec_transform.xml</file>
</header>
- <module>megaco_codec_transform</module>
+ <module since="">megaco_codec_transform</module>
<modulesummary>Megaco message transformation utility.</modulesummary>
<description>
@@ -45,8 +45,8 @@
<funcs>
<func>
- <name>export_messages() -> void()</name>
- <name>export_messages(MessagePackage) -> void()</name>
+ <name since="">export_messages() -> void()</name>
+ <name since="">export_messages(MessagePackage) -> void()</name>
<fsummary></fsummary>
<type>
<v>MessagePackage = atom()</v>
diff --git a/lib/megaco/doc/src/megaco_edist_compress.xml b/lib/megaco/doc/src/megaco_edist_compress.xml
index d5c7c7224d..16443e469c 100644
--- a/lib/megaco/doc/src/megaco_edist_compress.xml
+++ b/lib/megaco/doc/src/megaco_edist_compress.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_edist_compress.xml</file>
</header>
- <module>megaco_edist_compress</module>
+ <module since="">megaco_edist_compress</module>
<modulesummary>Megaco erlang dist compress behaviour.</modulesummary>
<description>
<p>The following functions should be exported from a
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>Module:encode(R, Version) -> T</name>
+ <name since="">Module:encode(R, Version) -> T</name>
<fsummary>Encode (compress) a megaco component.</fsummary>
<type>
<v>R = megaco_message() | transaction() | action_reply() | action_request() | command_request()</v>
@@ -53,7 +53,7 @@
</desc>
</func>
<func>
- <name>Module:decode(T, Version) -> R</name>
+ <name since="">Module:decode(T, Version) -> R</name>
<fsummary>Decode (decompress) a megaco component.</fsummary>
<type>
<v>T = term()</v>
diff --git a/lib/megaco/doc/src/megaco_encoder.xml b/lib/megaco/doc/src/megaco_encoder.xml
index 13c6ed324b..cc8270440b 100644
--- a/lib/megaco/doc/src/megaco_encoder.xml
+++ b/lib/megaco/doc/src/megaco_encoder.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_encoder.xml</file>
</header>
- <module>megaco_encoder</module>
+ <module since="">megaco_encoder</module>
<modulesummary>Megaco encoder behaviour.</modulesummary>
<description>
<p>The following functions should be exported from a
@@ -64,7 +64,7 @@ action_reply() = #'ActionReply'{}
<funcs>
<func>
- <name>Module:encode_message(EncodingConfig, Version, Message) -> {ok, Bin} | Error</name>
+ <name since="">Module:encode_message(EncodingConfig, Version, Message) -> {ok, Bin} | Error</name>
<fsummary>Encode a megaco message.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -81,7 +81,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:decode_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
+ <name since="">Module:decode_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
<fsummary>Decode a megaco message.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -104,7 +104,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:decode_mini_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
+ <name since="">Module:decode_mini_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name>
<fsummary>Perform a minimal decode of a megaco message.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -129,7 +129,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:encode_transaction(EncodingConfig, Version, Transaction) -> OK | Error</name>
+ <name since="">Module:encode_transaction(EncodingConfig, Version, Transaction) -> OK | Error</name>
<fsummary>Encode a megaco transaction.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -155,7 +155,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:encode_action_requests(EncodingConfig, Version, ARs) -> OK | Error</name>
+ <name since="">Module:encode_action_requests(EncodingConfig, Version, ARs) -> OK | Error</name>
<fsummary>Encode megaco action requests.</fsummary>
<type>
<v>EncodingConfig = list()</v>
@@ -181,7 +181,7 @@ action_reply() = #'ActionReply'{}
</func>
<func>
- <name>Module:encode_action_reply(EncodingConfig, Version, AR) -> OK | Error</name>
+ <name since="">Module:encode_action_reply(EncodingConfig, Version, AR) -> OK | Error</name>
<fsummary>Encode a megaco action reply.</fsummary>
<type>
<v>EncodingConfig = list()</v>
diff --git a/lib/megaco/doc/src/megaco_flex_scanner.xml b/lib/megaco/doc/src/megaco_flex_scanner.xml
index 0856f3f429..121a7fbcff 100644
--- a/lib/megaco/doc/src/megaco_flex_scanner.xml
+++ b/lib/megaco/doc/src/megaco_flex_scanner.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_flex_scanner.xml</file>
</header>
- <module>megaco_flex_scanner</module>
+ <module since="">megaco_flex_scanner</module>
<modulesummary>Interface module to the flex scanner linked in driver.</modulesummary>
<description>
<p>This module contains the public interface to the flex scanner
@@ -72,7 +72,7 @@ megaco_version() = integer() >= 1
<funcs>
<func>
- <name>start() -> {ok, PortOrPorts} | {error, Reason}</name>
+ <name since="">start() -> {ok, PortOrPorts} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>PortOrPorts = megaco_ports()</v>
@@ -94,7 +94,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>stop(PortOrPorts) -> stopped</name>
+ <name since="">stop(PortOrPorts) -> stopped</name>
<fsummary></fsummary>
<type>
<v>PortOrPorts = megaco_ports()</v>
@@ -108,7 +108,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>is_reentrant_enabled() -> Boolean</name>
+ <name since="">is_reentrant_enabled() -> Boolean</name>
<fsummary></fsummary>
<type>
<v>Boolean = boolean()</v>
@@ -121,7 +121,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>is_scanner_port(Port, PortOrPorts) -> Boolean</name>
+ <name since="">is_scanner_port(Port, PortOrPorts) -> Boolean</name>
<fsummary></fsummary>
<type>
<v>Port = port()</v>
@@ -137,7 +137,7 @@ megaco_version() = integer() >= 1
</func>
<func>
- <name>scan(Binary, PortOrPorts) -> {ok, Tokens, Version, LatestLine} | {error, Reason, LatestLine} </name>
+ <name since="">scan(Binary, PortOrPorts) -> {ok, Tokens, Version, LatestLine} | {error, Reason, LatestLine} </name>
<fsummary></fsummary>
<type>
<v>Binary = binary()</v>
diff --git a/lib/megaco/doc/src/megaco_tcp.xml b/lib/megaco/doc/src/megaco_tcp.xml
index 77aee32f6c..63713b2c56 100644
--- a/lib/megaco/doc/src/megaco_tcp.xml
+++ b/lib/megaco/doc/src/megaco_tcp.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_tcp.xml</file>
</header>
- <module>megaco_tcp</module>
+ <module since="">megaco_tcp</module>
<modulesummary>Interface module to TPKT transport protocol for Megaco/H.248.</modulesummary>
<description>
<p>This module contains the public interface to the TPKT (TCP/IP) version
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>start_transport() -> {ok, TransportRef}</name>
+ <name since="">start_transport() -> {ok, TransportRef}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid()</v>
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name>listen(TransportRef, ListenPortSpecList) -> ok</name>
+ <name since="">listen(TransportRef, ListenPortSpecList) -> ok</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid() | regname()</v>
@@ -65,7 +65,7 @@
</desc>
</func>
<func>
- <name>connect(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
+ <name since="">connect(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid() | regname()</v>
@@ -86,7 +86,7 @@
</desc>
</func>
<func>
- <name>close(Handle) -> ok</name>
+ <name since="">close(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -96,7 +96,7 @@
</desc>
</func>
<func>
- <name>socket(Handle) -> Socket</name>
+ <name since="">socket(Handle) -> Socket</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -109,7 +109,7 @@
</desc>
</func>
<func>
- <name>send_message(Handle, Message) -> ok</name>
+ <name since="">send_message(Handle, Message) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -120,7 +120,7 @@
</desc>
</func>
<func>
- <name>block(Handle) -> ok</name>
+ <name since="">block(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -130,7 +130,7 @@
</desc>
</func>
<func>
- <name>unblock(Handle) -> ok</name>
+ <name since="">unblock(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -141,7 +141,7 @@
</desc>
</func>
<func>
- <name>upgrade_receive_handle(ControlPid) -> ok</name>
+ <name since="">upgrade_receive_handle(ControlPid) -> ok</name>
<fsummary></fsummary>
<type>
<v>ControlPid = pid()</v>
@@ -153,9 +153,9 @@
</desc>
</func>
<func>
- <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name>
- <name>get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
- <name>get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
+ <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TotalStats = [send_handle_stats()]</v>
@@ -173,8 +173,8 @@
</desc>
</func>
<func>
- <name>reset_stats() -> void()</name>
- <name>reset_stats(SendHandle) -> void()</name>
+ <name since="">reset_stats() -> void()</name>
+ <name since="">reset_stats(SendHandle) -> void()</name>
<fsummary></fsummary>
<type>
<v>SendHandle = send_handle()</v>
diff --git a/lib/megaco/doc/src/megaco_transport.xml b/lib/megaco/doc/src/megaco_transport.xml
index 3002e9b74e..ba8c794750 100644
--- a/lib/megaco/doc/src/megaco_transport.xml
+++ b/lib/megaco/doc/src/megaco_transport.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_transport.xml</file>
</header>
- <module>megaco_transport</module>
+ <module since="">megaco_transport</module>
<modulesummary>Megaco transport behaviour.</modulesummary>
<description>
<p>The following functions should be exported from a
@@ -54,8 +54,8 @@
</description>
<funcs>
<func>
- <name>Module:send_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
- <name>Module:send_message(Handle, Msg, Resend) -> ok | {cancel, Reason} | Error</name>
+ <name since="">Module:send_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
+ <name since="">Module:send_message(Handle, Msg, Resend) -> ok | {cancel, Reason} | Error</name>
<fsummary>Send a megaco message.</fsummary>
<type>
<v>Handle = term()</v>
@@ -99,7 +99,7 @@
</func>
<func>
- <name>Module:resend_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
+ <name since="">Module:resend_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name>
<fsummary>Re-send a megaco message.</fsummary>
<type>
<v>Handle = term()</v>
diff --git a/lib/megaco/doc/src/megaco_udp.xml b/lib/megaco/doc/src/megaco_udp.xml
index b2559c77d5..3d776c19b6 100644
--- a/lib/megaco/doc/src/megaco_udp.xml
+++ b/lib/megaco/doc/src/megaco_udp.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_udp.xml</file>
</header>
- <module>megaco_udp</module>
+ <module since="">megaco_udp</module>
<modulesummary>Interface module to UDP transport protocol for Megaco/H.248.</modulesummary>
<description>
<p>This module contains the public interface to the UDP/IP version
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>start_transport() -> {ok, TransportRef}</name>
+ <name since="">start_transport() -> {ok, TransportRef}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid()</v>
@@ -51,7 +51,7 @@
</desc>
</func>
<func>
- <name>open(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
+ <name since="">open(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TransportRef = pid() | regname()</v>
@@ -73,7 +73,7 @@
</desc>
</func>
<func>
- <name>close(Handle, Msg) -> ok</name>
+ <name since="">close(Handle, Msg) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -84,7 +84,7 @@
</desc>
</func>
<func>
- <name>socket(Handle) -> Socket</name>
+ <name since="">socket(Handle) -> Socket</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -97,7 +97,7 @@
</desc>
</func>
<func>
- <name>create_send_handle(Handle, Host, Port) -> send_handle()</name>
+ <name since="">create_send_handle(Handle, Host, Port) -> send_handle()</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -110,7 +110,7 @@
</desc>
</func>
<func>
- <name>send_message(SendHandle, Msg) -> ok</name>
+ <name since="">send_message(SendHandle, Msg) -> ok</name>
<fsummary></fsummary>
<type>
<v>SendHandle = send_handle()</v>
@@ -125,7 +125,7 @@
</desc>
</func>
<func>
- <name>block(Handle) -> ok</name>
+ <name since="">block(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -135,7 +135,7 @@
</desc>
</func>
<func>
- <name>unblock(Handle) -> ok</name>
+ <name since="">unblock(Handle) -> ok</name>
<fsummary></fsummary>
<type>
<v>Handle = socket_handle()</v>
@@ -146,7 +146,7 @@
</desc>
</func>
<func>
- <name>upgrade_receive_handle(ControlPid, NewHandle) -> ok</name>
+ <name since="">upgrade_receive_handle(ControlPid, NewHandle) -> ok</name>
<fsummary></fsummary>
<type>
<v>ControlPid = pid()</v>
@@ -160,9 +160,9 @@
</desc>
</func>
<func>
- <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name>
- <name>get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
- <name>get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
+ <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name>
+ <name since="">get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>TotalStats = [total_stats()]</v>
@@ -180,8 +180,8 @@
</desc>
</func>
<func>
- <name>reset_stats() -> void()</name>
- <name>reset_stats(SendHandle) -> void()</name>
+ <name since="">reset_stats() -> void()</name>
+ <name since="">reset_stats(SendHandle) -> void()</name>
<fsummary></fsummary>
<type>
<v>SendHandle = send_handle()</v>
diff --git a/lib/megaco/doc/src/megaco_user.xml b/lib/megaco/doc/src/megaco_user.xml
index 067be15fe0..198f2aa24c 100644
--- a/lib/megaco/doc/src/megaco_user.xml
+++ b/lib/megaco/doc/src/megaco_user.xml
@@ -32,7 +32,7 @@
<rev>%VSN%</rev>
<file>megaco_user.xml</file>
</header>
- <module>megaco_user</module>
+ <module since="">megaco_user</module>
<modulesummary>Callback module for users of the Megaco application</modulesummary>
<description>
<p>This module defines the callback behaviour of Megaco users. A
@@ -164,8 +164,8 @@ protocol_version() = integer() ]]></code>
<funcs>
<func>
- <name>handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name>
- <name>handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name>
+ <name since="">handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name>
+ <name since="">handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name>
<fsummary>Invoked when a new connection is established</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -202,7 +202,7 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> ok</name>
+ <name since="">handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> ok</name>
<fsummary>Invoked when a connection is teared down</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -220,8 +220,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
- <name>handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
+ <name since="">handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
+ <name since="">handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name>
<fsummary>Invoked when a received message had syntax errors</fsummary>
<type>
<v>ReceiveHandle = receive_handle()</v>
@@ -258,8 +258,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> ok</name>
- <name>handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> ok</name>
+ <name since="">handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> ok</name>
+ <name since="">handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> ok</name>
<fsummary>Invoked when a received message just contains an error</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -291,8 +291,8 @@ protocol_version() = integer() ]]></code>
<!--
<func>
- <name>handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError) -> ok</name>
- <name>handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError, Extra) -> ok</name>
+ <name since="">handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError) -> ok</name>
+ <name since="">handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError, Extra) -> ok</name>
<fsummary>Invoked when a segment error has been detected</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -316,8 +316,8 @@ protocol_version() = integer() ]]></code>
-->
<func>
- <name>handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> pending() | reply() | ignore_trans_request</name>
- <name>handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra) -> pending() | reply() | ignore_trans_request</name>
+ <name since="">handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> pending() | reply() | ignore_trans_request</name>
+ <name since="">handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra) -> pending() | reply() | ignore_trans_request</name>
<fsummary>Invoked for each transaction request</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -420,8 +420,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> reply()</name>
- <name>handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> reply()</name>
+ <name since="">handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> reply()</name>
+ <name since="">handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> reply()</name>
<fsummary>Optionally invoked for a time consuming transaction request</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -460,8 +460,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData) -> ok</name>
- <name>handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData, Extra) -> ok</name>
+ <name since="">handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData) -> ok</name>
+ <name since="">handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData, Extra) -> ok</name>
<fsummary>Optionally invoked for a transaction reply</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -593,8 +593,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> ok</name>
- <name>handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Extra) -> ok</name>
+ <name since="">handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> ok</name>
+ <name since="">handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Extra) -> ok</name>
<fsummary>Optionally invoked for a transaction acknowledgement</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -658,8 +658,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> ok</name>
- <name>handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> ok</name>
+ <name since="">handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> ok</name>
+ <name since="">handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> ok</name>
<fsummary>Invoked when an unexpected message is received</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -685,8 +685,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid) -> ok</name>
- <name>handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid, Extra) -> ok</name>
+ <name since="">handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid) -> ok</name>
+ <name since="">handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid, Extra) -> ok</name>
<fsummary>Invoked when an transaction request has been aborted</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
@@ -710,8 +710,8 @@ protocol_version() = integer() ]]></code>
</func>
<func>
- <name>handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl) -> ok</name>
- <name>handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl, Extra) -> ok</name>
+ <name since="">handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl) -> ok</name>
+ <name since="">handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl, Extra) -> ok</name>
<fsummary>Segment Reply Indication</fsummary>
<type>
<v>ConnHandle = conn_handle()</v>
diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml
index 621b6047ee..11b0b8e987 100644
--- a/lib/mnesia/doc/src/mnesia.xml
+++ b/lib/mnesia/doc/src/mnesia.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file></file>
</header>
- <module>mnesia</module>
+ <module since="">mnesia</module>
<modulesummary>A distributed telecommunications DBMS</modulesummary>
<description>
@@ -183,7 +183,7 @@
<funcs>
<func>
- <name>abort(Reason) -> transaction abort</name>
+ <name since="">abort(Reason) -> transaction abort</name>
<fsummary>Terminates the current transaction.</fsummary>
<desc>
<p>Makes the transaction silently
@@ -195,7 +195,7 @@
</desc>
</func>
<func>
- <name>activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name>
+ <name since="">activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name>
<fsummary>Activates a checkpoint.</fsummary>
<desc>
<marker id="activate_checkpoint"></marker>
@@ -259,7 +259,7 @@
</desc>
</func>
<func>
- <name>activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name>
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_2_3"></marker>
@@ -271,7 +271,7 @@
</desc>
</func>
<func>
- <name>activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name>
+ <name since="">activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name>
<fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary>
<desc>
<marker id="activity_4"></marker>
@@ -403,7 +403,7 @@
</desc>
</func>
<func>
- <name>add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Copies a table to a remote node.</fsummary>
<desc>
<marker id="add_table_copy"></marker>
@@ -420,7 +420,7 @@ mnesia:add_table_copy(person, Node, disc_copies)</code>
</desc>
</func>
<func>
- <name>add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Creates an index for a table.</fsummary>
<desc>
<marker id="add_table_index"></marker>
@@ -441,7 +441,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>all_keys(Tab) -> KeyList | transaction abort</name>
+ <name since="">all_keys(Tab) -> KeyList | transaction abort</name>
<fsummary>Returns all keys in a table.</fsummary>
<desc>
<marker id="all_keys"></marker>
@@ -453,7 +453,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
<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 +493,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name since="">backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name>
<fsummary>Backs up all tables in the database.</fsummary>
<desc>
<marker id="backup"></marker>
@@ -505,7 +505,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name>
+ <name since="">backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name>
<fsummary>Backs up all tables in a checkpoint.</fsummary>
<desc>
<marker id="backup_checkpoint"></marker>
@@ -520,7 +520,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name>
+ <name since="">change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name>
<fsummary>Changes a configuration parameter.</fsummary>
<desc>
<marker id="change_config"></marker>
@@ -554,7 +554,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the access mode for the table.</fsummary>
<desc>
<marker id="change_table_access_mode"></marker>
@@ -568,7 +568,7 @@ mnesia:add_table_index(person, age)</code>
</desc>
</func>
<func>
- <name>change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the storage type of a table.</fsummary>
<desc>
<marker id="change_table_copy_type"></marker>
@@ -585,7 +585,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes the load order priority for the table.</fsummary>
<desc>
<marker id="change_table_load_order"></marker>
@@ -595,7 +595,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
+ <name since="OTP R14B03">change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
<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 +607,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes all entries in a table.</fsummary>
<desc>
<marker id="clear_table"></marker>
@@ -615,7 +615,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>create_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name since="">create_schema(DiscNodes) -> ok | {error,Reason}</name>
<fsummary>Creates a new schema on the specified nodes.</fsummary>
<desc>
<marker id="create_schema"></marker>
@@ -637,7 +637,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code>
</desc>
</func>
<func>
- <name>create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name>
+ <name since="">create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name>
<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 +799,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>deactivate_checkpoint(Name) -> ok | {error, Reason}</name>
+ <name since="">deactivate_checkpoint(Name) -> ok | {error, Reason}</name>
<fsummary>Deactivates a checkpoint.</fsummary>
<desc>
<marker id="deactivate_checkpoint"></marker>
@@ -811,7 +811,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes the replica of table <c>Tab</c> at node <c>Node</c>.</fsummary>
<desc>
<marker id="del_table_copy"></marker>
@@ -825,7 +825,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes an index in a table.</fsummary>
<desc>
<marker id="del_table_index"></marker>
@@ -834,7 +834,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete({Tab, Key}) -> transaction abort | ok</name>
+ <name since="">delete({Tab, Key}) -> transaction abort | ok</name>
<fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary>
<desc>
<marker id="delete_2"></marker>
@@ -842,7 +842,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete(Tab, Key, LockKind) -> transaction abort | ok</name>
+ <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>
<desc>
<marker id="delete_3"></marker>
@@ -857,7 +857,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_object(Record) -> transaction abort | ok</name>
+ <name since="">delete_object(Record) -> transaction abort | ok</name>
<fsummary>Delete a record.</fsummary>
<desc>
<marker id="delete_object_1"></marker>
@@ -866,7 +866,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_object(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name since="">delete_object(Tab, Record, LockKind) -> transaction abort | ok</name>
<fsummary>Deletes a record.</fsummary>
<desc>
<marker id="delete_object_3"></marker>
@@ -883,7 +883,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_schema(DiscNodes) -> ok | {error,Reason}</name>
+ <name since="">delete_schema(DiscNodes) -> ok | {error,Reason}</name>
<fsummary>Deletes the schema on the given nodes.</fsummary>
<desc>
<marker id="delete_schema"></marker>
@@ -904,7 +904,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name>
+ <name since="">delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name>
<fsummary>Deletes permanently all replicas of table <c>Tab</c>.</fsummary>
<desc>
<marker id="delete_table"></marker>
@@ -912,7 +912,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name>
+ <name since="">dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name>
<fsummary>Dirty search for all record keys in table.</fsummary>
<desc>
<marker id="delete_all_keys"></marker>
@@ -920,7 +920,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete"></marker>
@@ -928,14 +928,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:delete/3</c>.</p>
</desc>
</func>
<func>
- <name>dirty_delete_object(Record)</name>
+ <name since="">dirty_delete_object(Record)</name>
<fsummary>Dirty delete of a record.</fsummary>
<desc>
<marker id="dirty_delete_object_1"></marker>
@@ -944,14 +944,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_delete_object(Tab, Record)</name>
+ <name since="">dirty_delete_object(Tab, Record)</name>
<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>dirty_first(Tab) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_first(Tab) -> Key | exit({aborted, Reason})</name>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="dirty_first"></marker>
@@ -967,7 +967,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_index_match_object(Pattern, Pos)</name>
+ <name since="">dirty_index_match_object(Pattern, Pos)</name>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<marker id="dirty_index_match_object_2"></marker>
@@ -977,7 +977,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_index_match_object(Tab, Pattern, Pos)</name>
+ <name since="">dirty_index_match_object(Tab, Pattern, Pos)</name>
<fsummary>Dirty pattern match using index.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -985,7 +985,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_index_read(Tab, SecondaryKey, Pos)</name>
+ <name since="">dirty_index_read(Tab, SecondaryKey, Pos)</name>
<fsummary>Dirty read using index.</fsummary>
<desc>
<marker id="dirty_index_read"></marker>
@@ -994,7 +994,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_last(Tab) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_last(Tab) -> Key | exit({aborted, Reason})</name>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<marker id="dirty_last"></marker>
@@ -1006,7 +1006,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name since="">dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<marker id="dirty_match_object_1"></marker>
@@ -1015,7 +1015,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name>
+ <name since="">dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name>
<fsummary>Dirty pattern match pattern.</fsummary>
<desc>
<p>Dirty equivalent of the function
@@ -1023,7 +1023,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name>
<fsummary>Return the next key in a table.</fsummary>
<desc>
<marker id="dirty_next"></marker>
@@ -1038,7 +1038,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name>
+ <name since="">dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<marker id="dirty_prev"></marker>
@@ -1050,7 +1050,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name>
+ <name since="">dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name>
<fsummary>Dirty read of records.</fsummary>
<desc>
<marker id="dirty_read"></marker>
@@ -1058,14 +1058,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name>
+ <name since="">dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name>
<fsummary>Dirty read of records.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:read/3</c>.</p>
</desc>
</func>
<func>
- <name>dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name>
+ <name since="">dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name>
<fsummary>Dirty matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="dirty_select"></marker>
@@ -1073,7 +1073,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name>
+ <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>
@@ -1089,7 +1089,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name since="">dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<marker id="dirty_update_counter"></marker>
@@ -1097,7 +1097,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name>
+ <name since="">dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name>
<fsummary>Dirty update of a counter record.</fsummary>
<desc>
<p>Mnesia has no special counter records. However,
@@ -1126,7 +1126,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_write(Record) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_write(Record) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<marker id="dirty_write_1"></marker>
@@ -1135,14 +1135,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name>
+ <name since="">dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name>
<fsummary>Dirty write of a record.</fsummary>
<desc>
<p>Dirty equivalent of the function <c>mnesia:write/3</c>.</p>
</desc>
</func>
<func>
- <name>dump_log() -> dumped</name>
+ <name since="">dump_log() -> dumped</name>
<fsummary>Performs a user-initiated dump of the local log file.</fsummary>
<desc>
<marker id="dump_log"></marker>
@@ -1156,7 +1156,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name>
+ <name since="">dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name>
<fsummary>Dumps all RAM tables to disc.</fsummary>
<desc>
<marker id="dump_tables"></marker>
@@ -1168,7 +1168,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>dump_to_textfile(Filename)</name>
+ <name since="">dump_to_textfile(Filename)</name>
<fsummary>Dumps local tables into a text file.</fsummary>
<desc>
<marker id="dump_to_textfile"></marker>
@@ -1181,7 +1181,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>error_description(Error) -> String</name>
+ <name since="">error_description(Error) -> String</name>
<fsummary>Returns a string describing a particular Mnesia error.</fsummary>
<desc>
<marker id="error_description"></marker>
@@ -1259,7 +1259,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
<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 +1278,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>first(Tab) -> Key | transaction abort</name>
+ <name since="">first(Tab) -> Key | transaction abort</name>
<fsummary>Returns the key for the first record in a table.</fsummary>
<desc>
<marker id="first"></marker>
@@ -1293,7 +1293,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>foldl(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name since="">foldl(Function, Acc, Table) -> NewAcc | transaction abort</name>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldl"></marker>
@@ -1306,7 +1306,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>foldr(Function, Acc, Table) -> NewAcc | transaction abort</name>
+ <name since="">foldr(Function, Acc, Table) -> NewAcc | transaction abort</name>
<fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary>
<desc>
<marker id="foldr"></marker>
@@ -1317,7 +1317,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>force_load_table(Tab) -> yes | ErrorDescription</name>
+ <name since="">force_load_table(Tab) -> yes | ErrorDescription</name>
<fsummary>Forces a table to be loaded into the system.</fsummary>
<desc>
<marker id="force_load_table"></marker>
@@ -1335,7 +1335,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>index_match_object(Pattern, Pos) -> transaction abort | ObjList</name>
+ <name since="">index_match_object(Pattern, Pos) -> transaction abort | ObjList</name>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_2"></marker>
@@ -1345,7 +1345,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name>
+ <name since="">index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name>
<fsummary>Matches records and uses index information.</fsummary>
<desc>
<marker id="index_match_object_4"></marker>
@@ -1377,7 +1377,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name>
+ <name since="">index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name>
<fsummary>Reads records through index table.</fsummary>
<desc>
<marker id="index_read"></marker>
@@ -1397,7 +1397,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>info() -> ok</name>
+ <name since="">info() -> ok</name>
<fsummary>Prints system information on the terminal.</fsummary>
<desc>
<marker id="info"></marker>
@@ -1408,7 +1408,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>install_fallback(Opaque) -> ok | {error,Reason}</name>
+ <name since="">install_fallback(Opaque) -> ok | {error,Reason}</name>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<marker id="install_fallback_1"></marker>
@@ -1417,7 +1417,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name>
+ <name since="">install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Calls <c>mnesia:install_fallback(Opaque, Args)</c>, where
@@ -1425,7 +1425,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>install_fallback(Opaque, Args) -> ok | {error,Reason}</name>
+ <name since="">install_fallback(Opaque, Args) -> ok | {error,Reason}</name>
<fsummary>Installs a backup as fallback.</fsummary>
<desc>
<p>Installs a backup as fallback. The fallback is used to
@@ -1483,7 +1483,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>is_transaction() -> boolean</name>
+ <name since="">is_transaction() -> boolean</name>
<fsummary>Checks if code is running in a transaction.</fsummary>
<desc>
<marker id="is_transaction"></marker>
@@ -1492,7 +1492,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>last(Tab) -> Key | transaction abort</name>
+ <name since="">last(Tab) -> Key | transaction abort</name>
<fsummary>Returns the key for the last record in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1503,7 +1503,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>load_textfile(Filename)</name>
+ <name since="">load_textfile(Filename)</name>
<fsummary>Loads tables from a text file.</fsummary>
<desc>
<marker id="load_textfile"></marker>
@@ -1516,7 +1516,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name>
+ <name since="">lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name>
<fsummary>Explicit grab lock.</fsummary>
<desc>
<marker id="lock"></marker>
@@ -1605,7 +1605,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>match_object(Pattern) -> transaction abort | RecList</name>
+ <name since="">match_object(Pattern) -> transaction abort | RecList</name>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_1"></marker>
@@ -1614,7 +1614,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name>
+ <name since="">match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name>
<fsummary>Matches <c>Pattern</c> for records.</fsummary>
<desc>
<marker id="match_object_3"></marker>
@@ -1639,7 +1639,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name>
+ <name since="">move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name>
<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 +1653,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>next(Tab, Key) -> Key | transaction abort</name>
+ <name since="">next(Tab, Key) -> Key | transaction abort</name>
<fsummary>Returns the next key in a table.</fsummary>
<desc>
<marker id="next"></marker>
@@ -1665,7 +1665,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>prev(Tab, Key) -> Key | transaction abort</name>
+ <name since="">prev(Tab, Key) -> Key | transaction abort</name>
<fsummary>Returns the previous key in a table.</fsummary>
<desc>
<p>Works exactly like
@@ -1676,7 +1676,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>read({Tab, Key}) -> transaction abort | RecordList</name>
+ <name since="">read({Tab, Key}) -> transaction abort | RecordList</name>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_2"></marker>
@@ -1684,14 +1684,14 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>read(Tab, Key) -> transaction abort | RecordList</name>
+ <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>read(Tab, Key, LockKind) -> transaction abort | RecordList</name>
+ <name since="">read(Tab, Key, LockKind) -> transaction abort | RecordList</name>
<fsummary>Reads records(s) with a given key.</fsummary>
<desc>
<marker id="read_3"></marker>
@@ -1716,7 +1716,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>read_lock_table(Tab) -> ok | transaction abort</name>
+ <name since="">read_lock_table(Tab) -> ok | transaction abort</name>
<fsummary>Sets a read lock on an entire table.</fsummary>
<desc>
<marker id="read_lock_table"></marker>
@@ -1725,7 +1725,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>report_event(Event) -> ok</name>
+ <name since="">report_event(Event) -> ok</name>
<fsummary>Reports a user event to the Mnesia event handler.</fsummary>
<desc>
<marker id="report_event"></marker>
@@ -1743,7 +1743,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name>
+ <name since="">restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name>
<fsummary>Online restore of backup.</fsummary>
<desc>
<marker id="restore"></marker>
@@ -1803,7 +1803,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>s_delete({Tab, Key}) -> ok | transaction abort</name>
+ <name since="">s_delete({Tab, Key}) -> ok | transaction abort</name>
<fsummary>Sets sticky lock and delete records.</fsummary>
<desc>
<marker id="s_delete"></marker>
@@ -1812,7 +1812,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>s_delete_object(Record) -> ok | transaction abort</name>
+ <name since="">s_delete_object(Record) -> ok | transaction abort</name>
<fsummary>Sets sticky lock and delete record.</fsummary>
<desc>
<marker id="s_delete_object"></marker>
@@ -1822,7 +1822,7 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>s_write(Record) -> ok | transaction abort</name>
+ <name since="">s_write(Record) -> ok | transaction abort</name>
<fsummary>Writes <c>Record</c> and sets sticky lock.</fsummary>
<desc>
<marker id="s_write"></marker>
@@ -1832,21 +1832,21 @@ mnesia:create_table(person,
</desc>
</func>
<func>
- <name>schema() -> ok</name>
+ <name since="">schema() -> ok</name>
<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>schema(Tab) -> ok</name>
+ <name since="">schema(Tab) -> ok</name>
<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>select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name>
+ <name since="">select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_2_3"></marker>
@@ -1884,7 +1884,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name since="">select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
<fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary>
<desc>
<marker id="select_4"></marker>
@@ -1907,7 +1907,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
+ <name since="">select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name>
<fsummary>Continues selecting objects.</fsummary>
<desc>
<p>Selects more objects with the match specification initiated
@@ -1919,7 +1919,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>set_debug_level(Level) -> OldLevel</name>
+ <name since="">set_debug_level(Level) -> OldLevel</name>
<fsummary>Changes the internal debug level of Mnesia.</fsummary>
<desc>
<marker id="set_debug_level"></marker>
@@ -1930,7 +1930,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>set_master_nodes(MasterNodes) -> ok | {error, Reason}</name>
+ <name since="">set_master_nodes(MasterNodes) -> ok | {error, Reason}</name>
<fsummary>Sets the master nodes for all tables.</fsummary>
<desc>
<marker id="set_master_nodes_1"></marker>
@@ -1943,7 +1943,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name>
+ <name since="">set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name>
<fsummary>Sets the master nodes for a table.</fsummary>
<desc>
<marker id="set_master_nodes_2"></marker>
@@ -1968,14 +1968,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name>
<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>snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name>
+ <name since="">snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name>
<fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -1990,7 +1990,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name>
+ <name since="">snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name>
<fsummary>Gets the index of the next lexicographical row.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2006,7 +2006,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name>
+ <name since="">snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name>
<fsummary>Retrieves a row indexed by an SNMP index.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2019,7 +2019,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code>
</desc>
</func>
<func>
- <name>snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Organizes a Mnesia table as an SNMP table.</fsummary>
<type>
<v>Tab ::= atom()</v>
@@ -2073,10 +2073,17 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>start() -> ok | {error, Reason}</name>
+ <name since="">start() -> ok | {error, Reason}</name>
<fsummary>Starts a local Mnesia system.</fsummary>
<desc>
<marker id="start"></marker>
+ <p>Mnesia startup is asynchronous. The function call
+ <c>mnesia:start()</c> returns the atom <c>ok</c> and then
+ starts to initialize the different tables. Depending on the
+ size of the database, this can take some time, and the
+ application programmer must wait for the tables that the
+ application needs before they can be used. This is achieved
+ by using the function <c>mnesia:wait_for_tables/2</c>.</p>
<p>The startup procedure for a set of Mnesia nodes is a
fairly complicated operation. A Mnesia system consists
of a set of nodes, with Mnesia started locally on all
@@ -2108,7 +2115,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>stop() -> stopped</name>
+ <name since="">stop() -> stopped</name>
<fsummary>Stops Mnesia locally.</fsummary>
<desc>
<marker id="stop"></marker>
@@ -2117,7 +2124,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name since="">subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="subscribe"></marker>
@@ -2127,7 +2134,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
+ <name since="">sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name>
<fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary>
<desc>
<marker id="sync_dirty"></marker>
@@ -2143,7 +2150,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>sync_log() -> ok | {error, Reason}</name>
+ <name since="OTP 17.0">sync_log() -> ok | {error, Reason}</name>
<fsummary>Performs a file sync of the local log file.</fsummary>
<desc>
<p>Ensures that the local transaction log file is synced to disk.
@@ -2153,7 +2160,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name since="">sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
<fsummary>Synchronously executes a transaction.</fsummary>
<desc>
<marker id="sync_transaction"></marker>
@@ -2166,7 +2173,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>system_info(InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name since="">system_info(InfoKey) -> Info | exit({aborted, Reason})</name>
<fsummary>Returns information about the Mnesia system.</fsummary>
<desc>
<marker id="system_info"></marker>
@@ -2353,7 +2360,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>table(Tab [,[Option]]) -> QueryHandle</name>
+ <name since="">table(Tab [,[Option]]) -> QueryHandle</name>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<marker id="table"></marker>
@@ -2408,7 +2415,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name>
+ <name since="">table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name>
<fsummary>Returns local information about table.</fsummary>
<desc>
<marker id="table_info"></marker>
@@ -2560,7 +2567,7 @@ mnesia:create_table(employee,
</desc>
</func>
<func>
- <name>transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
+ <name since="">transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name>
<fsummary>Executes a transaction.</fsummary>
<desc>
<marker id="transaction"></marker>
@@ -2628,7 +2635,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<marker id="transform_table_4"></marker>
@@ -2649,7 +2656,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name>
+ <name since="">transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Changes format on all records in table <c>Tab</c>.</fsummary>
<desc>
<p>Calls <c>mnesia:transform_table(Tab, Fun,
@@ -2658,7 +2665,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name>
+ <name since="">traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name>
<fsummary>Traversal of a backup.</fsummary>
<desc>
<marker id="traverse_backup"></marker>
@@ -2689,7 +2696,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>uninstall_fallback() -> ok | {error,Reason}</name>
+ <name since="">uninstall_fallback() -> ok | {error,Reason}</name>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<marker id="uninstall_fallback_0"></marker>
@@ -2698,7 +2705,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>uninstall_fallback(Args) -> ok | {error,Reason}</name>
+ <name since="">uninstall_fallback(Args) -> ok | {error,Reason}</name>
<fsummary>Uninstalls a fallback.</fsummary>
<desc>
<p>Deinstalls a fallback before it
@@ -2725,7 +2732,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
+ <name since="">unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name>
<fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary>
<desc>
<marker id="unsubscribe"></marker>
@@ -2735,7 +2742,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name>
+ <name since="">wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name>
<fsummary>Waits for tables to be accessible.</fsummary>
<desc>
<marker id="wait_for_tables"></marker>
@@ -2746,7 +2753,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>wread({Tab, Key}) -> transaction abort | RecordList</name>
+ <name since="">wread({Tab, Key}) -> transaction abort | RecordList</name>
<fsummary>Reads records with given key.</fsummary>
<desc>
<marker id="wread"></marker>
@@ -2754,7 +2761,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>write(Record) -> transaction abort | ok</name>
+ <name since="">write(Record) -> transaction abort | ok</name>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_1"></marker>
@@ -2763,7 +2770,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>write(Tab, Record, LockKind) -> transaction abort | ok</name>
+ <name since="">write(Tab, Record, LockKind) -> transaction abort | ok</name>
<fsummary>Writes a record into the database.</fsummary>
<desc>
<marker id="write_3"></marker>
@@ -2779,7 +2786,7 @@ raise(Name, Amount) ->
</desc>
</func>
<func>
- <name>write_lock_table(Tab) -> ok | transaction abort</name>
+ <name since="">write_lock_table(Tab) -> ok | transaction abort</name>
<fsummary>Sets write lock on an entire table.</fsummary>
<desc>
<marker id="write_lock_table"></marker>
diff --git a/lib/mnesia/doc/src/mnesia_frag_hash.xml b/lib/mnesia/doc/src/mnesia_frag_hash.xml
index c233acdb05..ccaba412d0 100644
--- a/lib/mnesia/doc/src/mnesia_frag_hash.xml
+++ b/lib/mnesia/doc/src/mnesia_frag_hash.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>mnesia_frag_hash.sgml</file>
</header>
- <module>mnesia_frag_hash</module>
+ <module since="">mnesia_frag_hash</module>
<modulesummary>Defines mnesia_frag_hash callback behavior</modulesummary>
<description>
<p>This module defines a callback behavior for user-defined hash
@@ -50,7 +50,7 @@
<funcs>
<func>
- <name>init_state(Tab, State) -> NewState | abort(Reason)</name>
+ <name since="">init_state(Tab, State) -> NewState | abort(Reason)</name>
<fsummary>Initiates the hash state for a new table.</fsummary>
<type>
<v>Tab = atom()</v>
@@ -72,7 +72,7 @@
</desc>
</func>
<func>
- <name>add_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
+ <name since="">add_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
<fsummary>Starts when a new fragment is added to a fragmented table.</fsummary>
<type>
<v>State = term()</v>
@@ -100,7 +100,7 @@
</desc>
</func>
<func>
- <name>del_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
+ <name since="">del_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name>
<fsummary>Starts when a fragment is deleted from a fragmented table.</fsummary>
<type>
<v>State = term()</v>
@@ -127,7 +127,7 @@
</desc>
</func>
<func>
- <name>key_to_frag_number(State, Key) -> FragNum | abort(Reason)</name>
+ <name since="">key_to_frag_number(State, Key) -> FragNum | abort(Reason)</name>
<fsummary>Resolves the key of a record into a fragment number.</fsummary>
<type>
<v>FragNum = integer()</v>
@@ -140,7 +140,7 @@
</desc>
</func>
<func>
- <name>match_spec_to_frag_numbers(State, MatchSpec) -> FragNums | abort(Reason)</name>
+ <name since="">match_spec_to_frag_numbers(State, MatchSpec) -> FragNums | abort(Reason)</name>
<fsummary>Resolves a <c>MatchSpec</c> into a list of fragment numbers.</fsummary>
<type>
<v>MatcSpec = ets_select_match_spec()</v>
diff --git a/lib/mnesia/doc/src/mnesia_registry.xml b/lib/mnesia/doc/src/mnesia_registry.xml
index a76f716981..18ddc4ab9e 100644
--- a/lib/mnesia/doc/src/mnesia_registry.xml
+++ b/lib/mnesia/doc/src/mnesia_registry.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>mnesia_registry.sgml</file>
</header>
- <module>mnesia_registry</module>
+ <module since="">mnesia_registry</module>
<modulesummary>Dump support for registries in erl_interface.</modulesummary>
<description>
<p>This module is usually part of the <c>erl_interface</c>
@@ -57,7 +57,7 @@
<funcs>
<func>
- <name>create_table(Tab) -> ok | exit(Reason)</name>
+ <name since="">create_table(Tab) -> ok | exit(Reason)</name>
<fsummary>Creates a registry table in Mnesia.</fsummary>
<desc>
<p>A wrapper function for <c>mnesia:create_table/2</c>,
@@ -73,7 +73,7 @@
</desc>
</func>
<func>
- <name>create_table(Tab, TabDef) -> ok | exit(Reason)</name>
+ <name since="">create_table(Tab, TabDef) -> ok | exit(Reason)</name>
<fsummary>Creates a customized registry table in Mnesia.</fsummary>
<desc>
<p>A wrapper function for <c>mnesia:create_table/2</c>,
diff --git a/lib/observer/doc/src/crashdump.xml b/lib/observer/doc/src/crashdump.xml
index 48f944cbce..62c6ff1f25 100644
--- a/lib/observer/doc/src/crashdump.xml
+++ b/lib/observer/doc/src/crashdump.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>crashdump.xml</file>
</header>
- <module>crashdump_viewer</module>
+ <module since="">crashdump_viewer</module>
<modulesummary>A WxWidgets based tool for browsing Erlang
crashdumps.</modulesummary>
<description>
@@ -46,8 +46,8 @@
</description>
<funcs>
<func>
- <name>start() -> ok</name>
- <name>start(File) -> ok</name>
+ <name since="">start() -> ok</name>
+ <name since="OTP 17.0">start(File) -> ok</name>
<fsummary>Start the Crashdump Viewer.</fsummary>
<type>
<v>File = string()</v>
@@ -62,7 +62,7 @@
</desc>
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="">stop() -> ok</name>
<fsummary>Terminate the Crashdump Viewer.</fsummary>
<desc>
<p>Terminates the Crashdump Viewer and closes
diff --git a/lib/observer/doc/src/etop.xml b/lib/observer/doc/src/etop.xml
index e7a83d0514..f0acc7b5d8 100644
--- a/lib/observer/doc/src/etop.xml
+++ b/lib/observer/doc/src/etop.xml
@@ -34,7 +34,7 @@
<rev></rev>
<file></file>
</header>
- <module>etop</module>
+ <module since="">etop</module>
<modulesummary>Erlang Top is a tool for presenting information about Erlang
processes similar to the information presented by "top" in UNIX.</modulesummary>
<description>
@@ -98,7 +98,7 @@
</description>
<funcs>
<func>
- <name>start() -> ok</name>
+ <name since="OTP R15B01">start() -> ok</name>
<fsummary>Start etop.</fsummary>
<desc>
<p>Starts <c>etop</c>.
@@ -106,7 +106,7 @@
</desc>
</func>
<func>
- <name>start(Options) -> ok</name>
+ <name since="OTP R15B01">start(Options) -> ok</name>
<fsummary>Start etop.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -120,7 +120,7 @@
</desc>
</func>
<func>
- <name>help() -> ok</name>
+ <name since="OTP R15B01">help() -> ok</name>
<fsummary>Display the etop help.</fsummary>
<desc>
<p>Displays the help of <c>etop</c> and
@@ -128,7 +128,7 @@
</desc>
</func>
<func>
- <name>config(Key,Value) -> Result</name>
+ <name since="">config(Key,Value) -> Result</name>
<fsummary>Change the configuration of the tool.</fsummary>
<type>
<v>Result = ok | {error,Reason}</v>
@@ -142,7 +142,7 @@
</desc>
</func>
<func>
- <name>dump(File) -> Result</name>
+ <name since="">dump(File) -> Result</name>
<fsummary>Dump the current display to a file.</fsummary>
<type>
<v>Result = ok | {error,Reason}</v>
@@ -153,7 +153,7 @@
</desc>
</func>
<func>
- <name>stop() -> stop</name>
+ <name since="">stop() -> stop</name>
<fsummary>Terminate etop.</fsummary>
<desc>
<p>Terminates <c>etop</c>.</p>
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index 4d1a9a4f55..22035af982 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,43 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Literals such as <c>#{"one"=>1}</c> dumped to a crash
+ dump would cause <c>crashdump_viewer</c> to crash.</p>
+ <p>
+ Own Id: OTP-15365 Aux Id: ERL-722 </p>
+ </item>
+ <item>
+ <p>
+ <c>crashdump_viewer</c> would sometimes crash when
+ processing a dump which was truncated in the
+ <c>literals</c> area. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15377</p>
+ </item>
+ <item>
+ <p>
+ Since OTP-20.2, <c>crashdump_viewer</c> was very slow
+ when opening a crash dump with many processes. An
+ ets:select per process could be removed, which improved
+ the performance a lot.</p>
+ <p>
+ A bug when parsing heap data in a crashdump caused
+ <c>crashdump_viewer</c> to crash when multiple <c>Yc</c>
+ lines referenced the same reference counted binary. This
+ is now corrected.</p>
+ <p>
+ Own Id: OTP-15391</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.8.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml
index 843be26ee1..7fb1dd044e 100644
--- a/lib/observer/doc/src/observer.xml
+++ b/lib/observer/doc/src/observer.xml
@@ -33,7 +33,7 @@
<rev>PA1</rev>
<file>observer.xml</file>
</header>
- <module>observer</module>
+ <module since="OTP R15B">observer</module>
<modulesummary>A GUI tool for observing an Erlang system.</modulesummary>
<description>
<p>Observer is a graphical tool for observing the characteristics of
@@ -48,7 +48,7 @@
</description>
<funcs>
<func>
- <name>start() -> ok</name>
+ <name since="OTP R15B">start() -> ok</name>
<fsummary>Start the Observer GUI.</fsummary>
<desc>
<p>Starts the Observer GUI.
diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml
index 7cd15e15d3..fee95e0b21 100644
--- a/lib/observer/doc/src/ttb.xml
+++ b/lib/observer/doc/src/ttb.xml
@@ -33,7 +33,7 @@
<rev>PA1</rev>
<file>ttb.xml</file>
</header>
- <module>ttb</module>
+ <module since="">ttb</module>
<modulesummary>A base for building trace tools for distributed systems.</modulesummary>
<description>
<p>The Trace Tool Builder, <c>ttb</c>, is a base for building trace
@@ -44,7 +44,7 @@
</description>
<funcs>
<func>
- <name>start_trace(Nodes, Patterns, FlagSpec, Opts) -> Result</name>
+ <name since="OTP R15B">start_trace(Nodes, Patterns, FlagSpec, Opts) -> Result</name>
<fsummary>Start a trace port on each specified node.</fsummary>
<type>
<v>Result = see p/2</v>
@@ -76,7 +76,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer() -> Result</name>
+ <name since="">tracer() -> Result</name>
<fsummary>Equivalent to tracer(node()).</fsummary>
<desc>
<p>Equivalent to <c>tracer(node())</c>.</p>
@@ -84,7 +84,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer(Shortcut) -> Result</name>
+ <name since="">tracer(Shortcut) -> Result</name>
<fsummary>Handy shortcuts for common tracing settings.</fsummary>
<type>
<v>Shortcut = shell | dbg</v>
@@ -97,7 +97,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer(Nodes) -> Result</name>
+ <name since="">tracer(Nodes) -> Result</name>
<fsummary>Equivalent to tracer(Nodes,[]).</fsummary>
<desc>
<p>Equivalent to <c>tracer(Nodes,[])</c>.</p>
@@ -105,7 +105,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tracer(Nodes,Opts) -> Result</name>
+ <name since="">tracer(Nodes,Opts) -> Result</name>
<fsummary>Start a trace port on each specified node.</fsummary>
<type>
<v>Result = {ok, ActivatedNodes} | {error,Reason}</v>
@@ -243,7 +243,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>p(Item,Flags) -> Return</name>
+ <name since="">p(Item,Flags) -> Return</name>
<fsummary>Set the specified trace flags on the specified processes or ports.</fsummary>
<type>
<v>Return = {ok,[{Item,MatchDesc}]}</v>
@@ -277,7 +277,8 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>tp, tpl, tpe, ctp, ctpl, ctpg, ctpe</name>
+ <name since="">tp, tpl, ctp, ctpl, ctpg</name>
+ <name since="OTP 19.0">tpe, ctpe</name>
<fsummary>Set and clear trace patterns.</fsummary>
<desc>
<p>These functions are to be used with trace
@@ -338,7 +339,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>list_history() -> History</name>
+ <name since="">list_history() -> History</name>
<fsummary>Return all calls stored in history.</fsummary>
<type>
<v>History = [{N,Func,Args}]</v>
@@ -352,7 +353,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>run_history(N) -> ok | {error, Reason}</name>
+ <name since="">run_history(N) -> ok | {error, Reason}</name>
<fsummary>Execute one entry of the history.</fsummary>
<type>
<v>N = integer() | [integer()]</v>
@@ -364,7 +365,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>write_config(ConfigFile,Config)</name>
+ <name since="">write_config(ConfigFile,Config)</name>
<fsummary>Equivalent to write_config(ConfigFile,Config,[]).</fsummary>
<desc>
<p>Equivalent to <c>write_config(ConfigFile,Config,[])</c>.</p>
@@ -372,7 +373,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>write_config(ConfigFile,Config,Opts) -> ok | {error,Reason}</name>
+ <name since="">write_config(ConfigFile,Config,Opts) -> ok | {error,Reason}</name>
<fsummary>Create a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -405,7 +406,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>run_config(ConfigFile) -> ok | {error,Reason}</name>
+ <name since="">run_config(ConfigFile) -> ok | {error,Reason}</name>
<fsummary>Execute all entries in a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -418,7 +419,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>run_config(ConfigFile,NumList) -> ok | {error,Reason}</name>
+ <name since="">run_config(ConfigFile,NumList) -> ok | {error,Reason}</name>
<fsummary>Execute selected entries from a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -437,7 +438,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>list_config(ConfigFile) -> Config | {error,Reason}</name>
+ <name since="">list_config(ConfigFile) -> Config | {error,Reason}</name>
<fsummary>List all entries in a configuration file.</fsummary>
<type>
<v>ConfigFile = string()</v>
@@ -449,7 +450,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>write_trace_info(Key,Info) -> ok</name>
+ <name since="">write_trace_info(Key,Info) -> ok</name>
<fsummary>Write any information to file <c>.ti</c>.</fsummary>
<type>
<v>Key = term()</v>
@@ -465,7 +466,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>seq_trigger_ms() -> MatchSpec</name>
+ <name since="">seq_trigger_ms() -> MatchSpec</name>
<fsummary>Equivalent to seq_trigger_ms(all).</fsummary>
<desc>
<p>Equivalent to <c>seq_trigger_ms(all)</c>.</p>
@@ -473,7 +474,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>seq_trigger_ms(Flags) -> MatchSpec</name>
+ <name since="">seq_trigger_ms(Flags) -> MatchSpec</name>
<fsummary>Return a match_spec() which starts sequential tracing.</fsummary>
<type>
<v>MatchSpec = match_spec()</v>
@@ -521,7 +522,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>stop()</name>
+ <name since="">stop()</name>
<fsummary>Equivalent to stop([]).</fsummary>
<desc>
<p>Equivalent to <c>stop([])</c>.</p>
@@ -529,7 +530,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>stop(Opts) -> stopped | {stopped, Dir}</name>
+ <name since="">stop(Opts) -> stopped | {stopped, Dir}</name>
<fsummary>Stop tracing and fetch/format logs from all nodes.</fsummary>
<type>
<v>Opts = Opt | [Opt]</v>
@@ -573,7 +574,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>get_et_handler()</name>
+ <name since="OTP R15B">get_et_handler()</name>
<fsummary>Return the <c>et</c> handler.</fsummary>
<desc>
<p>Returns the <c>et</c> handler, which can be used with <c>format/2</c>
@@ -583,7 +584,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>format(File)</name>
+ <name since="">format(File)</name>
<fsummary>Equivalent to <c>format(File,[])</c>.</fsummary>
<desc>
<p>Equivalent to <c>format(File,[])</c>.</p>
@@ -591,7 +592,7 @@ ttb:p(all, call).</input></pre>
</func>
<func>
- <name>format(File,Options) -> ok | {error, Reason}</name>
+ <name since="">format(File,Options) -> ok | {error, Reason}</name>
<fsummary>Format a binary trace log.</fsummary>
<type>
<v>File = string() | [string()]</v>
diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl
index ffef83227c..8956173c93 100644
--- a/lib/observer/src/cdv_html_wx.erl
+++ b/lib/observer/src/cdv_html_wx.erl
@@ -79,14 +79,14 @@ handle_info(active, #state{panel=HtmlWin,delayed_fetch=Callback}=State)
observer_lib:sync_destroy_progress_dialog(),
wx_misc:beginBusyCursor(),
wxHtmlWindow:setPage(HtmlWin,HtmlText),
- cdv_wx:set_status(TW),
+ cdv_wx_set_status(State, TW),
wx_misc:endBusyCursor(),
{noreply, State#state{expand_table=Tab,
delayed_fetch=undefined,
trunc_warn=TW}};
handle_info(active, State) ->
- cdv_wx:set_status(State#state.trunc_warn),
+ cdv_wx_set_status(State, State#state.trunc_warn),
{noreply, State};
handle_info(Info, State) ->
@@ -164,3 +164,10 @@ expand(Id,Callback,#state{expand_wins=Opened0, app=App}=State) ->
Opened0
end,
State#state{expand_wins=Opened}.
+
+cdv_wx_set_status(#state{app = cdv}, Status) ->
+ %% this module is used by the observer when cdw_wx isn't started
+ %% only try to set status when used by cdv
+ cdv_wx:set_status(Status);
+cdv_wx_set_status(_, _) ->
+ ok.
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index 0627c15b1c..97bb344cbf 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -589,9 +589,9 @@ truncated_here(Tag) ->
case get(truncated) of
true ->
case get(last_tag) of
- Tag -> % Tag == {TagType,Id}
+ {Tag,_Pos} -> % Tag == {TagType,Id}
true;
- {Tag,_Id} ->
+ {{Tag,_Id},_Pos} ->
true;
_LastTag ->
truncated_earlier(Tag)
@@ -846,8 +846,8 @@ do_read_file(File) ->
case check_dump_version(Id) of
{ok,DumpVsn} ->
reset_tables(),
- insert_index(Tag,Id,N1+1),
- put_last_tag(Tag,""),
+ insert_index(Tag,Id,Pos=N1+1),
+ put_last_tag(Tag,"",Pos),
DecodeOpts = get_decode_opts(DumpVsn),
indexify(Fd,DecodeOpts,Rest,N1),
end_progress(),
@@ -915,23 +915,11 @@ indexify(Fd,DecodeOpts,Bin,N) ->
_ ->
insert_index(Tag,Id,NewPos)
end,
- case put_last_tag(Tag,Id) of
- {?proc_heap,LastId} ->
- [{_,LastPos}] = lookup_index(?proc_heap,LastId),
+ case put_last_tag(Tag,Id,NewPos) of
+ {{?proc_heap,LastId},LastPos} ->
ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos});
- {?literals,[]} ->
- case get(truncated_reason) of
- undefined ->
- [{_,LastPos}] = lookup_index(?literals,[]),
- ets:insert(cdv_heap_file_chars,
- {literals,N+Start+1-LastPos});
- _ ->
- %% Literals are truncated. Make sure we never
- %% attempt to read in the literals. (Heaps that
- %% references literals will show markers for
- %% incomplete heaps, but will otherwise work.)
- delete_index(?literals, [])
- end;
+ {{?literals,[]},LastPos} ->
+ ets:insert(cdv_heap_file_chars,{literals,N+Start+1-LastPos});
_ -> ok
end,
indexify(Fd,DecodeOpts,Rest,N1);
@@ -973,10 +961,18 @@ tag(Fd,<<>>,N,Gat,Di,Now) ->
check_if_truncated() ->
case get(last_tag) of
- {?ende,_} ->
+ {{?ende,_},_} ->
put(truncated,false),
put(truncated_proc,false);
- TruncatedTag ->
+ {{?literals,[]},_} ->
+ put(truncated,true),
+ put(truncated_proc,false),
+ %% Literals are truncated. Make sure we never
+ %% attempt to read in the literals. (Heaps that
+ %% references literals will show markers for
+ %% incomplete heaps, but will otherwise work.)
+ delete_index(?literals, []);
+ {TruncatedTag,_} ->
put(truncated,true),
find_truncated_proc(TruncatedTag)
end.
@@ -984,7 +980,6 @@ check_if_truncated() ->
find_truncated_proc({Tag,_Id}) when Tag==?atoms;
Tag==?binary;
Tag==?instr_data;
- Tag==?literals;
Tag==?memory_status;
Tag==?memory_map ->
put(truncated_proc,false);
@@ -2870,12 +2865,12 @@ parse_heap_term("Yc"++Line0, Addr, DecodeOpts, D0) -> %Reference-counted binary.
SymbolicBin = {'#CDVBin',Start},
Term = cdvbin(Offset, Sz, SymbolicBin),
D1 = gb_trees:insert(Addr, Term, D0),
- D = gb_trees:insert(Binp, SymbolicBin, D1),
+ D = gb_trees:enter(Binp, SymbolicBin, D1),
{Term,Line,D};
[] ->
Term = '#CDVNonexistingBinary',
D1 = gb_trees:insert(Addr, Term, D0),
- D = gb_trees:insert(Binp, Term, D1),
+ D = gb_trees:enter(Binp, Term, D1),
{Term,Line,D}
end;
parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) -> %Sub binary.
@@ -2887,12 +2882,17 @@ parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) -> %Sub binary.
{Term,Line,D};
parse_heap_term("Mf"++Line0, Addr, DecodeOpts, D0) -> %Flatmap.
{Size,":"++Line1} = get_hex(Line0),
- {Keys,":"++Line2,D1} = parse_term(Line1, DecodeOpts, D0),
- {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []),
- Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []),
- Map = maps:from_list(Pairs),
- D = gb_trees:update(Addr, Map, D2),
- {Map,Line,D};
+ case parse_term(Line1, DecodeOpts, D0) of
+ {Keys,":"++Line2,D1} when is_tuple(Keys) ->
+ {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []),
+ Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []),
+ Map = maps:from_list(Pairs),
+ D = gb_trees:update(Addr, Map, D2),
+ {Map,Line,D};
+ {Incomplete,_Line,D1} ->
+ D = gb_trees:insert(Addr, Incomplete, D1),
+ {Incomplete,"",D}
+ end;
parse_heap_term("Mh"++Line0, Addr, DecodeOpts, D0) -> %Head node in a hashmap.
{MapSize,":"++Line1} = get_hex(Line0),
{N,":"++Line2} = get_hex(Line1),
@@ -3051,7 +3051,7 @@ do_deref_ptr(Lookup, Line, DecodeOpts, D0) ->
{Term,Line,D0};
none ->
put(incomplete_heap, true),
- {['#CDVIncompleteHeap'],Line,D0};
+ {'#CDVIncompleteHeap',Line,D0};
{line,Addr,NewLine} ->
D = parse_line(Addr, NewLine, DecodeOpts, D0),
do_deref_ptr(Lookup, Line, DecodeOpts, D)
@@ -3275,13 +3275,13 @@ tag_to_atom(UnknownTag) ->
%%%-----------------------------------------------------------------
%%% Store last tag for use when truncated, and reason if aborted
-put_last_tag(?abort,Reason) ->
- %% Don't overwrite the real last tag, and make sure to return
- %% the previous last tag.
- put(truncated_reason,Reason),
- get(last_tag);
-put_last_tag(Tag,Id) ->
- put(last_tag,{Tag,Id}).
+put_last_tag(?abort,Reason,_Pos) ->
+ %% Don't overwrite the real last tag, and don't return it either,
+ %% since that would make the caller of this function believe that
+ %% the tag was complete.
+ put(truncated_reason,Reason);
+put_last_tag(Tag,Id,Pos) ->
+ put(last_tag,{{Tag,Id},Pos}).
%%%-----------------------------------------------------------------
%%% Fetch next chunk from crashdump file
diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl
index dd50bddf38..d6b5eff9b5 100644
--- a/lib/observer/test/crashdump_helper.erl
+++ b/lib/observer/test/crashdump_helper.erl
@@ -64,6 +64,7 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) ->
put(ref,Ref),
put(pid,Pid),
put(bin,Bin),
+ put(proc_bins,create_proc_bins()),
put(bins,create_binaries()),
put(sub_bin,SubBin),
put(sub_bins,create_sub_binaries(get(bins))),
@@ -119,6 +120,23 @@ create_sub_binary(Bin, Start, LenSub) ->
<<_:Start/bytes,Sub:Len/bytes,_/bytes>> = Bin,
Sub.
+create_proc_bins() ->
+ Parent = self(),
+ Pid =
+ spawn(
+ fun() ->
+ %% Just reverse the list here, so this binary is not
+ %% confused with the one created in n1_proc/5 above,
+ %% which is used for testing truncation (see
+ %% crashdump_viewer_SUITE:truncate_dump_binary/1)
+ Bin = list_to_binary(lists:reverse(lists:seq(1, 255))),
+ <<A:65/bytes,B:65/bytes,C/bytes>> = Bin,
+ Parent ! {self(),{A,B,C}}
+ end),
+ receive
+ {Pid,ProcBins} -> ProcBins
+ end.
+
%%%
%%% Test dumping of maps. Dumping of maps only from OTP 20.2.
%%%
@@ -186,4 +204,4 @@ dump_persistent_terms() ->
create_persistent_terms() ->
persistent_term:put({?MODULE,first}, {pid,42.0}),
persistent_term:put({?MODULE,second}, [1,2,3]),
- persistent_term:get().
+ {persistent_term:get({?MODULE,first}),persistent_term:get({?MODULE,second})}.
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index ed53c46a0d..31cf7011d4 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -400,6 +400,13 @@ special(File,Procs) ->
crashdump_viewer:expand_binary({SOffset,SSize,SPos}),
io:format(" expand binary ok",[]),
+ ProcBins = proplists:get_value(proc_bins,Dict),
+ {['#CDVBin',0,65,ProcBin],
+ ['#CDVBin',65,65,ProcBin],
+ ['#CDVBin',130,125,ProcBin]} = ProcBins,
+ io:format(" ProcBins ok",[]),
+
+
Binaries = crashdump_helper:create_binaries(),
verify_binaries(Binaries, proplists:get_value(bins,Dict)),
io:format(" binaries ok",[]),
@@ -608,9 +615,8 @@ special(File,Procs) ->
#proc{dict=Dict} = ProcDetails,
%% io:format("~p\n", [Dict]),
- Pts1 = crashdump_helper:create_persistent_terms(),
- Pts2 = proplists:get_value(pts,Dict),
- true = lists:sort(Pts1) =:= lists:sort(Pts2),
+ Pts = crashdump_helper:create_persistent_terms(),
+ Pts = proplists:get_value(pts,Dict),
io:format(" persistent terms ok",[]),
ok;
_ ->
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 21c8f4e6c9..5ce0aca589 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.8.1
+OBSERVER_VSN = 2.8.2
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index 8c799f6ff1..fb4f61417e 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -2749,6 +2749,11 @@ static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle, Boolean ext
errmsg_buffer_size = errmsg_buffer_size - errmsg_size;
acc_errmsg_size = acc_errmsg_size + errmsg_size;
current_errmsg_pos = current_errmsg_pos + errmsg_size;
+ } else if(result == SQL_SUCCESS_WITH_INFO && errmsg_size >= errmsg_buffer_size) {
+ memcpy(diagnos.sqlState, current_sql_state, SQL_STATE_SIZE);
+ diagnos.nativeError = nativeError;
+ acc_errmsg_size = errmsg_buffer_size;
+ break;
} else {
break;
}
diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml
index 9760908855..1a3063e6ce 100644
--- a/lib/odbc/doc/src/odbc.xml
+++ b/lib/odbc/doc/src/odbc.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>odbc</module>
+ <module since="">odbc</module>
<modulesummary>Erlang ODBC application</modulesummary>
<description>
<p>This application provides an Erlang interface to communicate
@@ -130,8 +130,8 @@
</section>
<funcs>
<func>
- <name>commit(Ref, CommitMode) -></name>
- <name>commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name>
+ <name since="">commit(Ref, CommitMode) -></name>
+ <name since="">commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name>
<fsummary>Commits or rollbacks a transaction. </fsummary>
<type>
<v>Ref = connection_reference() </v>
@@ -145,7 +145,7 @@
</desc>
</func>
<func>
- <name>connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name>
+ <name since="">connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name>
<fsummary>Opens a connection to the database. </fsummary>
<type>
<v>ConnectStr = string()</v>
@@ -236,7 +236,7 @@
</desc>
</func>
<func>
- <name>disconnect(Ref) -> ok | {error, Reason} </name>
+ <name since="">disconnect(Ref) -> ok | {error, Reason} </name>
<fsummary>Closes a connection to a database. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -255,8 +255,8 @@
</desc>
</func>
<func>
- <name>describe_table(Ref, Table) -> </name>
- <name>describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name>
+ <name since="">describe_table(Ref, Table) -> </name>
+ <name since="">describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name>
<fsummary>Queries the database to find out the data types of the columns of the table <c>Table</c>. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -271,8 +271,8 @@
</desc>
</func>
<func>
- <name>first(Ref) -></name>
- <name>first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">first(Ref) -></name>
+ <name since="">first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the first row of the result set and positions a cursor at this row.</fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -287,8 +287,8 @@
</desc>
</func>
<func>
- <name>last(Ref) -></name>
- <name>last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">last(Ref) -></name>
+ <name since="">last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the last row of the result set and positions a cursor at this row. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -303,8 +303,8 @@
</desc>
</func>
<func>
- <name>next(Ref) -> </name>
- <name>next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">next(Ref) -> </name>
+ <name since="">next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the next row of the result set relative the current cursor position and positions the cursor at this row. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -325,8 +325,8 @@
</desc>
</func>
<func>
- <name>param_query(Ref, SQLQuery, Params) -> </name>
- <name>param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name>
+ <name since="">param_query(Ref, SQLQuery, Params) -> </name>
+ <name since="">param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name>
<fsummary>Executes a parameterized SQL query.</fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -353,8 +353,8 @@
</desc>
</func>
<func>
- <name>prev(Ref) -> </name>
- <name>prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">prev(Ref) -> </name>
+ <name since="">prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Returns the previous row of the result set relative the current cursor position and positions the cursor at this row. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -371,8 +371,8 @@
</func>
<func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="">start() -> </name>
+ <name since="">start(Type) -> ok | {error, Reason}</name>
<fsummary>Starts the odb application. </fsummary>
<type>
@@ -389,7 +389,7 @@
</func>
<func>
- <name>stop() -> ok </name>
+ <name since="">stop() -> ok </name>
<fsummary> Stops the odbc application.</fsummary>
<desc>
@@ -400,8 +400,8 @@
</func>
<func>
- <name>sql_query(Ref, SQLQuery) -> </name>
- <name>sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name>
+ <name since="">sql_query(Ref, SQLQuery) -> </name>
+ <name since="">sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name>
<fsummary>Executes a SQL query or a batch of SQL queries. If it is a SELECT query the result set is returned, on the format<c>{selected, ColNames, Rows}</c>. For other query types the tuple <c>{updated, NRows}</c>is returned, and for batched queries, if the driver supports them, this function can also return a list of result tuples.</fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -429,8 +429,8 @@
</desc>
</func>
<func>
- <name>select_count(Ref, SelectQuery) -> </name>
- <name>select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name>
+ <name since="">select_count(Ref, SelectQuery) -> </name>
+ <name since="">select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name>
<fsummary>Executes a SQL SELECT query and associates the result set with the connection. A cursor is positioned before the first row in the result set and the tuple <c>{ok, NrRows}</c>is returned. </fsummary>
<type>
<v>Ref = connection_reference()</v>
@@ -453,8 +453,8 @@
</desc>
</func>
<func>
- <name>select(Ref, Position, N) -></name>
- <name>select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
+ <name since="">select(Ref, Position, N) -></name>
+ <name since="">select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name>
<fsummary>Selects <c>N</c>consecutive rows of the result set.</fsummary>
<type>
<v>Ref = connection_reference()</v>
diff --git a/lib/os_mon/doc/src/cpu_sup.xml b/lib/os_mon/doc/src/cpu_sup.xml
index bada165a06..b7adb2bcd2 100644
--- a/lib/os_mon/doc/src/cpu_sup.xml
+++ b/lib/os_mon/doc/src/cpu_sup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>cpu_sup</module>
+ <module since="">cpu_sup</module>
<modulesummary>A CPU Load and CPU Utilization Supervisor Process</modulesummary>
<description>
<p><c>cpu_sup</c> is a process which supervises the CPU load
@@ -76,7 +76,7 @@
</description>
<funcs>
<func>
- <name>nprocs() -> UnixProcesses | {error, Reason}</name>
+ <name since="">nprocs() -> UnixProcesses | {error, Reason}</name>
<fsummary>Get the number of UNIX processes running on this host</fsummary>
<type>
<v>UnixProcesses = int()</v>
@@ -90,7 +90,7 @@
</desc>
</func>
<func>
- <name>avg1() -> SystemLoad | {error, Reason}</name>
+ <name since="">avg1() -> SystemLoad | {error, Reason}</name>
<fsummary>Get the system load average for the last minute</fsummary>
<type>
<v>SystemLoad = int()</v>
@@ -104,7 +104,7 @@
</desc>
</func>
<func>
- <name>avg5() -> SystemLoad | {error, Reason}</name>
+ <name since="">avg5() -> SystemLoad | {error, Reason}</name>
<fsummary>Get the system load average for the last five minutes</fsummary>
<type>
<v>SystemLoad = int()</v>
@@ -118,7 +118,7 @@
</desc>
</func>
<func>
- <name>avg15() -> SystemLoad | {error, Reason}</name>
+ <name since="">avg15() -> SystemLoad | {error, Reason}</name>
<fsummary>Get the system load average for the last fifteen minutes</fsummary>
<type>
<v>SystemLoad = int()</v>
@@ -132,7 +132,7 @@
</desc>
</func>
<func>
- <name>util() -> CpuUtil | {error, Reason}</name>
+ <name since="">util() -> CpuUtil | {error, Reason}</name>
<fsummary>Get the CPU utilization</fsummary>
<type>
<v>CpuUtil = float()</v>
@@ -156,7 +156,7 @@
</desc>
</func>
<func>
- <name>util(Opts) -> UtilSpec | {error, Reason}</name>
+ <name since="">util(Opts) -> UtilSpec | {error, Reason}</name>
<fsummary>Get the CPU utilization</fsummary>
<type>
<v>Opts = [detailed | per_cpu]</v>
diff --git a/lib/os_mon/doc/src/disksup.xml b/lib/os_mon/doc/src/disksup.xml
index 610ef2c907..116a6dfd19 100644
--- a/lib/os_mon/doc/src/disksup.xml
+++ b/lib/os_mon/doc/src/disksup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>disksup</module>
+ <module since="">disksup</module>
<modulesummary>A Disk Supervisor Process</modulesummary>
<description>
<p><c>disksup</c> is a process which supervises the available disk
@@ -92,7 +92,7 @@
</section>
<funcs>
<func>
- <name>get_disk_data() -> [DiskData]</name>
+ <name since="">get_disk_data() -> [DiskData]</name>
<fsummary>Get data for the disks in the system</fsummary>
<type>
<v>DiskData = {Id, KByte, Capacity}</v>
@@ -112,7 +112,7 @@
</desc>
</func>
<func>
- <name>get_check_interval() -> MS</name>
+ <name since="">get_check_interval() -> MS</name>
<fsummary>Get time interval, in milliseconds, for the periodic disk space check</fsummary>
<type>
<v>MS = int()</v>
@@ -123,7 +123,7 @@
</desc>
</func>
<func>
- <name>set_check_interval(Minutes) -> ok</name>
+ <name since="">set_check_interval(Minutes) -> ok</name>
<fsummary>Set time interval, in minutes, for the periodic disk space check</fsummary>
<type>
<v>Minutes = int()>=1</v>
@@ -138,7 +138,7 @@
</desc>
</func>
<func>
- <name>get_almost_full_threshold() -> Percent</name>
+ <name since="">get_almost_full_threshold() -> Percent</name>
<fsummary>Get threshold, in percent, for disk space utilization</fsummary>
<type>
<v>Percent = int()</v>
@@ -148,7 +148,7 @@
</desc>
</func>
<func>
- <name>set_almost_full_threshold(Float) -> ok</name>
+ <name since="">set_almost_full_threshold(Float) -> ok</name>
<fsummary>Set threshold, as percentage represented by a float, for disk space utilization</fsummary>
<type>
<v>Float = float(), 0=&lt;Float=&lt;1</v>
diff --git a/lib/os_mon/doc/src/memsup.xml b/lib/os_mon/doc/src/memsup.xml
index c669e4670a..51c78b07c2 100644
--- a/lib/os_mon/doc/src/memsup.xml
+++ b/lib/os_mon/doc/src/memsup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>memsup</module>
+ <module since="">memsup</module>
<modulesummary>A Memory Supervisor Process</modulesummary>
<description>
<p><c>memsup</c> is a process which supervises the memory usage for
@@ -127,7 +127,7 @@
</section>
<funcs>
<func>
- <name>get_memory_data() -> {Total,Allocated,Worst}</name>
+ <name since="">get_memory_data() -> {Total,Allocated,Worst}</name>
<fsummary>Get data for the memory in the system</fsummary>
<type>
<v>Total = Allocated = int()</v>
@@ -155,7 +155,7 @@
</desc>
</func>
<func>
- <name>get_system_memory_data() -> MemDataList</name>
+ <name since="">get_system_memory_data() -> MemDataList</name>
<fsummary>Get system dependent memory data</fsummary>
<type>
<v>MemDataList = [{Tag, Size}]</v>
@@ -216,7 +216,7 @@
</desc>
</func>
<func>
- <name>get_os_wordsize() -> Wordsize</name>
+ <name since="">get_os_wordsize() -> Wordsize</name>
<fsummary>Get the wordsize of running os.</fsummary>
<type>
<v>Wordsize = 32 | 64 | unsupported_os</v>
@@ -226,7 +226,7 @@
</desc>
</func>
<func>
- <name>get_check_interval() -> MS</name>
+ <name since="">get_check_interval() -> MS</name>
<fsummary>Get time interval, in milliseconds, for the periodic memory check</fsummary>
<type>
<v>MS = int()</v>
@@ -237,7 +237,7 @@
</desc>
</func>
<func>
- <name>set_check_interval(Minutes) -> ok</name>
+ <name since="">set_check_interval(Minutes) -> ok</name>
<fsummary>Set time interval, in minutes, for the periodic memory check</fsummary>
<type>
<v>Minutes = int()>0</v>
@@ -252,7 +252,7 @@
</desc>
</func>
<func>
- <name>get_procmem_high_watermark() -> int()</name>
+ <name since="">get_procmem_high_watermark() -> int()</name>
<fsummary>Get threshold, in percent, for process memory allocation</fsummary>
<desc>
<p>Returns the threshold, in percent, for process memory
@@ -260,7 +260,7 @@
</desc>
</func>
<func>
- <name>set_procmem_high_watermark(Float) -> ok</name>
+ <name since="">set_procmem_high_watermark(Float) -> ok</name>
<fsummary>Set threshold, as percentage represented by a float, for process memory allocation</fsummary>
<desc>
<p>Changes the threshold, given as a float, for process memory
@@ -273,7 +273,7 @@
</desc>
</func>
<func>
- <name>get_sysmem_high_watermark() -> int()</name>
+ <name since="">get_sysmem_high_watermark() -> int()</name>
<fsummary>Get threshold, in percent, for system memory allocation</fsummary>
<desc>
<p>Returns the threshold, in percent, for system memory
@@ -281,7 +281,7 @@
</desc>
</func>
<func>
- <name>set_sysmem_high_watermark(Float) -> ok</name>
+ <name since="">set_sysmem_high_watermark(Float) -> ok</name>
<fsummary>Set threshold, given as a float, for system memory allocation</fsummary>
<desc>
<p>Changes the threshold, given as a float, for system memory
@@ -294,7 +294,7 @@
</desc>
</func>
<func>
- <name>get_helper_timeout() -> Seconds</name>
+ <name since="">get_helper_timeout() -> Seconds</name>
<fsummary>Get the timeout value, in seconds, for memory checks</fsummary>
<type>
<v>Seconds = int()</v>
@@ -304,7 +304,7 @@
</desc>
</func>
<func>
- <name>set_helper_timeout(Seconds) -> ok</name>
+ <name since="">set_helper_timeout(Seconds) -> ok</name>
<fsummary>Set the timeout value, in seconds, for memory checks</fsummary>
<type>
<v>Seconds = int() (>= 1)</v>
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 0910b3c0f3..64e9f281e3 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -31,6 +31,24 @@
</header>
<p>This document describes the changes made to the OS_Mon application.</p>
+<section><title>Os_Mon 2.4.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Due to <c>/proc</c> restrictions in newer Android
+ releases enforced by SELinux, cpu_sup is fixed so that it
+ gets some basic CPU stats using the <c>sysinfo</c>
+ syscall rather than reading <c>/proc/loadavg</c>.</p>
+ <p>
+ Own Id: OTP-15387 Aux Id: PR-1966 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.4.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/os_mon/doc/src/nteventlog.xml b/lib/os_mon/doc/src/nteventlog.xml
index d32427227c..08cf165a24 100644
--- a/lib/os_mon/doc/src/nteventlog.xml
+++ b/lib/os_mon/doc/src/nteventlog.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>nteventlog</module>
+ <module since="">nteventlog</module>
<modulesummary>Interface to Windows Event Log</modulesummary>
<description>
<p><c>nteventlog</c> provides a generic interface to the Windows
@@ -61,8 +61,8 @@
</description>
<funcs>
<func>
- <name>start(Identifier, MFA) -> Result</name>
- <name>start_link(Identifier, MFA) -> Result</name>
+ <name since="">start(Identifier, MFA) -> Result</name>
+ <name since="">start_link(Identifier, MFA) -> Result</name>
<fsummary>Start the NT eventlog server</fsummary>
<type>
<v>Identifier = string() | atom()</v>
@@ -82,7 +82,7 @@
</desc>
</func>
<func>
- <name>stop() -> stopped</name>
+ <name since="">stop() -> stopped</name>
<fsummary>Stop the NT eventlog server</fsummary>
<type>
<v>Result = stopped</v>
diff --git a/lib/os_mon/doc/src/os_sup.xml b/lib/os_mon/doc/src/os_sup.xml
index d517f387b4..4a84165a6c 100644
--- a/lib/os_mon/doc/src/os_sup.xml
+++ b/lib/os_mon/doc/src/os_sup.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>os_sup</module>
+ <module since="">os_sup</module>
<modulesummary>Interface to OS System Messages</modulesummary>
<description>
<p><c>os_sup</c> is a process providing a message passing service
@@ -159,8 +159,8 @@
</section>
<funcs>
<func>
- <name>enable() -> ok | {error, Res}</name>
- <name>enable(Dir, Conf) -> ok | {error, Error}</name>
+ <name since="">enable() -> ok | {error, Res}</name>
+ <name since="">enable(Dir, Conf) -> ok | {error, Error}</name>
<fsummary>Enable the service (Solaris only)</fsummary>
<type>
<v>Dir = Conf = Res = string()</v>
@@ -194,8 +194,8 @@
</desc>
</func>
<func>
- <name>disable() -> ok | {error, Res}</name>
- <name>disable(Dir, Conf) -> ok | {error, Error}</name>
+ <name since="">disable() -> ok | {error, Res}</name>
+ <name since="">disable(Dir, Conf) -> ok | {error, Error}</name>
<fsummary>Disable the service (Solaris only)</fsummary>
<type>
<v>Dir = Conf = Res = string()</v>
diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk
index 0c62c3db35..9713f6bc6b 100644
--- a/lib/os_mon/vsn.mk
+++ b/lib/os_mon/vsn.mk
@@ -1 +1 @@
-OS_MON_VSN = 2.4.6
+OS_MON_VSN = 2.4.7
diff --git a/lib/parsetools/doc/src/leex.xml b/lib/parsetools/doc/src/leex.xml
index 1227625287..3b82f60201 100644
--- a/lib/parsetools/doc/src/leex.xml
+++ b/lib/parsetools/doc/src/leex.xml
@@ -21,7 +21,7 @@
<rev>A</rev>
<file>leex.xml</file>
</header>
- <module>leex</module>
+ <module since="">leex</module>
<modulesummary>Lexical analyzer generator for Erlang</modulesummary>
<description>
<p>A regular expression based lexical analyzer generator for
@@ -38,7 +38,8 @@ Token = tuple()</code>
</section>
<funcs>
<func>
- <name>file(FileName, [, Options]) -> LeexRet</name>
+ <name since="">file(FileName) -> LeexRet</name>
+ <name since="OTP R16B02">file(FileName, Options) -> LeexRet</name>
<fsummary>Generate a lexical analyzer</fsummary>
<type>
<v>FileName = filename()</v>
@@ -124,7 +125,7 @@ Token = tuple()</code>
</desc>
</func>
<func>
- <name>format_error(ErrorInfo) -> Chars</name>
+ <name since="">format_error(ErrorInfo) -> Chars</name>
<fsummary>Return an English description of a an error tuple.</fsummary>
<type>
<v>Chars = [char() | Chars]</v>
@@ -145,8 +146,8 @@ Token = tuple()</code>
<funcs>
<func>
- <name>string(String) -> StringRet</name>
- <name>string(String, StartLine) -> StringRet</name>
+ <name since="">string(String) -> StringRet</name>
+ <name since="">string(String, StartLine) -> StringRet</name>
<fsummary>Generated by Leex</fsummary>
<type>
<v>String = string()</v>
@@ -163,9 +164,9 @@ Token = tuple()</code>
</func>
<func>
- <name>token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
+ <name since="">token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars}
</name>
- <name>token(Cont, Chars, StartLine) -> {more,Cont1}
+ <name since="">token(Cont, Chars, StartLine) -> {more,Cont1}
| {done,TokenRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
@@ -198,9 +199,9 @@ io:request(InFile, {get_until,Prompt,Module,token,[Line]})
</func>
<func>
- <name>tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
+ <name since="">tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars}
</name>
- <name>tokens(Cont, Chars, StartLine) ->
+ <name since="">tokens(Cont, Chars, StartLine) ->
{more,Cont1} | {done,TokensRet,RestChars}
</name>
<fsummary>Generated by Leex</fsummary>
diff --git a/lib/parsetools/doc/src/yecc.xml b/lib/parsetools/doc/src/yecc.xml
index 5f95b5c150..67a2c95c25 100644
--- a/lib/parsetools/doc/src/yecc.xml
+++ b/lib/parsetools/doc/src/yecc.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>yecc.sgml</file>
</header>
- <module>yecc</module>
+ <module since="">yecc</module>
<modulesummary>LALR-1 Parser Generator</modulesummary>
<description>
<p>An LALR-1 parser generator for Erlang, similar to <c>yacc</c>.
@@ -46,7 +46,7 @@
</description>
<funcs>
<func>
- <name>file(Grammarfile [, Options]) -> YeccRet</name>
+ <name since="">file(Grammarfile [, Options]) -> YeccRet</name>
<fsummary>Give information about resolved and unresolved parse action conflicts.</fsummary>
<type>
<v>Grammarfile = filename()</v>
@@ -137,7 +137,7 @@
</desc>
</func>
<func>
- <name>format_error(Reason) -> Chars</name>
+ <name since="">format_error(Reason) -> Chars</name>
<fsummary>Return an English description of a an error tuple.</fsummary>
<type>
<v>Reason =&nbsp;-&nbsp;as returned by yecc:file/1,2&nbsp;-</v>
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index 7ed60ed3ca..b3e6023c41 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,24 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.6.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added ed25519 and ed448 sign/verify.</p>
+ <p>
+ Requires OpenSSL 1.1.1 or higher as cryptolib under the
+ OTP application <c>crypto</c>.</p>
+ <p>
+ Own Id: OTP-15419 Aux Id: OTP-15094 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.6.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index 9523a3b7c2..4c61139197 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -31,7 +31,7 @@
<date></date>
<rev></rev>
</header>
- <module>public_key</module>
+ <module since="">public_key</module>
<modulesummary>API module for public-key infrastructure.</modulesummary>
<description>
<p>Provides functions to handle public-key infrastructure,
@@ -176,7 +176,7 @@
<funcs>
<func>
- <name name="compute_key" arity="2"/>
+ <name name="compute_key" arity="2" since="OTP R16B01"/>
<fsummary>Computes shared secret.</fsummary>
<desc>
<p>Computes shared secret.</p>
@@ -184,7 +184,7 @@
</func>
<func>
- <name name="compute_key" arity="3"/>
+ <name name="compute_key" arity="3" since="OTP R16B01"/>
<fsummary>Computes shared secret.</fsummary>
<desc>
<p>Computes shared secret.</p>
@@ -192,8 +192,8 @@
</func>
<func>
- <name name="decrypt_private" arity="2"/>
- <name name="decrypt_private" arity="3"/>
+ <name name="decrypt_private" arity="2" since="OTP R14B"/>
+ <name name="decrypt_private" arity="3" since="OTP R14B"/>
<fsummary>Public-key decryption.</fsummary>
<desc>
<p>Public-key decryption using the private key. See also <seealso
@@ -202,8 +202,8 @@
</func>
<func>
- <name name="decrypt_public" arity="2"/>
- <name name="decrypt_public" arity="3"/>
+ <name name="decrypt_public" arity="2" since="OTP R14B"/>
+ <name name="decrypt_public" arity="3" since="OTP R14B"/>
<fsummary>Public-key decryption.</fsummary>
<desc>
<p>Public-key decryption using the public key. See also <seealso
@@ -212,7 +212,7 @@
</func>
<func>
- <name name="der_decode" arity="2"/>
+ <name name="der_decode" arity="2" since="OTP R14B"/>
<fsummary>Decodes a public-key ASN.1 DER encoded entity.</fsummary>
<desc>
<p>Decodes a public-key ASN.1 DER encoded entity.</p>
@@ -220,7 +220,7 @@
</func>
<func>
- <name name="der_encode" arity="2"/>
+ <name name="der_encode" arity="2" since="OTP R14B"/>
<fsummary>Encodes a public-key entity with ASN.1 DER encoding.</fsummary>
<desc>
<p>Encodes a public-key entity with ASN.1 DER encoding.</p>
@@ -228,7 +228,7 @@
</func>
<func>
- <name name="dh_gex_group" arity="4"/>
+ <name name="dh_gex_group" arity="4" since="OTP 18.2"/>
<fsummary>Selects a group for Diffie-Hellman key exchange</fsummary>
<desc>
<p>Selects a group for Diffie-Hellman key exchange with the key size in the range <c>MinSize...MaxSize</c>
@@ -249,8 +249,8 @@
</func>
<func>
- <name name="encrypt_private" arity="2"/>
- <name name="encrypt_private" arity="3"/>
+ <name name="encrypt_private" arity="2" since="OTP R14B"/>
+ <name name="encrypt_private" arity="3" since="OTP 21.1"/>
<fsummary>Public-key encryption using the private key.</fsummary>
<desc>
<p>Public-key encryption using the private key.
@@ -260,8 +260,8 @@
</func>
<func>
- <name name="encrypt_public" arity="2"/>
- <name name="encrypt_public" arity="3"/>
+ <name name="encrypt_public" arity="2" since="OTP R14B"/>
+ <name name="encrypt_public" arity="3" since="OTP 21.1"/>
<fsummary>Public-key encryption using the public key.</fsummary>
<desc>
<p>Public-key encryption using the public key. See also <seealso
@@ -270,7 +270,7 @@
</func>
<func>
- <name name="generate_key" arity="1"/>
+ <name name="generate_key" arity="1" since="OTP R16B01"/>
<fsummary>Generates a new keypair.</fsummary>
<desc>
<p>Generates a new keypair. Note that except for Diffie-Hellman
@@ -281,7 +281,7 @@
</func>
<func>
- <name name="pem_decode" arity="1"/>
+ <name name="pem_decode" arity="1" since="OTP R14B"/>
<fsummary>Decodes PEM binary data and returns
entries as ASN.1 DER encoded entities.</fsummary>
<desc>
@@ -291,7 +291,7 @@
</func>
<func>
- <name name="pem_encode" arity="1"/>
+ <name name="pem_encode" arity="1" since="OTP R14B"/>
<fsummary>Creates a PEM binary.</fsummary>
<desc>
<p>Creates a PEM binary.</p>
@@ -299,8 +299,8 @@
</func>
<func>
- <name name="pem_entry_decode" arity="1"/>
- <name name="pem_entry_decode" arity="2"/>
+ <name name="pem_entry_decode" arity="1" since="OTP R14B"/>
+ <name name="pem_entry_decode" arity="2" since="OTP R14B"/>
<fsummary>Decodes a PEM entry.</fsummary>
<desc>
<p>Decodes a PEM entry. <c>pem_decode/1</c> returns a list of PEM
@@ -311,8 +311,8 @@
</func>
<func>
- <name name="pem_entry_encode" arity="2"/>
- <name name="pem_entry_encode" arity="3"/>
+ <name name="pem_entry_encode" arity="2" since="OTP R14B"/>
+ <name name="pem_entry_encode" arity="3" since="OTP R14B"/>
<fsummary>Creates a PEM entry that can be fed to <c>pem_encode/1</c>.</fsummary>
<desc>
<p>Creates a PEM entry that can be feed to <c>pem_encode/1</c>.</p>
@@ -326,7 +326,7 @@
</func>
<func>
- <name name="pkix_decode_cert" arity="2"/>
+ <name name="pkix_decode_cert" arity="2" since=""/>
<fsummary>Decodes an ASN.1 DER-encoded PKIX x509 certificate.</fsummary>
<desc>
<p>Decodes an ASN.1 DER-encoded PKIX certificate. Option <c>otp</c>
@@ -337,7 +337,7 @@
</func>
<func>
- <name name="pkix_encode" arity="3"/>
+ <name name="pkix_encode" arity="3" since="OTP R14B"/>
<fsummary>DER encodes a PKIX x509 certificate or part of such a
certificate.</fsummary>
<desc>
@@ -349,7 +349,7 @@
</func>
<func>
- <name name="pkix_is_issuer" arity="2"/>
+ <name name="pkix_is_issuer" arity="2" since="OTP R14B"/>
<fsummary>Checks if <c>IssuerCert</c> issued <c>Cert</c>.</fsummary>
<desc>
<p>Checks if <c>IssuerCert</c> issued <c>Cert</c>.</p>
@@ -357,7 +357,7 @@
</func>
<func>
- <name name="pkix_is_fixed_dh_cert" arity="1"/>
+ <name name="pkix_is_fixed_dh_cert" arity="1" since="OTP R14B"/>
<fsummary>Checks if a certificate is a fixed Diffie-Hellman certificate.</fsummary>
<desc>
<p>Checks if a certificate is a fixed Diffie-Hellman certificate.</p>
@@ -365,7 +365,7 @@
</func>
<func>
- <name name="pkix_is_self_signed" arity="1"/>
+ <name name="pkix_is_self_signed" arity="1" since="OTP R14B"/>
<fsummary>Checks if a certificate is self-signed.</fsummary>
<desc>
<p>Checks if a certificate is self-signed.</p>
@@ -373,7 +373,7 @@
</func>
<func>
- <name name="pkix_issuer_id" arity="2"/>
+ <name name="pkix_issuer_id" arity="2" since="OTP R14B"/>
<fsummary>Returns the issuer id.</fsummary>
<desc>
<p>Returns the issuer id.</p>
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="pkix_normalize_name" arity="1"/>
+ <name name="pkix_normalize_name" arity="1" since="OTP R14B"/>
<fsummary>Normalizes an issuer name so that it can be easily
compared to another issuer name.</fsummary>
<desc>
@@ -391,7 +391,7 @@
</func>
<func>
- <name>pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name>
+ <name since="OTP R16B">pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name>
<fsummary>Performs a basic path validation according to RFC 5280.</fsummary>
<type>
<v>TrustedCert = #'OTPCertificate'{} | der_encoded() | atom()</v>
@@ -491,7 +491,7 @@ fun(OtpCert :: #'OTPCertificate'{},
</func>
<func>
- <name name="pkix_crl_issuer" arity="1"/>
+ <name name="pkix_crl_issuer" arity="1" since="OTP 17.5"/>
<fsummary>Returns the issuer of the <c>CRL</c>.</fsummary>
<desc>
<p>Returns the issuer of the <c>CRL</c>.</p>
@@ -499,7 +499,7 @@ fun(OtpCert :: #'OTPCertificate'{},
</func>
<func>
- <name name="pkix_crls_validate" arity="3"/>
+ <name name="pkix_crls_validate" arity="3" since="OTP R16B"/>
<fsummary>Performs CRL validation.</fsummary>
<desc>
<p>Performs CRL validation. It is intended to be called from
@@ -551,7 +551,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_crl_verify" arity="2"/>
+ <name name="pkix_crl_verify" arity="2" since="OTP 17.5"/>
<fsummary> Verify that <c>Cert</c> is the <c> CRL</c> signer. </fsummary>
<desc>
<p>Verify that <c>Cert</c> is the <c>CRL</c> signer.</p>
@@ -559,7 +559,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_dist_point" arity="1"/>
+ <name name="pkix_dist_point" arity="1" since="OTP 17.5"/>
<fsummary>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.</fsummary>
<desc>
<p>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.
@@ -570,7 +570,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_dist_points" arity="1"/>
+ <name name="pkix_dist_points" arity="1" since="OTP 17.5"/>
<fsummary> Extracts distribution points from the certificates extensions.</fsummary>
<desc>
<p> Extracts distribution points from the certificates extensions.</p>
@@ -578,7 +578,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_match_dist_point" arity="2"/>
+ <name name="pkix_match_dist_point" arity="2" since="OTP 19.0"/>
<fsummary>Checks whether the given distribution point matches the
Issuing Distribution Point of the CRL.</fsummary>
<desc>
@@ -590,7 +590,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_sign" arity="2"/>
+ <name name="pkix_sign" arity="2" since="OTP R14B"/>
<fsummary>Signs certificate.</fsummary>
<desc>
<p>Signs an 'OTPTBSCertificate'. Returns the corresponding
@@ -599,7 +599,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_sign_types" arity="1"/>
+ <name name="pkix_sign_types" arity="1" since="OTP R16B01"/>
<fsummary>Translates signature algorithm OID to Erlang digest and signature algorithm types.</fsummary>
<desc>
<p>Translates signature algorithm OID to Erlang digest and signature types.
@@ -609,8 +609,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name>pkix_test_data(Options) -> Config </name>
- <name>pkix_test_data([chain_opts()]) -> [conf_opt()]</name>
+ <name since="OTP 20.1">pkix_test_data(Options) -> Config </name>
+ <name since="OTP 20.1">pkix_test_data([chain_opts()]) -> [conf_opt()]</name>
<fsummary>Creates certificate test data.</fsummary>
<type>
<v>Options = #{chain_type() := chain_opts()} </v>
@@ -742,7 +742,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name>pkix_test_root_cert(Name, Options) -> RootCert</name>
+ <name since="OTP 20.2">pkix_test_root_cert(Name, Options) -> RootCert</name>
<fsummary>Generates a test data root cert.</fsummary>
<type>
<v>Name = string()</v>
@@ -772,7 +772,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name name="pkix_verify" arity="2"/>
+ <name name="pkix_verify" arity="2" since="OTP R14B"/>
<fsummary>Verifies PKIX x.509 certificate signature.</fsummary>
<desc>
<p>Verifies PKIX x.509 certificate signature.</p>
@@ -780,8 +780,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{},
</func>
<func>
- <name>pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()</name>
- <name>pkix_verify_hostname(Cert, ReferenceIDs, Opts) -> boolean()</name>
+ <name since="OTP 19.3">pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()</name>
+ <name since="OTP 19.3">pkix_verify_hostname(Cert, ReferenceIDs, Opts) -> boolean()</name>
<fsummary>Verifies that a PKIX x.509 certificate <i>presented identifier</i> (e.g hostname) is
an expected one.</fsummary>
<type>
@@ -864,7 +864,7 @@ end
</func>
<func>
- <name>pkix_verify_hostname_match_fun(Protcol) -> fun(RefId | FQDN::string(), PresentedID) -> boolean() | default</name>
+ <name since="OTP 21.0">pkix_verify_hostname_match_fun(Protcol) -> fun(RefId | FQDN::string(), PresentedID) -> boolean() | default</name>
<fsummary>Returns a fun that is intendended as argument to the match_fun option in pkix_verify_hostname/3.
</fsummary>
<type>
@@ -889,8 +889,8 @@ end
<func>
- <name name="sign" arity="3"/>
- <name name="sign" arity="4"/>
+ <name name="sign" arity="3" since=""/>
+ <name name="sign" arity="4" since="OTP 20.1"/>
<fsummary>Creates a digital signature.</fsummary>
<desc>
<p>Creates a digital signature.</p>
@@ -901,7 +901,7 @@ end
</func>
<func>
- <name name="ssh_decode" arity="2"/>
+ <name name="ssh_decode" arity="2" since="OTP R14B03"/>
<fsummary>Decodes an SSH file-binary.</fsummary>
<desc>
<p>Decodes an SSH file-binary. In the case of <c>known_hosts</c> or
@@ -933,7 +933,7 @@ end
</func>
<func>
- <name name="ssh_encode" arity="2"/>
+ <name name="ssh_encode" arity="2" since="OTP R14B03"/>
<fsummary>Encodes a list of SSH file entries to a binary.</fsummary>
<desc>
<p>Encodes a list of SSH file entries (public keys and attributes) to a binary. Possible
@@ -947,9 +947,9 @@ end
</func>
<func>
- <name>ssh_hostkey_fingerprint(HostKey) -> string()</name>
- <name>ssh_hostkey_fingerprint(DigestType, HostKey) -> string()</name>
- <name>ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name>
+ <name since="OTP 19.2">ssh_hostkey_fingerprint(HostKey) -> string()</name>
+ <name since="OTP 19.2">ssh_hostkey_fingerprint(DigestType, HostKey) -> string()</name>
+ <name since="OTP 19.2">ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name>
<fsummary>Calculates a ssh fingerprint for a hostkey.</fsummary>
<type>
<v>HostKey = <seealso marker="#type-public_key">public_key()</seealso></v>
@@ -982,8 +982,8 @@ end
</func>
<func>
- <name name="verify" arity="4"/>
- <name name="verify" arity="5"/>
+ <name name="verify" arity="4" since="OTP R14B"/>
+ <name name="verify" arity="5" since="OTP 20.1"/>
<fsummary>Verifies a digital signature.</fsummary>
<desc>
<p>Verifies a digital signature.</p>
@@ -993,7 +993,7 @@ end
</func>
<func>
- <name name="short_name_hash" arity="1"/>
+ <name name="short_name_hash" arity="1" since="OTP 19.0"/>
<fsummary>Generates a short hash of an issuer name.</fsummary>
<desc>
<p>Generates a short hash of an issuer name. The hash is
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 96eaf4f962..5e2643f0ea 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.6.3
+PUBLIC_KEY_VSN = 1.6.4
diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml
index e201ad4e23..165ae6db6a 100644
--- a/lib/reltool/doc/src/notes.xml
+++ b/lib/reltool/doc/src/notes.xml
@@ -38,7 +38,25 @@
thus constitutes one section in this document. The title of each
section is the version number of Reltool.</p>
- <section><title>Reltool 0.7.7</title>
+ <section><title>Reltool 0.7.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Reltool would earlier erroneously split paths like
+ <c>"c:\foo"</c> into <c>["c","\foo"]</c> when reading the
+ <c>$ERL_LIBS</c> variable on windows. This is now
+ corrected.</p>
+ <p>
+ Own Id: OTP-15454</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Reltool 0.7.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml
index 874cda8369..5afbad0ba8 100644
--- a/lib/reltool/doc/src/reltool.xml
+++ b/lib/reltool/doc/src/reltool.xml
@@ -33,7 +33,7 @@
<date></date>
<rev>%VSN%</rev>
</header>
- <module>reltool</module>
+ <module since="">reltool</module>
<modulesummary>Main API of the Reltool application</modulesummary>
<description>
<p>This is an interface module for the Reltool application.</p>
@@ -591,7 +591,7 @@ target_spec() = [target_spec()]
<funcs>
<func>
- <name>create_target(Server, TargetDir) -> ok | {error, Reason}</name>
+ <name since="">create_target(Server, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Create a target system</fsummary>
<type>
<v>Server = server()</v>
@@ -604,7 +604,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name>
+ <name since="">eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Create a target system</fsummary>
<type>
<v>TargetSpec = target_spec()</v>
@@ -655,7 +655,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_config(Server) -> {ok, Config} | {error, Reason}</name>
+ <name since="">get_config(Server) -> {ok, Config} | {error, Reason}</name>
<fsummary>Get reltool configuration</fsummary>
<type>
<v>Server = server()</v>
@@ -667,7 +667,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name>
+ <name since="">get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name>
<fsummary>Get reltool configuration</fsummary>
<type>
<v>Server = server()</v>
@@ -685,7 +685,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name>
+ <name since="">get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name>
<fsummary>Get contents of a release file</fsummary>
<type>
<v>Server = server()</v>
@@ -698,7 +698,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name>
+ <name since="">get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name>
<fsummary>Get contents of a boot script file</fsummary>
<type>
<v>Server = server()</v>
@@ -711,7 +711,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_status(Server) -> {ok, [Warning]} | {error, Reason}</name>
+ <name since="OTP R14B">get_status(Server) -> {ok, [Warning]} | {error, Reason}</name>
<fsummary>Get contents of a release file</fsummary>
<type>
<v>Server = server()</v>
@@ -722,7 +722,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name>
+ <name since="">get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name>
<fsummary>Start server process with options</fsummary>
<type>
<v>WindowPid = window_pid()</v>
@@ -733,7 +733,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>get_target_spec(Server) -> {ok, TargetSpec} | {error, Reason}</name>
+ <name since="">get_target_spec(Server) -> {ok, TargetSpec} | {error, Reason}</name>
<fsummary>Return a specification of the target system</fsummary>
<type>
<v>Server = server()</v>
@@ -747,7 +747,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>install(RelName, TargetDir) -> ok | {error, Reason}</name>
+ <name since="">install(RelName, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Install a target system</fsummary>
<type>
<v>RelName = rel_name()</v>
@@ -758,7 +758,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start() -> {ok, WindowPid} | {error, Reason}</name>
+ <name since="">start() -> {ok, WindowPid} | {error, Reason}</name>
<fsummary>Start main window process</fsummary>
<type>
<v>WindowPid = window_pid()</v>
@@ -768,7 +768,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start(Options) -> {ok, WindowPid} | {error, Reason}</name>
+ <name since="">start(Options) -> {ok, WindowPid} | {error, Reason}</name>
<fsummary>Start main window process with options</fsummary>
<type>
<v>Options = options()</v>
@@ -779,7 +779,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start_link(Options) -> {ok, WindowPid} | {error, Reason}</name>
+ <name since="">start_link(Options) -> {ok, WindowPid} | {error, Reason}</name>
<fsummary>Start main window process with options</fsummary>
<type>
<v>Options = options()</v>
@@ -790,7 +790,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>start_server(Options) -> {ok, ServerPid} | {error, Reason}</name>
+ <name since="">start_server(Options) -> {ok, ServerPid} | {error, Reason}</name>
<fsummary>Start server process with options</fsummary>
<type>
<v>Options = options()</v>
@@ -803,7 +803,7 @@ target_spec() = [target_spec()]
</func>
<func>
- <name>stop(Pid) -> ok | {error, Reason}</name>
+ <name since="">stop(Pid) -> ok | {error, Reason}</name>
<fsummary>Stop a server or window process</fsummary>
<type>
<v>Pid = server_pid() | window_pid()</v>
diff --git a/lib/reltool/src/reltool_utils.erl b/lib/reltool/src/reltool_utils.erl
index 060a0912f9..2afa386cb3 100644
--- a/lib/reltool/src/reltool_utils.erl
+++ b/lib/reltool/src/reltool_utils.erl
@@ -47,6 +47,9 @@
call/2, cast/2, reply/3]).
+%% For testing
+-export([erl_libs/2]).
+
-include_lib("kernel/include/file.hrl").
-include_lib("wx/include/wx.hrl").
-include("reltool.hrl").
@@ -55,7 +58,15 @@ root_dir() ->
code:root_dir().
erl_libs() ->
- string:lexemes(os:getenv("ERL_LIBS", ""), ":;").
+ erl_libs(os:getenv("ERL_LIBS", ""), os:type()).
+
+erl_libs(ErlLibs, OsType) when is_list(ErlLibs) ->
+ Sep =
+ case OsType of
+ {win32, _} -> ";";
+ _ -> ":"
+ end,
+ string:lexemes(ErlLibs, Sep).
lib_dirs(Dir) ->
case erl_prim_loader:list_dir(Dir) of
diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl
index fc976bfb94..990bd5c217 100644
--- a/lib/reltool/test/reltool_server_SUITE.erl
+++ b/lib/reltool/test/reltool_server_SUITE.erl
@@ -142,7 +142,8 @@ all() ->
use_selected_vsn,
use_selected_vsn_relative_path,
non_standard_vsn_id,
- undefined_regexp].
+ undefined_regexp,
+ windows_erl_libs].
groups() ->
[].
@@ -2546,10 +2547,21 @@ undefined_regexp(_Config) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Checks that reltool_utils can correctly read Windows ERL_LIBS
+
+windows_erl_libs(_Config) ->
+ WinErlLibs =
+ "C:\\Program Files\\Erlang Libs;C:\\Program Files\\More Erlang Libs",
+ Ret = reltool_utils:erl_libs(WinErlLibs, {win32, nt}),
+ ?m(["C:\\Program Files\\Erlang Libs","C:\\Program Files\\More Erlang Libs"],
+ Ret),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Library functions
erl_libs() ->
- string:lexemes(os:getenv("ERL_LIBS", ""), ":;").
+ reltool_utils:erl_libs().
datadir(Config) ->
%% Removes the trailing slash...
diff --git a/lib/reltool/vsn.mk b/lib/reltool/vsn.mk
index 6bfc8c60ea..a649a3e0c0 100644
--- a/lib/reltool/vsn.mk
+++ b/lib/reltool/vsn.mk
@@ -1 +1 @@
-RELTOOL_VSN = 0.7.7
+RELTOOL_VSN = 0.7.8
diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml
index ae60b610ed..e15fc3efe6 100644
--- a/lib/runtime_tools/doc/src/dbg.xml
+++ b/lib/runtime_tools/doc/src/dbg.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>dbg.sgml</file>
</header>
- <module>dbg</module>
+ <module since="">dbg</module>
<modulesummary>The Text Based Trace Facility</modulesummary>
<description>
<p>This module implements a text based interface to the
@@ -68,7 +68,7 @@
</description>
<funcs>
<func>
- <name>fun2ms(LiteralFun) -> MatchSpec</name>
+ <name since="">fun2ms(LiteralFun) -> MatchSpec</name>
<fsummary>Pseudo function that transforms fun syntax to match_spec.</fsummary>
<type>
<v>LiteralFun = fun() literal</v>
@@ -145,14 +145,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>h() -> ok </name>
+ <name since="">h() -> ok </name>
<fsummary>Give a list of available help items on standard output.</fsummary>
<desc>
<p>Gives a list of items for brief online help.</p>
</desc>
</func>
<func>
- <name>h(Item) -> ok </name>
+ <name since="">h(Item) -> ok </name>
<fsummary>Give brief help for an item.</fsummary>
<type>
<v>Item = atom()</v>
@@ -163,14 +163,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>p(Item) -> {ok, MatchDesc} | {error, term()} </name>
+ <name since="">p(Item) -> {ok, MatchDesc} | {error, term()} </name>
<fsummary>Trace messages to and from Item.</fsummary>
<desc>
<p>Equivalent to <c>p(Item, [m])</c>.</p>
</desc>
</func>
<func>
- <name>p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Trace Item according to Flags.</fsummary>
<type>
<v>MatchDesc = [MatchNum]</v>
@@ -303,14 +303,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>c(Mod, Fun, Args)</name>
+ <name since="">c(Mod, Fun, Args)</name>
<fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>all</c>trace flags set.</fsummary>
<desc>
<p>Equivalent to <c>c(Mod, Fun, Args, all)</c>.</p>
</desc>
</func>
<func>
- <name>c(Mod, Fun, Args, Flags)</name>
+ <name since="">c(Mod, Fun, Args, Flags)</name>
<fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>Flags</c>trace flags set.</fsummary>
<desc>
<p>Evaluates the expression <c>apply(Mod, Fun, Args)</c> with the trace
@@ -319,35 +319,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>i() -> ok</name>
+ <name since="">i() -> ok</name>
<fsummary>Display information about all traced processes and ports.</fsummary>
<desc>
<p>Displays information about all traced processes and ports.</p>
</desc>
</func>
<func>
- <name>tp(Module,MatchSpec)</name>
+ <name since="">tp(Module,MatchSpec)</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<desc>
<p>Same as tp({Module, '_', '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tp(Module,Function,MatchSpec)</name>
+ <name since="">tp(Module,Function,MatchSpec)</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<desc>
<p>Same as tp({Module, Function, '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tp(Module, Function, Arity, MatchSpec)</name>
+ <name since="">tp(Module, Function, Arity, MatchSpec)</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<desc>
<p>Same as tp({Module, Function, Arity}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced global function calls</fsummary>
<type>
<v>Module = atom() | '_'</v>
@@ -410,28 +410,28 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>tpl(Module,MatchSpec)</name>
+ <name since="">tpl(Module,MatchSpec)</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>Same as tpl({Module, '_', '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tpl(Module,Function,MatchSpec)</name>
+ <name since="">tpl(Module,Function,MatchSpec)</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>Same as tpl({Module, Function, '_'}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tpl(Module, Function, Arity, MatchSpec)</name>
+ <name since="">tpl(Module, Function, Arity, MatchSpec)</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>Same as tpl({Module, Function, Arity}, MatchSpec)</p>
</desc>
</func>
<func>
- <name>tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced local (as well as global) function calls</fsummary>
<desc>
<p>This function works as <seealso marker="#tp-2"><c>tp/2</c></seealso>, but enables
@@ -442,7 +442,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
<func>
- <name>tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="OTP 19.0">tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Set pattern for traced event</fsummary>
<type>
<v>Event = send | 'receive'</v>
@@ -484,35 +484,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctp()</name>
+ <name since="">ctp()</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({'_', '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctp(Module)</name>
+ <name since="">ctp(Module)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({Module, '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctp(Module, Function)</name>
+ <name since="">ctp(Module, Function)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({Module, Function, '_'})</p>
</desc>
</func>
<func>
- <name>ctp(Module, Function, Arity)</name>
+ <name since="">ctp(Module, Function, Arity)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctp({Module, Function, Arity})</p>
</desc>
</func>
<func>
- <name>ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<type>
<v>Module = atom() | '_'</v>
@@ -533,35 +533,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctpl()</name>
+ <name since="">ctpl()</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({'_', '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpl(Module)</name>
+ <name since="">ctpl(Module)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({Module, '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpl(Module, Function)</name>
+ <name since="">ctpl(Module, Function)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({Module, Function, '_'})</p>
</desc>
</func>
<func>
- <name>ctpl(Module, Function, Arity)</name>
+ <name since="">ctpl(Module, Function, Arity)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpl({Module, Function, Arity})</p>
</desc>
</func>
<func>
- <name>ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables
@@ -570,35 +570,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctpg()</name>
+ <name since="">ctpg()</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({'_', '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpg(Module)</name>
+ <name since="">ctpg(Module)</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({Module, '_', '_'})</p>
</desc>
</func>
<func>
- <name>ctpg(Module, Function)</name>
+ <name since="">ctpg(Module, Function)</name>
<fsummary>>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({Module, Function, '_'})</p>
</desc>
</func>
<func>
- <name>ctpg(Module, Function, Arity)</name>
+ <name since="">ctpg(Module, Function, Arity)</name>
<fsummary>>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>Same as ctpg({Module, Function, Arity})</p>
</desc>
</func>
<func>
- <name>ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="">ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear call trace pattern for the specified functions</fsummary>
<desc>
<p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables
@@ -607,7 +607,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ctpe(Event) -> {ok, MatchDesc} | {error, term()}</name>
+ <name since="OTP 19.0">ctpe(Event) -> {ok, MatchDesc} | {error, term()}</name>
<fsummary>Clear trace pattern for the specified event</fsummary>
<type>
<v>Event = send | 'receive'</v>
@@ -623,7 +623,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ltp() -> ok</name>
+ <name since="">ltp() -> ok</name>
<fsummary>List saved and built-in match specifications on the console.</fsummary>
<desc>
<p>Use this function to recall all match specifications previously
@@ -654,7 +654,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>dtp() -> ok</name>
+ <name since="">dtp() -> ok</name>
<fsummary>Delete all saved match specifications.</fsummary>
<desc>
<p>Use this function to "forget" all match specifications
@@ -665,7 +665,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>dtp(N) -> ok</name>
+ <name since="">dtp(N) -> ok</name>
<fsummary>Delete a specific saved match_spec.</fsummary>
<type>
<v>N = integer()</v>
@@ -676,7 +676,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>wtp(Name) -> ok | {error, IOError}</name>
+ <name since="">wtp(Name) -> ok | {error, IOError}</name>
<fsummary>Write all saved and built-in match specifications to a file</fsummary>
<type>
<v>Name = string()</v>
@@ -699,7 +699,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>rtp(Name) -> ok | {error, Error}</name>
+ <name since="">rtp(Name) -> ok | {error, Error}</name>
<fsummary>Read saved match specifications from file.</fsummary>
<type>
<v>Name = string()</v>
@@ -728,7 +728,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>n(Nodename) -> {ok, Nodename} | {error, Reason}</name>
+ <name since="">n(Nodename) -> {ok, Nodename} | {error, Reason}</name>
<fsummary>Add a remote node to the list of traced nodes</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -767,7 +767,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>cn(Nodename) -> ok</name>
+ <name since="">cn(Nodename) -> ok</name>
<fsummary>Clear a node from the list of traced nodes.</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -782,14 +782,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>ln() -> ok</name>
+ <name since="">ln() -> ok</name>
<fsummary>Show the list of traced nodes on the console.</fsummary>
<desc>
<p>Shows the list of traced nodes on the console.</p>
</desc>
</func>
<func>
- <name>tracer() -> {ok, pid()} | {error, already_started}</name>
+ <name since="">tracer() -> {ok, pid()} | {error, already_started}</name>
<fsummary>Start a tracer server that handles trace messages.</fsummary>
<desc>
<p>This function starts a server on the local node that will
@@ -805,7 +805,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>tracer(Type, Data) -> {ok, pid()} | {error, Error}</name>
+ <name since="">tracer(Type, Data) -> {ok, pid()} | {error, Error}</name>
<fsummary>Start a tracer server with additional parameters</fsummary>
<type>
<v>Type = port | process | module</v>
@@ -859,7 +859,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name>
+ <name since="">tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name>
<fsummary>Start a tracer server on given node with additional parameters</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -881,7 +881,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>trace_port(Type, Parameters) -> fun()</name>
+ <name since="">trace_port(Type, Parameters) -> fun()</name>
<fsummary>Create and returns a trace port generating<em>fun</em></fsummary>
<type>
<v>Type = ip | file</v>
@@ -958,28 +958,28 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>flush_trace_port()</name>
+ <name since="">flush_trace_port()</name>
<fsummary>Equivalent to flush_trace_port(node()).</fsummary>
<desc>
<p>Equivalent to <c>flush_trace_port(node())</c>.</p>
</desc>
</func>
<func>
- <name>flush_trace_port(Nodename) -> ok | {error, Reason}</name>
+ <name since="">flush_trace_port(Nodename) -> ok | {error, Reason}</name>
<fsummary>Flush internal data buffers in a trace driver on the given node.</fsummary>
<desc>
<p>Equivalent to <c>trace_port_control(Nodename,flush)</c>.</p>
</desc>
</func>
<func>
- <name>trace_port_control(Operation)</name>
+ <name since="">trace_port_control(Operation)</name>
<fsummary>Equivalent to trace_port_control(node(),Operation).</fsummary>
<desc>
<p>Equivalent to <c>trace_port_control(node(),Operation)</c>.</p>
</desc>
</func>
<func>
- <name>trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name>
+ <name since="">trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name>
<fsummary>Perform a control operation on the active trace port driver on the given node.</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -1013,7 +1013,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\
</desc>
</func>
<func>
- <name>trace_client(Type, Parameters) -> pid()</name>
+ <name since="">trace_client(Type, Parameters) -> pid()</name>
<fsummary>Start a trace client that reads messages created by a trace port driver</fsummary>
<type>
<v>Type = ip | file | follow_file</v>
@@ -1080,7 +1080,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>trace_client(Type, Parameters, HandlerSpec) -> pid()</name>
+ <name since="">trace_client(Type, Parameters, HandlerSpec) -> pid()</name>
<fsummary>Start a trace client that reads messages created by a trace port driver, with a user defined handler</fsummary>
<type>
<v>Type = ip | file | follow_file</v>
@@ -1110,7 +1110,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>stop_trace_client(Pid) -> ok</name>
+ <name since="">stop_trace_client(Pid) -> ok</name>
<fsummary>Stop a trace client gracefully.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -1123,14 +1123,14 @@ hello</pre>
</desc>
</func>
<func>
- <name>get_tracer()</name>
+ <name since="">get_tracer()</name>
<fsummary>Equivalent to get_tracer(node()).</fsummary>
<desc>
<p>Equivalent to <c>get_tracer(node())</c>.</p>
</desc>
</func>
<func>
- <name>get_tracer(Nodename) -> {ok, Tracer}</name>
+ <name since="">get_tracer(Nodename) -> {ok, Tracer}</name>
<fsummary>Return the process or port to which all trace messages are sent.</fsummary>
<type>
<v>Nodename = atom()</v>
@@ -1142,7 +1142,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="">stop() -> ok</name>
<fsummary>Stop the <c>dbg</c>server and the tracing of all processes.</fsummary>
<desc>
<p>Stops the <c>dbg</c> server and clears all trace flags for
@@ -1153,7 +1153,7 @@ hello</pre>
</desc>
</func>
<func>
- <name>stop_clear() -> ok</name>
+ <name since="">stop_clear() -> ok</name>
<fsummary>Stop the <c>dbg</c>server and the tracing of all processes, and clears trace patterns.</fsummary>
<desc>
<p>Same as stop/0, but also clears all trace patterns on global functions calls.</p>
diff --git a/lib/runtime_tools/doc/src/dyntrace.xml b/lib/runtime_tools/doc/src/dyntrace.xml
index 0cdcecab68..4935dfcd71 100644
--- a/lib/runtime_tools/doc/src/dyntrace.xml
+++ b/lib/runtime_tools/doc/src/dyntrace.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>dyntrace.xml</file>
</header>
- <module>dyntrace</module>
+ <module since="OTP R15B01">dyntrace</module>
<modulesummary>Interface to dynamic tracing</modulesummary>
<description>
<p>This module implements interfaces to dynamic tracing, should such be compiled into the virtual machine. For a standard and/or commercial build, no dynamic tracing is available, in which case none of the functions in this module is usable or give any effect.</p>
@@ -47,7 +47,7 @@
</description>
<funcs>
<func>
- <name>available() -> boolean()</name>
+ <name since="OTP R15B01">available() -> boolean()</name>
<fsummary>Check if dynamic tracing is available</fsummary>
<desc>
<p>This function uses the NIF library to determine if dynamic
@@ -59,42 +59,42 @@
</desc>
</func>
<func>
- <name>p() -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p() -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message only containing the user tag and zeroes/empty strings in all other fields.</p>
</desc>
</func>
<func>
- <name>p(integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer or string parameter in the first integer/string field.</p>
</desc>
</func>
<func>
- <name>p(integer() | string(), integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters. I.e. <c>p(1,"Hello")</c> is ok, as is <c>p(1,1)</c> and <c>p("Hello","Again")</c>, but not <c>p("Hello",1)</c>.</p>
</desc>
</func>
<func>
- <name>p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
</desc>
</func>
<func>
- <name>p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
</desc>
</func>
<func>
- <name>p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
@@ -102,7 +102,7 @@
</desc>
</func>
<func>
- <name>p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
@@ -110,7 +110,7 @@
</desc>
</func>
<func>
- <name>p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p>
@@ -118,14 +118,14 @@
</desc>
</func>
<func>
- <name>p(integer(), integer(), integer(), integer(), string(), string(), string(), string()) -> true | false | error | badarg</name>
+ <name since="OTP R15B01">p(integer(), integer(), integer(), integer(), string(), string(), string(), string()) -> true | false | error | badarg</name>
<fsummary>Trigger the user trace probe.</fsummary>
<desc>
<p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing all the integer()'s and string()'s provided, as well as any user tag set in the current process.</p>
</desc>
</func>
<func>
- <name>get_tag() -> binary() | undefined</name>
+ <name since="OTP R15B01">get_tag() -> binary() | undefined</name>
<fsummary>Get the user tag set in the process.</fsummary>
<desc>
<p>This function returns the user tag set in the current
@@ -134,7 +134,7 @@
</desc>
</func>
<func>
- <name>get_tag() -> binary() | undefined</name>
+ <name since="OTP R15B01">get_tag() -> binary() | undefined</name>
<fsummary>Get the user tag set in the process or sent to the process.</fsummary>
<desc>
<p>This function returns the user tag set in the current
@@ -151,7 +151,7 @@
</func>
<func>
- <name>put_tag(Item) -> binary() | undefined </name>
+ <name since="OTP R15B01">put_tag(Item) -> binary() | undefined </name>
<fsummary>Set the user tag of the current process.</fsummary>
<type>
<v>Item = iodata()</v>
@@ -163,7 +163,7 @@
</desc>
</func>
<func>
- <name>spread_tag(boolean()) -> TagData</name>
+ <name since="OTP R15B01">spread_tag(boolean()) -> TagData</name>
<fsummary>Start or stop spreading dynamic trace user tags with the next message.</fsummary>
<type>
<v>TagData = opaque data that can be used as parameter to <seealso marker="#restore_tag/1">restore_tag/1</seealso></v>
@@ -185,7 +185,7 @@ f() ->
</desc>
</func>
<func>
- <name>restore_tag(TagData) -> true</name>
+ <name since="OTP R15B01">restore_tag(TagData) -> true</name>
<fsummary>Restore to a previous state of user tag spreading.</fsummary>
<type>
<v>TagData = opaque data returned by <seealso marker="#spread_tag/1">spread_tag/1</seealso></v>
diff --git a/lib/runtime_tools/doc/src/erts_alloc_config.xml b/lib/runtime_tools/doc/src/erts_alloc_config.xml
index ffc4ec5285..5bcce1b5e3 100644
--- a/lib/runtime_tools/doc/src/erts_alloc_config.xml
+++ b/lib/runtime_tools/doc/src/erts_alloc_config.xml
@@ -29,7 +29,7 @@
<rev>1</rev>
<file>erts_alloc_config.sgml</file>
</header>
- <module>erts_alloc_config</module>
+ <module since="">erts_alloc_config</module>
<modulesummary>Configuration tool for erts_alloc</modulesummary>
<description>
<note>
@@ -136,7 +136,7 @@
</description>
<funcs>
<func>
- <name>save_scenario() -> ok | {error, Error}</name>
+ <name since="">save_scenario() -> ok | {error, Error}</name>
<fsummary>Saves information about current runtime scenario</fsummary>
<type>
<v>Error = term()</v>
@@ -154,7 +154,7 @@
</desc>
</func>
<func>
- <name>make_config() -> ok | {error, Error}</name>
+ <name since="">make_config() -> ok | {error, Error}</name>
<fsummary>Creates an erts_alloc configuration</fsummary>
<type>
<v>Error = term()</v>
@@ -165,7 +165,7 @@
</desc>
</func>
<func>
- <name>make_config(FileNameOrIODev) -> ok | {error, Error}</name>
+ <name since="">make_config(FileNameOrIODev) -> ok | {error, Error}</name>
<fsummary>Creates an erts_alloc configuration</fsummary>
<type>
<v>FileNameOrIODev = string() | io_device()</v>
@@ -190,7 +190,7 @@
</desc>
</func>
<func>
- <name>stop() -> ok | {error, Error}</name>
+ <name since="">stop() -> ok | {error, Error}</name>
<fsummary></fsummary>
<type>
<v>Error = term()</v>
diff --git a/lib/runtime_tools/doc/src/msacc.xml b/lib/runtime_tools/doc/src/msacc.xml
index 129da3d230..ae089de8d0 100644
--- a/lib/runtime_tools/doc/src/msacc.xml
+++ b/lib/runtime_tools/doc/src/msacc.xml
@@ -31,7 +31,7 @@
<rev>A</rev>
<file>msacc.xml</file>
</header>
- <module>msacc</module>
+ <module since="OTP 19.0">msacc</module>
<modulesummary>Convenience functions for microstate accounting</modulesummary>
<description>
<p>This module implements some convenience functions for analyzing
@@ -146,7 +146,7 @@ ok
</datatypes>
<funcs>
<func>
- <name name="available" arity="0"/>
+ <name name="available" arity="0" since="OTP 19.0"/>
<fsummary>Check if microstate accounting is available</fsummary>
<desc>
<p>This function checks whether microstate accounting
@@ -154,7 +154,7 @@ ok
</desc>
</func>
<func>
- <name name="start" arity="0"/>
+ <name name="start" arity="0" since="OTP 19.0"/>
<fsummary>Start microstate accounting.</fsummary>
<desc>
<p>Start microstate accounting. Returns whether it was
@@ -162,7 +162,7 @@ ok
</desc>
</func>
<func>
- <name name="start" arity="1"/>
+ <name name="start" arity="1" since="OTP 19.0"/>
<fsummary>Start microstate accounting for a time.</fsummary>
<desc>
<p>Resets all counters and then starts microstate accounting
@@ -170,7 +170,7 @@ ok
</desc>
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since="OTP 19.0"/>
<fsummary>Stop microstate accounting.</fsummary>
<desc>
<p>Stop microstate accounting.
@@ -178,7 +178,7 @@ ok
</desc>
</func>
<func>
- <name name="reset" arity="0"/>
+ <name name="reset" arity="0" since="OTP 19.0"/>
<fsummary>Reset microstate accounting counters</fsummary>
<desc>
<p>Reset microstate accounting counters.
@@ -186,7 +186,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="0"/>
+ <name name="print" arity="0" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>
@@ -199,7 +199,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="1"/>
+ <name name="print" arity="1" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>Print the given microstate statistics values to stdout.
@@ -211,7 +211,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="2"/>
+ <name name="print" arity="2" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>Print the given microstate statistics values to standard out.
@@ -234,7 +234,7 @@ ok
</desc>
</func>
<func>
- <name name="print" arity="3"/>
+ <name name="print" arity="3" since="OTP 19.0"/>
<fsummary>Print microstate statistics</fsummary>
<desc>
<p>Print the given microstate statistics values to the given file
@@ -243,7 +243,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="0"/>
+ <name name="stats" arity="0" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a runtime system independent version of the microstate
@@ -254,7 +254,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="2" clause_i="1"/>
+ <name name="stats" arity="2" clause_i="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns the system time for the given microstate statistics values.
@@ -269,7 +269,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="2" clause_i="2"/>
+ <name name="stats" arity="2" clause_i="2" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns fractions of real-time or run-time spent in the various
@@ -277,7 +277,7 @@ ok
</desc>
</func>
<func>
- <name name="stats" arity="2" clause_i="3"/>
+ <name name="stats" arity="2" clause_i="3" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a list of microstate statistics values where the values
@@ -285,7 +285,7 @@ ok
</desc>
</func>
<func>
- <name name="to_file" arity="1"/>
+ <name name="to_file" arity="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Dumps the current microstate statistics counters to a file that can
@@ -294,7 +294,7 @@ ok
</desc>
</func>
<func>
- <name name="from_file" arity="1"/>
+ <name name="from_file" arity="1" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Read a file dump produced by <seealso marker="#to_file/1">
diff --git a/lib/runtime_tools/doc/src/scheduler.xml b/lib/runtime_tools/doc/src/scheduler.xml
index dd8bf73bae..b033430183 100644
--- a/lib/runtime_tools/doc/src/scheduler.xml
+++ b/lib/runtime_tools/doc/src/scheduler.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>scheduler.xml</file>
</header>
- <module>scheduler</module>
+ <module since="OTP 21.0">scheduler</module>
<modulesummary>Measure scheduler utilization</modulesummary>
<description>
<p>This module contains utility functions for easier measurement and
@@ -84,7 +84,7 @@
<funcs>
<func>
- <name name="sample" arity="0"/>
+ <name name="sample" arity="0" since="OTP 21.0"/>
<fsummary>Get scheduler utilization sample.</fsummary>
<desc>
<p>Return a scheduler utilization sample for normal and dirty-cpu
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="sample_all" arity="0"/>
+ <name name="sample_all" arity="0" since="OTP 21.0"/>
<fsummary>Get scheduler utilization sample.</fsummary>
<desc>
<p>Return a scheduler utilization sample for all schedulers,
@@ -102,7 +102,7 @@
</func>
<func>
- <name name="utilization" arity="1" clause_i="1"/>
+ <name name="utilization" arity="1" clause_i="1" since="OTP 21.0"/>
<fsummary>Measure scheduler utilizations during a period of time.</fsummary>
<desc>
<p>Measure utilization for normal and dirty-cpu schedulers during
@@ -111,7 +111,7 @@
</func>
<func>
- <name name="utilization" arity="1" clause_i="2"/>
+ <name name="utilization" arity="1" clause_i="2" since="OTP 21.0"/>
<fsummary>Measure scheduler utilizations since sample.</fsummary>
<desc>
<p>Calculate scheduler utilizations for the time interval from when
@@ -121,7 +121,7 @@
</func>
<func>
- <name name="utilization" arity="2"/>
+ <name name="utilization" arity="2" since="OTP 21.0"/>
<fsummary>Measure scheduler utilizations between two samples.</fsummary>
<desc>
<p>Calculates scheduler utilizations for the time interval between
diff --git a/lib/runtime_tools/doc/src/system_information.xml b/lib/runtime_tools/doc/src/system_information.xml
index 53dc595e64..a356b5c6f8 100644
--- a/lib/runtime_tools/doc/src/system_information.xml
+++ b/lib/runtime_tools/doc/src/system_information.xml
@@ -32,14 +32,14 @@
<rev></rev>
<file>system_information.xml</file>
</header>
- <module>system_information</module>
+ <module since="OTP 17.0">system_information</module>
<modulesummary>System Information</modulesummary>
<description>
<p></p>
</description>
<funcs>
<func>
- <name name="sanity_check" arity="0"/>
+ <name name="sanity_check" arity="0" since="OTP 17.0"/>
<fsummary>Perform a sanity check</fsummary>
<desc>
<p>Performs a sanity check on the system. If no issues
@@ -88,7 +88,7 @@
</desc>
</func>
<func>
- <name name="to_file" arity="1"/>
+ <name name="to_file" arity="1" since="OTP 17.0"/>
<fsummary>Write miscellaneous system information to file</fsummary>
<desc><p>Writes miscellaneous system information to file. This
information will typically be requested by the Erlang/OTP team
diff --git a/lib/runtime_tools/examples/dist.systemtap b/lib/runtime_tools/examples/dist.systemtap
index bb20d617e1..4102a5243c 100644
--- a/lib/runtime_tools/examples/dist.systemtap
+++ b/lib/runtime_tools/examples/dist.systemtap
@@ -19,18 +19,18 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("dist-monitor")
+probe process("beam.smp").mark("dist-monitor")
{
printf("monitor: pid %d, who %s, what %s, node %s, type %s, reason %s\n",
pid(),
@@ -38,38 +38,38 @@ probe process("beam").mark("dist-monitor")
user_string($arg5));
}
-probe process("beam").mark("dist-port_busy")
+probe process("beam.smp").mark("dist-port_busy")
{
printf("dist port_busy: node %s, port %s, remote_node %s, blocked pid %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
- blocked_procs[user_string($arg4)] = timestamp;
+ blocked_procs[user_string($arg4)] = local_clock_ns();
}
-probe process("beam").mark("dist-port_busy")
+probe process("beam.smp").mark("dist-port_busy")
{
printf("dist port_busy: node %s, port %s, remote_node %s, blocked pid %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
- blocked_procs[user_string($arg4)] = timestamp;
+ blocked_procs[user_string($arg4)] = local_clock_ns();
}
-probe process("beam").mark("dist-output")
+probe process("beam.smp").mark("dist-output")
{
printf("dist output: node %s, port %s, remote_node %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("dist-outputv")
+probe process("beam.smp").mark("dist-outputv")
{
printf("port outputv: node %s, port %s, remote_node %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("process-scheduled")
+probe process("beam.smp").mark("process-scheduled")
{
pidstr = user_string($arg1);
if (pidstr in blocked_procs) {
printf("blocked pid %s scheduled now, waited %d microseconds\n",
- pidstr, (timestamp - blocked_procs[pidstr]) / 1000);
+ pidstr, (local_clock_ns() - blocked_procs[pidstr]) / 1000);
delete blocked_procs[pidstr];
}
}
diff --git a/lib/runtime_tools/examples/driver1.systemtap b/lib/runtime_tools/examples/driver1.systemtap
index e1ee8ecffc..f5bc28b42d 100644
--- a/lib/runtime_tools/examples/driver1.systemtap
+++ b/lib/runtime_tools/examples/driver1.systemtap
@@ -19,108 +19,102 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("driver-init")
+probe process("beam.smp").mark("driver__init")
{
printf("driver init name %s major %d minor %d flags %d\n",
user_string($arg1), $arg2, $arg3, $arg4);
}
-probe process("beam").mark("driver-start")
+probe process("beam.smp").mark("driver__start")
{
printf("driver start pid %s driver name %s port %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-stop")
+probe process("beam.smp").mark("driver__stop")
{
printf("driver stop pid %s driver name %s port %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-finish")
+probe process("beam.smp").mark("driver__finish")
{
printf("driver finish driver name %s\n",
user_string($arg1));
}
-probe process("beam").mark("driver-flush")
+probe process("beam.smp").mark("driver__flush")
{
printf("driver flush pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-output")
+probe process("beam.smp").mark("driver__output")
{
printf("driver output pid %s port %s port name %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("driver-outputv")
+probe process("beam.smp").mark("driver__outputv")
{
printf("driver outputv pid %s port %s port name %s bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4);
}
-probe process("beam").mark("driver-control")
+probe process("beam.smp").mark("driver__control")
{
printf("driver control pid %s port %s port name %s command %d bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4, $arg5);
}
-probe process("beam").mark("driver-call")
+probe process("beam.smp").mark("driver__call")
{
printf("driver call pid %s port %s port name %s command %d bytes %d\n",
user_string($arg1), user_string($arg2), user_string($arg3), $arg4, $arg5);
}
-probe process("beam").mark("driver-event")
-{
- printf("driver event pid %s port %s port name %s\n",
- user_string($arg1), user_string($arg2), user_string($arg3));
-}
-
-probe process("beam").mark("driver-ready_input")
+probe process("beam.smp").mark("driver__ready_input")
{
printf("driver ready_input pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-ready_output")
+probe process("beam.smp").mark("driver__ready_output")
{
printf("driver ready_output pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-timeout")
+probe process("beam.smp").mark("driver__timeout")
{
printf("driver timeout pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-ready_async")
+probe process("beam.smp").mark("driver__ready_async")
{
printf("driver ready_async pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-process_exit")
+probe process("beam.smp").mark("driver__process_exit")
{
printf("driver process_exit pid %s port %s port name %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("driver-stop_select")
+probe process("beam.smp").mark("driver__stop_select")
{
printf("driver stop_select driver name %s\n", user_string($arg1));
}
diff --git a/lib/runtime_tools/examples/function-calls.systemtap b/lib/runtime_tools/examples/function-calls.systemtap
index 9c44b2d014..6bb173b3ec 100644
--- a/lib/runtime_tools/examples/function-calls.systemtap
+++ b/lib/runtime_tools/examples/function-calls.systemtap
@@ -18,51 +18,51 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("local-function-entry")
+probe process("beam.smp").mark("local-function-entry")
{
printf("pid %s enter (local) %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
-probe process("beam").mark("global-function-entry")
+probe process("beam.smp").mark("global-function-entry")
{
printf("pid %s enter (global) %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
-probe process("beam").mark("function-return")
+probe process("beam.smp").mark("function-return")
{
printf("pid %s return %s depth %d\n",
user_string($arg1), user_string($arg2), $arg3);
}
-probe process("beam").mark("bif-entry")
+probe process("beam.smp").mark("bif-entry")
{
printf("pid %s BIF entry mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("bif-return")
+probe process("beam.smp").mark("bif-return")
{
printf("pid %s BIF return mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("nif-entry")
+probe process("beam.smp").mark("nif-entry")
{
printf("pid %s NIF entry mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("nif-return")
+probe process("beam.smp").mark("nif-return")
{
printf("pid %s NIF return mfa %s\n", user_string($arg1), user_string($arg2));
}
diff --git a/lib/runtime_tools/examples/garbage-collection.systemtap b/lib/runtime_tools/examples/garbage-collection.systemtap
index e414eea821..14f0d6851c 100644
--- a/lib/runtime_tools/examples/garbage-collection.systemtap
+++ b/lib/runtime_tools/examples/garbage-collection.systemtap
@@ -18,33 +18,33 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("gc_major-start")
+probe process("beam.smp").mark("gc_major-start")
{
printf("GC major start pid %s need %d words\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("gc_minor-start")
+probe process("beam.smp").mark("gc_minor-start")
{
printf("GC minor start pid %s need %d words\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("gc_major-end")
+probe process("beam.smp").mark("gc_major-end")
{
printf("GC major end pid %s reclaimed %d words\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("gc_minor-start")
+probe process("beam.smp").mark("gc_minor-start")
{
printf("GC minor end pid %s reclaimed %d words\n", user_string($arg1), $arg2);
}
diff --git a/lib/runtime_tools/examples/memory1.systemtap b/lib/runtime_tools/examples/memory1.systemtap
index 04df4d64c4..2fdc5a796c 100644
--- a/lib/runtime_tools/examples/memory1.systemtap
+++ b/lib/runtime_tools/examples/memory1.systemtap
@@ -18,34 +18,34 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("copy-struct")
+probe process("beam.smp").mark("copy-struct")
{
printf("copy_struct %d bytes\n", $arg1);
}
-probe process("beam").mark("copy-object")
+probe process("beam.smp").mark("copy-object")
{
printf("copy_object pid %s %d bytes\n", user_string($arg1), $arg2);
}
-probe process("beam").mark("process-heap_grow")
+probe process("beam.smp").mark("process-heap_grow")
{
printf("proc heap grow pid %s %d -> %d bytes\n", user_string($arg1),
$arg2, $arg3);
}
-probe process("beam").mark("process-heap_shrink")
+probe process("beam.smp").mark("process-heap_shrink")
{
printf("proc heap shrink pid %s %d -> %d bytes\n", user_string($arg1),
$arg2, $arg3);
diff --git a/lib/runtime_tools/examples/messages.systemtap b/lib/runtime_tools/examples/messages.systemtap
index f2ef56a22b..49b7f46d69 100644
--- a/lib/runtime_tools/examples/messages.systemtap
+++ b/lib/runtime_tools/examples/messages.systemtap
@@ -18,15 +18,15 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
probe begin
@@ -38,7 +38,7 @@ probe begin
printf("\n");
}
-probe process("beam").mark("message-send")
+probe process("beam.smp").mark("message-send")
{
if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) {
printf("send: %s -> %s: %d words\n",
@@ -51,7 +51,7 @@ probe process("beam").mark("message-send")
}
}
-probe process("beam").mark("message-send-remote")
+probe process("beam.smp").mark("message-send-remote")
{
if ($arg5 == 0 && $arg6 == 0 && $arg7 == 0) {
printf("send : %s -> %s %s: %d words\n",
@@ -64,7 +64,7 @@ probe process("beam").mark("message-send-remote")
}
}
-probe process("beam").mark("message-queued")
+probe process("beam.smp").mark("message-queued")
{
if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) {
printf("queued: %s: %d words, queue len %d\n", user_string($arg1), $arg2, $arg3);
@@ -75,7 +75,7 @@ probe process("beam").mark("message-queued")
}
}
-probe process("beam").mark("message-receive")
+probe process("beam.smp").mark("message-receive")
{
if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) {
printf("receive: %s: %d words, queue len %d\n",
diff --git a/lib/runtime_tools/examples/port1.systemtap b/lib/runtime_tools/examples/port1.systemtap
index f7ce03a65e..235581b0b1 100644
--- a/lib/runtime_tools/examples/port1.systemtap
+++ b/lib/runtime_tools/examples/port1.systemtap
@@ -18,15 +18,15 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
probe begin
@@ -96,19 +96,19 @@ probe begin
driver_map["udp_inet", 62] = "BINDX";
}
-probe process("beam").mark("port-open")
+probe process("beam.smp").mark("port-open")
{
printf("port open pid %s port name %s port %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("port-command")
+probe process("beam.smp").mark("port-command")
{
printf("port command pid %s port %s port name %s command type %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
}
-probe process("beam").mark("port-control")
+probe process("beam.smp").mark("port-control")
{
cmd = driver_map[user_string($arg3), $arg4];
cmd_str = (cmd == "") ? "unknown" : cmd;
@@ -118,36 +118,36 @@ probe process("beam").mark("port-control")
/* port-exit is fired as a result of port_close() or exit signal */
-probe process("beam").mark("port-exit")
+probe process("beam.smp").mark("port-exit")
{
printf("port exit pid %s port %s port name %s reason %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
}
-probe process("beam").mark("port-connect")
+probe process("beam.smp").mark("port-connect")
{
printf("port connect pid %s port %s port name %s new pid %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
}
-probe process("beam").mark("port-busy")
+probe process("beam.smp").mark("port-busy")
{
printf("port busy %s\n", user_string($arg1));
}
-probe process("beam").mark("port-not_busy")
+probe process("beam.smp").mark("port-not_busy")
{
printf("port not busy %s\n", user_string($arg1));
}
-probe process("beam").mark("aio_pool-add")
+probe process("beam.smp").mark("aio_pool-add")
{
printf("async I/O pool add thread %d queue len %d\n", $arg1, $arg2);
}
-probe process("beam").mark("aio_pool-get")
+probe process("beam.smp").mark("aio_pool-get")
{
printf("async I/O pool get thread %d queue len %d\n", $arg1, $arg2);
}
-global driver_map; \ No newline at end of file
+global driver_map;
diff --git a/lib/runtime_tools/examples/process-scheduling.systemtap b/lib/runtime_tools/examples/process-scheduling.systemtap
index b0b74257b3..231c589f64 100644
--- a/lib/runtime_tools/examples/process-scheduling.systemtap
+++ b/lib/runtime_tools/examples/process-scheduling.systemtap
@@ -18,28 +18,28 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("process-scheduled")
+probe process("beam.smp").mark("process-scheduled")
{
printf(" Schedule pid %s mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("process-unscheduled")
+probe process("beam.smp").mark("process-unscheduled")
{
printf("Unschedule pid %s\n", user_string($arg1));
}
-probe process("beam").mark("process-hibernate")
+probe process("beam.smp").mark("process-hibernate")
{
printf(" Hibernate pid %s resume mfa %s\n",
user_string($arg1), user_string($arg2));
diff --git a/lib/runtime_tools/examples/spawn-exit.systemtap b/lib/runtime_tools/examples/spawn-exit.systemtap
index 89bca14496..a7b4a0a3ea 100644
--- a/lib/runtime_tools/examples/spawn-exit.systemtap
+++ b/lib/runtime_tools/examples/spawn-exit.systemtap
@@ -18,34 +18,34 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("process-spawn")
+probe process("beam.smp").mark("process-spawn")
{
printf("pid %s mfa %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("process-exit")
+probe process("beam.smp").mark("process-exit")
{
printf("pid %s reason %s\n", user_string($arg1), user_string($arg2));
}
-probe process("beam").mark("process-exit_signal")
+probe process("beam.smp").mark("process-exit_signal")
{
printf("sender %s -> pid %s reason %s\n",
user_string($arg1), user_string($arg2), user_string($arg3));
}
-probe process("beam").mark("process-exit_signal-remote")
+probe process("beam.smp").mark("process-exit_signal-remote")
{
printf("sender %s -> node %s pid %s reason %s\n",
user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4));
diff --git a/lib/runtime_tools/examples/user-probe-n.systemtap b/lib/runtime_tools/examples/user-probe-n.systemtap
index 25f7503283..8a0a89c931 100644
--- a/lib/runtime_tools/examples/user-probe-n.systemtap
+++ b/lib/runtime_tools/examples/user-probe-n.systemtap
@@ -18,18 +18,19 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("user_trace-n0")
+
+probe process("beam.smp").mark("user_trace-n0")
{
printf("probe n0: %s %s %d %d %d %d '%s' '%s' '%s' '%s'\n",
user_string($arg1),
@@ -41,7 +42,7 @@ probe process("beam").mark("user_trace-n0")
$arg9 == NULL ? "" : user_string($arg9));
}
-probe process("beam").mark("user_trace-n1")
+probe process("beam.smp").mark("user_trace-n1")
{
printf("probe n1: %s %s %d %d %d %d '%s' '%s' '%s' '%s'\n",
user_string($arg1),
diff --git a/lib/runtime_tools/examples/user-probe.systemtap b/lib/runtime_tools/examples/user-probe.systemtap
index 1777476e54..ce9dde30f8 100644
--- a/lib/runtime_tools/examples/user-probe.systemtap
+++ b/lib/runtime_tools/examples/user-probe.systemtap
@@ -18,23 +18,23 @@
* %CopyrightEnd%
*/
/*
- * Note: This file assumes that you're using the non-SMP-enabled Erlang
- * virtual machine, "beam". The SMP-enabled VM is called "beam.smp".
+ * Note: This file assumes that you're using the SMP-enabled Erlang
+ * virtual machine, "beam.smp".
* Note that other variations of the virtual machine also have
* different names, e.g. the debug build of the SMP-enabled VM
* is "beam.debug.smp".
*
* To use a different virtual machine, replace each instance of
- * "beam" with "beam.smp" or the VM name appropriate to your
- * environment.
+ * "beam.smp" with "beam.debug.smp" or the VM name appropriate
+ * to your environment.
*/
-probe process("beam").mark("user_trace-s1")
+probe process("beam.smp").mark("user_trace-s1")
{
printf("%s\n", user_string($arg1));
}
-probe process("beam").mark("user_trace-i4s4")
+probe process("beam.smp").mark("user_trace-i4s4")
{
printf("%s %s %d %d %d %d '%s' '%s' '%s' '%s'\n",
user_string($arg1),
diff --git a/lib/sasl/doc/src/alarm_handler.xml b/lib/sasl/doc/src/alarm_handler.xml
index 4160757164..6e74f833cd 100644
--- a/lib/sasl/doc/src/alarm_handler.xml
+++ b/lib/sasl/doc/src/alarm_handler.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>alarm_handler.sgml.t1</file>
</header>
- <module>alarm_handler</module>
+ <module since="">alarm_handler</module>
<modulesummary>An Alarm Handling Process</modulesummary>
<description>
<p>The alarm handler process is a
@@ -81,7 +81,7 @@
<funcs>
<func>
- <name>clear_alarm(AlarmId) -> void()</name>
+ <name since="">clear_alarm(AlarmId) -> void()</name>
<fsummary>Clears the specified alarms.</fsummary>
<type>
<v>AlarmId = term()</v>
@@ -94,7 +94,7 @@
</func>
<func>
- <name>get_alarms() -> [alarm()]</name>
+ <name since="">get_alarms() -> [alarm()]</name>
<fsummary>Gets all active alarms.</fsummary>
<desc>
<p>Returns a list of all active alarms. This function can only
@@ -103,7 +103,7 @@
</func>
<func>
- <name>set_alarm(alarm())</name>
+ <name since="">set_alarm(alarm())</name>
<fsummary>Sets an alarm with an id.</fsummary>
<type>
<v>alarm() = {AlarmId, AlarmDescription}</v>
diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml
index fce032136d..982c874117 100644
--- a/lib/sasl/doc/src/notes.xml
+++ b/lib/sasl/doc/src/notes.xml
@@ -31,6 +31,23 @@
</header>
<p>This document describes the changes made to the SASL application.</p>
+<section><title>SASL 3.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ New <c>counters</c> and <c>atomics</c> modules supplies
+ access to highly efficient operations on mutable fixed
+ word sized variables.</p>
+ <p>
+ Own Id: OTP-13468</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SASL 3.2.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/sasl/doc/src/rb.xml b/lib/sasl/doc/src/rb.xml
index d5df4fd345..0ed7e91c11 100644
--- a/lib/sasl/doc/src/rb.xml
+++ b/lib/sasl/doc/src/rb.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>rb.sgml</file>
</header>
- <module>rb</module>
+ <module since="">rb</module>
<modulesummary>The Report Browser Tool</modulesummary>
<description>
<p>The Report Browser (RB) tool is used to browse and
@@ -43,8 +43,8 @@
<funcs>
<func>
- <name>filter(Filters)</name>
- <name>filter(Filters, Dates)</name>
+ <name since="OTP R13B04">filter(Filters)</name>
+ <name since="OTP R13B04">filter(Filters, Dates)</name>
<fsummary>Filters reports and displays them on the screen.</fsummary>
<type>
<v>Filters = [filter()]</v>
@@ -86,7 +86,7 @@
</func>
<func>
- <name>grep(RegExp)</name>
+ <name since="">grep(RegExp)</name>
<fsummary>Searches the reports for a regular expression.</fsummary>
<type>
<v>RegExp = string() | {string(), Options} | re:mp() | {re:mp(), Options}</v>
@@ -109,8 +109,8 @@
</func>
<func>
- <name>h()</name>
- <name>help()</name>
+ <name since="">h()</name>
+ <name since="">help()</name>
<fsummary>Displays help information.</fsummary>
<desc>
<p>Displays online help information.</p>
@@ -118,8 +118,8 @@
</func>
<func>
- <name>list()</name>
- <name>list(Type)</name>
+ <name since="">list()</name>
+ <name since="">list(Type)</name>
<fsummary>Lists all reports.</fsummary>
<type>
<v>Type = type()</v>
@@ -137,8 +137,8 @@
</func>
<func>
- <name>log_list()</name>
- <name>log_list(Type)</name>
+ <name since="OTP R16B02">log_list()</name>
+ <name since="OTP R16B02">log_list(Type)</name>
<fsummary>Logs report lists.</fsummary>
<type>
<v>Type = type()</v>
@@ -157,8 +157,8 @@
</func>
<func>
- <name>rescan()</name>
- <name>rescan(Options)</name>
+ <name since="">rescan()</name>
+ <name since="">rescan(Options)</name>
<fsummary>Rescans the report directory.</fsummary>
<type>
<v>Options = [opt()]</v>
@@ -171,8 +171,8 @@
</func>
<func>
- <name>show()</name>
- <name>show(Report)</name>
+ <name since="">show()</name>
+ <name since="">show(Report)</name>
<fsummary>Displays reports.</fsummary>
<type>
<v>Report = integer() | type()</v>
@@ -186,8 +186,8 @@
</func>
<func>
- <name>start()</name>
- <name>start(Options)</name>
+ <name since="">start()</name>
+ <name since="">start(Options)</name>
<fsummary>Starts the <c>rb_server</c>.</fsummary>
<type>
<v>Options = [opt()]</v>
@@ -256,7 +256,7 @@
</func>
<func>
- <name>start_log(FileName)</name>
+ <name since="">start_log(FileName)</name>
<fsummary>Redirects all output to <c>FileName</c>.</fsummary>
<type>
<v>FileName = string() | atom() | pid()</v>
@@ -268,7 +268,7 @@
</func>
<func>
- <name>stop()</name>
+ <name since="">stop()</name>
<fsummary>Stops the <c>rb_server</c>.</fsummary>
<desc>
<p>Stops <c>rb_server</c>.</p>
@@ -276,7 +276,7 @@
</func>
<func>
- <name>stop_log()</name>
+ <name since="">stop_log()</name>
<fsummary>Stops logging to file.</fsummary>
<desc>
<p>Closes the log file. The output from the RB tool is
diff --git a/lib/sasl/doc/src/release_handler.xml b/lib/sasl/doc/src/release_handler.xml
index 9ba276aeac..f8ee0306d8 100644
--- a/lib/sasl/doc/src/release_handler.xml
+++ b/lib/sasl/doc/src/release_handler.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>release_handler</module>
+ <module since="">release_handler</module>
<modulesummary>Unpacking and Installation of Release Packages</modulesummary>
<description>
<p>The <em>release handler</em> process belongs to the SASL
@@ -168,8 +168,8 @@
<funcs>
<func>
- <name>check_install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
- <name>check_install_release(Vsn,Opts) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="">check_install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="OTP R14B04">check_install_release(Vsn,Opts) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
<fsummary>Checks installation of a release in the system.</fsummary>
<type>
<v>Vsn = OtherVsn = string()</v>
@@ -202,7 +202,7 @@
</func>
<func>
- <name>create_RELEASES(Root, RelDir, RelFile, AppDirs) -> ok | {error, Reason}</name>
+ <name since="">create_RELEASES(Root, RelDir, RelFile, AppDirs) -> ok | {error, Reason}</name>
<fsummary>Creates an initial <c>RELEASES</c> file.</fsummary>
<type>
<v>Root = RelDir = RelFile = string()</v>
@@ -233,7 +233,7 @@
</func>
<func>
- <name>install_file(Vsn, File) -> ok | {error, Reason}</name>
+ <name since="">install_file(Vsn, File) -> ok | {error, Reason}</name>
<fsummary>Installs a release file in the release structure.</fsummary>
<type>
<v>Vsn = File = string()</v>
@@ -252,8 +252,8 @@
</func>
<func>
- <name>install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
- <name>install_release(Vsn, [Opt]) -> {ok, OtherVsn, Descr} | {continue_after_restart, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="">install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name>
+ <name since="">install_release(Vsn, [Opt]) -> {ok, OtherVsn, Descr} | {continue_after_restart, OtherVsn, Descr} | {error, Reason}</name>
<fsummary>Installs a release in the system.</fsummary>
<type>
<v>Vsn = OtherVsn = string()</v>
@@ -383,7 +383,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>make_permanent(Vsn) -> ok | {error, Reason}</name>
+ <name since="">make_permanent(Vsn) -> ok | {error, Reason}</name>
<fsummary>Makes the specified release version permanent.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -396,7 +396,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>remove_release(Vsn) -> ok | {error, Reason}</name>
+ <name since="">remove_release(Vsn) -> ok | {error, Reason}</name>
<fsummary>Removes a release from the system.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -410,7 +410,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>reboot_old_release(Vsn) -> ok | {error, Reason}</name>
+ <name since="">reboot_old_release(Vsn) -> ok | {error, Reason}</name>
<fsummary>Reboots the system from an old release.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -425,7 +425,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>set_removed(Vsn) -> ok | {error, Reason}</name>
+ <name since="">set_removed(Vsn) -> ok | {error, Reason}</name>
<fsummary>Marks a release as removed.</fsummary>
<type>
<v>Vsn = string()</v>
@@ -440,7 +440,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>set_unpacked(RelFile, AppDirs) -> {ok, Vsn} | {error, Reason}</name>
+ <name since="">set_unpacked(RelFile, AppDirs) -> {ok, Vsn} | {error, Reason}</name>
<fsummary>Marks a release as unpacked.</fsummary>
<type>
<v>RelFile = string()</v>
@@ -466,7 +466,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>unpack_release(Name) -> {ok, Vsn} | {error, Reason}</name>
+ <name since="">unpack_release(Name) -> {ok, Vsn} | {error, Reason}</name>
<fsummary>Unpacks a release package.</fsummary>
<type>
<v>Name = Vsn = string()</v>
@@ -482,7 +482,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>which_releases() -> [{Name, Vsn, Apps, Status}]</name>
+ <name since="">which_releases() -> [{Name, Vsn, Apps, Status}]</name>
<fsummary>Returns all known releases.</fsummary>
<type>
<v>Name = Vsn = string()</v>
@@ -495,7 +495,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>which_releases(Status) -> [{Name, Vsn, Apps, Status}]</name>
+ <name since="OTP R15B">which_releases(Status) -> [{Name, Vsn, Apps, Status}]</name>
<fsummary>Returns all known releases of a specific status.</fsummary>
<type>
<v>Name = Vsn = string()</v>
@@ -537,7 +537,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
<funcs>
<func>
- <name>upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
+ <name since="">upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Upgrades to a new application version.</fsummary>
<type>
<v>App = atom()</v>
@@ -586,8 +586,8 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>downgrade_app(App, Dir) -></name>
- <name>downgrade_app(App, OldVsn, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
+ <name since="">downgrade_app(App, Dir) -></name>
+ <name since="">downgrade_app(App, OldVsn, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Downgrades to a previous application version.</fsummary>
<type>
<v>App = atom()</v>
@@ -633,7 +633,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>upgrade_script(App, Dir) -> {ok, NewVsn, Script}</name>
+ <name since="">upgrade_script(App, Dir) -> {ok, NewVsn, Script}</name>
<fsummary>Finds an application upgrade script.</fsummary>
<type>
<v>App = atom()</v>
@@ -671,7 +671,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>downgrade_script(App, OldVsn, Dir) -> {ok, Script}</name>
+ <name since="">downgrade_script(App, OldVsn, Dir) -> {ok, Script}</name>
<fsummary>Finds an application downgrade script.</fsummary>
<type>
<v>App = atom()</v>
@@ -710,7 +710,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]).
</func>
<func>
- <name>eval_appup_script(App, ToVsn, ToDir, Script) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
+ <name since="">eval_appup_script(App, ToVsn, ToDir, Script) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name>
<fsummary>Evaluates an application upgrade or downgrade script.</fsummary>
<type>
<v>App = atom()</v>
diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml
index 4842c732b1..6facb8ddae 100644
--- a/lib/sasl/doc/src/systools.xml
+++ b/lib/sasl/doc/src/systools.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>systools</module>
+ <module since="">systools</module>
<modulesummary>A Set of Release Handling Tools</modulesummary>
<description>
<p>This module contains functions to generate boot scripts
@@ -40,8 +40,8 @@
<funcs>
<func>
- <name>make_relup(Name, UpFrom, DownTo) -> Result</name>
- <name>make_relup(Name, UpFrom, DownTo, [Opt]) -> Result</name>
+ <name since="">make_relup(Name, UpFrom, DownTo) -> Result</name>
+ <name since="">make_relup(Name, UpFrom, DownTo, [Opt]) -> Result</name>
<fsummary>Generates a release upgrade file <c>relup</c>.</fsummary>
<type>
<v>Name = string()</v>
@@ -136,8 +136,8 @@
</func>
<func>
- <name>make_script(Name) -> Result</name>
- <name>make_script(Name, [Opt]) -> Result</name>
+ <name since="">make_script(Name) -> Result</name>
+ <name since="">make_script(Name, [Opt]) -> Result</name>
<fsummary>Generates a boot script <c>.script/.boot</c>.</fsummary>
<type>
<v>Name = string()</v>
@@ -263,8 +263,8 @@
</func>
<func>
- <name>make_tar(Name) -> Result</name>
- <name>make_tar(Name, [Opt]) -> Result</name>
+ <name since="">make_tar(Name) -> Result</name>
+ <name since="">make_tar(Name, [Opt]) -> Result</name>
<fsummary>Creates a release package.</fsummary>
<type>
<v>Name = string()</v>
@@ -369,7 +369,7 @@ myapp-1/ebin/myapp.app
</func>
<func>
- <name>script2boot(File) -> ok | error</name>
+ <name since="">script2boot(File) -> ok | error</name>
<fsummary>Generates a binary version of a boot script.</fsummary>
<type>
<v>File = string()</v>
diff --git a/lib/sasl/src/sasl.app.src b/lib/sasl/src/sasl.app.src
index 5d45af0b50..293af504df 100644
--- a/lib/sasl/src/sasl.app.src
+++ b/lib/sasl/src/sasl.app.src
@@ -43,5 +43,5 @@
{env, []},
{mod, {sasl, []}},
{runtime_dependencies, ["tools-2.6.14","stdlib-3.4","kernel-5.3",
- "erts-@OTP-13468@"]}]}.
+ "erts-10.2"]}]}.
diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src
index d37c5b3d95..26127eae84 100644
--- a/lib/sasl/src/sasl.appup.src
+++ b/lib/sasl/src/sasl.appup.src
@@ -16,13 +16,30 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
+%%
+%% We allow upgrade from, and downgrade to all previous
+%% versions from the following OTP releases:
+%% - OTP 20
+%% - OTP 21
+%%
+%% We also allow upgrade from, and downgrade to all
+%% versions that have branched off from the above
+%% stated previous versions.
+%%
{"%VSN%",
- %% Up from - max one major revision back
- [{<<"3\\.0\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0
- {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+
- {<<"3\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21.*
- %% Down to - max one major revision back
- [{<<"3\\.0\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+
- {<<"3\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.*
-}.
+ [{<<"^3\\.0\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.1$">>,[restart_new_emulator]},
+ {<<"^3\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.1\\.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]}],
+ [{<<"^3\\.0\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.1$">>,[restart_new_emulator]},
+ {<<"^3\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.1\\.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]}]}.
diff --git a/lib/sasl/src/systools.erl b/lib/sasl/src/systools.erl
index dd1a58c3c1..34eca6679f 100644
--- a/lib/sasl/src/systools.erl
+++ b/lib/sasl/src/systools.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-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.
@@ -73,7 +73,7 @@ make_tar(RelName, Opt) ->
script2boot(File) ->
case systools_lib:file_term2binary(File ++ ".script", File ++ ".boot") of
{error,Error} ->
- io:format(systools_make:format_error(Error)),
+ io:format("~ts", [systools_make:format_error(Error)]),
error;
_ ->
ok
@@ -84,7 +84,7 @@ script2boot(File, Output0, _Opt) ->
Output = Output0++".boot",
case systools_lib:file_term2binary(Input, Output) of
{error,Error} ->
- io:format(systools_make:format_error(Error)),
+ io:format("~ts", [systools_make:format_error(Error)]),
error;
_ ->
ok
diff --git a/lib/sasl/src/systools_relup.erl b/lib/sasl/src/systools_relup.erl
index e836d57670..5f1176ec69 100644
--- a/lib/sasl/src/systools_relup.erl
+++ b/lib/sasl/src/systools_relup.erl
@@ -587,7 +587,7 @@ default(warnings_as_errors) -> false.
print_error({error, Mod, Error}) ->
S = apply(Mod, format_error, [Error]),
- io:format(S, []);
+ io:format("~ts", [S]);
print_error(Other) ->
io:format("Error: ~tp~n", [Other]).
diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk
index 94b7f9dc56..c1f80752a7 100644
--- a/lib/sasl/vsn.mk
+++ b/lib/sasl/vsn.mk
@@ -1 +1 @@
-SASL_VSN = 3.2.1
+SASL_VSN = 3.3
diff --git a/lib/snmp/doc/src/snmp.xml b/lib/snmp/doc/src/snmp.xml
index 480ed2e825..d20f1a8d06 100644
--- a/lib/snmp/doc/src/snmp.xml
+++ b/lib/snmp/doc/src/snmp.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp.xml</file>
</header>
- <module>snmp</module>
+ <module since="">snmp</module>
<modulesummary>Interface functions to the SNMP toolkit</modulesummary>
<description>
<p>The module <c>snmp</c> contains interface functions to the
@@ -56,7 +56,7 @@
<funcs>
<func>
- <name>config() -> ok | {error, Reason}</name>
+ <name since="">config() -> ok | {error, Reason}</name>
<fsummary>Configure with a simple interactive tool</fsummary>
<desc>
<p>A simple interactive configuration tool. Simple
@@ -78,8 +78,8 @@
</func>
<func>
- <name>start() -> ok | {error, Reason}</name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="">start() -> ok | {error, Reason}</name>
+ <name since="">start(Type) -> ok | {error, Reason}</name>
<fsummary>Start the SNMP application</fsummary>
<type>
<v>Type = start_type()</v>
@@ -93,8 +93,8 @@
</func>
<func>
- <name>start_agent() -> ok | {error, Reason}</name>
- <name>start_agent(Type) -> ok | {error, Reason}</name>
+ <name since="">start_agent() -> ok | {error, Reason}</name>
+ <name since="">start_agent(Type) -> ok | {error, Reason}</name>
<fsummary>Start the agent part of the SNMP application</fsummary>
<type>
<v>Type = start_type()</v>
@@ -117,8 +117,8 @@
</func>
<func>
- <name>start_manager() -> ok | {error, Reason}</name>
- <name>start_manager(Type) -> ok | {error, Reason}</name>
+ <name since="">start_manager() -> ok | {error, Reason}</name>
+ <name since="">start_manager(Type) -> ok | {error, Reason}</name>
<fsummary>Start the manager part of the SNMP application</fsummary>
<type>
<v>Type = start_type()</v>
@@ -141,7 +141,7 @@
</func>
<func>
- <name>date_and_time() -> DateAndTime</name>
+ <name since="">date_and_time() -> DateAndTime</name>
<fsummary>Return the current date and time as an OCTET STRING</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -155,7 +155,7 @@
</func>
<func>
- <name>date_and_time_to_universal_time_dst(DateAndTime) -> [utc()]</name>
+ <name since="">date_and_time_to_universal_time_dst(DateAndTime) -> [utc()]</name>
<fsummary>Convert a DateAndTime value to a list of possible utc()</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -171,8 +171,8 @@
</func>
<func>
- <name>date_and_time_to_string(DateAndTime) -> string()</name>
- <name>date_and_time_to_string(DateAndTime, Validate) -> string()</name>
+ <name since="">date_and_time_to_string(DateAndTime) -> string()</name>
+ <name since="">date_and_time_to_string(DateAndTime, Validate) -> string()</name>
<fsummary>Convert a DateAndTime value to a string</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -194,7 +194,7 @@
</func>
<func>
- <name>date_and_time_to_string2(DateAndTime) -> string()</name>
+ <name since="">date_and_time_to_string2(DateAndTime) -> string()</name>
<fsummary>Convert a DateAndTime value to a string</fsummary>
<type>
<v>DateAndTime = [int()]</v>
@@ -210,7 +210,7 @@
</func>
<func>
- <name>local_time_to_date_and_time_dst(Local) -> [DateAndTime]</name>
+ <name since="">local_time_to_date_and_time_dst(Local) -> [DateAndTime]</name>
<fsummary>Convert a Local time value to a list of possible DateAndTime(s)</fsummary>
<type>
<v>Local = {{Y,Mo,D},{H,M,S}}</v>
@@ -226,7 +226,7 @@
</func>
<func>
- <name>universal_time_to_date_and_time(UTC) -> DateAndTime</name>
+ <name since="">universal_time_to_date_and_time(UTC) -> DateAndTime</name>
<fsummary>Convert a UTC value to DateAndTime</fsummary>
<type>
<v>UTC = {{Y,Mo,D},{H,M,S}}</v>
@@ -241,8 +241,8 @@
</func>
<func>
- <name>validate_date_and_time(DateAndTime) -> bool()</name>
- <name>validate_date_and_time(DateAndTime, Validate) -> bool()</name>
+ <name since="">validate_date_and_time(DateAndTime) -> bool()</name>
+ <name since="">validate_date_and_time(DateAndTime, Validate) -> bool()</name>
<fsummary>Check if a DateAndTime value is correct</fsummary>
<type>
<v>DateAndTime = term()</v>
@@ -279,7 +279,7 @@
</func>
<func>
- <name>passwd2localized_key(Alg, Passwd, EngineID) -> Key</name>
+ <name since="">passwd2localized_key(Alg, Passwd, EngineID) -> Key</name>
<fsummary>Generates an localized key</fsummary>
<type>
<v>Alg = algorithm()</v>
@@ -298,7 +298,7 @@
</func>
<func>
- <name>octet_string_to_bits(S) -> Val</name>
+ <name since="">octet_string_to_bits(S) -> Val</name>
<fsummary>Convert an OCTET-STRING to BITS</fsummary>
<type>
<v>Val = bits()</v>
@@ -312,7 +312,7 @@
</func>
<func>
- <name>bits_to_octet_string(B) -> Val</name>
+ <name since="">bits_to_octet_string(B) -> Val</name>
<fsummary>Convert an OCTET-STRING to BITS</fsummary>
<type>
<v>Val = octet_string()</v>
@@ -326,7 +326,7 @@
</func>
<func>
- <name>read_mib(FileName) -> {ok, mib()} | {error, Reason}</name>
+ <name since="">read_mib(FileName) -> {ok, mib()} | {error, Reason}</name>
<fsummary></fsummary>
<type>
<v>FileName = string()</v>
@@ -341,10 +341,10 @@
</func>
<func>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -412,10 +412,10 @@
</func>
<func>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -440,7 +440,7 @@
</func>
<func>
- <name>change_log_size(LogName, NewSize) -> ok | {error, Reason}</name>
+ <name since="">change_log_size(LogName, NewSize) -> ok | {error, Reason}</name>
<fsummary>Change the size of the Audit Trail Log</fsummary>
<type>
<v>LogName = string()</v>
@@ -463,8 +463,8 @@
</func>
<func>
- <name>print_version_info() -> void()</name>
- <name>print_version_info(Prefix) -> void()</name>
+ <name since="">print_version_info() -> void()</name>
+ <name since="">print_version_info(Prefix) -> void()</name>
<fsummary>Formatted print of result of the versions functions</fsummary>
<type>
<v>Prefix = string() | integer()</v>
@@ -484,8 +484,8 @@
</func>
<func>
- <name>versions1() -> {ok, Info} | {error, Reason}</name>
- <name>versions2() -> {ok, Info} | {error, Reason}</name>
+ <name since="">versions1() -> {ok, Info} | {error, Reason}</name>
+ <name since="">versions2() -> {ok, Info} | {error, Reason}</name>
<fsummary>Retrieve various system and application info</fsummary>
<type>
<v>Info = [info()]</v>
@@ -504,8 +504,8 @@
</func>
<func>
- <name>print_versions(VersionInfo) -> void()</name>
- <name>print_versions(Prefix, VersionInfo) -> void()</name>
+ <name since="">print_versions(VersionInfo) -> void()</name>
+ <name since="">print_versions(Prefix, VersionInfo) -> void()</name>
<fsummary>Formatted print of result of the versions functions</fsummary>
<type>
<v>VersionInfo = [version_info()]</v>
@@ -527,7 +527,7 @@
</func>
<func>
- <name>enable_trace() -> void()</name>
+ <name since="">enable_trace() -> void()</name>
<fsummary>Starts a tracer</fsummary>
<!--
<type>
@@ -543,7 +543,7 @@
</func>
<func>
- <name>disable_trace() -> void()</name>
+ <name since="">disable_trace() -> void()</name>
<fsummary>Stop the tracer</fsummary>
<!--
<type>
@@ -558,7 +558,7 @@
</func>
<func>
- <name>set_trace(Targets) -> void()</name>
+ <name since="">set_trace(Targets) -> void()</name>
<fsummary>Set trace target</fsummary>
<type>
<v>Targets = target() | targets()</v>
@@ -582,7 +582,7 @@
</func>
<func>
- <name>reset_trace(Targets) -> void()</name>
+ <name since="">reset_trace(Targets) -> void()</name>
<fsummary>Reset trace target</fsummary>
<type>
<v>Targets = module() | modules()</v>
@@ -598,7 +598,7 @@
</func>
<func>
- <name>set_trace(Targets, Opts) -> void()</name>
+ <name since="">set_trace(Targets, Opts) -> void()</name>
<fsummary>Set trace target</fsummary>
<type>
<v>Targets = target() | targets()</v>
diff --git a/lib/snmp/doc/src/snmp_community_mib.xml b/lib/snmp/doc/src/snmp_community_mib.xml
index 61dea05950..9800fb6c00 100644
--- a/lib/snmp/doc/src/snmp_community_mib.xml
+++ b/lib/snmp/doc/src/snmp_community_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_community_mib.xml</file>
</header>
- <module>snmp_community_mib</module>
+ <module since="">snmp_community_mib</module>
<modulesummary>Instrumentation Functions for SNMP-COMMUNITY-MIB</modulesummary>
<description>
<p>The module <c>snmp_community_mib</c> implements the instrumentation
@@ -45,7 +45,7 @@
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-COMMUNITY-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -77,7 +77,7 @@
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-COMMUNITY-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -108,8 +108,8 @@
</func>
<func>
- <name>add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Ret</name>
- <name>add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Ret</name>
+ <name since="">add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Ret</name>
+ <name since="OTP R14B03">add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Ret</name>
<fsummary>Added one community</fsummary>
<type>
<v>Idx = string()</v>
@@ -132,7 +132,7 @@
</func>
<func>
- <name>delete_community(Key) -> Ret</name>
+ <name since="">delete_community(Key) -> Ret</name>
<fsummary>Delete one community</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_framework_mib.xml b/lib/snmp/doc/src/snmp_framework_mib.xml
index 64e5df6ff5..d84327d4d5 100644
--- a/lib/snmp/doc/src/snmp_framework_mib.xml
+++ b/lib/snmp/doc/src/snmp_framework_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_framework_mib.xml</file>
</header>
- <module>snmp_framework_mib</module>
+ <module since="">snmp_framework_mib</module>
<modulesummary>Instrumentation Functions for SNMP-FRAMEWORK-MIB</modulesummary>
<description>
<p>The module <c>snmp_framework_mib</c> implements instrumentation
@@ -44,7 +44,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-FRAMEWORK-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -75,7 +75,7 @@
</desc>
</func>
<func>
- <name>init() -> void()</name>
+ <name since="">init() -> void()</name>
<fsummary>Initialize the SNMP-FRAMEWORK-MIB</fsummary>
<desc>
<p>This function is called from the supervisor at system
@@ -88,7 +88,7 @@
</desc>
</func>
<func>
- <name>add_context(Ctx) -> Ret</name>
+ <name since="">add_context(Ctx) -> Ret</name>
<fsummary>Added one context</fsummary>
<type>
<v>Ctx = string()</v>
@@ -103,7 +103,7 @@
</desc>
</func>
<func>
- <name>delete_context(Key) -> Ret</name>
+ <name since="">delete_context(Key) -> Ret</name>
<fsummary>Delete one context</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_generic.xml b/lib/snmp/doc/src/snmp_generic.xml
index 44762dec59..6fb714907c 100644
--- a/lib/snmp/doc/src/snmp_generic.xml
+++ b/lib/snmp/doc/src/snmp_generic.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_generic.xml</file>
</header>
- <module>snmp_generic</module>
+ <module since="">snmp_generic</module>
<modulesummary>Generic Functions for Implementing SNMP Objects in a Database</modulesummary>
<description>
<marker id="description"></marker>
@@ -127,8 +127,8 @@ value() = term()
<funcs>
<func>
- <name>get_status_col(Name, Cols)</name>
- <name>get_status_col(NameDb, Cols) -> {ok, StatusVal} | false</name>
+ <name since="">get_status_col(Name, Cols)</name>
+ <name since="">get_status_col(NameDb, Cols) -> {ok, StatusVal} | false</name>
<fsummary>Get the value of the status column from <c>Cols</c></fsummary>
<type>
<v>Name = name()</v>
@@ -148,7 +148,7 @@ value() = term()
</func>
<func>
- <name>get_index_types(Name)</name>
+ <name since="">get_index_types(Name)</name>
<fsummary>Get the index types of <c>Name</c></fsummary>
<type>
<v>Name = name()</v>
@@ -163,7 +163,7 @@ value() = term()
</func>
<func>
- <name>get_table_info(Name, Item) -> table_info_result()</name>
+ <name since="OTP R15B01">get_table_info(Name, Item) -> table_info_result()</name>
<fsummary>Get table info item of MIB table <c>Name</c></fsummary>
<type>
<v>Name = name()</v>
@@ -187,8 +187,8 @@ value() = term()
</func>
<func>
- <name>table_func(Op1, NameDb)</name>
- <name>table_func(Op2, RowIndex, Cols, NameDb) -> Ret</name>
+ <name since="">table_func(Op1, NameDb)</name>
+ <name since="">table_func(Op2, RowIndex, Cols, NameDb) -> Ret</name>
<fsummary>Default instrumentation function for tables</fsummary>
<type>
<v>Op1 = new | delete </v>
@@ -232,7 +232,7 @@ value() = term()
</func>
<func>
- <name>table_get_elements(NameDb, RowIndex, Cols) -> Values</name>
+ <name since="">table_get_elements(NameDb, RowIndex, Cols) -> Values</name>
<fsummary>Get elements in a table row</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -249,7 +249,7 @@ value() = term()
</func>
<func>
- <name>table_next(NameDb, RestOid) -> RowIndex | endOfTable</name>
+ <name since="">table_next(NameDb, RestOid) -> RowIndex | endOfTable</name>
<fsummary>Find the next row in the table</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -265,7 +265,7 @@ value() = term()
</func>
<func>
- <name>table_row_exists(NameDb, RowIndex) -> bool()</name>
+ <name since="">table_row_exists(NameDb, RowIndex) -> bool()</name>
<fsummary>Check if a row in a table exists</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -279,7 +279,7 @@ value() = term()
</func>
<func>
- <name>table_set_elements(NameDb, RowIndex, Cols) -> bool()</name>
+ <name since="">table_set_elements(NameDb, RowIndex, Cols) -> bool()</name>
<fsummary>Set elements in a table row</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -300,8 +300,8 @@ value() = term()
</func>
<func>
- <name>variable_func(Op1, NameDb)</name>
- <name>variable_func(Op2, Val, NameDb) -> Ret</name>
+ <name since="">variable_func(Op1, NameDb)</name>
+ <name since="">variable_func(Op2, Val, NameDb) -> Ret</name>
<fsummary>Default instrumentation function for tables</fsummary>
<type>
<v>Op1 = new | delete | get</v>
@@ -325,7 +325,7 @@ value() = term()
</func>
<func>
- <name>variable_get(NameDb) -> {value, Value} | undefined</name>
+ <name since="">variable_get(NameDb) -> {value, Value} | undefined</name>
<fsummary>Get the value of a variable</fsummary>
<type>
<v>NameDb = name_db()</v>
@@ -339,7 +339,7 @@ value() = term()
</func>
<func>
- <name>variable_set(NameDb, NewVal) -> true | false</name>
+ <name since="">variable_set(NameDb, NewVal) -> true | false</name>
<fsummary>Set a value for a variable</fsummary>
<type>
<v>NameDb = name_db()</v>
diff --git a/lib/snmp/doc/src/snmp_index.xml b/lib/snmp/doc/src/snmp_index.xml
index 646e9661a3..1497f4cf67 100644
--- a/lib/snmp/doc/src/snmp_index.xml
+++ b/lib/snmp/doc/src/snmp_index.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_index.xml</file>
</header>
- <module>snmp_index</module>
+ <module since="">snmp_index</module>
<modulesummary>Abstract Data Type for SNMP Indexing</modulesummary>
<description>
<p>The module <c>snmp_index</c> implements an Abstract
@@ -159,7 +159,7 @@ get_next_pid(Oid, SnmpIndex) ->
</section>
<funcs>
<func>
- <name>delete(Index) -> true</name>
+ <name since="">delete(Index) -> true</name>
<fsummary>Delete an index table</fsummary>
<type>
<v>Index = NewIndex = index()</v>
@@ -173,7 +173,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>delete(Index, Key) -> NewIndex</name>
+ <name since="">delete(Index, Key) -> NewIndex</name>
<fsummary>Delete an item from the index</fsummary>
<type>
<v>Index = NewIndex = index()</v>
@@ -185,7 +185,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>get(Index, KeyOid) -> {ok, {KeyOid, Value}} | undefined</name>
+ <name since="">get(Index, KeyOid) -> {ok, {KeyOid, Value}} | undefined</name>
<fsummary>Get the item with <c>KeyOid</c></fsummary>
<type>
<v>Index = index()</v>
@@ -198,7 +198,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>get_last(Index) -> {ok, {KeyOid, Value}} | undefined</name>
+ <name since="">get_last(Index) -> {ok, {KeyOid, Value}} | undefined</name>
<fsummary>Get the last item in the index structure</fsummary>
<type>
<v>Index = index()</v>
@@ -210,7 +210,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>get_next(Index, KeyOid) -> {ok, {NextKeyOid, Value}} | undefined</name>
+ <name since="">get_next(Index, KeyOid) -> {ok, {NextKeyOid, Value}} | undefined</name>
<fsummary>Get the next item</fsummary>
<type>
<v>Index = index()</v>
@@ -224,7 +224,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>insert(Index, Key, Value) -> NewIndex</name>
+ <name since="">insert(Index, Key, Value) -> NewIndex</name>
<fsummary>Insert an item into the index</fsummary>
<type>
<v>Index = NewIndex = index()</v>
@@ -238,7 +238,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>key_to_oid(Index, Key) -> KeyOid</name>
+ <name since="">key_to_oid(Index, Key) -> KeyOid</name>
<fsummary>Convert a key to an OBJECT IDENTIFIER</fsummary>
<type>
<v>Index = index()</v>
@@ -250,7 +250,7 @@ get_next_pid(Oid, SnmpIndex) ->
</desc>
</func>
<func>
- <name>new(KeyTypes) -> Index</name>
+ <name since="">new(KeyTypes) -> Index</name>
<fsummary>Create a new snmp index structure</fsummary>
<type>
<v>KeyTypes = key_types()</v>
diff --git a/lib/snmp/doc/src/snmp_notification_mib.xml b/lib/snmp/doc/src/snmp_notification_mib.xml
index d2e288ec15..9395edf155 100644
--- a/lib/snmp/doc/src/snmp_notification_mib.xml
+++ b/lib/snmp/doc/src/snmp_notification_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_notification_mib.xml</file>
</header>
- <module>snmp_notification_mib</module>
+ <module since="">snmp_notification_mib</module>
<modulesummary>Instrumentation Functions for SNMP-NOTIFICATION-MIB</modulesummary>
<description>
<p>The module <c>snmp_notification_mib</c> implements the
@@ -43,7 +43,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-NOTIFICATION-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -70,7 +70,7 @@
</desc>
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-NOTIFICATION-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -98,7 +98,7 @@
</desc>
</func>
<func>
- <name>add_notify(Name, Tag, Type) -> Ret</name>
+ <name since="">add_notify(Name, Tag, Type) -> Ret</name>
<fsummary>Added one notify definition</fsummary>
<type>
<v>Name = string()</v>
@@ -115,7 +115,7 @@
</desc>
</func>
<func>
- <name>delete_notify(Key) -> Ret</name>
+ <name since="">delete_notify(Key) -> Ret</name>
<fsummary>Delete one notify definition</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_pdus.xml b/lib/snmp/doc/src/snmp_pdus.xml
index 1d086e6f48..f403b6edf4 100644
--- a/lib/snmp/doc/src/snmp_pdus.xml
+++ b/lib/snmp/doc/src/snmp_pdus.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_pdus.xml</file>
</header>
- <module>snmp_pdus</module>
+ <module since="">snmp_pdus</module>
<modulesummary>Encode and Decode Functions for SNMP PDUs</modulesummary>
<description>
<p>RFC1157, RFC1905 and/or RFC2272 should be studied carefully
@@ -55,7 +55,7 @@
</description>
<funcs>
<func>
- <name>dec_message([byte()]) -> Message</name>
+ <name since="">dec_message([byte()]) -> Message</name>
<fsummary>Decode an SNMP Message</fsummary>
<type>
<v>Message = #message</v>
@@ -71,7 +71,7 @@
</desc>
</func>
<func>
- <name>dec_message_only([byte()]) -> Message</name>
+ <name since="">dec_message_only([byte()]) -> Message</name>
<fsummary>Decode an SNMP Message, but not the data part</fsummary>
<type>
<v>Message = #message</v>
@@ -84,7 +84,7 @@
</desc>
</func>
<func>
- <name>dec_pdu([byte()]) -> Pdu</name>
+ <name since="">dec_pdu([byte()]) -> Pdu</name>
<fsummary>Decode an SNMP Pdu</fsummary>
<type>
<v>Pdu = #pdu</v>
@@ -94,7 +94,7 @@
</desc>
</func>
<func>
- <name>dec_scoped_pdu([byte()]) -> ScopedPdu</name>
+ <name since="">dec_scoped_pdu([byte()]) -> ScopedPdu</name>
<fsummary>Decode an SNMP ScopedPdu</fsummary>
<type>
<v>ScopedPdu = #scoped_pdu</v>
@@ -104,7 +104,7 @@
</desc>
</func>
<func>
- <name>dec_scoped_pdu_data([byte()]) -> ScopedPduData</name>
+ <name since="">dec_scoped_pdu_data([byte()]) -> ScopedPduData</name>
<fsummary>Decode an SNMP ScopedPduData</fsummary>
<type>
<v>ScopedPduData = #scoped_pdu | EncryptedPDU</v>
@@ -116,7 +116,7 @@
</desc>
</func>
<func>
- <name>dec_usm_security_parameters([byte()]) -> UsmSecParams</name>
+ <name since="">dec_usm_security_parameters([byte()]) -> UsmSecParams</name>
<fsummary>Decode SNMP UsmSecurityParameters</fsummary>
<type>
<v>UsmSecParams = #usmSecurityParameters</v>
@@ -126,7 +126,7 @@
</desc>
</func>
<func>
- <name>enc_encrypted_scoped_pdu(EncryptedScopedPdu) -> [byte()]</name>
+ <name since="">enc_encrypted_scoped_pdu(EncryptedScopedPdu) -> [byte()]</name>
<fsummary>Encode an encrypted SNMP scopedPDU</fsummary>
<type>
<v>EncryptedScopedPdu = [byte()]</v>
@@ -142,7 +142,7 @@
</desc>
</func>
<func>
- <name>enc_message(Message) -> [byte()]</name>
+ <name since="">enc_message(Message) -> [byte()]</name>
<fsummary>Encode an SNMP Message</fsummary>
<type>
<v>Message = #message</v>
@@ -152,7 +152,7 @@
</desc>
</func>
<func>
- <name>enc_message_only(Message) -> [byte()]</name>
+ <name since="">enc_message_only(Message) -> [byte()]</name>
<fsummary>Encode an SNMP Message, but not the data part</fsummary>
<type>
<v>Message = #message</v>
@@ -166,7 +166,7 @@
</desc>
</func>
<func>
- <name>enc_pdu(Pd) -> [byte()]</name>
+ <name since="">enc_pdu(Pd) -> [byte()]</name>
<fsummary>Encode an SNMP Pdu</fsummary>
<type>
<v>Pdu = #pdu</v>
@@ -176,7 +176,7 @@
</desc>
</func>
<func>
- <name>enc_scoped_pdu(ScopedPdu) -> [byte()]</name>
+ <name since="">enc_scoped_pdu(ScopedPdu) -> [byte()]</name>
<fsummary>Encode an SNMP scopedPDU</fsummary>
<type>
<v>ScopedPdu = #scoped_pdu</v>
@@ -190,7 +190,7 @@
</desc>
</func>
<func>
- <name>enc_usm_security_parameters(UsmSecParams) -> [byte()]</name>
+ <name since="">enc_usm_security_parameters(UsmSecParams) -> [byte()]</name>
<fsummary>Encode SNMP UsmSecurityParameters</fsummary>
<type>
<v>UsmSecParams = #usmSecurityParameters</v>
diff --git a/lib/snmp/doc/src/snmp_standard_mib.xml b/lib/snmp/doc/src/snmp_standard_mib.xml
index 35efbba483..eb4e2fd097 100644
--- a/lib/snmp/doc/src/snmp_standard_mib.xml
+++ b/lib/snmp/doc/src/snmp_standard_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_standard_mib.xml</file>
</header>
- <module>snmp_standard_mib</module>
+ <module since="">snmp_standard_mib</module>
<modulesummary>Instrumentation Functions for STANDARD-MIB and SNMPv2-MIB</modulesummary>
<description>
<p>The module <c>snmp_standard_mib</c> implements the instrumentation functions for the
@@ -42,7 +42,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the STANDARD-MIB and SNMPv2-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -71,8 +71,8 @@
</desc>
</func>
<func>
- <name>inc(Name) -> void()</name>
- <name>inc(Name, N) -> void()</name>
+ <name since="">inc(Name) -> void()</name>
+ <name since="">inc(Name, N) -> void()</name>
<fsummary>Increment a variable in the MIB</fsummary>
<type>
<v>Name = atom()</v>
@@ -84,7 +84,7 @@
</desc>
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the STANDARD-MIB and SNMPv2-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -114,14 +114,14 @@
</desc>
</func>
<func>
- <name>reset() -> void()</name>
+ <name since="">reset() -> void()</name>
<fsummary>Reset all <c>snmp</c>counters to 0</fsummary>
<desc>
<p>Resets all <c>snmp</c> counters to 0.</p>
</desc>
</func>
<func>
- <name>sys_up_time() -> Time</name>
+ <name since="">sys_up_time() -> Time</name>
<fsummary>Get the system up time</fsummary>
<type>
<v>Time = int()</v>
diff --git a/lib/snmp/doc/src/snmp_target_mib.xml b/lib/snmp/doc/src/snmp_target_mib.xml
index c3bcd3b4e3..c46edb810d 100644
--- a/lib/snmp/doc/src/snmp_target_mib.xml
+++ b/lib/snmp/doc/src/snmp_target_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_target_mib.xml</file>
</header>
- <module>snmp_target_mib</module>
+ <module since="">snmp_target_mib</module>
<modulesummary>Instrumentation Functions for SNMP-TARGET-MIB</modulesummary>
<description>
<p>The module <c>snmp_target_mib</c> implements the instrumentation
@@ -57,7 +57,7 @@
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-TARGET-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -85,7 +85,7 @@
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-TARGET-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -113,7 +113,7 @@
</func>
<func>
- <name>set_target_engine_id(TargetAddrName, EngineId) -> boolean()</name>
+ <name since="">set_target_engine_id(TargetAddrName, EngineId) -> boolean()</name>
<fsummary>Set the engine id for a targetAddr row.</fsummary>
<type>
<v>TargetAddrName = string()</v>
@@ -130,7 +130,7 @@
</func>
<func>
- <name>add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name>
+ <name since="">add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name>
<fsummary>Add one target address definition</fsummary>
<type>
<v>Name = string()</v>
@@ -156,7 +156,7 @@
</func>
<func>
- <name>delete_addr(Key) -> Ret</name>
+ <name since="">delete_addr(Key) -> Ret</name>
<fsummary>Delete one target address definition</fsummary>
<type>
<v>Key = term()</v>
@@ -171,7 +171,7 @@
</func>
<func>
- <name>add_params(Name, MPModel, SecModel, SecName, SecLevel) -> Ret</name>
+ <name since="">add_params(Name, MPModel, SecModel, SecName, SecLevel) -> Ret</name>
<fsummary>Add one target parameter definition</fsummary>
<type>
<v>Name = string()</v>
@@ -191,7 +191,7 @@
</desc>
</func>
<func>
- <name>delete_params(Key) -> Ret</name>
+ <name since="">delete_params(Key) -> Ret</name>
<fsummary>Delete one target parameter definition</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_user_based_sm_mib.xml b/lib/snmp/doc/src/snmp_user_based_sm_mib.xml
index cc376ac118..6c2203ed22 100644
--- a/lib/snmp/doc/src/snmp_user_based_sm_mib.xml
+++ b/lib/snmp/doc/src/snmp_user_based_sm_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_user_based_sm_mib.xml</file>
</header>
- <module>snmp_user_based_sm_mib</module>
+ <module since="">snmp_user_based_sm_mib</module>
<modulesummary>Instrumentation Functions for SNMP-USER-BASED-SM-MIB</modulesummary>
<description>
<p>The module <c>snmp_user_based_sm_mib</c> implements the instrumentation
@@ -43,7 +43,7 @@
</description>
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-USER-BASED-SM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -72,7 +72,7 @@
</desc>
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-USER-BASED-SM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -103,7 +103,7 @@
</desc>
</func>
<func>
- <name>add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> Ret</name>
+ <name since="">add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> Ret</name>
<fsummary>Add one user</fsummary>
<type>
<v>EngineID = string()</v>
@@ -130,7 +130,7 @@
</desc>
</func>
<func>
- <name>delete_user(Key) -> Ret</name>
+ <name since="">delete_user(Key) -> Ret</name>
<fsummary>Delete one user</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
index fdad735e71..c5e98a3eb5 100644
--- a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
+++ b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmp_view_based_acm_mib.xml</file>
</header>
- <module>snmp_view_based_acm_mib</module>
+ <module since="">snmp_view_based_acm_mib</module>
<modulesummary>Instrumentation Functions for SNMP-VIEW-BASED-ACM-MIB</modulesummary>
<description>
<p>The module <c>snmp_view_based_acm_mib</c> implements the instrumentation functions for the
@@ -45,7 +45,7 @@
<funcs>
<func>
- <name>configure(ConfDir) -> void()</name>
+ <name since="">configure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-VIEW-BASED-ACM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -71,7 +71,7 @@
</func>
<func>
- <name>reconfigure(ConfDir) -> void()</name>
+ <name since="">reconfigure(ConfDir) -> void()</name>
<fsummary>Configure the SNMP-VIEW-BASED-ACM-MIB</fsummary>
<type>
<v>ConfDir = string()</v>
@@ -104,7 +104,7 @@
</func>
<func>
- <name>add_sec2group(SecModel, SecName, GroupName) -> Ret</name>
+ <name since="">add_sec2group(SecModel, SecName, GroupName) -> Ret</name>
<fsummary>Add one security to group definition</fsummary>
<type>
<v>SecModel = v1 | v2c | usm</v>
@@ -124,7 +124,7 @@
</func>
<func>
- <name>delete_sec2group(Key) -> Ret</name>
+ <name since="">delete_sec2group(Key) -> Ret</name>
<fsummary>Delete one security to group definition</fsummary>
<type>
<v>Key = term()</v>
@@ -139,7 +139,7 @@
</func>
<func>
- <name>add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Ret</name>
+ <name since="">add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Ret</name>
<fsummary>Add one access definition</fsummary>
<type>
<v>GroupName = string()</v>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>delete_access(Key) -> Ret</name>
+ <name since="">delete_access(Key) -> Ret</name>
<fsummary>Delete one access definition</fsummary>
<type>
<v>Key = term()</v>
@@ -178,7 +178,7 @@
</func>
<func>
- <name>add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Ret</name>
+ <name since="">add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Ret</name>
<fsummary>Add one view tree family definition</fsummary>
<type>
<v>ViewIndex = integer()</v>
@@ -199,7 +199,7 @@
</func>
<func>
- <name>delete_view_tree_fam(Key) -> Ret</name>
+ <name since="">delete_view_tree_fam(Key) -> Ret</name>
<fsummary>Delete one view tree family definition</fsummary>
<type>
<v>Key = term()</v>
diff --git a/lib/snmp/doc/src/snmpa.xml b/lib/snmp/doc/src/snmpa.xml
index b78f14da01..dc2f4e6d66 100644
--- a/lib/snmp/doc/src/snmpa.xml
+++ b/lib/snmp/doc/src/snmpa.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa.xml</file>
</header>
- <module>snmpa</module>
+ <module since="">snmpa</module>
<modulesummary>Interface Functions to the SNMP toolkit agent</modulesummary>
<description>
<p>The module <c>snmpa</c> contains interface functions to the
@@ -77,7 +77,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<funcs>
<func>
- <name>add_agent_caps(SysORID, SysORDescr) -> SysORIndex</name>
+ <name since="">add_agent_caps(SysORID, SysORDescr) -> SysORIndex</name>
<fsummary>Add an AGENT-CAPABILITY definition to the agent</fsummary>
<type>
<v>SysORID = oid()</v>
@@ -93,7 +93,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>del_agent_caps(SysORIndex) -> void()</name>
+ <name since="">del_agent_caps(SysORIndex) -> void()</name>
<fsummary>Delete an AGENT-CAPABILITY definition from the agent</fsummary>
<type>
<v>SysORIndex = integer()</v>
@@ -108,7 +108,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>get_agent_caps() -> [[SysORIndex, SysORID, SysORDescr, SysORUpTime]]</name>
+ <name since="">get_agent_caps() -> [[SysORIndex, SysORID, SysORDescr, SysORUpTime]]</name>
<fsummary>Return all AGENT-CAPABILITY definitions in the agent</fsummary>
<type>
<v>SysORIndex = integer()</v>
@@ -125,8 +125,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>get(Agent, Vars) -> Values | {error, Reason}</name>
- <name>get(Agent, Vars, Context) -> Values | {error, Reason}</name>
+ <name since="">get(Agent, Vars) -> Values | {error, Reason}</name>
+ <name since="">get(Agent, Vars, Context) -> Values | {error, Reason}</name>
<fsummary>Perform a get operation on the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -150,8 +150,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>get_next(Agent, Vars) -> Values | {error, Reason}</name>
- <name>get_next(Agent, Vars, Context) -> Values | {error, Reason}</name>
+ <name since="">get_next(Agent, Vars) -> Values | {error, Reason}</name>
+ <name since="">get_next(Agent, Vars, Context) -> Values | {error, Reason}</name>
<fsummary>Perform a get-next operation on the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -176,7 +176,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<!--
<func>
- <name>get_symbolic_store_db() -> Db</name>
+ <name since="">get_symbolic_store_db() -> Db</name>
<fsummary>Retrieve the symbolic store database reference</fsummary>
<type>
<v>Db = term()</v>
@@ -193,8 +193,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
-->
<func>
- <name>backup(BackupDir) -> ok | {error, Reason}</name>
- <name>backup(Agent, BackupDir) -> ok | {error, Reason}</name>
+ <name since="">backup(BackupDir) -> ok | {error, Reason}</name>
+ <name since="">backup(Agent, BackupDir) -> ok | {error, Reason}</name>
<fsummary>Backup agent data</fsummary>
<type>
<v>BackupDir = string()</v>
@@ -216,8 +216,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</desc>
</func>
<func>
- <name>info() -> [{Key, Value}]</name>
- <name>info(Agent) -> [{Key, Value}]</name>
+ <name since="">info() -> [{Key, Value}]</name>
+ <name since="">info(Agent) -> [{Key, Value}]</name>
<fsummary>Return information about the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -236,7 +236,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>old_info_format(NewInfo) -> OldInfo</name>
+ <name since="">old_info_format(NewInfo) -> OldInfo</name>
<fsummary>Return information about the agent</fsummary>
<type>
<v>OldInfo = NewInfo = [{Key, Value}]</v>
@@ -251,8 +251,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>load_mib(Mib) -> ok | {error, Reason}</name>
- <name>load_mib(Agent, Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">load_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">load_mib(Agent, Mib) -> ok | {error, Reason}</name>
<fsummary>Load single MIB into the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -273,10 +273,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>load_mibs(Mibs) -> ok | {error, Reason}</name>
- <name>load_mibs(Mibs, Force) -> ok | {error, Reason}</name>
- <name>load_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
- <name>load_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">load_mibs(Mibs) -> ok | {error, Reason}</name>
+ <name since="">load_mibs(Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">load_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">load_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
<fsummary>Load MIBs into the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -305,8 +305,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>unload_mib(Mib) -> ok | {error, Reason}</name>
- <name>unload_mib(Agent, Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">unload_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">unload_mib(Agent, Mib) -> ok | {error, Reason}</name>
<fsummary>Unload single MIB from the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -321,10 +321,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>unload_mibs(Mibs) -> ok | {error, Reason}</name>
- <name>unload_mibs(Mibs, Force) -> ok | {error, Reason}</name>
- <name>unload_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
- <name>unload_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">unload_mibs(Mibs) -> ok | {error, Reason}</name>
+ <name since="">unload_mibs(Mibs, Force) -> ok | {error, Reason}</name>
+ <name since="">unload_mibs(Agent, Mibs) -> ok | {error, Reason}</name>
+ <name since="OTP R16B02">unload_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name>
<fsummary>Unload MIBs from the agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -347,8 +347,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_mibs() -> Mibs</name>
- <name>which_mibs(Agent) -> Mibs</name>
+ <name since="">which_mibs() -> Mibs</name>
+ <name since="">which_mibs(Agent) -> Mibs</name>
<fsummary>Get a list of all the loaded mibs</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -365,8 +365,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>whereis_mib(MibName) -> {ok, MibFile} | {error, Reason}</name>
- <name>whereis_mib(Agent, MibName) -> {ok, MibFile} | {error, Reason}</name>
+ <name since="">whereis_mib(MibName) -> {ok, MibFile} | {error, Reason}</name>
+ <name since="">whereis_mib(Agent, MibName) -> {ok, MibFile} | {error, Reason}</name>
<fsummary>Get the path to the mib file</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -385,10 +385,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>current_request_id() -> {value, RequestId} | false</name>
- <name>current_context() -> {value, Context} | false</name>
- <name>current_community() -> {value, Community} | false</name>
- <name>current_address() -> {value, Address} | false</name>
+ <name since="">current_request_id() -> {value, RequestId} | false</name>
+ <name since="">current_context() -> {value, Context} | false</name>
+ <name since="">current_community() -> {value, Community} | false</name>
+ <name since="">current_address() -> {value, Address} | false</name>
<fsummary>Get the request-id, context, community and address of the current request</fsummary>
<type>
<v>RequestId = integer()</v>
@@ -409,8 +409,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>enum_to_int(Name, Enum) -> {value, Int} | false</name>
- <name>enum_to_int(Db, Name, Enum) -> {value, Int} | false</name>
+ <name since="">enum_to_int(Name, Enum) -> {value, Int} | false</name>
+ <name since="">enum_to_int(Db, Name, Enum) -> {value, Int} | false</name>
<fsummary>Convert an enum value to an integer</fsummary>
<type>
<v>Db = term()</v>
@@ -435,8 +435,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>int_to_enum(Name, Int) -> {value, Enum} | false</name>
- <name>int_to_enum(Db, Name, Int) -> {value, Enum} | false</name>
+ <name since="">int_to_enum(Name, Int) -> {value, Enum} | false</name>
+ <name since="">int_to_enum(Db, Name, Int) -> {value, Enum} | false</name>
<fsummary>Convert an integer to an enum value</fsummary>
<type>
<v>Db = term()</v>
@@ -461,8 +461,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>name_to_oid(Name) -> {value, oid()} | false</name>
- <name>name_to_oid(Db, Name) -> {value, oid()} | false</name>
+ <name since="">name_to_oid(Name) -> {value, oid()} | false</name>
+ <name since="">name_to_oid(Db, Name) -> {value, oid()} | false</name>
<fsummary>Convert a symbolic name to an OID</fsummary>
<type>
<v>Db = term()</v>
@@ -482,8 +482,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>oid_to_name(OID) -> {value, Name} | false</name>
- <name>oid_to_name(Db, OID) -> {value, Name} | false</name>
+ <name since="">oid_to_name(OID) -> {value, Name} | false</name>
+ <name since="">oid_to_name(Db, OID) -> {value, Name} | false</name>
<fsummary>Convert an OID to a symbolic name</fsummary>
<type>
<v>Db = term()</v>
@@ -503,7 +503,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_aliasnames() -> Result</name>
+ <name since="">which_aliasnames() -> Result</name>
<fsummary>Get all alias-names known to the agent</fsummary>
<type>
<v>Result = [atom()]</v>
@@ -515,7 +515,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_tables() -> Result</name>
+ <name since="">which_tables() -> Result</name>
<fsummary>Get all tables known to the agent</fsummary>
<type>
<v>Result = [atom()]</v>
@@ -528,7 +528,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_variables() -> Result</name>
+ <name since="">which_variables() -> Result</name>
<fsummary>Get all variables known to the agent</fsummary>
<type>
<v>Result = [atom()]</v>
@@ -541,7 +541,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_notifications() -> Result</name>
+ <name since="">which_notifications() -> Result</name>
<fsummary>Get all notifications known to the agent</fsummary>
<type>
<v>Result = [{Name, MibName, Info}]</v>
@@ -557,15 +557,15 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>log_to_txt(LogDir)</name>
- <name>log_to_txt(LogDir, Block | Mibs)</name>
- <name>log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_txt(LogDir)</name>
+ <name since="">log_to_txt(LogDir, Block | Mibs)</name>
+ <name since="">log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -600,14 +600,14 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -641,7 +641,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>change_log_size(NewSize) -> ok | {error, Reason}</name>
+ <name since="">change_log_size(NewSize) -> ok | {error, Reason}</name>
<fsummary>Change the size of the Audit Trail Log</fsummary>
<type>
<v>NewSize = {MaxBytes, MaxFiles}</v>
@@ -662,8 +662,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
- <name>set_log_type(Agent, NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(Agent, NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the type of the Audit Trail Log</fsummary>
<type>
<v>NewType = OldType = atl_type()</v>
@@ -684,8 +684,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>mib_of(Oid) -> {ok, MibName} | {error, Reason}</name>
- <name>mib_of(Agent, Oid) -> {ok, MibName} | {error, Reason}</name>
+ <name since="">mib_of(Oid) -> {ok, MibName} | {error, Reason}</name>
+ <name since="">mib_of(Agent, Oid) -> {ok, MibName} | {error, Reason}</name>
<fsummary>Which mib an Oid belongs to</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -704,8 +704,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>me_of(Oid) -> {ok, Me} | {error, Reason}</name>
- <name>me_of(Agent, Oid) -> {ok, Me} | {error, Reason}</name>
+ <name since="">me_of(Oid) -> {ok, Me} | {error, Reason}</name>
+ <name since="">me_of(Agent, Oid) -> {ok, Me} | {error, Reason}</name>
<fsummary>Retrieve the mib-entry of an Oid</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -724,8 +724,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>invalidate_mibs_cache() -> void()</name>
- <name>invalidate_mibs_cache(Agent) -> void()</name>
+ <name since="">invalidate_mibs_cache() -> void()</name>
+ <name since="">invalidate_mibs_cache(Agent) -> void()</name>
<fsummary>Invalidate the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -739,8 +739,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>enable_mibs_cache() -> void()</name>
- <name>enable_mibs_cache(Agent) -> void()</name>
+ <name since="">enable_mibs_cache() -> void()</name>
+ <name since="">enable_mibs_cache(Agent) -> void()</name>
<fsummary>Enable the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -753,8 +753,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>disable_mibs_cache() -> void()</name>
- <name>disable_mibs_cache(Agent) -> void()</name>
+ <name since="">disable_mibs_cache() -> void()</name>
+ <name since="">disable_mibs_cache(Agent) -> void()</name>
<fsummary>Disable the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -767,8 +767,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_mibs_cache_size() -> void()</name>
- <name>which_mibs_cache_size(Agent) -> void()</name>
+ <name since="OTP R14B">which_mibs_cache_size() -> void()</name>
+ <name since="OTP R14B">which_mibs_cache_size(Agent) -> void()</name>
<fsummary>The size of the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -781,12 +781,12 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>gc_mibs_cache() -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Agent) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Agent, Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
- <name>gc_mibs_cache(Agent, Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache() -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Agent) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Agent, Age) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
+ <name since="">gc_mibs_cache(Agent, Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name>
<fsummary>Perform mib server cache gc</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -808,8 +808,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>enable_mibs_cache_autogc() -> void()</name>
- <name>enable_mibs_cache_autogc(Agent) -> void()</name>
+ <name since="">enable_mibs_cache_autogc() -> void()</name>
+ <name since="">enable_mibs_cache_autogc(Agent) -> void()</name>
<fsummary>Enable automatic gc of the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -822,8 +822,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>disable_mibs_cache_autogc() -> void()</name>
- <name>disable_mibs_cache_autogc(Agent) -> void()</name>
+ <name since="">disable_mibs_cache_autogc() -> void()</name>
+ <name since="">disable_mibs_cache_autogc(Agent) -> void()</name>
<fsummary>Disable automatic gc of the mib server cache</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -836,8 +836,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>update_mibs_cache_age(NewAge) -> ok | {error, Reason}</name>
- <name>update_mibs_cache_age(Agent, NewAge) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_age(NewAge) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_age(Agent, NewAge) -> ok | {error, Reason}</name>
<fsummary>Change the mib server cache age property</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -852,8 +852,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>update_mibs_cache_gclimit(NewGcLimit) -> ok | {error, Reason}</name>
- <name>update_mibs_cache_gclimit(Agent, NewGCLimit) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_gclimit(NewGcLimit) -> ok | {error, Reason}</name>
+ <name since="">update_mibs_cache_gclimit(Agent, NewGCLimit) -> ok | {error, Reason}</name>
<fsummary>Change the mib server cache gclimit property</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -869,10 +869,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<func>
- <name>register_notification_filter(Id, Mod, Data) -> ok | {error, Reason}</name>
- <name>register_notification_filter(Agent, Id, Mod, Data) -> ok | {error, Reason}</name>
- <name>register_notification_filter(Id, Mod, Data, Where) -> ok | {error, Reason}</name>
- <name>register_notification_filter(Agent, Id, Mod, Data, Where) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Id, Mod, Data) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Agent, Id, Mod, Data) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Id, Mod, Data, Where) -> ok | {error, Reason}</name>
+ <name since="">register_notification_filter(Agent, Id, Mod, Data, Where) -> ok | {error, Reason}</name>
<fsummary>Register a notification filter</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -897,8 +897,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>unregister_notification_filter(Id) -> ok | {error, Reason}</name>
- <name>unregister_notification_filter(Agent, Id) -> ok | {error, Reason}</name>
+ <name since="">unregister_notification_filter(Id) -> ok | {error, Reason}</name>
+ <name since="">unregister_notification_filter(Agent, Id) -> ok | {error, Reason}</name>
<fsummary>Unregister a notification filter</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -913,8 +913,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>which_notification_filter() -> Filters</name>
- <name>which_notification_filter(Agent) -> Filters</name>
+ <name since="">which_notification_filter() -> Filters</name>
+ <name since="">which_notification_filter(Agent) -> Filters</name>
<fsummary>Which notification filter</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -929,8 +929,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>set_request_limit(NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
- <name>set_request_limit(Agent, NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
+ <name since="">set_request_limit(NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
+ <name since="">set_request_limit(Agent, NewLimit) -> {ok, OldLimit} | {error, Reason}</name>
<fsummary>Change the request limit</fsummary>
<type>
<v>NewLimit = OldLimit = infinity | integer() >= 0</v>
@@ -950,7 +950,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
</func>
<func>
- <name>register_subagent(Agent, SubTreeOid, Subagent) -> ok | {error, Reason}</name>
+ <name since="">register_subagent(Agent, SubTreeOid, Subagent) -> ok | {error, Reason}</name>
<fsummary>Register a sub-agent under a sub-tree</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -975,7 +975,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>unregister_subagent(Agent, SubagentOidOrPid) -> ok | {ok, SubAgentPid} | {error, Reason}</name>
+ <name since="">unregister_subagent(Agent, SubagentOidOrPid) -> ok | {ok, SubAgentPid} | {error, Reason}</name>
<fsummary>Unregister a sub-agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -992,7 +992,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<func>
- <name>send_notification2(Agent, Notification, SendOpts) -> void()</name>
+ <name since="OTP R14B03">send_notification2(Agent, Notification, SendOpts) -> void()</name>
<fsummary>Send notification</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1119,11 +1119,11 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<func>
- <name>send_notification(Agent, Notification, Receiver)</name>
- <name>send_notification(Agent, Notification, Receiver, Varbinds)</name>
- <name>send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name>
- <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name>
- <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name>
+ <name since="">send_notification(Agent, Notification, Receiver)</name>
+ <name since="">send_notification(Agent, Notification, Receiver, Varbinds)</name>
+ <name since="">send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name>
+ <name since="">send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name>
+ <name since="OTP R14B">send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name>
<fsummary>Send a notification</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1324,13 +1324,13 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<func>
- <name>discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, ContextName, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
- <name>discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, ContextName, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name>
+ <name since="">discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> {ok, ManagerEngineID} | {error, Reason}</name>
<fsummary>Initiate the discovery process with a manager</fsummary>
<type>
<v>TargetName = string()</v>
@@ -1379,7 +1379,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>convert_config(OldConfig) -> AgentConfig</name>
+ <name since="">convert_config(OldConfig) -> AgentConfig</name>
<fsummary>Convert old snmp config to new agent config</fsummary>
<type>
<v>OldConfig = list()</v>
@@ -1403,8 +1403,8 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>restart_worker() -> void()</name>
- <name>restart_worker(Agent) -> void()</name>
+ <name since="">restart_worker() -> void()</name>
+ <name since="">restart_worker(Agent) -> void()</name>
<fsummary>Restart the worker process of a multi-threaded agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1419,8 +1419,8 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>restart_set_worker() -> void()</name>
- <name>restart_set_worker(Agent) -> void()</name>
+ <name since="">restart_set_worker() -> void()</name>
+ <name since="">restart_set_worker(Agent) -> void()</name>
<fsummary>Restart the set worker process of a multi-threaded agent</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -1435,7 +1435,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>print_mib_info() -> void()</name>
+ <name since="OTP R14B02">print_mib_info() -> void()</name>
<fsummary>Print mib info</fsummary>
<desc>
<p>Prints the content of all the (snmp) tables and variables
@@ -1446,7 +1446,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>print_mib_tables() -> void()</name>
+ <name since="OTP R14B02">print_mib_tables() -> void()</name>
<fsummary>Print mib tables</fsummary>
<desc>
<p>Prints the content of all the (snmp) tables
@@ -1457,7 +1457,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>print_mib_variables() -> void()</name>
+ <name since="OTP R14B02">print_mib_variables() -> void()</name>
<fsummary>Print mib variables</fsummary>
<desc>
<p>Prints the content of all the (snmp) variables
@@ -1468,7 +1468,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
</func>
<func>
- <name>verbosity(Ref,Verbosity) -> void()</name>
+ <name since="">verbosity(Ref,Verbosity) -> void()</name>
<fsummary>Assign a new verbosity for the process</fsummary>
<type>
<v>Ref = pid() | sub_agents | master_agent | net_if | mib_server | symbolic_store | note_store | local_db</v>
diff --git a/lib/snmp/doc/src/snmpa_conf.xml b/lib/snmp/doc/src/snmpa_conf.xml
index 503e44a6a2..4134a81c0c 100644
--- a/lib/snmp/doc/src/snmpa_conf.xml
+++ b/lib/snmp/doc/src/snmpa_conf.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_conf.xml</file>
</header>
- <module>snmpa_conf</module>
+ <module since="">snmpa_conf</module>
<modulesummary>Utility functions for handling the agent config files.</modulesummary>
<description>
<p>The module <c>snmpa_conf</c> contains various utility functions to
@@ -92,7 +92,7 @@ word() = 0..65535
<funcs>
<func>
- <name>agent_entry(Tag, Val) -> agent_entry()</name>
+ <name since="">agent_entry(Tag, Val) -> agent_entry()</name>
<fsummary>Create an agent entry</fsummary>
<type>
<v>Tag = intAgentTransports | intAgentUDPPort | intAgentMaxPacketSize | snmpEngineMaxMessageSize | snmpEngineID</v>
@@ -111,8 +111,8 @@ word() = 0..65535
</func>
<func>
- <name>write_agent_config(Dir, Conf) -> ok</name>
- <name>write_agent_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_agent_config(Dir, Conf) -> ok</name>
+ <name since="">write_agent_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -134,7 +134,7 @@ word() = 0..65535
</func>
<func>
- <name>append_agent_config(Dir, Conf) -> ok</name>
+ <name since="">append_agent_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -153,7 +153,7 @@ word() = 0..65535
</func>
<func>
- <name>read_agent_config(Dir) -> Conf</name>
+ <name since="">read_agent_config(Dir) -> Conf</name>
<fsummary>Read the agent config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -172,7 +172,7 @@ word() = 0..65535
</func>
<func>
- <name>standard_entry(Tag, Val) -> standard_entry()</name>
+ <name since="">standard_entry(Tag, Val) -> standard_entry()</name>
<fsummary>Create an standard entry</fsummary>
<type>
<v>Tag = sysDescr | sysObjectID | sysContact | sysName | sysLocation | sysServices | snmpEnableAuthenTraps</v>
@@ -192,8 +192,8 @@ word() = 0..65535
</func>
<func>
- <name>write_standard_config(Dir, Conf) -> ok</name>
- <name>write_standard_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_standard_config(Dir, Conf) -> ok</name>
+ <name since="">write_standard_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent standard config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -216,7 +216,7 @@ word() = 0..65535
</func>
<func>
- <name>append_standard_config(Dir, Conf) -> ok</name>
+ <name since="">append_standard_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent standard config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -236,7 +236,7 @@ word() = 0..65535
</func>
<func>
- <name>read_standard_config(Dir) -> Conf</name>
+ <name since="">read_standard_config(Dir) -> Conf</name>
<fsummary>Read the agent standard config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -255,7 +255,7 @@ word() = 0..65535
</func>
<func>
- <name>context_entry(Context) -> context_entry()</name>
+ <name since="">context_entry(Context) -> context_entry()</name>
<fsummary>Create an context entry</fsummary>
<type>
<v>Context = string()</v>
@@ -273,8 +273,8 @@ word() = 0..65535
</func>
<func>
- <name>write_context_config(Dir, Conf) -> ok</name>
- <name>write_context_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_context_config(Dir, Conf) -> ok</name>
+ <name since="">write_context_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent context(s) to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -297,7 +297,7 @@ word() = 0..65535
</func>
<func>
- <name>append_context_config(Dir, Conf) -> ok</name>
+ <name since="">append_context_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent context(s) to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -317,7 +317,7 @@ word() = 0..65535
</func>
<func>
- <name>read_context_config(Dir) -> Conf</name>
+ <name since="">read_context_config(Dir) -> Conf</name>
<fsummary>Read the agent context config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -336,8 +336,8 @@ word() = 0..65535
</func>
<func>
- <name>community_entry(CommunityIndex) -> community_entry()</name>
- <name>community_entry(CommunityIndex, CommunityName, SecName, ContextName, TransportTag) -> community_entry()</name>
+ <name since="">community_entry(CommunityIndex) -> community_entry()</name>
+ <name since="">community_entry(CommunityIndex, CommunityName, SecName, ContextName, TransportTag) -> community_entry()</name>
<fsummary>Create an community entry</fsummary>
<type>
<v>CommunityIndex = string()</v>
@@ -364,8 +364,8 @@ word() = 0..65535
</func>
<func>
- <name>write_community_config(Dir, Conf) -> ok</name>
- <name>write_community_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_community_config(Dir, Conf) -> ok</name>
+ <name since="">write_community_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent community config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -388,7 +388,7 @@ word() = 0..65535
</func>
<func>
- <name>append_community_config(Dir, Conf) -> ok</name>
+ <name since="">append_community_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent community config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -408,7 +408,7 @@ word() = 0..65535
</func>
<func>
- <name>read_community_config(Dir) -> Conf</name>
+ <name since="">read_community_config(Dir) -> Conf</name>
<fsummary>Read the agent community config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -427,10 +427,10 @@ word() = 0..65535
</func>
<func>
- <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name>
- <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name>
- <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
- <name>target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
+ <name since="">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name>
+ <name since="OTP 17.3">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name>
+ <name since="">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
+ <name since="">target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name>
<fsummary>Create an target_addr entry</fsummary>
<type>
<v>Name = string()</v>
@@ -464,8 +464,8 @@ word() = 0..65535
</func>
<func>
- <name>write_target_addr_config(Dir, Conf) -> ok</name>
- <name>write_target_addr_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_target_addr_config(Dir, Conf) -> ok</name>
+ <name since="">write_target_addr_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent target_addr config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -488,7 +488,7 @@ word() = 0..65535
</func>
<func>
- <name>append_target_addr_config(Dir, Conf) -> ok</name>
+ <name since="">append_target_addr_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent target_addr config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -508,7 +508,7 @@ word() = 0..65535
</func>
<func>
- <name>read_target_addr_config(Dir) -> Conf</name>
+ <name since="">read_target_addr_config(Dir) -> Conf</name>
<fsummary>Read the agent target_addr config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -527,9 +527,9 @@ word() = 0..65535
</func>
<func>
- <name>target_params_entry(Name, Vsn) -> target_params_entry()</name>
- <name>target_params_entry(Name, Vsn, SecName, SecLevel) -> target_params_entry()</name>
- <name>target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> target_params_entry()</name>
+ <name since="">target_params_entry(Name, Vsn) -> target_params_entry()</name>
+ <name since="">target_params_entry(Name, Vsn, SecName, SecLevel) -> target_params_entry()</name>
+ <name since="">target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> target_params_entry()</name>
<fsummary>Create an target_params entry</fsummary>
<type>
<v>Name = string()</v>
@@ -564,8 +564,8 @@ word() = 0..65535
</func>
<func>
- <name>write_target_params_config(Dir, Conf) -> ok</name>
- <name>write_target_params_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_target_params_config(Dir, Conf) -> ok</name>
+ <name since="">write_target_params_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent target_params config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -588,7 +588,7 @@ word() = 0..65535
</func>
<func>
- <name>append_target_params_config(Dir, Conf) -> ok</name>
+ <name since="">append_target_params_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent target_params config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -608,7 +608,7 @@ word() = 0..65535
</func>
<func>
- <name>read_target_params_config(Dir) -> Conf</name>
+ <name since="">read_target_params_config(Dir) -> Conf</name>
<fsummary>Read the agent target_params config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -627,10 +627,10 @@ word() = 0..65535
</func>
<func>
- <name>vacm_s2g_entry(SecModel, SecName, GroupName) -> vacm_s2g_entry()</name>
- <name>vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, ReadView, WriteView, NotifyView) -> vacm_acc_entry()</name>
- <name>vacm_vtf_entry(ViewIndex, ViewSubtree) -> vacm_vtf_entry()</name>
- <name>vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> vacm_vtf_entry()</name>
+ <name since="">vacm_s2g_entry(SecModel, SecName, GroupName) -> vacm_s2g_entry()</name>
+ <name since="">vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, ReadView, WriteView, NotifyView) -> vacm_acc_entry()</name>
+ <name since="">vacm_vtf_entry(ViewIndex, ViewSubtree) -> vacm_vtf_entry()</name>
+ <name since="">vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> vacm_vtf_entry()</name>
<fsummary>Create an vacm entry</fsummary>
<type>
<v>SecModel = v1 | v2c | usm</v>
@@ -665,8 +665,8 @@ word() = 0..65535
</func>
<func>
- <name>write_vacm_config(Dir, Conf) -> ok</name>
- <name>write_vacm_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_vacm_config(Dir, Conf) -> ok</name>
+ <name since="">write_vacm_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent vacm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -690,7 +690,7 @@ word() = 0..65535
</func>
<func>
- <name>append_vacm_config(Dir, Conf) -> ok</name>
+ <name since="">append_vacm_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent vacm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -710,7 +710,7 @@ word() = 0..65535
</func>
<func>
- <name>read_vacm_config(Dir) -> Conf</name>
+ <name since="">read_vacm_config(Dir) -> Conf</name>
<fsummary>Read the agent vacm config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -729,8 +729,8 @@ word() = 0..65535
</func>
<func>
- <name>usm_entry(EngineId) -> usm_entry()</name>
- <name>usm_entry(EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> usm_entry()</name>
+ <name since="">usm_entry(EngineId) -> usm_entry()</name>
+ <name since="">usm_entry(EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> usm_entry()</name>
<fsummary>Create an usm entry</fsummary>
<type>
<v>EngineId = string()</v>
@@ -762,8 +762,8 @@ word() = 0..65535
</func>
<func>
- <name>write_usm_config(Dir, Conf) -> ok</name>
- <name>write_usm_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -786,7 +786,7 @@ word() = 0..65535
</func>
<func>
- <name>append_usm_config(Dir, Conf) -> ok</name>
+ <name since="">append_usm_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -805,7 +805,7 @@ word() = 0..65535
</func>
<func>
- <name>read_usm_config(Dir) -> Conf</name>
+ <name since="">read_usm_config(Dir) -> Conf</name>
<fsummary>Read the agent usm config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -824,7 +824,7 @@ word() = 0..65535
</func>
<func>
- <name>notify_entry(Name, Tag, Type) -> notify_entry()</name>
+ <name since="">notify_entry(Name, Tag, Type) -> notify_entry()</name>
<fsummary>Create an notify entry</fsummary>
<type>
<v>Name = string()</v>
@@ -845,8 +845,8 @@ word() = 0..65535
</func>
<func>
- <name>write_notify_config(Dir, Conf) -> ok</name>
- <name>write_notify_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_notify_config(Dir, Conf) -> ok</name>
+ <name since="">write_notify_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the agent notify config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -869,7 +869,7 @@ word() = 0..65535
</func>
<func>
- <name>append_notify_config(Dir, Conf) -> ok</name>
+ <name since="">append_notify_config(Dir, Conf) -> ok</name>
<fsummary>Append the agent notify config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -889,7 +889,7 @@ word() = 0..65535
</func>
<func>
- <name>read_notify_config(Dir) -> Conf</name>
+ <name since="">read_notify_config(Dir) -> Conf</name>
<fsummary>Read the agent notify config from the config file</fsummary>
<type>
<v>Dir = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_discovery_handler.xml b/lib/snmp/doc/src/snmpa_discovery_handler.xml
index 0ea72a880c..21b8746c11 100644
--- a/lib/snmp/doc/src/snmpa_discovery_handler.xml
+++ b/lib/snmp/doc/src/snmpa_discovery_handler.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_discovery_handler.xml</file>
</header>
- <module>snmpa_discovery_handler</module>
+ <module since="">snmpa_discovery_handler</module>
<modulesummary>Behaviour module for the SNMP agent discovery handler.</modulesummary>
<description>
<p>This module defines the behaviour of the agent discovery
@@ -51,7 +51,7 @@
<funcs>
<func>
- <name>stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> ignore | {ok, usm_entry() | [usm_entry()]} | {ok, usm_entry() | [usm_entry()], NewExtraInfo}</name>
+ <name since="">stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> ignore | {ok, usm_entry() | [usm_entry()]} | {ok, usm_entry() | [usm_entry()], NewExtraInfo}</name>
<fsummary>Discovery stage 1 finish</fsummary>
<type>
<v>TargetName = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error.xml b/lib/snmp/doc/src/snmpa_error.xml
index 7cc4a3513d..6e6761b7a5 100644
--- a/lib/snmp/doc/src/snmpa_error.xml
+++ b/lib/snmp/doc/src/snmpa_error.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error.xml</file>
</header>
- <module>snmpa_error</module>
+ <module since="">snmpa_error</module>
<modulesummary>Functions for Reporting SNMP Errors</modulesummary>
<description>
<marker id="desc"></marker>
@@ -57,7 +57,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -76,7 +76,7 @@
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error_io.xml b/lib/snmp/doc/src/snmpa_error_io.xml
index bcb2688646..d78e09ff13 100644
--- a/lib/snmp/doc/src/snmpa_error_io.xml
+++ b/lib/snmp/doc/src/snmpa_error_io.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error_io.xml</file>
</header>
- <module>snmpa_error_io</module>
+ <module since="">snmpa_error_io</module>
<modulesummary>Functions for Reporting SNMP Errors on stdio</modulesummary>
<description>
<p>The module <c>snmpa_error_io</c> implements the
@@ -52,7 +52,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -68,7 +68,7 @@
</desc>
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error_logger.xml b/lib/snmp/doc/src/snmpa_error_logger.xml
index 4feb2e7f32..b0565a6839 100644
--- a/lib/snmp/doc/src/snmpa_error_logger.xml
+++ b/lib/snmp/doc/src/snmpa_error_logger.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error_logger.xml</file>
</header>
- <module>snmpa_error_logger</module>
+ <module since="">snmpa_error_logger</module>
<modulesummary>Functions for Reporting SNMP Errors through the error_logger</modulesummary>
<description>
<p>The module <c>snmpa_error_logger</c> implements the
@@ -54,7 +54,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -70,7 +70,7 @@
</desc>
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_error_report.xml b/lib/snmp/doc/src/snmpa_error_report.xml
index 282d9b4e59..f08dc1df23 100644
--- a/lib/snmp/doc/src/snmpa_error_report.xml
+++ b/lib/snmp/doc/src/snmpa_error_report.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_error_report.xml</file>
</header>
- <module>snmpa_error_report</module>
+ <module since="">snmpa_error_report</module>
<modulesummary>Behaviour module for reporting SNMP agent errors</modulesummary>
<description>
<marker id="desc"></marker>
@@ -52,7 +52,7 @@
</description>
<funcs>
<func>
- <name>config_err(Format, Args) -> void()</name>
+ <name since="">config_err(Format, Args) -> void()</name>
<fsummary>Called if a configuration error occurs</fsummary>
<type>
<v>Format = string()</v>
@@ -68,7 +68,7 @@
</desc>
</func>
<func>
- <name>user_err(Format, Args) -> void()</name>
+ <name since="">user_err(Format, Args) -> void()</name>
<fsummary>Called if a user related error occurs</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/snmp/doc/src/snmpa_local_db.xml b/lib/snmp/doc/src/snmpa_local_db.xml
index ac8d466ab3..229f22ab70 100644
--- a/lib/snmp/doc/src/snmpa_local_db.xml
+++ b/lib/snmp/doc/src/snmpa_local_db.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_local_db.xml</file>
</header>
- <module>snmpa_local_db</module>
+ <module since="">snmpa_local_db</module>
<modulesummary>The SNMP built-in database</modulesummary>
<description>
<p>The module <c>snmpa_local_db</c> contains functions for
@@ -86,7 +86,7 @@
</section>
<funcs>
<func>
- <name>dump() -> ok | {error, Reason}</name>
+ <name since="">dump() -> ok | {error, Reason}</name>
<fsummary>Dump the database to disk</fsummary>
<type>
<v>Reason = term()</v>
@@ -97,7 +97,7 @@
</desc>
</func>
<func>
- <name>match(NameDb, Pattern)</name>
+ <name since="">match(NameDb, Pattern)</name>
<fsummary>Perform a match on the table</fsummary>
<desc>
<p>Performs an ets/dets matching on the table.
@@ -106,9 +106,9 @@
</desc>
</func>
<func>
- <name>print()</name>
- <name>print(TableName)</name>
- <name>print(TableName, Db)</name>
+ <name since="">print()</name>
+ <name since="">print(TableName)</name>
+ <name since="">print(TableName, Db)</name>
<fsummary>Print the database to screen</fsummary>
<type>
<v>TableName = atom()</v>
@@ -124,7 +124,7 @@
</desc>
</func>
<func>
- <name>table_create(NameDb) -> bool()</name>
+ <name since="">table_create(NameDb) -> bool()</name>
<fsummary>Create a table</fsummary>
<desc>
<p>Creates a table. If the table already exist, the old copy
@@ -135,7 +135,7 @@
</desc>
</func>
<func>
- <name>table_create_row(NameDb, RowIndex, Row) -> bool()</name>
+ <name since="">table_create_row(NameDb, RowIndex, Row) -> bool()</name>
<fsummary>Create a row in a table</fsummary>
<type>
<v>Row = {Val1, Val2, ..., ValN}</v>
@@ -147,28 +147,28 @@
</desc>
</func>
<func>
- <name>table_delete(NameDb) -> void()</name>
+ <name since="">table_delete(NameDb) -> void()</name>
<fsummary>Delete a table</fsummary>
<desc>
<p>Deletes a table.</p>
</desc>
</func>
<func>
- <name>table_delete_row(NameDb, RowIndex) -> bool()</name>
+ <name since="">table_delete_row(NameDb, RowIndex) -> bool()</name>
<fsummary>Delete the row in the table</fsummary>
<desc>
<p>Deletes the row in the table.</p>
</desc>
</func>
<func>
- <name>table_exists(NameDb) -> bool()</name>
+ <name since="">table_exists(NameDb) -> bool()</name>
<fsummary>Check if a table exists</fsummary>
<desc>
<p>Checks if a table exists.</p>
</desc>
</func>
<func>
- <name>table_get_row(NameDb, RowIndex) -> Row | undefined</name>
+ <name since="">table_get_row(NameDb, RowIndex) -> Row | undefined</name>
<fsummary>Get a row from the table</fsummary>
<type>
<v>Row = {Val1, Val2, ..., ValN}</v>
diff --git a/lib/snmp/doc/src/snmpa_mib_data.xml b/lib/snmp/doc/src/snmpa_mib_data.xml
index 1a73c9b634..b849a2826a 100644
--- a/lib/snmp/doc/src/snmpa_mib_data.xml
+++ b/lib/snmp/doc/src/snmpa_mib_data.xml
@@ -30,7 +30,7 @@
<file>snmpa_mib_data.xml</file>
</header>
- <module>snmpa_mib_data</module>
+ <module since="OTP R16B01">snmpa_mib_data</module>
<modulesummary>Behaviour module for the SNMP agent mib-server
data module.</modulesummary>
<description>
@@ -108,7 +108,7 @@
<funcs>
<func>
- <name>Module:new(Storage) -> State</name>
+ <name since="OTP R16B01">Module:new(Storage) -> State</name>
<fsummary>Create new (mib-server) data instance</fsummary>
<type>
<v>Storage = mib_storage()</v>
@@ -122,7 +122,7 @@
</func>
<func>
- <name>Module:close(State) -> void()</name>
+ <name since="OTP R16B01">Module:close(State) -> void()</name>
<fsummary>Close the mib-server data instance</fsummary>
<type>
<v>State = term()</v>
@@ -135,7 +135,7 @@
</func>
<func>
- <name>Module:sync(State) -> void()</name>
+ <name since="OTP R16B01">Module:sync(State) -> void()</name>
<fsummary>Synchronize to disc</fsummary>
<type>
<v>State = term()</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name>Module:load_mib(State, Filename, MeOverride, TeOverride) -> {ok, NewState} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:load_mib(State, Filename, MeOverride, TeOverride) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Load a mib into the mib-server</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -172,7 +172,7 @@
</func>
<func>
- <name>Module:unload_mib(State, Filename) -> {ok, NewState} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:unload_mib(State, Filename) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Unload mib from the mib-server</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -188,7 +188,7 @@
</func>
<func>
- <name>Module:lookup(State, Oid) -> Reply</name>
+ <name since="OTP R16B01">Module:lookup(State, Oid) -> Reply</name>
<fsummary>Find the mib-entry corresponding to the Oid</fsummary>
<type>
<v>State = term()</v>
@@ -210,7 +210,7 @@
</func>
<func>
- <name>Module:next(State, Oid, MibView) -> Reply</name>
+ <name since="OTP R16B01">Module:next(State, Oid, MibView) -> Reply</name>
<fsummary>Finds the lexicographically next oid</fsummary>
<type>
<v>State = term()</v>
@@ -227,7 +227,7 @@
</func>
<func>
- <name>Module:register_subagent(State, Oid, Pid) -> Reply</name>
+ <name since="OTP R16B01">Module:register_subagent(State, Oid, Pid) -> Reply</name>
<fsummary>Register the subagent</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -245,7 +245,7 @@
</func>
<func>
- <name>Module:unregister_subagent(State, PidOrOid) -> Reply</name>
+ <name since="OTP R16B01">Module:unregister_subagent(State, PidOrOid) -> Reply</name>
<fsummary>Unregister the subagent</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -266,7 +266,7 @@
</func>
<func>
- <name>Module:dump(State, Destination) -> Reply</name>
+ <name since="OTP R16B01">Module:dump(State, Destination) -> Reply</name>
<fsummary>Unregister the subagent</fsummary>
<type>
<v>State = term()</v>
@@ -284,7 +284,7 @@
</func>
<func>
- <name>Module:which_mib(State, Oid) -> Reply</name>
+ <name since="OTP R16B01">Module:which_mib(State, Oid) -> Reply</name>
<fsummary>Retrieve the mib file for an oid()</fsummary>
<type>
<v>State = term()</v>
@@ -301,7 +301,7 @@
</func>
<func>
- <name>Module:which_mibs(State) -> Reply</name>
+ <name since="OTP R16B01">Module:which_mibs(State) -> Reply</name>
<fsummary>Retrieve all loaded mib files</fsummary>
<type>
<v>State = term()</v>
@@ -317,7 +317,7 @@
</func>
<func>
- <name>Module:whereis_mib(State, MibName) -> Reply</name>
+ <name since="OTP R16B01">Module:whereis_mib(State, MibName) -> Reply</name>
<fsummary>Retrieve the mib file for the mib</fsummary>
<type>
<v>State = term()</v>
@@ -334,7 +334,7 @@
</func>
<func>
- <name>Module:info(State) -> Reply</name>
+ <name since="OTP R16B01">Module:info(State) -> Reply</name>
<fsummary>Retrieve misc info for the mib data</fsummary>
<type>
<v>State = term()</v>
@@ -352,7 +352,7 @@
</func>
<func>
- <name>Module:backup(State, BackupDir) -> Reply</name>
+ <name since="OTP R16B01">Module:backup(State, BackupDir) -> Reply</name>
<fsummary>Perform a backup of the mib-server data</fsummary>
<type>
<v>State = term()</v>
@@ -370,7 +370,7 @@
</func>
<func>
- <name>Module:code_change(Destination, Vsn, Extra, State) -> NewState</name>
+ <name since="OTP R16B01">Module:code_change(Destination, Vsn, Extra, State) -> NewState</name>
<fsummary>Perform a code-change</fsummary>
<type>
<v>Destination = up | down</v>
diff --git a/lib/snmp/doc/src/snmpa_mib_storage.xml b/lib/snmp/doc/src/snmpa_mib_storage.xml
index 58ce2167ec..ee2b009e77 100644
--- a/lib/snmp/doc/src/snmpa_mib_storage.xml
+++ b/lib/snmp/doc/src/snmpa_mib_storage.xml
@@ -30,7 +30,7 @@
<file>snmpa_mib_storage.xml</file>
</header>
- <module>snmpa_mib_storage</module>
+ <module since="OTP R16B01">snmpa_mib_storage</module>
<modulesummary>
Behaviour module for the SNMP agent mib storage.
</modulesummary>
@@ -96,7 +96,7 @@
<funcs>
<func>
- <name>Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name>
<fsummary>Create new (mib-server) data instance</fsummary>
<type>
<v>Name = atom()</v>
@@ -122,7 +122,7 @@
</func>
<func>
- <name>Module:close(TabId) -> void()</name>
+ <name since="OTP R16B01">Module:close(TabId) -> void()</name>
<fsummary>Close the mib-storage table</fsummary>
<type>
<v>State = term()</v>
@@ -135,7 +135,7 @@
</func>
<func>
- <name>Module:read(TabId, Key) -> false | {value, Record}</name>
+ <name since="OTP R16B01">Module:read(TabId, Key) -> false | {value, Record}</name>
<fsummary>Read a record from the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -150,7 +150,7 @@
</func>
<func>
- <name>Module:write(TabId, Record) -> ok | {error, Reason}</name>
+ <name since="OTP R16B01">Module:write(TabId, Record) -> ok | {error, Reason}</name>
<fsummary>Write a record to the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -165,7 +165,7 @@
</func>
<func>
- <name>Module:delete(TabId) -> void()</name>
+ <name since="OTP R16B01">Module:delete(TabId) -> void()</name>
<fsummary>Delete an entire mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -178,7 +178,7 @@
</func>
<func>
- <name>Module:delete(TabId, Key) -> ok | {error, Reason}</name>
+ <name since="OTP R16B01">Module:delete(TabId, Key) -> ok | {error, Reason}</name>
<fsummary>Delete a record from the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -193,7 +193,7 @@
</func>
<func>
- <name>Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
<fsummary>Search the mib-storage table for record matching pattern</fsummary>
<type>
<v>TabId = term()</v>
@@ -210,7 +210,7 @@
</func>
<func>
- <name>Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name>
<fsummary>Delete records in the mib-storage table matching pattern</fsummary>
<type>
<v>TabId = term()</v>
@@ -228,7 +228,7 @@
</func>
<func>
- <name>Module:tab2list(TabId) -> Recs</name>
+ <name since="OTP R16B01">Module:tab2list(TabId) -> Recs</name>
<fsummary>Return all records of the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>Module:info(TabId) -> {ok, Info} | {error, Reason}</name>
+ <name since="OTP R16B01">Module:info(TabId) -> {ok, Info} | {error, Reason}</name>
<fsummary>Returns information about the mib-storage table. </fsummary>
<type>
<v>TabId = term()</v>
@@ -259,7 +259,7 @@
</func>
<func>
- <name>Module:sync(TabId) -> void()</name>
+ <name since="OTP R16B01">Module:sync(TabId) -> void()</name>
<fsummary>Synchronize mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
@@ -273,7 +273,7 @@
</func>
<func>
- <name>Module:backup(TabId, BackupDir) -> ok | {error, Reason}</name>
+ <name since="OTP R16B01">Module:backup(TabId, BackupDir) -> ok | {error, Reason}</name>
<fsummary>Perform a backup of the mib-storage table</fsummary>
<type>
<v>TabId = term()</v>
diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml
index a39c087c20..d76a9c4a94 100644
--- a/lib/snmp/doc/src/snmpa_mpd.xml
+++ b/lib/snmp/doc/src/snmpa_mpd.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_mpd.xml</file>
</header>
- <module>snmpa_mpd</module>
+ <module since="">snmpa_mpd</module>
<modulesummary>Message Processing and Dispatch module for the SNMP agent</modulesummary>
<description>
<p>The module <c>snmpa_mpd</c> implements the version independent
@@ -52,7 +52,7 @@
<funcs>
<func>
- <name>init(Vsns) -> mpd_state()</name>
+ <name since="">init(Vsns) -> mpd_state()</name>
<fsummary>Initialize the MPD module</fsummary>
<type>
<v>Vsns = [Vsn]</v>
@@ -70,8 +70,8 @@
</func>
<func>
- <name>process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
- <name>process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
+ <name since="OTP 17.3">process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
+ <name since="OTP R14B">process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
<fsummary>Process a packet received from the network</fsummary>
<type>
<v>Packet = binary()</v>
@@ -108,8 +108,8 @@
</func>
<func>
- <name>generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name>
- <name>generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a response packet to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
@@ -136,8 +136,8 @@
</func>
<func>
- <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
- <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
+ <name since="OTP R14B">generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
<fsummary>Generate a request message to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
@@ -185,7 +185,7 @@
</func>
<func>
- <name>process_taddrs(TDests) -> Dests</name>
+ <name since="OTP 17.3">process_taddrs(TDests) -> Dests</name>
<fsummary>Transform addresses from internal MIB format to a less internal
</fsummary>
<type>
@@ -211,7 +211,7 @@
</func>
<func>
- <name>discarded_pdu(Variable) -> void()</name>
+ <name since="">discarded_pdu(Variable) -> void()</name>
<fsummary>Increment the variable associated with a discarded pdu</fsummary>
<type>
<v>Variable = atom()</v>
diff --git a/lib/snmp/doc/src/snmpa_network_interface.xml b/lib/snmp/doc/src/snmpa_network_interface.xml
index d4d4989e90..3e79df11b1 100644
--- a/lib/snmp/doc/src/snmpa_network_interface.xml
+++ b/lib/snmp/doc/src/snmpa_network_interface.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_network_interface.xml</file>
</header>
- <module>snmpa_network_interface</module>
+ <module since="">snmpa_network_interface</module>
<modulesummary>Behaviour module for the SNMP agent network interface.</modulesummary>
<description>
<p>This module defines the behaviour of the agent network
@@ -68,7 +68,7 @@
<funcs>
<func>
- <name>start_link(Prio, NoteStore, MasterAgent, Opts) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start_link(Prio, NoteStore, MasterAgent, Opts) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Start-link the network interface process</fsummary>
<type>
<v>Prio = priority()</v>
@@ -93,7 +93,7 @@
</func>
<func>
- <name>info(Pid) -> [{Key, Value}]</name>
+ <name since="">info(Pid) -> [{Key, Value}]</name>
<fsummary>Return information about the running network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -112,7 +112,7 @@
</func>
<func>
- <name>verbosity(Pid, Verbosity) -> void()</name>
+ <name since="">verbosity(Pid, Verbosity) -> void()</name>
<fsummary>Change the verbosity of a running network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -126,7 +126,7 @@
</func>
<func>
- <name>get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
+ <name since="">get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
<fsummary>Get the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
@@ -147,7 +147,7 @@
</func>
<func>
- <name>set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/snmp/doc/src/snmpa_network_interface_filter.xml b/lib/snmp/doc/src/snmpa_network_interface_filter.xml
index 7cd08f8935..02c7d291dd 100644
--- a/lib/snmp/doc/src/snmpa_network_interface_filter.xml
+++ b/lib/snmp/doc/src/snmpa_network_interface_filter.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_network_interface_filter.xml</file>
</header>
- <module>snmpa_network_interface_filter</module>
+ <module since="">snmpa_network_interface_filter</module>
<modulesummary>Behaviour module for the SNMP agent network-interface filter.</modulesummary>
<description>
<p>This module defines the behaviour of the agent network interface
@@ -101,7 +101,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</section>
<funcs>
<func>
- <name>accept_recv(Domain, Addr) -> boolean()</name>
+ <name since="">accept_recv(Domain, Addr) -> boolean()</name>
<fsummary>Shall the received message be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -116,7 +116,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</desc>
</func>
<func>
- <name>accept_send(Domain, Addr) -> boolean()</name>
+ <name since="">accept_send(Domain, Addr) -> boolean()</name>
<fsummary>Shall the message be sent</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -131,7 +131,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</desc>
</func>
<func>
- <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
+ <name since="">accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
<fsummary>Shall the received pdu be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -148,7 +148,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</desc>
</func>
<func>
- <name>accept_send_pdu(Targets, PduType) -> Reply</name>
+ <name since="">accept_send_pdu(Targets, PduType) -> Reply</name>
<fsummary>Shall the pdu be sent</fsummary>
<type>
<v>Targets = targets()</v>
diff --git a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
index cbae158544..5dad372710 100644
--- a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
+++ b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml
@@ -34,7 +34,7 @@
<rev></rev>
<file>snmpa_notification_delivery_info_receiver.xml</file>
</header>
- <module>snmpa_notification_delivery_info_receiver</module>
+ <module since="">snmpa_notification_delivery_info_receiver</module>
<modulesummary>
Behaviour module for the SNMP agent notification delivery
information receiver.
@@ -76,7 +76,7 @@
<funcs>
<func>
- <name>delivery_targets(Tag, Targets, Extra) -> void()</name>
+ <name since="">delivery_targets(Tag, Targets, Extra) -> void()</name>
<fsummary>Inform about target addresses</fsummary>
<type>
<v>Tag = term()</v>
@@ -97,7 +97,7 @@
</func>
<func>
- <name>delivery_info(Tag, Target, DeliveryResult, Extra) -> void()</name>
+ <name since="">delivery_info(Tag, Target, DeliveryResult, Extra) -> void()</name>
<fsummary>Inform about delivery result</fsummary>
<type>
<v>Tag = term()</v>
diff --git a/lib/snmp/doc/src/snmpa_notification_filter.xml b/lib/snmp/doc/src/snmpa_notification_filter.xml
index 0f16ba4440..902412ccc5 100644
--- a/lib/snmp/doc/src/snmpa_notification_filter.xml
+++ b/lib/snmp/doc/src/snmpa_notification_filter.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_notification_filter.xml</file>
</header>
- <module>snmpa_notification_filter</module>
+ <module since="">snmpa_notification_filter</module>
<modulesummary>Behaviour module for the SNMP agent notification filters.</modulesummary>
<description>
<p>This module defines the behaviour of the agent notification
@@ -51,7 +51,7 @@
</description>
<funcs>
<func>
- <name>handle_notification(Notif, Data) -> Reply</name>
+ <name since="">handle_notification(Notif, Data) -> Reply</name>
<fsummary>Handle a notification</fsummary>
<type>
<v>Reply = send | {send, NewNotif} | dont_send</v>
diff --git a/lib/snmp/doc/src/snmpa_supervisor.xml b/lib/snmp/doc/src/snmpa_supervisor.xml
index 86c6fbc350..e11cde390f 100644
--- a/lib/snmp/doc/src/snmpa_supervisor.xml
+++ b/lib/snmp/doc/src/snmpa_supervisor.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpa_supervisor.xml</file>
</header>
- <module>snmpa_supervisor</module>
+ <module since="">snmpa_supervisor</module>
<modulesummary>A supervisor for the SNMP agent Processes</modulesummary>
<description>
<p>This is the top supervisor for the agent part of the SNMP
@@ -42,7 +42,7 @@
</description>
<funcs>
<func>
- <name>start_sub_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
+ <name since="">start_sub_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
<fsummary>Start the SNMP supervisor for sub-agents only</fsummary>
<type>
<v>Opts = [opt()]</v>
@@ -60,7 +60,7 @@
</desc>
</func>
<func>
- <name>start_master_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
+ <name since="">start_master_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name>
<fsummary>Start the SNMP supervisor for all agents</fsummary>
<type>
<v>Opts = [opt()]</v>
@@ -82,7 +82,7 @@
</desc>
</func>
<func>
- <name>start_sub_agent(ParentAgent,Subtree,Mibs) -> {ok, pid()} | {error, Reason}</name>
+ <name since="">start_sub_agent(ParentAgent,Subtree,Mibs) -> {ok, pid()} | {error, Reason}</name>
<fsummary>Start a sub-agent</fsummary>
<type>
<v>ParentAgent = pid()</v>
@@ -99,7 +99,7 @@
</desc>
</func>
<func>
- <name>stop_sub_agent(SubAgent) -> ok | no_such_child</name>
+ <name since="">stop_sub_agent(SubAgent) -> ok | no_such_child</name>
<fsummary>Stop a sub-agent</fsummary>
<type>
<v>SubAgent = pid()</v>
diff --git a/lib/snmp/doc/src/snmpc.xml b/lib/snmp/doc/src/snmpc.xml
index aba51bb500..b22b32a133 100644
--- a/lib/snmp/doc/src/snmpc.xml
+++ b/lib/snmp/doc/src/snmpc.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpc.xml</file>
</header>
- <module>snmpc</module>
+ <module since="">snmpc</module>
<modulesummary>Interface Functions to the SNMP toolkit MIB compiler</modulesummary>
<description>
<p>The module <c>snmpc</c> contains interface functions to the
@@ -43,8 +43,8 @@
<funcs>
<func>
- <name>compile(File)</name>
- <name>compile(File, Options) -> {ok, BinFileName} | {error, Reason}</name>
+ <name since="">compile(File)</name>
+ <name since="">compile(File, Options) -> {ok, BinFileName} | {error, Reason}</name>
<fsummary>Compile the specified MIB</fsummary>
<type>
<v>File = string()</v>
@@ -236,7 +236,7 @@
</func>
<func>
- <name>is_consistent(Mibs) -> ok | {error, Reason}</name>
+ <name since="">is_consistent(Mibs) -> ok | {error, Reason}</name>
<fsummary>Check for OID conflicts between MIBs</fsummary>
<type>
<v>Mibs = [MibName]</v>
@@ -252,7 +252,7 @@
</func>
<func>
- <name>mib_to_hrl(MibName) -> ok | {error, Reason}</name>
+ <name since="">mib_to_hrl(MibName) -> ok | {error, Reason}</name>
<fsummary>Generate constants for the objects in the MIB</fsummary>
<type>
<v>MibName = string()</v>
diff --git a/lib/snmp/doc/src/snmpm.xml b/lib/snmp/doc/src/snmpm.xml
index be4cd58a1a..c45df98ee0 100644
--- a/lib/snmp/doc/src/snmpm.xml
+++ b/lib/snmp/doc/src/snmpm.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm.xml</file>
</header>
- <module>snmpm</module>
+ <module since="">snmpm</module>
<modulesummary>Interface functions to the SNMP toolkit manager</modulesummary>
<description>
<p>The module <c>snmpm</c> contains interface functions to the
@@ -77,7 +77,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</section>
<funcs>
<func>
- <name>monitor() -> Ref</name>
+ <name since="">monitor() -> Ref</name>
<fsummary>Monitor the snmp manager</fsummary>
<type>
<v>Ref = reference()</v>
@@ -92,7 +92,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>demonitor(Ref) -> void()</name>
+ <name since="">demonitor(Ref) -> void()</name>
<fsummary>Turn off monitoring of the snmp manager</fsummary>
<type>
<v>Ref = reference()</v>
@@ -105,7 +105,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>notify_started(Timeout) -> Pid</name>
+ <name since="">notify_started(Timeout) -> Pid</name>
<fsummary>Request to be notified when manager started</fsummary>
<type>
<v>Timeout = integer()</v>
@@ -148,7 +148,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>cancel_notify_started(Pid) -> void()</name>
+ <name since="">cancel_notify_started(Pid) -> void()</name>
<fsummary>Cancel request to be notified when manager started</fsummary>
<type>
<v>Pid = pid()</v>
@@ -161,8 +161,8 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_user(Id, Module, Data) -> ok | {error, Reason}</name>
- <name>register_user(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
+ <name since="">register_user(Id, Module, Data) -> ok | {error, Reason}</name>
+ <name since="">register_user(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
<fsummary>Register a user of the manager</fsummary>
<type>
<v>Id = term()</v>
@@ -204,8 +204,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_user_monitor(Id, Module, Data) -> ok | {error, Reason}</name>
- <name>register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
+ <name since="">register_user_monitor(Id, Module, Data) -> ok | {error, Reason}</name>
+ <name since="">register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name>
<fsummary>Register a monitored user of the manager</fsummary>
<type>
<v>Id = term()</v>
@@ -252,7 +252,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>unregister_user(Id) -> ok | {error, Reason}</name>
+ <name since="">unregister_user(Id) -> ok | {error, Reason}</name>
<fsummary>Unregister the user</fsummary>
<type>
<v>Id = term()</v>
@@ -265,7 +265,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>which_users() -> Users</name>
+ <name since="">which_users() -> Users</name>
<fsummary>Get a list of all users</fsummary>
<type>
<v>Users = [UserId]</v>
@@ -279,7 +279,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_agent(UserId, TargetName, Config) -> ok | {error, Reason}</name>
+ <name since="">register_agent(UserId, TargetName, Config) -> ok | {error, Reason}</name>
<fsummary>Register this agent</fsummary>
<type>
<v>UserId = term()</v>
@@ -325,7 +325,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>unregister_agent(UserId, TargetName) -> ok | {error, Reason}</name>
+ <name since="">unregister_agent(UserId, TargetName) -> ok | {error, Reason}</name>
<fsummary>Unregister the user</fsummary>
<type>
<v>UserId = term()</v>
@@ -339,7 +339,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>agent_info(TargetName, Item) -> {ok, Val} | {error, Reason}</name>
+ <name since="">agent_info(TargetName, Item) -> {ok, Val} | {error, Reason}</name>
<fsummary>Retrieve agent config</fsummary>
<type>
<v>TargetName = target_name()</v>
@@ -354,8 +354,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name>
- <name>update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name>
+ <name since="OTP R14B04">update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name>
+ <name since="">update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name>
<fsummary>Update agent config</fsummary>
<type>
<v>UserId = term()</v>
@@ -379,8 +379,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>which_agents() -> Agents</name>
- <name>which_agents(UserId) -> Agents</name>
+ <name since="">which_agents() -> Agents</name>
+ <name since="">which_agents(UserId) -> Agents</name>
<fsummary>List the registered agents</fsummary>
<type>
<v>UserId = term()</v>
@@ -396,7 +396,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv
</func>
<func>
- <name>register_usm_user(EngineID, UserName, Conf) -> ok | {error, Reason}</name>
+ <name since="">register_usm_user(EngineID, UserName, Conf) -> ok | {error, Reason}</name>
<fsummary>Register this USM user</fsummary>
<type>
<v>EngineID = string()</v>
@@ -427,7 +427,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>unregister_usm_user(EngineID, UserName) -> ok | {error, Reason}</name>
+ <name since="">unregister_usm_user(EngineID, UserName) -> ok | {error, Reason}</name>
<fsummary>Unregister this USM user</fsummary>
<type>
<v>EngineID = string()</v>
@@ -442,7 +442,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>usm_user_info(EngineID, UserName, Item) -> {ok, Val} | {error, Reason}</name>
+ <name since="">usm_user_info(EngineID, UserName, Item) -> {ok, Val} | {error, Reason}</name>
<fsummary>Retrieve usm user config</fsummary>
<type>
<v>EngineID = string()</v>
@@ -458,7 +458,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>update_usm_user_info(EngineID, UserName, Item, Val) -> ok | {error, Reason}</name>
+ <name since="">update_usm_user_info(EngineID, UserName, Item, Val) -> ok | {error, Reason}</name>
<fsummary>Update agent config</fsummary>
<type>
<v>EngineID = string()</v>
@@ -475,7 +475,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>which_usm_users() -> UsmUsers</name>
+ <name since="">which_usm_users() -> UsmUsers</name>
<fsummary>List all the registered usm users</fsummary>
<type>
<v>UsmUsers = [{EngineID,UserName}]</v>
@@ -490,7 +490,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>which_usm_users(EngineID) -> UsmUsers</name>
+ <name since="">which_usm_users(EngineID) -> UsmUsers</name>
<fsummary>List the registered usm users</fsummary>
<type>
<v>UsmUsers = [UserName]</v>
@@ -505,8 +505,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -559,11 +559,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -605,8 +605,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -647,11 +647,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -683,8 +683,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -737,11 +737,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_next(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_next(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -775,8 +775,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -814,11 +814,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_next(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_next(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-next-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -849,8 +849,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -906,11 +906,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -946,8 +946,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -990,11 +990,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, ContextName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_set(UserId, TargetName, ContextName, VarsAndVals, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>set-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1026,8 +1026,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1082,11 +1082,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
- <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
+ <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name>
<fsummary>Synchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1121,8 +1121,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1162,11 +1162,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
- <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name>
+ <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name>
<fsummary>Asynchronous <c>get-bulk-request</c></fsummary>
<type>
<v>UserId = term()</v>
@@ -1199,7 +1199,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name>
+ <name since="">cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name>
<fsummary>Cancel a asynchronous request</fsummary>
<type>
<v>UserId = term()</v>
@@ -1214,15 +1214,15 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>log_to_txt(LogDir)</name>
- <name>log_to_txt(LogDir, Block | Mibs)</name>
- <name>log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir)</name>
+ <name since="">log_to_txt(LogDir, Block | Mibs)</name>
+ <name since="">log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -1257,15 +1257,15 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs) -> ok | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
- <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs) -> ok | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
+ <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name>
<fsummary>Convert an Audit Trail Log to text format</fsummary>
<type>
<v>LogDir = string()</v>
@@ -1299,7 +1299,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>change_log_size(NewSize) -> ok | {error, Reason}</name>
+ <name since="">change_log_size(NewSize) -> ok | {error, Reason}</name>
<fsummary>Change the size of the Audit Trail Log</fsummary>
<type>
<v>NewSize = {MaxBytes, MaxFiles}</v>
@@ -1321,7 +1321,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the Audit Trail Log type</fsummary>
<type>
<v>NewType = OldType = atl_type()</v>
@@ -1340,7 +1340,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>load_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="">load_mib(Mib) -> ok | {error, Reason}</name>
<fsummary>Load a MIB into the manager</fsummary>
<type>
<v>Mib = MibName</v>
@@ -1361,7 +1361,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>unload_mib(Mib) -> ok | {error, Reason}</name>
+ <name since="">unload_mib(Mib) -> ok | {error, Reason}</name>
<fsummary>Unload a MIB from the manager</fsummary>
<type>
<v>Mib = MibName</v>
@@ -1382,7 +1382,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>which_mibs() -> Mibs</name>
+ <name since="">which_mibs() -> Mibs</name>
<fsummary>Which mibs are loaded into the manager</fsummary>
<type>
<v>Mibs = [{MibName, MibFile}]</v>
@@ -1397,7 +1397,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>name_to_oid(Name) -> {ok, Oids} | {error, Reason}</name>
+ <name since="">name_to_oid(Name) -> {ok, Oids} | {error, Reason}</name>
<fsummary>Get all the possible oid's for an alias-name</fsummary>
<type>
<v>Name = atom()</v>
@@ -1414,7 +1414,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>oid_to_name(Oid) -> {ok, Name} | {error, Reason}</name>
+ <name since="">oid_to_name(Oid) -> {ok, Name} | {error, Reason}</name>
<fsummary>Get the alias-name of the oid </fsummary>
<type>
<v>Oid = oid()</v>
@@ -1429,7 +1429,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>oid_to_type(Oid) -> {ok, Type} | {error, Reason}</name>
+ <name since="">oid_to_type(Oid) -> {ok, Type} | {error, Reason}</name>
<fsummary>Get the type of the the oid</fsummary>
<type>
<v>Oid = oid()</v>
@@ -1444,7 +1444,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>backup(BackupDir) -> ok | {error, Reason}</name>
+ <name since="">backup(BackupDir) -> ok | {error, Reason}</name>
<fsummary>Backup manager data</fsummary>
<type>
<v>BackupDir = string()</v>
@@ -1458,7 +1458,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>info() -> [{Key, Value}]</name>
+ <name since="">info() -> [{Key, Value}]</name>
<fsummary>Return information about the manager</fsummary>
<type>
<v>Key = atom()</v>
@@ -1475,7 +1475,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>verbosity(Ref, Verbosity) -> void()</name>
+ <name since="">verbosity(Ref, Verbosity) -> void()</name>
<fsummary>Assign a new verbosity for the process</fsummary>
<type>
<v>Ref = server | config | net_if | note_store | all</v>
@@ -1492,8 +1492,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1
</func>
<func>
- <name>format_reason(Reason) -> string()</name>
- <name>format_reason(Prefix, Reason) -> string()</name>
+ <name since="">format_reason(Reason) -> string()</name>
+ <name since="">format_reason(Prefix, Reason) -> string()</name>
<fsummary>Assign a new verbosity for the process</fsummary>
<type>
<v>Reason = term()</v>
diff --git a/lib/snmp/doc/src/snmpm_conf.xml b/lib/snmp/doc/src/snmpm_conf.xml
index a3097e5f7e..17ecf2df7c 100644
--- a/lib/snmp/doc/src/snmpm_conf.xml
+++ b/lib/snmp/doc/src/snmpm_conf.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_conf.xml</file>
</header>
- <module>snmpm_conf</module>
+ <module since="">snmpm_conf</module>
<modulesummary>Utility functions for handling the manager config files.</modulesummary>
<description>
<p>The module <c>snmpm_conf</c> contains various utility functions to
@@ -42,7 +42,7 @@
</description>
<funcs>
<func>
- <name>manager_entry(Tag, Val) -> manager_entry()</name>
+ <name since="">manager_entry(Tag, Val) -> manager_entry()</name>
<fsummary>Create an manager entry</fsummary>
<type>
<v>Tag = address | port | engine_id | max_message_size</v>
@@ -60,8 +60,8 @@
</desc>
</func>
<func>
- <name>write_manager_config(Dir, Conf) -> ok</name>
- <name>write_manager_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_manager_config(Dir, Conf) -> ok</name>
+ <name since="">write_manager_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -81,7 +81,7 @@
</desc>
</func>
<func>
- <name>append_manager_config(Dir, Conf) -> ok</name>
+ <name since="">append_manager_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -98,7 +98,7 @@
</desc>
</func>
<func>
- <name>read_manager_config(Dir) -> Conf</name>
+ <name since="">read_manager_config(Dir) -> Conf</name>
<fsummary>Read the manager config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -115,9 +115,9 @@
</desc>
</func>
<func>
- <name>users_entry(UserId) -> users_entry()</name>
- <name>users_entry(UserId, UserMod) -> users_entry()</name>
- <name>users_entry(UserId, UserMod, UserData) -> users_entry()</name>
+ <name since="">users_entry(UserId) -> users_entry()</name>
+ <name since="">users_entry(UserId, UserMod) -> users_entry()</name>
+ <name since="">users_entry(UserId, UserMod, UserData) -> users_entry()</name>
<fsummary>Create an users entry</fsummary>
<type>
<v>UserId = term()</v>
@@ -139,8 +139,8 @@
</desc>
</func>
<func>
- <name>write_users_config(Dir, Conf) -> ok</name>
- <name>write_users_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_users_config(Dir, Conf) -> ok</name>
+ <name since="">write_users_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager users config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -161,7 +161,7 @@
</desc>
</func>
<func>
- <name>append_users_config(Dir, Conf) -> ok</name>
+ <name since="">append_users_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager users config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -179,7 +179,7 @@
</desc>
</func>
<func>
- <name>read_users_config(Dir) -> Conf</name>
+ <name since="">read_users_config(Dir) -> Conf</name>
<fsummary>Read the manager users config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -196,7 +196,7 @@
</desc>
</func>
<func>
- <name>agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name>
+ <name since="">agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name>
<fsummary>Create an agents entry</fsummary>
<type>
<v>UserId = term()</v>
@@ -223,8 +223,8 @@
</desc>
</func>
<func>
- <name>write_agents_config(Dir, Conf) -> ok</name>
- <name>write_agents_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_agents_config(Dir, Conf) -> ok</name>
+ <name since="">write_agents_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager agents to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -245,7 +245,7 @@
</desc>
</func>
<func>
- <name>append_agents_config(Dir, Conf) -> ok</name>
+ <name since="">append_agents_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager agents to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -263,7 +263,7 @@
</desc>
</func>
<func>
- <name>read_agents_config(Dir) -> Conf</name>
+ <name since="">read_agents_config(Dir) -> Conf</name>
<fsummary>Read the manager agents config from the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -280,8 +280,8 @@
</desc>
</func>
<func>
- <name>usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
- <name>usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
+ <name since="">usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
+ <name since="">usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name>
<fsummary>Create an usm entry</fsummary>
<type>
<v>EngineID = string()</v>
@@ -303,8 +303,8 @@
</desc>
</func>
<func>
- <name>write_usm_config(Dir, Conf) -> ok</name>
- <name>write_usm_config(Dir, Hdr, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Conf) -> ok</name>
+ <name since="">write_usm_config(Dir, Hdr, Conf) -> ok</name>
<fsummary>Write the manager usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -325,7 +325,7 @@
</desc>
</func>
<func>
- <name>append_usm_config(Dir, Conf) -> ok</name>
+ <name since="">append_usm_config(Dir, Conf) -> ok</name>
<fsummary>Append the manager usm config to the config file</fsummary>
<type>
<v>Dir = string()</v>
@@ -343,7 +343,7 @@
</desc>
</func>
<func>
- <name>read_usm_config(Dir) -> Conf</name>
+ <name since="">read_usm_config(Dir) -> Conf</name>
<fsummary>Read the manager usm config from the config file</fsummary>
<type>
<v>Dir = string()</v>
diff --git a/lib/snmp/doc/src/snmpm_mpd.xml b/lib/snmp/doc/src/snmpm_mpd.xml
index 08276e4b30..b024f8d294 100644
--- a/lib/snmp/doc/src/snmpm_mpd.xml
+++ b/lib/snmp/doc/src/snmpm_mpd.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_mpd.xml</file>
</header>
- <module>snmpm_mpd</module>
+ <module since="">snmpm_mpd</module>
<modulesummary>Message Processing and Dispatch module for the SNMP manager</modulesummary>
<description>
<p>The module <c>snmpm_mpd</c> implements the version independent
@@ -48,7 +48,7 @@
<funcs>
<func>
- <name>init_mpd(Vsns) -> mpd_state()</name>
+ <name since="">init_mpd(Vsns) -> mpd_state()</name>
<fsummary>Initialize the MPD module</fsummary>
<type>
<v>Vsns = [Vsn]</v>
@@ -64,7 +64,7 @@
</desc>
</func>
<func>
- <name>process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name>
+ <name since="OTP 17.3">process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name>
<fsummary>Process a message received from the network</fsummary>
<type>
<v>Msg = binary()</v>
@@ -92,7 +92,7 @@
</desc>
</func>
<func>
- <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="">generate_msg(Vsn, NoteStore, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a request message to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
@@ -117,7 +117,7 @@
</desc>
</func>
<func>
- <name>generate_response_msg(Vsn, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
+ <name since="">generate_response_msg(Vsn, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a response packet to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
diff --git a/lib/snmp/doc/src/snmpm_network_interface.xml b/lib/snmp/doc/src/snmpm_network_interface.xml
index 73892aa2e8..e4a66ceef2 100644
--- a/lib/snmp/doc/src/snmpm_network_interface.xml
+++ b/lib/snmp/doc/src/snmpm_network_interface.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_network_interface.xml</file>
</header>
- <module>snmpm_network_interface</module>
+ <module since="">snmpm_network_interface</module>
<modulesummary>Behaviour module for the SNMP manager network interface.</modulesummary>
<description>
<p>This module defines the behaviour of the manager network
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name>start_link(Server, NoteStore) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start_link(Server, NoteStore) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Start-link the network interface process</fsummary>
<type>
<v>Server = pid()</v>
@@ -95,7 +95,7 @@
</func>
<func>
- <name>stop(Pid) -> void()</name>
+ <name since="">stop(Pid) -> void()</name>
<fsummary>Stop the network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -108,7 +108,7 @@
</func>
<func>
- <name>send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name>
+ <name since="">send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name>
<fsummary>Request the network interface process to send this pdu</fsummary>
<type>
<v>Pid = pid()</v>
@@ -142,7 +142,7 @@
</func>
<func>
- <name>inform_response(Pid, Ref, Addr, Port) -> void()</name>
+ <name since="">inform_response(Pid, Ref, Addr, Port) -> void()</name>
<fsummary>Send the inform-request ack</fsummary>
<type>
<v>Pid = pid()</v>
@@ -163,7 +163,7 @@
</func>
<func>
- <name>note_store(Pid, NoteStore) -> void()</name>
+ <name since="">note_store(Pid, NoteStore) -> void()</name>
<fsummary>Change the verbosity of the network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -179,7 +179,7 @@
</func>
<func>
- <name>info(Pid) -> [{Key, Value}]</name>
+ <name since="">info(Pid) -> [{Key, Value}]</name>
<fsummary>Return information about the running network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -198,7 +198,7 @@
</func>
<func>
- <name>verbosity(Pid, Verbosity) -> void()</name>
+ <name since="">verbosity(Pid, Verbosity) -> void()</name>
<fsummary>Change the verbosity of the network interface process</fsummary>
<type>
<v>Pid = pid()</v>
@@ -212,7 +212,7 @@
</func>
<func>
- <name>get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
+ <name since="">get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name>
<fsummary>Get the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
@@ -233,7 +233,7 @@
</func>
<func>
- <name>set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
+ <name since="">set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name>
<fsummary>Change the Audit Trail Log type</fsummary>
<type>
<v>Pid = pid()</v>
diff --git a/lib/snmp/doc/src/snmpm_network_interface_filter.xml b/lib/snmp/doc/src/snmpm_network_interface_filter.xml
index 742cd53fc6..a50572da51 100644
--- a/lib/snmp/doc/src/snmpm_network_interface_filter.xml
+++ b/lib/snmp/doc/src/snmpm_network_interface_filter.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_network_interface_filter.xml</file>
</header>
- <module>snmpm_network_interface_filter</module>
+ <module since="">snmpm_network_interface_filter</module>
<modulesummary>Behaviour module for the SNMP manager network-interface filter.</modulesummary>
<description>
<p>This module defines the behaviour of the manager network interface
@@ -100,7 +100,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
<funcs>
<func>
- <name>accept_recv(Domain, Addr) -> boolean()</name>
+ <name since="">accept_recv(Domain, Addr) -> boolean()</name>
<fsummary>Shall the received message be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -116,7 +116,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</func>
<func>
- <name>accept_send(Domain, Addr) -> boolean()</name>
+ <name since="">accept_send(Domain, Addr) -> boolean()</name>
<fsummary>Shall the message be sent</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -132,7 +132,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</func>
<func>
- <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
+ <name since="">accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name>
<fsummary>Shall the received pdu be accepted</fsummary>
<type>
<v>Domain = transportDomain()</v>
@@ -150,7 +150,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' |
</func>
<func>
- <name>accept_send_pdu(Domain, Addr, PduType) -> boolean()</name>
+ <name since="">accept_send_pdu(Domain, Addr, PduType) -> boolean()</name>
<fsummary>Shall the pdu be sent</fsummary>
<type>
<v>Domain = transportDomain()</v>
diff --git a/lib/snmp/doc/src/snmpm_user.xml b/lib/snmp/doc/src/snmpm_user.xml
index 87ae1d224a..9abf596c83 100644
--- a/lib/snmp/doc/src/snmpm_user.xml
+++ b/lib/snmp/doc/src/snmpm_user.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>snmpm_user.xml</file>
</header>
- <module>snmpm_user</module>
+ <module since="">snmpm_user</module>
<modulesummary>Behaviour module for the SNMP manager user.</modulesummary>
<description>
<p>This module defines the behaviour of the manager user.
@@ -93,7 +93,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
<funcs>
<func>
- <name>handle_error(ReqId, Reason, UserData) -> void()</name>
+ <name since="">handle_error(ReqId, Reason, UserData) -> void()</name>
<fsummary>Handle error</fsummary>
<type>
<v>ReqId = integer()</v>
@@ -122,7 +122,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name>
+ <name since="">handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name>
<fsummary>Handle agent</fsummary>
<type>
<v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v>
@@ -181,7 +181,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> void()</name>
+ <name since="">handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> void()</name>
<fsummary>Handle the reply to an asynchronous request</fsummary>
<type>
<v>TargetName = target_name()</v>
@@ -202,7 +202,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply</name>
+ <name since="">handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply</name>
<fsummary>Handle a trap/notification message</fsummary>
<type>
<v>TargetName = TargetName2 = target_name()</v>
@@ -225,7 +225,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_inform(TargetName, SnmpInformInfo, UserData) -> Reply</name>
+ <name since="">handle_inform(TargetName, SnmpInformInfo, UserData) -> Reply</name>
<fsummary>Handle a inform message</fsummary>
<type>
<v>TargetName = TargetName2 = target_name()</v>
@@ -253,7 +253,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_report(TargetName, SnmpReportInfo, UserData) -> Reply</name>
+ <name since="">handle_report(TargetName, SnmpReportInfo, UserData) -> Reply</name>
<fsummary>Handle a report message</fsummary>
<type>
<v>TargetName = TargetName2 = target_name()</v>
@@ -278,7 +278,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(),
</func>
<func>
- <name>handle_invalid_result(IN, OUT) -> void()</name>
+ <name since="OTP R16B03">handle_invalid_result(IN, OUT) -> void()</name>
<fsummary>Handle a report message</fsummary>
<type>
<v>IN = {Func, Args}</v>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 1012c80fb1..0bc4baf5eb 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,84 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.7.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed port leakage if a ssh:daemon call failed.</p>
+ <p>
+ Own Id: OTP-15397 Aux Id: ERL-801 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.7.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Incompatibility with newer OpenSSH fixed. Previously
+ versions 7.8 and later could cause Erlang SSH to exit.</p>
+ <p>
+ Own Id: OTP-15413</p>
+ </item>
+ <item>
+ <p>
+ The '<c>exec</c>' option for ssh daemons had wrong format
+ in the documentation.</p>
+ <p>
+ Own Id: OTP-15416</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added public key methods ssh-ed25519 and ssh-ed448.</p>
+ <p>
+ Requires OpenSSL 1.1.1 or higher as cryptolib under the
+ OTP application <c>crypto</c>.</p>
+ <p>
+ Own Id: OTP-15094 Aux Id: OTP-15419 </p>
+ </item>
+ <item>
+ <p>
+ The SSH property tests are now adapted to the PropEr
+ testing tool.</p>
+ <p>
+ Own Id: OTP-15312</p>
+ </item>
+ <item>
+ <p>
+ The term "user" was not documented in the SSH app. A new
+ chapter with terminology is added to the User's Manual
+ where the term "user" is defined.</p>
+ <p>
+ A reference manual page about the module <c>ssh_file</c>
+ is also added. This is the default callback module for
+ user's keys, host keys etc.</p>
+ <p>
+ Own Id: OTP-15314</p>
+ </item>
+ <item>
+ <p>
+ Host and user key checking is made more robust.</p>
+ <p>
+ Own Id: OTP-15424</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.1</title>
<section><title>Improvements and New Features</title>
@@ -230,6 +308,21 @@
</section>
</section>
+<section><title>Ssh 4.6.9.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed port leakage if a ssh:daemon call failed.</p>
+ <p>
+ Own Id: OTP-15397 Aux Id: ERL-801 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.6.9.2</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 fcf79969d3..3fd6eae423 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -28,7 +28,7 @@
<date>2007-10-06</date>
<rev></rev>
</header>
- <module>ssh</module>
+ <module since="">ssh</module>
<modulesummary>Main API of the ssh application</modulesummary>
<description>
<p>This is the interface module for the <c>SSH</c> application.
@@ -1059,17 +1059,17 @@
<!-- CLOSE/1 -->
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Closes an SSH connection.</fsummary>
<desc><p>Closes an SSH connection.</p></desc>
</func>
<!-- CONNECT/2 etc -->
<func>
- <name>connect(Host, Port, Options) -> Result </name>
- <name>connect(Host, Port, Options, NegotiationTimeout) -> Result </name>
- <name>connect(TcpSocket, Options) -> Result</name>
- <name>connect(TcpSocket, Options, NegotiationTimeout) -> Result</name>
+ <name since="">connect(Host, Port, Options) -> Result </name>
+ <name since="">connect(Host, Port, Options, NegotiationTimeout) -> Result </name>
+ <name since="OTP 19.0">connect(TcpSocket, Options) -> Result</name>
+ <name since="">connect(TcpSocket, Options, NegotiationTimeout) -> Result</name>
<fsummary>Connects to an SSH server.</fsummary>
<type>
<v>Host = <seealso marker="#type-host">host()</seealso></v>
@@ -1098,7 +1098,7 @@
<!-- CONNECTION_INFO/1, CONNECTION_INFO/2 -->
<func>
- <name name="connection_info" arity="2"/>
+ <name name="connection_info" arity="2" since=""/>
<fsummary>Retrieves information about a connection.</fsummary>
<desc>
<p>Retrieves information about a connection. The list <c>Keys</c> defines which information that
@@ -1108,9 +1108,9 @@
<!-- DEAMON/1,2,3 -->
<func>
- <name>daemon(Port | TcpSocket) -> Result</name>
- <name>daemon(Port | TcpSocket, Options) -> Result</name>
- <name>daemon(HostAddress, Port, Options) -> Result</name>
+ <name since="">daemon(Port | TcpSocket) -> Result</name>
+ <name since="">daemon(Port | TcpSocket, Options) -> Result</name>
+ <name since="">daemon(HostAddress, Port, Options) -> Result</name>
<fsummary>Starts a server listening for SSH connections.</fsummary>
<type>
<v>Port = integer()</v>
@@ -1154,7 +1154,7 @@
<!-- DAEMON_INFO/1 -->
<func>
- <name name="daemon_info" arity="1"/>
+ <name name="daemon_info" arity="1" since="OTP 19.0"/>
<fsummary>Get info about a daemon</fsummary>
<desc>
<p>Returns a key-value list with information about the daemon.</p>
@@ -1164,7 +1164,7 @@
<!-- DEFAULT_ALGORITHMS/0 -->
<func>
- <name name="default_algorithms" arity="0"/>
+ <name name="default_algorithms" arity="0" since="OTP 18.0"/>
<fsummary>Get a list declaring the supported algorithms</fsummary>
<desc>
<p>Returns a key-value list, where the keys are the different types of algorithms and the values are the
@@ -1176,9 +1176,9 @@
<!-- SHELL/1,2,3 -->
<func>
- <name>shell(Host | TcpSocket) -> Result </name>
- <name>shell(Host | TcpSocket, Options) -> Result </name>
- <name>shell(Host, Port, Options) -> Result </name>
+ <name since="">shell(Host | TcpSocket) -> Result </name>
+ <name since="">shell(Host | TcpSocket, Options) -> Result </name>
+ <name since="">shell(Host, Port, Options) -> Result </name>
<fsummary>Starts an interactive shell on a remote SSH server.</fsummary>
<type>
<v>Host = <seealso marker="#type-host">host()</seealso></v>
@@ -1203,8 +1203,8 @@
</func>
<func>
- <name name="start" arity="0"/>
- <name name="start" arity="1"/>
+ <name name="start" arity="0" since=""/>
+ <name name="start" arity="1" since=""/>
<fsummary>Starts the SSH application.</fsummary>
<desc>
<p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>,
@@ -1215,7 +1215,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Stops the <c>ssh</c> application.</fsummary>
<desc>
<p>Stops the <c>ssh</c> application.
@@ -1225,9 +1225,9 @@
</func>
<func>
- <name name="stop_daemon" arity="1"/>
- <name name="stop_daemon" arity="2"/>
- <name name="stop_daemon" arity="3"/>
+ <name name="stop_daemon" arity="1" since=""/>
+ <name name="stop_daemon" arity="2" since=""/>
+ <name name="stop_daemon" arity="3" since="OTP 21.0"/>
<fsummary>Stops the listener and all connections started by the listener.</fsummary>
<desc>
<p>Stops the listener and all connections started by the listener.</p>
@@ -1235,9 +1235,9 @@
</func>
<func>
- <name name="stop_listener" arity="1"/>
- <name name="stop_listener" arity="2"/>
- <name name="stop_listener" arity="3"/>
+ <name name="stop_listener" arity="1" since=""/>
+ <name name="stop_listener" arity="2" since=""/>
+ <name name="stop_listener" arity="3" since="OTP 21.0"/>
<fsummary>Stops the listener, but leaves existing connections started by the listener operational.</fsummary>
<desc>
<p>Stops the listener, but leaves existing connections started by the listener operational.</p>
diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml
index 9be4007c68..cd28b95fd3 100644
--- a/lib/ssh/doc/src/ssh_client_channel.xml
+++ b/lib/ssh/doc/src/ssh_client_channel.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_client_channel</module>
+ <module since="OTP 21.0">ssh_client_channel</module>
<modulesummary>-behaviour(ssh_client_channel). (Replaces ssh_channel)
</modulesummary>
<description>
@@ -68,8 +68,8 @@
<funcs>
<func>
- <name>call(ChannelRef, Msg) -></name>
- <name>call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name>
+ <name since="OTP 21.0">call(ChannelRef, Msg) -></name>
+ <name since="OTP 21.0">call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name>
<fsummary>Makes a synchronous call to a channel.</fsummary>
<type>
<v>ChannelRef = pid() </v>
@@ -92,7 +92,7 @@
</func>
<func>
- <name>cast(ChannelRef, Msg) -> ok </name>
+ <name since="OTP 21.0">cast(ChannelRef, Msg) -> ok </name>
<fsummary>Sends an asynchronous message to the channel
ChannelRef and returns ok.</fsummary>
<type>
@@ -111,7 +111,7 @@
</func>
<func>
- <name>enter_loop(State) -> _ </name>
+ <name since="OTP 21.0">enter_loop(State) -> _ </name>
<fsummary>Makes an existing process an ssh_client_channel (replaces ssh_channel) process.</fsummary>
<type>
<v>State = term()</v>
@@ -131,7 +131,7 @@
</func>
<func>
- <name>init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name>
+ <name since="OTP 21.0">init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name>
<fsummary>Initiates an <c>ssh_client_channel</c> process.</fsummary>
<type>
<v>Options = [{Option, Value}]</v>
@@ -173,7 +173,7 @@
</func>
<func>
- <name>reply(Client, Reply) -> _</name>
+ <name since="OTP 21.0">reply(Client, Reply) -> _</name>
<fsummary>Sends a reply to a client.</fsummary>
<type>
<v>Client = opaque()</v>
@@ -193,8 +193,8 @@
</func>
<func>
- <name>start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name>
- <name>start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) ->
+ <name since="OTP 21.0">start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name>
+ <name since="OTP 21.0">start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) ->
{ok, ChannelRef} | {error, Reason}</name>
<fsummary>Starts a process that handles an SSH channel.</fsummary>
<type>
@@ -244,7 +244,7 @@
<funcs>
<func>
- <name>Module:code_change(OldVsn, State, Extra) -> {ok,
+ <name since="OTP 21.0">Module:code_change(OldVsn, State, Extra) -> {ok,
NewState}</name>
<fsummary>Converts process state when code is changed.</fsummary>
<type>
@@ -287,7 +287,7 @@
</func>
<func>
- <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
+ <name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
<fsummary>Makes necessary initializations and returns the
initial channel state if the initializations succeed.</fsummary>
@@ -307,7 +307,7 @@
</func>
<func>
- <name>Module:handle_call(Msg, From, State) -> Result</name>
+ <name since="OTP 21.0">Module:handle_call(Msg, From, State) -> Result</name>
<fsummary>Handles messages sent by calling
<c>call/[2,3]</c>.</fsummary>
<type>
@@ -334,7 +334,7 @@
</func>
<func>
- <name>Module:handle_cast(Msg, State) -> Result</name>
+ <name since="OTP 21.0">Module:handle_cast(Msg, State) -> Result</name>
<fsummary>Handles messages sent by calling
<c>cast/2</c>.</fsummary>
<type>
@@ -355,7 +355,7 @@
</func>
<func>
- <name>Module:handle_msg(Msg, State) -> {ok, State} |
+ <name since="OTP 21.0">Module:handle_msg(Msg, State) -> {ok, State} |
{stop, ChannelId, State}</name>
<fsummary>Handles other messages than SSH connection protocol,
@@ -389,7 +389,7 @@
</func>
<func>
- <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
+ <name since="OTP 21.0">Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
ChannelId, State}</name>
<fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
@@ -416,7 +416,7 @@
</func>
<func>
- <name>Module:terminate(Reason, State) -> _</name>
+ <name since="OTP 21.0">Module:terminate(Reason, State) -> _</name>
<fsummary>Does cleaning up before channel process termination.
</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml
index bc77756147..9f2f3013e5 100644
--- a/lib/ssh/doc/src/ssh_client_key_api.xml
+++ b/lib/ssh/doc/src/ssh_client_key_api.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_client_key_api</module>
+ <module since="OTP R16B">ssh_client_key_api</module>
<modulesummary>
-behaviour(ssh_client_key_api).
</modulesummary>
@@ -86,7 +86,7 @@
<funcs>
<func>
- <name>Module:add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
+ <name since="OTP R16B">Module:add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
<fsummary>Adds a host key to the set of trusted host keys.</fsummary>
<type>
<v>HostNames = string()</v>
@@ -103,7 +103,7 @@
</func>
<func>
- <name>Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
+ <name since="OTP R16B">Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
<fsummary>Checks if a host key is trusted.</fsummary>
<type>
<v>Key = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
@@ -125,7 +125,7 @@
</func>
<func>
- <name>Module:user_key(Algorithm, ConnectOptions) ->
+ <name since="OTP R16B">Module:user_key(Algorithm, ConnectOptions) ->
{ok, PrivateKey} | {error, Reason}</name>
<fsummary>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 8e1cf156a8..2a701929f6 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_connection</module>
+ <module since="">ssh_connection</module>
<modulesummary>
This module provides API functions to send SSH Connection Protocol
events to the other side of an SSH channel.
@@ -201,7 +201,7 @@
<funcs>
<func>
- <name>adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name>
+ <name since="">adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name>
<fsummary>Adjusts the SSH flow control window.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -221,7 +221,7 @@
</func>
<func>
- <name>close(ConnectionRef, ChannelId) -> ok</name>
+ <name since="">close(ConnectionRef, ChannelId) -> ok</name>
<fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -240,7 +240,7 @@
</func>
<func>
- <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() |
+ <name since="">exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() |
{error, reason()}</name>
<fsummary>Requests that the server starts the execution of the given command.</fsummary>
<type>
@@ -284,7 +284,7 @@
</func>
<func>
- <name>exit_status(ConnectionRef, ChannelId, Status) -> ok</name>
+ <name since="">exit_status(ConnectionRef, ChannelId, Status) -> ok</name>
<fsummary>Sends the exit status of a command to the client.</fsummary>
<type>
<v>ConnectionRef = connection_ref() </v>
@@ -298,8 +298,8 @@
</func>
<func>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options) -></name>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() |
+ <name since="OTP 17.5">ptty_alloc(ConnectionRef, ChannelId, Options) -></name>
+ <name since="OTP 17.4">ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() |
{error, reason()}</name>
<fsummary>Sends an SSH Connection Protocol <c>pty_req</c>,
to allocate a pseudo-terminal.</fsummary>
@@ -339,7 +339,7 @@
</func>
<func>
- <name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name>
+ <name since="">reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name>
<fsummary>Sends status replies to requests that want such replies.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -357,10 +357,10 @@
</func>
<func>
- <name>send(ConnectionRef, ChannelId, Data) -></name>
- <name>send(ConnectionRef, ChannelId, Data, Timeout) -></name>
- <name>send(ConnectionRef, ChannelId, Type, Data) -></name>
- <name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
+ <name since="">send(ConnectionRef, ChannelId, Data) -></name>
+ <name since="">send(ConnectionRef, ChannelId, Data, Timeout) -></name>
+ <name since="">send(ConnectionRef, ChannelId, Type, Data) -></name>
+ <name since="">send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
ok | {error, timeout} | {error, closed}</name>
<fsummary>Sends channel data.</fsummary>
<type>
@@ -380,7 +380,7 @@
</func>
<func>
- <name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
+ <name since="">send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
<fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary>
<type>
<v>ConnectionRef = connection_ref()</v>
@@ -392,8 +392,8 @@
</func>
<func>
- <name>session_channel(ConnectionRef, Timeout) -></name>
- <name>session_channel(ConnectionRef, InitialWindowSize,
+ <name since="">session_channel(ConnectionRef, Timeout) -></name>
+ <name since="">session_channel(ConnectionRef, InitialWindowSize,
MaxPacketSize, Timeout) -> {ok, channel_id()} | {error, reason()}</name>
<fsummary>Opens a channel for an SSH session.</fsummary>
<type>
@@ -410,7 +410,7 @@
</func>
<func>
- <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() |
+ <name since="">setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() |
{error, reason()}</name>
<fsummary>Environment variables can be passed to the
shell/command to be started later.</fsummary>
@@ -428,7 +428,7 @@
</func>
<func>
- <name>shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed}
+ <name since="">shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed}
</name>
<fsummary>Requests that the user default shell (typically defined in
/etc/passwd in Unix systems) is to be executed at the server end.</fsummary>
@@ -448,7 +448,7 @@
</func>
<func>
- <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() |
+ <name since="">subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() |
{error, reason()}</name>
<fsummary>Requests to execute a predefined subsystem on the server.</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_file.xml b/lib/ssh/doc/src/ssh_file.xml
index 6681d9c306..f1fef09083 100644
--- a/lib/ssh/doc/src/ssh_file.xml
+++ b/lib/ssh/doc/src/ssh_file.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_file</module>
+ <module since="OTP 21.2">ssh_file</module>
<modulesummary>Default callback module for the client's and server's database operations in the ssh application</modulesummary>
<description>
<p>This module is the default callback handler for the client's and the server's user and host "database" operations.
@@ -169,7 +169,7 @@
<funcs>
<func>
- <name>host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name>
+ <name since="OTP 21.2">host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -195,7 +195,7 @@
</func>
<func>
- <name>is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
+ <name since="OTP 21.2">is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -216,7 +216,7 @@
</func>
<func>
- <name>add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
+ <name since="OTP 21.2">add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -235,7 +235,7 @@
</func>
<func>
- <name>is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
+ <name since="OTP 21.2">is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
@@ -254,7 +254,7 @@
</func>
<func>
- <name>user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name>
+ <name since="OTP 21.2">user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name>
<fsummary></fsummary>
<desc>
<p><strong>Types and description</strong></p>
diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml
index 31ba9a3231..a4e18bbfbf 100644
--- a/lib/ssh/doc/src/ssh_server_channel.xml
+++ b/lib/ssh/doc/src/ssh_server_channel.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_server_channel</module>
+ <module since="OTP 21.0">ssh_server_channel</module>
<modulesummary>-behaviour(ssh_server_channel). (Replaces ssh_daemon_channel)
</modulesummary>
<description>
@@ -70,7 +70,7 @@
<funcs>
<func>
- <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
+ <name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
<fsummary>Makes necessary initializations and returns the
initial channel state if the initializations succeed.</fsummary>
@@ -93,7 +93,7 @@
</func>
<func>
- <name>Module:handle_msg(Msg, State) -> {ok, State} |
+ <name since="OTP 21.0">Module:handle_msg(Msg, State) -> {ok, State} |
{stop, ChannelId, State}</name>
<fsummary>Handles other messages than SSH connection protocol,
@@ -125,7 +125,7 @@
</func>
<func>
- <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
+ <name since="OTP 21.0">Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
ChannelId, State}</name>
<fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
@@ -152,7 +152,7 @@
</func>
<func>
- <name>Module:terminate(Reason, State) -> _</name>
+ <name since="OTP 21.0">Module:terminate(Reason, State) -> _</name>
<fsummary>Does cleaning up before channel process termination.
</fsummary>
<type>
diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml
index e2a31bd5f5..013a788a4a 100644
--- a/lib/ssh/doc/src/ssh_server_key_api.xml
+++ b/lib/ssh/doc/src/ssh_server_key_api.xml
@@ -29,7 +29,7 @@
<date></date>
<rev></rev>
</header>
- <module>ssh_server_key_api</module>
+ <module since="OTP R16B">ssh_server_key_api</module>
<modulesummary>
-behaviour(ssh_server_key_api).
</modulesummary>
@@ -87,7 +87,7 @@
<funcs>
<func>
- <name>Module:host_key(Algorithm, DaemonOptions) ->
+ <name since="OTP R16B">Module:host_key(Algorithm, DaemonOptions) ->
{ok, Key} | {error, Reason}</name>
<fsummary>Fetches the host’s private key.</fsummary>
<type>
@@ -111,7 +111,7 @@
</func>
<func>
- <name>Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
+ <name since="OTP R16B">Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name>
<fsummary>Checks if the user key is authorized.</fsummary>
<type>
<v>PublicUserKey = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index 8c105147d6..c89092798d 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>ssh_sftp.sgml</file>
</header>
- <module>ssh_sftp</module>
+ <module since="">ssh_sftp</module>
<modulesummary>SFTP client.</modulesummary>
<description>
<p>This module implements an SSH FTP (SFTP) client. SFTP is a
@@ -82,7 +82,7 @@
<funcs>
<func>
- <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name>
+ <name since="">apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name>
<fsummary>Reads asynchronously from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -98,7 +98,7 @@
</func>
<func>
- <name>apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name>
+ <name since="">apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name>
<fsummary>Writes asynchronously to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -116,7 +116,7 @@
</func>
<func>
- <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name>
+ <name since="">aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name>
<fsummary>Reads asynchronously from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -137,7 +137,7 @@
</func>
<func>
- <name>awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name>
+ <name since="">awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name>
<fsummary>Writes asynchronously to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -159,8 +159,8 @@
</func>
<func>
- <name>close(ChannelPid, Handle) -></name>
- <name>close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name>
+ <name since="">close(ChannelPid, Handle) -></name>
+ <name since="">close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name>
<fsummary>Closes an open handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -173,8 +173,8 @@
</func>
<func>
- <name>delete(ChannelPid, Name) -></name>
- <name>delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
+ <name since="">delete(ChannelPid, Name) -></name>
+ <name since="">delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
<fsummary>Deletes a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -188,8 +188,8 @@
</func>
<func>
- <name>del_dir(ChannelPid, Name) -></name>
- <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
+ <name since="">del_dir(ChannelPid, Name) -></name>
+ <name since="">del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
<fsummary>Deletes an empty directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -204,8 +204,8 @@
</func>
<func>
- <name>list_dir(ChannelPid, Path) -></name>
- <name>list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name>
+ <name since="">list_dir(ChannelPid, Path) -></name>
+ <name since="">list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name>
<fsummary>Lists the directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -221,8 +221,8 @@
</func>
<func>
- <name>make_dir(ChannelPid, Name) -></name>
- <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
+ <name since="">make_dir(ChannelPid, Name) -></name>
+ <name since="">make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name>
<fsummary>Creates a directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -237,8 +237,8 @@
</func>
<func>
- <name>make_symlink(ChannelPid, Name, Target) -></name>
- <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name>
+ <name since="">make_symlink(ChannelPid, Name, Target) -></name>
+ <name since="">make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name>
<fsummary>Creates a symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -253,8 +253,8 @@
</func>
<func>
- <name>open(ChannelPid, File, Mode) -></name>
- <name>open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
+ <name since="">open(ChannelPid, File, Mode) -></name>
+ <name since="">open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
<fsummary>Opens a file and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -270,8 +270,8 @@
</desc>
</func>
<func>
- <name>opendir(ChannelPid, Path) -></name>
- <name>opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name>
+ <name since="">opendir(ChannelPid, Path) -></name>
+ <name since="">opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name>
<fsummary>Opens a directory and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -285,8 +285,8 @@
</func>
<func>
- <name>open_tar(ChannelPid, Path, Mode) -></name>
- <name>open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
+ <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode) -></name>
+ <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name>
<fsummary>Opens a tar file on the server to which <c>ChannelPid</c>
is connected and returns a handle.</fsummary>
<type>
@@ -339,8 +339,8 @@
</func>
<func>
- <name>position(ChannelPid, Handle, Location) -></name>
- <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name>
+ <name since="">position(ChannelPid, Handle, Location) -></name>
+ <name since="">position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name>
<fsummary>Sets the file position of a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -384,8 +384,8 @@
</func>
<func>
- <name>pread(ChannelPid, Handle, Position, Len) -></name>
- <name>pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
+ <name since="">pread(ChannelPid, Handle, Position, Len) -></name>
+ <name since="">pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
<fsummary>Reads from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -402,8 +402,8 @@
</func>
<func>
- <name>pwrite(ChannelPid, Handle, Position, Data) -> ok</name>
- <name>pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name>
+ <name since="">pwrite(ChannelPid, Handle, Position, Data) -> ok</name>
+ <name since="">pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -419,8 +419,8 @@
</func>
<func>
- <name>read(ChannelPid, Handle, Len) -></name>
- <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
+ <name since="">read(ChannelPid, Handle, Len) -></name>
+ <name since="">read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name>
<fsummary>Reads from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -441,8 +441,8 @@
</func>
<func>
- <name>read_file(ChannelPid, File) -></name>
- <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name>
+ <name since="">read_file(ChannelPid, File) -></name>
+ <name since="">read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name>
<fsummary>Reads a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -456,8 +456,8 @@
</func>
<func>
- <name>read_file_info(ChannelPid, Name) -></name>
- <name>read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
+ <name since="">read_file_info(ChannelPid, Name) -></name>
+ <name since="">read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
<fsummary>Gets information about a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -481,8 +481,8 @@
</func>
<func>
- <name>read_link(ChannelPid, Name) -></name>
- <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name>
+ <name since="">read_link(ChannelPid, Name) -></name>
+ <name since="">read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name>
<fsummary>Reads symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -496,8 +496,8 @@
</func>
<func>
- <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name>
- <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
+ <name since="">read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name>
+ <name since="">read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name>
<fsummary>Gets information about a symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -517,8 +517,8 @@
</func>
<func>
- <name>rename(ChannelPid, OldName, NewName) -> </name>
- <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name>
+ <name since="">rename(ChannelPid, OldName, NewName) -> </name>
+ <name since="">rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name>
<fsummary>Renames a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -534,16 +534,16 @@
</func>
<func>
- <name>start_channel(ConnectionRef) -></name>
- <name>start_channel(ConnectionRef, Options) ->
+ <name since="">start_channel(ConnectionRef) -></name>
+ <name since="">start_channel(ConnectionRef, Options) ->
{ok, Pid} | {error, reason()|term()}</name>
- <name>start_channel(Host, Options) -></name>
- <name>start_channel(Host, Port, Options) ->
+ <name since="">start_channel(Host, Options) -></name>
+ <name since="">start_channel(Host, Port, Options) ->
{ok, Pid, ConnectionRef} | {error, reason()|term()}</name>
- <name>start_channel(TcpSocket) -></name>
- <name>start_channel(TcpSocket, Options) ->
+ <name since="">start_channel(TcpSocket) -></name>
+ <name since="">start_channel(TcpSocket, Options) ->
{ok, Pid, ConnectionRef} | {error, reason()|term()}</name>
<fsummary>Starts an SFTP client.</fsummary>
@@ -594,7 +594,7 @@
</func>
<func>
- <name>stop_channel(ChannelPid) -> ok</name>
+ <name since="">stop_channel(ChannelPid) -> ok</name>
<fsummary>Stops the SFTP client channel.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -606,8 +606,8 @@
</func>
<func>
- <name>write(ChannelPid, Handle, Data) -></name>
- <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name>
+ <name since="">write(ChannelPid, Handle, Data) -></name>
+ <name since="">write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -625,8 +625,8 @@
</func>
<func>
- <name>write_file(ChannelPid, File, Iolist) -></name>
- <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name>
+ <name since="">write_file(ChannelPid, File, Iolist) -></name>
+ <name since="">write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
@@ -641,8 +641,8 @@
</func>
<func>
- <name>write_file_info(ChannelPid, Name, Info) -></name>
- <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name>
+ <name since="">write_file_info(ChannelPid, Name, Info) -></name>
+ <name since="">write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name>
<fsummary>Writes information for a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index 3b34150e98..ee72784add 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>ssh_sftpd.sgml</file>
</header>
- <module>ssh_sftpd</module>
+ <module since="">ssh_sftpd</module>
<modulesummary>Specifies the channel process to handle an SFTP subsystem.</modulesummary>
<description>
<p>Specifies a channel process to handle an SFTP subsystem.</p>
@@ -51,7 +51,7 @@
</section>
<funcs>
<func>
- <name>subsystem_spec(Options) -> subsystem_spec()</name>
+ <name since="">subsystem_spec(Options) -> subsystem_spec()</name>
<fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary>
<type>
<v>Options = [{Option, Value}]</v>
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 086fa6e5f8..9281bf84a7 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -270,25 +270,38 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535,
try
{Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
#{} = Options0 = ssh_options:handle_options(server, UserOptions),
-
- {{Host,Port}, ListenSocket} =
- open_listen_socket(Host1, Port0, Options0),
-
- %% Now Host,Port is what to use for the supervisor to register its name,
- %% and ListenSocket is for listening on connections. But it is still owned
- %% by self()...
-
- finalize_start(Host, Port, ?GET_OPT(profile, Options0),
- ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0),
- fun(Opts, Result) ->
- {_, Callback, _} = ?GET_OPT(transport, Opts),
- receive
- {request_control, ListenSocket, ReqPid} ->
- ok = Callback:controlling_process(ListenSocket, ReqPid),
- ReqPid ! {its_yours,ListenSocket},
- Result
- end
- end)
+ {open_listen_socket(Host1, Port0, Options0), Options0}
+ of
+ {{{Host,Port}, ListenSocket}, Options1} ->
+ try
+ %% Now Host,Port is what to use for the supervisor to register its name,
+ %% and ListenSocket is for listening on connections. But it is still owned
+ %% by self()...
+ finalize_start(Host, Port, ?GET_OPT(profile, Options1),
+ ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options1),
+ fun(Opts, Result) ->
+ {_, Callback, _} = ?GET_OPT(transport, Opts),
+ receive
+ {request_control, ListenSocket, ReqPid} ->
+ ok = Callback:controlling_process(ListenSocket, ReqPid),
+ ReqPid ! {its_yours,ListenSocket},
+ Result
+ end
+ end)
+ of
+ {error,Err} ->
+ close_listen_socket(ListenSocket, Options1),
+ {error,Err};
+ OK ->
+ OK
+ catch
+ error:Error ->
+ close_listen_socket(ListenSocket, Options1),
+ error(Error);
+ exit:Exit ->
+ close_listen_socket(ListenSocket, Options1),
+ exit(Exit)
+ end
catch
throw:bad_fd ->
{error,bad_fd};
@@ -524,6 +537,15 @@ open_listen_socket(_Host0, Port0, Options0) ->
{{LHost,LPort}, LSock}.
%%%----------------------------------------------------------------
+close_listen_socket(ListenSocket, Options) ->
+ try
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ Callback:close(ListenSocket)
+ catch
+ _C:_E -> ok
+ end.
+
+%%%----------------------------------------------------------------
finalize_start(Host, Port, Profile, Options0, F) ->
try
%% throws error:Error if no usable hostkey is found
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 278f6a9780..aa9ba0f9bb 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -508,11 +508,8 @@ close_our_file({_,Fd}, FileMod, FS0) ->
FS1.
%%% stat: do the stat
-stat(Vsn, ReqId, Data, State, F) when Vsn =< 3->
- <<?UINT32(BLen), BPath:BLen/binary>> = Data,
- stat(ReqId, unicode:characters_to_list(BPath), State, F);
-stat(Vsn, ReqId, Data, State, F) when Vsn >= 4->
- <<?UINT32(BLen), BPath:BLen/binary, ?UINT32(_Flags)>> = Data,
+stat(Vsn, ReqId, Data, State, F) ->
+ <<?UINT32(BLen), BPath:BLen/binary, _/binary>> = Data,
stat(ReqId, unicode:characters_to_list(BPath), State, F).
fstat(Vsn, ReqId, Data, State) when Vsn =< 3->
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index da94b5722f..5de6d52092 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -43,7 +43,9 @@ suite() ->
{timetrap,{seconds,40}}].
all() ->
- [{group, all_tests}].
+ [{group, all_tests},
+ daemon_already_started
+ ].
groups() ->
[{all_tests, [parallel], [{group, ssh_renegotiate_SUITE},
@@ -801,6 +803,24 @@ daemon_already_started(Config) when is_list(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+%%% Test that a failed daemon start does not leave the port open
+daemon_error_closes_port(Config) ->
+ GoodSystemDir = proplists:get_value(data_dir, Config),
+ Port = ssh_test_lib:inet_port(),
+ {error,_} = ssh_test_lib:daemon(Port, []), % No system dir
+ case ssh_test_lib:daemon(Port, [{system_dir, GoodSystemDir}]) of
+ {error,eaddrinuse} ->
+ {fail, "Port leakage"};
+ {error,Error} ->
+ ct:log("Strange error: ~p",[Error]),
+ {fail, "Strange error"};
+ {Pid, _Host, Port} ->
+ %% Ok
+ ssh:stop_daemon(Pid)
+ end.
+
+
+%%--------------------------------------------------------------------
%%% check that known_hosts is updated correctly
known_hosts(Config) when is_list(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl
index f4eef2dc77..8e82527c6e 100644
--- a/lib/ssh/test/ssh_compat_SUITE.erl
+++ b/lib/ssh/test/ssh_compat_SUITE.erl
@@ -1126,7 +1126,24 @@ prepare_local_directory(ServerRootDir) ->
"chmod 222 unreadable_file",
"exit"].
+
check_local_directory(ServerRootDir) ->
+ TimesToTry = 3, % sleep 0.5, 1, 2 and then 4 secs (7.5s in total)
+ check_local_directory(ServerRootDir, 500, TimesToTry-1).
+
+check_local_directory(ServerRootDir, SleepTime, N) ->
+ case do_check_local_directory(ServerRootDir) of
+ {error,Error} when N>0 ->
+ %% Could be that the erlang side is faster and the docker's operations
+ %% are not yet finalized.
+ %% Sleep for a while and retry a few times:
+ timer:sleep(SleepTime),
+ check_local_directory(ServerRootDir, 2*SleepTime, N-1);
+ Other ->
+ Other
+ end.
+
+do_check_local_directory(ServerRootDir) ->
case lists:sort(ok(file:list_dir(ServerRootDir)) -- [".",".."]) of
["ex_tst1","mydir","tst2"] ->
{ok,Expect} = file:read_file(filename:join(ServerRootDir,"ex_tst1")),
@@ -1161,6 +1178,7 @@ check_local_directory(ServerRootDir) ->
{error,{bad_dir_contents,"/"}}
end.
+
call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) ->
{DockerIP,DockerPort} = ip_port(Config),
{ok,C} = ssh:connect(DockerIP, DockerPort,
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index 778e4a5fc8..6aa587dc7f 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -1124,12 +1124,12 @@ start_our_shell(_User, _Peer) ->
ssh_exec_echo(Cmd) ->
spawn(fun() ->
- io:format("echo "++Cmd ++ "\n")
+ io:format("echo ~s\n", [Cmd])
end).
ssh_exec_echo(Cmd, User) ->
spawn(fun() ->
- io:format(io_lib:format("echo ~s ~s\n",[User,Cmd]))
+ io:format("echo ~s ~s\n",[User,Cmd])
end).
ssh_exec_echo(Cmd, User, _PeerAddr) ->
ssh_exec_echo(Cmd,User).
diff --git a/lib/ssh/test/ssh_engine_SUITE.erl b/lib/ssh/test/ssh_engine_SUITE.erl
index c2e6ac1fee..3adb23acdb 100644
--- a/lib/ssh/test/ssh_engine_SUITE.erl
+++ b/lib/ssh/test/ssh_engine_SUITE.erl
@@ -126,10 +126,17 @@ simple_connect(Config) ->
load_engine() ->
case crypto:get_test_engine() of
{ok, Engine} ->
- try crypto:engine_load(<<"dynamic">>,
+ try
+ %% The test engine has it's own fake rsa sign/verify that
+ %% you don't want to use, so exclude it from methods to load:
+ Methods =
+ crypto:engine_get_all_methods() -- [engine_method_rsa],
+ crypto:engine_load(<<"dynamic">>,
[{<<"SO_PATH">>, Engine},
<<"LOAD">>],
- [])
+ [],
+ Methods
+ )
catch
error:notsup ->
{error, notsup}
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index ae7b4cf3f2..2890d7fe5b 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.7.1
+SSH_VSN = 4.7.3
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 4baedf7431..82eb8ff700 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,135 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 9.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix encoding of the SRP extension length field in ssl.
+ The old encoding of the SRP extension length could cause
+ interoperability problems with third party SSL
+ implementations when SRP was used.</p>
+ <p>
+ Own Id: OTP-15477 Aux Id: ERL-790 </p>
+ </item>
+ <item>
+ <p>
+ Guarantee active once data delivery, handling TCP stream
+ properly.</p>
+ <p>
+ Own Id: OTP-15504 Aux Id: ERL-371 </p>
+ </item>
+ <item>
+ <p>
+ Correct gen_statem returns for some error cases</p>
+ <p>
+ Own Id: OTP-15505</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed renegotiation bug. Client did not handle server
+ initiated renegotiation correctly after rewrite to two
+ connection processes, due to ERL-622 commit
+ d87ac1c55188f5ba5cdf72384125d94d42118c18. This could
+ manifest it self as a " bad_record_mac" alert.</p>
+ <p>
+ Also included are some optimizations</p>
+ <p>
+ Own Id: OTP-15489 Aux Id: ERL-308 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ PEM cache was not evicting expired entries due to due to
+ timezone confusion.</p>
+ <p>
+ Own Id: OTP-15368</p>
+ </item>
+ <item>
+ <p>
+ Make sure an error is returned if a "transport_accept
+ socket" is used in some other call than ssl:handshake* or
+ ssl:controlling_process</p>
+ <p>
+ Own Id: OTP-15384 Aux Id: ERL-756 </p>
+ </item>
+ <item>
+ <p>
+ Fix timestamp handling in the PEM-cache could cause
+ entries to not be invalidated at the correct time.</p>
+ <p>
+ Own Id: OTP-15402</p>
+ </item>
+ <item>
+ <p>
+ Extend check for undelivered data at closing, could under
+ some circumstances fail to deliver all data that was
+ actually received.</p>
+ <p>
+ Own Id: OTP-15412 Aux Id: ERL-731 </p>
+ </item>
+ <item>
+ <p>
+ Correct signature check for TLS-1.2 that allows different
+ algorithms for signature of peer cert and peer cert key.
+ Not all allowed combinations where accepted.</p>
+ <p>
+ Own Id: OTP-15415 Aux Id: ERL-763 </p>
+ </item>
+ <item>
+ <p>
+ Correct gen_statem return value, could cause
+ renegotiation to fail.</p>
+ <p>
+ Own Id: OTP-15418 Aux Id: ERL-770 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add engine support for RSA key exchange</p>
+ <p>
+ Own Id: OTP-15420 Aux Id: ERIERL-268 </p>
+ </item>
+ <item>
+ <p>
+ ssl now uses active n internally to boost performance.
+ Old active once behavior can be restored by setting
+ application variable see manual page for ssl application
+ (man 6).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15449</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.0.3</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 ef99ace351..200fb89a4d 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -28,7 +28,7 @@
<rev></rev>
<file>ssl.xml</file>
</header>
- <module>ssl</module>
+ <module since="">ssl</module>
<modulesummary>Interface Functions for Secure Socket Layer</modulesummary>
<description>
<p>
@@ -83,8 +83,9 @@
<p><c>| {ciphers, ciphers()}</c></p>
<p><c>| {user_lookup_fun, {fun(), term()}}, {psk_identity, string()},
{srp_identity, {string(), string()}}</c></p>
- <p><c>| {reuse_sessions, boolean()}</c></p>
- <p><c>| {reuse_session, fun()} {next_protocols_advertised, [binary()]}</c></p>
+ <p><c>| {reuse_sessions, boolean() | save()}</c></p>
+ <p><c>| {reuse_session, fun() | binary()} </c></p>
+ <p><c>| {next_protocols_advertised, [binary()]}</c></p>
<p><c>| {client_preferred_next_protocols, {client | server,
[binary()]} | {client | server, [binary()], binary()}}</c></p>
<p><c>| {log_alert, boolean()}</c></p>
@@ -593,11 +594,23 @@ fun(srp, Username :: string(), UserState :: term()) ->
<item><p>In mode <c>verify_none</c> the default behavior is to allow
all x509-path validation errors. See also option <c>verify_fun</c>.</p>
</item>
+
+ <tag><marker id="client_reuse_session"/><c>{reuse_session, binary()}</c></tag>
+ <item><p>Reuses a specific session earlier saved with the option
+ <c>{reuse_sessions, save} since ssl-9.2</c>
+ </p></item>
- <tag><c>{reuse_sessions, boolean()}</c></tag>
- <item><p>Specifies if the client is to try to reuse sessions
- when possible.</p></item>
-
+ <tag><c>{reuse_sessions, boolean() | save}</c></tag>
+ <item><p>When <c>save</c> is specified a new connection will be negotiated
+ and saved for later reuse. The session ID can be fetched with
+ <seealso marker="#connection_information">connection_information/2</seealso>
+ and used with the client option <seealso marker="#client_reuse_session">reuse_session</seealso>
+ The boolean value true specifies that if possible, automatized session reuse will
+ be performed. If a new session is created, and is unique in regard
+ to previous stored sessions, it will be saved for possible later reuse.
+ Value <c>save</c> since ssl-9.2
+ </p></item>
+
<tag><c>{cacerts, [public_key:der_encoded()]}</c></tag>
<item><p>The DER-encoded trusted certificates. If this option
is supplied it overrides option <c>cacertfile</c>.</p></item>
@@ -796,11 +809,14 @@ fun(srp, Username :: string(), UserState :: term()) ->
</item>
<tag><c>{reuse_sessions, boolean()}</c></tag>
- <item><p>Specifies if the server is to agree to reuse sessions
- when requested by the clients. See also option <c>reuse_session</c>.
+ <item><p>The boolean value true specifies that the server will
+ agree to reuse sessions. Setting it to false will result in an empty
+ session table, that is no sessions will be reused.
+ See also option <seealso marker="#server_reuse_session">reuse_session</seealso>
</p></item>
- <tag><c>{reuse_session, fun(SuggestedSessionId,
+ <tag><marker id="server_reuse_session"/>
+ <c>{reuse_session, fun(SuggestedSessionId,
PeerCert, Compression, CipherSuite) -> boolean()}</c></tag>
<item><p>Enables the TLS/DTLS server to have a local policy
for deciding if a session is to be reused or not.
@@ -920,7 +936,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
<funcs>
<func>
- <name>append_cipher_suites(Deferred, Suites) -> ciphers() </name>
+ <name since="OTP 20.3">append_cipher_suites(Deferred, Suites) -> ciphers() </name>
<fsummary></fsummary>
<type>
<v>Deferred = ciphers() | cipher_filters() </v>
@@ -936,8 +952,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>cipher_suites() -></name>
- <name>cipher_suites(Type) -> old_ciphers()</name>
+ <name since="OTP R14B">cipher_suites() -></name>
+ <name since="OTP R14B">cipher_suites(Type) -> old_ciphers()</name>
<fsummary>Returns a list of supported cipher suites.</fsummary>
<type>
<v>Type = erlang | openssl | all</v>
@@ -948,7 +964,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>cipher_suites(Supported, Version) -> ciphers()</name>
+ <name since="OTP 20.3">cipher_suites(Supported, Version) -> ciphers()</name>
<fsummary>Returns a list of all default or
all supported cipher suites.</fsummary>
<type>
@@ -962,8 +978,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>eccs() -></name>
- <name>eccs(protocol_version()) -> [named_curve()]</name>
+ <name since="OTP 19.2">eccs() -></name>
+ <name since="OTP 19.2">eccs(protocol_version()) -> [named_curve()]</name>
<fsummary>Returns a list of supported ECCs.</fsummary>
<desc><p>Returns a list of supported ECCs. <c>eccs()</c>
@@ -973,7 +989,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>clear_pem_cache() -> ok </name>
+ <name since="OTP 17.5">clear_pem_cache() -> ok </name>
<fsummary> Clears the pem cache</fsummary>
<desc><p>PEM files, used by ssl API-functions, are cached. The
@@ -985,8 +1001,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connect(Socket, SslOptions) -> </name>
- <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext}
+ <name since="OTP R14B">connect(Socket, SslOptions) -> </name>
+ <name since="">connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext}
| {error, Reason}</name>
<fsummary>Upgrades a <c>gen_tcp</c>, or
equivalent, connected socket to an TLS socket.</fsummary>
@@ -1022,8 +1038,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connect(Host, Port, Options) -></name>
- <name>connect(Host, Port, Options, Timeout) ->
+ <name since="">connect(Host, Port, Options) -></name>
+ <name since="">connect(Host, Port, Options, Timeout) ->
{ok, SslSocket}| {ok, SslSocket, Ext} | {error, Reason}</name>
<fsummary>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</fsummary>
<type>
@@ -1065,7 +1081,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>close(SslSocket) -> ok | {error, Reason}</name>
+ <name since="">close(SslSocket) -> ok | {error, Reason}</name>
<fsummary>Closes an TLS/DTLS connection.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1076,7 +1092,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name>
+ <name since="OTP 18.1">close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name>
<fsummary>Closes an TLS connection.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1091,7 +1107,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>controlling_process(SslSocket, NewOwner) ->
+ <name since="">controlling_process(SslSocket, NewOwner) ->
ok | {error, Reason}</name>
<fsummary>Assigns a new controlling process to the
TLS/DTLS socket.</fsummary>
@@ -1107,7 +1123,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connection_information(SslSocket) ->
+ <name since="OTP 18.0">connection_information(SslSocket) ->
{ok, Result} | {error, Reason} </name>
<fsummary>Returns all the connection information.
</fsummary>
@@ -1128,7 +1144,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>connection_information(SslSocket, Items) ->
+ <name since="OTP 18.0">connection_information(SslSocket, Items) ->
{ok, Result} | {error, Reason} </name>
<fsummary>Returns the requested connection information.
</fsummary>
@@ -1150,7 +1166,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>filter_cipher_suites(Suites, Filters) -> ciphers()</name>
+ <name since="OTP 20.3">filter_cipher_suites(Suites, Filters) -> ciphers()</name>
<fsummary></fsummary>
<type>
<v> Suites = ciphers()</v>
@@ -1165,7 +1181,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>format_error(Reason) -> string()</name>
+ <name since="">format_error(Reason) -> string()</name>
<fsummary>Returns an error string.</fsummary>
<type>
<v>Reason = term()</v>
@@ -1176,7 +1192,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>getopts(SslSocket, OptionNames) ->
+ <name since="">getopts(SslSocket, OptionNames) ->
{ok, [socketoption()]} | {error, Reason}</name>
<fsummary>Gets the values of the specified options.</fsummary>
<type>
@@ -1190,9 +1206,9 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>getstat(SslSocket) ->
+ <name since="OTP 19.0">getstat(SslSocket) ->
{ok, OptionValues} | {error, inet:posix()}</name>
- <name>getstat(SslSocket, OptionNames) ->
+ <name since="OTP 19.0">getstat(SslSocket, OptionNames) ->
{ok, OptionValues} | {error, inet:posix()}</name>
<fsummary>Get one or more statistic options for a socket</fsummary>
<type>
@@ -1207,8 +1223,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>handshake(HsSocket) -> </name>
- <name>handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake(HsSocket) -> </name>
+ <name since="OTP 21.0">handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS handshake.</fsummary>
<type>
<v>HsSocket = SslSocket = sslsocket()</v>
@@ -1222,8 +1238,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>handshake(Socket, SslOptions) -> </name>
- <name>handshake(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake(Socket, SslOptions) -> </name>
+ <name since="OTP 21.0">handshake(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary>
<type>
<v>Socket = socket() | sslsocket() </v>
@@ -1261,7 +1277,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>handshake_cancel(SslSocket) -> ok </name>
+ <name since="OTP 21.0">handshake_cancel(SslSocket) -> ok </name>
<fsummary>Cancel handshake with a fatal alert</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1272,8 +1288,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>handshake_continue(HsSocket, SSLOptions) -> {ok, SslSocket} | {error, Reason}</name>
- <name>handshake_continue(HsSocket, SSLOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake_continue(HsSocket, SSLOptions) -> {ok, SslSocket} | {error, Reason}</name>
+ <name since="OTP 21.0">handshake_continue(HsSocket, SSLOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name>
<fsummary>Continue the SSL/TLS handshake.</fsummary>
<type>
<v>HsSocket = SslSocket = sslsocket()</v>
@@ -1287,7 +1303,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>listen(Port, Options) ->
+ <name since="">listen(Port, Options) ->
{ok, ListenSocket} | {error, Reason}</name>
<fsummary>Creates an SSL listen socket.</fsummary>
<type>
@@ -1301,7 +1317,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name>
+ <name since="OTP 18.0">negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name>
<fsummary>Returns the protocol negotiated through ALPN or NPN extensions.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1315,7 +1331,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name>
+ <name since="">peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name>
<fsummary>Returns the peer certificate.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1330,7 +1346,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>peername(SslSocket) -> {ok, {Address, Port}} |
+ <name since="">peername(SslSocket) -> {ok, {Address, Port}} |
{error, Reason}</name>
<fsummary>Returns the peer address and port.</fsummary>
<type>
@@ -1344,7 +1360,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>prepend_cipher_suites(Preferred, Suites) -> ciphers()</name>
+ <name since="OTP 20.3">prepend_cipher_suites(Preferred, Suites) -> ciphers()</name>
<fsummary></fsummary>
<type>
<v>Preferred = ciphers() | cipher_filters() </v>
@@ -1360,7 +1376,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name>
+ <name since="OTP R15B01">prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name>
<fsummary>Uses a session Pseudo-Random Function to generate key material.</fsummary>
<type>
<v>Socket = sslsocket()</v>
@@ -1380,8 +1396,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>recv(SslSocket, Length) -> </name>
- <name>recv(SslSocket, Length, Timeout) -> {ok, Data} | {error,
+ <name since="">recv(SslSocket, Length) -> </name>
+ <name since="">recv(SslSocket, Length, Timeout) -> {ok, Data} | {error,
Reason}</name>
<fsummary>Receives data on a socket.</fsummary>
<type>
@@ -1407,7 +1423,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>renegotiate(SslSocket) -> ok | {error, Reason}</name>
+ <name since="OTP R14B">renegotiate(SslSocket) -> ok | {error, Reason}</name>
<fsummary>Initiates a new handshake.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1420,7 +1436,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>send(SslSocket, Data) -> ok | {error, Reason}</name>
+ <name since="">send(SslSocket, Data) -> ok | {error, Reason}</name>
<fsummary>Writes data to a socket.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1434,7 +1450,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>setopts(SslSocket, Options) -> ok | {error, Reason}</name>
+ <name since="">setopts(SslSocket, Options) -> ok | {error, Reason}</name>
<fsummary>Sets socket options.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1447,7 +1463,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>set_log_level(Level) -> ok | {error, Reason}</name>
+ <name since="OTP 22.0">set_log_level(Level) -> ok | {error, Reason}</name>
<fsummary>Sets log level for the SSL application.</fsummary>
<type>
<v>Level = atom()</v>
@@ -1458,7 +1474,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>shutdown(SslSocket, How) -> ok | {error, Reason}</name>
+ <name since="OTP R14B">shutdown(SslSocket, How) -> ok | {error, Reason}</name>
<fsummary>Immediately closes a socket.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1476,8 +1492,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>ssl_accept(SslSocket) -> </name>
- <name>ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name>
+ <name since="">ssl_accept(SslSocket) -> </name>
+ <name since="">ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS handshake.</fsummary>
<type>
<v>SslSocket = sslsocket()</v>
@@ -1491,8 +1507,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>ssl_accept(Socket, SslOptions) -> </name>
- <name>ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name>
+ <name since="">ssl_accept(Socket, SslOptions) -> </name>
+ <name since="OTP R14B">ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name>
<fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary>
<type>
<v>Socket = socket() | sslsocket() </v>
@@ -1507,7 +1523,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>sockname(SslSocket) -> {ok, {Address, Port}} |
+ <name since="">sockname(SslSocket) -> {ok, {Address, Port}} |
{error, Reason}</name>
<fsummary>Returns the local address and port.</fsummary>
<type>
@@ -1522,8 +1538,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
+ <name since="OTP R14B">start() -> </name>
+ <name since="OTP R14B">start(Type) -> ok | {error, Reason}</name>
<fsummary>Starts the SSL application.</fsummary>
<type>
<v>Type = permanent | transient | temporary</v>
@@ -1535,7 +1551,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>stop() -> ok </name>
+ <name since="OTP R14B">stop() -> ok </name>
<fsummary>Stops the SSL application.</fsummary>
<desc>
<p>Stops the SSL application.</p>
@@ -1543,7 +1559,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>suite_to_str(CipherSuite) -> String</name>
+ <name since="OTP 21.0">suite_to_str(CipherSuite) -> String</name>
<fsummary>Returns the string representation of a cipher suite.</fsummary>
<type>
<v>CipherSuite = erl_cipher_suite()</v>
@@ -1555,8 +1571,8 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>transport_accept(ListenSocket) -></name>
- <name>transport_accept(ListenSocket, Timeout) ->
+ <name since="">transport_accept(ListenSocket) -></name>
+ <name since="">transport_accept(ListenSocket, Timeout) ->
{ok, SslSocket} | {error, Reason}</name>
<fsummary>Accepts an incoming connection and
prepares for <c>ssl_accept</c>.</fsummary>
@@ -1590,7 +1606,7 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
- <name>versions() -> [versions_info()]</name>
+ <name since="OTP R14B">versions() -> [versions_info()]</name>
<fsummary>Returns version information relevant for the
SSL application.</fsummary>
<type>
diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml
index f6d9021d4a..893919aeb4 100644
--- a/lib/ssl/doc/src/ssl_app.xml
+++ b/lib/ssl/doc/src/ssl_app.xml
@@ -171,6 +171,20 @@
shutdown gracefully. Defaults to 5000 milliseconds.
</p>
</item>
+
+ <tag><c><![CDATA[internal_active_n = integer() <optional>]]></c></tag>
+ <item>
+ <p>
+ For TLS connections this value is used to handle the
+ internal socket. As the implementation was changed from an
+ active once to an active N behavior (N = 100), for
+ performance reasons, this option exist for possible tweaking
+ or restoring of the old behavior (internal_active_n = 1) in
+ unforeseen scenarios. The option will not affect erlang
+ distribution over TLS that will always run in active N mode.
+ Added in ssl-9.1 (OTP-21.2).
+ </p>
+ </item>
</taglist>
</section>
diff --git a/lib/ssl/doc/src/ssl_crl_cache.xml b/lib/ssl/doc/src/ssl_crl_cache.xml
index 71c6d5e49e..b766cfd2d9 100644
--- a/lib/ssl/doc/src/ssl_crl_cache.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache.xml
@@ -24,7 +24,7 @@
<file>ssl_crl_cache.xml</file>
</header>
- <module>ssl_crl_cache</module>
+ <module since="OTP 18.0">ssl_crl_cache</module>
<modulesummary>CRL cache </modulesummary>
<description>
<p>
@@ -37,7 +37,7 @@
<funcs>
<func>
- <name>delete(Entries) -> ok | {error, Reason} </name>
+ <name since="OTP 18.0">delete(Entries) -> ok | {error, Reason} </name>
<fsummary> </fsummary>
<type>
<v> Entries = <seealso marker="stdlib:uri_string">uri_string:uri_string()</seealso> | {file, string()} | {der, [<seealso
@@ -49,8 +49,8 @@
</desc>
</func>
<func>
- <name>insert(CRLSrc) -> ok | {error, Reason}</name>
- <name>insert(URI, CRLSrc) -> ok | {error, Reason}</name>
+ <name since="OTP 18.0">insert(CRLSrc) -> ok | {error, Reason}</name>
+ <name since="OTP 18.0">insert(URI, CRLSrc) -> ok | {error, Reason}</name>
<fsummary> </fsummary>
<type>
<v> CRLSrc = {file, string()} | {der, [ <seealso
diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml
index c6774b4df6..c7e501867f 100644
--- a/lib/ssl/doc/src/ssl_crl_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml
@@ -24,7 +24,7 @@
<file>ssl_crl_cache_api.xml</file>
</header>
- <module>ssl_crl_cache_api</module>
+ <module since="OTP 18.0">ssl_crl_cache_api</module>
<modulesummary>API for a SSL/TLS CRL (Certificate Revocation List) cache.</modulesummary>
<description>
<p>
@@ -59,7 +59,7 @@
</section>
<funcs>
<func>
- <name>fresh_crl(DistributionPoint, CRL) -> FreshCRL</name>
+ <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL</name>
<fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to
public_key:pkix_crls_validate/3 </fsummary>
<type>
@@ -76,8 +76,8 @@
</func>
<func>
- <name>lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
- <name>lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
+ <name since="OTP 19.0">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name>
+ <name since="OTP 18.0">lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name>
<fsummary> </fsummary>
<type>
<v> DistributionPoint = dist_point() </v>
@@ -106,7 +106,7 @@
</func>
<func>
- <name>select(Issuer, DbHandle) -> CRLs </name>
+ <name since="OTP 18.0">select(Issuer, DbHandle) -> CRLs </name>
<fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary>
<type>
<v> Issuer = <seealso
diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml
index a84a3dfce9..463cf15309 100644
--- a/lib/ssl/doc/src/ssl_session_cache_api.xml
+++ b/lib/ssl/doc/src/ssl_session_cache_api.xml
@@ -28,7 +28,7 @@
<rev></rev>
<file>ssl_session_cache_api.xml</file>
</header>
- <module>ssl_session_cache_api</module>
+ <module since="OTP R14B">ssl_session_cache_api</module>
<modulesummary>TLS session cache API</modulesummary>
<description>
@@ -66,7 +66,7 @@
<funcs>
<func>
- <name>delete(Cache, Key) -> _</name>
+ <name since="OTP R14B">delete(Cache, Key) -> _</name>
<fsummary>Deletes a cache entry.</fsummary>
<type>
<v>Cache = cache_ref()</v>
@@ -80,7 +80,7 @@
</func>
<func>
- <name>foldl(Fun, Acc0, Cache) -> Acc</name>
+ <name since="OTP R14B">foldl(Fun, Acc0, Cache) -> Acc</name>
<fsummary></fsummary>
<type>
<v></v>
@@ -96,7 +96,7 @@
</func>
<func>
- <name>init(Args) -> opaque() </name>
+ <name since="OTP 18.0">init(Args) -> opaque() </name>
<fsummary>Returns cache reference.</fsummary>
<type>
<v>Args = proplists:proplist()</v>
@@ -121,7 +121,7 @@
</func>
<func>
- <name>lookup(Cache, Key) -> Entry</name>
+ <name since="OTP R14B">lookup(Cache, Key) -> Entry</name>
<fsummary>Looks up a cache entry.</fsummary>
<type>
<v>Cache = cache_ref()</v>
@@ -136,7 +136,7 @@
</func>
<func>
- <name>select_session(Cache, PartialKey) -> [session()]</name>
+ <name since="OTP R14B">select_session(Cache, PartialKey) -> [session()]</name>
<fsummary>Selects sessions that can be reused.</fsummary>
<type>
<v>Cache = cache_ref()</v>
@@ -151,7 +151,7 @@
</func>
<func>
- <name>size(Cache) -> integer()</name>
+ <name since="OTP 19.3">size(Cache) -> integer()</name>
<fsummary>Returns the number of sessions in the cache.</fsummary>
<type>
<v>Cache = cache_ref()</v>
@@ -166,7 +166,7 @@
</func>
<func>
- <name>terminate(Cache) -> _</name>
+ <name since="OTP R14B">terminate(Cache) -> _</name>
<fsummary>Called by the process that handles the cache when it
is about to terminate.</fsummary>
<type>
@@ -180,7 +180,7 @@
</func>
<func>
- <name>update(Cache, Key, Session) -> _</name>
+ <name since="OTP R14B">update(Cache, Key, Session) -> _</name>
<fsummary>Caches a new session or updates an already cached one.</fsummary>
<type>
<v>Cache = cache_ref()</v>
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index 8ed4505256..cbd5c8e0a9 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -40,7 +40,7 @@
-export([start_fsm/8, start_link/7, init/1, pids/1]).
%% State transition handling
--export([next_record/1, next_event/3, next_event/4, handle_common_event/4]).
+-export([next_event/3, next_event/4, handle_protocol_record/3]).
%% Handshake handling
-export([renegotiate/2, send_handshake/2,
@@ -51,7 +51,7 @@
-export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
%% Data handling
--export([encode_data/3, passive_receive/2, next_record_if_active/1,
+-export([encode_data/3, next_record/1,
send/3, socket/5, setopts/3, getopts/3]).
%% gen_statem state functions
@@ -108,9 +108,11 @@ pids(_) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
next_record(#state{protocol_buffers =
#protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]}
= Buffers,
@@ -142,14 +144,14 @@ next_record(#state{protocol_buffers =
next_record(State#state{protocol_buffers =
Buffers#protocol_buffers{dtls_cipher_texts = Rest},
connection_states = ConnectionStates});
-next_record(#state{role = server,
- socket = {Listener, {Client, _}}} = State) ->
+next_record(#state{static_env = #static_env{role = server,
+ socket = {Listener, {Client, _}}}} = State) ->
dtls_packet_demux:active_once(Listener, Client, self()),
{no_record, State};
-next_record(#state{role = client,
- socket = {_Server, Socket} = DTLSSocket,
- close_tag = CloseTag,
- transport_cb = Transport} = State) ->
+next_record(#state{static_env = #static_env{role = client,
+ socket = {_Server, Socket} = DTLSSocket,
+ close_tag = CloseTag,
+ transport_cb = Transport}} = State) ->
case dtls_socket:setopts(Transport, Socket, [{active,once}]) of
ok ->
{no_record, State};
@@ -163,9 +165,9 @@ next_record(State) ->
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
-next_event(connection = StateName, no_record,
+next_event(StateName, no_record,
#state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
- case next_record_if_active(State0) of
+ case next_record(State0) of
{no_record, State} ->
ssl_connection:hibernate_after(StateName, State, Actions);
{#ssl_tls{epoch = CurrentEpoch,
@@ -179,21 +181,18 @@ next_event(connection = StateName, no_record,
{#ssl_tls{epoch = Epoch,
type = ?HANDSHAKE,
version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- {NextRecord, State} = next_record(State2),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
%% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
{#ssl_tls{epoch = Epoch,
type = ?CHANGE_CIPHER_SPEC,
version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 ->
- {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
- {NextRecord, State} = next_record(State2),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
{#ssl_tls{epoch = _Epoch,
- version = _Version}, State1} ->
+ version = _Version}, State} ->
%% TODO maybe buffer later epoch
- {Record, State} = next_record(State1),
- next_event(StateName, Record, State, Actions);
+ next_event(StateName, no_record, State, Actions);
{#alert{} = Alert, State} ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end;
@@ -211,24 +210,20 @@ next_event(connection = StateName, Record,
#ssl_tls{epoch = Epoch,
type = ?HANDSHAKE,
version = _Version} when Epoch == CurrentEpoch-1 ->
- {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- {NextRecord, State} = next_record(State1),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
%% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake
#ssl_tls{epoch = Epoch,
type = ?CHANGE_CIPHER_SPEC,
version = _Version} when Epoch == CurrentEpoch-1 ->
- {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
- {NextRecord, State} = next_record(State1),
- next_event(StateName, NextRecord, State, Actions ++ MoreActions);
+ {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch),
+ next_event(StateName, no_record, State, Actions ++ MoreActions);
_ ->
next_event(StateName, no_record, State0, Actions)
end;
next_event(StateName, Record,
#state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) ->
case Record of
- no_record ->
- {next_state, StateName, State0, Actions};
#ssl_tls{epoch = CurrentEpoch,
version = Version} = Record ->
State = dtls_version(StateName, Version, State0),
@@ -237,43 +232,48 @@ next_event(StateName, Record,
#ssl_tls{epoch = _Epoch,
version = _Version} = _Record ->
%% TODO maybe buffer later epoch
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State, Actions);
+ next_event(StateName, no_record, State0, Actions);
#alert{} = Alert ->
{next_state, StateName, State0, [{next_event, internal, Alert} | Actions]}
end.
-handle_common_event(internal, #alert{} = Alert, StateName,
- #state{negotiated_version = Version} = State) ->
- handle_own_alert(Alert, Version, StateName, State);
+%%% DTLS record protocol level application data messages
+
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) ->
+ case ssl_connection:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ end;
%%% DTLS record protocol level handshake messages
-handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE,
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE,
fragment = Data},
StateName,
#state{protocol_buffers = Buffers0,
- negotiated_version = Version} = State0) ->
+ negotiated_version = Version} = State) ->
try
case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of
{[], Buffers} ->
- {Record, State} = next_record(State0#state{protocol_buffers = Buffers}),
- next_event(StateName, Record, State);
+ next_event(StateName, no_record, State#state{protocol_buffers = Buffers});
{Packets, Buffers} ->
- State = State0#state{protocol_buffers = Buffers},
+ HsEnv = State#state.handshake_env,
Events = dtls_handshake_events(Packets),
{next_state, StateName,
- State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
end
catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0)
+ handle_own_alert(Alert, Version, StateName, State)
end;
-%%% DTLS record protocol level application data messages
-handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]};
%%% DTLS record protocol level change cipher messages
-handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
{next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
%%% DTLS record protocol level Alert messages
-handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
#state{negotiated_version = Version} = State) ->
case decode_alerts(EncAlerts) of
Alerts = [_|_] ->
@@ -282,31 +282,30 @@ handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, Sta
handle_own_alert(Alert, Version, StateName, State)
end;
%% Ignore unknown TLS record level protocol messages
-handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State}.
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
%%====================================================================
%% Handshake handling
%%====================================================================
-renegotiate(#state{role = client} = State, Actions) ->
+renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
{next_state, connection, State,
[{next_event, internal, #hello_request{}} | Actions]};
-renegotiate(#state{role = server} = State0, Actions) ->
+renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
State1 = prepare_flight(State0),
- {State2, MoreActions} = send_handshake(HelloRequest, State1),
- {Record, State} = next_record(State2),
- next_event(hello, Record, State, Actions ++ MoreActions).
+ {State, MoreActions} = send_handshake(HelloRequest, State1),
+ next_event(hello, no_record, State, Actions ++ MoreActions).
send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) ->
#{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write),
send_handshake_flight(queue_handshake(Handshake, State), Epoch).
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
negotiated_version = Version,
flight_buffer = #{handshakes := HsBuffer0,
change_cipher_spec := undefined,
@@ -315,9 +314,9 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist};
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
-queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
+queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
negotiated_version = Version,
flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0,
next_sequence := Seq} = Flight0} = State) ->
@@ -325,7 +324,7 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0,
Hist = update_handshake_history(Handshake0, Handshake, Hist0),
State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0],
next_sequence => Seq +1},
- tls_handshake_history = Hist}.
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}.
queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
connection_states = ConnectionStates0} = State) ->
@@ -337,10 +336,11 @@ queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight,
reinit(State) ->
%% To be API compatible with TLS NOOP here
reinit_handshake_data(State).
-reinit_handshake_data(#state{protocol_buffers = Buffers} = State) ->
+reinit_handshake_data(#state{protocol_buffers = Buffers,
+ handshake_env = HsEnv} = State) ->
State#state{premaster_secret = undefined,
public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history(),
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history()},
flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
flight_buffer = new_flight(),
protocol_buffers =
@@ -366,8 +366,8 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
dtls_record:encode_alert_record(Alert, Version, ConnectionStates).
send_alert(Alert, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
+ static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
connection_states = ConnectionStates0} = State0) ->
{BinMsg, ConnectionStates} =
encode_alert(Alert, Version, ConnectionStates0),
@@ -394,23 +394,6 @@ protocol_name() ->
encode_data(Data, Version, ConnectionStates0)->
dtls_record:encode_data(Data, Version, ConnectionStates0).
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
- case Buffer of
- <<>> ->
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State);
- _ ->
- {Record, State} = ssl_connection:read_application_data(<<>>, State0),
- next_event(StateName, Record, State)
- end.
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
-
-next_record_if_active(State) ->
- next_record(State).
-
send(Transport, {_, {{_,_}, _} = Socket}, Data) ->
send(Transport, Socket, Data);
send(Transport, Socket, Data) ->
@@ -436,13 +419,15 @@ getopts(Transport, Socket, Tag) ->
init(enter, _, State) ->
{keep_state, State};
init({call, From}, {start, Timeout},
- #state{host = Host, port = Port, role = client,
+ #state{static_env = #static_env{host = Host,
+ port = Port,
+ role = client,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
@@ -461,7 +446,8 @@ init({call, From}, {start, Timeout},
},
{Record, State} = next_record(State3),
next_event(hello, Record, State, Actions);
-init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->
+init({call, _} = Type, Event, #state{static_env = #static_env{role = server,
+ data_tag = udp}} = State) ->
Result = gen_handshake(?FUNCTION_NAME, Type, Event,
State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT},
protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(),
@@ -470,8 +456,7 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) ->
max_ignored_alerts => 10}}),
erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret),
Result;
-
-init({call, _} = Type, Event, #state{role = server} = State) ->
+init({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) ->
%% I.E. DTLS over sctp
gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable});
init(Type, Event, State) ->
@@ -486,8 +471,8 @@ error(enter, _, State) ->
{keep_state, State};
error({call, From}, {start, _Timeout},
#state{protocol_specific = #{error := Error}} = State) ->
- ssl_connection:stop_and_reply(
- normal, {reply, From, {error, Error}}, State);
+ {stop_and_reply, {shutdown, normal},
+ [{reply, From, {error, Error}}], State};
error({call, _} = Call, Msg, State) ->
gen_handshake(?FUNCTION_NAME, Call, Msg, State);
error(_, _, _) ->
@@ -499,16 +484,17 @@ error(_, _, _) ->
#state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-hello(enter, _, #state{role = server} = State) ->
+hello(enter, _, #state{static_env = #static_env{role = server}} = State) ->
{keep_state, State};
-hello(enter, _, #state{role = client} = State0) ->
+hello(enter, _, #state{static_env = #static_env{role = client}} = State0) ->
{State, Actions} = handle_flight_timer(State0),
{keep_state, State, Actions};
hello(internal, #client_hello{cookie = <<>>,
client_version = Version} = Hello,
- #state{role = server,
- transport_cb = Transport,
- socket = Socket,
+ #state{static_env = #static_env{role = server,
+ transport_cb = Transport,
+ socket = Socket},
+ handshake_env = HsEnv,
protocol_specific = #{current_cookie_secret := Secret}} = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello),
@@ -522,33 +508,40 @@ hello(internal, #client_hello{cookie = <<>>,
State1 = prepare_flight(State0#state{negotiated_version = Version}),
{State2, Actions} = send_handshake(VerifyRequest, State1),
{Record, State} = next_record(State2),
- next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions);
-hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client,
- host = Host, port = Port,
+ next_event(?FUNCTION_NAME, Record,
+ State#state{handshake_env = HsEnv#handshake_env{
+ tls_handshake_history =
+ ssl_handshake:init_handshake_history()}},
+ Actions);
+hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
ssl_options = SslOpts,
session = #session{own_certificate = OwnCert}
= Session0,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0,
SslOpts,
Cache, CacheCb, Renegotiation, OwnCert),
Version = Hello#client_hello.client_version,
- State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}),
+ State1 = prepare_flight(State0#state{handshake_env =
+ HsEnv#handshake_env{tls_handshake_history
+ = ssl_handshake:init_handshake_history()}}),
{State2, Actions} = send_handshake(Hello, State1),
- State3 = State2#state{negotiated_version = Version, %% Requested version
- session =
- Session0#session{session_id =
- Hello#client_hello.session_id}},
- {Record, State} = next_record(State3),
- next_event(?FUNCTION_NAME, Record, State, Actions);
-hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello},
- start_or_recv_from = From} = State) ->
+ State = State2#state{negotiated_version = Version, %% Requested version
+ session =
+ Session0#session{session_id =
+ Hello#client_hello.session_id}},
+ next_event(?FUNCTION_NAME, no_record, State, Actions);
+hello(internal, #client_hello{extensions = Extensions} = Hello,
+ #state{ssl_options = #ssl_options{handshake = hello},
+ start_or_recv_from = From} = State) ->
{next_state, user_hello, State#state{start_or_recv_from = undefined,
hello = Hello},
[{reply, From, {ok, Extensions}}]};
@@ -557,11 +550,12 @@ hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_optio
{next_state, user_hello, State#state{start_or_recv_from = undefined,
hello = Hello},
[{reply, From, {ok, Extensions}}]};
-hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server,
- transport_cb = Transport,
- socket = Socket,
+hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server,
+ transport_cb = Transport,
+ socket = Socket},
protocol_specific = #{current_cookie_secret := Secret,
- previous_cookie_secret := PSecret}} = State0) ->
+ previous_cookie_secret := PSecret}
+ } = State0) ->
{ok, {IP, Port}} = dtls_socket:peername(Transport, Socket),
case dtls_handshake:cookie(Secret, IP, Port, Hello) of
Cookie ->
@@ -576,11 +570,12 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server,
end
end;
hello(internal, #server_hello{} = Hello,
- #state{connection_states = ConnectionStates0,
- negotiated_version = ReqVersion,
- role = client,
- renegotiation = {Renegotiation, _},
- ssl_options = SslOptions} = State) ->
+ #state{
+ static_env = #static_env{role = client},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
+ connection_states = ConnectionStates0,
+ negotiated_version = ReqVersion,
+ ssl_options = SslOptions} = State) ->
case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State);
@@ -596,8 +591,7 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) ->
{next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]};
hello(internal, #change_cipher_spec{type = <<1>>}, State0) ->
{State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {Record, State2} = next_record(State1),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0),
+ {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
hello(info, Event, State) ->
@@ -648,8 +642,7 @@ certify(internal = Type, #server_hello_done{} = Event, State) ->
ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE);
certify(internal, #change_cipher_spec{type = <<1>>}, State0) ->
{State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)),
- {Record, State2} = next_record(State1),
- {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0),
+ {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions};
certify(state_timeout, Event, State) ->
@@ -690,12 +683,17 @@ connection(enter, _, State) ->
{keep_state, State};
connection(info, Event, State) ->
gen_info(Event, ?FUNCTION_NAME, State);
-connection(internal, #hello_request{}, #state{host = Host, port = Port,
+connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb
+ },
+ handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}},
session = #session{own_certificate = Cert} = Session0,
- session_cache = Cache, session_cache_cb = CacheCb,
+
ssl_options = SslOpts,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}} = State0) ->
+ connection_states = ConnectionStates0
+ } = State0) ->
Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts,
Cache, CacheCb, Renegotiation, Cert),
@@ -709,16 +707,19 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port,
session = Session0#session{session_id
= Hello#client_hello.session_id}}),
next_event(hello, Record, State, Actions);
-connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) ->
+connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server},
+ allow_renegotiate = true} = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}},
+ {next_state, hello, State#state{allow_renegotiate = false,
+ handshake_env = #handshake_env{renegotiation = {true, peer}}},
[{next_event, internal, Hello}]};
-connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) ->
+connection(internal, #client_hello{}, #state{static_env = #static_env{role = server},
+ allow_renegotiate = false} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
State1 = send_alert(Alert, State0),
{Record, State} = ssl_connection:prepare_connection(State1, ?MODULE),
@@ -773,29 +774,35 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User,
end,
Monitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = ?MODULE,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ session_cache_cb = SessionCacheCb
+ },
+
+ #state{static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first}
+ },
+ socket_options = SocketOptions,
%% We do not want to save the password in the state so that
%% could be written in the clear into error logs.
ssl_options = SSLOptions#ssl_options{password = undefined},
session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
connection_states = ConnectionStates,
protocol_buffers = #protocol_buffers{},
user_application = {Monitor, User},
user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
start_or_recv_from = undefined,
- protocol_cb = ?MODULE,
flight_buffer = new_flight(),
flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}
}.
@@ -836,17 +843,18 @@ decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts
{Alert, State}
end.
-dtls_version(hello, Version, #state{role = server} = State) ->
+dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) ->
State#state{negotiated_version = Version}; %%Inital version
dtls_version(_,_, State) ->
State.
handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
+ static_env = #static_env{port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ session = #session{own_certificate = Cert} = Session0,
negotiated_protocol = CurrentProtocol,
key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State0) ->
@@ -865,7 +873,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
State = prepare_flight(State0#state{connection_states = ConnectionStates,
negotiated_version = Version,
hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
+ handshake_env = HsEnv#handshake_env{client_hello_version = ClientVersion},
session = Session,
negotiated_protocol = Protocol}),
@@ -876,19 +884,19 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello,
%% raw data from socket, unpack records
handle_info({Protocol, _, _, _, Data}, StateName,
- #state{data_tag = Protocol} = State0) ->
+ #state{static_env = #static_env{data_tag = Protocol}} = State0) ->
case next_dtls_record(Data, StateName, State0) of
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- ssl_connection:stop({shutdown, own_alert}, State0)
+ {stop, {shutdown, own_alert}, State0}
end;
handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket,
+ #state{static_env = #static_env{socket = Socket,
+ close_tag = CloseTag},
socket_options = #socket_options{active = Active},
protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs},
- close_tag = CloseTag,
negotiated_version = Version} = State) ->
%% Note that as of DTLS 1.2 (TLS 1.1),
%% failure to properly close a connection no longer requires that a
@@ -907,7 +915,7 @@ handle_info({CloseTag, Socket}, StateName,
ok
end,
ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- ssl_connection:stop({shutdown, transport_closed}, State);
+ {stop, {shutdown, transport_closed}, State};
true ->
%% Fixes non-delivery of final DTLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
@@ -928,8 +936,7 @@ handle_state_timeout(flight_retransmission_timeout, StateName,
#state{flight_state = {retransmit, NextTimeout}} = State0) ->
{State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}},
retransmit_epoch(StateName, State0)),
- {Record, State2} = next_record(State1),
- {next_state, StateName, State, Actions} = next_event(StateName, Record, State2, Actions0),
+ {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0),
%% This will reset the retransmission timer by repeating the enter state event
{repeat_state, State, Actions}.
@@ -942,8 +949,8 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)).
-handle_own_alert(Alert, Version, StateName, #state{data_tag = udp,
- role = Role,
+handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data_tag = udp,
+ role = Role},
ssl_options = Options} = State0) ->
case ignore_alert(Alert, State0) of
{true, State} ->
@@ -1033,10 +1040,10 @@ next_flight(Flight) ->
change_cipher_spec => undefined,
handshakes_after_change_cipher_spec => []}.
-handle_flight_timer(#state{data_tag = udp,
+handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
flight_state = {retransmit, Timeout}} = State) ->
start_retransmision_timer(Timeout, State);
-handle_flight_timer(#state{data_tag = udp,
+handle_flight_timer(#state{static_env = #static_env{data_tag = udp},
flight_state = connection} = State) ->
{State, []};
handle_flight_timer(State) ->
@@ -1052,8 +1059,8 @@ new_timeout(N) when N =< 30 ->
new_timeout(_) ->
60.
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := Flight,
change_cipher_spec := undefined},
negotiated_version = Version,
@@ -1064,8 +1071,8 @@ send_handshake_flight(#state{socket = Socket,
send(Transport, Socket, Encoded),
{State0#state{connection_states = ConnectionStates}, []};
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := [_|_] = Flight0,
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := []},
@@ -1078,8 +1085,8 @@ send_handshake_flight(#state{socket = Socket,
send(Transport, Socket, [HsBefore, EncChangeCipher]),
{State0#state{connection_states = ConnectionStates}, []};
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := [_|_] = Flight0,
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := Flight1},
@@ -1094,8 +1101,8 @@ send_handshake_flight(#state{socket = Socket,
send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]),
{State0#state{connection_states = ConnectionStates}, []};
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = #{handshakes := [],
change_cipher_spec := ChangeCipher,
handshakes_after_change_cipher_spec := Flight1},
@@ -1152,16 +1159,17 @@ log_ignore_alert(_, _, _, _) ->
ok.
send_application_data(Data, From, _StateName,
- #state{socket = Socket,
+ #state{static_env = #static_env{socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport},
+ handshake_env = HsEnv,
negotiated_version = Version,
- protocol_cb = Connection,
- transport_cb = Transport,
connection_states = ConnectionStates0,
ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
- renegotiate(State0#state{renegotiation = {true, internal}},
+ renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}},
[{next_event, {call, From}, {application_data, Data}}]);
false ->
{Msgs, ConnectionStates} =
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index 3dbda2c91b..eb0f742e70 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -214,8 +214,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, dtls_v1:corresponding_tls_version(Version),
SslOpts, Session0,
ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign}
catch throw:Alert ->
@@ -224,17 +222,16 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
- Compression, HelloExt,
- dtls_v1:corresponding_tls_version(Version),
- SslOpt, ConnectionStates0, Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite,
+ Compression, HelloExt,
+ dtls_v1:corresponding_tls_version(Version),
+ SslOpt, ConnectionStates0, Renegotiation) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ catch throw:Alert ->
+ Alert
end.
-
%%--------------------------------------------------------------------
enc_handshake(#hello_verify_request{protocol_version = {Major, Minor},
@@ -343,8 +340,9 @@ decode_handshake(Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_),
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
TLSVersion = dtls_v1:corresponding_tls_version(Version),
+ LegacyVersion = dtls_v1:corresponding_tls_version({Major, Minor}),
Exts = ssl_handshake:decode_vector(Extensions),
- DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, TLSVersion, client),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, TLSVersion, LegacyVersion, client),
#client_hello{
client_version = {Major,Minor},
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index 2c3f8bc20f..616e9e26e7 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -942,8 +942,6 @@ handle_options(Opts0, Role, Host) ->
{list, [{mode, list}]}], Opts0),
assert_proplist(Opts),
RecordCb = record_cb(Opts),
-
- ReuseSessionFun = fun(_, _, _, _) -> true end,
CaCerts = handle_option(cacerts, Opts, undefined),
{Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} =
@@ -1014,9 +1012,8 @@ handle_options(Opts0, Role, Host) ->
Opts,
undefined), %% Do not send by default
tls_version(HighestVersion)),
- %% Server side option
- reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
- reuse_sessions = handle_option(reuse_sessions, Opts, true),
+ reuse_sessions = handle_reuse_sessions_option(reuse_sessions, Opts, Role),
+ reuse_session = handle_reuse_session_option(reuse_session, Opts, Role),
secure_renegotiate = handle_option(secure_renegotiate, Opts, true),
client_renegotiation = handle_option(client_renegotiation, Opts,
default_option_role(server, true, Role),
@@ -1211,11 +1208,16 @@ validate_option(srp_identity, {Username, Password})
{unicode:characters_to_binary(Username),
unicode:characters_to_binary(Password)};
+validate_option(reuse_session, undefined) ->
+ undefined;
validate_option(reuse_session, Value) when is_function(Value) ->
Value;
+validate_option(reuse_session, Value) when is_binary(Value) ->
+ Value;
validate_option(reuse_sessions, Value) when is_boolean(Value) ->
Value;
-
+validate_option(reuse_sessions, save = Value) ->
+ Value;
validate_option(secure_renegotiate, Value) when is_boolean(Value) ->
Value;
validate_option(client_renegotiation, Value) when is_boolean(Value) ->
@@ -1374,6 +1376,26 @@ handle_signature_algorithms_option(Value, Version) when is_list(Value)
handle_signature_algorithms_option(_, _Version) ->
undefined.
+handle_reuse_sessions_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_sessions_option(Key, Opts0, server) ->
+ Opts = proplists:delete({Key, save}, Opts0),
+ Value = proplists:get_value(Key, Opts, true),
+ validate_option(Key, Value),
+ Value.
+
+handle_reuse_session_option(Key, Opts, client) ->
+ Value = proplists:get_value(Key, Opts, undefined),
+ validate_option(Key, Value),
+ Value;
+handle_reuse_session_option(Key, Opts, server) ->
+ ReuseSessionFun = fun(_, _, _, _) -> true end,
+ Value = proplists:get_value(Key, Opts, ReuseSessionFun),
+ validate_option(Key, Value),
+ Value.
+
validate_options([]) ->
[];
validate_options([{Opt, Value} | Tail]) ->
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index c4b8e2172a..d08b2cc7ad 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -34,7 +34,7 @@
-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/3,
+-export([security_parameters/2, security_parameters/3, security_parameters_1_3/2,
cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, 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,
@@ -44,10 +44,11 @@
hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1,
random_bytes/1, calc_mac_hash/4,
is_stream_ciphersuite/1, signature_scheme/1,
- scheme_to_components/1, hash_size/1]).
+ scheme_to_components/1, hash_size/1, effective_key_bits/1,
+ key_material/1]).
%% RFC 8446 TLS 1.3
--export([generate_client_shares/1, generate_server_share/1]).
+-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]).
-compile(inline).
@@ -88,23 +89,14 @@ security_parameters(Version, CipherSuite, SecParams) ->
prf_algorithm = prf_algorithm(PrfHashAlg, Version),
hash_size = hash_size(Hash)}.
-security_parameters_1_3(SecParams, ClientRandom, CipherSuite) ->
- #{cipher := Cipher,
- mac := Hash,
- prf := PrfHashAlg} = ssl_cipher_format:suite_definition(CipherSuite),
+security_parameters_1_3(SecParams, CipherSuite) ->
+ #{cipher := Cipher, prf := PrfHashAlg} =
+ ssl_cipher_format:suite_definition(CipherSuite),
SecParams#security_parameters{
- client_random = ClientRandom,
cipher_suite = CipherSuite,
bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
- cipher_type = type(Cipher),
- key_size = effective_key_bits(Cipher),
- expanded_key_material_length = expanded_key_material(Cipher),
- key_material_length = key_material(Cipher),
- iv_size = iv_size(Cipher),
- mac_algorithm = mac_algorithm(Hash),
- prf_algorithm =prf_algorithm(PrfHashAlg, {3,4}),
- hash_size = hash_size(Hash),
- compression_algorithm = 0}.
+ prf_algorithm = PrfHashAlg, %% HKDF hash algorithm
+ cipher_type = ?AEAD}.
%%--------------------------------------------------------------------
-spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}.
@@ -578,7 +570,8 @@ crypto_support_filters() ->
end]}.
is_acceptable_keyexchange(KeyExchange, _Algos) when KeyExchange == psk;
- KeyExchange == null ->
+ KeyExchange == null;
+ KeyExchange == any ->
true;
is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == dh_anon;
KeyExchange == dhe_psk ->
@@ -621,7 +614,7 @@ is_acceptable_cipher(rc4_128, Algos) ->
is_acceptable_cipher(des_cbc, Algos) ->
proplists:get_bool(des_cbc, Algos);
is_acceptable_cipher('3des_ede_cbc', Algos) ->
- proplists:get_bool(des3_cbc, Algos);
+ proplists:get_bool(des_ede3, Algos);
is_acceptable_cipher(aes_128_cbc, Algos) ->
proplists:get_bool(aes_cbc128, Algos);
is_acceptable_cipher(aes_256_cbc, Algos) ->
@@ -690,10 +683,9 @@ hash_size(sha) ->
hash_size(sha256) ->
32;
hash_size(sha384) ->
- 48.
-%% Uncomment when adding cipher suite that needs it
-%hash_size(sha512) ->
-% 64.
+ 48;
+hash_size(sha512) ->
+ 64.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -897,8 +889,8 @@ scheme_to_components(ecdsa_secp521r1_sha512) -> {sha512, ecdsa, secp521r1};
scheme_to_components(rsa_pss_rsae_sha256) -> {sha256, rsa_pss_rsae, undefined};
scheme_to_components(rsa_pss_rsae_sha384) -> {sha384, rsa_pss_rsae, undefined};
scheme_to_components(rsa_pss_rsae_sha512) -> {sha512, rsa_pss_rsae, undefined};
-%% scheme_to_components(ed25519) -> {undefined, undefined, undefined};
-%% scheme_to_components(ed448) -> {undefined, undefined, undefined};
+scheme_to_components(ed25519) -> {undefined, undefined, undefined};
+scheme_to_components(ed448) -> {undefined, undefined, undefined};
scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined};
scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined};
scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined};
@@ -972,7 +964,7 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) ->
is_correct_padding(#generic_block_cipher{padding_length = Len,
padding = Padding}, _, _) ->
Len == byte_size(Padding) andalso
- list_to_binary(lists:duplicate(Len, Len)) == Padding.
+ binary:copy(?byte(Len), Len) == Padding.
get_padding(Length, BlockSize) ->
get_padding_aux(BlockSize, Length rem BlockSize).
@@ -981,7 +973,7 @@ get_padding_aux(_, 0) ->
{0, <<>>};
get_padding_aux(BlockSize, PadLength) ->
N = BlockSize - PadLength,
- {N, list_to_binary(lists:duplicate(N, N))}.
+ {N, binary:copy(?byte(N), N)}.
random_iv(IV) ->
IVSz = byte_size(IV),
@@ -1240,5 +1232,24 @@ generate_key_exchange(secp384r1) ->
public_key:generate_key({namedCurve, secp384r1});
generate_key_exchange(secp521r1) ->
public_key:generate_key({namedCurve, secp521r1});
+generate_key_exchange(x25519) ->
+ crypto:generate_key(ecdh, x25519);
+generate_key_exchange(x448) ->
+ crypto:generate_key(ecdh, x448);
generate_key_exchange(FFDHE) ->
public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)).
+
+
+%% TODO: Move this functionality to crypto!
+%% 7.4.1. Finite Field Diffie-Hellman
+%%
+%% For finite field groups, a conventional Diffie-Hellman [DH76]
+%% computation is performed. The negotiated key (Z) is converted to a
+%% byte string by encoding in big-endian form and left-padded with zeros
+%% up to the size of the prime. This byte string is used as the shared
+%% secret in the key schedule as specified above.
+add_zero_padding(Bin, PrimeSize)
+ when byte_size (Bin) =:= PrimeSize ->
+ Bin;
+add_zero_padding(Bin, PrimeSize) ->
+ add_zero_padding(<<0, Bin/binary>>, PrimeSize).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 2abc678ed9..af18ceb322 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -52,8 +52,8 @@
%% Alert and close handling
-export([handle_own_alert/4, handle_alert/3,
- handle_normal_shutdown/3, stop/2, stop_and_reply/3
- ]).
+ handle_normal_shutdown/3,
+ handle_trusted_certs_db/1]).
%% Data handling
-export([read_application_data/2, internal_renegotiation/2]).
@@ -336,8 +336,8 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
%% Alert and close handling
%%====================================================================
handle_own_alert(Alert, _, StateName,
- #state{role = Role,
- protocol_cb = Connection,
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
ssl_options = SslOpts} = State) ->
try %% Try to tell the other side
send_alert(Alert, StateName, State)
@@ -352,175 +352,217 @@ handle_own_alert(Alert, _, StateName,
catch _:_ ->
ok
end,
- stop({shutdown, own_alert}, State).
-
-handle_normal_shutdown(Alert, _, #state{socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- start_or_recv_from = StartFrom,
- tracker = Tracker,
- role = Role, renegotiation = {false, first}} = State) ->
+ {stop, {shutdown, own_alert}, State}.
+
+handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ tracker = Tracker},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection);
-handle_normal_shutdown(Alert, StateName, #state{socket = Socket,
- socket_options = Opts,
- transport_cb = Transport,
- protocol_cb = Connection,
- user_application = {_Mon, Pid},
- tracker = Tracker,
- start_or_recv_from = RecvFrom, role = Role} = State) ->
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ tracker = Tracker},
+ socket_options = Opts,
+ user_application = {_Mon, Pid},
+ start_or_recv_from = RecvFrom} = State) ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection).
handle_alert(#alert{level = ?FATAL} = Alert, StateName,
- #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection,
- ssl_options = SslOpts, start_or_recv_from = From, host = Host,
- port = Port, session = Session, user_application = {_Mon, Pid},
- role = Role, socket_options = Opts,
- tracker = Tracker} = State) ->
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ host = Host,
+ port = Port,
+ tracker = Tracker,
+ transport_cb = Transport,
+ protocol_cb = Connection},
+ ssl_options = SslOpts,
+ start_or_recv_from = From,
+ session = Session, user_application = {_Mon, Pid},
+ socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(),
StateName, Alert#alert{role = opposite_role(Role)}),
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection),
- stop(normal, State);
+ {stop, {shutdown, normal}, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- StateName, State) ->
+ downgrade= StateName, State) ->
+ {next_state, StateName, State, [{next_event, internal, Alert}]};
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ StateName, State) ->
handle_normal_shutdown(Alert, StateName, State),
- stop({shutdown, peer_close}, State);
-
+ {stop,{shutdown, peer_close}, State};
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection,
- renegotiation = {true, internal}} = State) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, internal}},
+ ssl_options = SslOpts} = State) ->
log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
handle_normal_shutdown(Alert, StateName, State),
- stop({shutdown, peer_close}, State);
+ {stop,{shutdown, peer_close}, State};
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName,
- #state{role = Role,
- ssl_options = SslOpts, renegotiation = {true, From},
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = SslOpts
+ } = State0) ->
log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
gen_statem:reply(From, {error, renegotiation_rejected}),
- State1 = Connection:reinit_handshake_data(State0),
- {Record, State} = Connection:next_record(State1#state{renegotiation = undefined}),
- Connection:next_event(connection, Record, State);
+ State = Connection:reinit_handshake_data(State0),
+ Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}});
handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
- #state{role = Role,
- ssl_options = SslOpts, renegotiation = {true, From},
- protocol_cb = Connection} = State0) ->
- log_alert(SslOpts#ssl_options.log_level, Role,
- Connection:protocol_name(), StateName,
- Alert#alert{role = opposite_role(Role)}),
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv,
+ ssl_options = SslOpts
+ } = State0) ->
+ log_alert(SslOpts#ssl_options.log_level, Role,
+ Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
gen_statem:reply(From, {error, renegotiation_rejected}),
- {Record, State1} = Connection:next_record(State0),
%% Go back to connection!
- State = Connection:reinit(State1#state{renegotiation = undefined}),
- Connection:next_event(connection, Record, State);
+ State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}),
+ Connection:next_event(connection, no_record, State);
%% Gracefully log and ignore all other warning alerts
handle_alert(#alert{level = ?WARNING} = Alert, StateName,
- #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) ->
+ #state{static_env = #static_env{role = Role,
+ protocol_cb = Connection},
+ ssl_options = SslOpts} = State) ->
log_alert(SslOpts#ssl_options.log_level, Role,
Connection:protocol_name(), StateName,
Alert#alert{role = opposite_role(Role)}),
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(StateName, Record, State).
+ Connection:next_event(StateName, no_record, State).
%%====================================================================
%% Data handling
%%====================================================================
-read_application_data(Data, #state{user_application = {_Mon, Pid},
- socket = Socket,
- protocol_cb = Connection,
- transport_cb = Transport,
- socket_options = SOpts,
- bytes_to_read = BytesToRead,
- start_or_recv_from = RecvFrom,
- timer = Timer,
- user_data_buffer = Buffer0,
- tracker = Tracker} = State0) ->
- Buffer1 = if
- Buffer0 =:= <<>> -> Data;
- Data =:= <<>> -> Buffer0;
- true -> <<Buffer0/binary, Data/binary>>
- end,
- case get_data(SOpts, BytesToRead, Buffer1) of
+passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) ->
+ case Buffer of
+ <<>> ->
+ {Record, State} = Connection:next_record(State0),
+ Connection:next_event(StateName, Record, State);
+ _ ->
+ case read_application_data(<<>>, State0) of
+ {stop, _, _} = ShutdownError ->
+ ShutdownError;
+ {Record, State} ->
+ Connection:next_event(StateName, Record, State)
+ end
+ end.
+
+read_application_data(
+ Data,
+ #state{
+ user_data_buffer = Buffer0,
+ erl_dist_handle = DHandle} = State) ->
+ %%
+ Buffer = bincat(Buffer0, Data),
+ case DHandle of
+ undefined ->
+ #state{
+ socket_options = SocketOpts,
+ bytes_to_read = BytesToRead,
+ start_or_recv_from = RecvFrom,
+ timer = Timer} = State,
+ read_application_data(
+ Buffer, State, SocketOpts, RecvFrom, Timer, BytesToRead);
+ _ ->
+ try read_application_dist_data(Buffer, State, DHandle)
+ catch error:_ ->
+ {stop,disconnect,
+ State#state{
+ user_data_buffer = Buffer,
+ bytes_to_read = undefined}}
+ end
+ end.
+
+read_application_dist_data(Buffer, State, DHandle) ->
+ case Buffer of
+ <<Size:32,Data:Size/binary>> ->
+ erlang:dist_ctrl_put_data(DHandle, Data),
+ {no_record,
+ State#state{
+ user_data_buffer = <<>>,
+ bytes_to_read = undefined}};
+ <<Size:32,Data:Size/binary,Rest/binary>> ->
+ erlang:dist_ctrl_put_data(DHandle, Data),
+ read_application_dist_data(Rest, State, DHandle);
+ _ ->
+ {no_record,
+ State#state{
+ user_data_buffer = Buffer,
+ bytes_to_read = undefined}}
+ end.
+
+read_application_data(
+ Buffer0, State, SocketOpts0, RecvFrom, Timer, BytesToRead) ->
+ %%
+ case get_data(SocketOpts0, BytesToRead, Buffer0) of
{ok, ClientData, Buffer} -> % Send data
- #state{ssl_options = #ssl_options{erl_dist = Dist},
- erl_dist_data = DistData} = State0,
- case Dist andalso is_dist_up(DistData) of
- true ->
- dist_app_data(ClientData, State0#state{user_data_buffer = Buffer,
- bytes_to_read = undefined});
- _ ->
- SocketOpt =
- deliver_app_data(Connection:pids(State0),
- Transport, Socket, SOpts,
- ClientData, Pid, RecvFrom, Tracker, Connection),
- cancel_timer(Timer),
- State =
- State0#state{
- user_data_buffer = Buffer,
- start_or_recv_from = undefined,
- timer = undefined,
- bytes_to_read = undefined,
- socket_options = SocketOpt
- },
- if
- SocketOpt#socket_options.active =:= false;
- Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- Connection:next_record_if_active(State);
- true -> %% We have more data
- read_application_data(<<>>, State)
- end
- end;
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ tracker = Tracker},
+ user_application = {_Mon, Pid}} = State,
+ SocketOpts =
+ deliver_app_data(
+ Connection:pids(State),
+ Transport, Socket, SocketOpts0,
+ ClientData, Pid, RecvFrom, Tracker, Connection),
+ cancel_timer(Timer),
+ if
+ SocketOpts#socket_options.active =:= false;
+ Buffer =:= <<>> ->
+ %% Passive mode, wait for active once or recv
+ %% Active and empty, get more data
+ {no_record,
+ State#state{
+ user_data_buffer = Buffer,
+ start_or_recv_from = undefined,
+ timer = undefined,
+ bytes_to_read = undefined,
+ socket_options = SocketOpts
+ }};
+ true -> %% We have more data
+ read_application_data(
+ Buffer, State, SocketOpts,
+ undefined, undefined, undefined)
+ end;
{more, Buffer} -> % no reply, we need more data
- Connection:next_record(State0#state{user_data_buffer = Buffer});
+ {no_record, State#state{user_data_buffer = Buffer}};
{passive, Buffer} ->
- Connection:next_record_if_active(State0#state{user_data_buffer = Buffer});
+ {no_record, State#state{user_data_buffer = Buffer}};
{error,_Reason} -> %% Invalid packet in packet mode
- deliver_packet_error(Connection:pids(State0),
- Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection),
- stop(normal, State0)
- end.
-
-dist_app_data(ClientData, #state{protocol_cb = Connection,
- erl_dist_data = #{dist_handle := undefined,
- dist_buffer := DistBuff} = DistData} = State) ->
- Connection:next_record_if_active(State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}});
-dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle,
- dist_buffer := DistBuff} = ErlDistData,
- protocol_cb = Connection,
- user_data_buffer = Buffer,
- socket_options = SOpts} = State) ->
- Data = merge_dist_data(DistBuff, ClientData),
- try erlang:dist_ctrl_put_data(DHandle, Data) of
- _ when SOpts#socket_options.active =:= false;
- Buffer =:= <<>> ->
- %% Passive mode, wait for active once or recv
- %% Active and empty, get more data
- Connection:next_record_if_active(State#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}});
- _ -> %% We have more data
- read_application_data(<<>>, State)
- catch error:_ ->
- stop(State, disconnect)
+ #state{
+ static_env =
+ #static_env{
+ socket = Socket,
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ tracker = Tracker},
+ user_application = {_Mon, Pid}} = State,
+ deliver_packet_error(
+ Connection:pids(State), Transport, Socket, SocketOpts0,
+ Buffer0, Pid, RecvFrom, Tracker, Connection),
+ {stop, {shutdown, normal}, State}
end.
-merge_dist_data(<<>>, ClientData) ->
- ClientData;
-merge_dist_data(DistBuff, <<>>) ->
- DistBuff;
-merge_dist_data(DistBuff, ClientData) ->
- [DistBuff, ClientData].
%%====================================================================
%% Help functions for tls|dtls_connection.erl
%%====================================================================
@@ -569,7 +611,8 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
ssl_config(Opts, Role, State) ->
ssl_config(Opts, Role, State, new).
-ssl_config(Opts, Role, State0, Type) ->
+ssl_config(Opts, Role, #state{static_env = InitStatEnv0,
+ handshake_env = HsEnv} = State0, Type) ->
{ok, #{cert_db_ref := Ref,
cert_db_handle := CertDbHandle,
fileref_db_handle := FileRefHandle,
@@ -581,20 +624,23 @@ ssl_config(Opts, Role, State0, Type) ->
ssl_config:init(Opts, Role),
TimeStamp = erlang:monotonic_time(),
Session = State0#state.session,
+
State = State0#state{session = Session#session{own_certificate = OwnCert,
time_stamp = TimeStamp},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
private_key = Key,
diffie_hellman_params = DHParams,
ssl_options = Opts},
case Type of
new ->
- Handshake = ssl_handshake:init_handshake_history(),
- State#state{tls_handshake_history = Handshake};
+ Hist = ssl_handshake:init_handshake_history(),
+ State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}};
continue ->
State
end.
@@ -612,11 +658,10 @@ ssl_config(Opts, Role, State0, Type) ->
init({call, From}, {start, Timeout}, State0, Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, From),
- {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From,
- timer = Timer}),
- Connection:next_event(hello, Record, State);
+ Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer});
init({call, From}, {start, {Opts, EmOpts}, Timeout},
- #state{role = Role, ssl_options = OrigSSLOptions,
+ #state{static_env = #static_env{role = Role},
+ ssl_options = OrigSSLOptions,
socket_options = SockOpts} = State0, Connection) ->
try
SslOpts = ssl:handle_options(Opts, OrigSSLOptions),
@@ -625,7 +670,7 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout},
State#state{ssl_options = SslOpts,
socket_options = new_emulated(EmOpts, SockOpts)}, Connection)
catch throw:Error ->
- stop_and_reply(normal, {reply, From, {error, Error}}, State0)
+ {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0}
end;
init({call, From}, {new_user, _} = Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
@@ -641,7 +686,7 @@ init(_Type, _Event, _State, _Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
error({call, From}, {close, _}, State, _Connection) ->
- stop_and_reply(normal, {reply, From, ok}, State);
+ {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State};
error({call, From}, _Msg, State, _Connection) ->
{next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}.
@@ -664,10 +709,11 @@ user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _
gen_statem:reply(From, ok),
handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled),
Version, ?FUNCTION_NAME, State);
-user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{hello = Hello,
- role = Role,
- start_or_recv_from = RecvFrom,
- ssl_options = Options0} = State0, _Connection) ->
+user_hello({call, From}, {handshake_continue, NewOptions, Timeout},
+ #state{hello = Hello,
+ static_env = #static_env{role = Role},
+ start_or_recv_from = RecvFrom,
+ ssl_options = Options0} = State0, _Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}),
State = ssl_config(Options, Role, State0, continue),
@@ -686,16 +732,16 @@ user_hello(_, _, _, _) ->
abbreviated({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{role = server,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
negotiated_version = Version,
expecting_finished = true,
- tls_handshake_history = Handshake,
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client,
get_current_prf(ConnectionStates0, write),
- MasterSecret, Handshake) of
+ MasterSecret, Hist) of
verified ->
ConnectionStates =
ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
@@ -706,13 +752,14 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
abbreviated(internal, #finished{verify_data = Data} = Finished,
- #state{role = client, tls_handshake_history = Handshake0,
+ #state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist0},
session = #session{master_secret = MasterSecret},
negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server,
get_pending_prf(ConnectionStates0, write),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist0) of
verified ->
ConnectionStates1 =
ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
@@ -727,20 +774,20 @@ abbreviated(internal, #finished{verify_data = Data} = Finished,
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{role = server, expecting_next_protocol_negotiation = true} = State0,
+ #state{static_env = #static_env{role = server},
+ expecting_next_protocol_negotiation = true} = State,
Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
- Connection:next_event(?FUNCTION_NAME, Record,
- State#state{expecting_next_protocol_negotiation = false});
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{negotiated_protocol = SelectedProtocol,
+ expecting_next_protocol_negotiation = false});
abbreviated(internal,
#change_cipher_spec{type = <<1>>},
- #state{connection_states = ConnectionStates0} = State0, Connection) ->
+ #state{connection_states = ConnectionStates0} = State, Connection) ->
ConnectionStates1 =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- {Record, State} = Connection:next_record(State0#state{connection_states =
- ConnectionStates1}),
- Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
+ ConnectionStates1,
+ expecting_finished = true});
abbreviated(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
abbreviated(Type, Msg, State, Connection) ->
@@ -758,34 +805,34 @@ certify({call, From}, Msg, State, Connection) ->
certify(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{role = server, negotiated_version = Version,
+ #state{static_env = #static_env{role = server},
+ negotiated_version = Version,
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = true}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{asn1_certificates = []},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = false}} =
State0, Connection) ->
- {Record, State} =
- Connection:next_record(State0#state{client_certificate_requested = false}),
- Connection:next_event(?FUNCTION_NAME, Record, State);
+ Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false});
certify(internal, #certificate{},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
negotiated_version = Version,
ssl_options = #ssl_options{verify = verify_none}} =
State, _) ->
Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate),
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
certify(internal, #certificate{} = Cert,
- #state{negotiated_version = Version,
- role = Role,
- host = Host,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
- crl_db = CRLDbInfo,
+ #state{static_env = #static_env{
+ role = Role,
+ host = Host,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ crl_db = CRLDbInfo},
+ negotiated_version = Version,
ssl_options = Opts} = State, Connection) ->
case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef,
Opts, CRLDbInfo, Role, Host) of
@@ -796,7 +843,8 @@ certify(internal, #certificate{} = Cert,
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
certify(internal, #server_key_exchange{exchange_keys = Keys},
- #state{role = client, negotiated_version = Version,
+ #state{static_env = #static_env{role = client},
+ negotiated_version = Version,
key_algorithm = Alg,
public_key_info = PubKeyInfo,
session = Session,
@@ -830,7 +878,8 @@ certify(internal, #server_key_exchange{exchange_keys = Keys},
end
end;
certify(internal, #certificate_request{},
- #state{role = client, negotiated_version = Version,
+ #state{static_env = #static_env{role = client},
+ negotiated_version = Version,
key_algorithm = Alg} = State, _)
when Alg == dh_anon; Alg == ecdh_anon;
Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk;
@@ -838,36 +887,34 @@ certify(internal, #certificate_request{},
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE),
Version, ?FUNCTION_NAME, State);
certify(internal, #certificate_request{},
- #state{session = #session{own_certificate = undefined},
- role = client} = State0, Connection) ->
+ #state{static_env = #static_env{role = client},
+ session = #session{own_certificate = undefined}} = State, Connection) ->
%% The client does not have a certificate and will send an empty reply, the server may fail
%% or accept the connection by its own preference. No signature algorihms needed as there is
%% no certificate to verify.
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(?FUNCTION_NAME, Record, State#state{client_certificate_requested = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true});
certify(internal, #certificate_request{} = CertRequest,
- #state{session = #session{own_certificate = Cert},
- role = client,
- ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
- negotiated_version = Version} = State0, Connection) ->
- case ssl_handshake:select_hashsign(CertRequest, Cert,
- SupportedHashSigns,
- ssl:tls_version(Version)) of
+ #state{static_env = #static_env{role = client},
+ session = #session{own_certificate = Cert},
+ ssl_options = #ssl_options{signature_algs = SupportedHashSigns},
+ negotiated_version = Version} = State, Connection) ->
+ case ssl_handshake:select_hashsign(CertRequest, Cert,
+ SupportedHashSigns, ssl:tls_version(Version)) of
#alert {} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0);
- NegotiatedHashSign ->
- {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}),
- Connection:next_event(?FUNCTION_NAME, Record,
- State#state{cert_hashsign_algorithm = NegotiatedHashSign})
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State);
+ NegotiatedHashSign ->
+ Connection:next_event(?FUNCTION_NAME, no_record,
+ State#state{client_certificate_requested = true,
+ cert_hashsign_algorithm = NegotiatedHashSign})
end;
%% PSK and RSA_PSK might bypass the Server-Key-Exchange
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = undefined},
+ #state{static_env = #static_env{role = client},
+ session = #session{master_secret = undefined},
negotiated_version = Version,
psk_identity = PSKIdentity,
ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
premaster_secret = undefined,
- role = client,
key_algorithm = Alg} = State0, Connection)
when Alg == psk ->
case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of
@@ -879,12 +926,12 @@ certify(internal, #server_hello_done{},
client_certify_and_key_exchange(State, Connection)
end;
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = undefined},
+ #state{static_env = #static_env{role = client},
+ session = #session{master_secret = undefined},
ssl_options = #ssl_options{user_lookup_fun = PSKLookup},
negotiated_version = {Major, Minor} = Version,
psk_identity = PSKIdentity,
premaster_secret = undefined,
- role = client,
key_algorithm = Alg} = State0, Connection)
when Alg == rsa_psk ->
Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
@@ -900,11 +947,11 @@ certify(internal, #server_hello_done{},
end;
%% Master secret was determined with help of server-key exchange msg
certify(internal, #server_hello_done{},
- #state{session = #session{master_secret = MasterSecret} = Session,
+ #state{static_env = #static_env{role = client},
+ session = #session{master_secret = MasterSecret} = Session,
connection_states = ConnectionStates0,
negotiated_version = Version,
- premaster_secret = undefined,
- role = client} = State0, Connection) ->
+ premaster_secret = undefined} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -915,11 +962,11 @@ certify(internal, #server_hello_done{},
end;
%% Master secret is calculated from premaster_secret
certify(internal, #server_hello_done{},
- #state{session = Session0,
+ #state{static_env = #static_env{role = client},
+ session = Session0,
connection_states = ConnectionStates0,
negotiated_version = Version,
- premaster_secret = PremasterSecret,
- role = client} = State0, Connection) ->
+ premaster_secret = PremasterSecret} = State0, Connection) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
@@ -931,7 +978,7 @@ certify(internal, #server_hello_done{},
handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
end;
certify(internal = Type, #client_key_exchange{} = Msg,
- #state{role = server,
+ #state{static_env = #static_env{role = server},
client_certificate_requested = true,
ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State,
Connection) ->
@@ -961,49 +1008,49 @@ cipher(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
cipher(internal, #certificate_verify{signature = Signature,
hashsign_algorithm = CertHashSign},
- #state{role = server,
+ #state{static_env = #static_env{role = server},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
key_algorithm = KexAlg,
public_key_info = PublicKeyInfo,
negotiated_version = Version,
- session = #session{master_secret = MasterSecret},
- tls_handshake_history = Handshake
- } = State0, Connection) ->
+ session = #session{master_secret = MasterSecret}
+ } = State, Connection) ->
TLSVersion = ssl:tls_version(Version),
%% Use negotiated value if TLS-1.2 otherwhise return default
HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, TLSVersion),
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
- TLSVersion, HashSign, MasterSecret, Handshake) of
+ TLSVersion, HashSign, MasterSecret, Hist) of
valid ->
- {Record, State} = Connection:next_record(State0),
- Connection:next_event(?FUNCTION_NAME, Record,
+ Connection:next_event(?FUNCTION_NAME, no_record,
State#state{cert_hashsign_algorithm = HashSign});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0)
+ handle_own_alert(Alert, Version, ?FUNCTION_NAME, State)
end;
%% client must send a next protocol message if we are expecting it
cipher(internal, #finished{},
- #state{role = server, expecting_next_protocol_negotiation = true,
+ #state{static_env = #static_env{role = server},
+ expecting_next_protocol_negotiation = true,
negotiated_protocol = undefined, negotiated_version = Version} = State0,
_Connection) ->
handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0);
cipher(internal, #finished{verify_data = Data} = Finished,
- #state{negotiated_version = Version,
- host = Host,
- port = Port,
- role = Role,
- expecting_finished = true,
+ #state{static_env = #static_env{role = Role,
+ host = Host,
+ port = Port},
+ negotiated_version = Version,
+ expecting_finished = true,
session = #session{master_secret = MasterSecret}
= Session0,
ssl_options = SslOpts,
connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ handshake_env = #handshake_env{tls_handshake_history = Hist}} = State, Connection) ->
case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished,
opposite_role(Role),
get_current_prf(ConnectionStates0, read),
- MasterSecret, Handshake0) of
+ MasterSecret, Hist) of
verified ->
- Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0),
+ Session = handle_session(Role, SslOpts, Host, Port, Session0),
cipher_role(Role, Data, Session,
State#state{expecting_finished = false}, Connection);
#alert{} = Alert ->
@@ -1012,19 +1059,20 @@ cipher(internal, #finished{verify_data = Data} = Finished,
%% only allowed to send next_protocol message after change cipher spec
%% & before finished message and it is not allowed during renegotiation
cipher(internal, #next_protocol{selected_protocol = SelectedProtocol},
- #state{role = server, expecting_next_protocol_negotiation = true,
+ #state{static_env = #static_env{role = server},
+ expecting_next_protocol_negotiation = true,
expecting_finished = true} = State0, Connection) ->
{Record, State} =
Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}),
Connection:next_event(?FUNCTION_NAME, Record,
State#state{expecting_next_protocol_negotiation = false});
cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} =
- State0, Connection) ->
- ConnectionStates1 =
+ State, Connection) ->
+ ConnectionStates =
ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection),
- {Record, State} = Connection:next_record(State0#state{connection_states =
- ConnectionStates1}),
- Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true});
+ Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states =
+ ConnectionStates,
+ expecting_finished = true});
cipher(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -1034,15 +1082,18 @@ cipher(Type, Msg, State, Connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
connection({call, RecvFrom}, {recv, N, Timeout},
- #state{protocol_cb = Connection, socket_options =
- #socket_options{active = false}} = State0, Connection) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ socket_options =
+ #socket_options{active = false}} = State0, Connection) ->
Timer = start_or_recv_cancel_timer(Timeout, RecvFrom),
- Connection:passive_receive(State0#state{bytes_to_read = N,
- start_or_recv_from = RecvFrom,
- timer = Timer}, ?FUNCTION_NAME);
-connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State,
+ passive_receive(State0#state{bytes_to_read = N,
+ start_or_recv_from = RecvFrom,
+ timer = Timer}, ?FUNCTION_NAME, Connection);
+
+connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv} = State,
Connection) ->
- Connection:renegotiate(State#state{renegotiation = {true, From}}, []);
+ Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []);
connection({call, From}, peer_certificate,
#state{session = #session{peer_certificate = Cert}} = State, _) ->
hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]);
@@ -1061,27 +1112,27 @@ connection({call, From}, negotiated_protocol,
[{reply, From, {ok, SelectedProtocol}}]);
connection({call, From}, Msg, State, Connection) ->
handle_call(Msg, From, ?FUNCTION_NAME, State, Connection);
-connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connection,
+connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection},
+ handshake_env = HsEnv,
connection_states = ConnectionStates}
= State, Connection) ->
- Connection:renegotiate(State#state{renegotiation = {true, internal},
+ Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}},
connection_states = ConnectionStates#{current_write => WriteState}}, []);
connection(cast, {dist_handshake_complete, DHandle},
#state{ssl_options = #ssl_options{erl_dist = true},
- erl_dist_data = ErlDistData,
socket_options = SockOpts} = State0, Connection) ->
process_flag(priority, normal),
State1 =
State0#state{
- socket_options =
- SockOpts#socket_options{active = true},
- erl_dist_data = ErlDistData#{dist_handle => DHandle}},
- {Record, State} = dist_app_data(<<>>, State1),
+ socket_options = SockOpts#socket_options{active = true},
+ erl_dist_handle = DHandle,
+ bytes_to_read = undefined},
+ {Record, State} = read_application_data(<<>>, State1),
Connection:next_event(connection, Record, State);
connection(info, Msg, State, _) ->
handle_info(Msg, ?FUNCTION_NAME, State);
connection(internal, {recv, _}, State, Connection) ->
- Connection:passive_receive(State, ?FUNCTION_NAME);
+ passive_receive(State, ?FUNCTION_NAME, Connection);
connection(Type, Msg, State, Connection) ->
handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
@@ -1090,16 +1141,6 @@ connection(Type, Msg, State, Connection) ->
#state{}, tls_connection | dtls_connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
-downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
- #state{transport_cb = Transport, socket = Socket,
- downgrade = {Pid, From}} = State, _) ->
- tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
- Transport:controlling_process(Socket, Pid),
- gen_statem:reply(From, {ok, Socket}),
- stop(normal, State);
-downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) ->
- gen_statem:reply(From, {error, timeout}),
- stop(normal, State);
downgrade(Type, Event, State, Connection) ->
handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection).
@@ -1108,14 +1149,17 @@ downgrade(Type, Event, State, Connection) ->
%% common or unexpected events for the state.
%%--------------------------------------------------------------------
handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName,
- #state{role = client} = State, _) ->
+ #state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, _) ->
%% Should not be included in handshake history
- {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]};
-handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _)
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}},
+ [{next_event, internal, Handshake}]};
+handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName,
+ #state{static_env = #static_env{role = client}}, _)
when StateName =/= connection ->
- {keep_state_and_data};
+ keep_state_and_data;
handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
- #state{tls_handshake_history = Hs0} = State0,
+ #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv} = State0,
Connection) ->
PossibleSNI = Connection:select_sni_extension(Handshake),
@@ -1123,27 +1167,14 @@ handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName,
%% a client_hello, which needs to be determined by the connection callback.
%% In other cases this is a noop
State = handle_sni_extension(PossibleSNI, State0),
- HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw)),
- {next_state, StateName, State#state{tls_handshake_history = HsHist},
+
+ Hist = ssl_handshake:update_handshake_history(Hist0, Raw),
+ {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}},
[{next_event, internal, Handshake}]};
handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) ->
- Connection:handle_common_event(internal, TLSorDTLSRecord, StateName, State);
+ Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State);
handle_common_event(timeout, hibernate, _, _, _) ->
{keep_state_and_data, [hibernate]};
-handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) ->
- case read_application_data(Data, State0) of
- {stop, _, _} = Stop->
- Stop;
- {Record, State} ->
- case Connection:next_event(StateName, Record, State) of
- {next_state, StateName, State} ->
- hibernate_after(StateName, State, []);
- {next_state, StateName, State, Actions} ->
- hibernate_after(StateName, State, Actions);
- {stop, _, _} = Stop ->
- Stop
- end
- end;
handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName,
#state{negotiated_version = Version} = State, _) ->
handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version,
@@ -1156,39 +1187,37 @@ handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version}
handle_call({application_data, _Data}, _, _, _, _) ->
%% In renegotiation priorities handshake, send data when handshake is finished
{keep_state_and_data, [postpone]};
-handle_call({close, {Pid, Timeout}}, From, StateName, State0, Connection) when is_pid(Pid) ->
- %% terminate will send close alert to peer
- State = State0#state{downgrade = {Pid, From}},
- Connection:terminate(downgrade, StateName, State),
- %% User downgrades connection
- %% When downgrading an TLS connection to a transport connection
- %% we must recive the close alert from the peer before releasing the
- %% transport socket.
- {next_state, downgrade, State#state{terminated = true}, [{timeout, Timeout, downgrade}]};
handle_call({close, _} = Close, From, StateName, State, _Connection) ->
%% Run terminate before returning so that the reuseaddr
%% inet-option works properly
Result = terminate(Close, StateName, State),
- stop_and_reply(
- {shutdown, normal},
- {reply, From, Result}, State#state{terminated = true});
+ {stop_and_reply,
+ {shutdown, normal},
+ {reply, From, Result}, State#state{terminated = true}};
+handle_call({shutdown, read_write = How}, From, StateName,
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket}} = State, _) ->
+ try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ StateName, State) of
+ _ ->
+ case Transport:shutdown(Socket, How) of
+ ok ->
+ {next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]};
+ Error ->
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State#state{terminated = true}}
+ end
+ catch
+ throw:Return ->
+ Return
+ end;
handle_call({shutdown, How0}, From, StateName,
- #state{transport_cb = Transport,
- socket = Socket} = State, _) ->
- case How0 of
- How when How == write; How == both ->
- send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
- StateName, State);
- _ ->
- ok
- end,
-
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket}} = State, _) ->
case Transport:shutdown(Socket, How0) of
ok ->
- {keep_state_and_data, [{reply, From, ok}]};
+ {next_state, StateName, State, [{reply, From, ok}]};
Error ->
- gen_statem:reply(From, {error, Error}),
- stop(normal, State)
+ {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State}
end;
handle_call({recv, _N, _Timeout}, From, _,
#state{socket_options =
@@ -1208,15 +1237,16 @@ handle_call({new_user, User}, From, StateName,
{next_state, StateName, State#state{user_application = {NewMon,User}},
[{reply, From, ok}]};
handle_call({get_opts, OptTags}, From, _,
- #state{socket = Socket,
- transport_cb = Transport,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
socket_options = SockOpts}, Connection) ->
OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []),
{keep_state_and_data, [{reply, From, OptsReply}]};
handle_call({set_opts, Opts0}, From, StateName,
- #state{socket_options = Opts1,
- socket = Socket,
- transport_cb = Transport} = State0, Connection) ->
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ socket_options = Opts1
+ } = State0, Connection) ->
{Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []),
State = State0#state{socket_options = Opts},
handle_active_option(Opts#socket_options.active, StateName, From, Reply, State);
@@ -1257,22 +1287,25 @@ handle_call(_,_,_,_,_) ->
{keep_state_and_data, [postpone]}.
handle_info({ErrorTag, Socket, econnaborted}, StateName,
- #state{socket = Socket, transport_cb = Transport,
- protocol_cb = Connection,
- start_or_recv_from = StartFrom, role = Role,
- error_tag = ErrorTag,
- tracker = Tracker} = State) when StateName =/= connection ->
+ #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ error_tag = ErrorTag,
+ tracker = Tracker,
+ protocol_cb = Connection},
+ start_or_recv_from = StartFrom
+ } = State) when StateName =/= connection ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker,Socket,
StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection),
- stop(normal, State);
+ {stop, {shutdown, normal}, State};
-handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket,
- error_tag = ErrorTag} = State) ->
+handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket,
+ error_tag = ErrorTag}} = State) ->
Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
?LOG_ERROR(Report),
handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- stop(normal, State);
+ {stop, {shutdown,normal}, State};
handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
#state{user_application = {MonitorRef, _Pid},
@@ -1280,7 +1313,7 @@ handle_info({'DOWN', MonitorRef, _, _, Reason}, _,
{stop, {shutdown, Reason}};
handle_info({'DOWN', MonitorRef, _, _, _}, _,
#state{user_application = {MonitorRef, _Pid}}) ->
- {stop, normal};
+ {stop, {shutdown, normal}};
handle_info({'EXIT', Pid, _Reason}, StateName,
#state{user_application = {_MonitorRef, Pid}} = State) ->
%% It seems the user application has linked to us
@@ -1288,22 +1321,22 @@ handle_info({'EXIT', Pid, _Reason}, StateName,
{next_state, StateName, State};
%%% So that terminate will be run when supervisor issues shutdown
handle_info({'EXIT', _Sup, shutdown}, _StateName, State) ->
- stop(shutdown, State);
-handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) ->
+ {stop, shutdown, State};
+handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
%% Handle as transport close"
- stop({shutdown, transport_closed}, State);
-handle_info({'EXIT', Socket, Reason}, _StateName, #state{socket = Socket} = State) ->
- stop({shutdown, Reason}, State);
+ {stop,{shutdown, transport_closed}, State};
+handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) ->
+ {stop,{shutdown, Reason}, State};
handle_info(allow_renegotiate, StateName, State) ->
{next_state, StateName, State#state{allow_renegotiate = true}};
handle_info({cancel_start_or_recv, StartFrom}, StateName,
- #state{renegotiation = {false, first}} = State) when StateName =/= connection ->
- stop_and_reply(
- {shutdown, user_timeout},
- {reply, StartFrom, {error, timeout}},
- State#state{timer = undefined});
+ #state{handshake_env = #handshake_env{renegotiation = {false, first}}} = State) when StateName =/= connection ->
+ {stop_and_reply,
+ {shutdown, user_timeout},
+ {reply, StartFrom, {error, timeout}},
+ State#state{timer = undefined}};
handle_info({cancel_start_or_recv, RecvFrom}, StateName,
#state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined ->
{next_state, StateName, State#state{start_or_recv_from = undefined,
@@ -1312,7 +1345,7 @@ handle_info({cancel_start_or_recv, RecvFrom}, StateName,
handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) ->
{next_state, StateName, State#state{timer = undefined}};
-handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) ->
+handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) ->
Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]),
?LOG_NOTICE(Report),
{next_state, StateName, State}.
@@ -1329,14 +1362,15 @@ terminate(_, _, #state{terminated = true}) ->
%% before run by gen_statem which will end up here
ok;
terminate({shutdown, transport_closed} = Reason,
- _StateName, #state{protocol_cb = Connection,
- socket = Socket, transport_cb = Transport} = State) ->
+ _StateName, #state{static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
handle_trusted_certs_db(State),
Connection:close(Reason, Socket, Transport, undefined, undefined);
terminate({shutdown, own_alert}, _StateName, #state{
- protocol_cb = Connection,
- socket = Socket,
- transport_cb = Transport} = State) ->
+ static_env = #static_env{protocol_cb = Connection,
+ socket = Socket,
+ transport_cb = Transport}} = State) ->
handle_trusted_certs_db(State),
case application:get_env(ssl, alert_timeout) of
{ok, Timeout} when is_integer(Timeout) ->
@@ -1344,23 +1378,27 @@ terminate({shutdown, own_alert}, _StateName, #state{
_ ->
Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined)
end;
-terminate(downgrade = Reason, connection, #state{protocol_cb = Connection,
- transport_cb = Transport, socket = Socket
- } = State) ->
+terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket}
+ } = State) ->
handle_trusted_certs_db(State),
Connection:close(Reason, Socket, Transport, undefined, undefined);
-terminate(Reason, connection, #state{protocol_cb = Connection,
- connection_states = ConnectionStates,
- ssl_options = #ssl_options{padding_check = Check},
- transport_cb = Transport, socket = Socket
- } = State) ->
+terminate(Reason, connection, #state{static_env = #static_env{
+ protocol_cb = Connection,
+ transport_cb = Transport,
+ socket = Socket},
+ connection_states = ConnectionStates,
+ ssl_options = #ssl_options{padding_check = Check}
+ } = State) ->
handle_trusted_certs_db(State),
Alert = terminate_alert(Reason),
%% Send the termination ALERT if possible
catch (ok = Connection:send_alert_in_connection(Alert, State)),
- Connection:close(Reason, Socket, Transport, ConnectionStates, Check);
-terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection,
- socket = Socket
+ Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check);
+terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport,
+ protocol_cb = Connection,
+ socket = Socket}
} = State) ->
handle_trusted_certs_db(State),
Connection:close(Reason, Socket, Transport, undefined, undefined).
@@ -1379,7 +1417,7 @@ format_status(terminate, [_, StateName, State]) ->
[{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT,
protocol_buffers = ?SECRET_PRINTOUT,
user_data_buffer = ?SECRET_PRINTOUT,
- tls_handshake_history = ?SECRET_PRINTOUT,
+ handshake_env = ?SECRET_PRINTOUT,
session = ?SECRET_PRINTOUT,
private_key = ?SECRET_PRINTOUT,
diffie_hellman_params = ?SECRET_PRINTOUT,
@@ -1394,15 +1432,15 @@ format_status(terminate, [_, StateName, State]) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-send_alert(Alert, connection, #state{protocol_cb = Connection} = State) ->
+send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
Connection:send_alert_in_connection(Alert, State);
-send_alert(Alert, _, #state{protocol_cb = Connection} = State) ->
+send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) ->
Connection:send_alert(Alert, State).
-connection_info(#state{sni_hostname = SNIHostname,
- session = #session{session_id = SessionId,
+connection_info(#state{static_env = #static_env{protocol_cb = Connection},
+ sni_hostname = SNIHostname,
+ session = #session{session_id = SessionId,
cipher_suite = CipherSuite, ecc = ECCCurve},
- protocol_cb = Connection,
negotiated_version = {_,_} = Version,
ssl_options = Opts}) ->
RecordCB = record_cb(Connection),
@@ -1514,13 +1552,12 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite,
negotiated_version = Version} = State0, Connection) ->
try server_certify_and_key_exchange(State0, Connection) of
#state{} = State1 ->
- {State2, Actions} = server_hello_done(State1, Connection),
+ {State, Actions} = server_hello_done(State1, Connection),
Session =
Session0#session{session_id = SessionId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {Record, State} = Connection:next_record(State2#state{session = Session}),
- Connection:next_event(certify, Record, State, Actions)
+ Connection:next_event(certify, no_record, State#state{session = Session}, Actions)
catch
#alert{} = Alert ->
handle_own_alert(Alert, Version, hello, State0)
@@ -1535,10 +1572,9 @@ resumed_server_hello(#state{session = Session,
{_, ConnectionStates1} ->
State1 = State0#state{connection_states = ConnectionStates1,
session = Session},
- {State2, Actions} =
+ {State, Actions} =
finalize_handshake(State1, abbreviated, Connection),
- {Record, State} = Connection:next_record(State2),
- Connection:next_event(abbreviated, Record, State, Actions);
+ Connection:next_event(abbreviated, no_record, State, Actions);
#alert{} = Alert ->
handle_own_alert(Alert, Version, hello, State0)
end.
@@ -1560,10 +1596,8 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo,
Session#session{peer_certificate = PeerCert},
public_key_info = PublicKeyInfo},
#{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite),
- State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
-
- {Record, State} = Connection:next_record(State2),
- Connection:next_event(certify, Record, State).
+ State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1),
+ Connection:next_event(certify, no_record, State).
handle_peer_cert_key(client, _,
{?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey,
@@ -1586,9 +1620,10 @@ handle_peer_cert_key(client, _,
handle_peer_cert_key(_, _, _, _, State) ->
State.
-certify_client(#state{client_certificate_requested = true, role = client,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+certify_client(#state{static_env = #static_env{role = client,
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ client_certificate_requested = true,
session = #session{own_certificate = OwnCert}}
= State, Connection) ->
Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
@@ -1596,16 +1631,17 @@ certify_client(#state{client_certificate_requested = true, role = client,
certify_client(#state{client_certificate_requested = false} = State, _) ->
State.
-verify_client_cert(#state{client_certificate_requested = true, role = client,
+verify_client_cert(#state{static_env = #static_env{role = client},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ client_certificate_requested = true,
negotiated_version = Version,
private_key = PrivateKey,
session = #session{master_secret = MasterSecret,
own_certificate = OwnCert},
- cert_hashsign_algorithm = HashSign,
- tls_handshake_history = Handshake0} = State, Connection) ->
+ cert_hashsign_algorithm = HashSign} = State, Connection) ->
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
- ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of
+ ssl:tls_version(Version), HashSign, PrivateKey, Hist) of
#certificate_verify{} = Verified ->
Connection:queue_handshake(Verified, State);
ignore ->
@@ -1621,11 +1657,10 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} =
try do_client_certify_and_key_exchange(State0, Connection) of
State1 = #state{} ->
{State2, Actions} = finalize_handshake(State1, certify, Connection),
- State3 = State2#state{
- %% Reinitialize
- client_certificate_requested = false},
- {Record, State} = Connection:next_record(State3),
- Connection:next_event(cipher, Record, State, Actions)
+ State = State2#state{
+ %% Reinitialize
+ client_certificate_requested = false},
+ Connection:next_event(cipher, no_record, State, Actions)
catch
throw:#alert{} = Alert ->
handle_own_alert(Alert, Version, certify, State0)
@@ -1642,7 +1677,9 @@ server_certify_and_key_exchange(State0, Connection) ->
request_client_cert(State2, Connection).
certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS},
- #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) ->
+ #state{private_key = Key,
+ handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}}
+ = State, Connection) ->
FakeSecret = make_premaster_secret(Version, rsa),
%% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret
%% and fail handshake later.RFC 5246 section 7.4.7.1.
@@ -1717,8 +1754,8 @@ certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon;
Algo == ecdhe_psk;
Algo == srp_anon ->
State;
-certify_server(#state{cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+certify_server(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
session = #session{own_certificate = OwnCert}} = State, Connection) ->
case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
Cert = #certificate{} ->
@@ -1727,9 +1764,9 @@ certify_server(#state{cert_db = CertDbHandle,
throw(Alert)
end.
-key_exchange(#state{role = server, key_algorithm = rsa} = State,_) ->
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) ->
State;
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
hashsign_algorithm = HashSignAlgo,
diffie_hellman_params = #'DHParameter'{} = Params,
private_key = PrivateKey,
@@ -1750,12 +1787,14 @@ key_exchange(#state{role = server, key_algorithm = Algo,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
-key_exchange(#state{role = server, private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server},
+ private_key = #'ECPrivateKey'{parameters = ECCurve} = Key,
+ key_algorithm = Algo,
session = Session} = State, _)
when Algo == ecdh_ecdsa; Algo == ecdh_rsa ->
State#state{diffie_hellman_keys = Key,
session = Session#session{ecc = ECCurve}};
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
session = #session{ecc = ECCCurve},
@@ -1777,10 +1816,10 @@ key_exchange(#state{role = server, key_algorithm = Algo,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = ECDHKeys};
-key_exchange(#state{role = server, key_algorithm = psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk,
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{role = server, key_algorithm = psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
@@ -1797,7 +1836,7 @@ key_exchange(#state{role = server, key_algorithm = psk,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = server, key_algorithm = dhe_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
diffie_hellman_params = #'DHParameter'{} = Params,
@@ -1818,7 +1857,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = DHKeys};
-key_exchange(#state{role = server, key_algorithm = ecdhe_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
@@ -1839,10 +1878,10 @@ key_exchange(#state{role = server, key_algorithm = ecdhe_psk,
PrivateKey}),
State = Connection:queue_handshake(Msg, State0),
State#state{diffie_hellman_keys = ECDHKeys};
-key_exchange(#state{role = server, key_algorithm = rsa_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk,
ssl_options = #ssl_options{psk_identity = undefined}} = State, _) ->
State;
-key_exchange(#state{role = server, key_algorithm = rsa_psk,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk,
ssl_options = #ssl_options{psk_identity = PskIdentityHint},
hashsign_algorithm = HashSignAlgo,
private_key = PrivateKey,
@@ -1859,7 +1898,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk,
ServerRandom,
PrivateKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = server, key_algorithm = Algo,
+key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo,
ssl_options = #ssl_options{user_lookup_fun = LookupFun},
hashsign_algorithm = HashSignAlgo,
session = #session{srp_username = Username},
@@ -1889,14 +1928,14 @@ key_exchange(#state{role = server, key_algorithm = Algo,
State = Connection:queue_handshake(Msg, State0),
State#state{srp_params = SrpParams,
srp_keys = Keys};
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = rsa,
public_key_info = PublicKeyInfo,
negotiated_version = Version,
premaster_secret = PremasterSecret} = State0, Connection) ->
Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = Algorithm,
negotiated_version = Version,
diffie_hellman_keys = {DhPubKey, _}
@@ -1907,7 +1946,7 @@ key_exchange(#state{role = client,
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = Algorithm,
negotiated_version = Version,
session = Session,
@@ -1917,14 +1956,14 @@ key_exchange(#state{role = client,
Algorithm == ecdh_anon ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}),
Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}});
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = psk,
negotiated_version = Version} = State0, Connection) ->
Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version),
{psk, SslOpts#ssl_options.psk_identity}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = dhe_psk,
negotiated_version = Version,
@@ -1934,7 +1973,7 @@ key_exchange(#state{role = client,
SslOpts#ssl_options.psk_identity, DhPubKey}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = ecdhe_psk,
negotiated_version = Version,
@@ -1944,7 +1983,7 @@ key_exchange(#state{role = client,
SslOpts#ssl_options.psk_identity, ECDHKeys}),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
ssl_options = SslOpts,
key_algorithm = rsa_psk,
public_key_info = PublicKeyInfo,
@@ -1954,7 +1993,7 @@ key_exchange(#state{role = client,
Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity,
PremasterSecret, PublicKeyInfo),
Connection:queue_handshake(Msg, State0);
-key_exchange(#state{role = client,
+key_exchange(#state{static_env = #static_env{role = client},
key_algorithm = Algorithm,
negotiated_version = Version,
srp_keys = {ClientPubKey, _}}
@@ -2004,11 +2043,11 @@ request_client_cert(#state{key_algorithm = Alg} = State, _)
Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon ->
State;
-request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer,
- signature_algs = SupportedHashSigns},
- connection_states = ConnectionStates0,
- cert_db = CertDbHandle,
- cert_db_ref = CertDbRef,
+request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef},
+ ssl_options = #ssl_options{verify = verify_peer,
+ signature_algs = SupportedHashSigns},
+ connection_states = ConnectionStates0,
negotiated_version = Version} = State0, Connection) ->
#{security_parameters :=
#security_parameters{cipher_suite = CipherSuite}} =
@@ -2034,10 +2073,9 @@ calculate_master_secret(PremasterSecret,
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
- State1 = State0#state{connection_states = ConnectionStates,
+ State = State0#state{connection_states = ConnectionStates,
session = Session},
- {Record, State} = Connection:next_record(State1),
- Connection:next_event(Next, Record, State);
+ Connection:next_event(Next, no_record, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version, certify, State0)
end.
@@ -2054,7 +2092,7 @@ finalize_handshake(State0, StateName, Connection) ->
State = next_protocol(State2, Connection),
finished(State, StateName, Connection).
-next_protocol(#state{role = server} = State, _) ->
+next_protocol(#state{static_env = #static_env{role = server}} = State, _) ->
State;
next_protocol(#state{negotiated_protocol = undefined} = State, _) ->
State;
@@ -2067,14 +2105,16 @@ next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) -
cipher_protocol(State, Connection) ->
Connection:queue_change_cipher(#change_cipher_spec{}, State).
-finished(#state{role = Role, negotiated_version = Version,
+finished(#state{static_env = #static_env{role = Role},
+ handshake_env = #handshake_env{tls_handshake_history = Hist},
+ negotiated_version = Version,
session = Session,
- connection_states = ConnectionStates0,
- tls_handshake_history = Handshake0} = State0, StateName, Connection) ->
+ connection_states = ConnectionStates0} = State0,
+ StateName, Connection) ->
MasterSecret = Session#session.master_secret,
Finished = ssl_handshake:finished(ssl:tls_version(Version), Role,
get_current_prf(ConnectionStates0, write),
- MasterSecret, Handshake0),
+ MasterSecret, Hist),
ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName),
Connection:send_handshake(Finished, State0#state{connection_states =
ConnectionStates}).
@@ -2110,10 +2150,9 @@ calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey},
calculate_secret(#server_psk_params{
hint = IdentityHint},
- State0, Connection) ->
+ State, Connection) ->
%% store for later use
- {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}),
- Connection:next_event(certify, Record, State);
+ Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint});
calculate_secret(#server_dhe_psk_params{
dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey,
@@ -2147,8 +2186,9 @@ calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKe
master_secret(#alert{} = Alert, _) ->
Alert;
-master_secret(PremasterSecret, #state{session = Session,
- negotiated_version = Version, role = Role,
+master_secret(PremasterSecret, #state{static_env = #static_env{role = Role},
+ session = Session,
+ negotiated_version = Version,
connection_states = ConnectionStates0} = State) ->
case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret,
ConnectionStates0, Role) of
@@ -2366,18 +2406,18 @@ handle_trusted_certs_db(#state{ssl_options =
#ssl_options{cacertfile = <<>>, cacerts = []}}) ->
%% No trusted certs specified
ok;
-handle_trusted_certs_db(#state{cert_db_ref = Ref,
- cert_db = CertDb,
- ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined ->
- %% Certs provided as DER directly cannot be shared
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ cert_db = CertDb},
+ ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined ->
+ %% Certs provided as DER directly can not be shared
%% with other connections and it is safe to delete them when the connection ends.
ssl_pkix_db:remove_trusted_certs(Ref, CertDb);
-handle_trusted_certs_db(#state{file_ref_db = undefined}) ->
+handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) ->
%% Something went wrong early (typically cacertfile does not
%% exist) so there is nothing to handle
ok;
-handle_trusted_certs_db(#state{cert_db_ref = Ref,
- file_ref_db = RefDb,
+handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref,
+ file_ref_db = RefDb},
ssl_options = #ssl_options{cacertfile = File}}) ->
case ssl_pkix_db:ref_count(Ref, RefDb, -1) of
0 ->
@@ -2386,29 +2426,28 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref,
ok
end.
-prepare_connection(#state{renegotiation = Renegotiate,
+prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate},
start_or_recv_from = RecvFrom} = State0, Connection)
when Renegotiate =/= {false, first},
RecvFrom =/= undefined ->
- State1 = Connection:reinit(State0),
- {Record, State} = Connection:next_record(State1),
- {Record, ack_connection(State)};
+ State = Connection:reinit(State0),
+ {no_record, ack_connection(State)};
prepare_connection(State0, Connection) ->
State = Connection:reinit(State0),
{no_record, ack_connection(State)}.
-ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer;
- Initiater == internal ->
- State#state{renegotiation = undefined};
-ack_connection(#state{renegotiation = {true, From}} = State) ->
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer;
+ Initiater == internal ->
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) ->
gen_statem:reply(From, ok),
- State#state{renegotiation = undefined};
-ack_connection(#state{renegotiation = {false, first},
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}};
+ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv,
start_or_recv_from = StartFrom,
timer = Timer} = State) when StartFrom =/= undefined ->
gen_statem:reply(StartFrom, connected),
cancel_timer(Timer),
- State#state{renegotiation = undefined,
+ State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined},
start_or_recv_from = undefined, timer = undefined};
ack_connection(State) ->
State.
@@ -2424,15 +2463,35 @@ session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) ->
session_handle_params(_, Session) ->
Session.
-register_session(client, Host, Port, #session{is_resumable = new} = Session0) ->
+handle_session(Role = server, #ssl_options{reuse_sessions = true} = SslOpts,
+ Host, Port, Session0) ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true);
+handle_session(Role = client, #ssl_options{verify = verify_peer,
+ reuse_sessions = Reuse} = SslOpts,
+ Host, Port, Session0) when Reuse =/= false ->
+ register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse));
+handle_session(server, _, Host, Port, Session) ->
+ %% Remove "session of type new" entry from session DB
+ ssl_manager:invalidate_session(Host, Port, Session),
+ Session;
+handle_session(client, _,_,_, Session) ->
+ %% In client case there is no entry yet, so nothing to remove
+ Session.
+
+reg_type(save) ->
+ true;
+reg_type(true) ->
+ unique.
+
+register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) ->
Session = Session0#session{is_resumable = true},
- ssl_manager:register_session(Host, Port, Session),
+ ssl_manager:register_session(Host, Port, Session, Save),
Session;
-register_session(server, _, Port, #session{is_resumable = new} = Session0) ->
+register_session(server, _, Port, #session{is_resumable = new} = Session0, _) ->
Session = Session0#session{is_resumable = true},
ssl_manager:register_session(Port, Session),
Session;
-register_session(_, _, _, Session) ->
+register_session(_, _, _, Session, _) ->
Session. %% Already registered
host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) ->
@@ -2441,31 +2500,31 @@ host_id(_, Host, _) ->
Host.
handle_new_session(NewId, CipherSuite, Compression,
- #state{session = Session0,
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{protocol_cb = Connection},
+ session = Session0
+ } = State0) ->
Session = Session0#session{session_id = NewId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {Record, State} = Connection:next_record(State0#state{session = Session}),
- Connection:next_event(certify, Record, State).
-
-handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
- negotiated_version = Version,
- host = Host, port = Port,
- protocol_cb = Connection,
- session_cache = Cache,
- session_cache_cb = CacheCb} = State0) ->
+ Connection:next_event(certify, no_record, State0#state{session = Session}).
+
+handle_resumed_session(SessId, #state{static_env = #static_env{host = Host,
+ port = Port,
+ protocol_cb = Connection,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ connection_states = ConnectionStates0,
+ negotiated_version = Version
+ } = State) ->
Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
case ssl_handshake:master_secret(ssl:tls_version(Version), Session,
ConnectionStates0, client) of
{_, ConnectionStates} ->
- {Record, State} =
- Connection:next_record(State0#state{
- connection_states = ConnectionStates,
- session = Session}),
- Connection:next_event(abbreviated, Record, State);
+ Connection:next_event(abbreviated, no_record, State#state{
+ connection_states = ConnectionStates,
+ session = Session});
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State0)
+ handle_own_alert(Alert, Version, hello, State)
end.
make_premaster_secret({MajVer, MinVer}, rsa) ->
@@ -2513,12 +2572,9 @@ ssl_options_list([Key | Keys], [Value | Values], Acc) ->
handle_active_option(false, connection = StateName, To, Reply, State) ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
-handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection,
- user_data_buffer = <<>>} = State0) ->
- %% Need data, set active once
- {Record, State1} = Connection:next_record_if_active(State0),
- %% Note: Renogotiation may cause StateName0 =/= StateName
- case Connection:next_event(StateName0, Record, State1) of
+handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection},
+ user_data_buffer = <<>>} = State0) ->
+ case Connection:next_event(StateName0, no_record, State0) of
{next_state, StateName, State} ->
hibernate_after(StateName, State, [{reply, To, Reply}]);
{next_state, StateName, State, Actions} ->
@@ -2531,7 +2587,8 @@ handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} =
{next_state, StateName, State, [{reply, To, Reply}]};
%% user_data_buffer =/= <<>>
-handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) ->
+handle_active_option(_, StateName0, To, Reply,
+ #state{static_env = #static_env{protocol_cb = Connection}} = State0) ->
case read_application_data(<<>>, State0) of
{stop, _, _} = Stop ->
Stop;
@@ -2593,21 +2650,28 @@ decode_packet(Type, Buffer, PacketOpts) ->
%% Note that if the user has explicitly configured the socket to expect
%% HTTP headers using the {packet, httph} option, we don't do any automatic
%% switching of states.
-deliver_app_data(CPids, Transport, Socket, SOpts = #socket_options{active=Active, packet=Type},
- Data, Pid, From, Tracker, Connection) ->
- send_or_reply(Active, Pid, From,
- format_reply(CPids, Transport, Socket, SOpts, Data, Tracker, Connection)),
- SO = case Data of
- {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)),
- ((Type =:= http) or (Type =:= http_bin)) ->
- SOpts#socket_options{packet={Type, headers}};
- http_eoh when tuple_size(Type) =:= 2 ->
- % End of headers - expect another Request/Response line
- {Type1, headers} = Type,
- SOpts#socket_options{packet=Type1};
- _ ->
- SOpts
- end,
+deliver_app_data(
+ CPids, Transport, Socket,
+ #socket_options{active=Active, packet=Type} = SOpts,
+ Data, Pid, From, Tracker, Connection) ->
+ %%
+ send_or_reply(
+ Active, Pid, From,
+ format_reply(
+ CPids, Transport, Socket, SOpts, Data, Tracker, Connection)),
+ SO =
+ case Data of
+ {P, _, _, _}
+ when ((P =:= http_request) or (P =:= http_response)),
+ ((Type =:= http) or (Type =:= http_bin)) ->
+ SOpts#socket_options{packet={Type, headers}};
+ http_eoh when tuple_size(Type) =:= 2 ->
+ %% End of headers - expect another Request/Response line
+ {Type1, headers} = Type,
+ SOpts#socket_options{packet=Type1};
+ _ ->
+ SOpts
+ end,
case Active of
once ->
SO#socket_options{active=false};
@@ -2712,7 +2776,7 @@ invalidate_session(server, _, Port, Session) ->
handle_sni_extension(undefined, State) ->
State;
-handle_sni_extension(#sni{hostname = Hostname}, State0) ->
+handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) ->
NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname),
case NewOptions of
undefined ->
@@ -2726,14 +2790,16 @@ handle_sni_extension(#sni{hostname = Hostname}, State0) ->
private_key := Key,
dh_params := DHParams,
own_certificate := OwnCert}} =
- ssl_config:init(NewOptions, State0#state.role),
+ ssl_config:init(NewOptions, Role),
State0#state{
session = State0#state.session#session{own_certificate = OwnCert},
- file_ref_db = FileRefHandle,
- cert_db_ref = Ref,
- cert_db = CertDbHandle,
- crl_db = CRLDbHandle,
- session_cache = CacheHandle,
+ static_env = InitStatEnv0#static_env{
+ file_ref_db = FileRefHandle,
+ cert_db_ref = Ref,
+ cert_db = CertDbHandle,
+ crl_db = CRLDbHandle,
+ session_cache = CacheHandle
+ },
private_key = Key,
diffie_hellman_params = DHParams,
ssl_options = NewOptions,
@@ -2762,13 +2828,10 @@ new_emulated([], EmOpts) ->
new_emulated(NewEmOpts, _) ->
NewEmOpts.
-stop(Reason, State) ->
- {stop, Reason, State}.
-
-stop_and_reply(Reason, Replies, State) ->
- {stop_and_reply, Reason, Replies, State}.
-
-is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined ->
- true;
-is_dist_up(_) ->
- false.
+-compile({inline, [bincat/2]}).
+bincat(<<>>, B) ->
+ B;
+bincat(A, <<>>) ->
+ A;
+bincat(A, B) ->
+ <<A/binary, B/binary>>.
diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl
index 91467e9b26..dd3bdd7478 100644
--- a/lib/ssl/src/ssl_connection.hrl
+++ b/lib/ssl/src/ssl_connection.hrl
@@ -33,75 +33,145 @@
-include("ssl_cipher.hrl").
-include_lib("public_key/include/public_key.hrl").
+-record(static_env, {
+ role :: client | server,
+ transport_cb :: atom(), % callback module
+ protocol_cb :: tls_connection | dtls_connection,
+ data_tag :: atom(), % ex tcp.
+ close_tag :: atom(), % ex tcp_closed
+ error_tag :: atom(), % ex tcp_error
+ host :: string() | inet:ip_address(),
+ port :: integer(),
+ socket :: port() | tuple(), %% TODO: dtls socket
+ cert_db :: reference() | 'undefined',
+ session_cache :: db_handle(),
+ session_cache_cb :: atom(),
+ crl_db :: term(),
+ file_ref_db :: db_handle(),
+ cert_db_ref :: certdb_ref() | 'undefined',
+ tracker :: pid() | 'undefined' %% Tracker process for listen socket
+ }).
+
+-record(handshake_env, {
+ client_hello_version :: ssl_record:ssl_version() | 'undefined',
+ unprocessed_handshake_events = 0 :: integer(),
+ tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
+ | 'undefined',
+ renegotiation :: undefined | {boolean(), From::term() | internal | peer}
+ }).
+
-record(state, {
- role :: client | server,
- user_application :: {Monitor::reference(), User::pid()},
- transport_cb :: atom(), % callback module
- protocol_cb :: tls_connection | dtls_connection,
- data_tag :: atom(), % ex tcp.
- close_tag :: atom(), % ex tcp_closed
- error_tag :: atom(), % ex tcp_error
- host :: string() | inet:ip_address(),
- port :: integer(),
- socket :: port() | tuple(), %% TODO: dtls socket
- sender :: pid() | undefined,
- ssl_options :: #ssl_options{},
- socket_options :: #socket_options{},
- connection_states :: ssl_record:connection_states() | secret_printout(),
- protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl
- unprocessed_handshake_events = 0 :: integer(),
- tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout()
- | 'undefined',
- cert_db :: reference() | 'undefined',
- session :: #session{} | secret_printout(),
- session_cache :: db_handle(),
- session_cache_cb :: atom(),
- crl_db :: term(),
- negotiated_version :: ssl_record:ssl_version() | 'undefined',
- client_hello_version :: ssl_record:ssl_version() | 'undefined',
- client_certificate_requested = false :: boolean(),
- key_algorithm :: ssl_cipher_format:key_algo(),
- hashsign_algorithm = {undefined, undefined},
- cert_hashsign_algorithm = {undefined, undefined},
- public_key_info :: ssl_handshake:public_key_info() | 'undefined',
- private_key :: public_key:private_key() | secret_printout() | 'undefined',
- diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
- diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
- psk_identity :: binary() | 'undefined', % server psk identity hint
- srp_params :: #srp_user{} | secret_printout() | 'undefined',
- srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined',
- premaster_secret :: binary() | secret_printout() | 'undefined',
- file_ref_db :: db_handle(),
- cert_db_ref :: certdb_ref() | 'undefined',
- bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
- user_data_buffer :: undefined | binary() | secret_printout(),
- erl_dist_data = #{} :: map(),
- renegotiation :: undefined | {boolean(), From::term() | internal | peer},
- start_or_recv_from :: term(),
- timer :: undefined | reference(), % start_or_recive_timer
- %%send_queue :: queue:queue(),
- hello, %%:: #client_hello{} | #server_hello{},
- terminated = false ::boolean(),
- allow_renegotiate = true ::boolean(),
- expecting_next_protocol_negotiation = false ::boolean(),
- expecting_finished = false ::boolean(),
- next_protocol = undefined :: undefined | binary(),
- negotiated_protocol,
- tracker :: pid() | 'undefined', %% Tracker process for listen socket
- sni_hostname = undefined,
- downgrade,
- flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
- %% to when possible pack more than one TLS record into the
- %% underlaying packet format. Introduced by DTLS - RFC 4347.
- %% The mecahnism is also usefull in TLS although we do not
- %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
- flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
- protocol_specific = #{} :: map(),
- key_share
- }).
+ static_env :: #static_env{},
+ handshake_env :: #handshake_env{} | secret_printout(),
+ %% Change seldome
+ user_application :: {Monitor::reference(), User::pid()},
+ ssl_options :: #ssl_options{},
+ socket_options :: #socket_options{},
+ session :: #session{} | secret_printout(),
+ allow_renegotiate = true ::boolean(),
+ terminated = false ::boolean() | closed,
+ negotiated_version :: ssl_record:ssl_version() | 'undefined',
+ bytes_to_read :: undefined | integer(), %% bytes to read in passive mode
+ downgrade,
+
+ %% Changed often
+ connection_states :: ssl_record:connection_states() | secret_printout(),
+ protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr
+ user_data_buffer :: undefined | binary() | secret_printout(),
+
+ %% Used only in HS
+
+ client_certificate_requested = false :: boolean(),
+ key_algorithm :: ssl_cipher_format:key_algo(),
+ hashsign_algorithm = {undefined, undefined},
+ cert_hashsign_algorithm = {undefined, undefined},
+ public_key_info :: ssl_handshake:public_key_info() | 'undefined',
+ private_key :: public_key:private_key() | secret_printout() | 'undefined',
+ diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(),
+ diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(),
+ psk_identity :: binary() | 'undefined', % server psk identity hint
+ srp_params :: #srp_user{} | secret_printout() | 'undefined',
+ srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined',
+ premaster_secret :: binary() | secret_printout() | 'undefined',
+ start_or_recv_from :: term(),
+ timer :: undefined | reference(), % start_or_recive_timer
+ hello, %%:: #client_hello{} | #server_hello{},
+ expecting_next_protocol_negotiation = false ::boolean(),
+ expecting_finished = false ::boolean(),
+ next_protocol = undefined :: undefined | binary(),
+ negotiated_protocol,
+ sni_hostname = undefined,
+ flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake
+ %% to when possible pack more than one TLS record into the
+ %% underlaying packet format. Introduced by DTLS - RFC 4347.
+ %% The mecahnism is also usefull in TLS although we do not
+ %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr
+ flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp.
+ erl_dist_handle = undefined :: erlang:dist_handle() | undefined,
+ protocol_specific = #{} :: map(),
+ key_share
+ }).
+
-define(DEFAULT_DIFFIE_HELLMAN_PARAMS,
#'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}).
-define(WAIT_TO_ALLOW_RENEGOTIATION, 12000).
+
+%%----------------------------------------------------------------------
+%% TLS 1.3
+%%----------------------------------------------------------------------
+
+%% TLS 1.3 uses the same state record with the following differences:
+%%
+%% state :: record()
+%%
+%% session_cache - not implemented
+%% session_cache_cb - not implemented
+%% crl_db - not implemented
+%% client_hello_version - Bleichenbacher mitigation in TLS 1.2
+%% client_certificate_requested - Built into TLS 1.3 state machine
+%% key_algorithm - not used
+%% diffie_hellman_params - used in TLS 1.2 ECDH key exchange
+%% diffie_hellman_keys - used in TLS 1.2 ECDH key exchange
+%% psk_identity - not used
+%% srp_params - not used, no srp extension in TLS 1.3
+%% srp_keys - not used, no srp extension in TLS 1.3
+%% premaster_secret - not used
+%% renegotiation - TLS 1.3 forbids renegotiation
+%% hello - used in user_hello, handshake continue
+%% allow_renegotiate - TLS 1.3 forbids renegotiation
+%% expecting_next_protocol_negotiation - ALPN replaced NPN, depricated in TLS 1.3
+%% expecting_finished - not implemented, used by abbreviated
+%% next_protocol - ALPN replaced NPN, depricated in TLS 1.3
+%%
+%% connection_state :: map()
+%%
+%% compression_state - not used
+%% mac_secret - not used
+%% sequence_number - not used
+%% secure_renegotiation - not used, no renegotiation_info in TLS 1.3
+%% client_verify_data - not used, no renegotiation_info in TLS 1.3
+%% server_verify_data - not used, no renegotiation_info in TLS 1.3
+%% beast_mitigation - not used
+%%
+%% security_parameters :: map()
+%%
+%% cipher_type - TLS 1.3 uses only AEAD ciphers
+%% iv_size - not used
+%% key_size - not used
+%% key_material_length - not used
+%% expanded_key_material_length - used in SSL 3.0
+%% mac_algorithm - not used
+%% prf_algorithm - not used
+%% hash_size - not used
+%% compression_algorithm - not used
+%% master_secret - used for multiple secret types in TLS 1.3
+%% client_random - not used
+%% server_random - not used
+%% exportable - not used
+%%
+%% cipher_state :: record()
+%% nonce - used for sequence_number
+
-endif. % -ifdef(ssl_connection).
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 417e5d9eb6..5e3c767c2c 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -61,7 +61,7 @@
-export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2,
encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]).
%% Decode
--export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, decode_extensions/3,
+-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3,
decode_server_key/3, decode_client_key/3,
decode_suites/2
]).
@@ -639,7 +639,7 @@ encode_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Re
?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>);
encode_extensions([#srp{username = UserName} | Rest], Acc) ->
SRPLen = byte_size(UserName),
- Len = SRPLen + 2,
+ Len = SRPLen + 1,
encode_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
UserName/binary, Acc/binary>>);
encode_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) ->
@@ -680,9 +680,9 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) ->
encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) ->
Versions = encode_versions(Versions0),
VerLen = byte_size(Versions),
- Len = VerLen + 2,
+ Len = VerLen + 1,
encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT),
- ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>);
+ ?UINT16(Len), ?BYTE(VerLen), Versions/binary, Acc/binary>>);
encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) ->
Version = encode_versions([Version0]),
Len = byte_size(Version), %% 2
@@ -745,8 +745,7 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32
?BYTE(SID_length), Session_ID:SID_length/binary,
Cipher_suite:2/binary, ?BYTE(Comp_method),
?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
-
- HelloExtensions = decode_hello_extensions(Extensions, Version, server_hello),
+ HelloExtensions = decode_hello_extensions(Extensions, Version, {Major, Minor}, server_hello),
#server_hello{
server_version = {Major,Minor},
@@ -803,11 +802,12 @@ decode_vector(<<?UINT16(Len), Vector:Len/binary>>) ->
Vector.
%%--------------------------------------------------------------------
--spec decode_hello_extensions(binary(), ssl_record:ssl_version(), atom()) -> map().
+-spec decode_hello_extensions(binary(), ssl_record:ssl_version(),
+ ssl_record:ssl_version(), atom()) -> map().
%%
%% Description: Decodes TLS hello extensions
%%--------------------------------------------------------------------
-decode_hello_extensions(Extensions, Version, MessageType0) ->
+decode_hello_extensions(Extensions, LocalVersion, LegacyVersion, MessageType0) ->
%% Convert legacy atoms
MessageType =
case MessageType0 of
@@ -815,6 +815,13 @@ decode_hello_extensions(Extensions, Version, MessageType0) ->
server -> server_hello;
T -> T
end,
+ %% RFC 8446 - 4.2.1
+ %% Servers MUST be prepared to receive ClientHellos that include this extension but
+ %% do not include 0x0304 in the list of versions.
+ %% Clients MUST check for this extension prior to processing the rest of the
+ %% ServerHello (although they will have to parse the ServerHello in order to read
+ %% the extension).
+ Version = process_supported_versions_extension(Extensions, LocalVersion, LegacyVersion),
decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)).
%%--------------------------------------------------------------------
@@ -1167,7 +1174,12 @@ kse_remove_private_key(#key_share_entry{
signature_algs_ext(undefined) ->
undefined;
-signature_algs_ext(SignatureSchemes) ->
+signature_algs_ext(SignatureSchemes0) ->
+ %% The SSL option signature_algs contains both hash-sign algorithms (tuples) and
+ %% signature schemes (atoms) if TLS 1.3 is configured.
+ %% Filter out all hash-sign tuples when creating the signature_algs extension.
+ %% (TLS 1.3 specific record type)
+ SignatureSchemes = lists:filter(fun is_atom/1, SignatureSchemes0),
#signature_algorithms{signature_scheme_list = SignatureSchemes}.
signature_algs_cert(undefined) ->
@@ -1191,7 +1203,8 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
Empty = empty_extensions(Version, server_hello),
ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
ConnectionStates, Renegotiation),
- ec_point_formats => server_ecc_extension(Version, maps:get(ec_point_formats, Exts, undefined))
+ ec_point_formats => server_ecc_extension(Version,
+ maps:get(ec_point_formats, Exts, undefined))
},
%% If we receive an ALPN extension and have ALPN configured for this connection,
@@ -1199,13 +1212,9 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
ALPN = maps:get(alpn, Exts, undefined),
if
ALPN =/= undefined, ALPNPreferredProtocols =/= undefined ->
- case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {Session, ConnectionStates, Protocol,
- ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}}
- end;
+ Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)),
+ {Session, ConnectionStates, Protocol,
+ ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}};
true ->
NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
@@ -1219,7 +1228,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
#ssl_options{secure_renegotiate = SecureRenegotation,
next_protocol_selector = NextProtoSelector},
ConnectionStates0, Renegotiation) ->
- ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random,
+ ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version,
+ maps:get(renegotiation_info, Exts, undefined), Random,
CipherSuite, undefined,
Compression, ConnectionStates0,
Renegotiation, SecureRenegotation),
@@ -1234,12 +1244,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
{ConnectionStates, alpn, Protocol};
undefined ->
NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
- case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of
- #alert{} = Alert ->
- Alert;
- Protocol ->
- {ConnectionStates, npn, Protocol}
- end;
+ Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation),
+ {ConnectionStates, npn, Protocol};
{error, Reason} ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
[] ->
@@ -2201,6 +2207,47 @@ dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) ->
dec_server_key_signature(_, _, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)).
+%% Processes a ClientHello/ServerHello message and returns the version to be used
+%% in the decoding functions. The following rules apply:
+%% - IF supported_versions extension is absent:
+%% RETURN the lowest of (LocalVersion and LegacyVersion)
+%% - IF supported_versions estension is present:
+%% RETURN the lowest of (LocalVersion and first element of supported versions)
+process_supported_versions_extension(<<>>, LocalVersion, LegacyVersion)
+ when LegacyVersion =< LocalVersion ->
+ LegacyVersion;
+process_supported_versions_extension(<<>>, LocalVersion, _LegacyVersion) ->
+ LocalVersion;
+process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ExtData:Len/binary, _Rest/binary>>,
+ LocalVersion, _LegacyVersion) when Len > 2 ->
+ <<?BYTE(_),Versions0/binary>> = ExtData,
+ [Highest|_] = decode_versions(Versions0),
+ if Highest =< LocalVersion ->
+ Highest;
+ true ->
+ LocalVersion
+ end;
+process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
+ ?BYTE(Major),?BYTE(Minor), _Rest/binary>>,
+ LocalVersion, _LegacyVersion) when Len =:= 2 ->
+ SelectedVersion = {Major, Minor},
+ if SelectedVersion =< LocalVersion ->
+ SelectedVersion;
+ true ->
+ LocalVersion
+ end;
+process_supported_versions_extension(<<?UINT16(_), ?UINT16(Len),
+ _ExtData:Len/binary, Rest/binary>>,
+ LocalVersion, LegacyVersion) ->
+ process_supported_versions_extension(Rest, LocalVersion, LegacyVersion);
+%% Tolerate protocol encoding errors and skip parsing the rest of the extension.
+process_supported_versions_extension(_, LocalVersion, LegacyVersion)
+ when LegacyVersion =< LocalVersion ->
+ LegacyVersion;
+process_supported_versions_extension(_, LocalVersion, _) ->
+ LocalVersion.
+
decode_extensions(<<>>, _Version, _MessageType, Acc) ->
Acc;
decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len),
@@ -2229,7 +2276,7 @@ decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len),
decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen),
SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc)
- when Len == SRPLen + 2 ->
+ when Len == SRPLen + 1 ->
decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}});
decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len),
@@ -2327,7 +2374,7 @@ decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len),
decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len),
ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 ->
- <<?UINT16(_),Versions/binary>> = ExtData,
+ <<?BYTE(_),Versions/binary>> = ExtData,
decode_extensions(Rest, Version, MessageType,
Acc#{client_hello_versions =>
#client_hello_versions{
@@ -2596,30 +2643,26 @@ filter_unavailable_ecc_suites(_, Suites) ->
handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite,
ClientCipherSuites, Compression,
ConnectionStates0, Renegotiation, SecureRenegotation) ->
- case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
- Renegotiation, SecureRenegotation,
- ClientCipherSuites) of
- {ok, ConnectionStates} ->
- hello_pending_connection_states(RecordCB, Role,
- Version,
- NegotiatedCipherSuite,
- Random,
- Compression,
- ConnectionStates);
- #alert{} = Alert ->
- throw(Alert)
- end.
+ {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation,
+ ClientCipherSuites),
+ hello_pending_connection_states(RecordCB, Role,
+ Version,
+ NegotiatedCipherSuite,
+ Random,
+ Compression,
+ ConnectionStates).
%% Receive protocols, choose one from the list, return it.
handle_alpn_extension(_, {error, Reason}) ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason));
handle_alpn_extension([], _) ->
- ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL);
+ throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL));
handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) ->
- case lists:member(ServerProtocol, ClientProtocols) of
- true -> ServerProtocol;
- false -> handle_alpn_extension(Tail, ClientProtocols)
- end.
+ case lists:member(ServerProtocol, ClientProtocols) of
+ true -> ServerProtocol;
+ false -> handle_alpn_extension(Tail, ClientProtocols)
+ end.
handle_next_protocol(undefined,
_NextProtocolSelector, _Renegotiating) ->
@@ -2632,14 +2675,14 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols,
true ->
select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector);
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension))
end.
handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)->
case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of
#alert{} = Alert ->
- Alert;
+ throw(Alert);
ProtocolsToAdvertise ->
ProtocolsToAdvertise
end.
@@ -2894,14 +2937,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation))
end;
handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify},
ConnectionStates, true, _, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
false ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
Data = maps:get(client_verify_data, ConnectionState),
@@ -2909,7 +2952,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co
true ->
{ok, ConnectionStates};
false ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation))
end
end;
@@ -2919,7 +2962,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S
handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
true ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv});
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}));
false ->
handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation)
end.
@@ -2928,9 +2971,9 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) ->
ConnectionState = ssl_record:current_connection_state(ConnectionStates, read),
case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of
{_, true} ->
- ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure);
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure));
{true, false} ->
- ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
+ throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION));
{false, false} ->
{ok, ConnectionStates}
end.
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index 48798799f7..57b72366d3 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -61,6 +61,7 @@
-define(CDR_MAGIC, "GIOP").
-define(CDR_HDR_SIZE, 12).
+-define(INTERNAL_ACTIVE_N, 100).
-define(DEFAULT_TIMEOUT, 5000).
-define(NO_DIST_POINT, "http://dummy/no_distribution_point").
@@ -136,10 +137,10 @@
%% Local policy for the server if it want's to reuse the session
%% or not. Defaluts to allways returning true.
%% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean()
- reuse_session,
+ reuse_session :: fun() | binary() | undefined, %% Server side is a fun()
%% If false sessions will never be reused, if true they
%% will be reused if possible.
- reuse_sessions :: boolean(),
+ reuse_sessions :: boolean() | save, %% Only client side can use value save
renegotiate_at,
secure_renegotiate,
client_renegotiation,
@@ -175,6 +176,8 @@
max_handshake_size :: integer(),
handshake,
customize_hostname_check
+ %% ,
+ %% save_session :: boolean()
}).
-record(socket_options,
diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl
index 35c8dcfd48..ce8225bf72 100644
--- a/lib/ssl/src/ssl_logger.erl
+++ b/lib/ssl/src/ssl_logger.erl
@@ -32,6 +32,7 @@
-define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))).
-include("tls_record.hrl").
+-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include("tls_handshake.hrl").
-include_lib("kernel/include/logger.hrl").
@@ -87,20 +88,32 @@ format_handshake(Direction, BinMsg) ->
parse_handshake(Direction, #client_hello{
- client_version = Version
+ client_version = Version0,
+ cipher_suites = CipherSuites0,
+ extensions = Extensions
} = ClientHello) ->
+ Version = get_client_version(Version0, Extensions),
Header = io_lib:format("~s ~s Handshake, ClientHello",
[header_prefix(Direction),
version(Version)]),
- Message = io_lib:format("~p", [?rec_info(client_hello, ClientHello)]),
+ CipherSuites = parse_cipher_suites(CipherSuites0),
+ Message = io_lib:format("~p",
+ [?rec_info(client_hello,
+ ClientHello#client_hello{cipher_suites = CipherSuites})]),
{Header, Message};
parse_handshake(Direction, #server_hello{
- server_version = Version
+ server_version = Version0,
+ cipher_suite = CipherSuite0,
+ extensions = Extensions
} = ServerHello) ->
+ Version = get_server_version(Version0, Extensions),
Header = io_lib:format("~s ~s Handshake, ServerHello",
[header_prefix(Direction),
version(Version)]),
- Message = io_lib:format("~p", [?rec_info(server_hello, ServerHello)]),
+ CipherSuite = format_cipher(CipherSuite0),
+ Message = io_lib:format("~p",
+ [?rec_info(server_hello,
+ ServerHello#server_hello{cipher_suite = CipherSuite})]),
{Header, Message};
parse_handshake(Direction, #certificate{} = Certificate) ->
Header = io_lib:format("~s Handshake, Certificate",
@@ -148,7 +161,34 @@ parse_handshake(Direction, #hello_request{} = HelloRequest) ->
Message = io_lib:format("~p", [?rec_info(hello_request, HelloRequest)]),
{Header, Message}.
+parse_cipher_suites([_|_] = Ciphers) ->
+ [format_cipher(C) || C <- Ciphers].
+
+format_cipher(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) ->
+ 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV';
+format_cipher(C0) ->
+ list_to_atom(ssl_cipher_format:openssl_suite_name(C0)).
+
+get_client_version(Version, Extensions) ->
+ CHVersions = maps:get(client_hello_versions, Extensions, undefined),
+ case CHVersions of
+ #client_hello_versions{versions = [Highest|_]} ->
+ Highest;
+ undefined ->
+ Version
+ end.
+
+get_server_version(Version, Extensions) ->
+ SHVersion = maps:get(server_hello_selected_version, Extensions, undefined),
+ case SHVersion of
+ #server_hello_selected_version{selected_version = SelectedVersion} ->
+ SelectedVersion;
+ undefined ->
+ Version
+ end.
+version({3,4}) ->
+ "TLS 1.3";
version({3,3}) ->
"TLS 1.2";
version({3,2}) ->
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index c938772bc1..b1f080b0fe 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -30,7 +30,7 @@
connection_init/3, cache_pem_file/2,
lookup_trusted_cert/4,
new_session_id/1, clean_cert_db/2,
- register_session/2, register_session/3, invalidate_session/2,
+ register_session/2, register_session/4, invalidate_session/2,
insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2,
invalidate_session/3, name/1]).
@@ -170,9 +170,11 @@ clean_cert_db(Ref, File) ->
%%
%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
--spec register_session(host(), inet:port_number(), #session{}) -> ok.
-register_session(Host, Port, Session) ->
- cast({register_session, Host, Port, Session}).
+-spec register_session(host(), inet:port_number(), #session{}, unique | true) -> ok.
+register_session(Host, Port, Session, true) ->
+ call({register_session, Host, Port, Session});
+register_session(Host, Port, Session, unique = Save) ->
+ cast({register_session, Host, Port, Session, Save}).
-spec register_session(inet:port_number(), #session{}) -> ok.
register_session(Port, Session) ->
@@ -301,7 +303,10 @@ handle_call({{new_session_id, Port}, _},
_, #state{session_cache_cb = CacheCb,
session_cache_server = Cache} = State) ->
Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb),
- {reply, Id, State}.
+ {reply, Id, State};
+handle_call({{register_session, Host, Port, Session},_}, _, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
+ {reply, ok, State}.
%%--------------------------------------------------------------------
-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}.
@@ -311,8 +316,12 @@ handle_call({{new_session_id, Port}, _},
%%
%% Description: Handling cast messages
%%--------------------------------------------------------------------
-handle_cast({register_session, Host, Port, Session}, State0) ->
- State = ssl_client_register_session(Host, Port, Session, State0),
+handle_cast({register_session, Host, Port, Session, unique}, State0) ->
+ State = client_register_unique_session(Host, Port, Session, State0),
+ {noreply, State};
+
+handle_cast({register_session, Host, Port, Session, true}, State0) ->
+ State = client_register_session(Host, Port, Session, State0),
{noreply, State};
handle_cast({register_session, Port, Session}, State0) ->
@@ -540,10 +549,10 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) ->
ok
end.
-ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
- session_cache_cb = CacheCb,
- session_cache_client_max = Max,
- session_client_invalidator = Pid0} = State) ->
+client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
TimeStamp = erlang:monotonic_time(),
NewSession = Session#session{time_stamp = TimeStamp},
@@ -557,6 +566,17 @@ ssl_client_register_session(Host, Port, Session, #state{session_cache_client = C
register_unique_session(Sessions, NewSession, {Host, Port}, State)
end.
+client_register_session(Host, Port, Session, #state{session_cache_client = Cache,
+ session_cache_cb = CacheCb,
+ session_cache_client_max = Max,
+ session_client_invalidator = Pid0} = State) ->
+ TimeStamp = erlang:monotonic_time(),
+ NewSession = Session#session{time_stamp = TimeStamp},
+ Pid = do_register_session({{Host, Port},
+ NewSession#session.session_id},
+ NewSession, Max, Pid0, Cache, CacheCb),
+ State#state{session_client_invalidator = Pid}.
+
server_register_session(Port, Session, #state{session_cache_server_max = Max,
session_cache_server = Cache,
session_cache_cb = CacheCb,
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index ddc83821b4..499ba108f2 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -39,7 +39,8 @@
set_renegotiation_flag/2,
set_client_verify_data/3,
set_server_verify_data/3,
- empty_connection_state/2, initial_connection_state/2, record_protocol_role/1]).
+ empty_connection_state/2, initial_connection_state/2, record_protocol_role/1,
+ step_encryption_state/1]).
%% Compression
-export([compress/3, uncompress/3, compressions/0]).
@@ -118,6 +119,20 @@ activate_pending_connection_state(#{current_write := Current,
}.
%%--------------------------------------------------------------------
+-spec step_encryption_state(connection_states()) -> connection_states().
+%%
+%% Description: Activates the next encyrption state (e.g. handshake
+%% encryption).
+%%--------------------------------------------------------------------
+step_encryption_state(#{pending_read := PendingRead,
+ pending_write := PendingWrite} = States) ->
+ NewRead = PendingRead#{sequence_number => 0},
+ NewWrite = PendingWrite#{sequence_number => 0},
+ States#{current_read => NewRead,
+ current_write => NewWrite}.
+
+
+%%--------------------------------------------------------------------
-spec set_security_params(#security_parameters{}, #security_parameters{},
connection_states()) -> connection_states().
%%
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index c9607489e9..a9759c9b43 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -53,6 +53,13 @@ is_new(_ClientSuggestion, _ServerDecision) ->
%% Description: Should be called by the client side to get an id
%% for the client hello message.
%%--------------------------------------------------------------------
+client_id({Host, Port, #ssl_options{reuse_session = SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)->
+ case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of
+ undefined ->
+ <<>>;
+ #session{} ->
+ SessionId
+ end;
client_id(ClientInfo, Cache, CacheCb, OwnCert) ->
case select_session(ClientInfo, Cache, CacheCb, OwnCert) of
no_session ->
@@ -91,7 +98,8 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) ->
+select_session({_, _, #ssl_options{reuse_sessions = Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true ->
+ %% If reuse_sessions == true | save a new session should be created
no_session;
select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) ->
Sessions = CacheCb:select_session(Cache, {HostIP, Port}),
@@ -132,7 +140,7 @@ is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} =
false -> {false, undefined}
end;
undefined ->
- {false, undefined}
+ {false, undefined}
end.
resumable(new) ->
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 5e6ba652f0..26f9fc99d3 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -17,7 +17,6 @@
%%
%% %CopyrightEnd%
%%
-
%%
%%----------------------------------------------------------------------
%% Purpose: Handles an ssl connection, e.i. both the setup
@@ -47,8 +46,8 @@
-export([start_fsm/8, start_link/8, init/1, pids/1]).
%% State transition handling
--export([next_record/1, next_event/3, next_event/4,
- handle_common_event/4]).
+-export([next_event/3, next_event/4,
+ handle_protocol_record/3]).
%% Handshake handling
-export([renegotiation/2, renegotiate/2, send_handshake/2,
@@ -62,7 +61,7 @@
encode_alert/3, close/5, protocol_name/0]).
%% Data handling
--export([encode_data/3, passive_receive/2, next_record_if_active/1,
+-export([encode_data/3, next_record/1,
send/3, socket/5, setopts/3, getopts/3]).
%% gen_statem state functions
@@ -162,49 +161,51 @@ pids(#state{protocol_specific = #{sender := Sender}}) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 ->
- {no_record, State#state{unprocessed_handshake_events = N-1}};
-
+next_record(#state{handshake_env =
+ #handshake_env{unprocessed_handshake_events = N} = HsEnv}
+ = State) when N > 0 ->
+ {no_record, State#state{handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
next_record(#state{protocol_buffers =
- #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]}
- = Buffers,
- connection_states = ConnStates0,
+ #protocol_buffers{tls_packets = [], tls_cipher_texts = [#ssl_tls{type = Type}| _] = CipherTexts0}
+ = Buffers,
+ connection_states = ConnectionStates0,
negotiated_version = Version,
- ssl_options = #ssl_options{padding_check = Check}} = State) ->
-
- case tls_record:decode_cipher_text(Version, CT, ConnStates0, Check) of
- {Plain, ConnStates} ->
- {Plain, State#state{protocol_buffers =
- Buffers#protocol_buffers{tls_cipher_texts = Rest},
- connection_states = ConnStates}};
- #alert{} = Alert ->
- {Alert, State}
- end;
+ ssl_options = #ssl_options{padding_check = Check}} = State) ->
+ case decode_cipher_texts(Version, Type, CipherTexts0, ConnectionStates0, Check, <<>>) of
+ {#ssl_tls{} = Record, ConnectionStates, CipherTexts} ->
+ {Record, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}};
+ {#alert{} = Alert, ConnectionStates, CipherTexts} ->
+ {Alert, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
+ connection_states = ConnectionStates}}
+ end;
next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []},
- socket = Socket,
- close_tag = CloseTag,
- transport_cb = Transport} = State) ->
- case tls_socket:setopts(Transport, Socket, [{active,once}]) of
- ok ->
- {no_record, State};
- _ ->
- self() ! {CloseTag, Socket},
- {no_record, State}
- end;
+ protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
+ static_env = #static_env{socket = Socket,
+ close_tag = CloseTag,
+ transport_cb = Transport}
+ } = State) ->
+ case tls_socket:setopts(Transport, Socket, [{active, N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, Socket},
+ {no_record, State}
+ end;
next_record(State) ->
{no_record, State}.
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
-
-next_event(connection = StateName, no_record, State0, Actions) ->
- case next_record_if_active(State0) of
- {no_record, State} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {#ssl_tls{} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- {#alert{} = Alert, State} ->
- {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
+next_event(StateName, no_record, State0, Actions) ->
+ case next_record(State0) of
+ {no_record, State} ->
+ {next_state, StateName, State, Actions};
+ {#ssl_tls{} = Record, State} ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+ {#alert{} = Alert, State} ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end;
next_event(StateName, Record, State, Actions) ->
case Record of
@@ -216,11 +217,34 @@ next_event(StateName, Record, State, Actions) ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end.
-handle_common_event(internal, #alert{} = Alert, StateName,
- #state{negotiated_version = Version} = State) ->
- ssl_connection:handle_own_alert(Alert, Version, StateName, State);
+decode_cipher_texts(_, Type, [] = CipherTexts, ConnectionStates, _, Acc) ->
+ {#ssl_tls{type = Type, fragment = Acc}, ConnectionStates, CipherTexts};
+decode_cipher_texts(Version, Type,
+ [#ssl_tls{type = Type} = CT | CipherTexts], ConnectionStates0, Check, Acc) ->
+ case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of
+ {#ssl_tls{type = ?APPLICATION_DATA, fragment = Plain}, ConnectionStates} ->
+ decode_cipher_texts(Version, Type, CipherTexts,
+ ConnectionStates, Check, <<Acc/binary, Plain/binary>>);
+ {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates} ->
+ {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates, CipherTexts};
+ #alert{} = Alert ->
+ {Alert, ConnectionStates0, CipherTexts}
+ end;
+decode_cipher_texts(_, Type, CipherTexts, ConnectionStates, _, Acc) ->
+ {#ssl_tls{type = Type, fragment = Acc}, ConnectionStates, CipherTexts}.
+
+%%% TLS record protocol level application data messages
+
+handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) ->
+ case ssl_connection:read_application_data(Data, State0) of
+ {stop, _, _} = Stop->
+ Stop;
+ {Record, State1} ->
+ {next_state, StateName, State, Actions} = next_event(StateName, Record, State1),
+ ssl_connection:hibernate_after(StateName, State, Actions)
+ end;
%%% TLS record protocol level handshake messages
-handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
+handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
StateName, #state{protocol_buffers =
#protocol_buffers{tls_handshake_buffer = Buf0} = Buffers,
negotiated_version = Version,
@@ -228,36 +252,36 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
try
EffectiveVersion = effective_version(Version, Options),
{Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options),
- State1 =
+ State =
State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
case Packets of
[] ->
assert_buffer_sanity(Buf, Options),
- {Record, State} = next_record(State1),
- next_event(StateName, Record, State);
+ next_event(StateName, no_record, State);
_ ->
Events = tls_handshake_events(Packets),
case StateName of
connection ->
- ssl_connection:hibernate_after(StateName, State1, Events);
+ ssl_connection:hibernate_after(StateName, State, Events);
_ ->
+ HsEnv = State#state.handshake_env,
{next_state, StateName,
- State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
+ State#state{protocol_buffers = Buffers,
+ handshake_env =
+ HsEnv#handshake_env{unprocessed_handshake_events
+ = unprocessed_events(Events)}}, Events}
end
end
catch throw:#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
end;
-%%% TLS record protocol level application data messages
-handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
- {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]};
%%% TLS record protocol level change cipher messages
-handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
+handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) ->
{next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]};
%%% TLS record protocol level Alert messages
-handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
- #state{negotiated_version = Version} = State) ->
+handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName,
+ #state{negotiated_version = Version} = State) ->
try decode_alerts(EncAlerts) of
Alerts = [_|_] ->
handle_alerts(Alerts, {next_state, StateName, State});
@@ -273,23 +297,25 @@ handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, Sta
end;
%% Ignore unknown TLS record level protocol messages
-handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) ->
- {next_state, StateName, State}.
+handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) ->
+ {next_state, StateName, State, []}.
%%====================================================================
%% Handshake handling
%%====================================================================
renegotiation(Pid, WriteState) ->
gen_statem:call(Pid, {user_renegotiate, WriteState}).
-renegotiate(#state{role = client} = State, Actions) ->
+renegotiate(#state{static_env = #static_env{role = client},
+ handshake_env = HsEnv} = State, Actions) ->
%% Handle same way as if server requested
%% the renegotiation
Hs0 = ssl_handshake:init_handshake_history(),
- {next_state, connection, State#state{tls_handshake_history = Hs0},
+ {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
[{next_event, internal, #hello_request{}} | Actions]};
-renegotiate(#state{role = server,
- socket = Socket,
- transport_cb = Transport,
+renegotiate(#state{static_env = #static_env{role = server,
+ socket = Socket,
+ transport_cb = Transport},
+ handshake_env = HsEnv,
negotiated_version = Version,
connection_states = ConnectionStates0} = State0, Actions) ->
HelloRequest = ssl_handshake:hello_request(),
@@ -298,17 +324,16 @@ renegotiate(#state{role = server,
{BinMsg, ConnectionStates} =
tls_record:encode_handshake(Frag, Version, ConnectionStates0),
send(Transport, Socket, BinMsg),
- State1 = State0#state{connection_states =
+ State = State0#state{connection_states =
ConnectionStates,
- tls_handshake_history = Hs0},
- {Record, State} = next_record(State1),
- next_event(hello, Record, State, Actions).
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}},
+ next_event(hello, no_record, State, Actions).
send_handshake(Handshake, State) ->
send_handshake_flight(queue_handshake(Handshake, State)).
queue_handshake(Handshake, #state{negotiated_version = Version,
- tls_handshake_history = Hist0,
+ handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv,
flight_buffer = Flight0,
connection_states = ConnectionStates0,
ssl_options = SslOpts} = State0) ->
@@ -324,11 +349,11 @@ queue_handshake(Handshake, #state{negotiated_version = Version,
ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
State0#state{connection_states = ConnectionStates,
- tls_handshake_history = Hist,
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist},
flight_buffer = Flight0 ++ [BinHandshake]}.
-send_handshake_flight(#state{socket = Socket,
- transport_cb = Transport,
+send_handshake_flight(#state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
flight_buffer = Flight} = State0) ->
send(Transport, Socket, Flight),
{State0#state{flight_buffer = []}, []}.
@@ -352,14 +377,14 @@ reinit(#state{protocol_specific = #{sender := Sender},
tls_sender:update_connection_state(Sender, Write, Version),
reinit_handshake_data(State).
-reinit_handshake_data(State) ->
+reinit_handshake_data(#state{handshake_env = HsEnv} =State) ->
%% premaster_secret, public_key_info and tls_handshake_info
%% are only needed during the handshake phase.
%% To reduce memory foot print of a connection reinitialize them.
State#state{
premaster_secret = undefined,
public_key_info = undefined,
- tls_handshake_history = ssl_handshake:init_handshake_history()
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history()}
}.
select_sni_extension(#client_hello{extensions = #{sni := SNI}}) ->
@@ -383,12 +408,13 @@ empty_connection_state(ConnectionEnd, BeastMitigation) ->
encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
tls_record:encode_alert_record(Alert, Version, ConnectionStates).
-send_alert(Alert, #state{negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- ssl_options = SslOpts} = StateData0) ->
- {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0),
+send_alert(Alert, #state{negotiated_version = Version,
+ static_env = #static_env{socket = Socket,
+ transport_cb = Transport},
+ ssl_options = SslOpts,
+ connection_states = ConnectionStates0} = StateData0) ->
+ {BinMsg, ConnectionStates} =
+ encode_alert(Alert, Version, ConnectionStates0),
send(Transport, Socket, BinMsg),
Report = #{direction => outbound,
protocol => 'tls_record',
@@ -408,13 +434,11 @@ send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) ->
send_alert_in_connection(Alert,
#state{protocol_specific = #{sender := Sender}}) ->
tls_sender:send_alert(Sender, Alert).
-send_sync_alert(Alert, #state{protocol_specific = #{sender := Sender}}= State) ->
- tls_sender:send_and_ack_alert(Sender, Alert),
- receive
- {Sender, ack_alert} ->
- ok
- after ?DEFAULT_TIMEOUT ->
- %% Sender is blocked terminate anyway
+send_sync_alert(
+ Alert, #state{protocol_specific = #{sender := Sender}} = State) ->
+ try tls_sender:send_and_ack_alert(Sender, Alert)
+ catch
+ _:_ ->
throw({stop, {shutdown, own_alert}, State})
end.
@@ -452,23 +476,6 @@ protocol_name() ->
encode_data(Data, Version, ConnectionStates0)->
tls_record:encode_data(Data, Version, ConnectionStates0).
-passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
- case Buffer of
- <<>> ->
- {Record, State} = next_record(State0),
- next_event(StateName, Record, State);
- _ ->
- {Record, State} = ssl_connection:read_application_data(<<>>, State0),
- next_event(StateName, Record, State)
- end.
-
-next_record_if_active(State =
- #state{socket_options =
- #socket_options{active = false}}) ->
- {no_record ,State};
-next_record_if_active(State) ->
- next_record(State).
-
send(Transport, Socket, Data) ->
tls_socket:send(Transport, Socket, Data).
@@ -491,14 +498,17 @@ getopts(Transport, Socket, Tag) ->
%%--------------------------------------------------------------------
init({call, From}, {start, Timeout},
- #state{host = Host, port = Port, role = client,
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ transport_cb = Transport,
+ socket = Socket,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
ssl_options = SslOpts,
session = #session{own_certificate = Cert} = Session0,
- transport_cb = Transport, socket = Socket,
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb
+ connection_states = ConnectionStates0
} = State0) ->
KeyShare = maybe_generate_client_shares(SslOpts),
Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From),
@@ -518,16 +528,16 @@ init({call, From}, {start, Timeout},
message => Hello},
ssl_logger:debug(SslOpts#ssl_options.log_level, HelloMsg, #{domain => [otp,ssl,handshake]}),
ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- State1 = State0#state{connection_states = ConnectionStates,
+ State = State0#state{connection_states = ConnectionStates,
negotiated_version = HelloVersion, %% Requested version
- session =
- Session0#session{session_id = Hello#client_hello.session_id},
- tls_handshake_history = Handshake,
- start_or_recv_from = From,
- timer = Timer,
- key_share = KeyShare},
- {Record, State} = next_record(State1),
- next_event(hello, Record, State);
+ session =
+ Session0#session{session_id = Hello#client_hello.session_id},
+ handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake},
+ start_or_recv_from = From,
+ timer = Timer,
+ key_share = KeyShare},
+ next_event(hello, no_record, State);
+
init(Type, Event, State) ->
gen_handshake(?FUNCTION_NAME, Type, Event, State).
@@ -538,8 +548,9 @@ init(Type, Event, State) ->
%%--------------------------------------------------------------------
error({call, From}, {start, _Timeout},
#state{protocol_specific = #{error := Error}} = State) ->
- ssl_connection:stop_and_reply(
- normal, {reply, From, {error, Error}}, State);
+ {stop_and_reply, {shutdown, normal},
+ [{reply, From, {error, Error}}], State};
+
error({call, _} = Call, Msg, State) ->
gen_handshake(?FUNCTION_NAME, Call, Msg, State);
error(_, _, _) ->
@@ -565,13 +576,16 @@ hello(internal, #server_hello{extensions = Extensions} = Hello,
[{reply, From, {ok, Extensions}}]};
hello(internal, #client_hello{client_version = ClientVersion} = Hello,
#state{connection_states = ConnectionStates0,
- port = Port, session = #session{own_certificate = Cert} = Session0,
- renegotiation = {Renegotiation, _},
- session_cache = Cache,
- session_cache_cb = CacheCb,
+ static_env = #static_env{
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv,
+ session = #session{own_certificate = Cert} = Session0,
negotiated_protocol = CurrentProtocol,
key_algorithm = KeyExAlg,
ssl_options = SslOpts} = State) ->
+
case choose_tls_version(SslOpts, Hello) of
'tls_v1.3' ->
%% Continue in TLS 1.3 'start' state
@@ -598,7 +612,8 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
State#state{connection_states = ConnectionStates,
negotiated_version = Version,
hashsign_algorithm = HashSign,
- client_hello_version = ClientVersion,
+ handshake_env = HsEnv#handshake_env{client_hello_version =
+ ClientVersion},
session = Session,
negotiated_protocol = Protocol})
end
@@ -606,8 +621,8 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello,
hello(internal, #server_hello{} = Hello,
#state{connection_states = ConnectionStates0,
negotiated_version = ReqVersion,
- role = client,
- renegotiation = {Renegotiation, _},
+ static_env = #static_env{role = client},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
ssl_options = SslOptions} = State) ->
case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
#alert{} = Alert ->
@@ -663,46 +678,96 @@ connection({call, From}, {user_renegotiate, WriteState},
#state{connection_states = ConnectionStates} = State) ->
{next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}},
[{next_event,{call, From}, renegotiate}]};
+connection({call, From},
+ {close, {Pid, _Timeout}},
+ #state{terminated = closed} = State) ->
+ {next_state, downgrade, State#state{terminated = true, downgrade = {Pid, From}},
+ [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]};
+connection({call, From},
+ {close,{Pid, Timeout}},
+ #state{connection_states = ConnectionStates,
+ protocol_specific = #{sender := Sender}
+ } = State0) ->
+ case tls_sender:downgrade(Sender, Timeout) of
+ {ok, Write} ->
+ %% User downgrades connection
+ %% When downgrading an TLS connection to a transport connection
+ %% we must recive the close alert from the peer before releasing the
+ %% transport socket.
+ State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ State0#state{connection_states =
+ ConnectionStates#{current_write => Write}}),
+ {next_state, downgrade, State#state{downgrade = {Pid, From},
+ terminated = true}, [{timeout, Timeout, downgrade}]};
+ {error, timeout} ->
+ {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]}
+ end;
+connection(internal, #hello_request{},
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}},
+ session = #session{own_certificate = Cert} = Session0,
+ ssl_options = SslOpts,
+ protocol_specific = #{sender := Pid},
+ connection_states = ConnectionStates} = State0) ->
+ try tls_sender:peer_renegotiate(Pid) of
+ {ok, Write} ->
+ Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
+ Cache, CacheCb, Renegotiation, Cert, undefined),
+ {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}}),
+ next_event(hello, no_record, State#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}, Actions)
+ catch
+ _:_ ->
+ {stop, {shutdown, sender_blocked}, State0}
+ end;
connection(internal, #hello_request{},
- #state{role = client,
- renegotiation = {Renegotiation, _},
- host = Host, port = Port,
+ #state{static_env = #static_env{role = client,
+ host = Host,
+ port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb},
+ handshake_env = #handshake_env{renegotiation = {Renegotiation, _}},
session = #session{own_certificate = Cert} = Session0,
- session_cache = Cache, session_cache_cb = CacheCb,
- ssl_options = SslOpts,
+ ssl_options = SslOpts,
connection_states = ConnectionStates} = State0) ->
Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts,
Cache, CacheCb, Renegotiation, Cert, undefined),
- {State1, Actions} = send_handshake(Hello, State0),
- {Record, State} =
- next_record(
- State1#state{session = Session0#session{session_id
- = Hello#client_hello.session_id}}),
- next_event(hello, Record, State, Actions);
+
+ {State, Actions} = send_handshake(Hello, State0),
+ next_event(hello, no_record, State#state{session = Session0#session{session_id
+ = Hello#client_hello.session_id}}, Actions);
connection(internal, #client_hello{} = Hello,
- #state{role = server, allow_renegotiate = true, connection_states = CS,
- %%protocol_cb = Connection,
+ #state{static_env = #static_env{role = server},
+ handshake_env = HsEnv,
+ allow_renegotiate = true,
+ connection_states = CS,
protocol_specific = #{sender := Sender}
- } = State0) ->
+ } = State) ->
%% Mitigate Computational DoS attack
%% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html
%% http://www.thc.org/thc-ssl-dos/ Rather than disabling client
%% initiated renegotiation we will disallow many client initiated
%% renegotiations immediately after each other.
erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate),
- {Record, State} = next_record(State0#state{allow_renegotiate = false,
- renegotiation = {true, peer}}),
{ok, Write} = tls_sender:renegotiate(Sender),
- next_event(hello, Record, State#state{connection_states = CS#{current_write => Write}},
+ next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write},
+ allow_renegotiate = false,
+ handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}
+ },
[{next_event, internal, Hello}]);
connection(internal, #client_hello{},
- #state{role = server, allow_renegotiate = false,
- protocol_cb = Connection} = State0) ->
+ #state{static_env = #static_env{role = server,
+ protocol_cb = Connection},
+ allow_renegotiate = false} = State0) ->
Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION),
send_alert_in_connection(Alert, State0),
- State1 = Connection:reinit_handshake_data(State0),
- {Record, State} = next_record(State1),
- next_event(?FUNCTION_NAME, Record, State);
+ State = Connection:reinit_handshake_data(State0),
+ next_event(?FUNCTION_NAME, no_record, State);
+
connection(Type, Event, State) ->
ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
@@ -710,6 +775,22 @@ connection(Type, Event, State) ->
-spec downgrade(gen_statem:event_type(), term(), #state{}) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+downgrade(internal, #alert{description = ?CLOSE_NOTIFY},
+ #state{static_env = #static_env{transport_cb = Transport,
+ socket = Socket},
+ downgrade = {Pid, From}} = State) ->
+ tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]),
+ Transport:controlling_process(Socket, Pid),
+ {stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State};
+downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State) ->
+ {stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State};
+downgrade(info, {CloseTag, Socket},
+ #state{static_env = #static_env{socket = Socket,
+ close_tag = CloseTag}, downgrade = {_, From}} =
+ State) ->
+ {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State};
+downgrade(info, Info, State) ->
+ handle_info(Info, ?FUNCTION_NAME, State);
downgrade(Type, Event, State) ->
ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE).
@@ -830,6 +911,12 @@ wait_sh(Type, Event, State) ->
callback_mode() ->
state_functions.
+terminate({shutdown, sender_died, Reason}, _StateName,
+ #state{static_env = #static_env{socket = Socket,
+ transport_cb = Transport}}
+ = State) ->
+ ssl_connection:handle_trusted_certs_db(State),
+ close(Reason, Socket, Transport, undefined, undefined);
terminate(Reason, StateName, State) ->
catch ssl_connection:terminate(Reason, StateName, State),
ensure_sender_terminate(Reason, State).
@@ -848,54 +935,62 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac
#ssl_options{beast_mitigation = BeastMitigation,
erl_dist = IsErlDist} = SSLOptions,
ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation),
- ErlDistData = erl_dist_data(IsErlDist),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
Cb;
_ ->
ssl_session_cache
end,
-
+ InternalActiveN = case application:get_env(ssl, internal_active_n) of
+ {ok, N} when is_integer(N) andalso (not IsErlDist) ->
+ N;
+ _ ->
+ ?INTERNAL_ACTIVE_N
+ end,
UserMonitor = erlang:monitor(process, User),
-
- #state{socket_options = SocketOptions,
- ssl_options = SSLOptions,
- session = #session{is_resumable = new},
- transport_cb = CbModule,
- data_tag = DataTag,
- close_tag = CloseTag,
- error_tag = ErrorTag,
- role = Role,
- host = Host,
- port = Port,
- socket = Socket,
- erl_dist_data = ErlDistData,
- connection_states = ConnectionStates,
- protocol_buffers = #protocol_buffers{},
- user_application = {UserMonitor, User},
- user_data_buffer = <<>>,
- session_cache_cb = SessionCacheCb,
- renegotiation = {false, first},
- allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
- start_or_recv_from = undefined,
- protocol_cb = ?MODULE,
- tracker = Tracker,
- flight_buffer = [],
- protocol_specific = #{sender => Sender}
- }.
-
-erl_dist_data(true) ->
- #{dist_handle => undefined,
- dist_buffer => <<>>};
-erl_dist_data(false) ->
- #{}.
-
-initialize_tls_sender(#state{role = Role,
- socket = Socket,
+ InitStatEnv = #static_env{
+ role = Role,
+ transport_cb = CbModule,
+ protocol_cb = ?MODULE,
+ data_tag = DataTag,
+ close_tag = CloseTag,
+ error_tag = ErrorTag,
+ host = Host,
+ port = Port,
+ socket = Socket,
+ session_cache_cb = SessionCacheCb,
+ tracker = Tracker
+ },
+ #state{
+ static_env = InitStatEnv,
+ handshake_env = #handshake_env{
+ tls_handshake_history = ssl_handshake:init_handshake_history(),
+ renegotiation = {false, first}
+ },
+ socket_options = SocketOptions,
+ ssl_options = SSLOptions,
+ session = #session{is_resumable = new},
+ connection_states = ConnectionStates,
+ protocol_buffers = #protocol_buffers{},
+ user_application = {UserMonitor, User},
+ user_data_buffer = <<>>,
+ allow_renegotiate = SSLOptions#ssl_options.client_renegotiation,
+ start_or_recv_from = undefined,
+ flight_buffer = [],
+ protocol_specific = #{sender => Sender,
+ active_n => InternalActiveN,
+ active_n_toggle => true
+ }
+ }.
+
+initialize_tls_sender(#state{static_env = #static_env{
+ role = Role,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ socket = Socket,
+ tracker = Tracker
+ },
socket_options = SockOpts,
- tracker = Tracker,
- protocol_cb = Connection,
- transport_cb = Transport,
negotiated_version = Version,
ssl_options = #ssl_options{renegotiate_at = RenegotiateAt,
log_level = LogLevel},
@@ -929,18 +1024,14 @@ next_tls_record(Data, StateName, #state{protocol_buffers =
handle_record_alert(Alert, State0)
end.
-
%% TLS 1.3 Client/Server
%% - Ignore TLSPlaintext.legacy_record_version
%% - Verify that TLSCiphertext.legacy_record_version is set to 0x0303 for all records
%% other than an initial ClientHello, where it MAY also be 0x0301.
+acceptable_record_versions(StateName, #state{negotiated_version = Version}) when StateName =/= hello->
+ Version;
acceptable_record_versions(hello, _) ->
- [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_TLS_RECORD_VERSIONS];
-acceptable_record_versions(_, #state{negotiated_version = {Major, Minor}})
- when Major > 3; Major =:= 3, Minor >= 4 ->
- [{3, 3}];
-acceptable_record_versions(_, #state{negotiated_version = Version}) ->
- [Version].
+ [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS].
handle_record_alert(Alert, _) ->
Alert.
@@ -952,19 +1043,26 @@ tls_handshake_events(Packets) ->
%% raw data from socket, upack records
handle_info({Protocol, _, Data}, StateName,
- #state{data_tag = Protocol} = State0) ->
+ #state{static_env = #static_env{data_tag = Protocol}} = State0) ->
case next_tls_record(Data, StateName, State0) of
{Record, State} ->
next_event(StateName, Record, State);
#alert{} = Alert ->
ssl_connection:handle_normal_shutdown(Alert, StateName, State0),
- ssl_connection:stop({shutdown, own_alert}, State0)
+ {stop, {shutdown, own_alert}, State0}
end;
+handle_info({tcp_passive, Socket}, StateName,
+ #state{static_env = #static_env{socket = Socket},
+ protocol_specific = PS
+ } = State) ->
+ next_event(StateName, no_record,
+ State#state{protocol_specific = PS#{active_n_toggle => true}});
handle_info({CloseTag, Socket}, StateName,
- #state{socket = Socket, close_tag = CloseTag,
+ #state{static_env = #static_env{socket = Socket, close_tag = CloseTag},
socket_options = #socket_options{active = Active},
protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs},
user_data_buffer = Buffer,
+ protocol_specific = PS,
negotiated_version = Version} = State) ->
%% Note that as of TLS 1.1,
@@ -986,12 +1084,13 @@ handle_info({CloseTag, Socket}, StateName,
end,
ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State),
- ssl_connection:stop({shutdown, transport_closed}, State);
+ {stop, {shutdown, transport_closed}, State};
true ->
%% Fixes non-delivery of final TLS record in {active, once}.
%% Basically allows the application the opportunity to set {active, once} again
- %% and then receive the final message.
- next_event(StateName, no_record, State)
+ %% and then receive the final message. Set internal active_n to zero
+ %% to ensure socket close message is sent if there is not enough data to deliver.
+ next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}})
end;
handle_info({'EXIT', Sender, Reason}, _,
#state{protocol_specific = #{sender := Sender}} = State) ->
@@ -1003,6 +1102,12 @@ handle_alerts([], Result) ->
Result;
handle_alerts(_, {stop, _, _} = Stop) ->
Stop;
+handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts],
+ {next_state, connection = StateName, #state{user_data_buffer = Buffer,
+ protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} =
+ State}) when (Buffer =/= <<>>) orelse
+ (CTs =/= []) ->
+ {next_state, StateName, State#state{terminated = true}};
handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State));
handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) ->
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 04bcea1e1b..a20499972b 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -134,67 +134,57 @@ start(internal,
end.
-%% TODO: move these functions
+
+negotiated(internal,
+ Map,
+ #state{connection_states = ConnectionStates0,
+ session = #session{session_id = SessionId,
+ own_certificate = OwnCert},
+ ssl_options = #ssl_options{} = SslOpts,
+ key_share = KeyShare,
+ handshake_env = #handshake_env{tls_handshake_history = HHistory0},
+ private_key = CertPrivateKey,
+ static_env = #static_env{
+ cert_db = CertDbHandle,
+ cert_db_ref = CertDbRef,
+ socket = Socket,
+ transport_cb = Transport}} = State0, _Module) ->
+ Env = #{connection_states => ConnectionStates0,
+ session_id => SessionId,
+ own_certificate => OwnCert,
+ cert_db => CertDbHandle,
+ cert_db_ref => CertDbRef,
+ ssl_options => SslOpts,
+ key_share => KeyShare,
+ tls_handshake_history => HHistory0,
+ transport_cb => Transport,
+ socket => Socket,
+ private_key => CertPrivateKey},
+ case tls_handshake_1_3:do_negotiated(Map, Env) of
+ #alert{} = Alert ->
+ ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0);
+ M ->
+ %% TODO: implement update_state
+ %% State = update_state(State0, M),
+ {next_state, wait_flight2, State0, [{next_event, internal, M}]}
+
+ end.
+
+
update_state(#state{connection_states = ConnectionStates0,
session = Session} = State,
- #{client_random := ClientRandom,
- cipher := Cipher,
+ #{cipher := Cipher,
key_share := KeyShare,
session_id := SessionId}) ->
#{security_parameters := SecParamsR0} = PendingRead =
maps:get(pending_read, ConnectionStates0),
#{security_parameters := SecParamsW0} = PendingWrite =
maps:get(pending_write, ConnectionStates0),
- SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, ClientRandom, Cipher),
- SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, ClientRandom, Cipher),
+ SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher),
+ SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher),
ConnectionStates =
ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR},
pending_write => PendingWrite#{security_parameters => SecParamsW}},
State#state{connection_states = ConnectionStates,
key_share = KeyShare,
session = Session#session{session_id = SessionId}}.
-
-
-negotiated(internal,
- Map,
- #state{connection_states = ConnectionStates0,
- session = #session{session_id = SessionId},
- ssl_options = #ssl_options{} = SslOpts,
- key_share = KeyShare,
- tls_handshake_history = HHistory0,
- transport_cb = Transport,
- socket = Socket}, _Module) ->
-
- %% Create server_hello
- %% Extensions: supported_versions, key_share, (pre_shared_key)
- ServerHello = tls_handshake_1_3:server_hello(SessionId, KeyShare,
- ConnectionStates0, Map),
-
- %% Update handshake_history (done in encode!)
- %% Encode handshake
- {BinMsg, _ConnectionStates, _HHistory} =
- tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0),
- %% Send server_hello
- tls_connection:send(Transport, Socket, BinMsg),
- Report = #{direction => outbound,
- protocol => 'tls_record',
- message => BinMsg},
- Msg = #{direction => outbound,
- protocol => 'handshake',
- message => ServerHello},
- ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}),
- ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- ok.
-
- %% K_send = handshake ???
- %% (Send EncryptedExtensions)
- %% ([Send CertificateRequest])
- %% [Send Certificate + CertificateVerify]
- %% Send Finished
- %% K_send = application ???
-
- %% Will be called implicitly
- %% {Record, State} = Connection:next_record(State2#state{session = Session}),
- %% Connection:next_event(wait_flight2, Record, State, Actions),
- %% OR
- %% Connection:next_event(WAIT_EOED, Record, State, Actions)
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 5aca4bf8c8..f0bbd0f94f 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -232,7 +232,8 @@ hello(#client_hello{client_version = ClientVersion,
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist().
+-spec encode_handshake(tls_handshake() | tls_handshake_1_3:tls_handshake_1_3(),
+ tls_record:tls_version()) -> iolist().
%%
%% Description: Encode a handshake packet
%%--------------------------------------------------------------------
@@ -318,8 +319,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
HelloExt, Version, SslOpts,
Session0, ConnectionStates0,
Renegotiation) of
- #alert{} = Alert ->
- Alert;
{Session, ConnectionStates, Protocol, ServerHelloExt} ->
{Version, {Type, Session}, ConnectionStates, Protocol,
ServerHelloExt, HashSign}
@@ -330,14 +329,14 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites,
handle_server_hello_extensions(Version, SessionId, Random, CipherSuite,
Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) ->
- case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
+ try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite,
Compression, HelloExt, Version,
SslOpt, ConnectionStates0,
- Renegotiation) of
- #alert{} = Alert ->
- Alert;
+ Renegotiation) of
{ConnectionStates, ProtoExt, Protocol} ->
{Version, SessionId, ConnectionStates, ProtoExt, Protocol}
+ catch throw:Alert ->
+ Alert
end.
@@ -403,14 +402,15 @@ get_tls_handshake_aux(_Version, Data, _, Acc) ->
decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 ->
#hello_request{};
-decode_handshake(Version, ?CLIENT_HELLO,
+decode_handshake(Version, ?CLIENT_HELLO,
<<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
Extensions/binary>>) ->
Exts = ssl_handshake:decode_vector(Extensions),
- DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client_hello),
+ DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, {Major, Minor},
+ client_hello),
#client_hello{
client_version = {Major,Minor},
random = Random,
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index f381e038cf..670c4d424d 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -27,6 +27,7 @@
-include("tls_handshake_1_3.hrl").
-include("ssl_alert.hrl").
+-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -38,7 +39,11 @@
-export([handle_client_hello/3]).
%% Create handshake messages
--export([server_hello/4]).
+-export([certificate/5,
+ certificate_verify/5,
+ server_hello/4]).
+
+-export([do_negotiated/2]).
%%====================================================================
%% Create handshake messages
@@ -50,8 +55,7 @@ server_hello(SessionId, KeyShare, ConnectionStates, _Map) ->
Extensions = server_hello_extensions(KeyShare),
#server_hello{server_version = {3,3}, %% legacy_version
cipher_suite = SecParams#security_parameters.cipher_suite,
- compression_method =
- SecParams#security_parameters.compression_algorithm,
+ compression_method = 0, %% legacy attribute
random = SecParams#security_parameters.server_random,
session_id = SessionId,
extensions = Extensions
@@ -63,6 +67,37 @@ server_hello_extensions(KeyShare) ->
ssl_handshake:add_server_share(Extensions, KeyShare).
+%% TODO: use maybe monad for error handling!
+certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, server) ->
+ case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of
+ {ok, _, Chain} ->
+ CertList = chain_to_cert_list(Chain),
+ %% If this message is in response to a CertificateRequest, the value of
+ %% certificate_request_context in that message. Otherwise (in the case
+ %%of server authentication), this field SHALL be zero length.
+ #certificate_1_3{
+ certificate_request_context = <<>>,
+ certificate_list = CertList};
+ {error, Error} ->
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error})
+ end.
+
+%% TODO: use maybe monad for error handling!
+certificate_verify(OwnCert, PrivateKey, SignatureScheme, Messages, server) ->
+ {HashAlgo, _, _} =
+ ssl_cipher:scheme_to_components(SignatureScheme),
+
+ %% Transcript-Hash(Handshake Context, Certificate)
+ Context = [Messages, OwnCert],
+ THash = tls_v1:transcript_hash(Context, HashAlgo),
+
+ Signature = digitally_sign(THash, <<"TLS 1.3, server CertificateVerify">>,
+ HashAlgo, PrivateKey),
+
+ #certificate_verify_1_3{
+ algorithm = SignatureScheme,
+ signature = Signature
+ }.
%%====================================================================
%% Encode handshake
@@ -76,7 +111,7 @@ encode_handshake(#certificate_request_1_3{
{?CERTIFICATE_REQUEST, <<EncContext/binary, BinExts/binary>>};
encode_handshake(#certificate_1_3{
certificate_request_context = Context,
- entries = Entries}) ->
+ certificate_list = Entries}) ->
EncContext = encode_cert_req_context(Context),
EncEntries = encode_cert_entries(Entries),
{?CERTIFICATE, <<EncContext/binary, EncEntries/binary>>};
@@ -120,14 +155,14 @@ decode_handshake(?CERTIFICATE, <<?BYTE(0), ?UINT24(Size), Certs:Size/binary>>) -
CertList = decode_cert_entries(Certs),
#certificate_1_3{
certificate_request_context = <<>>,
- entries = CertList
+ certificate_list = CertList
};
decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary,
?UINT24(Size), Certs:Size/binary>>) ->
CertList = decode_cert_entries(Certs),
#certificate_1_3{
certificate_request_context = Context,
- entries = CertList
+ certificate_list = CertList
};
decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) ->
#encrypted_extensions{
@@ -193,12 +228,60 @@ extensions_list(HelloExtensions) ->
[Ext || {_, Ext} <- maps:to_list(HelloExtensions)].
+%% TODO: add extensions!
+chain_to_cert_list(L) ->
+ chain_to_cert_list(L, []).
+%%
+chain_to_cert_list([], Acc) ->
+ lists:reverse(Acc);
+chain_to_cert_list([H|T], Acc) ->
+ chain_to_cert_list(T, [certificate_entry(H)|Acc]).
+
+
+certificate_entry(DER) ->
+ #certificate_entry{
+ data = DER,
+ extensions = #{} %% Extensions not supported.
+ }.
+
+%% The digital signature is then computed over the concatenation of:
+%% - A string that consists of octet 32 (0x20) repeated 64 times
+%% - The context string
+%% - A single 0 byte which serves as the separator
+%% - The content to be signed
+%%
+%% For example, if the transcript hash was 32 bytes of 01 (this length
+%% would make sense for SHA-256), the content covered by the digital
+%% signature for a server CertificateVerify would be:
+%%
+%% 2020202020202020202020202020202020202020202020202020202020202020
+%% 2020202020202020202020202020202020202020202020202020202020202020
+%% 544c5320312e332c207365727665722043657274696669636174655665726966
+%% 79
+%% 00
+%% 0101010101010101010101010101010101010101010101010101010101010101
+digitally_sign(THash, Context, HashAlgo, PrivateKey = #'RSAPrivateKey'{}) ->
+ Content = build_content(Context, THash),
+
+ %% The length of the Salt MUST be equal to the length of the output
+ %% of the digest algorithm.
+ PadLen = ssl_cipher:hash_size(HashAlgo),
+
+ public_key:sign(Content, HashAlgo, PrivateKey,
+ [{rsa_padding, rsa_pkcs1_pss_padding},
+ {rsa_pss_saltlen, PadLen}]).
+
+
+build_content(Context, THash) ->
+ <<" ",
+ " ",
+ Context/binary,?BYTE(0),THash/binary>>.
+
%%====================================================================
%% Handle handshake messages
%%====================================================================
handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
- random = Random,
session_id = SessionId,
extensions = Extensions} = _Hello,
#ssl_options{ciphers = ServerCiphers,
@@ -233,26 +316,24 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)),
Group = Maybe(select_server_group(ServerGroups, ClientGroups)),
Maybe(validate_key_share(ClientGroups, ClientShares)),
- _ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)),
- %% Handle certificate
- {PublicKeyAlgo, SignAlgo} = get_certificate_params(Cert),
+ ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)),
+
+ {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert),
%% Check if client supports signature algorithm of server certificate
- Maybe(check_cert_sign_algo(SignAlgo, ClientSignAlgs, ClientSignAlgsCert)),
+ Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)),
- %% Check if server supports
+ %% Select signature algorithm (used in CertificateVerify message).
SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)),
%% Generate server_share
KeyShare = ssl_cipher:generate_server_share(Group),
-
_Ret = #{cipher => Cipher,
group => Group,
sign_alg => SelectedSignAlg,
- %% client_share => ClientPubKey,
+ client_share => ClientPubKey,
key_share => KeyShare,
- client_random => Random,
session_id => SessionId}
%% TODO:
@@ -265,9 +346,9 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups);
{Ref, illegal_parameter} ->
?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER);
- {Ref, {client_hello_retry_request, _Group0}} ->
+ {Ref, {hello_retry_request, _Group0}} ->
%% TODO
- exit({client_hello_retry_request, not_implemented});
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, "hello_retry_request not implemented");
{Ref, no_suitable_cipher} ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher);
{Ref, {insufficient_security, no_suitable_signature_algorithm}} ->
@@ -277,6 +358,197 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
end.
+do_negotiated(#{client_share := ClientKey,
+ group := SelectedGroup,
+ sign_alg := SignatureScheme
+ } = Map,
+ #{connection_states := ConnectionStates0,
+ session_id := SessionId,
+ own_certificate := OwnCert,
+ cert_db := CertDbHandle,
+ cert_db_ref := CertDbRef,
+ ssl_options := SslOpts,
+ key_share := KeyShare,
+ tls_handshake_history := HHistory0,
+ transport_cb := Transport,
+ socket := Socket,
+ private_key := CertPrivateKey}) ->
+ {Ref,Maybe} = maybe(),
+
+ try
+ %% Create server_hello
+ %% Extensions: supported_versions, key_share, (pre_shared_key)
+ ServerHello = server_hello(SessionId, KeyShare, ConnectionStates0, Map),
+
+ %% Update handshake_history (done in encode!)
+ %% Encode handshake
+ {BinMsg, ConnectionStates1, HHistory1} =
+ tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0),
+ %% Send server_hello
+ tls_connection:send(Transport, Socket, BinMsg),
+ log_handshake(SslOpts, ServerHello),
+ log_tls_record(SslOpts, BinMsg),
+
+ %% ConnectionStates2 = calculate_security_parameters(ClientKey, SelectedGroup, KeyShare,
+ %% HHistory1, ConnectionStates1),
+ {HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV} =
+ calculate_security_parameters(ClientKey, SelectedGroup, KeyShare,
+ HHistory1, ConnectionStates1),
+ ConnectionStates2 =
+ update_pending_connection_states(ConnectionStates1, HandshakeSecret,
+ ReadKey, ReadIV, WriteKey, WriteIV),
+ ConnectionStates3 =
+ ssl_record:step_encryption_state(ConnectionStates2),
+
+ %% Create Certificate
+ Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server),
+
+ %% Encode Certificate
+ {_, _ConnectionStates4, HHistory2} =
+ tls_connection:encode_handshake(Certificate, {3,4}, ConnectionStates3, HHistory1),
+ %% log_handshake(SslOpts, Certificate),
+
+ %% Create CertificateVerify
+ {Messages, _} = HHistory2,
+
+ %% Use selected signature_alg from here, HKDF only used for key_schedule
+ CertificateVerify =
+ tls_handshake_1_3:certificate_verify(OwnCert, CertPrivateKey, SignatureScheme,
+ Messages, server),
+ io:format("### CertificateVerify: ~p~n", [CertificateVerify]),
+
+ %% Encode CertificateVerify
+
+ %% Send Certificate, CertifricateVerify
+
+ %% Send finished
+
+ %% Next record/Next event
+
+ Maybe(not_implemented(negotiated))
+
+
+ catch
+ {Ref, {state_not_implemented, State}} ->
+ %% TODO
+ ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State})
+ end.
+
+
+%% TODO: Remove this function!
+not_implemented(State) ->
+ {error, {state_not_implemented, State}}.
+
+
+log_handshake(SslOpts, Message) ->
+ Msg = #{direction => outbound,
+ protocol => 'handshake',
+ message => Message},
+ ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}).
+
+
+log_tls_record(SslOpts, BinMsg) ->
+ Report = #{direction => outbound,
+ protocol => 'tls_record',
+ message => BinMsg},
+ ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}).
+
+
+calculate_security_parameters(ClientKey, SelectedGroup, KeyShare, HHistory, ConnectionStates) ->
+ #{security_parameters := SecParamsR} =
+ ssl_record:pending_connection_state(ConnectionStates, read),
+ #security_parameters{prf_algorithm = HKDFAlgo,
+ cipher_suite = CipherSuite} = SecParamsR,
+
+ %% Calculate handshake_secret
+ EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, <<>>}),
+ PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{}
+
+ IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup),
+ HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret),
+
+ %% Calculate [sender]_handshake_traffic_secret
+ {Messages, _} = HHistory,
+ ClientHSTrafficSecret =
+ tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+ ServerHSTrafficSecret =
+ tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)),
+
+ %% Calculate traffic keys
+ #{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite),
+ {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret),
+ {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret),
+
+ {HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV}.
+
+ %% %% Update pending connection state
+ %% PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read),
+ %% PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write),
+
+ %% PendingRead = update_conn_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV),
+ %% PendingWrite = update_conn_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV),
+
+ %% %% Update pending and copy to current (activate)
+ %% %% All subsequent handshake messages are encrypted
+ %% %% ([sender]_handshake_traffic_secret)
+ %% #{current_read => PendingRead,
+ %% current_write => PendingWrite,
+ %% pending_read => PendingRead,
+ %% pending_write => PendingWrite}.
+
+
+get_server_private_key(#key_share_server_hello{server_share = ServerShare}) ->
+ get_private_key(ServerShare).
+
+get_private_key(#key_share_entry{
+ key_exchange = #'ECPrivateKey'{} = PrivateKey}) ->
+ PrivateKey;
+get_private_key(#key_share_entry{
+ key_exchange =
+ {_, PrivateKey}}) ->
+ PrivateKey.
+
+%% X25519, X448
+calculate_shared_secret(OthersKey, MyKey, Group)
+ when is_binary(OthersKey) andalso is_binary(MyKey) andalso
+ (Group =:= x25519 orelse Group =:= x448)->
+ crypto:compute_key(ecdh, OthersKey, MyKey, Group);
+%% FFDHE
+calculate_shared_secret(OthersKey, MyKey, Group)
+ when is_binary(OthersKey) andalso is_binary(MyKey) ->
+ Params = #'DHParameter'{prime = P} = ssl_dh_groups:dh_params(Group),
+ S = public_key:compute_key(OthersKey, MyKey, Params),
+ Size = byte_size(binary:encode_unsigned(P)),
+ ssl_cipher:add_zero_padding(S, Size);
+%% ECDHE
+calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group)
+ when is_binary(OthersKey) ->
+ Point = #'ECPoint'{point = OthersKey},
+ public_key:compute_key(Point, MyKey).
+
+
+update_pending_connection_states(CS = #{pending_read := PendingRead0,
+ pending_write := PendingWrite0},
+ HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV) ->
+ PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV),
+ PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV),
+ CS#{pending_read => PendingRead,
+ pending_write => PendingWrite}.
+
+update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0},
+ HandshakeSecret, Key, IV) ->
+ %% Store secret
+ SecurityParameters = SecurityParameters0#security_parameters{
+ master_secret = HandshakeSecret},
+ ConnectionState#{security_parameters => SecurityParameters,
+ cipher_state => cipher_init(Key, IV)}.
+
+
+
+cipher_init(Key, IV) ->
+ #cipher_state{key = Key, iv = IV, tag_len = 16}.
+
+
%% If there is no overlap between the received
%% "supported_groups" and the groups supported by the server, then the
%% server MUST abort the handshake with a "handshake_failure" or an
@@ -324,14 +596,20 @@ get_client_public_key(Group, ClientShares) ->
{value, {_, _, ClientPublicKey}} ->
{ok, ClientPublicKey};
false ->
- %% ClientHelloRetryRequest
- {error, {client_hello_retry_request, Group}}
+ %% 4.1.4. Hello Retry Request
+ %%
+ %% The server will send this message in response to a ClientHello
+ %% 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.
+ {error, {hello_retry_request, Group}}
end.
select_cipher_suite([], _) ->
{error, no_suitable_cipher};
select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) ->
- case lists:member(Cipher, ServerCiphers) of
+ case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso
+ lists:member(Cipher, ServerCiphers) of
true ->
{ok, Cipher};
false ->
@@ -349,22 +627,28 @@ select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) ->
%% If no "signature_algorithms_cert" extension is
%% present, then the "signature_algorithms" extension also applies to
%% signatures appearing in certificates.
-check_cert_sign_algo(SignAlgo, ClientSignAlgs, undefined) ->
- maybe_lists_member(SignAlgo, ClientSignAlgs,
- {insufficient_security, no_suitable_signature_algorithm});
-check_cert_sign_algo(SignAlgo, _, ClientSignAlgsCert) ->
- maybe_lists_member(SignAlgo, ClientSignAlgsCert,
- {insufficient_security, no_suitable_signature_algorithm}).
+
+%% Check if the signature algorithm of the server certificate is supported
+%% by the client.
+check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, undefined) ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs);
+check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgsCert).
%% DSA keys are not supported by TLS 1.3
select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) ->
{error, {insufficient_security, no_suitable_public_key}};
-%% TODO: Implement check for ellipctic curves!
+%% TODO: Implement support for ECDSA keys!
+select_sign_algo(_, [], _) ->
+ {error, {insufficient_security, no_suitable_signature_algorithm}};
select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
{_, S, _} = ssl_cipher:scheme_to_components(C),
- case PublicKeyAlgo =:= rsa andalso
- ((S =:= rsa_pkcs1) orelse (S =:= rsa_pss_rsae) orelse (S =:= rsa_pss_pss)) andalso
+ %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed
+ %% TLS handshake messages: filter sha-1 and rsa_pkcs1.
+ case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae)
+ orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae))
+ andalso
lists:member(C, ServerSignAlgs) of
true ->
{ok, C};
@@ -373,51 +657,51 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) ->
end.
-maybe_lists_member(Elem, List, Error) ->
- case lists:member(Elem, List) of
+do_check_cert_sign_algo(_, _, []) ->
+ {error, {insufficient_security, no_suitable_signature_algorithm}};
+do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) ->
+ {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme),
+ case compare_sign_algos(SignAlgo, SignHash, Sign, Hash) of
true ->
ok;
- false ->
- {error, Error}
+ _Else ->
+ do_check_cert_sign_algo(SignAlgo, SignHash, T)
end.
-%% TODO: test with ecdsa, rsa_pss_rsae, rsa_pss_pss
+
+%% 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;
+%% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or
+%% signature schemes.
+compare_sign_algos(rsa, Hash, Algo, Hash)
+ when Algo =:= rsa_pss_rsae orelse
+ Algo =:= rsa_pkcs1 ->
+ true;
+compare_sign_algos(Algo, Hash, Algo, Hash) ->
+ true;
+compare_sign_algos(_, _, _, _) ->
+ false.
+
+
get_certificate_params(Cert) ->
{SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert),
- SignAlgo = public_key:pkix_sign_types(SignAlgo0),
+ {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),
- Scheme = sign_algo_to_scheme(SignAlgo),
- {PublicKeyAlgo, Scheme}.
-
-sign_algo_to_scheme({Hash0, Sign0}) ->
- SupportedSchemes = tls_v1:default_signature_schemes({3,4}),
- Hash = case Hash0 of
- sha ->
- sha1;
- H ->
- H
- end,
- Sign = case Sign0 of
- rsa ->
- rsa_pkcs1;
- S ->
- S
- end,
- sign_algo_to_scheme(Hash, Sign, SupportedSchemes).
-%%
-sign_algo_to_scheme(_, _, []) ->
- not_found;
-sign_algo_to_scheme(H, S, [Scheme|T]) ->
- {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme),
- case H =:= Hash andalso S =:= Sign of
- true ->
- Scheme;
- false ->
- sign_algo_to_scheme(H, S, T)
- end.
+ {PublicKeyAlgo, SignAlgo, SignHash}.
%% Note: copied from ssl_handshake
+public_key_algo(?'id-RSASSA-PSS') ->
+ rsa_pss;
public_key_algo(?rsaEncryption) ->
rsa;
public_key_algo(?'id-ecPublicKey') ->
diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl
index 6ef5364399..7ae1b93e1c 100644
--- a/lib/ssl/src/tls_handshake_1_3.hrl
+++ b/lib/ssl/src/tls_handshake_1_3.hrl
@@ -191,7 +191,7 @@
%% case RawPublicKey:
%% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */
%% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>;
-
+ %%
%% case X509:
%% opaque cert_data<1..2^24-1>;
%% };
@@ -200,9 +200,14 @@
-record(certificate_1_3, {
certificate_request_context, % opaque certificate_request_context<0..2^8-1>;
- entries % CertificateEntry certificate_list<0..2^24-1>;
+ certificate_list % CertificateEntry certificate_list<0..2^24-1>;
}).
+-record(certificate_verify_1_3, {
+ algorithm, % SignatureScheme
+ signature % signature<0..2^16-1>
+ }).
+
%% RFC 8446 B.3.4. Ticket Establishment
-record(new_session_ticket, {
ticket_lifetime, %unit32
@@ -223,4 +228,11 @@
request_update
}).
+-type tls_handshake_1_3() :: #encrypted_extensions{} |
+ #certificate_request_1_3{} |
+ #certificate_1_3{} |
+ #certificate_verify_1_3{}.
+
+-export_type([tls_handshake_1_3/0]).
+
-endif. % -ifdef(tls_handshake_1_3).
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 50fad2e680..b8bf4603dd 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -76,25 +76,15 @@ init_connection_states(Role, BeastMitigation) ->
pending_write => Pending}.
%%--------------------------------------------------------------------
--spec get_tls_records(binary(), [tls_version()], binary(), ssl_options()) -> {[binary()], binary()} | #alert{}.
+-spec get_tls_records(binary(), [tls_version()] | tls_version(), binary(),
+ #ssl_options{}) -> {[binary()], binary()} | #alert{}.
%%
%% and returns it as a list of tls_compressed binaries also returns leftover
%% Description: Given old buffer and new data from TCP, packs up a records
%% data
%%--------------------------------------------------------------------
-get_tls_records(Data, Versions, Buffer, SslOpts) ->
- BinData = list_to_binary([Buffer, Data]),
- case erlang:byte_size(BinData) of
- N when N >= 3 ->
- case assert_version(BinData, Versions) of
- true ->
- get_tls_records_aux(BinData, [], SslOpts);
- false ->
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
- end;
- _ ->
- get_tls_records_aux(BinData, [], SslOpts)
- end.
+get_tls_records(Data, Version, Buffer, SslOpts) ->
+ get_tls_records_aux(Version, <<Buffer/binary, Data/binary>>, [], SslOpts).
%%====================================================================
%% Encoding
@@ -116,7 +106,7 @@ encode_handshake(Frag, Version,
ConnectionStates) ->
case iolist_size(Frag) of
N when N > ?MAX_PLAIN_TEXT_LENGTH ->
- Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
+ Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation),
encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates);
_ ->
encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates)
@@ -157,7 +147,7 @@ encode_data(Frag, Version,
security_parameters :=
#security_parameters{bulk_cipher_algorithm = BCA}}} =
ConnectionStates) ->
- Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation),
+ Data = split_bin(Frag, Version, BCA, BeastMitigation),
encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates).
%%====================================================================
@@ -408,72 +398,59 @@ initial_connection_state(ConnectionEnd, BeastMitigation) ->
server_verify_data => undefined
}.
-assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) ->
- is_acceptable_version({MajVer, MinVer}, Versions).
-
-get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc, SslOpts) ->
- RawTLSRecord = <<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary>>,
- Report = #{direction => inbound,
- protocol => 'tls_record',
- message => [RawTLSRecord]},
- ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA,
- version = {MajVer, MinVer},
- fragment = Data} | Acc],
- SslOpts);
-get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length),
- Data:Length/binary, Rest/binary>>, Acc, SslOpts) ->
- RawTLSRecord = <<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary>>,
- Report = #{direction => inbound,
- protocol => 'tls_record',
- message => [RawTLSRecord]},
- ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE,
- version = {MajVer, MinVer},
- fragment = Data} | Acc],
- SslOpts);
-get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary,
- Rest/binary>>, Acc, SslOpts) ->
- RawTLSRecord = <<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary>>,
- Report = #{direction => inbound,
- protocol => 'tls_record',
- message => [RawTLSRecord]},
- ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT,
- version = {MajVer, MinVer},
- fragment = Data} | Acc],
- SslOpts);
-get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary, Rest/binary>>,
- Acc, SslOpts) ->
- RawTLSRecord = <<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer),
- ?UINT16(Length), Data:Length/binary>>,
+get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawTLSRecord,
+ Acc, SslOpts) when Type == ?APPLICATION_DATA;
+ Type == ?HANDSHAKE;
+ Type == ?ALERT;
+ Type == ?CHANGE_CIPHER_SPEC ->
Report = #{direction => inbound,
protocol => 'tls_record',
message => [RawTLSRecord]},
ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
- get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC,
- version = {MajVer, MinVer},
- fragment = Data} | Acc],
- SslOpts);
-get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
- ?UINT16(Length), _/binary>>,
- _Acc, _SslOpts) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
+ get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type,
+ version = Version,
+ fragment = Data} | Acc], SslOpts);
+get_tls_records_aux(Versions, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),
+ ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawTLSRecord,
+ Acc, SslOpts) when is_list(Versions) andalso
+ ((Type == ?APPLICATION_DATA)
+ orelse
+ (Type == ?HANDSHAKE)
+ orelse
+ (Type == ?ALERT)
+ orelse
+ (Type == ?CHANGE_CIPHER_SPEC)) ->
+ case is_acceptable_version({MajVer, MinVer}, Versions) of
+ true ->
+ Report = #{direction => inbound,
+ protocol => 'tls_record',
+ message => [RawTLSRecord]},
+ ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}),
+ get_tls_records_aux(Versions, Rest, [#ssl_tls{type = Type,
+ version = {MajVer, MinVer},
+ fragment = Data} | Acc], SslOpts);
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+get_tls_records_aux(_, <<?BYTE(Type),?BYTE(_MajVer),?BYTE(_MinVer),
+ ?UINT16(Length), _:Length/binary, _Rest/binary>>,
+ _, _) when Type == ?APPLICATION_DATA;
+ Type == ?HANDSHAKE;
+ Type == ?ALERT;
+ Type == ?CHANGE_CIPHER_SPEC ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC);
+get_tls_records_aux(_, <<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer),
+ ?UINT16(Length), _/binary>>,
+ _Acc, _) when Length > ?MAX_CIPHER_TEXT_LENGTH ->
?ALERT_REC(?FATAL, ?RECORD_OVERFLOW);
-get_tls_records_aux(Data, Acc, _SslOpts) ->
+get_tls_records_aux(_, Data, Acc, _) ->
case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of
true ->
{lists:reverse(Acc), Data};
false ->
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
- end.
+ end.
%%--------------------------------------------------------------------
encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) ->
{CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0),
@@ -526,27 +503,26 @@ start_additional_data(Type, {MajVer, MinVer},
%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are
%% not vulnerable to this attack.
-split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when
+split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when
BCA =/= ?RC4 andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
- do_split_bin(Rest, ChunkSize, [[FirstByte]]);
+ [[FirstByte]|do_split_bin(Rest)];
%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1
%% splitting.
-split_bin(Bin, ChunkSize, Version, BCA, zero_n) when
+split_bin(Bin, Version, BCA, zero_n) when
BCA =/= ?RC4 andalso ({3, 1} == Version orelse
{3, 0} == Version) ->
- do_split_bin(Bin, ChunkSize, [[<<>>]]);
-split_bin(Bin, ChunkSize, _, _, _) ->
- do_split_bin(Bin, ChunkSize, []).
+ [<<>>|do_split_bin(Bin)];
+split_bin(Bin, _, _, _) ->
+ do_split_bin(Bin).
-do_split_bin(<<>>, _, Acc) ->
- lists:reverse(Acc);
-do_split_bin(Bin, ChunkSize, Acc) ->
+do_split_bin(<<>>) -> [];
+do_split_bin(Bin) ->
case Bin of
- <<Chunk:ChunkSize/binary, Rest/binary>> ->
- do_split_bin(Rest, ChunkSize, [Chunk | Acc]);
+ <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> ->
+ [Chunk|do_split_bin(Rest)];
_ ->
- lists:reverse(Acc, [Bin])
+ [Bin]
end.
%%--------------------------------------------------------------------
lowest_list_protocol_version(Ver, []) ->
diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl
index d424336187..1681babed9 100644
--- a/lib/ssl/src/tls_record_1_3.erl
+++ b/lib/ssl/src/tls_record_1_3.erl
@@ -76,8 +76,8 @@ encode_data(Frag, ConnectionStates) ->
encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) ->
PadLen = 0, %% TODO where to specify PadLen?
Data = inner_plaintext(Type, Data0, PadLen),
- {CipherFragment, Write1} = encode_plain_text(Data, Write0),
- {CipherText, Write} = encode_tls_cipher_text(CipherFragment, Write1),
+ CipherFragment = encode_plain_text(Data, Write0),
+ {CipherText, Write} = encode_tls_cipher_text(CipherFragment, Write0),
{CipherText, ConnectionStates#{current_write => Write}}.
encode_iolist(Type, Data, ConnectionStates0) ->
@@ -105,24 +105,23 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE,
fragment = CipherFragment},
#{current_read :=
#{sequence_number := Seq,
- cipher_state := CipherS0,
+ cipher_state := #cipher_state{key = Key,
+ iv = IV,
+ tag_len = TagLen},
security_parameters :=
#security_parameters{
cipher_type = ?AEAD,
bulk_cipher_algorithm =
BulkCipherAlgo}
} = ReadState0} = ConnectionStates0) ->
- AAD = start_additional_data(),
- CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0),
- case decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment) of
- {PlainFragment, CipherS1} ->
+ case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of
+ #alert{} = Alert ->
+ Alert;
+ PlainFragment ->
ConnectionStates =
ConnectionStates0#{current_read =>
- ReadState0#{cipher_state => CipherS1,
- sequence_number => Seq + 1}},
- decode_inner_plaintext(PlainFragment, ConnectionStates);
- #alert{} = Alert ->
- Alert
+ ReadState0#{sequence_number => Seq + 1}},
+ {decode_inner_plaintext(PlainFragment), ConnectionStates}
end;
decode_cipher_text(#ssl_tls{type = Type,
version = ?LEGACY_VERSION,
@@ -137,7 +136,7 @@ decode_cipher_text(#ssl_tls{type = Type,
fragment = CipherFragment}, ConnnectionStates0};
decode_cipher_text(#ssl_tls{type = Type}, _) ->
%% Version mismatch is already asserted
- ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_typ_mismatch, Type}).
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_type_mismatch, Type}).
%%--------------------------------------------------------------------
%%% Internal functions
@@ -170,62 +169,61 @@ encode_plain_text(#inner_plaintext{
content = Data,
type = Type,
zeros = Zeros
- }, #{cipher_state := CipherS0,
+ }, #{cipher_state := #cipher_state{key= Key,
+ iv = IV,
+ tag_len = TagLen},
sequence_number := Seq,
security_parameters :=
#security_parameters{
- cipher_type = ?AEAD}
- } = WriteState0) ->
- PlainText = <<Data/binary, ?BYTE(Type), Zeros/binary>>,
- AAD = start_additional_data(),
- CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0),
- {Encoded, WriteState} = cipher_aead(PlainText, WriteState0#{cipher_state => CipherS1}, AAD),
- {#tls_cipher_text{opaque_type = Type,
- legacy_version = {3,3},
- encoded_record = Encoded}, WriteState};
+ cipher_type = ?AEAD,
+ bulk_cipher_algorithm = BulkCipherAlgo}
+ }) ->
+ PlainText = [Data, Type, Zeros],
+ Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen),
+ #tls_cipher_text{opaque_type = 23, %% 23 (application_data) for outward compatibility
+ legacy_version = {3,3},
+ encoded_record = Encoded};
encode_plain_text(#inner_plaintext{
content = Data,
type = Type
}, #{security_parameters :=
#security_parameters{
cipher_suite = ?TLS_NULL_WITH_NULL_NULL}
- } = WriteState0) ->
+ }) ->
%% RFC8446 - 5.1. Record Layer
%% When record protection has not yet been engaged, TLSPlaintext
%% structures are written directly onto the wire.
- {#tls_cipher_text{opaque_type = Type,
+ #tls_cipher_text{opaque_type = Type,
legacy_version = {3,3},
- encoded_record = Data}, WriteState0};
+ encoded_record = Data};
encode_plain_text(_, CS) ->
exit({cs, CS}).
-start_additional_data() ->
- {MajVer, MinVer} = ?LEGACY_VERSION,
- <<?BYTE(?OPAQUE_TYPE), ?BYTE(MajVer), ?BYTE(MinVer)>>.
-
-end_additional_data(AAD, Len) ->
- <<AAD/binary, ?UINT16(Len)>>.
-
-nonce(#cipher_state{nonce = Nonce, iv = IV}) ->
- Len = size(IV),
- crypto:exor(<<Nonce:Len/bytes>>, IV).
+additional_data(Length) ->
+ <<?BYTE(?OPAQUE_TYPE), ?BYTE(3), ?BYTE(3),?UINT16(Length)>>.
-cipher_aead(Fragment,
- #{cipher_state := CipherS0,
- security_parameters :=
- #security_parameters{bulk_cipher_algorithm =
- BulkCipherAlgo}
- } = WriteState0, AAD) ->
- {CipherFragment, CipherS1} =
- cipher_aead(BulkCipherAlgo, CipherS0, AAD, Fragment),
- {CipherFragment, WriteState0#{cipher_state => CipherS1}}.
+%% The per-record nonce for the AEAD construction is formed as
+%% follows:
+%%
+%% 1. The 64-bit record sequence number is encoded in network byte
+%% order and padded to the left with zeros to iv_length.
+%%
+%% 2. The padded sequence number is XORed with either the static
+%% client_write_iv or server_write_iv (depending on the role).
+%%
+%% The resulting quantity (of length iv_length) is used as the
+%% per-record nonce.
+nonce(Seq, IV) ->
+ Padding = binary:copy(<<0>>, byte_size(IV) - 8),
+ crypto:exor(<<Padding/binary,?UINT64(Seq)>>, IV).
-cipher_aead(Type, #cipher_state{key=Key} = CipherState, AAD0, Fragment) ->
- AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)),
- Nonce = nonce(CipherState),
- {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD),
- {<<Content/binary, CipherTag/binary>>, CipherState}.
+cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
+ AAD = additional_data(erlang:iolist_size(Fragment) + TagLen),
+ Nonce = nonce(Seq, IV),
+ {Content, CipherTag} =
+ ssl_cipher:aead_encrypt(BulkCipherAlgo, Key, Nonce, Fragment, AAD),
+ <<Content/binary, CipherTag/binary>>.
encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
legacy_version = {MajVer, MinVer},
@@ -234,13 +232,14 @@ encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type,
{[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded],
Write#{sequence_number => Seq +1}}.
-decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment) ->
+decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) ->
try
- Nonce = nonce(CipherState),
- {AAD, CipherText, CipherTag} = aead_ciphertext_split(CipherState, CipherFragment, AAD0),
- case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of
+ AAD = additional_data(erlang:iolist_size(CipherFragment)),
+ Nonce = nonce(Seq, IV),
+ {CipherText, CipherTag} = aead_ciphertext_split(CipherFragment, TagLen),
+ case ssl_cipher:aead_decrypt(BulkCipherAlgo, Key, Nonce, CipherText, CipherTag, AAD) of
Content when is_binary(Content) ->
- {Content, CipherState};
+ Content;
_ ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end
@@ -249,39 +248,34 @@ decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed)
end.
-aead_ciphertext_split(#cipher_state{tag_len = Len}, CipherTextFragment, AAD) ->
- CipherLen = size(CipherTextFragment) - Len,
- <<CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment,
- {end_additional_data(AAD, CipherLen), CipherText, CipherTag}.
-decode_inner_plaintext(PlainText, ConnnectionStates) ->
- case remove_padding(PlainText) of
- #alert{} = Alert ->
- Alert;
- {Data, Type} ->
- {#ssl_tls{type = Type,
- version = {3,4}, %% Internally use real version
- fragment = Data}, ConnnectionStates}
- end.
+aead_ciphertext_split(CipherTextFragment, TagLen)
+ when is_binary(CipherTextFragment) ->
+ CipherLen = erlang:byte_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> = CipherTextFragment,
+ {CipherText, CipherTag};
+aead_ciphertext_split(CipherTextFragment, TagLen)
+ when is_list(CipherTextFragment) ->
+ CipherLen = erlang:iolist_size(CipherTextFragment) - TagLen,
+ <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> =
+ erlang:iolist_to_binary(CipherTextFragment),
+ {CipherText, CipherTag}.
-remove_padding(PlainText)->
- case binary:split(PlainText, <<0>>, [global, trim]) of
- [] ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, padding_error);
- [Content] ->
- Type = binary:last(Content),
- split_content(Type, Content, erlang:byte_size(Content) - 1)
+decode_inner_plaintext(PlainText) ->
+ case binary:last(PlainText) of
+ 0 ->
+ decode_inner_plaintext(init_binary(PlainText));
+ Type when Type =:= ?APPLICATION_DATA orelse
+ Type =:= ?HANDSHAKE orelse
+ Type =:= ?ALERT ->
+ #ssl_tls{type = Type,
+ version = {3,4}, %% Internally use real version
+ fragment = init_binary(PlainText)};
+ _Else ->
+ ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert)
end.
-split_content(?HANDSHAKE, _, 0) ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_handshake);
-split_content(?ALERT, _, 0) ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert);
-%% For special middlebox compatible case!
-split_content(?CHANGE_CIPHER_SPEC, _, 0) ->
- ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_change_cipher_spec);
-split_content(?APPLICATION_DATA = Type, _, 0) ->
- {Type, <<>>};
-split_content(Type, Content, N) ->
- <<Data:N/bytes, ?BYTE(Type)>> = Content,
- {Type, Data}.
+init_binary(B) ->
+ {Init, _} =
+ split_binary(B, byte_size(B) - 1),
+ Init.
diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl
index 75409143a8..1559fcbb37 100644
--- a/lib/ssl/src/tls_sender.erl
+++ b/lib/ssl/src/tls_sender.erl
@@ -29,7 +29,7 @@
%% API
-export([start/0, start/1, initialize/2, send_data/2, send_alert/2,
- send_and_ack_alert/2, setopts/2, renegotiate/1,
+ send_and_ack_alert/2, setopts/2, renegotiate/1, peer_renegotiate/1, downgrade/2,
update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]).
%% gen_statem callbacks
@@ -103,7 +103,7 @@ send_alert(Pid, Alert) ->
%% in the connection state and recive an ack.
%%--------------------------------------------------------------------
send_and_ack_alert(Pid, Alert) ->
- gen_statem:cast(Pid, {ack_alert, Alert}).
+ gen_statem:call(Pid, {ack_alert, Alert}, ?DEFAULT_TIMEOUT).
%%--------------------------------------------------------------------
-spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}.
%% Description: Send application data
@@ -119,6 +119,15 @@ setopts(Pid, Opts) ->
renegotiate(Pid) ->
%% Needs error handling for external API
call(Pid, renegotiate).
+
+%%--------------------------------------------------------------------
+-spec peer_renegotiate(pid()) -> {ok, WriteState::map()} | {error, term()}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when handshaking.
+%%--------------------------------------------------------------------
+peer_renegotiate(Pid) ->
+ gen_statem:call(Pid, renegotiate, ?DEFAULT_TIMEOUT).
+
%%--------------------------------------------------------------------
-spec update_connection_state(pid(), WriteState::map(), tls_record:tls_version()) -> ok.
%% Description: So TLS connection process can synchronize the
@@ -126,6 +135,21 @@ renegotiate(Pid) ->
%%--------------------------------------------------------------------
update_connection_state(Pid, NewState, Version) ->
gen_statem:cast(Pid, {new_write, NewState, Version}).
+
+%%--------------------------------------------------------------------
+-spec downgrade(pid(), integer()) -> {ok, ssl_record:connection_state()}
+ | {error, timeout}.
+%% Description: So TLS connection process can synchronize the
+%% encryption state to be used when sending application data.
+%%--------------------------------------------------------------------
+downgrade(Pid, Timeout) ->
+ try gen_statem:call(Pid, downgrade, Timeout) of
+ Result ->
+ Result
+ catch
+ _:_ ->
+ {error, timeout}
+ end.
%%--------------------------------------------------------------------
-spec dist_handshake_complete(pid(), node(), term()) -> ok.
%% Description: Erlang distribution callback
@@ -203,8 +227,9 @@ connection({call, From}, renegotiate,
#data{connection_states = #{current_write := Write}} = StateData) ->
{next_state, handshake, StateData, [{reply, From, {ok, Write}}]};
connection({call, From}, {application_data, AppData},
- #data{socket_options = SockOpts} = StateData) ->
- case encode_packet(AppData, SockOpts) of
+ #data{socket_options = #socket_options{packet = Packet}} =
+ StateData) ->
+ case encode_packet(Packet, AppData) of
{error, _} = Error ->
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]};
Data ->
@@ -220,17 +245,33 @@ connection({call, From}, dist_get_tls_socket,
tracker = Tracker} = StateData) ->
TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker),
{next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]};
-connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{connection_pid = Pid} = StateData) ->
+connection({call, From}, {dist_handshake_complete, _Node, DHandle},
+ #data{connection_pid = Pid,
+ socket_options = #socket_options{packet = Packet}} =
+ StateData) ->
ok = erlang:dist_ctrl_input_handler(DHandle, Pid),
ok = ssl_connection:dist_handshake_complete(Pid, DHandle),
%% From now on we execute on normal priority
process_flag(priority, normal),
- Events = dist_data_events(DHandle, []),
- {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, [{reply, From, ok} | Events]};
-connection(cast, {ack_alert, #alert{} = Alert}, #data{connection_pid = Pid} =StateData0) ->
+ {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle},
+ [{reply, From, ok}
+ | case dist_data(DHandle, Packet) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},Data}}]
+ end]};
+connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) ->
StateData = send_tls_alert(Alert, StateData0),
- Pid ! {self(), ack_alert},
- {next_state, ?FUNCTION_NAME, StateData};
+ {next_state, ?FUNCTION_NAME, StateData,
+ [{reply,From,ok}]};
+connection({call, From}, downgrade, #data{connection_states =
+ #{current_write := Write}} = StateData) ->
+ {next_state, death_row, StateData, [{reply,From, {ok, Write}}]};
+connection(internal, {application_packets, From, Data}, StateData) ->
+ send_application_data(Data, From, ?FUNCTION_NAME, StateData);
+%%
connection(cast, #alert{} = Alert, StateData0) ->
StateData = send_tls_alert(Alert, StateData0),
{next_state, ?FUNCTION_NAME, StateData};
@@ -240,9 +281,19 @@ connection(cast, {new_write, WritesState, Version},
StateData#data{connection_states =
ConnectionStates0#{current_write => WritesState},
negotiated_version = Version}};
-connection(info, dist_data, #data{dist_handle = DHandle} = StateData) ->
- Events = dist_data_events(DHandle, []),
- {next_state, ?FUNCTION_NAME, StateData, Events};
+%%
+connection(info, dist_data,
+ #data{dist_handle = DHandle,
+ socket_options = #socket_options{packet = Packet}} =
+ StateData) ->
+ {next_state, ?FUNCTION_NAME, StateData,
+ case dist_data(DHandle, Packet) of
+ [] ->
+ [];
+ Data ->
+ [{next_event, internal,
+ {application_packets,{self(),undefined},Data}}]
+ end};
connection(info, tick, StateData) ->
consume_ticks(),
{next_state, ?FUNCTION_NAME, StateData,
@@ -275,6 +326,8 @@ handshake(cast, {new_write, WritesState, Version},
StateData#data{connection_states =
ConnectionStates0#{current_write => WritesState},
negotiated_version = Version}};
+handshake(internal, {application_packets,_,_}, _) ->
+ {keep_state_and_data, [postpone]};
handshake(info, Msg, StateData) ->
handle_info(Msg, ?FUNCTION_NAME, StateData).
@@ -351,12 +404,13 @@ send_application_data(Data, From, StateName,
log_level = LogLevel} = StateData0) ->
case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of
true ->
- ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
+ ssl_connection:internal_renegotiation(Pid, ConnectionStates0),
{next_state, handshake, StateData0,
- [{next_event, {call, From}, {application_data, Data}}]};
+ [{next_event, internal, {application_packets, From, Data}}]};
false ->
{Msgs, ConnectionStates} =
- Connection:encode_data(Data, Version, ConnectionStates0),
+ Connection:encode_data(
+ iolist_to_binary(Data), Version, ConnectionStates0),
StateData = StateData0#data{connection_states = ConnectionStates},
case Connection:send(Transport, Socket, Msgs) of
ok when DistHandle =/= undefined ->
@@ -378,21 +432,18 @@ send_application_data(Data, From, StateName,
end
end.
-encode_packet(Data, #socket_options{packet=Packet}) ->
+-compile({inline, encode_packet/2}).
+encode_packet(Packet, Data) ->
+ Len = iolist_size(Data),
case Packet of
- 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1);
- 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1);
- 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1);
- _ -> Data
- end.
-
-encode_size_packet(Bin, Size, Max) ->
- Len = erlang:byte_size(Bin),
- case Len > Max of
- true ->
- {error, {badarg, {packet_to_large, Len, Max}}};
- false ->
- <<Len:Size, Bin/binary>>
+ 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data];
+ 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data];
+ 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data];
+ N when N =:= 1; N =:= 2; N =:= 4 ->
+ {error,
+ {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}};
+ _ ->
+ Data
end.
set_opts(SocketOptions, [{packet, N}]) ->
@@ -426,14 +477,18 @@ call(FsmPid, Event) ->
%%---------------Erlang distribution --------------------------------------
-dist_data_events(DHandle, Events) ->
+dist_data(DHandle, Packet) ->
case erlang:dist_ctrl_get_data(DHandle) of
none ->
erlang:dist_ctrl_get_data_notification(DHandle),
- lists:reverse(Events);
+ [];
Data ->
- Event = {next_event, {call, {self(), undefined}}, {application_data, Data}},
- dist_data_events(DHandle, [Event | Events])
+ %% This is encode_packet(4, Data) without Len check
+ %% since the emulator will always deliver a Data
+ %% smaller than 4 GB, and the distribution will
+ %% therefore always have to use {packet,4}
+ Len = iolist_size(Data),
+ [<<Len:32>>,Data|dist_data(DHandle, Packet)]
end.
consume_ticks() ->
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 83dd7585dd..df2a421bce 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -36,7 +36,15 @@
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]).
+-export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4,
+ key_schedule/3, key_schedule/4,
+ 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]).
-type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 |
sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 |
@@ -56,7 +64,7 @@
%% TLS 1.3 ---------------------------------------------------
-spec derive_secret(Secret::binary(), Label::binary(),
- Messages::binary(), Algo::ssl_cipher_format:hash()) -> Key::binary().
+ Messages::iodata(), Algo::ssl_cipher_format:hash()) -> Key::binary().
derive_secret(Secret, Label, Messages, Algo) ->
Hash = crypto:hash(mac_algo(Algo), Messages),
hkdf_expand_label(Secret, Label,
@@ -71,11 +79,14 @@ hkdf_expand_label(Secret, Label0, Context, Length, Algo) ->
%% opaque label<7..255> = "tls13 " + Label;
%% opaque context<0..255> = Context;
%% } HkdfLabel;
- Content = << <<"tls13">>/binary, Label0/binary, Context/binary>>,
+ Label1 = << <<"tls13 ">>/binary, Label0/binary>>,
+ LLen = size(Label1),
+ Label = <<?BYTE(LLen), Label1/binary>>,
+ Content = <<Label/binary, Context/binary>>,
Len = size(Content),
HkdfLabel = <<?UINT16(Len), Content/binary>>,
hkdf_expand(Secret, HkdfLabel, Length, Algo).
-
+
-spec hkdf_extract(MacAlg::ssl_cipher_format:hash(), Salt::binary(),
KeyingMaterial::binary()) -> PseudoRandKey::binary().
@@ -89,6 +100,12 @@ hkdf_extract(MacAlg, Salt, KeyingMaterial) ->
hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) ->
Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)),
hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>).
+
+
+-spec transcript_hash(Messages::iodata(), Algo::ssl_cipher_format:hash()) -> Hash::binary().
+
+transcript_hash(Messages, Algo) ->
+ crypto:hash(mac_algo(Algo), Messages).
%% TLS 1.3 ---------------------------------------------------
%% TLS 1.0 -1.2 ---------------------------------------------------
@@ -235,6 +252,153 @@ setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
ServerWriteKey, ClientIV, ServerIV}.
%% TLS v1.2 ---------------------------------------------------
+%% TLS v1.3 ---------------------------------------------------
+%% RFC 8446 - 7.1. Key Schedule
+%%
+%% 0
+%% |
+%% v
+%% PSK -> HKDF-Extract = Early Secret
+%% |
+%% +-----> Derive-Secret(., "ext binder" | "res binder", "")
+%% | = binder_key
+%% |
+%% +-----> Derive-Secret(., "c e traffic", ClientHello)
+%% | = client_early_traffic_secret
+%% |
+%% +-----> Derive-Secret(., "e exp master", ClientHello)
+%% | = early_exporter_master_secret
+%% v
+%% Derive-Secret(., "derived", "")
+%% |
+%% v
+%% (EC)DHE -> HKDF-Extract = Handshake Secret
+%% |
+%% +-----> Derive-Secret(., "c hs traffic",
+%% | ClientHello...ServerHello)
+%% | = client_handshake_traffic_secret
+%% |
+%% +-----> Derive-Secret(., "s hs traffic",
+%% | ClientHello...ServerHello)
+%% | = server_handshake_traffic_secret
+%% v
+%% Derive-Secret(., "derived", "")
+%% |
+%% v
+%% 0 -> HKDF-Extract = Master Secret
+%% |
+%% +-----> Derive-Secret(., "c ap traffic",
+%% | ClientHello...server Finished)
+%% | = client_application_traffic_secret_0
+%% |
+%% +-----> Derive-Secret(., "s ap traffic",
+%% | ClientHello...server Finished)
+%% | = server_application_traffic_secret_0
+%% |
+%% +-----> Derive-Secret(., "exp master",
+%% | ClientHello...server Finished)
+%% | = exporter_master_secret
+%% |
+%% +-----> Derive-Secret(., "res master",
+%% ClientHello...client Finished)
+%% = resumption_master_secret
+-spec key_schedule(early_secret | handshake_secret | master_secret,
+ atom(), {psk | early_secret | handshake_secret, binary()}) ->
+ {early_secret | handshake_secret | master_secret, binary()}.
+
+key_schedule(early_secret, Algo, {psk, PSK}) ->
+ Len = ssl_cipher:hash_size(Algo),
+ Salt = binary:copy(<<?BYTE(0)>>, Len),
+ {early_secret, hkdf_extract(Algo, Salt, PSK)};
+key_schedule(master_secret, Algo, {handshake_secret, Secret}) ->
+ Len = ssl_cipher:hash_size(Algo),
+ IKM = binary:copy(<<?BYTE(0)>>, Len),
+ Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
+ {master_secret, hkdf_extract(Algo, Salt, IKM)}.
+%%
+key_schedule(handshake_secret, Algo, IKM, {early_secret, Secret}) ->
+ Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo),
+ {handshake_secret, hkdf_extract(Algo, Salt, IKM)}.
+
+-spec external_binder_key(atom(), {early_secret, binary()}) -> binary().
+external_binder_key(Algo, {early_secret, Secret}) ->
+ derive_secret(Secret, <<"ext binder">>, <<>>, Algo).
+
+-spec resumption_binder_key(atom(), {early_secret, binary()}) -> binary().
+resumption_binder_key(Algo, {early_secret, Secret}) ->
+ derive_secret(Secret, <<"res binder">>, <<>>, Algo).
+
+-spec client_early_traffic_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello
+client_early_traffic_secret(Algo, {early_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c e traffic">>, M, Algo).
+
+-spec early_exporter_master_secret(atom(), {early_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello
+early_exporter_master_secret(Algo, {early_secret, Secret}, M) ->
+ derive_secret(Secret, <<"e exp master">>, M, Algo).
+
+-spec client_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...ServerHello
+client_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c hs traffic">>, M, Algo).
+
+-spec server_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...ServerHello
+server_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) ->
+ derive_secret(Secret, <<"s hs traffic">>, M, Algo).
+
+-spec client_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+client_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"c ap traffic">>, M, Algo).
+
+-spec server_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+server_application_traffic_secret_0(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"s ap traffic">>, M, Algo).
+
+-spec exporter_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...server Finished
+exporter_master_secret(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"exp master">>, M, Algo).
+
+-spec resumption_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary().
+%% M = ClientHello...client Finished
+resumption_master_secret(Algo, {master_secret, Secret}, M) ->
+ derive_secret(Secret, <<"res master">>, M, Algo).
+
+%% The next-generation application_traffic_secret is computed as:
+%%
+%% application_traffic_secret_N+1 =
+%% HKDF-Expand-Label(application_traffic_secret_N,
+%% "traffic upd", "", Hash.length)
+-spec update_traffic_secret(atom(), binary()) -> binary().
+update_traffic_secret(Algo, Secret) ->
+ hkdf_expand_label(Secret, <<"traffic upd">>, <<>>, ssl_cipher:hash_size(Algo), Algo).
+
+%% The traffic keying material is generated from the following input
+%% values:
+%%
+%% - A secret value
+%%
+%% - A purpose value indicating the specific value being generated
+%%
+%% - The length of the key being generated
+%%
+%% The traffic keying material is generated from an input traffic secret
+%% value using:
+%%
+%% [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
+%% [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
+-spec calculate_traffic_keys(atom(), atom(), binary()) -> {binary(), binary()}.
+calculate_traffic_keys(HKDFAlgo, Cipher, Secret) ->
+ Key = hkdf_expand_label(Secret, <<"key">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo),
+ IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo),
+ {Key, IV}.
+
+%% TLS v1.3 ---------------------------------------------------
+
%% TLS 1.0 -1.2 ---------------------------------------------------
-spec mac_hash(integer() | atom(), binary(), integer(), integer(), tls_record:tls_version(),
integer(), binary()) -> binary().
@@ -254,7 +418,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
%% TODO 1.3 same as above?
--spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()].
+-spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()].
suites(Minor) when Minor == 1; Minor == 2 ->
[
@@ -315,7 +479,17 @@ suites(4) ->
%% Not supported
%% ?TLS_AES_128_CCM_SHA256,
%% ?TLS_AES_128_CCM_8_SHA256
- ] ++ suites(3).
+ ] ++ suites(3);
+
+suites('TLS_v1.3') ->
+ [?TLS_AES_256_GCM_SHA384,
+ ?TLS_AES_128_GCM_SHA256,
+ ?TLS_CHACHA20_POLY1305_SHA256
+ %% Not supported
+ %% ?TLS_AES_128_CCM_SHA256,
+ %% ?TLS_AES_128_CCM_8_SHA256
+ ].
+
signature_algs({3, 4}, HashSigns) ->
signature_algs({3, 3}, HashSigns);
@@ -347,7 +521,9 @@ signature_algs({3, 3}, HashSigns) ->
lists:reverse(Supported).
default_signature_algs({3, 4} = Version) ->
- default_signature_schemes(Version);
+ %% TLS 1.3 servers shall be prepared to process TLS 1.2 ClientHellos
+ %% containing legacy hash-sign tuples.
+ default_signature_schemes(Version) ++ default_signature_algs({3,3});
default_signature_algs({3, 3} = Version) ->
Default = [%% SHA2
{sha512, ecdsa},
@@ -373,15 +549,23 @@ signature_schemes(Version, SignatureSchemes) when is_tuple(Version)
Hashes = proplists:get_value(hashs, CryptoSupports),
PubKeys = proplists:get_value(public_keys, CryptoSupports),
Curves = proplists:get_value(curves, CryptoSupports),
- Fun = fun (Scheme, Acc) ->
+ RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding,
+ proplists:get_value(rsa_opts, CryptoSupports)),
+ Fun = fun (Scheme, Acc) when is_atom(Scheme) ->
{Hash0, Sign0, Curve} =
ssl_cipher:scheme_to_components(Scheme),
Sign = case Sign0 of
- rsa_pkcs1 -> rsa;
+ rsa_pkcs1 ->
+ rsa;
+ rsa_pss_rsae when RSAPSSSupported ->
+ rsa;
+ rsa_pss_pss when RSAPSSSupported ->
+ rsa;
S -> S
end,
Hash = case Hash0 of
- sha1 -> sha;
+ sha1 ->
+ sha;
H -> H
end,
case proplists:get_bool(Sign, PubKeys)
@@ -394,7 +578,10 @@ signature_schemes(Version, SignatureSchemes) when is_tuple(Version)
[Scheme | Acc];
false ->
Acc
- end
+ end;
+ %% Special clause for filtering out the legacy hash-sign tuples.
+ (_ , Acc) ->
+ Acc
end,
Supported = lists:foldl(Fun, [], SignatureSchemes),
lists:reverse(Supported);
@@ -403,22 +590,29 @@ signature_schemes(_, _) ->
default_signature_schemes(Version) ->
Default = [
- 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,
+ ecdsa_secp384r1_sha384,
+ ecdsa_secp256r1_sha256,
+ rsa_pss_pss_sha512,
+ rsa_pss_pss_sha384,
+ rsa_pss_pss_sha256,
rsa_pss_rsae_sha512,
+ rsa_pss_rsae_sha384,
+ rsa_pss_rsae_sha256,
%% ed25519,
%% ed448,
- rsa_pss_pss_sha256,
- rsa_pss_pss_sha384,
- rsa_pss_pss_sha512,
- rsa_pkcs1_sha1,
- ecdsa_sha1
+
+ %% These values refer solely to signatures
+ %% which appear in certificates (see Section 4.4.2.2) and are not
+ %% defined for use in signed TLS handshake messages, although they
+ %% MAY appear in "signature_algorithms" and
+ %% "signature_algorithms_cert" for backward compatibility with
+ %% TLS 1.2.
+ rsa_pkcs1_sha512,
+ rsa_pkcs1_sha384,
+ rsa_pkcs1_sha256,
+ ecdsa_sha1,
+ rsa_pkcs1_sha1
],
signature_schemes(Version, Default).
@@ -553,7 +747,9 @@ ecc_curves(_Minor, TLSCurves) ->
-spec groups(4 | all | default) -> [group()].
groups(all) ->
- [secp256r1,
+ [x25519,
+ x448,
+ secp256r1,
secp384r1,
secp521r1,
ffdhe2048,
@@ -562,27 +758,33 @@ groups(all) ->
ffdhe6144,
ffdhe8192];
groups(default) ->
- [secp256r1,
- secp384r1,
- secp521r1,
- ffdhe2048];
+ [x25519,
+ x448,
+ secp256r1,
+ secp384r1];
groups(Minor) ->
TLSGroups = groups(all),
groups(Minor, TLSGroups).
%%
-spec groups(4, [group()]) -> [group()].
groups(_Minor, TLSGroups) ->
- %% TODO: Adding FFDHE groups to crypto?
- CryptoGroups = crypto:ec_curves() ++ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192],
+ CryptoGroups = supported_groups(),
lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups).
default_groups(Minor) ->
TLSGroups = groups(default),
groups(Minor, TLSGroups).
+supported_groups() ->
+ %% TODO: Add new function to crypto?
+ proplists:get_value(curves, crypto:supports()) ++
+ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192].
+
group_to_enum(secp256r1) -> 23;
group_to_enum(secp384r1) -> 24;
group_to_enum(secp521r1) -> 25;
+group_to_enum(x25519) -> 29;
+group_to_enum(x448) -> 30;
group_to_enum(ffdhe2048) -> 256;
group_to_enum(ffdhe3072) -> 257;
group_to_enum(ffdhe4096) -> 258;
@@ -592,6 +794,8 @@ group_to_enum(ffdhe8192) -> 260.
enum_to_group(23) -> secp256r1;
enum_to_group(24) -> secp384r1;
enum_to_group(25) -> secp521r1;
+enum_to_group(29) -> x25519;
+enum_to_group(30) -> x448;
enum_to_group(256) -> ffdhe2048;
enum_to_group(257) -> ffdhe3072;
enum_to_group(258) -> ffdhe4096;
diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl
index 578f6a731a..76bf0fa895 100644
--- a/lib/ssl/test/make_certs.erl
+++ b/lib/ssl/test/make_certs.erl
@@ -189,6 +189,18 @@ gencrl(Root, CA, C, CrlHours) ->
Env = [{"ROOTDIR", filename:absname(Root)}],
cmd(Cmd, Env).
+%% This function sets the number of seconds until the next CRL is due.
+gencrl_sec(Root, CA, C, CrlSecs) ->
+ CACnfFile = filename:join([Root, CA, "ca.cnf"]),
+ CACRLFile = filename:join([Root, CA, "crl.pem"]),
+ Cmd = [C#config.openssl_cmd, " ca"
+ " -gencrl ",
+ " -crlsec ", integer_to_list(CrlSecs),
+ " -out ", CACRLFile,
+ " -config ", CACnfFile],
+ Env = [{"ROOTDIR", filename:absname(Root)}],
+ cmd(Cmd, Env).
+
can_generate_expired_crls(C) ->
%% OpenSSL can generate CRLs with an expiration date in the past,
%% if we pass a negative number for -crlhours. However, LibreSSL
diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
index 6ffb6d311f..e4c4c89021 100644
--- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl
+++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl
@@ -160,7 +160,7 @@ certificate_1_3() ->
?LET(Certs, certificate_chain(),
#certificate_1_3{
certificate_request_context = certificate_request_context(),
- entries = certificate_entries(Certs, [])
+ certificate_list = certificate_entries(Certs, [])
}).
finished() ->
@@ -326,7 +326,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) ->
%% oneof([psk_key_exchange_modes(), undefined]),
%% oneof([early_data(), undefined]),
%% oneof([cookie(), undefined]),
- oneof([client_hello_versions(Version), undefined]),
+ oneof([client_hello_versions(Version)]),
%% oneof([cert_authorities(), undefined]),
%% oneof([post_handshake_auth(), undefined]),
oneof([signature_algs_cert(), undefined])
@@ -403,7 +403,7 @@ extensions(?'TLS_v1.3' = Version, server_hello) ->
{
oneof([key_share(server_hello), undefined]),
%% oneof([pre_shared_keys(), undefined]),
- oneof([server_hello_selected_version(), undefined])
+ oneof([server_hello_selected_version()])
},
maps:filter(fun(_, undefined) ->
false;
@@ -514,7 +514,9 @@ sig_scheme_list() ->
client_hello_versions(?'TLS_v1.3') ->
?LET(SupportedVersions,
oneof([[{3,4}],
- [{3,3},{3,4}],
+ %% This list breaks the property but can be used for negative tests
+ %% [{3,3},{3,4}],
+ [{3,4},{3,3}],
[{3,4},{3,3},{3,2},{3,1},{3,0}]
]),
#client_hello_versions{versions = SupportedVersions});
@@ -543,7 +545,10 @@ choose_certificate_chain(#{server_config := ServerConf,
oneof([certificate_chain(ServerConf), certificate_chain(ClientConf)]).
certificate_request_context() ->
- <<>>.
+ oneof([<<>>,
+ <<1>>,
+ <<"foobar">>
+ ]).
certificate_entries([], Acc) ->
lists:reverse(Acc);
certificate_entries([Cert | Rest], Acc) ->
diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec
index cb54168d36..24272327c3 100644
--- a/lib/ssl/test/ssl.spec
+++ b/lib/ssl/test/ssl.spec
@@ -1,4 +1,10 @@
-{suites,"../ssl_test",all}.
-{skip_suites, "../ssl_test",
- [ssl_bench_SUITE, ssl_dist_bench_SUITE],
- "Benchmarks run separately"}.
+% {merge_tests,false}.
+{alias,dir,"../ssl_test"}.
+
+{suites,dir,all}.
+{skip_groups,dir,ssl_bench_SUITE,setup,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_bench_SUITE,payload,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_bench_SUITE,pem_cache,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_dist_bench_SUITE,setup,"Benchmarks run separately"}.
+{skip_groups,dir,ssl_dist_bench_SUITE,throughput,"Benchmarks run separately"}.
+
diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
index 04c4b257d9..7f7c3da5ab 100644
--- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl
@@ -262,52 +262,12 @@ client_renegotiate(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
session_reused(Config) when is_list(Config)->
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0,
ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
- {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, session_info_result, []}},
- {options, ServerOpts}]),
-
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib, no_result_msg, []}},
- {options, ClientOpts}]),
-
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:fail(Other)
- end,
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Client1).
-
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
%--------------------------------------------------------------------------------
alpn_not_supported_client(Config) when is_list(Config) ->
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 1cfff436d2..0727505dde 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -53,7 +53,8 @@ all() ->
{group, options_tls},
{group, session},
{group, 'dtlsv1.2'},
- {group, 'dtlsv1'},
+ {group, 'dtlsv1'},
+ {group, 'tlsv1.3'},
{group, 'tlsv1.2'},
{group, 'tlsv1.1'},
{group, 'tlsv1'},
@@ -67,6 +68,7 @@ groups() ->
{options_tls, [], options_tests_tls()},
{'dtlsv1.2', [], all_versions_groups()},
{'dtlsv1', [], all_versions_groups()},
+ {'tlsv1.3', [], tls13_test_group()},
{'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]},
{'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()},
{'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()},
@@ -266,6 +268,12 @@ rizzo_tests() ->
rizzo_zero_n,
rizzo_disabled].
+%% For testing TLS 1.3 features and possible regressions
+tls13_test_group() ->
+ [tls13_enable_client_side,
+ tls13_enable_server_side,
+ tls_record_1_3_encode_decode].
+
%%--------------------------------------------------------------------
init_per_suite(Config0) ->
catch crypto:stop(),
@@ -295,7 +303,8 @@ init_per_group(GroupName, Config) when GroupName == basic_tls;
GroupName == options;
GroupName == basic;
GroupName == session;
- GroupName == error_handling_tests_tls
+ GroupName == error_handling_tests_tls;
+ GroupName == tls13_test_group
->
ssl_test_lib:clean_tls_version(Config);
init_per_group(GroupName, Config) ->
@@ -654,8 +663,8 @@ new_options_in_accept(Config) when is_list(Config) ->
handshake_continue() ->
[{doc, "Test API function ssl:handshake_continue/3"}].
handshake_continue(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -714,7 +723,7 @@ hello_client_cancel(Config) when is_list(Config) ->
hello_server_cancel() ->
[{doc, "Test API function ssl:handshake_cancel/1 on the server side"}].
hello_server_cancel(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -756,8 +765,8 @@ prf(Config) when is_list(Config) ->
secret_connection_info() ->
[{doc,"Test the API function ssl:connection_information/2"}].
secret_connection_info(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -838,42 +847,30 @@ controlling_process(Config) when is_list(Config) ->
ClientMsg = "Server hello",
ServerMsg = "Client hello",
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE,
- controlling_process_result, [self(),
- ServerMsg]}},
- {options, ServerOpts}]),
+ Server = ssl_test_lib:start_server([
+ {node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE,
+ controlling_process_result, [self(),
+ ServerMsg]}},
+ {options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
+ {Client, CSocket} = ssl_test_lib:start_client([return_socket,
+ {node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
{mfa, {?MODULE,
controlling_process_result, [self(),
ClientMsg]}},
{options, ClientOpts}]),
-
+
ct:log("Testcase ~p, Client ~p Server ~p ~n",
- [self(), Client, Server]),
+ [self(), Client, Server]),
- receive
- {ssl, _, "S"} ->
- receive_s_rizzo_duong_beast();
- {ssl, _, ServerMsg} ->
- receive
- {ssl, _, ClientMsg} ->
- ok
- end;
- {ssl, _, "C"} ->
- receive_c_rizzo_duong_beast();
- {ssl, _, ClientMsg} ->
- receive
- {ssl, _, ServerMsg} ->
- ok
- end;
- Unexpected ->
- ct:fail(Unexpected)
- end,
+ ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)),
+ %% We do not have the TLS server socket but all messages form the client
+ %% socket are now read, so ramining are form the server socket
+ ClientMsg = ssl_active_recv(length(ClientMsg)),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -1097,16 +1094,19 @@ tls_closed_in_active_once(Config) when is_list(Config) ->
end.
tls_closed_in_active_once_loop(Socket) ->
- ssl:setopts(Socket, [{active, once}]),
- receive
- {ssl, Socket, _} ->
- tls_closed_in_active_once_loop(Socket);
- {ssl_closed, Socket} ->
- ok
- after 5000 ->
- no_ssl_closed_received
+ case ssl:setopts(Socket, [{active, once}]) of
+ ok ->
+ receive
+ {ssl, Socket, _} ->
+ tls_closed_in_active_once_loop(Socket);
+ {ssl_closed, Socket} ->
+ ok
+ after 5000 ->
+ no_ssl_closed_received
+ end;
+ {error, closed} ->
+ ok
end.
-
%%--------------------------------------------------------------------
connect_dist() ->
[{doc,"Test a simple connect as is used by distribution"}].
@@ -1455,8 +1455,8 @@ cipher_suites_mix() ->
cipher_suites_mix(Config) when is_list(Config) ->
CipherSuites = [{dhe_rsa,aes_128_cbc,sha256,sha256}, {dhe_rsa,aes_128_cbc,sha}],
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -2115,15 +2115,21 @@ tls_downgrade(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, tls_downgrade_result, []}},
+ {mfa, {?MODULE, tls_downgrade_result, [self()]}},
{options, [{active, false} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, tls_downgrade_result, []}},
+ {mfa, {?MODULE, tls_downgrade_result, [self()]}},
{options, [{active, false} |ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ready, Client, ready),
+
+ Server ! go,
+ Client ! go,
+
ssl_test_lib:check_result(Server, ok, Client, ok),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -2361,8 +2367,8 @@ invalid_options() ->
[{doc,"Test what happens when we give invalid options"}].
invalid_options(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) ->
@@ -2377,27 +2383,28 @@ invalid_options(Config) when is_list(Config) ->
{error, {options, Option}})
end,
- TestOpts = [{versions, [sslv2, sslv3]},
- {verify, 4},
- {verify_fun, function},
- {fail_if_no_peer_cert, 0},
- {verify_client_once, 1},
- {depth, four},
- {certfile, 'cert.pem'},
- {keyfile,'key.pem' },
- {password, foo},
- {cacertfile, ""},
- {dhfile,'dh.pem' },
- {ciphers, [{foo, bar, sha, ignore}]},
- {reuse_session, foo},
- {reuse_sessions, 0},
- {renegotiate_at, "10"},
- {mode, depech},
- {packet, 8.0},
- {packet_size, "2"},
- {header, a},
- {active, trice},
- {key, 'key.pem' }],
+ TestOpts =
+ [{versions, [sslv2, sslv3]},
+ {verify, 4},
+ {verify_fun, function},
+ {fail_if_no_peer_cert, 0},
+ {verify_client_once, 1},
+ {depth, four},
+ {certfile, 'cert.pem'},
+ {keyfile,'key.pem' },
+ {password, foo},
+ {cacertfile, ""},
+ {dhfile,'dh.pem' },
+ {ciphers, [{foo, bar, sha, ignore}]},
+ {reuse_session, foo},
+ {reuse_sessions, 0},
+ {renegotiate_at, "10"},
+ {mode, depech},
+ {packet, 8.0},
+ {packet_size, "2"},
+ {header, a},
+ {active, trice},
+ {key, 'key.pem' }],
[begin
Server =
@@ -2690,175 +2697,69 @@ ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) ->
reuse_session() ->
[{doc,"Test reuse of sessions (short handshake)"}].
reuse_session(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:log("Expected: ~p, Unexpected: ~p~n",
- [SessionInfo, Other]),
- ct:fail(session_not_reused)
- end,
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- Client2 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, [{reuse_sessions, false}
- | ClientOpts]}]),
- receive
- {Client2, SessionInfo} ->
- ct:fail(
- session_reused_when_session_reuse_disabled_by_client);
- {Client2, _} ->
- ok
- end,
-
- ssl_test_lib:close(Server),
-
- Server1 =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {options, [{reuse_sessions, false} | ServerOpts]}]),
-
- Port1 = ssl_test_lib:inet_port(Server1),
- Client3 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- SessionInfo1 =
- receive
- {Server1, Info1} ->
- Info1
- end,
-
- Server1 ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client4 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client4, SessionInfo1} ->
- ct:fail(
- session_reused_when_session_reuse_disabled_by_server);
- {Client4, _Other} ->
- ct:log("OTHER: ~p ~n", [_Other]),
- ok
- end,
-
- ssl_test_lib:close(Server1),
- ssl_test_lib:close(Client0),
- ssl_test_lib:close(Client1),
- ssl_test_lib:close(Client2),
- ssl_test_lib:close(Client3),
- ssl_test_lib:close(Client4).
-
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
%%--------------------------------------------------------------------
reuse_session_expired() ->
[{doc,"Test sessions is not reused when it has expired"}].
reuse_session_expired(Config) when is_list(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {options, ServerOpts}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ Server0 ! listen,
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ SID = receive
+ {Client0, Id0} ->
+ Id0
+ end,
+
receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:log("Expected: ~p, Unexpected: ~p~n",
- [SessionInfo, Other]),
- ct:fail(session_not_reused)
+ {Client1, SID} ->
+ ok
+ after ?SLEEP ->
+ ct:fail(session_not_reused)
end,
- Server ! listen,
-
+ Server0 ! listen,
+
%% Make sure session is unregistered due to expiration
- ct:sleep((?EXPIRE+1)),
- [{session_id, Id} |_] = SessionInfo,
+ ct:sleep((?EXPIRE*2)),
- make_sure_expired(Hostname, Port, Id),
+ make_sure_expired(Hostname, Port0, SID),
Client2 =
ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
{from, self()}, {options, ClientOpts}]),
receive
- {Client2, SessionInfo} ->
+ {Client2, SID} ->
ct:fail(session_reused_when_session_expired);
{Client2, _} ->
ok
end,
process_flag(trap_exit, false),
- ssl_test_lib:close(Server),
+ ssl_test_lib:close(Server0),
ssl_test_lib:close(Client0),
ssl_test_lib:close(Client1),
ssl_test_lib:close(Client2).
@@ -2867,16 +2768,16 @@ make_sure_expired(Host, Port, Id) ->
{status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- Cache = element(2, State),
+ ClientCache = element(2, State),
- case ssl_session_cache:lookup(Cache, {{Host, Port}, Id}) of
+ case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of
undefined ->
- ok;
+ ok;
#session{is_resumable = false} ->
- ok;
+ ok;
_ ->
ct:sleep(?SLEEP),
- make_sure_expired(Host, Port, Id)
+ make_sure_expired(Host, Port, Id)
end.
%%--------------------------------------------------------------------
@@ -3977,8 +3878,8 @@ tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) ->
{status, _, _, StatusInfo} = sys:get_status(Pid),
[_, _,_, _, Prop] = StatusInfo,
State = ssl_test_lib:state(Prop),
- Socket = element(11, State),
-
+ StaticEnv = element(2, State),
+ Socket = element(10, StaticEnv),
%% Fake tcp error
Pid ! {tcp_error, Socket, etimedout},
@@ -4128,6 +4029,8 @@ rizzo(Config) when is_list(Config) ->
{cipher,
fun(rc4_128) ->
false;
+ (chacha20_poly1305) ->
+ false;
(_) ->
true
end}]),
@@ -4473,6 +4376,163 @@ accept_pool(Config) when is_list(Config) ->
ssl_test_lib:close(Client1),
ssl_test_lib:close(Client2).
+%%--------------------------------------------------------------------
+%% TLS 1.3
+%%--------------------------------------------------------------------
+
+tls13_enable_client_side() ->
+ [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}].
+
+tls13_enable_client_side(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.1', 'tlsv1.2']} | ServerOpts] }]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.2', 'tlsv1.3']} | ClientOpts]}]),
+
+ ServerMsg = ClientMsg = {ok, 'tlsv1.2'},
+ ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg).
+
+tls13_enable_server_side() ->
+ [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}].
+
+tls13_enable_server_side(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.2', 'tlsv1.3']} | ServerOpts] }]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, protocol_info_result, []}},
+ {options, [{versions,
+ ['tlsv1.2', 'tlsv1.1']} | ClientOpts]}]),
+
+ ServerMsg = ClientMsg = {ok, 'tlsv1.2'},
+ ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg).
+
+tls_record_1_3_encode_decode() ->
+ [{doc,"Test TLS 1.3 record encode/decode functions"}].
+
+tls_record_1_3_encode_decode(_Config) ->
+ ConnectionStates =
+ #{current_read =>
+ #{beast_mitigation => one_n_minus_one,
+ cipher_state =>
+ {cipher_state,
+ <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14,
+ 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>,
+ <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228,
+ 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>,
+ undefined,undefined,16},
+ client_verify_data => undefined,compression_state => undefined,
+ mac_secret => undefined,secure_renegotiation => undefined,
+ security_parameters =>
+ {security_parameters,
+ <<19,2>>,
+ 0,8,2,undefined,undefined,undefined,undefined,undefined,
+ sha384,undefined,undefined,
+ {handshake_secret,
+ <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
+ 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
+ 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
+ 157>>},
+ undefined,
+ <<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},
+ current_write =>
+ #{beast_mitigation => one_n_minus_one,
+ cipher_state =>
+ {cipher_state,
+ <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14,
+ 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>,
+ <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228,
+ 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>,
+ undefined,undefined,16},
+ client_verify_data => undefined,compression_state => undefined,
+ mac_secret => undefined,secure_renegotiation => undefined,
+ security_parameters =>
+ {security_parameters,
+ <<19,2>>,
+ 0,8,2,undefined,undefined,undefined,undefined,undefined,
+ sha384,undefined,undefined,
+ {handshake_secret,
+ <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121,
+ 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218,
+ 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56,
+ 157>>},
+ undefined,
+ <<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}},
+
+ PlainText = [11,
+ <<0,2,175>>,
+ <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255,
+ 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85,
+ 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50,
+ 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6,
+ 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,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,169,40,
+ 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88,
+ 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179,
+ 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245,
+ 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74,
+ 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53,
+ 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71,
+ 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148,
+ 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251,
+ 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15,
+ 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116,
+ 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198,
+ 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150,
+ 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76,
+ 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52,
+ 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111,
+ 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184,
+ 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218,
+ 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73,
+ 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76,
+ 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155,
+ 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239,
+ 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202,
+ 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78,
+ 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132,
+ 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62,
+ 146,152,146,151,107,126,216,210,9,93,0,0>>],
+
+ {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates),
+ CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded},
+
+ {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} =
+ tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates),
+
+ DecodedText = iolist_to_binary(PlainText),
+ ct:log("Decoded: ~p ~n", [DecodedText]),
+ ok.
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
@@ -4487,8 +4547,8 @@ tcp_send_recv_result(Socket) ->
ok.
basic_verify_test_no_close(Config) ->
- ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -4631,19 +4691,24 @@ recv_close(Socket) ->
send_recv_result_active_rizzo(Socket) ->
ssl:send(Socket, "Hello world"),
- receive
- {ssl, Socket, "H"} ->
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end
- end.
+ "Hello world" = ssl_test_lib:active_recv(Socket, 11),
+ ok.
send_recv_result_active_no_rizzo(Socket) ->
ssl:send(Socket, "Hello world"),
+ "Hello world" = ssl_test_lib:active_recv(Socket, 11),
+ ok.
+
+
+ssl_active_recv(N) ->
+ ssl_active_recv(N, []).
+
+ssl_active_recv(0, Acc) ->
+ Acc;
+ssl_active_recv(N, Acc) ->
receive
- {ssl, Socket, "Hello world"} ->
- ok
+ {ssl, _, Bytes} ->
+ ssl_active_recv(N-length(Bytes), Acc ++ Bytes)
end.
result_ok(_Socket) ->
@@ -4667,16 +4732,7 @@ renegotiate_reuse_session(Socket, Data) ->
renegotiate(Socket, Data).
renegotiate_immediately(Socket) ->
- receive
- {ssl, Socket, "Hello world"} ->
- ok;
- %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast
- {ssl, Socket, "H"} ->
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end
- end,
+ _ = ssl_test_lib:active_recv(Socket, 11),
ok = ssl:renegotiate(Socket),
{error, renegotiation_rejected} = ssl:renegotiate(Socket),
ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP),
@@ -4686,17 +4742,7 @@ renegotiate_immediately(Socket) ->
ok.
renegotiate_rejected(Socket) ->
- receive
- {ssl, Socket, "Hello world"} ->
- ok;
- %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast
- {ssl, Socket, "H"} ->
-
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end
- end,
+ _ = ssl_test_lib:active_recv(Socket, 11),
{error, renegotiation_rejected} = ssl:renegotiate(Socket),
{error, renegotiation_rejected} = ssl:renegotiate(Socket),
ct:sleep(?RENEGOTIATION_DISABLE_TIME +1),
@@ -4871,17 +4917,11 @@ session_loop(Sess) ->
erlang_ssl_receive(Socket, Data) ->
- receive
- {ssl, Socket, Data} ->
- io:format("Received ~p~n",[Data]),
- ok;
- {ssl, Socket, Byte} when length(Byte) == 1 -> %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast
- io:format("Received ~p~n",[Byte]),
- erlang_ssl_receive(Socket, tl(Data));
- Other ->
- ct:fail({unexpected_message, Other})
- after timer:seconds(?SEC_RENEGOTIATION_TIMEOUT) * test_server:timetrap_scale_factor() ->
- ct:fail({did_not_get, Data})
+ case ssl_test_lib:active_recv(Socket, length(Data)) of
+ Data ->
+ ok;
+ Other ->
+ ct:fail({{expected, Data}, {got, Other}})
end.
receive_msg(_) ->
@@ -4898,28 +4938,6 @@ controlling_process_result(Socket, Pid, Msg) ->
ssl:send(Socket, Msg),
no_result_msg.
-receive_s_rizzo_duong_beast() ->
- receive
- {ssl, _, "erver hello"} ->
- receive
- {ssl, _, "C"} ->
- receive
- {ssl, _, "lient hello"} ->
- ok
- end
- end
- end.
-receive_c_rizzo_duong_beast() ->
- receive
- {ssl, _, "lient hello"} ->
- receive
- {ssl, _, "S"} ->
- receive
- {ssl, _, "erver hello"} ->
- ok
- end
- end
- end.
controller_dies_result(_Socket, _Pid, _Msg) ->
receive Result -> Result end.
@@ -5005,16 +5023,16 @@ run_suites(Ciphers, Config, Type) ->
{ClientOpts, ServerOpts} =
case Type of
rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_opts, Config)]};
dsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_dsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_dsa_opts, Config)]};
anonymous ->
%% No certs in opts!
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options([], Config)]};
psk ->
@@ -5036,46 +5054,50 @@ run_suites(Ciphers, Config, Type) ->
ssl_test_lib:ssl_options(server_psk_anon_hint, Config)]};
srp ->
{ssl_test_lib:ssl_options(client_srp, Config),
- ssl_test_lib:ssl_options(server_srp, Config)};
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_srp, Config)]};
srp_anon ->
{ssl_test_lib:ssl_options(client_srp, Config),
- ssl_test_lib:ssl_options(server_srp_anon, Config)};
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_srp_anon, Config)]};
srp_dsa ->
{ssl_test_lib:ssl_options(client_srp_dsa, Config),
- ssl_test_lib:ssl_options(server_srp_dsa, Config)};
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_srp_dsa, Config)]};
ecdsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_ecdsa_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]};
ecdh_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
- ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)};
+ {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config),
+ [{ciphers, Ciphers} |
+ ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]};
rc4_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]};
rc4_ecdh_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]};
rc4_ecdsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]};
des_dhe_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_verification_opts, Config)]};
des_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]};
chacha_rsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
[{ciphers, Ciphers} |
- ssl_test_lib:ssl_options(server_verification_opts, Config)]};
+ ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]};
chacha_ecdsa ->
- {ssl_test_lib:ssl_options(client_verification_opts, Config),
+ {ssl_test_lib:ssl_options(client_ecdsa_opts, Config),
[{ciphers, Ciphers} |
ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}
end,
@@ -5173,23 +5195,28 @@ connect_dist_c(S) ->
{ok, Test} = ssl:recv(S, 0, 10000),
ok.
-tls_downgrade_result(Socket) ->
+tls_downgrade_result(Socket, Pid) ->
ok = ssl_test_lib:send_recv_result(Socket),
+ Pid ! {self(), ready},
+ receive
+ go ->
+ ok
+ end,
case ssl:close(Socket, {self(), 10000}) of
{ok, TCPSocket} ->
- inet:setopts(TCPSocket, [{active, true}]),
+ inet:setopts(TCPSocket, [{active, true}]),
gen_tcp:send(TCPSocket, "Downgraded"),
- receive
- {tcp, TCPSocket, <<"Downgraded">>} ->
- ok;
- {tcp_closed, TCPSocket} ->
- ct:pal("Peer timed out, downgrade aborted"),
- ok;
- Other ->
- {error, Other}
- end;
+ receive
+ {tcp, TCPSocket, <<"Downgraded">>} ->
+ ok;
+ {tcp_closed, TCPSocket} ->
+ ct:fail("Peer timed out, downgrade aborted"),
+ ok;
+ Other ->
+ {error, Other}
+ end;
{error, timeout} ->
- ct:pal("Timed out, downgrade aborted"),
+ ct:fail("Timed out, downgrade aborted"),
ok;
Fail ->
{error, Fail}
@@ -5220,14 +5247,14 @@ get_invalid_inet_option(Socket) ->
tls_shutdown_result(Socket, server) ->
ssl:send(Socket, "Hej"),
- ssl:shutdown(Socket, write),
+ ok = ssl:shutdown(Socket, write),
{ok, "Hej hopp"} = ssl:recv(Socket, 8),
ok;
tls_shutdown_result(Socket, client) ->
- {ok, "Hej"} = ssl:recv(Socket, 3),
ssl:send(Socket, "Hej hopp"),
- ssl:shutdown(Socket, write),
+ ok = ssl:shutdown(Socket, write),
+ {ok, "Hej"} = ssl:recv(Socket, 3),
ok.
tls_shutdown_write_result(Socket, server) ->
diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec
index 8b746c5ca9..217cc6fc83 100644
--- a/lib/ssl/test/ssl_bench.spec
+++ b/lib/ssl/test/ssl_bench.spec
@@ -1 +1,2 @@
{suites,"../ssl_test",[ssl_bench_SUITE, ssl_dist_bench_SUITE]}.
+{skip_groups,"../ssl_test",ssl_bench_SUITE,basic,"Benchmarks run separately"}.
diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl
index 13097b08b6..0b2011a627 100644
--- a/lib/ssl/test/ssl_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_bench_SUITE.erl
@@ -25,10 +25,11 @@
suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].
-all() -> [{group, setup}, {group, payload}, {group, pem_cache}].
+all() -> [{group, basic}, {group, setup}, {group, payload}, {group, pem_cache}].
groups() ->
- [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]},
+ [{basic, [], [basic_pem_cache]},
+ {setup, [{repeat, 3}], [setup_sequential, setup_concurrent]},
{payload, [{repeat, 3}], [payload_simple]},
{pem_cache, [{repeat, 3}], [use_pem_cache, bypass_pem_cache]}
].
@@ -51,29 +52,21 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
-init_per_testcase(use_pem_cache, Conf) ->
+init_per_testcase(TC, Conf) when TC =:= use_pem_cache;
+ TC =:= bypass_pem_cache;
+ TC =:= basic_pem_cache ->
case bypass_pem_cache_supported() of
false -> {skipped, "PEM cache bypass support required"};
true ->
application:set_env(ssl, bypass_pem_cache, false),
Conf
end;
-init_per_testcase(bypass_pem_cache, Conf) ->
- case bypass_pem_cache_supported() of
- false -> {skipped, "PEM cache bypass support required"};
- true ->
- application:set_env(ssl, bypass_pem_cache, true),
- Conf
- end;
init_per_testcase(_Func, Conf) ->
Conf.
-end_per_testcase(use_pem_cache, _Config) ->
- case bypass_pem_cache_supported() of
- false -> ok;
- true -> application:set_env(ssl, bypass_pem_cache, false)
- end;
-end_per_testcase(bypass_pem_cache, _Config) ->
+end_per_testcase(TC, _Config) when TC =:= use_pem_cache;
+ TC =:= bypass_pem_cache;
+ TC =:= basic_pem_cache ->
case bypass_pem_cache_supported() of
false -> ok;
true -> application:set_env(ssl, bypass_pem_cache, false)
@@ -119,6 +112,9 @@ payload_simple(Config) ->
{suite, "ssl"}, {name, "Payload simple"}]}),
ok.
+basic_pem_cache(_Config) ->
+ do_test(ssl, pem_cache, 10, 5, node()).
+
use_pem_cache(_Config) ->
{ok, Result} = do_test(ssl, pem_cache, 100, 500, node()),
ct_event:notify(#event{name = benchmark_data,
diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl
index 23c5eaf84d..c61039b5da 100644
--- a/lib/ssl/test/ssl_crl_SUITE.erl
+++ b/lib/ssl/test/ssl_crl_SUITE.erl
@@ -383,8 +383,11 @@ crl_hash_dir_expired(Config) when is_list(Config) ->
{verify, verify_peer}],
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- %% First make a CRL that expired yesterday.
- make_certs:gencrl(PrivDir, CA, CertsConfig, -24),
+ %% First make a CRL that will expire in one second.
+ make_certs:gencrl_sec(PrivDir, CA, CertsConfig, 1),
+ %% Sleep until the next CRL is due
+ ct:sleep({seconds, 1}),
+
CrlDir = filename:join(PrivDir, "crls"),
populate_crl_hash_dir(PrivDir, CrlDir,
[{CA, "1627b4b0"}],
diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index 3c7904cf24..d9d9c4e473 100644
--- a/lib/ssl/test/ssl_dist_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -32,6 +32,9 @@
-export(
[setup/1,
roundtrip/1,
+ sched_utilization/1,
+ throughput_0/1,
+ throughput_64/1,
throughput_1024/1,
throughput_4096/1,
throughput_16384/1,
@@ -54,8 +57,11 @@ groups() ->
%%
{setup, [{repeat, 1}], [setup]},
{roundtrip, [{repeat, 1}], [roundtrip]},
+ {sched_utilization,[{repeat, 1}], [sched_utilization]},
{throughput, [{repeat, 1}],
- [throughput_1024,
+ [throughput_0,
+ throughput_64,
+ throughput_1024,
throughput_4096,
throughput_16384,
throughput_65536,
@@ -65,7 +71,9 @@ groups() ->
all_groups() ->
[{group, setup},
{group, roundtrip},
- {group, throughput}].
+ {group, throughput},
+ {group, sched_utilization}
+ ].
init_per_suite(Config) ->
Digest = sha1,
@@ -247,8 +255,9 @@ setup(A, B, Prefix, HA, HB) ->
[] = ssl_apply(HB, erlang, nodes, []),
{SetupTime, CycleTime} =
ssl_apply(HA, fun () -> setup_runner(A, B, Rounds) end),
- [] = ssl_apply(HA, erlang, nodes, []),
- [] = ssl_apply(HB, erlang, nodes, []),
+ ok = ssl_apply(HB, fun () -> setup_wait_nodedown(A, 10000) end),
+ %% [] = ssl_apply(HA, erlang, nodes, []),
+ %% [] = ssl_apply(HB, erlang, nodes, []),
SetupSpeed = round((Rounds*1000000*1000) / SetupTime),
CycleSpeed = round((Rounds*1000000*1000) / CycleTime),
_ = report(Prefix++" Setup", SetupSpeed, "setups/1000s"),
@@ -275,6 +284,22 @@ setup_loop(A, B, T, N) ->
setup_loop(A, B, Time + T, N - 1)
end.
+setup_wait_nodedown(A, Time) ->
+ ok = net_kernel:monitor_nodes(true),
+ case nodes() of
+ [] ->
+ ok;
+ [A] ->
+ receive
+ {nodedown,A} ->
+ ok;
+ Unexpected ->
+ {error,{unexpected,Unexpected}}
+ after Time ->
+ {error,timeout}
+ end
+ end.
+
%%----------------
%% Roundtrip speed
@@ -330,10 +355,115 @@ roundtrip_client(Pid, Mon, StartTime, N) ->
roundtrip_client(Pid, Mon, StartTime, N - 1)
end.
+%%---------------------------------------
+%% Scheduler utilization at constant load
+
+
+sched_utilization(Config) ->
+ run_nodepair_test(
+ fun(A, B, Prefix, HA, HB) ->
+ sched_utilization(A, B, Prefix, HA, HB, proplists:get_value(ssl_dist, Config))
+ end, Config).
+
+sched_utilization(A, B, Prefix, HA, HB, SSL) ->
+ [] = ssl_apply(HA, erlang, nodes, []),
+ [] = ssl_apply(HB, erlang, nodes, []),
+ {ClientMsacc, ServerMsacc, Msgs} =
+ ssl_apply(HA, fun () -> sched_util_runner(A, B, SSL) end),
+ [B] = ssl_apply(HA, erlang, nodes, []),
+ [A] = ssl_apply(HB, erlang, nodes, []),
+ msacc:print(ClientMsacc),
+ msacc:print(ServerMsacc),
+ ct:pal("Got ~p msgs",[length(Msgs)]),
+ report(Prefix++" Sched Utilization Client",
+ 10000 * msacc:stats(system_runtime,ClientMsacc) /
+ msacc:stats(system_realtime,ClientMsacc), "util 0.01 %"),
+ report(Prefix++" Sched Utilization Server",
+ 10000 * msacc:stats(system_runtime,ServerMsacc) /
+ msacc:stats(system_realtime,ServerMsacc), "util 0.01 %"),
+ ok.
+
+%% Runs on node A and spawns a server on node B
+%% We want to avoid getting busy_dist_port as it hides the true SU usage
+%% of the receiver and sender.
+sched_util_runner(A, B, true) ->
+ sched_util_runner(A, B, 50);
+sched_util_runner(A, B, false) ->
+ sched_util_runner(A, B, 250);
+sched_util_runner(A, B, Senders) ->
+ Payload = payload(5),
+ [A] = rpc:call(B, erlang, nodes, []),
+ ServerPid =
+ erlang:spawn(
+ B,
+ fun () -> throughput_server() end),
+ ServerMsacc =
+ erlang:spawn(
+ B,
+ fun() ->
+ receive
+ {start,Pid} ->
+ msacc:start(10000),
+ Pid ! {ServerPid,msacc:stats()}
+ end
+ end),
+ spawn_link(
+ fun() ->
+ %% We spawn 250 senders which should mean that we
+ %% have a load of 250 msgs/msec
+ [spawn_link(
+ fun() ->
+ throughput_client(ServerPid,Payload)
+ end) || _ <- lists:seq(1, Senders)]
+ end),
+
+ erlang:system_monitor(self(),[busy_dist_port]),
+ ServerMsacc ! {start,self()},
+ msacc:start(10000),
+ ClientMsaccStats = msacc:stats(),
+ ServerMsaccStats = receive {ServerPid,Stats} -> Stats end,
+ {ClientMsaccStats,ServerMsaccStats, flush()}.
+
+flush() ->
+ receive
+ M ->
+ [M | flush()]
+ after 0 ->
+ []
+ end.
+
+throughput_server() ->
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ receive _ -> ok end,
+ throughput_server().
+
+throughput_client(Pid, Payload) ->
+ Pid ! Payload,
+ receive after 1 -> throughput_client(Pid, Payload) end.
%%-----------------
%% Throughput speed
+throughput_0(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 500000, 0)
+ end, Config).
+
+throughput_64(Config) ->
+ run_nodepair_test(
+ fun (A, B, Prefix, HA, HB) ->
+ throughput(A, B, Prefix, HA, HB, 500000, 64)
+ end, Config).
+
throughput_1024(Config) ->
run_nodepair_test(
fun (A, B, Prefix, HA, HB) ->
@@ -373,45 +503,198 @@ throughput_1048576(Config) ->
throughput(A, B, Prefix, HA, HB, Packets, Size) ->
[] = ssl_apply(HA, erlang, nodes, []),
[] = ssl_apply(HB, erlang, nodes, []),
- Time =
+ #{time := Time,
+ dist_stats := DistStats,
+ client_msacc_stats := ClientMsaccStats,
+ client_prof := ClientProf,
+ server_msacc_stats := ServerMsaccStats,
+ server_prof := ServerProf} =
ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end),
[B] = ssl_apply(HA, erlang, nodes, []),
[A] = ssl_apply(HB, erlang, nodes, []),
- Speed = round((Packets*Size*1000000) / (1024*Time)),
+ ClientMsaccStats =:= undefined orelse
+ msacc:print(ClientMsaccStats),
+ io:format("DistStats: ~p~n", [DistStats]),
+ Overhead =
+ 50 % Distribution protocol headers (empirical) (TLS+=54)
+ + byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead
+ Bytes = Packets * (Size + Overhead),
+ io:format("~w bytes, ~.4g s~n", [Bytes,Time/1000000]),
+ ClientMsaccStats =:= undefined orelse
+ io:format(
+ "Sender core usage ratio: ~.4g ns/byte~n",
+ [msacc:stats(system_runtime, ClientMsaccStats)*1000/Bytes]),
+ ServerMsaccStats =:= undefined orelse
+ begin
+ io:format(
+ "Receiver core usage ratio: ~.4g ns/byte~n",
+ [msacc:stats(system_runtime, ServerMsaccStats)*1000/Bytes]),
+ msacc:print(ServerMsaccStats)
+ end,
+ io:format("******* ClientProf:~n", []), prof_print(ClientProf),
+ io:format("******* ServerProf:~n", []), prof_print(ServerProf),
+ Speed = round((Bytes * 1000000) / (1024 * Time)),
report(Prefix++" Throughput_"++integer_to_list(Size), Speed, "kB/s").
%% Runs on node A and spawns a server on node B
throughput_runner(A, B, Rounds, Size) ->
Payload = payload(Size),
- ClientPid = self(),
[A] = rpc:call(B, erlang, nodes, []),
+ ClientPid = self(),
ServerPid =
erlang:spawn(
B,
fun () -> throughput_server(ClientPid, Rounds) end),
ServerMon = erlang:monitor(process, ServerPid),
- microseconds(
- throughput_client(
- ServerPid, ServerMon, Payload, start_time(), Rounds)).
+ msacc:available() andalso
+ begin
+ msacc:stop(),
+ msacc:reset(),
+ msacc:start(),
+ ok
+ end,
+ prof_start(),
+ {Time,ServerMsaccStats,ServerProf} =
+ throughput_client(ServerPid, ServerMon, Payload, Rounds),
+ prof_stop(),
+ ClientMsaccStats =
+ case msacc:available() of
+ true ->
+ MStats = msacc:stats(),
+ msacc:stop(),
+ MStats;
+ false ->
+ undefined
+ end,
+ ClientProf = prof_end(),
+ [{_Node,Socket}] = dig_dist_node_sockets(),
+ DistStats = inet:getstat(Socket),
+ #{time => microseconds(Time),
+ dist_stats => DistStats,
+ client_msacc_stats => ClientMsaccStats,
+ client_prof => ClientProf,
+ server_msacc_stats => ServerMsaccStats,
+ server_prof => ServerProf}.
+
+dig_dist_node_sockets() ->
+ [case DistCtrl of
+ {_Node,Socket} = NodeSocket when is_port(Socket) ->
+ NodeSocket;
+ {Node,DistCtrlPid} when is_pid(DistCtrlPid) ->
+ [{links,DistCtrlLinks}] = process_info(DistCtrlPid, [links]),
+ case [S || S <- DistCtrlLinks, is_port(S)] of
+ [Socket] ->
+ {Node,Socket};
+ [] ->
+ [{monitors,[{process,DistSenderPid}]}] =
+ process_info(DistCtrlPid, [monitors]),
+ [{links,DistSenderLinks}] =
+ process_info(DistSenderPid, [links]),
+ [Socket] = [S || S <- DistSenderLinks, is_port(S)],
+ {Node,Socket}
+ end
+ end || DistCtrl <- erlang:system_info(dist_ctrl)].
+
-throughput_server(_Pid, 0) ->
- ok;
throughput_server(Pid, N) ->
+ msacc:available() andalso
+ begin
+ msacc:stop(),
+ msacc:reset(),
+ msacc:start(),
+ ok
+ end,
+ prof_start(),
+ throughput_server_loop(Pid, N).
+
+throughput_server_loop(_Pid, 0) ->
+ prof_stop(),
+ MsaccStats =
+ case msacc:available() of
+ true ->
+ msacc:stop(),
+ MStats = msacc:stats(),
+ msacc:reset(),
+ MStats;
+ false ->
+ undefined
+ end,
+ Prof = prof_end(),
+ exit({ok,MsaccStats,Prof});
+throughput_server_loop(Pid, N) ->
receive
- [N|_] ->
- throughput_server(Pid, N-1)
+ {Pid, N, _} ->
+ throughput_server_loop(Pid, N-1)
end.
-throughput_client(_Pid, Mon, _Payload, StartTime, 0) ->
+throughput_client(Pid, Mon, Payload, N) ->
+ throughput_client_loop(Pid, Mon, Payload, N, start_time()).
+
+throughput_client_loop(_Pid, Mon, _Payload, 0, StartTime) ->
receive
- {'DOWN', Mon, _, _, normal} ->
- elapsed_time(StartTime);
+ {'DOWN', Mon, _, _, {ok,MsaccStats,Prof}} ->
+ {elapsed_time(StartTime),MsaccStats,Prof};
{'DOWN', Mon, _, _, Other} ->
exit(Other)
end;
-throughput_client(Pid, Mon, Payload, StartTime, N) ->
- Pid ! [N|Payload],
- throughput_client(Pid, Mon, Payload, StartTime, N - 1).
+throughput_client_loop(Pid, Mon, Payload, N, StartTime) ->
+ Pid ! {self(), N, Payload},
+ throughput_client_loop(Pid, Mon, Payload, N - 1, StartTime).
+
+
+-define(prof, none). % none | cprof | eprof
+
+-if(?prof =:= cprof).
+prof_start() ->
+ cprof:stop(),
+ cprof:start(),
+ ok.
+-elif(?prof =:= eprof).
+prof_start() ->
+ {ok,_} = eprof:start(),
+ profiling = eprof:start_profiling(processes()),
+ ok.
+-elif(?prof =:= none).
+prof_start() ->
+ ok.
+-endif.
+
+-if(?prof =:= cprof).
+prof_stop() ->
+ cprof:pause(),
+ ok.
+-elif(?prof =:= eprof).
+prof_stop() ->
+ _ = eprof:stop_profiling(),
+ ok.
+-elif(?prof =:= none).
+prof_stop() ->
+ ok.
+-endif.
+
+-if(?prof =:= cprof).
+prof_end() ->
+ Prof = cprof:analyse(),
+ cprof:stop(),
+ Prof.
+-elif(?prof =:= eprof).
+prof_end() ->
+ eprof:dump_data().
+-elif(?prof =:= none).
+prof_end() ->
+ [].
+-endif.
+
+-if(?prof =:= cprof).
+prof_print(Prof) ->
+ io:format("~p.~n", [Prof]).
+-elif(?prof =:= eprof).
+prof_print(Dump) ->
+ eprof:analyze(undefined, total, [], Dump).
+-elif(?prof =:= none).
+prof_print([]) ->
+ ok.
+-endif.
%%%-------------------------------------------------------------------
%%% Test cases helpers
diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl
index 1b9c853fc4..b8b805b2c4 100644
--- a/lib/ssl/test/ssl_dist_test_lib.erl
+++ b/lib/ssl/test/ssl_dist_test_lib.erl
@@ -144,6 +144,8 @@ mk_node_cmdline(ListenPort, Name, Args) ->
++ integer_to_list(ListenPort) ++ " "
++ Args ++ " "
++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " "
+ ++ "-kernel inet_dist_connect_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" "
+ ++ "-kernel inet_dist_listen_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" "
++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" "
++ "-setcookie " ++ atom_to_list(erlang:get_cookie()).
@@ -179,8 +181,9 @@ check_ssl_node_up(Socket, Name, Bin) ->
Parent = self(),
Go = make_ref(),
%% Spawn connection handler on test server side
- Pid = spawn_link(
+ Pid = spawn(
fun () ->
+ link(group_leader()),
receive Go -> ok end,
process_flag(trap_exit, true),
tstsrvr_con_loop(Name, Socket, Parent)
diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl
index e6c82d3eb5..a39a62e550 100644
--- a/lib/ssl/test/ssl_engine_SUITE.erl
+++ b/lib/ssl/test/ssl_engine_SUITE.erl
@@ -46,10 +46,17 @@ init_per_suite(Config) ->
ssl_test_lib:clean_start(),
case crypto:get_test_engine() of
{ok, EngineName} ->
- try crypto:engine_load(<<"dynamic">>,
- [{<<"SO_PATH">>, EngineName},
- <<"LOAD">>],
- []) of
+ try
+ %% The test engine has it's own fake rsa sign/verify that
+ %% you don't want to use, so exclude it from methods to load:
+ Methods =
+ crypto:engine_get_all_methods() -- [engine_method_rsa],
+ crypto:engine_load(<<"dynamic">>,
+ [{<<"SO_PATH">>, EngineName},
+ <<"LOAD">>],
+ [],
+ Methods)
+ of
{ok, Engine} ->
[{engine, Engine} |Config];
{error, Reason} ->
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index e39313e5cd..1b432970b6 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -26,6 +26,7 @@
-include_lib("common_test/include/ct.hrl").
-include("ssl_alert.hrl").
+-include("ssl_handshake.hrl").
-include("ssl_internal.hrl").
-include("tls_handshake.hrl").
-include_lib("public_key/include/public_key.hrl").
@@ -42,7 +43,8 @@ all() -> [decode_hello_handshake,
decode_empty_server_sni_correctly,
select_proper_tls_1_2_rsa_default_hashsign,
ignore_hassign_extension_pre_tls_1_2,
- unorded_chain, signature_algorithms].
+ unorded_chain, signature_algorithms,
+ encode_decode_srp].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
@@ -124,13 +126,13 @@ decode_supported_elliptic_curves_hello_extension_correctly(_Config) ->
Len = ListLen + 2,
Extension = <<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary>>,
% after decoding we should see only valid curves
- Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, client),
+ Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, {3,2}, client),
#{elliptic_curves := #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]}} = Extensions.
decode_unknown_hello_extension_correctly(_Config) ->
FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>,
Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>,
- Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, client),
+ Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, {3,2}, client),
#{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions.
@@ -145,12 +147,12 @@ encode_single_hello_sni_extension_correctly(_Config) ->
decode_single_hello_sni_extension_correctly(_Config) ->
SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08,
$t, $e, $s, $t, $., $c, $o, $m>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, client),
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, client),
#{sni := #sni{hostname = "test.com"}} = Decoded.
decode_empty_server_sni_correctly(_Config) ->
SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>,
- Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, server),
+ Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, server),
#{sni := #sni{hostname = ""}} = Decoded.
@@ -190,6 +192,30 @@ unorded_chain(Config) when is_list(Config) ->
{ok, _, OrderedChain} =
ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain).
+encode_decode_srp(_Config) ->
+ Exts = #{srp => #srp{username = <<"foo">>},
+ sni => #sni{hostname = "bar"},
+ renegotiation_info => undefined,
+ signature_algs => undefined,
+ alpn => undefined,
+ next_protocol_negotiation => undefined,
+ ec_point_formats => undefined,
+ elliptic_curves => undefined
+ },
+ EncodedExts0 = <<0,20, % Length
+ 0,12, % SRP extension
+ 0,4, % Length
+ 3, % srp_I length
+ 102,111,111, % username = "foo"
+ 0,0, % SNI extension
+ 0,8, % Length
+ 0,6, % ServerNameLength
+ 0, % NameType (host_name)
+ 0,3, % HostNameLength
+ 98,97,114>>, % hostname = "bar"
+ EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> =
+ ssl_handshake:encode_hello_extensions(Exts),
+ Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client).
signature_algorithms(Config) ->
Opts = proplists:get_value(server_opts, Config),
diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl
index 1c7d6b5f9f..878e983bb9 100644
--- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl
@@ -64,13 +64,12 @@ next_protocol_not_supported() ->
npn_not_supported_server
].
-init_per_suite(Config) ->
+init_per_suite(Config0) ->
catch crypto:stop(),
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config),
- proplists:get_value(priv_dir, Config)),
+ Config = ssl_test_lib:make_rsa_cert(Config0),
ssl_test_lib:cert_options(Config)
catch _:_ ->
{skip, "Crypto did not start"}
@@ -196,10 +195,10 @@ client_negotiate_server_does_not_support(Config) when is_list(Config) ->
renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) ->
Data = "hello world",
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols,
{client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts = [{next_protocols_advertised,
[<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
ExpectedProtocol = {ok, <<"http/1.0">>},
@@ -221,7 +220,7 @@ renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
npn_not_supported_client(Config) when is_list(Config) ->
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
PrefProtocols = {client_preferred_next_protocols,
{client, [<<"http/1.0">>], <<"http/1.1">>}},
ClientOpts = [PrefProtocols] ++ ClientOpts0,
@@ -236,7 +235,7 @@ npn_not_supported_client(Config) when is_list(Config) ->
%--------------------------------------------------------------------------------
npn_not_supported_server(Config) when is_list(Config)->
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]},
ServerOpts = [AdvProtocols] ++ ServerOpts0,
@@ -244,63 +243,24 @@ npn_not_supported_server(Config) when is_list(Config)->
%--------------------------------------------------------------------------------
npn_handshake_session_reused(Config) when is_list(Config)->
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols,
{client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts =[{next_protocols_advertised,
[<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0,
- {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, session_info_result, []}},
- {options, ServerOpts}]),
-
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {ssl_test_lib, no_result_msg, []}},
- {options, ClientOpts}]),
-
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}},
-
- %% Make sure session is registered
- ct:sleep(?SLEEP),
-
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- ct:fail(Other)
- end,
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client),
- ssl_test_lib:close(Client1).
-
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) ->
Data = "hello world",
- ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = ClientExtraOpts ++ ClientOpts0,
- ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
ServerOpts = ServerExtraOpts ++ ServerOpts0,
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index ebf8ddbfac..6d26b2df33 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -725,7 +725,7 @@ packet_switch(Config) when is_list(Config) ->
{options, [{nodelay, true}, {packet, 2} |
ClientOpts]}]),
- ssl_test_lib:check_result(Client, ok),
+ ssl_test_lib:check_result(Client, ok, Server, ok),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -2122,26 +2122,13 @@ active_once_packet(Socket, Data, N) ->
active_once_packet(Socket, Data, N-1).
active_raw(Socket, Data, N) ->
- active_raw(Socket, Data, N, []).
-
-active_raw(_Socket, _, 0, _) ->
+ active_raw(Socket, (length(Data) * N)).
+active_raw(_Socket, 0) ->
ok;
-active_raw(Socket, Data, N, Acc) ->
+active_raw(Socket, N) ->
receive
- {ssl, Socket, Byte} when length(Byte) == 1 ->
- receive
- {ssl, Socket, _} ->
- active_raw(Socket, Data, N -1)
- end;
- {ssl, Socket, Data} ->
- active_raw(Socket, Data, N-1, []);
- {ssl, Socket, Other} ->
- case Acc ++ Other of
- Data ->
- active_raw(Socket, Data, N-1, []);
- NewAcc ->
- active_raw(Socket, Data, NewAcc)
- end
+ {ssl, Socket, Bytes} ->
+ active_raw(Socket, N-length(Bytes))
end.
active_packet(Socket, _, 0) ->
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index 5939800001..27b9c258a0 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -64,14 +64,18 @@ payload_tests() ->
server_echos_active_huge,
client_echos_passive_huge,
client_echos_active_once_huge,
- client_echos_active_huge].
+ client_echos_active_huge,
+ client_active_once_server_close].
init_per_suite(Config) ->
catch crypto:stop(),
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)),
+ {ok, _} =
+ make_certs:all(
+ proplists:get_value(data_dir, Config),
+ proplists:get_value(priv_dir, Config)),
ssl_test_lib:cert_options(Config)
catch _:_ ->
{skip, "Crypto did not start"}
@@ -103,12 +107,13 @@ end_per_group(GroupName, Config) ->
Config
end.
-init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge;
- TestCase == server_echos_active_once_huge;
- TestCase == server_echos_active_huge;
- TestCase == client_echos_passive_huge;
- TestCase == client_echos_active_once_huge;
- TestCase == client_echos_active_huge ->
+init_per_testcase(TestCase, Config)
+ when TestCase == server_echos_passive_huge;
+ TestCase == server_echos_active_once_huge;
+ TestCase == server_echos_active_huge;
+ TestCase == client_echos_passive_huge;
+ TestCase == client_echos_active_once_huge;
+ TestCase == client_echos_active_huge ->
case erlang:system_info(system_architecture) of
"sparc-sun-solaris2.10" ->
{skip,"Will take to long time on an old Sparc"};
@@ -117,12 +122,13 @@ init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge;
Config
end;
-init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big;
- TestCase == server_echos_active_once_big;
- TestCase == server_echos_active_big;
- TestCase == client_echos_passive_big;
- TestCase == client_echos_active_once_big;
- TestCase == client_echos_active_big ->
+init_per_testcase(TestCase, Config)
+ when TestCase == server_echos_passive_big;
+ TestCase == server_echos_active_once_big;
+ TestCase == server_echos_active_big;
+ TestCase == client_echos_passive_big;
+ TestCase == client_echos_active_once_big;
+ TestCase == client_echos_active_big ->
ct:timetrap({seconds, 60}),
Config;
@@ -144,11 +150,10 @@ server_echos_passive_small(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_passive(Str, 1000, ClientOpts, ServerOpts,
- ClientNode, ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ server_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
@@ -160,11 +165,10 @@ server_echos_active_once_small(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_active_once(Str, 1000, ClientOpts, ServerOpts,
- ClientNode, ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ server_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
@@ -176,11 +180,10 @@ server_echos_active_small(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_active(Str, 1000, ClientOpts, ServerOpts,
- ClientNode, ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ server_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_passive_small() ->
@@ -191,11 +194,10 @@ client_echos_passive_small(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- client_echos_passive(Str, 1000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ client_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_once_small() ->
@@ -206,11 +208,10 @@ client_echos_active_once_small(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- client_echos_active_once(Str, 1000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ client_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_small() ->
@@ -221,11 +222,10 @@ client_echos_active_small(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- client_echos_active(Str, 1000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ client_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
@@ -237,11 +237,10 @@ server_echos_passive_big(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_passive(Str, 50000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ server_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
@@ -253,11 +252,10 @@ server_echos_active_once_big(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_active_once(Str, 50000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ server_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
@@ -269,11 +267,10 @@ server_echos_active_big(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_active(Str, 50000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ server_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_passive_big() ->
@@ -284,11 +281,10 @@ client_echos_passive_big(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- client_echos_passive(Str, 50000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ client_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_once_big() ->
@@ -299,11 +295,10 @@ client_echos_active_once_big(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- client_echos_active_once(Str, 50000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ client_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_big() ->
@@ -314,11 +309,10 @@ client_echos_active_big(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- client_echos_active(Str, 50000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ client_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
server_echos_passive_huge() ->
@@ -329,11 +323,10 @@ server_echos_passive_huge(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_passive(Str, 500000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ server_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
server_echos_active_once_huge() ->
@@ -344,11 +337,10 @@ server_echos_active_once_huge(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_active_once(Str, 500000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ server_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
server_echos_active_huge() ->
@@ -359,11 +351,10 @@ server_echos_active_huge(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
-
- server_echos_active(Str, 500000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ server_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_passive_huge() ->
@@ -374,10 +365,10 @@ client_echos_passive_huge(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
- client_echos_passive(Str, 500000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ client_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_once_huge() ->
@@ -388,10 +379,10 @@ client_echos_active_once_huge(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Str = "1234567890",
- client_echos_active_once(Str, 500000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ client_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_huge() ->
@@ -402,293 +393,264 @@ client_echos_active_huge(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ client_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+
+%%--------------------------------------------------------------------
+client_active_once_server_close() ->
+ [{doc, "Server sends 500000 bytes and immediately after closes the connection"
+ "Make sure client recives all data if possible"}].
- Str = "1234567890",
- client_echos_active(Str, 500000, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname).
+client_active_once_server_close(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ client_active_once_server_close(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-server_echos_passive(Data, Length, ClientOpts, ServerOpts,
- ClientNode, ServerNode, Hostname) ->
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa,
- {?MODULE, echoer,
- [Data, Length]}},
- {options,
- [{active, false},{mode, binary}
- | ServerOpts]}]),
- Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa,
- {?MODULE, sender,
- [Data,
- Length]}},
- {options,
- [{active, false}, {mode, binary} |
- ClientOpts]}]),
+server_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, echoer, [Length]}},
+ {options, [{active, false}, {mode, binary} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, sender, [Data]}},
+ {options, [{active, false}, {mode, binary} | ClientOpts]}]),
+ %%
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+ %%
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-server_echos_active_once(Data, Length, ClientOpts, ServerOpts, ClientNode,
- ServerNode, Hostname) ->
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa,
- {?MODULE, echoer_once,
- [Data, Length]}},
- {options, [{active, once},
- {mode, binary}|
- ServerOpts]}]),
+server_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, echoer_active_once, [Length]}},
+ {options, [{active, once}, {mode, binary} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa,
- {?MODULE, sender_once,
- [Data, Length]}},
- {options, [{active, once},
- {mode, binary} |
- ClientOpts]}]),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, sender_active_once, [Data]}},
+ {options, [{active, once}, {mode, binary} | ClientOpts]}]),
+ %%
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+ %%
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-server_echos_active(Data, Length, ClientOpts, ServerOpts,
- ClientNode, ServerNode, Hostname) ->
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa,
- {?MODULE, echoer_active,
- [Data, Length]}},
- {options,
- [{active, true},
- {mode, binary} | ServerOpts]}]),
+server_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, echoer_active, [Length]}},
+ {options, [{active, true}, {mode, binary} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa,
- {?MODULE, sender_active,
- [Data,
- Length]}},
- {options,
- [{active, true}, {mode, binary}
- | ClientOpts]}]),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, sender_active, [Data]}},
+ {options, [{active, true}, {mode, binary} | ClientOpts]}]),
+ %%
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+ %%
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-client_echos_passive(Data, Length, ClientOpts, ServerOpts,
- ClientNode, ServerNode, Hostname) ->
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa,
- {?MODULE, sender,
- [Data, Length]}},
- {options,
- [{active, false}, {mode, binary} |
- ServerOpts]}]),
+client_echos_passive(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, sender, [Data]}},
+ {options, [{active, false}, {mode, binary} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa,
- {?MODULE, echoer,
- [Data,
- Length]}},
- {options,
- [{active, false}, {mode, binary}
- | ClientOpts]}]),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, echoer, [Length]}},
+ {options, [{active, false}, {mode, binary} | ClientOpts]}]),
+ %%
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+ %%
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-client_echos_active_once(Data, Length,
- ClientOpts, ServerOpts, ClientNode, ServerNode,
- Hostname) ->
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa,
- {?MODULE, sender_once,
- [Data, Length]}},
- {options, [{active, once},
- {mode, binary} |
- ServerOpts]}]),
+client_echos_active_once(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, sender_active_once, [Data]}},
+ {options, [{active, once}, {mode, binary} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa,
- {?MODULE, echoer_once,
- [Data,
- Length]}},
- {options,[{active, once},
- {mode, binary}
- | ClientOpts]}]),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, echoer_active_once, [Length]}},
+ {options,[{active, once}, {mode, binary} | ClientOpts]}]),
+ %%
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+ %%
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-client_echos_active(Data, Length, ClientOpts, ServerOpts, ClientNode,
- ServerNode,
- Hostname) ->
- Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa,
- {?MODULE, sender_active,
- [Data, Length]}},
- {options, [{active, true},
- {mode, binary}
- | ServerOpts]}]),
+client_echos_active(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, sender_active, [Data]}},
+ {options, [{active, true}, {mode, binary} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa,
- {?MODULE, echoer_active,
- [Data,
- Length]}},
- {options, [{active, true},
- {mode, binary}
- | ClientOpts]}]),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, echoer_active, [Length]}},
+ {options, [{active, true}, {mode, binary} | ClientOpts]}]),
+ %
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+ %%
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-send(_, _, _, 0,_) ->
+client_active_once_server_close(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_close, [Data]}},
+ {options, [{active, once}, {mode, binary} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, active_once_recv, [Length]}},
+ {options,[{active, once}, {mode, binary} | ClientOpts]}]),
+ %%
+ ssl_test_lib:check_result(Server, ok, Client, ok).
+
+send(_Socket, _Data, 0, _) ->
ok;
-send(Socket, Data, Size, Repeate,F) ->
- NewData = lists:duplicate(Size div 10, Data),
- ssl:send(Socket, NewData),
- F(),
- send(Socket, Data, Size, Repeate - 1,F).
-
-sender(Socket, Data, Size) ->
- ok = send(Socket, Data, Size, 100, fun() -> do_recv(Socket, Data, Size, <<>>, false) end),
+send(Socket, Data, Count, RecvEcho) ->
+ ok = ssl:send(Socket, Data),
+ RecvEcho(),
+ send(Socket, Data, Count - 1, RecvEcho).
+
+send_close(Socket, Data) ->
+ ok = ssl:send(Socket, Data),
+ ssl:close(Socket).
+
+sender(Socket, Data) ->
ct:log("Sender recv: ~p~n", [ssl:getopts(Socket, [active])]),
- ok.
-
-sender_once(Socket, Data, Size) ->
- send(Socket, Data, Size, 100,
- fun() -> do_active_once(Socket, Data, Size, <<>>, false) end),
- ct:log("Sender active once: ~p~n",
- [ssl:getopts(Socket, [active])]),
- ok.
-
-sender_active(Socket, Data, Size) ->
- F = fun() -> do_active(Socket, Data, Size, <<>>, false) end,
- send(Socket, Data, Size, 100, F),
+ send(Socket, Data, 100,
+ fun() ->
+ ssl_test_lib:recv_disregard(Socket, byte_size(Data))
+ end).
+
+sender_active_once(Socket, Data) ->
+ ct:log("Sender active once: ~p~n", [ssl:getopts(Socket, [active])]),
+ send(Socket, Data, 100,
+ fun() ->
+ ssl_test_lib:active_once_disregard(Socket, byte_size(Data))
+ end).
+
+sender_active(Socket, Data) ->
ct:log("Sender active: ~p~n", [ssl:getopts(Socket, [active])]),
- ok.
+ send(Socket, Data, 100,
+ fun() ->
+ ssl_test_lib:active_disregard(Socket, byte_size(Data))
+ end).
-echoer(Socket, Data, Size) ->
+echoer(Socket, Size) ->
ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]),
- echo(fun() -> do_recv(Socket, Data, Size, <<>>, true) end, 100).
+ echo_recv(Socket, Size * 100).
-echoer_once(Socket, Data, Size) ->
- ct:log("Echoer active once: ~p ~n",
- [ssl:getopts(Socket, [active])]),
- echo(fun() -> do_active_once(Socket, Data, Size, <<>>, true) end, 100).
+echoer_active_once(Socket, Size) ->
+ ct:log("Echoer active once: ~p~n", [ssl:getopts(Socket, [active])]),
+ echo_active_once(Socket, Size * 100).
-echoer_active(Socket, Data, Size) ->
+echoer_active(Socket, Size) ->
ct:log("Echoer active: ~p~n", [ssl:getopts(Socket, [active])]),
- echo(fun() -> do_active(Socket, Data, Size, <<>>, true) end, 100).
-
-echo(_Fun, 0) -> ok;
-echo(Fun, N) ->
- Fun(),
- echo(Fun, N-1).
+ echo_active(Socket, Size * 100).
-do_recv(_Socket, _Data, 0, _Acc, true) ->
+%% Receive Size bytes
+echo_recv(_Socket, 0) ->
ok;
-do_recv(_Socket, Data, 0, Acc, false) ->
- Data = lists:sublist(binary_to_list(Acc), 10);
+echo_recv(Socket, Size) ->
+ {ok, Data} = ssl:recv(Socket, 0),
+ ok = ssl:send(Socket, Data),
+ echo_recv(Socket, Size - byte_size(Data)).
-do_recv(Socket, Data, Size, Acc, Echo) ->
- {ok, NewData} = ssl:recv(Socket, 0),
- NewSize = size(NewData),
- case Echo of
- true ->
- ssl:send(Socket, NewData),
- NewSize = size(NewData),
- do_recv(Socket, Data, Size - NewSize, [], Echo);
- false ->
- case size(Acc) < 10 of
- true ->
- do_recv(Socket, Data, Size - NewSize,
- <<Acc/binary, NewData/binary>>, Echo);
- false ->
- do_recv(Socket, Data, Size - NewSize, Acc, Echo)
- end
- end.
-
-do_active_once(_Socket, _Data, 0, _Acc, true) ->
+%% Receive Size bytes
+echo_active_once(_Socket, 0) ->
ok;
-do_active_once(_Socket, Data, 0, Acc, false) ->
- Data = lists:sublist(binary_to_list(Acc), 10);
-
-do_active_once(Socket, Data, Size, Acc, Echo) ->
- receive
- {ssl, Socket, NewData} ->
- NewSize = size(NewData),
- case Echo of
- true ->
- ssl:send(Socket, NewData),
- ssl:setopts(Socket, [{active, once}]),
- do_active_once(Socket, Data, Size - NewSize, [], Echo);
- false ->
- case size(Acc) < 10 of
- true ->
- ssl:setopts(Socket, [{active, once}]),
- do_active_once(Socket, Data, Size - NewSize,
- <<Acc/binary, NewData/binary>>,
- Echo);
- false ->
- ssl:setopts(Socket, [{active, once}]),
- do_active_once(Socket, Data,
- Size - NewSize, Acc, Echo)
- end
- end
+echo_active_once(Socket, Size) ->
+ receive
+ {ssl, Socket, Data} ->
+ ok = ssl:send(Socket, Data),
+ NewSize = Size - byte_size(Data),
+ ssl:setopts(Socket, [{active, once}]),
+ echo_active_once(Socket, NewSize)
end.
-
-do_active(_Socket, _Data, 0, _Acc, true) ->
+
+%% Receive Size bytes
+echo_active(_Socket, 0) ->
ok;
-do_active(_Socket, Data, 0, Acc, false) ->
- Data = lists:sublist(binary_to_list(Acc), 10);
-
-do_active(Socket, Data, Size, Acc, Echo) ->
- receive
- {ssl, Socket, NewData} ->
- NewSize = size(NewData),
- case Echo of
- true ->
- ssl:send(Socket, NewData),
- do_active(Socket, Data, Size - NewSize, [], Echo);
- false ->
- case size(Acc) < 10 of
- true ->
- do_active(Socket, Data, Size - NewSize,
- <<Acc/binary, NewData/binary>>,
- Echo);
- false ->
- do_active(Socket, Data,
- Size - NewSize, Acc, Echo)
- end
- end
- end.
+echo_active(Socket, Size) ->
+ receive
+ {ssl, Socket, Data} ->
+ ok = ssl:send(Socket, Data),
+ echo_active(Socket, Size - byte_size(Data))
+ end.
+
diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl
index 25d2cb300d..6f11e2bbe8 100644
--- a/lib/ssl/test/ssl_pem_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl
@@ -44,11 +44,8 @@ init_per_suite(Config0) ->
try crypto:start() of
ok ->
ssl_test_lib:clean_start(),
- %% make rsa certs using oppenssl
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0),
- proplists:get_value(priv_dir, Config0)),
- Config1 = ssl_test_lib:make_dsa_cert(Config0),
- ssl_test_lib:cert_options(Config1)
+ %% make rsa certs
+ ssl_test_lib:make_rsa_cert(Config0)
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -86,8 +83,8 @@ pem_cleanup() ->
[{doc, "Test pem cache invalidate mechanism"}].
pem_cleanup(Config)when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_verification_opts, Config),
- ServerOpts = proplists:get_value(server_verification_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -118,8 +115,8 @@ invalid_insert() ->
invalid_insert(Config)when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_verification_opts, Config),
- ServerOpts = proplists:get_value(server_verification_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
BadClientOpts = [{cacertfile, "tmp/does_not_exist.pem"} | proplists:delete(cacertfile, ClientOpts)],
Server =
diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl
index a0fab58b9d..7f33fe3204 100644
--- a/lib/ssl/test/ssl_session_cache_SUITE.erl
+++ b/lib/ssl/test/ssl_session_cache_SUITE.erl
@@ -48,7 +48,8 @@ all() ->
session_cache_process_list,
session_cache_process_mnesia,
client_unique_session,
- max_table_size
+ max_table_size,
+ save_specific_session
].
groups() ->
@@ -60,10 +61,7 @@ init_per_suite(Config0) ->
ok ->
ssl_test_lib:clean_start(),
%% make rsa certs using
- {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0),
- proplists:get_value(priv_dir, Config0)),
- Config = ssl_test_lib:make_dsa_cert(Config0),
- ssl_test_lib:cert_options(Config)
+ ssl_test_lib:make_rsa_cert(Config0)
catch _:_ ->
{skip, "Crypto did not start"}
end.
@@ -97,7 +95,10 @@ init_per_testcase(session_cleanup, Config) ->
init_per_testcase(client_unique_session, Config) ->
ct:timetrap({seconds, 40}),
Config;
-
+init_per_testcase(save_specific_session, Config) ->
+ ssl_test_lib:clean_start(),
+ ct:timetrap({seconds, 5}),
+ Config;
init_per_testcase(max_table_size, Config) ->
ssl:stop(),
application:load(ssl),
@@ -141,7 +142,7 @@ end_per_testcase(max_table_size, Config) ->
end_per_testcase(default_action, Config);
end_per_testcase(Case, Config) when Case == session_cache_process_list;
Case == session_cache_process_mnesia ->
- ets:delete(ssl_test),
+ catch ets:delete(ssl_test),
Config;
end_per_testcase(_, Config) ->
Config.
@@ -154,8 +155,8 @@ client_unique_session() ->
"sets up many connections"}].
client_unique_session(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_opts, Config),
- ServerOpts = proplists:get_value(server_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -164,8 +165,7 @@ client_unique_session(Config) when is_list(Config) ->
{tcp_options, [{active, false}]},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- LastClient = clients_start(Server,
- ClientNode, Hostname, Port, ClientOpts, client_unique_session, 20),
+ LastClient = clients_start(Server, ClientNode, Hostname, Port, ClientOpts, 20),
receive
{LastClient, {ok, _}} ->
ok
@@ -185,8 +185,8 @@ session_cleanup() ->
"does not grow and grow ..."}].
session_cleanup(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
- ServerOpts = ssl_test_lib:ssl_options(server_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
@@ -254,13 +254,75 @@ session_cache_process_mnesia(Config) when is_list(Config) ->
session_cache_process(mnesia,Config).
%%--------------------------------------------------------------------
+save_specific_session() ->
+ [{doc, "Test that we can save a specific client session"
+ }].
+save_specific_session(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+ Server ! listen,
+
+ Client2 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ SessionID1 =
+ receive
+ {Client1, S1} ->
+ S1
+ end,
+
+ SessionID2 =
+ receive
+ {Client2, S2} ->
+ S2
+ end,
+
+ true = SessionID1 =/= SessionID2,
+
+ {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)),
+ [_, _,_, _, Prop] = StatusInfo,
+ State = ssl_test_lib:state(Prop),
+ ClientCache = element(2, State),
+ 2 = ssl_session_cache:size(ClientCache),
+
+ Server ! listen,
+
+ Client3 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_session, SessionID2} | ClientOpts]}]),
+ receive
+ {Client3, SessionID2} ->
+ ok;
+ {Client3, SessionID3}->
+ ct:fail({got, SessionID3, expected, SessionID2});
+ Other ->
+ ct:fail({got,Other})
+ end.
+
+%%--------------------------------------------------------------------
max_table_size() ->
[{doc,"Test max limit on session table"}].
max_table_size(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ClientOpts = proplists:get_value(client_verification_opts, Config),
- ServerOpts = proplists:get_value(server_verification_opts, Config),
+ ClientOpts = proplists:get_value(client_rsa_verify_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server =
ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
@@ -270,7 +332,7 @@ max_table_size(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
LastClient = clients_start(Server,
- ClientNode, Hostname, Port, ClientOpts, max_table_size, 20),
+ ClientNode, Hostname, Port, ClientOpts, 20),
receive
{LastClient, {ok, _}} ->
ok
@@ -426,25 +488,27 @@ session_loop(Sess) ->
%%--------------------------------------------------------------------
session_cache_process(_Type,Config) when is_list(Config) ->
- ssl_basic_SUITE:reuse_session(Config).
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config).
-clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, Test, 0) ->
+clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) ->
%% Make sure session is registered
ct:sleep(?SLEEP * 2),
ssl_test_lib:start_client([{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {?MODULE, connection_info_result, []}},
- {from, self()}, {options, test_copts(Test, 0, ClientOpts)}]);
-clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N) ->
+ {from, self()}, {options, ClientOpts}]);
+clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) ->
spawn_link(ssl_test_lib, start_client,
[[{node, ClientNode},
{port, Port}, {host, Hostname},
{mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, test_copts(Test, N, ClientOpts)}]]),
+ {from, self()}, {options, ClientOpts}]]),
Server ! listen,
wait_for_server(),
- clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N-1).
+ clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1).
connection_info_result(Socket) ->
ssl:connection_information(Socket, [protocol, cipher_suite]).
@@ -481,21 +545,3 @@ get_delay_timers() ->
wait_for_server() ->
ct:sleep(100).
-
-
-test_copts(_, 0, ClientOpts) ->
- ClientOpts;
-test_copts(max_table_size, N, ClientOpts) ->
- Version = tls_record:highest_protocol_version([]),
- CipherSuites = %%lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))),
-[ Y|| Y = {Alg,_, _, _} <- lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), Alg =/= ecdhe_ecdsa, Alg =/= ecdh_ecdsa, Alg =/= ecdh_rsa, Alg =/= ecdhe_rsa, Alg =/= dhe_dss, Alg =/= dss],
- case length(CipherSuites) of
- M when M >= N ->
- Cipher = lists:nth(N, CipherSuites),
- ct:pal("~p",[Cipher]),
- [{ciphers, [Cipher]} | ClientOpts];
- _ ->
- ClientOpts
- end;
-test_copts(_, _, ClientOpts) ->
- ClientOpts.
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 8a2f0824fb..0173b98e1a 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -26,6 +26,7 @@
%% Note: This directive should only be used in test suites.
-compile(export_all).
+-compile(nowarn_export_all).
-record(sslsocket, { fd = nil, pid = nil}).
-define(SLEEP, 1000).
@@ -522,7 +523,7 @@ cert_options(Config) ->
{client_verification_opts, [{cacertfile, ServerCaCertFile},
{certfile, ClientCertFile},
{keyfile, ClientKeyFile},
- {ssl_imp, new}]},
+ {verify, verify_peer}]},
{client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile},
{certfile, ClientCertFileDigitalSignatureOnly},
{keyfile, ClientKeyFile},
@@ -1460,19 +1461,10 @@ cipher_result(Socket, Result) ->
%% Importante to send two packets here
%% to properly test "cipher state" handling
ssl:send(Socket, "Hello\n"),
- receive
- {ssl, Socket, "H"} ->
- ssl:send(Socket, " world\n"),
- receive_rizzo_duong_beast();
- {ssl, Socket, "Hello\n"} ->
- ssl:send(Socket, " world\n"),
- receive
- {ssl, Socket, " world\n"} ->
- ok
- end;
- Other ->
- {unexpected, Other}
- end.
+ "Hello\n" = active_recv(Socket, length( "Hello\n")),
+ ssl:send(Socket, " world\n"),
+ " world\n" = active_recv(Socket, length(" world\n")),
+ ok.
session_info_result(Socket) ->
{ok, Info} = ssl:connection_information(Socket, [session_id, cipher_suite]),
@@ -1545,7 +1537,12 @@ init_tls_version(Version, Config) ->
clean_tls_version(Config) ->
proplists:delete(protocol_opts, proplists:delete(protocol, Config)).
-
+
+sufficient_crypto_support(Version)
+ when Version == 'tlsv1.3' ->
+ CryptoSupport = crypto:supports(),
+ lists:member(rsa_pkcs1_pss_padding, proplists:get_value(rsa_opts, CryptoSupport)) andalso
+ lists:member(x448, proplists:get_value(curves, CryptoSupport));
sufficient_crypto_support(Version)
when Version == 'tlsv1.2'; Version == 'dtlsv1.2' ->
CryptoSupport = crypto:supports(),
@@ -1591,36 +1588,72 @@ v_1_2_check(ecdh_rsa, ecdh_ecdsa) ->
v_1_2_check(_, _) ->
false.
-send_recv_result_active(Socket) ->
- ssl:send(Socket, "Hello world"),
- receive
- {ssl, Socket, "H"} ->
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end;
- {ssl, Socket, "Hello world"} ->
- ok
- end.
-
send_recv_result(Socket) ->
- ssl:send(Socket, "Hello world"),
- {ok,"Hello world"} = ssl:recv(Socket, 11),
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ {ok, Data} = ssl:recv(Socket, length(Data)),
+ ok.
+
+send_recv_result_active(Socket) ->
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ Data = active_recv(Socket, length(Data)),
ok.
send_recv_result_active_once(Socket) ->
- ssl:send(Socket, "Hello world"),
- receive
- {ssl, Socket, "H"} ->
- ssl:setopts(Socket, [{active, once}]),
- receive
- {ssl, Socket, "ello world"} ->
- ok
- end;
- {ssl, Socket, "Hello world"} ->
- ok
+ Data = "Hello world",
+ ssl:send(Socket, Data),
+ active_once_recv_list(Socket, length(Data)).
+
+active_recv(Socket, N) ->
+ active_recv(Socket, N, []).
+
+active_recv(_Socket, 0, Acc) ->
+ Acc;
+active_recv(Socket, N, Acc) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ active_recv(Socket, N-length(Bytes), Acc ++ Bytes)
+ end.
+
+active_once_recv(_Socket, 0) ->
+ ok;
+active_once_recv(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_recv(Socket, N-byte_size(Bytes))
end.
+active_once_recv_list(_Socket, 0) ->
+ ok;
+active_once_recv_list(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_recv_list(Socket, N-length(Bytes))
+ end.
+recv_disregard(_Socket, 0) ->
+ ok;
+recv_disregard(Socket, N) ->
+ {ok, Bytes} = ssl:recv(Socket, 0),
+ recv_disregard(Socket, N-byte_size(Bytes)).
+
+active_disregard(_Socket, 0) ->
+ ok;
+active_disregard(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ active_disregard(Socket, N-byte_size(Bytes))
+ end.
+active_once_disregard(_Socket, 0) ->
+ ok;
+active_once_disregard(Socket, N) ->
+ receive
+ {ssl, Socket, Bytes} ->
+ ssl:setopts(Socket, [{active, once}]),
+ active_once_disregard(Socket, N-byte_size(Bytes))
+ end.
is_sane_ecc(openssl) ->
case os:cmd("openssl version") of
"OpenSSL 1.0.0a" ++ _ -> % Known bug in openssl
@@ -1706,10 +1739,10 @@ openssl_dsa_support() ->
true;
"LibreSSL" ++ _ ->
false;
- "OpenSSL 1.1" ++ Rest ->
+ "OpenSSL 1.1" ++ _Rest ->
false;
"OpenSSL 1.0.1" ++ Rest ->
- hd(Rest) >= s;
+ hd(Rest) >= $s;
_ ->
true
end.
@@ -1746,8 +1779,6 @@ openssl_sane_client_cert() ->
false;
"LibreSSL 2.0" ++ _ ->
false;
- "LibreSSL 2.0" ++ _ ->
- false;
"OpenSSL 1.0.1s-freebsd" ->
false;
"OpenSSL 1.0.0" ++ _ ->
@@ -2160,3 +2191,98 @@ server_msg(Server, ServerMsg) ->
Unexpected ->
ct:fail(Unexpected)
end.
+
+session_id(Socket) ->
+ {ok, [{session_id, ID}]} = ssl:connection_information(Socket, [session_id]),
+ ID.
+
+reuse_session(ClientOpts, ServerOpts, Config) ->
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server0 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, ServerOpts}]),
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+ Client0 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ Server0 ! listen,
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ SID = receive
+ {Client0, Id0} ->
+ Id0
+ end,
+
+ receive
+ {Client1, SID} ->
+ ok
+ after ?SLEEP ->
+ ct:fail(session_not_reused)
+ end,
+
+ Server0 ! listen,
+
+ Client2 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port0}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, false}
+ | ClientOpts]}]),
+ receive
+ {Client2, SID} ->
+ ct:fail(session_reused_when_session_reuse_disabled_by_client);
+ {Client2, _} ->
+ ok
+ end,
+
+ ssl_test_lib:close(Server0),
+ ssl_test_lib:close(Client0),
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Client2),
+
+ Server1 =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {tcp_options, [{active, false}]},
+ {options, [{reuse_sessions, false} |ServerOpts]}]),
+ Port1 = ssl_test_lib:inet_port(Server1),
+
+ Client3 = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]),
+ SID1 = receive
+ {Client3, Id3} ->
+ Id3
+ end,
+
+ Server1 ! listen,
+
+ Client4 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port1}, {host, Hostname},
+ {mfa, {ssl_test_lib, session_id, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ receive
+ {Client4, SID1} ->
+ ct:fail(session_reused_when_session_reuse_disabled_by_server);
+ {Client4, _} ->
+ ok
+ end,
+
+ ssl_test_lib:close(Server1),
+ ssl_test_lib:close(Client3),
+ ssl_test_lib:close(Client4).
+
diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl
index 5a38f5f9c1..d180021439 100644
--- a/lib/ssl/test/ssl_to_openssl_SUITE.erl
+++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl
@@ -91,6 +91,7 @@ all_versions_tests() ->
erlang_server_openssl_client_anon_with_cert,
erlang_server_openssl_client_reuse_session,
erlang_client_openssl_server_renegotiate,
+ erlang_client_openssl_server_renegotiate_after_client_data,
erlang_client_openssl_server_nowrap_seqnum,
erlang_server_openssl_client_nowrap_seqnum,
erlang_client_openssl_server_no_server_ca_cert,
@@ -259,8 +260,9 @@ special_init(TestCase, Config) when
Config;
special_init(TestCase, Config)
when TestCase == erlang_client_openssl_server_renegotiate;
- TestCase == erlang_client_openssl_server_nowrap_seqnum;
- TestCase == erlang_server_openssl_client_nowrap_seqnum
+ TestCase == erlang_client_openssl_server_nowrap_seqnum;
+ TestCase == erlang_server_openssl_client_nowrap_seqnum;
+ TestCase == erlang_client_openssl_server_renegotiate_after_client_data
->
{ok, Version} = application:get_env(ssl, protocol_version),
check_sane_openssl_renegotaite(Config, Version);
@@ -760,8 +762,8 @@ erlang_client_openssl_server_renegotiate() ->
[{doc,"Test erlang client when openssl server issuses a renegotiate"}].
erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -770,12 +772,14 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(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),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Args = ["s_server", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile, "-msg"],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -800,6 +804,53 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
ssl_test_lib:close(Client),
process_flag(trap_exit, false),
ok.
+%%--------------------------------------------------------------------
+erlang_client_openssl_server_renegotiate_after_client_data() ->
+ [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}].
+erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ ErlData = "From erlang to openssl",
+ OpenSslData = "From openssl to erlang",
+
+ 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),
+ Version = ssl_test_lib:protocol_version(Config),
+
+ Exe = "openssl",
+ Args = ["s_server", "-accept", integer_to_list(Port),
+ ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
+ "-cert", CertFile, "-key", KeyFile, "-msg"],
+
+ OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
+
+ ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ send_wait_send, [[ErlData, OpenSslData]]}},
+ {options, ClientOpts}]),
+
+ true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
+ ct:sleep(?SLEEP),
+ true = port_command(OpensslPort, OpenSslData),
+
+ ssl_test_lib:check_result(Client, ok),
+
+ %% Clean close down! Server needs to be closed first !!
+ ssl_test_lib:close_port(OpensslPort),
+ ssl_test_lib:close(Client),
+ process_flag(trap_exit, false),
+ ok.
%%--------------------------------------------------------------------
@@ -810,7 +861,7 @@ erlang_client_openssl_server_nowrap_seqnum() ->
" to lower treashold substantially."}].
erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config),
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -819,12 +870,14 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) ->
N = 10,
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),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Args = ["s_server", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile, "-msg"],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -853,7 +906,7 @@ erlang_server_openssl_client_nowrap_seqnum() ->
" to lower treashold substantially."}].
erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
{_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1602,8 +1655,8 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) ->
start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = ErlangClientOpts ++ ClientOpts0,
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1611,6 +1664,7 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
Data = "From openssl to erlang",
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),
Version = ssl_test_lib:protocol_version(Config),
@@ -1620,10 +1674,12 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
[] ->
["s_server", "-accept",
integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile,"-key", KeyFile];
[Opt, Value] ->
["s_server", Opt, Value, "-accept",
integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile,"-key", KeyFile]
end,
@@ -1648,8 +1704,8 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens
start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) ->
process_flag(trap_exit, true),
- ServerOpts = proplists:get_value(server_rsa_opts, Config),
- ClientOpts0 = proplists:get_value(client_rsa_opts, Config),
+ ServerOpts = proplists:get_value(server_rsa_verify_opts, Config),
+ ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config),
ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0],
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1657,12 +1713,14 @@ start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callba
Data = "From openssl to erlang",
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),
Version = ssl_test_lib:protocol_version(Config),
Exe = "openssl",
Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)),
@@ -1780,8 +1838,8 @@ start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Ca
start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) ->
process_flag(trap_exit, true),
- ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config),
- ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0],
{ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
@@ -1789,6 +1847,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac
Data = "From openssl to erlang",
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),
Version = ssl_test_lib:protocol_version(Config),
@@ -1796,6 +1855,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac
Exe = "openssl",
Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port),
ssl_test_lib:version_flag(Version),
+ "-CAfile", CaCertFile,
"-cert", CertFile, "-key", KeyFile],
OpensslPort = ssl_test_lib:portable_open_port(Exe, Args),
@@ -1924,6 +1984,12 @@ server_sent_garbage(Socket) ->
{error, closed} == ssl:send(Socket, "data")
end.
+
+send_wait_send(Socket, [ErlData, OpenSslData]) ->
+ ssl:send(Socket, ErlData),
+ ct:sleep(?SLEEP),
+ ssl:send(Socket, ErlData),
+ erlang_ssl_receive(Socket, OpenSslData).
check_openssl_sni_support(Config) ->
HelpText = os:cmd("openssl s_client --help"),
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 75d959accf..3527062a8a 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.1
+SSL_VSN = 9.1.2
diff --git a/lib/stdlib/doc/src/array.xml b/lib/stdlib/doc/src/array.xml
index db0ab42372..aa1577a067 100644
--- a/lib/stdlib/doc/src/array.xml
+++ b/lib/stdlib/doc/src/array.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>array.xml</file>
</header>
- <module>array</module>
+ <module since="">array</module>
<modulesummary>Functional, extendible arrays.</modulesummary>
<description>
<p>Functional, extendible arrays. Arrays can have fixed size, or can grow
@@ -137,7 +137,7 @@ A3 = array:fix(A2).</pre>
<funcs>
<func>
- <name name="default" arity="1"/>
+ <name name="default" arity="1" since=""/>
<fsummary>Get the value used for uninitialized entries.</fsummary>
<desc><marker id="default-1"/>
<p>Gets the value used for uninitialized entries.</p>
@@ -146,7 +146,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="fix" arity="1"/>
+ <name name="fix" arity="1" since=""/>
<fsummary>Fix the array size.</fsummary>
<desc><marker id="fix-1"/>
<p>Fixes the array size. This prevents it from growing automatically
@@ -157,7 +157,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Fold the array elements using the specified function and initial
accumulator value.</fsummary>
<desc><marker id="foldl-3"/>
@@ -172,7 +172,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="foldr" arity="3"/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold the array elements right-to-left using the specified
function and initial accumulator value.</fsummary>
<desc><marker id="foldr-3"/>
@@ -186,7 +186,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Equivalent to <c>from_list(List, undefined)</c>.</fsummary>
<desc><marker id="from_list-1"/>
<p>Equivalent to
@@ -195,7 +195,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_list" arity="2"/>
+ <name name="from_list" arity="2" since=""/>
<fsummary>Convert a list to an extendible array.</fsummary>
<desc><marker id="from_list-2"/>
<p>Converts a list to an extendible array. <c><anno>Default</anno></c>
@@ -208,7 +208,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_orddict" arity="1"/>
+ <name name="from_orddict" arity="1" since=""/>
<fsummary>Equivalent to <c>from_orddict(Orddict, undefined)</c>.
</fsummary>
<desc><marker id="from_orddict-1"/>
@@ -218,7 +218,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="from_orddict" arity="2"/>
+ <name name="from_orddict" arity="2" since=""/>
<fsummary>Convert an ordered list of pairs <c>{Index, Value}</c> to a
corresponding extendible array.</fsummary>
<desc><marker id="from_orddict-2"/>
@@ -234,7 +234,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since=""/>
<fsummary>Get the value of entry <c>I</c>.</fsummary>
<desc><marker id="get-2"/>
<p>Gets the value of entry <c><anno>I</anno></c>. If
@@ -249,7 +249,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="is_array" arity="1"/>
+ <name name="is_array" arity="1" since=""/>
<fsummary>Returns <c>true</c> if <c>X</c> is an array, otherwise
<c>false</c>.</fsummary>
<desc><marker id="is_array-1"/>
@@ -261,7 +261,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="is_fix" arity="1"/>
+ <name name="is_fix" arity="1" since=""/>
<fsummary>Check if the array has fixed size.</fsummary>
<desc><marker id="is_fix-1"/>
<p>Checks if the array has fixed size. Returns <c>true</c> if the array
@@ -271,7 +271,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map the specified function onto each array element.</fsummary>
<desc><marker id="map-2"/>
<p>Maps the specified function onto each array element. The elements are
@@ -285,7 +285,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create a new, extendible array with initial size zero.
</fsummary>
<desc><marker id="new-0"/>
@@ -296,7 +296,7 @@ A3 = array:fix(A2).</pre>
</func>
<func>
- <name name="new" arity="1"/>
+ <name name="new" arity="1" since=""/>
<fsummary>Create a new array according to the specified options.
</fsummary>
<desc><marker id="new-1"/>
@@ -346,7 +346,7 @@ array:new([{size,10},{fixed,false},{default,-1}])</pre>
</func>
<func>
- <name name="new" arity="2"/>
+ <name name="new" arity="2" since=""/>
<fsummary>Create a new array according to the specified size and options.
</fsummary>
<desc><marker id="new-2"/>
@@ -370,7 +370,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="relax" arity="1"/>
+ <name name="relax" arity="1" since=""/>
<fsummary>Make the array resizable.</fsummary>
<desc><marker id="relax-1"/>
<p>Makes the array resizable. (Reverses the effects of
@@ -380,7 +380,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="reset" arity="2"/>
+ <name name="reset" arity="2" since=""/>
<fsummary>Reset entry <c>I</c> to the default value for the array.
</fsummary>
<desc><marker id="reset-2"/>
@@ -399,7 +399,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="resize" arity="1"/>
+ <name name="resize" arity="1" since=""/>
<fsummary>Change the array size to that reported by <c>sparse_size/1</c>.
</fsummary>
<desc><marker id="resize-1"/>
@@ -413,7 +413,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="resize" arity="2"/>
+ <name name="resize" arity="2" since=""/>
<fsummary>Change the array size.</fsummary>
<desc><marker id="resize-2"/>
<p>Change the array size. If <c><anno>Size</anno></c> is not a
@@ -424,7 +424,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="set" arity="3"/>
+ <name name="set" arity="3" since=""/>
<fsummary>Set entry <c>I</c> of the array to <c>Value</c>.</fsummary>
<desc><marker id="set-3"/>
<p>Sets entry <c><anno>I</anno></c> of the array to
@@ -441,7 +441,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Get the number of entries in the array.</fsummary>
<desc><marker id="size-1"/>
<p>Gets the number of entries in the array. Entries are numbered from
@@ -454,7 +454,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_foldl" arity="3"/>
+ <name name="sparse_foldl" arity="3" since=""/>
<fsummary>Fold the array elements using the specified function and initial
accumulator value, skipping default-valued entries.</fsummary>
<desc><marker id="sparse_foldl-3"/>
@@ -469,7 +469,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_foldr" arity="3"/>
+ <name name="sparse_foldr" arity="3" since=""/>
<fsummary>Fold the array elements right-to-left using the specified
function and initial accumulator value, skipping default-valued
entries.</fsummary>
@@ -485,7 +485,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_map" arity="2"/>
+ <name name="sparse_map" arity="2" since=""/>
<fsummary>Map the specified function onto each array element, skipping
default-valued entries.</fsummary>
<desc><marker id="sparse_map-2"/>
@@ -498,7 +498,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_size" arity="1"/>
+ <name name="sparse_size" arity="1" since=""/>
<fsummary>Get the number of entries in the array up until the last
non-default-valued entry.</fsummary>
<desc><marker id="sparse_size-1"/>
@@ -512,7 +512,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_to_list" arity="1"/>
+ <name name="sparse_to_list" arity="1" since=""/>
<fsummary>Convert the array to a list, skipping default-valued entries.
</fsummary>
<desc><marker id="sparse_to_list-1"/>
@@ -522,7 +522,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="sparse_to_orddict" arity="1"/>
+ <name name="sparse_to_orddict" arity="1" since=""/>
<fsummary>Convert the array to an ordered list of pairs <c>{Index,
Value}</c>, skipping default-valued entries.</fsummary>
<desc><marker id="sparse_to_orddict-1"/>
@@ -534,7 +534,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert the array to a list.</fsummary>
<desc><marker id="to_list-1"/>
<p>Converts the array to a list.</p>
@@ -545,7 +545,7 @@ array:new(100, {default,0})</pre>
</func>
<func>
- <name name="to_orddict" arity="1"/>
+ <name name="to_orddict" arity="1" since=""/>
<fsummary>Convert the array to an ordered list of pairs <c>{Index,
Value}</c>.</fsummary>
<desc><marker id="to_orddict-1"/>
diff --git a/lib/stdlib/doc/src/base64.xml b/lib/stdlib/doc/src/base64.xml
index cfa1ecc006..479072ba4f 100644
--- a/lib/stdlib/doc/src/base64.xml
+++ b/lib/stdlib/doc/src/base64.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>base64.xml</file>
</header>
- <module>base64</module>
+ <module since="">base64</module>
<modulesummary>Provides base64 encode and decode, see
RFC 2045.</modulesummary>
<description>
@@ -51,10 +51,10 @@
<funcs>
<func>
- <name name="decode" arity="1"/>
- <name name="decode_to_string" arity="1"/>
- <name name="mime_decode" arity="1"/>
- <name name="mime_decode_to_string" arity="1"/>
+ <name name="decode" arity="1" since=""/>
+ <name name="decode_to_string" arity="1" since=""/>
+ <name name="mime_decode" arity="1" since=""/>
+ <name name="mime_decode_to_string" arity="1" since=""/>
<fsummary>Decode a base64 encoded string to data.</fsummary>
<type variable="Base64" name_i="1"/>
<type variable="Data" name_i="1"/>
@@ -69,8 +69,8 @@
</func>
<func>
- <name name="encode" arity="1"/>
- <name name="encode_to_string" arity="1"/>
+ <name name="encode" arity="1" since=""/>
+ <name name="encode_to_string" arity="1" since=""/>
<fsummary>Encode data into base64.</fsummary>
<type variable="Data"/>
<type variable="Base64" name_i="1"/>
diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml
index 213170df7f..8bb4cf9101 100644
--- a/lib/stdlib/doc/src/beam_lib.xml
+++ b/lib/stdlib/doc/src/beam_lib.xml
@@ -28,7 +28,7 @@
<date>1999-10-30</date>
<rev>PA1</rev>
</header>
- <module>beam_lib</module>
+ <module since="">beam_lib</module>
<modulesummary>An interface to the BEAM file format.</modulesummary>
<description>
<p>This module provides an interface to files created by
@@ -267,7 +267,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
<funcs>
<func>
- <name name="all_chunks" arity="1"/>
+ <name name="all_chunks" arity="1" since="OTP 18.2"/>
<fsummary>Read all chunks from a BEAM file or binary</fsummary>
<desc>
<p>Reads chunk data for all chunks.</p>
@@ -275,7 +275,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="build_module" arity="1"/>
+ <name name="build_module" arity="1" since="OTP 18.2"/>
<fsummary>Create a BEAM module from a list of chunks.</fsummary>
<desc>
<p>Builds a BEAM module (as a binary) from a list of chunks.</p>
@@ -283,7 +283,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="chunks" arity="2"/>
+ <name name="chunks" arity="2" since=""/>
<fsummary>Read selected chunks from a BEAM file or binary.</fsummary>
<desc>
<p>Reads chunk data for selected chunks references. The order of
@@ -293,7 +293,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="chunks" arity="3"/>
+ <name name="chunks" arity="3" since=""/>
<fsummary>Read selected chunks from a BEAM file or binary.</fsummary>
<desc>
<p>Reads chunk data for selected chunks references. The order of
@@ -312,7 +312,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="clear_crypto_key_fun" arity="0"/>
+ <name name="clear_crypto_key_fun" arity="0" since=""/>
<fsummary>Unregister the current crypto key fun.</fsummary>
<desc>
<p>Unregisters the crypto key fun and terminates the process
@@ -327,7 +327,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="cmp" arity="2"/>
+ <name name="cmp" arity="2" since=""/>
<fsummary>Compare two BEAM files.</fsummary>
<type name="cmp_rsn"/>
<desc>
@@ -341,7 +341,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="cmp_dirs" arity="2"/>
+ <name name="cmp_dirs" arity="2" since=""/>
<fsummary>Compare the BEAM files in two directories.</fsummary>
<desc>
<p>Compares the BEAM files in
@@ -359,7 +359,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code>
</func>
<func>
- <name name="crypto_key_fun" arity="1"/>
+ <name name="crypto_key_fun" arity="1" since=""/>
<fsummary>Register a fun that provides a crypto key.</fsummary>
<type name="crypto_fun"/>
<type name="crypto_fun_arg"/>
@@ -398,7 +398,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="diff_dirs" arity="2"/>
+ <name name="diff_dirs" arity="2" since=""/>
<fsummary>Compare the BEAM files in two directories.</fsummary>
<desc>
<p>Compares the BEAM files in two directories as
@@ -409,7 +409,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return an English description of a BEAM read error reply.
</fsummary>
<desc>
@@ -422,7 +422,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Information about a BEAM file.</fsummary>
<desc>
<p>Returns a list containing some information about a BEAM file
@@ -449,7 +449,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="md5" arity="1"/>
+ <name name="md5" arity="1" since=""/>
<fsummary>Read the module version of the BEAM file.</fsummary>
<desc>
<p>Calculates an MD5 redundancy check for the code of the module
@@ -458,7 +458,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="strip" arity="1"/>
+ <name name="strip" arity="1" since=""/>
<fsummary>Remove chunks not needed by the loader from a BEAM file.
</fsummary>
<desc>
@@ -470,7 +470,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="strip_files" arity="1"/>
+ <name name="strip_files" arity="1" since=""/>
<fsummary>Removes chunks not needed by the loader from BEAM files.
</fsummary>
<desc>
@@ -483,7 +483,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="strip_release" arity="1"/>
+ <name name="strip_release" arity="1" since=""/>
<fsummary>Remove chunks not needed by the loader from all BEAM files of
a release.</fsummary>
<desc>
@@ -497,7 +497,7 @@ CryptoKeyFun(clear) -> term()</code>
</func>
<func>
- <name name="version" arity="1"/>
+ <name name="version" arity="1" since=""/>
<fsummary>Read the module version of the BEAM file.</fsummary>
<desc>
<p>Returns the module version or versions. A version is defined by
diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml
index 6a86d6c7ba..f3d4edd30f 100644
--- a/lib/stdlib/doc/src/binary.xml
+++ b/lib/stdlib/doc/src/binary.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>binary.xml</file>
</header>
- <module>binary</module>
+ <module since="OTP R14B">binary</module>
<modulesummary>Library for handling binary data.</modulesummary>
<description>
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name name="at" arity="2"/>
+ <name name="at" arity="2" since="OTP R14B"/>
<fsummary>Return the byte at a specific position in a binary.</fsummary>
<desc>
<p>Returns the byte at position <c><anno>Pos</anno></c> (zero-based) in
@@ -90,7 +90,7 @@
</func>
<func>
- <name name="bin_to_list" arity="1"/>
+ <name name="bin_to_list" arity="1" since="OTP R14B"/>
<fsummary>Convert a binary to a list of integers.</fsummary>
<desc>
<p>Same as <c>bin_to_list(<anno>Subject</anno>, {0,byte_size(<anno>Subject</anno>)})</c>.</p>
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="bin_to_list" arity="2"/>
+ <name name="bin_to_list" arity="2" since="OTP R14B"/>
<fsummary>Convert a binary to a list of integers.</fsummary>
<desc>
<p>Converts <c><anno>Subject</anno></c> to a list of <c>byte()</c>s, each
@@ -118,7 +118,7 @@
</func>
<func>
- <name name="bin_to_list" arity="3"/>
+ <name name="bin_to_list" arity="3" since="OTP R14B"/>
<fsummary>Convert a binary to a list of integers.</fsummary>
<desc>
<p>Same as<c> bin_to_list(<anno>Subject</anno>, {<anno>Pos</anno>, <anno>Len</anno>})</c>.</p>
@@ -126,7 +126,7 @@
</func>
<func>
- <name name="compile_pattern" arity="1"/>
+ <name name="compile_pattern" arity="1" since="OTP R14B"/>
<fsummary>Precompile a binary search pattern.</fsummary>
<desc>
<p>Builds an internal structure representing a compilation of a
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="copy" arity="1"/>
+ <name name="copy" arity="1" since="OTP R14B"/>
<fsummary>Create a duplicate of a binary.</fsummary>
<desc>
<p>Same as <c>copy(<anno>Subject</anno>, 1)</c>.</p>
@@ -166,7 +166,7 @@
</func>
<func>
- <name name="copy" arity="2"/>
+ <name name="copy" arity="2" since="OTP R14B"/>
<fsummary>Duplicate a binary <c>N</c> times and create a new.</fsummary>
<desc>
<p>Creates a binary with the content of <c><anno>Subject</anno></c>
@@ -193,7 +193,7 @@
</func>
<func>
- <name name="decode_unsigned" arity="1"/>
+ <name name="decode_unsigned" arity="1" since="OTP R14B"/>
<fsummary>Decode a whole binary into an integer of arbitrary size.
</fsummary>
<desc>
@@ -202,7 +202,7 @@
</func>
<func>
- <name name="decode_unsigned" arity="2"/>
+ <name name="decode_unsigned" arity="2" since="OTP R14B"/>
<fsummary>Decode a whole binary into an integer of arbitrary size.
</fsummary>
<desc>
@@ -219,7 +219,7 @@
</func>
<func>
- <name name="encode_unsigned" arity="1"/>
+ <name name="encode_unsigned" arity="1" since="OTP R14B"/>
<fsummary>Encode an unsigned integer into the minimal binary.</fsummary>
<desc>
<p>Same as <c>encode_unsigned(<anno>Unsigned</anno>, big)</c>.</p>
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="encode_unsigned" arity="2"/>
+ <name name="encode_unsigned" arity="2" since="OTP R14B"/>
<fsummary>Encode an unsigned integer into the minimal binary.</fsummary>
<desc>
<p>Converts a positive integer to the smallest possible
@@ -243,7 +243,7 @@
</func>
<func>
- <name name="first" arity="1"/>
+ <name name="first" arity="1" since="OTP R14B"/>
<fsummary>Return the first byte of a binary.</fsummary>
<desc>
<p>Returns the first byte of binary <c><anno>Subject</anno></c> as an
@@ -253,7 +253,7 @@
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since="OTP R14B"/>
<fsummary>Return the last byte of a binary.</fsummary>
<desc>
<p>Returns the last byte of binary <c><anno>Subject</anno></c> as an
@@ -263,7 +263,7 @@
</func>
<func>
- <name name="list_to_bin" arity="1"/>
+ <name name="list_to_bin" arity="1" since="OTP R14B"/>
<fsummary>Convert a list of integers and binaries to a binary.</fsummary>
<desc>
<p>Works exactly as
@@ -273,7 +273,7 @@
</func>
<func>
- <name name="longest_common_prefix" arity="1"/>
+ <name name="longest_common_prefix" arity="1" since="OTP R14B"/>
<fsummary>Return length of longest common prefix for a set of binaries.
</fsummary>
<desc>
@@ -294,7 +294,7 @@
</func>
<func>
- <name name="longest_common_suffix" arity="1"/>
+ <name name="longest_common_suffix" arity="1" since="OTP R14B"/>
<fsummary>Return length of longest common suffix for a set of binaries.
</fsummary>
<desc>
@@ -315,7 +315,7 @@
</func>
<func>
- <name name="match" arity="2"/>
+ <name name="match" arity="2" since="OTP R14B"/>
<fsummary>Search for the first match of a pattern in a binary.</fsummary>
<desc>
<p>Same as <c>match(<anno>Subject</anno>, <anno>Pattern</anno>, [])</c>.
@@ -324,7 +324,7 @@
</func>
<func>
- <name name="match" arity="3"/>
+ <name name="match" arity="3" since="OTP R14B"/>
<fsummary>Search for the first match of a pattern in a binary.</fsummary>
<type name="part"/>
<desc>
@@ -372,7 +372,7 @@
</func>
<func>
- <name name="matches" arity="2"/>
+ <name name="matches" arity="2" since="OTP R14B"/>
<fsummary>Search for all matches of a pattern in a binary.</fsummary>
<desc>
<p>Same as <c>matches(<anno>Subject</anno>, <anno>Pattern</anno>, [])</c>.
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="matches" arity="3"/>
+ <name name="matches" arity="3" since="OTP R14B"/>
<fsummary>Search for all matches of a pattern in a binary.</fsummary>
<type name="part"/>
<desc>
@@ -425,7 +425,7 @@
</func>
<func>
- <name name="part" arity="2"/>
+ <name name="part" arity="2" since="OTP R14B"/>
<fsummary>Extract a part of a binary.</fsummary>
<desc>
<p>Extracts the part of binary <c><anno>Subject</anno></c> described by
@@ -453,7 +453,7 @@
</func>
<func>
- <name name="part" arity="3"/>
+ <name name="part" arity="3" since="OTP R14B"/>
<fsummary>Extract a part of a binary.</fsummary>
<desc>
<p>Same as <c>part(<anno>Subject</anno>, {<anno>Pos</anno>,
@@ -462,7 +462,7 @@
</func>
<func>
- <name name="referenced_byte_size" arity="1"/>
+ <name name="referenced_byte_size" arity="1" since="OTP R14B"/>
<fsummary>Determine the size of the binary pointed out by a subbinary.
</fsummary>
<desc>
@@ -525,7 +525,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="replace" arity="3"/>
+ <name name="replace" arity="3" since="OTP R14B"/>
<fsummary>Replace bytes in a binary according to a pattern.</fsummary>
<desc>
<p>Same as <c>replace(<anno>Subject</anno>, <anno>Pattern</anno>, <anno>Replacement</anno>,[])</c>.</p>
@@ -533,7 +533,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="replace" arity="4"/>
+ <name name="replace" arity="4" since="OTP R14B"/>
<fsummary>Replace bytes in a binary according to a pattern.</fsummary>
<type_desc variable="OnePos">An integer() =&lt; byte_size(<anno>Replacement</anno>)
</type_desc>
@@ -575,7 +575,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since="OTP R14B"/>
<fsummary>Split a binary according to a pattern.</fsummary>
<desc>
<p>Same as <c>split(<anno>Subject</anno>, <anno>Pattern</anno>,
@@ -584,7 +584,7 @@ store(Binary, GBSet) ->
</func>
<func>
- <name name="split" arity="3"/>
+ <name name="split" arity="3" since="OTP R14B"/>
<fsummary>Split a binary according to a pattern.</fsummary>
<desc>
<p>Splits <c><anno>Subject</anno></c> into a list of binaries based on
diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml
index b6cb6f5aae..29edc373c7 100644
--- a/lib/stdlib/doc/src/c.xml
+++ b/lib/stdlib/doc/src/c.xml
@@ -28,7 +28,7 @@
<date>1996-10-30</date>
<rev>B</rev>
</header>
- <module>c</module>
+ <module since="">c</module>
<modulesummary>Command interface module.</modulesummary>
<description>
<p>This module enables users to enter the short form of
@@ -41,7 +41,7 @@
<funcs>
<func>
- <name name="bt" arity="1"/>
+ <name name="bt" arity="1" since=""/>
<fsummary>Stack backtrace for a process.</fsummary>
<desc>
<p>Stack backtrace for a process. Equivalent to
@@ -50,9 +50,9 @@
</func>
<func>
- <name name="c" arity="1"/>
- <name name="c" arity="2"/>
- <name name="c" arity="3"/>
+ <name name="c" arity="1" since=""/>
+ <name name="c" arity="2" since=""/>
+ <name name="c" arity="3" since="OTP 20.0"/>
<fsummary>Compile and load a file or module.</fsummary>
<desc>
<p>Compiles and then purges and loads the code for a module.
@@ -80,7 +80,7 @@
</func>
<func>
- <name name="cd" arity="1"/>
+ <name name="cd" arity="1" since=""/>
<fsummary>Change working directory.</fsummary>
<desc>
<p>Changes working directory to <c><anno>Dir</anno></c>, which can be a
@@ -94,7 +94,7 @@
</func>
<func>
- <name name="erlangrc" arity="1"/>
+ <name name="erlangrc" arity="1" since="OTP 21.0"/>
<fsummary>Load an erlang resource file.</fsummary>
<desc>
<p>Search <c>PathList</c> and load <c>.erlang</c> resource file if
@@ -103,7 +103,7 @@
</func>
<func>
- <name name="flush" arity="0"/>
+ <name name="flush" arity="0" since=""/>
<fsummary>Flush any messages sent to the shell.</fsummary>
<desc>
<p>Flushes any messages sent to the shell.</p>
@@ -111,7 +111,7 @@
</func>
<func>
- <name name="help" arity="0"/>
+ <name name="help" arity="0" since=""/>
<fsummary>Help information.</fsummary>
<desc>
<p>Displays help information: all valid shell internal commands,
@@ -120,8 +120,8 @@
</func>
<func>
- <name name="i" arity="0"/>
- <name name="ni" arity="0"/>
+ <name name="i" arity="0" since=""/>
+ <name name="ni" arity="0" since=""/>
<fsummary>System information.</fsummary>
<desc>
<p><c>i/0</c> displays system information, listing
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="i" arity="3"/>
+ <name name="i" arity="3" since=""/>
<fsummary>Information about pid &lt;X.Y.Z&gt;.</fsummary>
<desc>
<p>Displays information about a process, Equivalent to
@@ -141,7 +141,7 @@
</func>
<func>
- <name name="l" arity="1"/>
+ <name name="l" arity="1" since=""/>
<fsummary>Load or reload a module.</fsummary>
<desc>
<p>Purges and loads, or reloads, a module by calling
@@ -154,7 +154,7 @@
</func>
<func>
- <name>lc(Files) -> ok</name>
+ <name since="">lc(Files) -> ok</name>
<fsummary>Compile a list of files.</fsummary>
<type>
<v>Files = [File]</v>
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="lm" arity="0"/>
+ <name name="lm" arity="0" since="OTP 20.0"/>
<fsummary>Loads all modified modules.</fsummary>
<desc>
<p>Reloads all currently loaded modules that have changed on disk (see <c>mm()</c>).
@@ -180,7 +180,7 @@
</func>
<func>
- <name name="ls" arity="0"/>
+ <name name="ls" arity="0" since=""/>
<fsummary>List files in the current directory.</fsummary>
<desc>
<p>Lists files in the current directory.</p>
@@ -188,7 +188,7 @@
</func>
<func>
- <name name="ls" arity="1"/>
+ <name name="ls" arity="1" since=""/>
<fsummary>List files in a directory or a single file.</fsummary>
<desc>
<p>Lists files in directory <c><anno>Dir</anno></c> or, if <c>Dir</c>
@@ -197,7 +197,7 @@
</func>
<func>
- <name name="m" arity="0"/>
+ <name name="m" arity="0" since=""/>
<fsummary>Which modules are loaded.</fsummary>
<desc>
<p>Displays information about the loaded modules, including
@@ -206,7 +206,7 @@
</func>
<func>
- <name name="m" arity="1"/>
+ <name name="m" arity="1" since=""/>
<fsummary>Information about a module.</fsummary>
<desc>
<p>Displays information about <c><anno>Module</anno></c>.</p>
@@ -214,7 +214,7 @@
</func>
<func>
- <name name="mm" arity="0"/>
+ <name name="mm" arity="0" since="OTP 20.0"/>
<fsummary>Lists all modified modules.</fsummary>
<desc>
<p>Lists all modified modules. Shorthand for
@@ -223,7 +223,7 @@
</func>
<func>
- <name name="memory" arity="0"/>
+ <name name="memory" arity="0" since=""/>
<fsummary>Memory allocation information.</fsummary>
<desc>
<p>Memory allocation information. Equivalent to
@@ -232,8 +232,8 @@
</func>
<func>
- <name name="memory" arity="1" clause_i="1"/>
- <name name="memory" arity="1" clause_i="2"/>
+ <name name="memory" arity="1" clause_i="1" since=""/>
+ <name name="memory" arity="1" clause_i="2" since=""/>
<fsummary>Memory allocation information.</fsummary>
<desc>
<p>Memory allocation information. Equivalent to
@@ -242,8 +242,8 @@
</func>
<func>
- <name name="nc" arity="1"/>
- <name name="nc" arity="2"/>
+ <name name="nc" arity="1" since=""/>
+ <name name="nc" arity="2" since=""/>
<fsummary>Compile and load code in a file on all nodes.</fsummary>
<desc>
<p>Compiles and then loads the code for a file on all nodes.
@@ -255,7 +255,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="nl" arity="1"/>
+ <name name="nl" arity="1" since=""/>
<fsummary>Load module on all nodes.</fsummary>
<desc>
<p>Loads <c><anno>Module</anno></c> on all nodes.</p>
@@ -263,7 +263,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="pid" arity="3"/>
+ <name name="pid" arity="3" since=""/>
<fsummary>Convert <c>X,Y,Z</c> to a pid.</fsummary>
<desc>
<p>Converts <c><anno>X</anno></c>, <c><anno>Y</anno></c>,
@@ -273,7 +273,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="pwd" arity="0"/>
+ <name name="pwd" arity="0" since=""/>
<fsummary>Print working directory.</fsummary>
<desc>
<p>Prints the name of the working directory.</p>
@@ -281,7 +281,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="q" arity="0"/>
+ <name name="q" arity="0" since=""/>
<fsummary>Quit - shorthand for <c>init:stop()</c>.</fsummary>
<desc>
<p>This function is shorthand for <c>init:stop()</c>, that is,
@@ -290,8 +290,8 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="regs" arity="0"/>
- <name name="nregs" arity="0"/>
+ <name name="regs" arity="0" since=""/>
+ <name name="nregs" arity="0" since=""/>
<fsummary>Information about registered processes.</fsummary>
<desc>
<p><c>regs/0</c> displays information about all registered
@@ -301,7 +301,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name name="uptime" arity="0"/>
+ <name name="uptime" arity="0" since="OTP 18.0"/>
<fsummary>Print node uptime.</fsummary>
<desc>
<p>Prints the node uptime (as specified by
@@ -310,7 +310,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name>xm(ModSpec) -> void()</name>
+ <name since="">xm(ModSpec) -> void()</name>
<fsummary>Cross-reference check a module.</fsummary>
<type>
<v>ModSpec = Module | Filename</v>
@@ -325,7 +325,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w
</func>
<func>
- <name>y(File) -> YeccRet</name>
+ <name since="">y(File) -> YeccRet</name>
<fsummary>Generate an LALR-1 parser.</fsummary>
<type>
<v>File = name()</v>
@@ -344,7 +344,7 @@ yecc:file(File)</code>
</func>
<func>
- <name>y(File, Options) -> YeccRet</name>
+ <name since="">y(File, Options) -> YeccRet</name>
<fsummary>Generate an LALR-1 parser.</fsummary>
<type>
<v>File = name()</v>
diff --git a/lib/stdlib/doc/src/calendar.xml b/lib/stdlib/doc/src/calendar.xml
index 6b4fa7f98a..518a085c89 100644
--- a/lib/stdlib/doc/src/calendar.xml
+++ b/lib/stdlib/doc/src/calendar.xml
@@ -28,7 +28,7 @@
<date>1996-11-05</date>
<rev>B</rev>
</header>
- <module>calendar</module>
+ <module since="">calendar</module>
<modulesummary>Local and universal time, day of the week, date and time
conversions.</modulesummary>
<description>
@@ -128,8 +128,8 @@
<funcs>
<func>
- <name name="date_to_gregorian_days" arity="1"/>
- <name name="date_to_gregorian_days" arity="3"/>
+ <name name="date_to_gregorian_days" arity="1" since=""/>
+ <name name="date_to_gregorian_days" arity="3" since=""/>
<fsummary>Compute the number of days from year 0 up to the specified
date.</fsummary>
<type variable="Date" name_i="1"/>
@@ -143,7 +143,7 @@
</func>
<func>
- <name name="datetime_to_gregorian_seconds" arity="1"/>
+ <name name="datetime_to_gregorian_seconds" arity="1" since=""/>
<fsummary>Compute the number of seconds from year 0 up to the specified
date and time.</fsummary>
<desc>
@@ -153,8 +153,8 @@
</func>
<func>
- <name name="day_of_the_week" arity="1"/>
- <name name="day_of_the_week" arity="3"/>
+ <name name="day_of_the_week" arity="1" since=""/>
+ <name name="day_of_the_week" arity="3" since=""/>
<fsummary>Compute the day of the week.</fsummary>
<type variable="Date" name_i="1"/>
<type variable="Year"/>
@@ -169,7 +169,7 @@
</func>
<func>
- <name name="gregorian_days_to_date" arity="1"/>
+ <name name="gregorian_days_to_date" arity="1" since=""/>
<fsummary>Compute the date from the number of gregorian days.</fsummary>
<desc>
<p>Computes the date from the specified number of gregorian days.</p>
@@ -177,7 +177,7 @@
</func>
<func>
- <name name="gregorian_seconds_to_datetime" arity="1"/>
+ <name name="gregorian_seconds_to_datetime" arity="1" since=""/>
<fsummary>Compute the date and time from the number of gregorian seconds.
</fsummary>
<desc>
@@ -187,7 +187,7 @@
</func>
<func>
- <name name="is_leap_year" arity="1"/>
+ <name name="is_leap_year" arity="1" since=""/>
<fsummary>Check if the year is a leap year.</fsummary>
<desc>
<p>Checks if the specified year is a leap year.</p>
@@ -195,7 +195,7 @@
</func>
<func>
- <name name="iso_week_number" arity="0"/>
+ <name name="iso_week_number" arity="0" since="OTP R14B02"/>
<fsummary>Compute the ISO week number for the actual date.</fsummary>
<desc>
<p>Returns tuple <c>{Year, WeekNum}</c> representing
@@ -206,7 +206,7 @@
</func>
<func>
- <name name="iso_week_number" arity="1"/>
+ <name name="iso_week_number" arity="1" since="OTP R14B02"/>
<fsummary>Compute the ISO week number for the specified date.</fsummary>
<desc>
<p>Returns tuple <c>{Year, WeekNum}</c> representing
@@ -215,7 +215,7 @@
</func>
<func>
- <name name="last_day_of_the_month" arity="2"/>
+ <name name="last_day_of_the_month" arity="2" since=""/>
<fsummary>Compute the number of days in a month.</fsummary>
<desc>
<p>Computes the number of days in a month.</p>
@@ -223,7 +223,7 @@
</func>
<func>
- <name name="local_time" arity="0"/>
+ <name name="local_time" arity="0" since=""/>
<fsummary>Compute local time.</fsummary>
<desc>
<p>Returns the local time reported by
@@ -232,7 +232,7 @@
</func>
<func>
- <name name="local_time_to_universal_time" arity="1"/>
+ <name name="local_time_to_universal_time" arity="1" since=""/>
<fsummary>Convert from local time to universal time (deprecated).
</fsummary>
<desc>
@@ -253,7 +253,7 @@
</func>
<func>
- <name name="local_time_to_universal_time_dst" arity="1"/>
+ <name name="local_time_to_universal_time_dst" arity="1" since=""/>
<fsummary>Convert from local time to universal time(s).</fsummary>
<desc>
<p>Converts from local time to Universal Coordinated Time (UTC).
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="now_to_datetime" arity="1"/>
+ <name name="now_to_datetime" arity="1" since=""/>
<fsummary>Convert now to date and time.</fsummary>
<desc>
<p>Returns Universal Coordinated Time (UTC)
@@ -296,7 +296,7 @@
</func>
<func>
- <name name="now_to_local_time" arity="1"/>
+ <name name="now_to_local_time" arity="1" since=""/>
<fsummary>Convert now to local date and time.</fsummary>
<desc>
<p>Returns local date and time converted from the return value from
@@ -306,7 +306,7 @@
</func>
<func>
- <name name="now_to_universal_time" arity="1"/>
+ <name name="now_to_universal_time" arity="1" since=""/>
<fsummary>Convert now to date and time.</fsummary>
<desc>
<p>Returns Universal Coordinated Time (UTC)
@@ -317,8 +317,8 @@
</func>
<func>
- <name name="rfc3339_to_system_time" arity="1"/>
- <name name="rfc3339_to_system_time" arity="2"/>
+ <name name="rfc3339_to_system_time" arity="1" since="OTP 21.0"/>
+ <name name="rfc3339_to_system_time" arity="2" since="OTP 21.0"/>
<fsummary>Convert from RFC 3339 timestamp to system time.</fsummary>
<type name="rfc3339_string"/>
<type name="rfc3339_time_unit"/>
@@ -343,7 +343,7 @@
</func>
<func>
- <name name="seconds_to_daystime" arity="1"/>
+ <name name="seconds_to_daystime" arity="1" since=""/>
<fsummary>Compute days and time from seconds.</fsummary>
<desc>
<p>Converts a specified number of seconds into days, hours, minutes,
@@ -354,7 +354,7 @@
</func>
<func>
- <name name="seconds_to_time" arity="1"/>
+ <name name="seconds_to_time" arity="1" since=""/>
<fsummary>Compute time from seconds.</fsummary>
<type name="secs_per_day"/>
<desc>
@@ -365,7 +365,7 @@
</func>
<func>
- <name name="system_time_to_local_time" arity="2"/>
+ <name name="system_time_to_local_time" arity="2" since="OTP 21.0"/>
<fsummary>Convert system time to local date and time.</fsummary>
<desc>
<p>Converts a specified system time into local date and time.</p>
@@ -373,8 +373,8 @@
</func>
<func>
- <name name="system_time_to_rfc3339" arity="1"/>
- <name name="system_time_to_rfc3339" arity="2"/>
+ <name name="system_time_to_rfc3339" arity="1" since="OTP 21.0"/>
+ <name name="system_time_to_rfc3339" arity="2" since="OTP 21.0"/>
<fsummary>Convert from system to RFC 3339 timestamp.</fsummary>
<type name="offset"/>
<type name="rfc3339_string"/>
@@ -403,7 +403,11 @@
default is <c>second</c>. If some other unit is given
(<c>millisecond</c>, <c>microsecond</c>, or
<c>nanosecond</c>), the formatted string includes a
- fraction of a second.</p>
+ fraction of a second. The number of fractional second
+ digits is three, six, or nine depending on what time unit
+ is chosen. Notice that trailing zeros are not removed from
+ the fraction.
+ </p>
</item>
</taglist>
<pre>
@@ -422,7 +426,7 @@
</func>
<func>
- <name name="system_time_to_universal_time" arity="2"/>
+ <name name="system_time_to_universal_time" arity="2" since="OTP 21.0"/>
<fsummary>Convert system time to universal date and time.</fsummary>
<desc>
<p>Converts a specified system time into universal date and time.</p>
@@ -430,7 +434,7 @@
</func>
<func>
- <name name="time_difference" arity="2"/>
+ <name name="time_difference" arity="2" since=""/>
<fsummary>Compute the difference between two times (deprecated).
</fsummary>
<desc>
@@ -445,7 +449,7 @@
</func>
<func>
- <name name="time_to_seconds" arity="1"/>
+ <name name="time_to_seconds" arity="1" since=""/>
<fsummary>Compute the number of seconds since midnight up to the
specified time.</fsummary>
<type name="secs_per_day"/>
@@ -456,7 +460,7 @@
</func>
<func>
- <name name="universal_time" arity="0"/>
+ <name name="universal_time" arity="0" since=""/>
<fsummary>Compute universal time.</fsummary>
<desc>
<p>Returns the Universal Coordinated Time (UTC)
@@ -466,7 +470,7 @@
</func>
<func>
- <name name="universal_time_to_local_time" arity="1"/>
+ <name name="universal_time_to_local_time" arity="1" since=""/>
<fsummary>Convert from universal time to local time.</fsummary>
<desc>
<p>Converts from Universal Coordinated Time (UTC) to local time.
@@ -476,8 +480,8 @@
</func>
<func>
- <name name="valid_date" arity="1"/>
- <name name="valid_date" arity="3"/>
+ <name name="valid_date" arity="1" since=""/>
+ <name name="valid_date" arity="3" since=""/>
<fsummary>Check if a date is valid</fsummary>
<type variable="Date" name_i="1"/>
<type variable="Year"/>
diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml
index eb6e32aecf..8e4e002000 100644
--- a/lib/stdlib/doc/src/dets.xml
+++ b/lib/stdlib/doc/src/dets.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>dets.xml</file>
</header>
- <module>dets</module>
+ <module since="">dets</module>
<modulesummary>A disk-based term storage.</modulesummary>
<description>
<p>This module provides a term storage on file. The
@@ -188,7 +188,7 @@
<funcs>
<func>
- <name name="all" arity="0"/>
+ <name name="all" arity="0" since=""/>
<fsummary>Return a list of the names of all open Dets tables on
this node.</fsummary>
<desc>
@@ -197,7 +197,7 @@
</func>
<func>
- <name name="bchunk" arity="2"/>
+ <name name="bchunk" arity="2" since=""/>
<fsummary>Return a chunk of objects stored in a Dets table.
</fsummary>
<desc>
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close a Dets table.</fsummary>
<desc>
<p>Closes a table. Only processes that have opened a table are
@@ -239,7 +239,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Delete all objects with a specified key from a Dets
table.</fsummary>
<desc>
@@ -249,7 +249,7 @@
</func>
<func>
- <name name="delete_all_objects" arity="1"/>
+ <name name="delete_all_objects" arity="1" since=""/>
<fsummary>Delete all objects from a Dets table.</fsummary>
<desc>
<p>Deletes all objects from a table in almost constant time.
@@ -259,7 +259,7 @@
</func>
<func>
- <name name="delete_object" arity="2"/>
+ <name name="delete_object" arity="2" since=""/>
<fsummary>Delete a specified object from a Dets table.</fsummary>
<desc>
<p>Deletes all instances of a specified object from a table. If a
@@ -270,7 +270,7 @@
</func>
<func>
- <name name="first" arity="1"/>
+ <name name="first" arity="1" since=""/>
<fsummary>Return the first key stored in a Dets table.</fsummary>
<desc>
<p>Returns the first key stored in table <c><anno>Name</anno></c>
@@ -295,8 +295,8 @@
</func>
<func>
- <name name="foldl" arity="3"/>
- <name name="foldr" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over a Dets table.</fsummary>
<desc>
<p>Calls <c><anno>Function</anno></c> on successive elements of
@@ -309,7 +309,7 @@
</func>
<func>
- <name name="from_ets" arity="2"/>
+ <name name="from_ets" arity="2" since=""/>
<fsummary>Replace the objects of a Dets table with the objects
of an ETS table.</fsummary>
<desc>
@@ -322,7 +322,7 @@
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about a Dets table.</fsummary>
<desc>
<p>Returns information about table <c><anno>Name</anno></c>
@@ -354,7 +354,7 @@
</func>
<func>
- <name name="info" arity="2"/>
+ <name name="info" arity="2" since=""/>
<fsummary>Return the information associated with a specified item for
a Dets table.</fsummary>
<desc>
@@ -455,8 +455,8 @@
</func>
<func>
- <name name="init_table" arity="2"/>
- <name name="init_table" arity="3"/>
+ <name name="init_table" arity="2" since=""/>
+ <name name="init_table" arity="3" since=""/>
<fsummary>Replace all objects of a Dets table.</fsummary>
<desc>
<p>Replaces the existing objects of table <c><anno>Name</anno></c>
@@ -516,7 +516,7 @@
</func>
<func>
- <name name="insert" arity="2"/>
+ <name name="insert" arity="2" since=""/>
<fsummary>Insert one or more objects into a Dets table.</fsummary>
<desc>
<p>Inserts one or more objects into the table <c><anno>Name</anno></c>.
@@ -527,7 +527,7 @@
</func>
<func>
- <name name="insert_new" arity="2"/>
+ <name name="insert_new" arity="2" since=""/>
<fsummary>Insert one or more objects into a Dets table.</fsummary>
<desc>
<p>Inserts one or more objects into table <c><anno>Name</anno></c>.
@@ -539,7 +539,7 @@
</func>
<func>
- <name name="is_compatible_bchunk_format" arity="2"/>
+ <name name="is_compatible_bchunk_format" arity="2" since=""/>
<fsummary>Test compatibility of chunk data of a table.</fsummary>
<desc>
<p>Returns <c>true</c> if it would be possible to initialize
@@ -554,7 +554,7 @@
</func>
<func>
- <name name="is_dets_file" arity="1"/>
+ <name name="is_dets_file" arity="1" since=""/>
<fsummary>Test for a Dets table.</fsummary>
<desc>
<p>Returns <c>true</c> if file <c><anno>Filename</anno></c>
@@ -563,7 +563,7 @@
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary>Return all objects with a specified key stored in a
Dets table.</fsummary>
<desc>
@@ -590,7 +590,7 @@ ok
</func>
<func>
- <name name="match" arity="1"/>
+ <name name="match" arity="1" since=""/>
<fsummary>Match a chunk of objects stored in a Dets table and
return a list of variable bindings.</fsummary>
<desc>
@@ -606,7 +606,7 @@ ok
</func>
<func>
- <name name="match" arity="2"/>
+ <name name="match" arity="2" since=""/>
<fsummary>Match the objects stored in a Dets table and return a
list of variable bindings.</fsummary>
<desc>
@@ -622,7 +622,7 @@ ok
</func>
<func>
- <name name="match" arity="3"/>
+ <name name="match" arity="3" since=""/>
<fsummary>Match the first chunk of objects stored in a Dets table
and return a list of variable bindings.</fsummary>
<desc>
@@ -654,7 +654,7 @@ ok
</func>
<func>
- <name name="match_delete" arity="2"/>
+ <name name="match_delete" arity="2" since=""/>
<fsummary>Delete all objects that match a given pattern from a
Dets table.</fsummary>
<desc>
@@ -667,7 +667,7 @@ ok
</func>
<func>
- <name name="match_object" arity="1"/>
+ <name name="match_object" arity="1" since=""/>
<fsummary>Match a chunk of objects stored in a Dets table and
return a list of objects.</fsummary>
<desc>
@@ -683,7 +683,7 @@ ok
</func>
<func>
- <name name="match_object" arity="2"/>
+ <name name="match_object" arity="2" since=""/>
<fsummary>Match the objects stored in a Dets table and return
a list of objects.</fsummary>
<desc>
@@ -702,7 +702,7 @@ ok
</func>
<func>
- <name name="match_object" arity="3"/>
+ <name name="match_object" arity="3" since=""/>
<fsummary>Match the first chunk of objects stored in a Dets table
and return a list of objects.</fsummary>
<desc>
@@ -735,7 +735,7 @@ ok
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Test for occurrence of a key in a Dets table.</fsummary>
<desc>
<p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>,
@@ -746,7 +746,7 @@ ok
</func>
<func>
- <name name="next" arity="2"/>
+ <name name="next" arity="2" since=""/>
<fsummary>Return the next key in a Dets table.</fsummary>
<desc>
<p>Returns either the key following <c><anno>Key1</anno></c> in table
@@ -760,7 +760,7 @@ ok
</func>
<func>
- <name name="open_file" arity="1"/>
+ <name name="open_file" arity="1" since=""/>
<fsummary>Open an existing Dets table.</fsummary>
<desc>
<p>Opens an existing table. If the table is not properly closed,
@@ -770,7 +770,7 @@ ok
</func>
<func>
- <name name="open_file" arity="2"/>
+ <name name="open_file" arity="2" since=""/>
<fsummary>Open a Dets table.</fsummary>
<desc>
<p>Opens a table. An empty Dets table is created if no file
@@ -872,7 +872,7 @@ ok
</func>
<func>
- <name name="pid2name" arity="1"/>
+ <name name="pid2name" arity="1" since=""/>
<fsummary>Return the name of the Dets table handled by a pid.</fsummary>
<desc>
<p>Returns the table name given the pid of a process
@@ -883,7 +883,7 @@ ok
</func>
<func>
- <name name="repair_continuation" arity="2"/>
+ <name name="repair_continuation" arity="2" since=""/>
<fsummary>Repair a continuation from <c>select/1</c> or <c>select/3</c>.
</fsummary>
<desc>
@@ -917,7 +917,7 @@ ok
</func>
<func>
- <name name="safe_fixtable" arity="2"/>
+ <name name="safe_fixtable" arity="2" since=""/>
<fsummary>Fix a Dets table for safe traversal.</fsummary>
<desc>
<p>If <c><anno>Fix</anno></c> is <c>true</c>, table
@@ -945,7 +945,7 @@ ok
</func>
<func>
- <name name="select" arity="1"/>
+ <name name="select" arity="1" since=""/>
<fsummary>Apply a match specification to some objects stored in a
Dets table.</fsummary>
<desc>
@@ -962,7 +962,7 @@ ok
</func>
<func>
- <name name="select" arity="2"/>
+ <name name="select" arity="2" since=""/>
<fsummary>Apply a match specification to all objects stored in a
Dets table.</fsummary>
<desc>
@@ -984,7 +984,7 @@ ok
</func>
<func>
- <name name="select" arity="3"/>
+ <name name="select" arity="3" since=""/>
<fsummary>Apply a match specification to the first chunk of objects
stored in a Dets table.</fsummary>
<desc>
@@ -1019,7 +1019,7 @@ ok
</func>
<func>
- <name name="select_delete" arity="2"/>
+ <name name="select_delete" arity="2" since=""/>
<fsummary>Delete all objects that match a given pattern from a
Dets table.</fsummary>
<desc>
@@ -1036,7 +1036,7 @@ ok
</func>
<func>
- <name name="slot" arity="2"/>
+ <name name="slot" arity="2" since=""/>
<fsummary>Return the list of objects associated with a slot of a
Dets table.</fsummary>
<desc>
@@ -1049,7 +1049,7 @@ ok
</func>
<func>
- <name name="sync" arity="1"/>
+ <name name="sync" arity="1" since=""/>
<fsummary>Ensure that all updates made to a Dets table are written
to disk.</fsummary>
<desc>
@@ -1064,8 +1064,8 @@ ok
</func>
<func>
- <name name="table" arity="1"/>
- <name name="table" arity="2"/>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<p>Returns a Query List
@@ -1140,7 +1140,7 @@ true</pre>
</func>
<func>
- <name name="to_ets" arity="2"/>
+ <name name="to_ets" arity="2" since=""/>
<fsummary>Insert all objects of a Dets table into an ETS
table.</fsummary>
<desc>
@@ -1153,7 +1153,7 @@ true</pre>
</func>
<func>
- <name name="traverse" arity="2"/>
+ <name name="traverse" arity="2" since=""/>
<fsummary>Apply a function to all or some objects stored in a Dets
table.</fsummary>
<desc>
@@ -1192,7 +1192,7 @@ fun(X) -> {continue, X} end.</pre>
</func>
<func>
- <name name="update_counter" arity="3"/>
+ <name name="update_counter" arity="3" since=""/>
<fsummary>Update a counter object stored in a Dets table.
</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/dict.xml b/lib/stdlib/doc/src/dict.xml
index c229a18721..95a98cef12 100644
--- a/lib/stdlib/doc/src/dict.xml
+++ b/lib/stdlib/doc/src/dict.xml
@@ -28,7 +28,7 @@
<date>1997-01-15</date>
<rev>B</rev>
</header>
- <module>dict</module>
+ <module since="">dict</module>
<modulesummary>Key-value dictionary.</modulesummary>
<description>
<p>This module provides a <c>Key</c>-<c>Value</c> dictionary.
@@ -55,7 +55,7 @@
<funcs>
<func>
- <name name="append" arity="3"/>
+ <name name="append" arity="3" since=""/>
<fsummary>Append a value to keys in a dictionary.</fsummary>
<desc>
<p>Appends a new <c><anno>Value</anno></c> to the current list
@@ -65,7 +65,7 @@
</func>
<func>
- <name name="append_list" arity="3"/>
+ <name name="append_list" arity="3" since=""/>
<fsummary>Append new values to keys in a dictionary.</fsummary>
<desc>
<p>Appends a list of values <c><anno>ValList</anno></c> to
@@ -77,7 +77,7 @@
</func>
<func>
- <name name="erase" arity="2"/>
+ <name name="erase" arity="2" since=""/>
<fsummary>Erase a key from a dictionary.</fsummary>
<desc>
<p>Erases all items with a given key from a dictionary.</p>
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="fetch" arity="2"/>
+ <name name="fetch" arity="2" since=""/>
<fsummary>Look up values in a dictionary.</fsummary>
<desc>
<p>Returns the value associated with <c><anno>Key</anno></c>
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="fetch_keys" arity="1"/>
+ <name name="fetch_keys" arity="1" since=""/>
<fsummary>Return all keys in a dictionary.</fsummary>
<desc>
<p>Returns a list of all keys in dictionary <c>Dict</c>.</p>
@@ -106,7 +106,7 @@
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
<fsummary>Return value and new dictionary without element with this value.</fsummary>
<desc>
<p>This function returns value from dictionary and a
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Select elements that satisfy a predicate.</fsummary>
<desc>
<p><c><anno>Dict2</anno></c> is a dictionary of all keys and values in
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="find" arity="2"/>
+ <name name="find" arity="2" since=""/>
<fsummary>Search for a key in a dictionary.</fsummary>
<desc>
<p>Searches for a key in dictionary <c>Dict</c>. Returns
@@ -139,7 +139,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
@@ -153,7 +153,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list of pairs to a dictionary.</fsummary>
<desc>
<p>Converts the <c><anno>Key</anno></c>-<c><anno>Value</anno></c> list
@@ -162,7 +162,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 17.0"/>
<fsummary>Return <c>true</c> if the dictionary is empty.</fsummary>
<desc>
<p>Returns <c>true</c> if dictionary <c><anno>Dict</anno></c> has no
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="is_key" arity="2"/>
+ <name name="is_key" arity="2" since=""/>
<fsummary>Test if a key is in a dictionary.</fsummary>
<desc>
<p>Tests if <c><anno>Key</anno></c> is contained in
@@ -180,7 +180,7 @@
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values
@@ -190,7 +190,7 @@
</func>
<func>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge two dictionaries.</fsummary>
<desc>
<p>Merges two dictionaries, <c><anno>Dict1</anno></c> and
@@ -209,7 +209,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create a dictionary.</fsummary>
<desc>
<p>Creates a new dictionary.</p>
@@ -217,7 +217,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a dictionary.</fsummary>
<desc>
<p>Returns the number of elements in dictionary
@@ -226,7 +226,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="store" arity="3"/>
+ <name name="store" arity="3" since=""/>
<fsummary>Store a value in a dictionary.</fsummary>
<desc>
<p>Stores a <c><anno>Key</anno></c>-<c><anno>Value</anno></c> pair in
@@ -237,7 +237,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a dictionary to a list of pairs.</fsummary>
<desc>
<p>Converts dictionary <c>Dict</c> to a list representation.</p>
@@ -245,7 +245,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> on
@@ -255,7 +255,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="4"/>
+ <name name="update" arity="4" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> on
@@ -269,7 +269,7 @@ append(Key, Val, D) ->
</func>
<func>
- <name name="update_counter" arity="3"/>
+ <name name="update_counter" arity="3" since=""/>
<fsummary>Increment a value in a dictionary.</fsummary>
<desc>
<p>Adds <c><anno>Increment</anno></c> to the value associated with
diff --git a/lib/stdlib/doc/src/digraph.xml b/lib/stdlib/doc/src/digraph.xml
index a5252b443b..cf2c0844c9 100644
--- a/lib/stdlib/doc/src/digraph.xml
+++ b/lib/stdlib/doc/src/digraph.xml
@@ -32,7 +32,7 @@
<rev>C</rev>
<file>digraph.xml</file>
</header>
- <module>digraph</module>
+ <module since="">digraph</module>
<modulesummary>Directed graphs.</modulesummary>
<description>
<p>This module provides a version of labeled
@@ -144,9 +144,9 @@
<funcs>
<func>
- <name name="add_edge" arity="3"/>
- <name name="add_edge" arity="4"/>
- <name name="add_edge" arity="5"/>
+ <name name="add_edge" arity="3" since=""/>
+ <name name="add_edge" arity="4" since=""/>
+ <name name="add_edge" arity="5" since=""/>
<fsummary>Add an edge to a digraph.</fsummary>
<type name="add_edge_err_rsn"/>
<desc>
@@ -183,9 +183,9 @@
</func>
<func>
- <name name="add_vertex" arity="1"/>
- <name name="add_vertex" arity="2"/>
- <name name="add_vertex" arity="3"/>
+ <name name="add_vertex" arity="1" since=""/>
+ <name name="add_vertex" arity="2" since=""/>
+ <name name="add_vertex" arity="3" since=""/>
<fsummary>Add or modify a vertex of a digraph.</fsummary>
<desc>
<p><c>add_vertex/3</c> creates (or modifies) vertex
@@ -204,7 +204,7 @@
</func>
<func>
- <name name="del_edge" arity="2"/>
+ <name name="del_edge" arity="2" since=""/>
<fsummary>Delete an edge from a digraph.</fsummary>
<desc>
<p>Deletes edge <c><anno>E</anno></c> from digraph
@@ -213,7 +213,7 @@
</func>
<func>
- <name name="del_edges" arity="2"/>
+ <name name="del_edges" arity="2" since=""/>
<fsummary>Delete edges from a digraph.</fsummary>
<desc>
<p>Deletes the edges in list <c><anno>Edges</anno></c> from digraph
@@ -222,7 +222,7 @@
</func>
<func>
- <name name="del_path" arity="3"/>
+ <name name="del_path" arity="3" since=""/>
<fsummary>Delete paths from a digraph.</fsummary>
<desc>
<p>Deletes edges from digraph <c><anno>G</anno></c> until there are no
@@ -252,7 +252,7 @@
</func>
<func>
- <name name="del_vertex" arity="2"/>
+ <name name="del_vertex" arity="2" since=""/>
<fsummary>Delete a vertex from a digraph.</fsummary>
<desc>
<p>Deletes vertex <c><anno>V</anno></c> from digraph
@@ -265,7 +265,7 @@
</func>
<func>
- <name name="del_vertices" arity="2"/>
+ <name name="del_vertices" arity="2" since=""/>
<fsummary>Delete vertices from a digraph.</fsummary>
<desc>
<p>Deletes the vertices in list <c><anno>Vertices</anno></c> from
@@ -274,7 +274,7 @@
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete a digraph.</fsummary>
<desc>
<p>Deletes digraph <c><anno>G</anno></c>. This call is important
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="edge" arity="2"/>
+ <name name="edge" arity="2" since=""/>
<fsummary>Return the vertices and the label of an edge of a digraph.
</fsummary>
<desc>
@@ -303,7 +303,7 @@
</func>
<func>
- <name name="edges" arity="1"/>
+ <name name="edges" arity="1" since=""/>
<fsummary>Return all edges of a digraph.</fsummary>
<desc>
<p>Returns a list of all edges of digraph <c><anno>G</anno></c>, in
@@ -312,7 +312,7 @@
</func>
<func>
- <name name="edges" arity="2"/>
+ <name name="edges" arity="2" since=""/>
<fsummary>Return the edges emanating from or incident on a vertex of
a digraph.</fsummary>
<desc>
@@ -324,7 +324,7 @@
</func>
<func>
- <name name="get_cycle" arity="2"/>
+ <name name="get_cycle" arity="2" since=""/>
<fsummary>Find one cycle in a digraph.</fsummary>
<desc>
<p>If a <seealso marker="#simple_cycle">simple cycle</seealso> of
@@ -341,7 +341,7 @@
</func>
<func>
- <name name="get_path" arity="3"/>
+ <name name="get_path" arity="3" since=""/>
<fsummary>Find one path in a digraph.</fsummary>
<desc>
<p>Tries to find
@@ -357,7 +357,7 @@
</func>
<func>
- <name name="get_short_cycle" arity="2"/>
+ <name name="get_short_cycle" arity="2" since=""/>
<fsummary>Find one short cycle in a digraph.</fsummary>
<desc>
<p>Tries to find an as short as possible
@@ -375,7 +375,7 @@
</func>
<func>
- <name name="get_short_path" arity="3"/>
+ <name name="get_short_path" arity="3" since=""/>
<fsummary>Find one short path in a digraph.</fsummary>
<desc>
<p>Tries to find an as short as possible
@@ -392,7 +392,7 @@
</func>
<func>
- <name name="in_degree" arity="2"/>
+ <name name="in_degree" arity="2" since=""/>
<fsummary>Return the in-degree of a vertex of a digraph.</fsummary>
<desc>
<p>Returns the <seealso marker="#in_degree">in-degree</seealso> of
@@ -401,7 +401,7 @@
</func>
<func>
- <name name="in_edges" arity="2"/>
+ <name name="in_edges" arity="2" since=""/>
<fsummary>Return all edges incident on a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of all
@@ -412,7 +412,7 @@
</func>
<func>
- <name name="in_neighbours" arity="2"/>
+ <name name="in_neighbours" arity="2" since=""/>
<fsummary>Return all in-neighbors of a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of
@@ -423,7 +423,7 @@
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about a digraph.</fsummary>
<type name="d_cyclicity"/>
<type name="d_protection"/>
@@ -453,7 +453,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return a protected empty digraph, where cycles are allowed.
</fsummary>
<desc>
@@ -462,7 +462,7 @@
</func>
<func>
- <name name="new" arity="1"/>
+ <name name="new" arity="1" since=""/>
<fsummary>Create a new empty digraph.</fsummary>
<type variable="Type"/>
<type name="d_type"/>
@@ -492,7 +492,7 @@
</func>
<func>
- <name name="no_edges" arity="1"/>
+ <name name="no_edges" arity="1" since=""/>
<fsummary>Return the number of edges of a digraph.</fsummary>
<desc>
<p>Returns the number of edges of digraph <c><anno>G</anno></c>.</p>
@@ -500,7 +500,7 @@
</func>
<func>
- <name name="no_vertices" arity="1"/>
+ <name name="no_vertices" arity="1" since=""/>
<fsummary>Return the number of vertices of a digraph.</fsummary>
<desc>
<p>Returns the number of vertices of digraph <c><anno>G</anno></c>.</p>
@@ -508,7 +508,7 @@
</func>
<func>
- <name name="out_degree" arity="2"/>
+ <name name="out_degree" arity="2" since=""/>
<fsummary>Return the out-degree of a vertex of a digraph.</fsummary>
<desc>
<p>Returns the <seealso marker="#out_degree">out-degree</seealso> of
@@ -517,7 +517,7 @@
</func>
<func>
- <name name="out_edges" arity="2"/>
+ <name name="out_edges" arity="2" since=""/>
<fsummary>Return all edges emanating from a vertex of a digraph.
</fsummary>
<desc>
@@ -529,7 +529,7 @@
</func>
<func>
- <name name="out_neighbours" arity="2"/>
+ <name name="out_neighbours" arity="2" since=""/>
<fsummary>Return all out-neighbors of a vertex of a digraph.</fsummary>
<desc>
<p>Returns a list of
@@ -540,7 +540,7 @@
</func>
<func>
- <name name="vertex" arity="2"/>
+ <name name="vertex" arity="2" since=""/>
<fsummary>Return the label of a vertex of a digraph.</fsummary>
<desc>
<p>Returns <c>{<anno>V</anno>,&nbsp;<anno>Label</anno>}</c>,
@@ -553,7 +553,7 @@
</func>
<func>
- <name name="vertices" arity="1"/>
+ <name name="vertices" arity="1" since=""/>
<fsummary>Return all vertices of a digraph.</fsummary>
<desc>
<p>Returns a list of all vertices of digraph <c><anno>G</anno></c>, in
diff --git a/lib/stdlib/doc/src/digraph_utils.xml b/lib/stdlib/doc/src/digraph_utils.xml
index cb316e5b93..13b0aaad9e 100644
--- a/lib/stdlib/doc/src/digraph_utils.xml
+++ b/lib/stdlib/doc/src/digraph_utils.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>digraph_utils.xml</file>
</header>
- <module>digraph_utils</module>
+ <module since="">digraph_utils</module>
<modulesummary>Algorithms for directed graphs.</modulesummary>
<description>
<p>This module provides algorithms based on depth-first traversal of
@@ -154,7 +154,7 @@
<funcs>
<func>
- <name name="arborescence_root" arity="1"/>
+ <name name="arborescence_root" arity="1" since=""/>
<fsummary>Check if a digraph is an arborescence.</fsummary>
<desc>
<p>Returns <c>{yes, <anno>Root</anno>}</c> if <c><anno>Root</anno></c>
@@ -164,7 +164,7 @@
</func>
<func>
- <name name="components" arity="1"/>
+ <name name="components" arity="1" since=""/>
<fsummary>Return the components of a digraph.</fsummary>
<desc>
<p>Returns a list
@@ -177,7 +177,7 @@
</func>
<func>
- <name name="condensation" arity="1"/>
+ <name name="condensation" arity="1" since=""/>
<fsummary>Return a condensed graph of a digraph.</fsummary>
<desc>
<p>Creates a digraph where the vertices are
@@ -202,7 +202,7 @@
</func>
<func>
- <name name="cyclic_strong_components" arity="1"/>
+ <name name="cyclic_strong_components" arity="1" since=""/>
<fsummary>Return the cyclic strong components of a digraph.</fsummary>
<desc>
<p>Returns a list of <seealso marker="#strong_components">strongly
@@ -218,7 +218,7 @@
</func>
<func>
- <name name="is_acyclic" arity="1"/>
+ <name name="is_acyclic" arity="1" since=""/>
<fsummary>Check if a digraph is acyclic.</fsummary>
<desc>
<p>Returns <c>true</c> if and only if digraph
@@ -228,7 +228,7 @@
</func>
<func>
- <name name="is_arborescence" arity="1"/>
+ <name name="is_arborescence" arity="1" since=""/>
<fsummary>Check if a digraph is an arborescence.</fsummary>
<desc>
<p>Returns <c>true</c> if and only if digraph
@@ -238,7 +238,7 @@
</func>
<func>
- <name name="is_tree" arity="1"/>
+ <name name="is_tree" arity="1" since=""/>
<fsummary>Check if a digraph is a tree.</fsummary>
<desc>
<p>Returns <c>true</c> if and only if digraph
@@ -248,7 +248,7 @@
</func>
<func>
- <name name="loop_vertices" arity="1"/>
+ <name name="loop_vertices" arity="1" since=""/>
<fsummary>Return the vertices of a digraph included in some loop.
</fsummary>
<desc>
@@ -258,7 +258,7 @@
</func>
<func>
- <name name="postorder" arity="1"/>
+ <name name="postorder" arity="1" since=""/>
<fsummary>Return the vertices of a digraph in postorder.</fsummary>
<desc>
<p>Returns all vertices of digraph <c><anno>Digraph</anno></c>.
@@ -273,7 +273,7 @@
</func>
<func>
- <name name="preorder" arity="1"/>
+ <name name="preorder" arity="1" since=""/>
<fsummary>Return the vertices of a digraph in preorder.</fsummary>
<desc>
<p>Returns all vertices of digraph <c><anno>Digraph</anno></c>.
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="reachable" arity="2"/>
+ <name name="reachable" arity="2" since=""/>
<fsummary>Return the vertices reachable from some vertices of a digraph.
</fsummary>
<desc>
@@ -300,7 +300,7 @@
</func>
<func>
- <name name="reachable_neighbours" arity="2"/>
+ <name name="reachable_neighbours" arity="2" since=""/>
<fsummary>Return the neighbors reachable from some vertices of a
digraph.</fsummary>
<desc>
@@ -316,7 +316,7 @@
</func>
<func>
- <name name="reaching" arity="2"/>
+ <name name="reaching" arity="2" since=""/>
<fsummary>Return the vertices that reach some vertices of a digraph.
</fsummary>
<desc>
@@ -330,7 +330,7 @@
</func>
<func>
- <name name="reaching_neighbours" arity="2"/>
+ <name name="reaching_neighbours" arity="2" since=""/>
<fsummary>Return the neighbors that reach some vertices of a digraph.
</fsummary>
<desc>
@@ -345,7 +345,7 @@
</func>
<func>
- <name name="strong_components" arity="1"/>
+ <name name="strong_components" arity="1" since=""/>
<fsummary>Return the strong components of a digraph.</fsummary>
<desc>
<p>Returns a list of <seealso marker="#strong_components">strongly
@@ -359,8 +359,8 @@
</func>
<func>
- <name name="subgraph" arity="2"/>
- <name name="subgraph" arity="3"/>
+ <name name="subgraph" arity="2" since=""/>
+ <name name="subgraph" arity="3" since=""/>
<fsummary>Return a subgraph of a digraph.</fsummary>
<desc>
<p>Creates a maximal <seealso marker="#subgraph">subgraph</seealso>
@@ -387,7 +387,7 @@
</func>
<func>
- <name name="topsort" arity="1"/>
+ <name name="topsort" arity="1" since=""/>
<fsummary>Return a topological sorting of the vertices of a digraph.
</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml
index d803d259aa..110c1cea2c 100644
--- a/lib/stdlib/doc/src/epp.xml
+++ b/lib/stdlib/doc/src/epp.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>epp.xml</file>
</header>
- <module>epp</module>
+ <module since="">epp</module>
<modulesummary>An Erlang code preprocessor.</modulesummary>
<description>
<p>The Erlang code preprocessor includes functions that are used by the
@@ -76,7 +76,7 @@
<funcs>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close the preprocessing of the file associated with <c>Epp</c>.
</fsummary>
<desc>
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="default_encoding" arity="0"/>
+ <name name="default_encoding" arity="0" since="OTP R16B"/>
<fsummary>Return the default encoding of Erlang source files.</fsummary>
<desc>
<p>Returns the default encoding of Erlang source files.</p>
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="encoding_to_string" arity="1"/>
+ <name name="encoding_to_string" arity="1" since="OTP R16B"/>
<fsummary>Return a string representation of an encoding.</fsummary>
<desc>
<p>Returns a string representation of an encoding. The string
@@ -107,7 +107,7 @@
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since="OTP R14B03"/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Takes an <c><anno>ErrorDescriptor</anno></c> and returns
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since="OTP 17.0"/>
<fsummary>Open a file for preprocessing.</fsummary>
<desc>
<p>Opens a file for preprocessing.</p>
@@ -136,7 +136,7 @@
</func>
<func>
- <name name="open" arity="2"/>
+ <name name="open" arity="2" since=""/>
<fsummary>Open a file for preprocessing.</fsummary>
<desc>
<p>Equivalent to
@@ -145,7 +145,7 @@
</func>
<func>
- <name name="open" arity="3"/>
+ <name name="open" arity="3" since=""/>
<fsummary>Open a file for preprocessing.</fsummary>
<desc>
<p>Equivalent to <c>epp:open([{name, FileName}, {includes, IncludePath},
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="parse_erl_form" arity="1"/>
+ <name name="parse_erl_form" arity="1" since=""/>
<fsummary>Return the next Erlang form from the opened Erlang source file.
</fsummary>
<type name="warning_info"/>
@@ -167,7 +167,7 @@
</func>
<func>
- <name name="parse_file" arity="2"/>
+ <name name="parse_file" arity="2" since="OTP 17.0"/>
<fsummary>Preprocess and parse an Erlang source file.</fsummary>
<desc>
<p>Preprocesses and parses an Erlang source file.
@@ -185,7 +185,7 @@
</func>
<func>
- <name name="parse_file" arity="3"/>
+ <name name="parse_file" arity="3" since=""/>
<fsummary>Preprocess and parse an Erlang source file.</fsummary>
<desc>
<p>Equivalent to <c>epp:parse_file(FileName, [{includes, IncludePath},
@@ -194,8 +194,8 @@
</func>
<func>
- <name name="read_encoding" arity="1"/>
- <name name="read_encoding" arity="2"/>
+ <name name="read_encoding" arity="1" since="OTP R16B"/>
+ <name name="read_encoding" arity="2" since="OTP R16B"/>
<fsummary>Read the encoding from a file.</fsummary>
<desc>
<p>Read the <seealso marker="#encoding">encoding</seealso> from
@@ -209,8 +209,8 @@
</func>
<func>
- <name name="read_encoding_from_binary" arity="1"/>
- <name name="read_encoding_from_binary" arity="2"/>
+ <name name="read_encoding_from_binary" arity="1" since="OTP R16B"/>
+ <name name="read_encoding_from_binary" arity="2" since="OTP R16B"/>
<fsummary>Read the encoding from a binary.</fsummary>
<desc>
<p>Read the <seealso marker="#encoding">encoding</seealso> from
@@ -224,7 +224,7 @@
</func>
<func>
- <name name="set_encoding" arity="1"/>
+ <name name="set_encoding" arity="1" since="OTP R16B"/>
<fsummary>Read and set the encoding of an I/O device.</fsummary>
<desc>
<p>Reads the <seealso marker="#encoding">encoding</seealso> from
@@ -239,7 +239,7 @@
</func>
<func>
- <name name="set_encoding" arity="2"/>
+ <name name="set_encoding" arity="2" since="OTP 17.0"/>
<fsummary>Read and set the encoding of an I/O device.</fsummary>
<desc>
<p>Reads the <seealso marker="#encoding">encoding</seealso> from
diff --git a/lib/stdlib/doc/src/erl_anno.xml b/lib/stdlib/doc/src/erl_anno.xml
index f316f63d98..dff93619ab 100644
--- a/lib/stdlib/doc/src/erl_anno.xml
+++ b/lib/stdlib/doc/src/erl_anno.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>erl_anno.xml</file>
</header>
- <module>erl_anno</module>
+ <module since="OTP 18.0">erl_anno</module>
<modulesummary>Abstract datatype for the annotations of the Erlang Compiler.
</modulesummary>
@@ -135,7 +135,7 @@
<funcs>
<func>
- <name name="column" arity="1"/>
+ <name name="column" arity="1" since="OTP 18.0"/>
<fsummary>Return the column.</fsummary>
<type name="column"></type>
<desc>
@@ -144,7 +144,7 @@
</func>
<func>
- <name name="end_location" arity="1"/>
+ <name name="end_location" arity="1" since="OTP 18.0"/>
<fsummary>Return the end location of the text.</fsummary>
<type name="location"></type>
<desc>
@@ -155,7 +155,7 @@
</func>
<func>
- <name name="file" arity="1"/>
+ <name name="file" arity="1" since="OTP 18.0"/>
<fsummary>Return the filename.</fsummary>
<type name="filename"></type>
<desc>
@@ -165,7 +165,7 @@
</func>
<func>
- <name name="from_term" arity="1"/>
+ <name name="from_term" arity="1" since="OTP 18.0"/>
<fsummary>Return annotations given a term.</fsummary>
<desc>
<p>Returns annotations with representation <anno>Term</anno>.</p>
@@ -174,7 +174,7 @@
</func>
<func>
- <name name="generated" arity="1"/>
+ <name name="generated" arity="1" since="OTP 18.0"/>
<fsummary>Return the generated Boolean.</fsummary>
<type name="generated"></type>
<desc>
@@ -185,7 +185,7 @@
</func>
<func>
- <name name="is_anno" arity="1"/>
+ <name name="is_anno" arity="1" since="OTP 18.0"/>
<fsummary>Test for a collection of annotations.</fsummary>
<desc>
<p>Returns <c>true</c> if <anno>Term</anno> is a collection of
@@ -194,7 +194,7 @@
</func>
<func>
- <name name="line" arity="1"/>
+ <name name="line" arity="1" since="OTP 18.0"/>
<fsummary>Return the line.</fsummary>
<type name="line"></type>
<desc>
@@ -203,7 +203,7 @@
</func>
<func>
- <name name="location" arity="1"/>
+ <name name="location" arity="1" since="OTP 18.0"/>
<fsummary>Return the location.</fsummary>
<type name="location"></type>
<desc>
@@ -212,7 +212,7 @@
</func>
<func>
- <name name="new" arity="1"/>
+ <name name="new" arity="1" since="OTP 18.0"/>
<fsummary>Create a new collection of annotations.</fsummary>
<type name="location"></type>
<desc>
@@ -221,7 +221,7 @@
</func>
<func>
- <name name="set_file" arity="2"/>
+ <name name="set_file" arity="2" since="OTP 18.0"/>
<fsummary>Modify the filename.</fsummary>
<type name="filename"></type>
<desc>
@@ -230,7 +230,7 @@
</func>
<func>
- <name name="set_generated" arity="2"/>
+ <name name="set_generated" arity="2" since="OTP 18.0"/>
<fsummary>Modify the generated marker.</fsummary>
<type name="generated"></type>
<desc>
@@ -240,7 +240,7 @@
</func>
<func>
- <name name="set_line" arity="2"/>
+ <name name="set_line" arity="2" since="OTP 18.0"/>
<fsummary>Modify the line.</fsummary>
<type name="line"></type>
<desc>
@@ -249,7 +249,7 @@
</func>
<func>
- <name name="set_location" arity="2"/>
+ <name name="set_location" arity="2" since="OTP 18.0"/>
<fsummary>Modify the location.</fsummary>
<type name="location"></type>
<desc>
@@ -258,7 +258,7 @@
</func>
<func>
- <name name="set_record" arity="2"/>
+ <name name="set_record" arity="2" since="OTP 18.0"/>
<fsummary>Modify the record marker.</fsummary>
<type name="record"></type>
<desc>
@@ -267,7 +267,7 @@
</func>
<func>
- <name name="set_text" arity="2"/>
+ <name name="set_text" arity="2" since="OTP 18.0"/>
<fsummary>Modify the text.</fsummary>
<type name="text"></type>
<desc>
@@ -276,7 +276,7 @@
</func>
<func>
- <name name="text" arity="1"/>
+ <name name="text" arity="1" since="OTP 18.0"/>
<fsummary>Return the text.</fsummary>
<type name="text"></type>
<desc>
@@ -286,7 +286,7 @@
</func>
<func>
- <name name="to_term" arity="1"/>
+ <name name="to_term" arity="1" since="OTP 18.0"/>
<fsummary>Return the term representing a collection of annotations.
</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/erl_eval.xml b/lib/stdlib/doc/src/erl_eval.xml
index 1c0f7f062f..813cbecd89 100644
--- a/lib/stdlib/doc/src/erl_eval.xml
+++ b/lib/stdlib/doc/src/erl_eval.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_eval.xml</file>
</header>
- <module>erl_eval</module>
+ <module since="">erl_eval</module>
<modulesummary>The Erlang meta interpreter.</modulesummary>
<description>
<p>This module provides an interpreter for Erlang expressions. The
@@ -96,7 +96,7 @@
<funcs>
<func>
- <name name="add_binding" arity="3"/>
+ <name name="add_binding" arity="3" since=""/>
<fsummary>Add a binding.</fsummary>
<desc>
<p>Adds binding <c><anno>Name</anno>=<anno>Value</anno></c>
@@ -106,7 +106,7 @@
</func>
<func>
- <name name="binding" arity="2"/>
+ <name name="binding" arity="2" since=""/>
<fsummary>Return bindings.</fsummary>
<desc>
<p>Returns the binding of <c><anno>Name</anno></c>
@@ -115,7 +115,7 @@
</func>
<func>
- <name name="bindings" arity="1"/>
+ <name name="bindings" arity="1" since=""/>
<fsummary>Return bindings.</fsummary>
<desc>
<p>Returns the list of bindings contained in the binding
@@ -124,7 +124,7 @@
</func>
<func>
- <name name="del_binding" arity="2"/>
+ <name name="del_binding" arity="2" since=""/>
<fsummary>Delete a binding.</fsummary>
<desc>
<p>Removes the binding of <c><anno>Name</anno></c>
@@ -134,10 +134,10 @@
</func>
<func>
- <name name="expr" arity="2"/>
- <name name="expr" arity="3"/>
- <name name="expr" arity="4"/>
- <name name="expr" arity="5"/>
+ <name name="expr" arity="2" since=""/>
+ <name name="expr" arity="3" since=""/>
+ <name name="expr" arity="4" since=""/>
+ <name name="expr" arity="5" since=""/>
<fsummary>Evaluate expression.</fsummary>
<desc>
<p>Evaluates <c><anno>Expression</anno></c> with the set of bindings
@@ -157,9 +157,9 @@
</func>
<func>
- <name name="expr_list" arity="2"/>
- <name name="expr_list" arity="3"/>
- <name name="expr_list" arity="4"/>
+ <name name="expr_list" arity="2" since=""/>
+ <name name="expr_list" arity="3" since=""/>
+ <name name="expr_list" arity="4" since=""/>
<fsummary>Evaluate a list of expressions.</fsummary>
<desc>
<p>Evaluates a list of expressions in parallel, using the same
@@ -174,9 +174,9 @@
</func>
<func>
- <name name="exprs" arity="2"/>
- <name name="exprs" arity="3"/>
- <name name="exprs" arity="4"/>
+ <name name="exprs" arity="2" since=""/>
+ <name name="exprs" arity="3" since=""/>
+ <name name="exprs" arity="4" since=""/>
<fsummary>Evaluate expressions.</fsummary>
<desc>
<p>Evaluates <c><anno>Expressions</anno></c> with the set of bindings
@@ -197,7 +197,7 @@
</func>
<func>
- <name name="new_bindings" arity="0"/>
+ <name name="new_bindings" arity="0" since=""/>
<fsummary>Return a bindings structure.</fsummary>
<desc>
<p>Returns an empty binding structure.</p>
diff --git a/lib/stdlib/doc/src/erl_expand_records.xml b/lib/stdlib/doc/src/erl_expand_records.xml
index b6aa75ed03..20e5f1960b 100644
--- a/lib/stdlib/doc/src/erl_expand_records.xml
+++ b/lib/stdlib/doc/src/erl_expand_records.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>erl_expand_records.xml</file>
</header>
- <module>erl_expand_records</module>
+ <module since="">erl_expand_records</module>
<modulesummary>Expands records in a module.</modulesummary>
<description>
<p>This module expands records in a module.</p>
@@ -42,7 +42,7 @@
<funcs>
<func>
- <name name="module" arity="2"/>
+ <name name="module" arity="2" since=""/>
<fsummary>Expand all records in a module.</fsummary>
<desc>
<p>Expands all records in a module to use explicit tuple
diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml
index 16952a9582..ec66842ac0 100644
--- a/lib/stdlib/doc/src/erl_id_trans.xml
+++ b/lib/stdlib/doc/src/erl_id_trans.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>erl_id_trans.xml</file>
</header>
- <module>erl_id_trans</module>
+ <module since="">erl_id_trans</module>
<modulesummary>An identity parse transform.</modulesummary>
<description>
<p>This module performs an identity parse transformation of Erlang code.
@@ -46,7 +46,7 @@
<funcs>
<func>
- <name>parse_transform(Forms, Options) -> Forms</name>
+ <name since="">parse_transform(Forms, Options) -> Forms</name>
<fsummary>Transform Erlang forms.</fsummary>
<type>
<v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>
diff --git a/lib/stdlib/doc/src/erl_internal.xml b/lib/stdlib/doc/src/erl_internal.xml
index 17cd0fb240..77551ffed7 100644
--- a/lib/stdlib/doc/src/erl_internal.xml
+++ b/lib/stdlib/doc/src/erl_internal.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>erl_internal.xml</file>
</header>
- <module>erl_internal</module>
+ <module since="">erl_internal</module>
<modulesummary>Internal Erlang definitions.</modulesummary>
<description>
<p>This module defines Erlang BIFs, guard tests, and operators.
@@ -44,7 +44,7 @@
<funcs>
<func>
- <name name="add_predefined_functions" arity="1"/>
+ <name name="add_predefined_functions" arity="1" since="OTP 20.0"/>
<fsummary>Add code for pre-defined functions.</fsummary>
<desc>
<p>Adds to <c><anno>Forms</anno></c> the code for the standard
@@ -54,7 +54,7 @@
</func>
<func>
- <name name="arith_op" arity="2"/>
+ <name name="arith_op" arity="2" since=""/>
<fsummary>Test for an arithmetic operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -63,7 +63,7 @@
</func>
<func>
- <name name="bif" arity="2"/>
+ <name name="bif" arity="2" since=""/>
<fsummary>Test for an Erlang BIF.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c>
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="bool_op" arity="2"/>
+ <name name="bool_op" arity="2" since=""/>
<fsummary>Test for a Boolean operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="comp_op" arity="2"/>
+ <name name="comp_op" arity="2" since=""/>
<fsummary>Test for a comparison operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -91,7 +91,7 @@
</func>
<func>
- <name name="guard_bif" arity="2"/>
+ <name name="guard_bif" arity="2" since=""/>
<fsummary>Test for an Erlang BIF allowed in guards.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> is
@@ -100,7 +100,7 @@
</func>
<func>
- <name name="list_op" arity="2"/>
+ <name name="list_op" arity="2" since=""/>
<fsummary>Test for a list operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -109,7 +109,7 @@
</func>
<func>
- <name name="op_type" arity="2"/>
+ <name name="op_type" arity="2" since=""/>
<fsummary>Return operator type.</fsummary>
<desc>
<p>Returns the <c><anno>Type</anno></c> of operator that
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="send_op" arity="2"/>
+ <name name="send_op" arity="2" since=""/>
<fsummary>Test for a send operator.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c>
@@ -129,7 +129,7 @@
</func>
<func>
- <name name="type_test" arity="2"/>
+ <name name="type_test" arity="2" since=""/>
<fsummary>Test for a valid type test.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> is
diff --git a/lib/stdlib/doc/src/erl_lint.xml b/lib/stdlib/doc/src/erl_lint.xml
index 77cb7a9916..12eaafc3a8 100644
--- a/lib/stdlib/doc/src/erl_lint.xml
+++ b/lib/stdlib/doc/src/erl_lint.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_lint.xml</file>
</header>
- <module>erl_lint</module>
+ <module since="">erl_lint</module>
<modulesummary>The Erlang code linter.</modulesummary>
<description>
<p>This module is used to check Erlang code for illegal syntax and
@@ -78,7 +78,7 @@
<funcs>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Takes an <c><anno>ErrorDescriptor</anno></c> and returns a string
@@ -90,7 +90,7 @@
</func>
<func>
- <name name="is_guard_test" arity="1"/>
+ <name name="is_guard_test" arity="1" since=""/>
<fsummary>Test for a guard test.</fsummary>
<desc>
<p>Tests if <c><anno>Expr</anno></c> is a legal guard test.
@@ -102,9 +102,9 @@
</func>
<func>
- <name name="module" arity="1"/>
- <name name="module" arity="2"/>
- <name name="module" arity="3"/>
+ <name name="module" arity="1" since=""/>
+ <name name="module" arity="2" since=""/>
+ <name name="module" arity="3" since=""/>
<fsummary>Check a module for errors.</fsummary>
<desc>
<p>Checks all the forms in a module for errors. It returns:</p>
diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml
index 647f36883c..8142e5c0aa 100644
--- a/lib/stdlib/doc/src/erl_parse.xml
+++ b/lib/stdlib/doc/src/erl_parse.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_parse.xml</file>
</header>
- <module>erl_parse</module>
+ <module since="">erl_parse</module>
<modulesummary>The Erlang parser.</modulesummary>
<description>
<p>This module is the basic Erlang parser that converts tokens into
@@ -89,7 +89,7 @@
<funcs>
<func>
- <name name="abstract" arity="1"/>
+ <name name="abstract" arity="1" since=""/>
<fsummary>Convert an Erlang term into an abstract form.</fsummary>
<desc>
<p>Converts the Erlang data structure <c><anno>Data</anno></c> into an
@@ -102,7 +102,7 @@
</func>
<func>
- <name name="abstract" arity="2"/>
+ <name name="abstract" arity="2" since="OTP R16B01"/>
<fsummary>Convert an Erlang term into an abstract form.</fsummary>
<type name="encoding_func"/>
<desc>
@@ -124,7 +124,7 @@
</func>
<func>
- <name name="anno_from_term" arity="1"/>
+ <name name="anno_from_term" arity="1" since="OTP 18.0"/>
<fsummary>Return annotations as terms.</fsummary>
<desc>
<p>Assumes that <c><anno>Term</anno></c> is a term with the same
@@ -140,7 +140,7 @@
</func>
<func>
- <name name="anno_to_term" arity="1"/>
+ <name name="anno_to_term" arity="1" since="OTP 18.0"/>
<fsummary>Return the representation of annotations.</fsummary>
<desc>
<p>Returns a term where each collection of annotations
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="fold_anno" arity="3"/>
+ <name name="fold_anno" arity="3" since="OTP 18.0"/>
<fsummary>Fold a function over the annotations of an <c>erl_parse</c> tree.
</fsummary>
<desc>
@@ -171,7 +171,7 @@
</func>
<func>
- <name>format_error(ErrorDescriptor) -> Chars</name>
+ <name since="">format_error(ErrorDescriptor) -> Chars</name>
<fsummary>Format an error descriptor.</fsummary>
<type>
<v>ErrorDescriptor = <seealso
@@ -188,7 +188,7 @@
</func>
<func>
- <name name="map_anno" arity="2"/>
+ <name name="map_anno" arity="2" since="OTP 18.0"/>
<fsummary>Map a function over the annotations of an <c>erl_parse</c> tree.
</fsummary>
<desc>
@@ -201,7 +201,7 @@
</func>
<func>
- <name name="mapfold_anno" arity="3"/>
+ <name name="mapfold_anno" arity="3" since="OTP 18.0"/>
<fsummary>Map and fold a function over the annotations of an
<c>erl_parse</c> tree.</fsummary>
<desc>
@@ -220,7 +220,7 @@
</func>
<func>
- <name name="new_anno" arity="1"/>
+ <name name="new_anno" arity="1" since="OTP 18.0"/>
<fsummary>Create new annotations.</fsummary>
<desc>
<p>Assumes that <c><anno>Term</anno></c> is a term with the same
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="normalise" arity="1"/>
+ <name name="normalise" arity="1" since=""/>
<fsummary>Convert abstract form to an Erlang term.</fsummary>
<desc>
<p>Converts the abstract form <c><anno>AbsTerm</anno></c> of a
@@ -247,7 +247,7 @@
</func>
<func>
- <name name="parse_exprs" arity="1"/>
+ <name name="parse_exprs" arity="1" since=""/>
<fsummary>Parse Erlang expressions.</fsummary>
<desc>
<p>Parses <c><anno>Tokens</anno></c> as if it was a list of expressions.
@@ -267,7 +267,7 @@
</func>
<func>
- <name name="parse_form" arity="1"/>
+ <name name="parse_form" arity="1" since=""/>
<fsummary>Parse an Erlang form.</fsummary>
<desc>
<p>Parses <c><anno>Tokens</anno></c> as if it was a form. Returns one
@@ -287,7 +287,7 @@
</func>
<func>
- <name name="parse_term" arity="1"/>
+ <name name="parse_term" arity="1" since=""/>
<fsummary>Parse an Erlang term.</fsummary>
<desc>
<p>Parses <c><anno>Tokens</anno></c> as if it was a term. Returns
@@ -307,8 +307,8 @@
</func>
<func>
- <name name="tokens" arity="1"/>
- <name name="tokens" arity="2"/>
+ <name name="tokens" arity="1" since=""/>
+ <name name="tokens" arity="2" since=""/>
<fsummary>Generate a list of tokens for an expression.</fsummary>
<desc>
<p>Generates a list of tokens representing the abstract
diff --git a/lib/stdlib/doc/src/erl_pp.xml b/lib/stdlib/doc/src/erl_pp.xml
index 77a7f1e8d1..f1c3aa5a41 100644
--- a/lib/stdlib/doc/src/erl_pp.xml
+++ b/lib/stdlib/doc/src/erl_pp.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>erl_pp.xml</file>
</header>
- <module>erl_pp</module>
+ <module since="">erl_pp</module>
<modulesummary>The Erlang pretty printer.</modulesummary>
<description>
<p>The functions in this module are used to generate
@@ -73,8 +73,8 @@
<funcs>
<func>
- <name name="attribute" arity="1"/>
- <name name="attribute" arity="2"/>
+ <name name="attribute" arity="1" since=""/>
+ <name name="attribute" arity="2" since=""/>
<fsummary>Pretty print an attribute.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
@@ -83,10 +83,10 @@
</func>
<func>
- <name name="expr" arity="1"/>
- <name name="expr" arity="2"/>
- <name name="expr" arity="3"/>
- <name name="expr" arity="4"/>
+ <name name="expr" arity="1" since=""/>
+ <name name="expr" arity="2" since=""/>
+ <name name="expr" arity="3" since=""/>
+ <name name="expr" arity="4" since=""/>
<fsummary>Pretty print one <c>Expression</c>.</fsummary>
<desc>
<p>Prints one expression. It is useful for implementing hooks (see
@@ -96,9 +96,9 @@
</func>
<func>
- <name name="exprs" arity="1"/>
- <name name="exprs" arity="2"/>
- <name name="exprs" arity="3"/>
+ <name name="exprs" arity="1" since=""/>
+ <name name="exprs" arity="2" since=""/>
+ <name name="exprs" arity="3" since=""/>
<fsummary>Pretty print <c>Expressions</c>.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
@@ -108,8 +108,8 @@
</func>
<func>
- <name name="form" arity="1"/>
- <name name="form" arity="2"/>
+ <name name="form" arity="1" since=""/>
+ <name name="form" arity="2" since=""/>
<fsummary>Pretty print a form.</fsummary>
<desc>
<p>Pretty prints a
@@ -120,8 +120,8 @@
</func>
<func>
- <name name="function" arity="1"/>
- <name name="function" arity="2"/>
+ <name name="function" arity="1" since=""/>
+ <name name="function" arity="2" since=""/>
<fsummary>Pretty print a function.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
@@ -130,8 +130,8 @@
</func>
<func>
- <name name="guard" arity="1"/>
- <name name="guard" arity="2"/>
+ <name name="guard" arity="1" since=""/>
+ <name name="guard" arity="2" since=""/>
<fsummary>Pretty print a guard.</fsummary>
<desc>
<p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>,
diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml
index 137ccd3416..38111f73bc 100644
--- a/lib/stdlib/doc/src/erl_scan.xml
+++ b/lib/stdlib/doc/src/erl_scan.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>erl_scan.xml</file>
</header>
- <module>erl_scan</module>
+ <module since="">erl_scan</module>
<modulesummary>The Erlang token scanner.</modulesummary>
<description>
<p>This module contains functions for tokenizing (scanning) characters into
@@ -74,7 +74,7 @@
<funcs>
<func>
- <name name="category" arity="1"/>
+ <name name="category" arity="1" since="OTP 18.0"/>
<fsummary>Return the category.</fsummary>
<desc>
<p>Returns the category of <c><anno>Token</anno></c>.</p>
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="column" arity="1"/>
+ <name name="column" arity="1" since="OTP 18.0"/>
<fsummary>Return the column.</fsummary>
<desc>
<p>Returns the column of <c><anno>Token</anno></c>'s
@@ -91,7 +91,7 @@
</func>
<func>
- <name name="end_location" arity="1"/>
+ <name name="end_location" arity="1" since="OTP 18.0"/>
<fsummary>Return the end location of the text.</fsummary>
<desc>
<p>Returns the end location of the text of
@@ -101,7 +101,7 @@
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Format an error descriptor.</fsummary>
<desc>
<p>Uses an <c><anno>ErrorDescriptor</anno></c> and returns a string
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="line" arity="1"/>
+ <name name="line" arity="1" since="OTP 18.0"/>
<fsummary>Return the line.</fsummary>
<desc>
<p>Returns the line of <c><anno>Token</anno></c>'s collection
@@ -122,7 +122,7 @@
</func>
<func>
- <name name="location" arity="1"/>
+ <name name="location" arity="1" since="OTP 18.0"/>
<fsummary>Return the location.</fsummary>
<desc>
<p>Returns the location of <c><anno>Token</anno></c>'s
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="reserved_word" arity="1"/>
+ <name name="reserved_word" arity="1" since=""/>
<fsummary>Test for a reserved word.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Atom</anno></c> is an
@@ -140,9 +140,9 @@
</func>
<func>
- <name name="string" arity="1"/>
- <name name="string" arity="2"/>
- <name name="string" arity="3"/>
+ <name name="string" arity="1" since=""/>
+ <name name="string" arity="2" since=""/>
+ <name name="string" arity="3" since=""/>
<fsummary>Scan a string and return the Erlang tokens.</fsummary>
<desc>
<p>Takes the list of characters <c><anno>String</anno></c> and tries to
@@ -229,7 +229,7 @@
</func>
<func>
- <name name="symbol" arity="1"/>
+ <name name="symbol" arity="1" since="OTP 18.0"/>
<fsummary>Return the symbol.</fsummary>
<desc>
<p>Returns the symbol of <c><anno>Token</anno></c>.</p>
@@ -237,7 +237,7 @@
</func>
<func>
- <name name="text" arity="1"/>
+ <name name="text" arity="1" since="OTP 18.0"/>
<fsummary>Return the text.</fsummary>
<desc>
<p>Returns the text of <c><anno>Token</anno></c>'s collection
@@ -247,8 +247,8 @@
</func>
<func>
- <name name="tokens" arity="3"/>
- <name name="tokens" arity="4"/>
+ <name name="tokens" arity="3" since=""/>
+ <name name="tokens" arity="4" since=""/>
<fsummary>Re-entrant scanner.</fsummary>
<type name="char_spec"/>
<type name="return_cont"/>
diff --git a/lib/stdlib/doc/src/erl_tar.xml b/lib/stdlib/doc/src/erl_tar.xml
index 68fa071090..ea8173748a 100644
--- a/lib/stdlib/doc/src/erl_tar.xml
+++ b/lib/stdlib/doc/src/erl_tar.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>erl_tar.xml</file>
</header>
- <module>erl_tar</module>
+ <module since="">erl_tar</module>
<modulesummary>Unix 'tar' utility for reading and writing tar archives.
</modulesummary>
<description>
@@ -127,7 +127,7 @@
<funcs>
<func>
- <name>add(TarDescriptor, Filename, Options) -> RetValue</name>
+ <name since="">add(TarDescriptor, Filename, Options) -> RetValue</name>
<fsummary>Add a file to an open tar file.</fsummary>
<type>
<v>TarDescriptor = term()</v>
@@ -211,7 +211,7 @@
</func>
<func>
- <name>add(TarDescriptor, FilenameOrBin, NameInArchive, Options) ->
+ <name since="">add(TarDescriptor, FilenameOrBin, NameInArchive, Options) ->
RetValue </name>
<fsummary>Add a file to an open tar file.</fsummary>
<type>
@@ -233,7 +233,7 @@
</func>
<func>
- <name>close(TarDescriptor)</name>
+ <name since="">close(TarDescriptor)</name>
<fsummary>Close an open tar file.</fsummary>
<type>
<v>TarDescriptor = term()</v>
@@ -245,7 +245,7 @@
</func>
<func>
- <name>create(Name, FileList) ->RetValue </name>
+ <name since="">create(Name, FileList) ->RetValue </name>
<fsummary>Create a tar archive.</fsummary>
<type>
<v>Name = filename()</v>
@@ -264,7 +264,7 @@
</func>
<func>
- <name>create(Name, FileList, OptionList)</name>
+ <name since="">create(Name, FileList, OptionList)</name>
<fsummary>Create a tar archive with options.</fsummary>
<type>
<v>Name = filename()</v>
@@ -315,7 +315,7 @@
</func>
<func>
- <name>extract(Name) -> RetValue</name>
+ <name since="">extract(Name) -> RetValue</name>
<fsummary>Extract all files from a tar file.</fsummary>
<type>
<v>Name = filename() | {binary,binary()} | {file,Fd}</v>
@@ -339,7 +339,7 @@
</func>
<func>
- <name>extract(Name, OptionList)</name>
+ <name since="">extract(Name, OptionList)</name>
<fsummary>Extract files from a tar file.</fsummary>
<type>
<v>Name = filename() | {binary,binary()} | {file,Fd}</v>
@@ -411,7 +411,7 @@
</func>
<func>
- <name>format_error(Reason) -> string()</name>
+ <name since="">format_error(Reason) -> string()</name>
<fsummary>Convert error term to a readable string.</fsummary>
<type>
<v>Reason = term()</v>
@@ -423,7 +423,7 @@
</func>
<func>
- <name>init(UserPrivate, AccessMode, Fun) ->
+ <name since="OTP 17.4">init(UserPrivate, AccessMode, Fun) ->
{ok,TarDescriptor} | {error,Reason}</name>
<fsummary>Create a <c>TarDescriptor</c> used in subsequent tar operations
when defining own low-level storage access functions.</fsummary>
@@ -518,7 +518,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>open(Name, OpenModeList) -> RetValue</name>
+ <name since="">open(Name, OpenModeList) -> RetValue</name>
<fsummary>Open a tar file for writing.</fsummary>
<type>
<v>Name = filename()</v>
@@ -565,7 +565,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>table(Name) -> RetValue</name>
+ <name since="">table(Name) -> RetValue</name>
<fsummary>Retrieve the name of all files in a tar file.</fsummary>
<type>
<v>Name = filename()|{binary,binary()}|{file,file_descriptor()}</v>
@@ -578,7 +578,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>table(Name, Options)</name>
+ <name since="">table(Name, Options)</name>
<fsummary>Retrieve name and information of all files in a tar file.
</fsummary>
<type>
@@ -590,7 +590,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>t(Name)</name>
+ <name since="">t(Name)</name>
<fsummary>Print the name of each file in a tar file.</fsummary>
<type>
<v>Name = filename()|{binary,binary()}|{file,file_descriptor()}</v>
@@ -602,7 +602,7 @@ erl_tar:close(TarDesc)</code>
</func>
<func>
- <name>tt(Name)</name>
+ <name since="">tt(Name)</name>
<fsummary>Print name and information for each file in a tar file.
</fsummary>
<type>
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index 611b176613..e67397c6fd 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>ets</module>
+ <module since="">ets</module>
<modulesummary>Built-in term storage.</modulesummary>
<description>
<p>This module is an interface to the Erlang built-in term storage
@@ -138,23 +138,56 @@
operation. In database terms the isolation level can be seen as
"serializable", as if all isolated operations are carried out serially,
one after the other in a strict order.</p>
+ </section>
- <p>No other support is available within this module that would guarantee
- consistency between objects. However, function
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
- can be used to guarantee that a sequence of
- <seealso marker="#first/1"><c>first/1</c></seealso> and
- <seealso marker="#next/2"><c>next/2</c></seealso> calls traverse the
- table without errors and that each existing object in the table is
- visited exactly once, even if another (or the same) process
- simultaneously deletes or inserts objects into the table.
- Nothing else is guaranteed; in particular objects that are inserted
- or deleted during such a traversal can be visited once or not at all.
- Functions that internally traverse over a table, like
- <seealso marker="#select/1"><c>select</c></seealso> and
- <seealso marker="#match/1"><c>match</c></seealso>,
- give the same guarantee as
- <seealso marker="#safe_fixtable/2"><c>safe_fixtable</c></seealso>.</p>
+ <section><marker id="traversal"></marker>
+ <title>Table traversal</title>
+ <p>There are different ways to traverse through the objects of a table.</p>
+ <list type="bulleted">
+ <item><p><em>Single-step</em> traversal one key at at time, using
+ <seealso marker="#first/1"><c>first/1</c></seealso>,
+ <seealso marker="#next/2"><c>next/2</c></seealso>,
+ <seealso marker="#last/1"><c>last/1</c></seealso> and
+ <seealso marker="#prev/2"><c>prev/2</c></seealso>.</p>
+ </item>
+ <item><p>Search with simple <em>match patterns</em>, using
+ <seealso marker="#match/1"><c>match/1/2/3</c></seealso>,
+ <seealso marker="#match_delete/2"><c>match_delete/2</c></seealso> and
+ <seealso marker="#match_object/1"><c>match_object/1/2/3</c></seealso>.</p>
+ </item>
+ <item><p>Search with more powerful <em>match specifications</em>, using
+ <seealso marker="#select/1"><c>select/1/2/3</c></seealso>,
+ <seealso marker="#select_count/2"><c>select_count/2</c></seealso>,
+ <seealso marker="#select_delete/2"><c>select_delete/2</c></seealso>,
+ <seealso marker="#select_replace/2"><c>select_replace/2</c></seealso> and
+ <seealso marker="#select_reverse/1"><c>select_reverse/1/2/3</c></seealso>.</p>
+ </item>
+ <item><p><em>Table conversions</em>, using
+ <seealso marker="#tab2file/2"><c>tab2file/2/3</c></seealso> and
+ <seealso marker="#tab2list/1"><c>tab2list/1</c></seealso>.</p>
+ </item>
+ </list>
+ <p>None of these ways of table traversal will guarantee a consistent table snapshot
+ if the table is also updated during the traversal. Moreover, traversals not
+ done in a <em>safe</em> way, on tables where keys are inserted or deleted
+ during the traversal, may yield the following undesired effects:</p>
+ <list type="bulleted">
+ <item><p>Any key may be missed.</p></item>
+ <item><p>Any key may be found more than once.</p></item>
+ <item><p>The traversal may fail with <c>badarg</c> exception if keys are deleted.</p>
+ </item>
+ </list>
+ <p>A table traversal is <em>safe</em> if either</p>
+ <list type="bulleted">
+ <item><p>the table is of type <c>ordered_set</c>.</p>
+ </item>
+ <item><p>the entire table traversal is done within one ETS function
+ call.</p>
+ </item>
+ <item><p>function <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ is used to keep the table fixated during the entire traversal.</p>
+ </item>
+ </list>
</section>
<section>
@@ -207,7 +240,7 @@
<funcs>
<func>
- <name name="all" arity="0"/>
+ <name name="all" arity="0" since=""/>
<fsummary>Return a list of all ETS tables.</fsummary>
<desc>
<p>Returns a list of all tables at the node. Named tables are
@@ -222,7 +255,7 @@
</func>
<func>
- <name name="delete" arity="1"/>
+ <name name="delete" arity="1" since=""/>
<fsummary>Delete an entire ETS table.</fsummary>
<desc>
<p>Deletes the entire table <c><anno>Tab</anno></c>.</p>
@@ -230,7 +263,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Delete all objects with a specified key from an ETS
table.</fsummary>
<desc>
@@ -240,7 +273,7 @@
</func>
<func>
- <name name="delete_all_objects" arity="1"/>
+ <name name="delete_all_objects" arity="1" since=""/>
<fsummary>Delete all objects in an ETS table.</fsummary>
<desc>
<p>Delete all objects in the ETS table <c><anno>Tab</anno></c>.
@@ -250,7 +283,7 @@
</func>
<func>
- <name name="delete_object" arity="2"/>
+ <name name="delete_object" arity="2" since=""/>
<fsummary>Deletes a specific from an ETS table.</fsummary>
<desc>
<p>Delete the exact object <c><anno>Object</anno></c> from the
@@ -262,7 +295,7 @@
</func>
<func>
- <name name="file2tab" arity="1"/>
+ <name name="file2tab" arity="1" since=""/>
<fsummary>Read an ETS table from a file.</fsummary>
<desc>
<p>Reads a file produced by <seealso marker="#tab2file/2">
@@ -274,7 +307,7 @@
</func>
<func>
- <name name="file2tab" arity="2"/>
+ <name name="file2tab" arity="2" since=""/>
<fsummary>Read an ETS table from a file.</fsummary>
<desc>
<p>Reads a file produced by <seealso marker="#tab2file/2">
@@ -306,7 +339,7 @@
</func>
<func>
- <name name="first" arity="1"/>
+ <name name="first" arity="1" since=""/>
<fsummary>Return the first key in an ETS table.</fsummary>
<desc>
<p>Returns the first key <c><anno>Key</anno></c> in table
@@ -321,7 +354,7 @@
</func>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Fold a function over an ETS table.</fsummary>
<desc>
<p><c><anno>Acc0</anno></c> is returned if the table is empty.
@@ -337,7 +370,7 @@
</func>
<func>
- <name name="foldr" arity="3"/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over an ETS table.</fsummary>
<desc>
<p><c><anno>Acc0</anno></c> is returned if the table is empty.
@@ -353,7 +386,7 @@
</func>
<func>
- <name name="from_dets" arity="2"/>
+ <name name="from_dets" arity="2" since=""/>
<fsummary>Fill an ETS table with objects from a Dets
table.</fsummary>
<desc>
@@ -367,7 +400,7 @@
</func>
<func>
- <name name="fun2ms" arity="1"/>
+ <name name="fun2ms" arity="1" since=""/>
<fsummary>Pseudo function that transforms fun syntax to a match
specification.</fsummary>
<desc>
@@ -436,7 +469,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="give_away" arity="3"/>
+ <name name="give_away" arity="3" since=""/>
<fsummary>Change owner of a table.</fsummary>
<desc>
<p>Make process <c><anno>Pid</anno></c> the new owner of table
@@ -454,7 +487,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="i" arity="0"/>
+ <name name="i" arity="0" since=""/>
<fsummary>Display information about all ETS tables on a terminal.
</fsummary>
<desc>
@@ -463,7 +496,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="i" arity="1"/>
+ <name name="i" arity="1" since=""/>
<fsummary>Browse an ETS table on a terminal.</fsummary>
<desc>
<p>Browses table <c><anno>Tab</anno></c> on a terminal.</p>
@@ -471,7 +504,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="info" arity="1"/>
+ <name name="info" arity="1" since=""/>
<fsummary>Return information about an <c>table</c>.</fsummary>
<desc>
<p>Returns information about table <c><anno>Tab</anno></c> as a list of
@@ -547,7 +580,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="info" arity="2"/>
+ <name name="info" arity="2" since=""/>
<fsummary>Return the information associated with the specified item for
an ETS table.</fsummary>
<desc>
@@ -618,7 +651,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="init_table" arity="2"/>
+ <name name="init_table" arity="2" since=""/>
<fsummary>Replace all objects of an ETS table.</fsummary>
<desc>
<p>Replaces the existing objects of table <c><anno>Tab</anno></c> with
@@ -648,7 +681,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="insert" arity="2"/>
+ <name name="insert" arity="2" since=""/>
<fsummary>Insert an object into an ETS table.</fsummary>
<desc>
<p>Inserts the object or all of the objects in list
@@ -680,7 +713,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="insert_new" arity="2"/>
+ <name name="insert_new" arity="2" since=""/>
<fsummary>Insert an object into an ETS table if the key is not
already present.</fsummary>
<desc>
@@ -699,7 +732,7 @@ Error: fun containing local Erlang function calls
</func>
<func>
- <name name="is_compiled_ms" arity="1"/>
+ <name name="is_compiled_ms" arity="1" since=""/>
<fsummary>Check if an Erlang term is the result of
<c>match_spec_compile</c>.</fsummary>
<desc>
@@ -731,7 +764,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since=""/>
<fsummary>Return the last key in an ETS table of type
<c>ordered_set</c>.</fsummary>
<desc>
@@ -746,7 +779,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary>Return all objects with a specified key in an ETS table.
</fsummary>
<desc>
@@ -786,7 +819,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="lookup_element" arity="3"/>
+ <name name="lookup_element" arity="3" since=""/>
<fsummary>Return the <c>Pos</c>:th element of all objects with a
specified key in an ETS table.</fsummary>
<desc>
@@ -809,7 +842,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match" arity="1"/>
+ <name name="match" arity="1" since=""/>
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
@@ -823,7 +856,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match" arity="2"/>
+ <name name="match" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a pattern.
</fsummary>
<desc>
@@ -855,7 +888,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match" arity="3"/>
+ <name name="match" arity="3" since=""/>
<fsummary>Match the objects in an ETS table against a pattern
and return part of the answers.</fsummary>
<desc>
@@ -870,11 +903,14 @@ ets:is_compiled_ms(Broken).</code>
<seealso marker="#first/1"><c>first/1</c></seealso> and
<seealso marker="#next/2"><c>next/2</c></seealso>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ to guarantee <seealso marker="#traversal">safe traversal</seealso>
+ for subsequent calls to <seealso marker="#match/1"><c>match/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="match_delete" arity="2"/>
+ <name name="match_delete" arity="2" since=""/>
<fsummary>Delete all objects that match a specified pattern from an
ETS table.</fsummary>
<desc>
@@ -885,7 +921,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_object" arity="1"/>
+ <name name="match_object" arity="1" since=""/>
<fsummary>Continues matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
@@ -900,7 +936,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_object" arity="2"/>
+ <name name="match_object" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a pattern.
</fsummary>
<desc>
@@ -919,7 +955,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_object" arity="3"/>
+ <name name="match_object" arity="3" since=""/>
<fsummary>Match the objects in an ETS table against a pattern and
return part of the answers.</fsummary>
<desc>
@@ -935,11 +971,15 @@ ets:is_compiled_ms(Broken).</code>
<seealso marker="#first/1"><c>first/1</c></seealso> and
<seealso marker="#next/2"><c>next/2</c></seealso>.</p>
<p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ to guarantee <seealso marker="#traversal">safe traversal</seealso>
+ for subsequent calls to <seealso marker="#match_object/1">
+ <c>match_object/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="match_spec_compile" arity="1"/>
+ <name name="match_spec_compile" arity="1" since=""/>
<fsummary>Compile a match specification into its internal representation.
</fsummary>
<desc>
@@ -967,7 +1007,7 @@ ets:is_compiled_ms(Broken).</code>
</func>
<func>
- <name name="match_spec_run" arity="2"/>
+ <name name="match_spec_run" arity="2" since=""/>
<fsummary>Perform matching, using a compiled match specification on a
list of terms.</fsummary>
<desc>
@@ -1004,7 +1044,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Tests for occurrence of a key in an ETS table.</fsummary>
<desc>
<p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>,
@@ -1015,7 +1055,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="new" arity="2"/>
+ <name name="new" arity="2" since=""/>
<fsummary>Create a new ETS table.</fsummary>
<desc>
<p>Creates a new table and returns a table identifier that can
@@ -1185,7 +1225,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="next" arity="2"/>
+ <name name="next" arity="2" since=""/>
<fsummary>Return the next key in an ETS table.</fsummary>
<desc>
<p>Returns the next key <c><anno>Key2</anno></c>, following key
@@ -1197,17 +1237,18 @@ ets:select(Table, MatchSpec),</code>
<p>To find the first key in the table, use
<seealso marker="#first/1"><c>first/1</c></seealso>.</p>
<p>Unless a table of type <c>set</c>, <c>bag</c>, or
- <c>duplicate_bag</c> is protected using
+ <c>duplicate_bag</c> is fixated using
<seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>,
- a traversal can fail if
- concurrent updates are made to the table. For table
- type <c>ordered_set</c>, the function returns the next key in
- order, even if the object does no longer exist.</p>
+ a call to <c>next/2</c> will fail if <c><anno>Key1</anno></c> no longer
+ exists in the table. For table type <c>ordered_set</c>, the function
+ always returns the next key after <c><anno>Key1</anno></c> in term
+ order, regardless whether <c><anno>Key1</anno></c> ever existed in the
+ table.</p>
</desc>
</func>
<func>
- <name name="prev" arity="2"/>
+ <name name="prev" arity="2" since=""/>
<fsummary>Return the previous key in an ETS table of type
<c>ordered_set</c>.</fsummary>
<desc>
@@ -1217,13 +1258,13 @@ ets:select(Table, MatchSpec),</code>
table types, the function is synonymous to
<seealso marker="#next/2"><c>next/2</c></seealso>.
If no previous key exists, <c>'$end_of_table'</c> is returned.</p>
- <p>To find the last key in the table, use
+ <p>To find the last key in an <c>ordered_set</c> table, use
<seealso marker="#last/1"><c>last/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="rename" arity="2"/>
+ <name name="rename" arity="2" since=""/>
<fsummary>Rename a named ETS table.</fsummary>
<desc>
<p>Renames the named table <c><anno>Tab</anno></c> to the new name
@@ -1233,7 +1274,7 @@ ets:select(Table, MatchSpec),</code>
</func>
<func>
- <name name="repair_continuation" arity="2"/>
+ <name name="repair_continuation" arity="2" since=""/>
<fsummary>Repair a continuation from <c>ets:select/1 or ets:select/3</c>
that has passed through external representation.</fsummary>
<desc>
@@ -1288,11 +1329,20 @@ ets:select(ets:repair_continuation(Broken,MS)).</code>
</func>
<func>
- <name name="safe_fixtable" arity="2"/>
+ <name name="safe_fixtable" arity="2" since=""/>
<fsummary>Fix an ETS table for safe traversal.</fsummary>
<desc>
<p>Fixes a table of type <c>set</c>, <c>bag</c>, or
- <c>duplicate_bag</c> for safe traversal.</p>
+ <c>duplicate_bag</c> for <seealso marker="#traversal">
+ safe traversal</seealso> using
+ <seealso marker="#first/1"><c>first/1</c></seealso> &amp;
+ <seealso marker="#next/2"><c>next/2</c></seealso>,
+ <seealso marker="#match/3"><c>match/3</c></seealso> &amp;
+ <seealso marker="#match/1"><c>match/1</c></seealso>,
+ <seealso marker="#match_object/3"><c>match_object/3</c></seealso> &amp;
+ <seealso marker="#match_object/1"><c>match_object/1</c></seealso>, or
+ <seealso marker="#select/3"><c>select/3</c></seealso> &amp;
+ <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
<p>A process fixes a table by calling
<c>safe_fixtable(<anno>Tab</anno>, true)</c>. The table remains
fixed until the process releases it by calling
@@ -1305,11 +1355,11 @@ ets:select(ets:repair_continuation(Broken,MS)).</code>
<p>When a table is fixed, a sequence of
<seealso marker="#first/1"><c>first/1</c></seealso> and
<seealso marker="#next/2"><c>next/2</c></seealso> calls are
- guaranteed to succeed, and each object in
- the table is returned only once, even if objects
- are removed or inserted during the traversal. The keys for new
- objects inserted during the traversal <em>can</em> be returned by
- <c>next/2</c> (it depends on the internal ordering of the keys).</p>
+ guaranteed to succeed even if keys are removed during the
+ traversal. The keys for objects inserted or deleted during a
+ traversal may or may not be returned by <c>next/2</c> depending on
+ the ordering of keys within the table and if the key exists at the time
+ <c>next/2</c> is called.</p>
<p><em>Example:</em></p>
<code type="none">
clean_all_with_value(Tab,X) ->
@@ -1327,7 +1377,7 @@ clean_all_with_value(Tab,X,Key) ->
true
end,
clean_all_with_value(Tab,X,ets:next(Tab,Key)).</code>
- <p>Notice that no deleted objects are removed from a
+ <p>Notice that deleted objects are not freed from a
fixed table until it has been released. If a process fixes a
table but never releases it, the memory used by the deleted
objects is never freed. The performance of operations on
@@ -1337,14 +1387,14 @@ clean_all_with_value(Tab,X,Key) ->
<c>info(Tab, safe_fixed_monotonic_time)</c></seealso>. A system with
many processes fixing tables can need a monitor that sends alarms
when tables have been fixed for too long.</p>
- <p>Notice that for table type <c>ordered_set</c>,
- <c>safe_fixtable/2</c> is not necessary, as calls to
- <c>first/1</c> and <c>next/2</c> always succeed.</p>
+ <p>Notice that <c>safe_fixtable/2</c> is not necessary for table type
+ <c>ordered_set</c> and for traversals done by a single ETS function call,
+ like <seealso marker="#select/2"><c>select/2</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="select" arity="1"/>
+ <name name="select" arity="1" since=""/>
<fsummary>Continue matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with
@@ -1358,7 +1408,7 @@ clean_all_with_value(Tab,X,Key) ->
</func>
<func>
- <name name="select" arity="2"/>
+ <name name="select" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a
match specification.</fsummary>
<desc>
@@ -1453,7 +1503,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</func>
<func>
- <name name="select" arity="3"/>
+ <name name="select" arity="3" since=""/>
<fsummary>Match the objects in an ETS table against a match
specification and return part of the answers.</fsummary>
<desc>
@@ -1467,12 +1517,15 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
table, which is still faster than traversing the table object by
object using <seealso marker="#first/1"><c>first/1</c></seealso>
and <seealso marker="#next/2"><c>next/2</c></seealso>.</p>
- <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p>
+ <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>
+ to guarantee <seealso marker="#traversal">safe traversal</seealso>
+ for subsequent calls to <seealso marker="#select/1"><c>select/1</c></seealso>.</p>
</desc>
</func>
<func>
- <name name="select_count" arity="2"/>
+ <name name="select_count" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a match
specification and return the number of objects for which the match
specification returned <c>true</c>.</fsummary>
@@ -1491,7 +1544,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</func>
<func>
- <name name="select_delete" arity="2"/>
+ <name name="select_delete" arity="2" since=""/>
<fsummary>Match the objects in an ETS table against a match
specification and delete objects where the match specification
returns <c>true</c>.</fsummary>
@@ -1515,7 +1568,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</func>
<func>
- <name name="select_replace" arity="2"/>
+ <name name="select_replace" arity="2" since="OTP 20.0"/>
<fsummary>Match and replace objects atomically in an ETS table</fsummary>
<desc>
<p>Matches the objects in the table <c><anno>Tab</anno></c> using a
@@ -1524,7 +1577,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
the match specification result.</p>
<p>The match-and-replace operation for each individual object is guaranteed to be
<seealso marker="#concurrency">atomic and isolated</seealso>. The
- <c>select_replace</c> table iteration as a whole, like all other select functions,
+ <c>select_replace</c> table traversal as a whole, like all other select functions,
does not give such guarantees.</p>
<p>The match specifiction must be guaranteed to <em>retain the key</em>
of any matched object. If not, <c>select_replace</c> will fail with <c>badarg</c>
@@ -1554,7 +1607,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="select_reverse" arity="1"/>
+ <name name="select_reverse" arity="1" since="OTP R14B"/>
<fsummary>Continue matching objects in an ETS table.</fsummary>
<desc>
<p>Continues a match started with <seealso marker="#select_reverse/3">
@@ -1587,7 +1640,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="select_reverse" arity="2"/>
+ <name name="select_reverse" arity="2" since="OTP R14B"/>
<fsummary>Match the objects in an ETS table against a
match specification.</fsummary>
<desc>
@@ -1599,7 +1652,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="select_reverse" arity="3"/>
+ <name name="select_reverse" arity="3" since="OTP R14B"/>
<fsummary>Match the objects in an ETS table against a
match specification and return part of the answers.</fsummary>
<desc>
@@ -1617,7 +1670,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="2" since=""/>
<fsummary>Set table options.</fsummary>
<desc>
<p>Sets table options. The only allowed option to be set after the
@@ -1628,7 +1681,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="slot" arity="2"/>
+ <name name="slot" arity="2" since=""/>
<fsummary>Return all objects in a specified slot of an ETS table.
</fsummary>
<desc>
@@ -1653,7 +1706,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tab2file" arity="2"/>
+ <name name="tab2file" arity="2" since=""/>
<fsummary>Dump an ETS table to a file.</fsummary>
<desc>
<p>Dumps table <c><anno>Tab</anno></c> to file
@@ -1664,7 +1717,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tab2file" arity="3"/>
+ <name name="tab2file" arity="3" since=""/>
<fsummary>Dump an ETS table to a file.</fsummary>
<desc>
<p>Dumps table <c><anno>Tab</anno></c> to file
@@ -1711,7 +1764,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tab2list" arity="1"/>
+ <name name="tab2list" arity="1" since=""/>
<fsummary>Return a list of all objects in an ETS table.</fsummary>
<desc>
<p>Returns a list of all objects in table <c><anno>Tab</anno></c>.</p>
@@ -1719,7 +1772,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="tabfile_info" arity="1"/>
+ <name name="tabfile_info" arity="1" since=""/>
<fsummary>Return a list of all objects in an ETS table.</fsummary>
<desc>
<p>Returns information about the table dumped to file by
@@ -1797,8 +1850,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])),
</func>
<func>
- <name name="table" arity="1"/>
- <name name="table" arity="2"/>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a QLC query handle.</fsummary>
<desc>
<p>Returns a Query List
@@ -1874,7 +1927,7 @@ true</pre>
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 18.0"/>
<fsummary>Return and remove all objects with a specified key from an
ETS table.</fsummary>
<desc>
@@ -1889,7 +1942,7 @@ true</pre>
</desc>
</func>
<func>
- <name name="test_ms" arity="2"/>
+ <name name="test_ms" arity="2" since=""/>
<fsummary>Test a match specification for use in <c>select/2</c>.
</fsummary>
<desc>
@@ -1916,7 +1969,7 @@ true</pre>
</func>
<func>
- <name name="to_dets" arity="2"/>
+ <name name="to_dets" arity="2" since=""/>
<fsummary>Fill a Dets table with objects from an ETS table.
</fsummary>
<desc>
@@ -1927,12 +1980,12 @@ true</pre>
</func>
<func>
- <name name="update_counter" arity="3" clause_i="1"/>
- <name name="update_counter" arity="4" clause_i="1"/>
- <name name="update_counter" arity="3" clause_i="2"/>
- <name name="update_counter" arity="4" clause_i="2"/>
- <name name="update_counter" arity="3" clause_i="3"/>
- <name name="update_counter" arity="4" clause_i="3"/>
+ <name name="update_counter" arity="3" clause_i="1" since=""/>
+ <name name="update_counter" arity="4" clause_i="1" since="OTP 18.0"/>
+ <name name="update_counter" arity="3" clause_i="2" since=""/>
+ <name name="update_counter" arity="4" clause_i="2" since="OTP 18.0"/>
+ <name name="update_counter" arity="3" clause_i="3" since=""/>
+ <name name="update_counter" arity="4" clause_i="3" since="OTP 18.0"/>
<fsummary>Update a counter object in an ETS table.</fsummary>
<type variable="Tab"/>
<type variable="Key"/>
@@ -2011,8 +2064,8 @@ true</pre>
</func>
<func>
- <name name="update_element" arity="3" clause_i="1"/>
- <name name="update_element" arity="3" clause_i="2"/>
+ <name name="update_element" arity="3" clause_i="1" since=""/>
+ <name name="update_element" arity="3" clause_i="2" since=""/>
<fsummary>Update the <c>Pos</c>:th element of the object with a
specified key in an ETS table.</fsummary>
<type variable="Tab"/>
@@ -2054,7 +2107,7 @@ true</pre>
</func>
<func>
- <name name="whereis" arity="1"/>
+ <name name="whereis" arity="1" since="OTP 21.0"/>
<fsummary>Retrieves the tid() of a named table.</fsummary>
<desc>
<p>This function returns the
diff --git a/lib/stdlib/doc/src/file_sorter.xml b/lib/stdlib/doc/src/file_sorter.xml
index e988d58c2f..942d98fe61 100644
--- a/lib/stdlib/doc/src/file_sorter.xml
+++ b/lib/stdlib/doc/src/file_sorter.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>file_sorter.xml</file>
</header>
- <module>file_sorter</module>
+ <module since="">file_sorter</module>
<modulesummary>File sorter.</modulesummary>
<description>
<p>This module contains functions for sorting terms on files, merging
@@ -334,8 +334,8 @@ output(L) ->
<funcs>
<func>
- <name name="check" arity="1"/>
- <name name="check" arity="2"/>
+ <name name="check" arity="1" since=""/>
+ <name name="check" arity="2" since=""/>
<fsummary>Check whether terms on files are sorted.</fsummary>
<desc>
<p>Checks files for sortedness. If a file is not sorted, the
@@ -347,8 +347,8 @@ output(L) ->
</func>
<func>
- <name name="keycheck" arity="2"/>
- <name name="keycheck" arity="3"/>
+ <name name="keycheck" arity="2" since=""/>
+ <name name="keycheck" arity="3" since=""/>
<fsummary>Check whether terms on files are sorted by key.</fsummary>
<desc>
<p>Checks files for sortedness. If a file is not sorted, the
@@ -360,8 +360,8 @@ output(L) ->
</func>
<func>
- <name name="keymerge" arity="3"/>
- <name name="keymerge" arity="4"/>
+ <name name="keymerge" arity="3" since=""/>
+ <name name="keymerge" arity="4" since=""/>
<fsummary>Merge terms on files by key.</fsummary>
<desc>
<p>Merges tuples on files. Each input file is assumed to be
@@ -372,7 +372,7 @@ output(L) ->
</func>
<func>
- <name name="keysort" arity="2"/>
+ <name name="keysort" arity="2" since=""/>
<fsummary>Sort terms on files by key.</fsummary>
<desc>
<p>Sorts tuples on files.</p>
@@ -382,8 +382,8 @@ output(L) ->
</func>
<func>
- <name name="keysort" arity="3"/>
- <name name="keysort" arity="4"/>
+ <name name="keysort" arity="3" since=""/>
+ <name name="keysort" arity="4" since=""/>
<fsummary>Sort terms on files by key.</fsummary>
<desc>
<p>Sorts tuples on files. The sort is performed on the
@@ -397,8 +397,8 @@ output(L) ->
</func>
<func>
- <name name="merge" arity="2"/>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="2" since=""/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge terms on files.</fsummary>
<desc>
<p>Merges terms on files. Each input file is assumed to be
@@ -409,7 +409,7 @@ output(L) ->
</func>
<func>
- <name name="sort" arity="1"/>
+ <name name="sort" arity="1" since=""/>
<fsummary>Sort terms on files.</fsummary>
<desc>
<p>Sorts terms on files.</p>
@@ -419,8 +419,8 @@ output(L) ->
</func>
<func>
- <name name="sort" arity="2"/>
- <name name="sort" arity="3"/>
+ <name name="sort" arity="2" since=""/>
+ <name name="sort" arity="3" since=""/>
<fsummary>Sort terms on files.</fsummary>
<desc>
<p>Sorts terms on files.</p>
diff --git a/lib/stdlib/doc/src/filelib.xml b/lib/stdlib/doc/src/filelib.xml
index 3b5be75bc0..5df415834f 100644
--- a/lib/stdlib/doc/src/filelib.xml
+++ b/lib/stdlib/doc/src/filelib.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>filelib.xml</file>
</header>
- <module>filelib</module>
+ <module since="">filelib</module>
<modulesummary>File utilities, such as wildcard matching of filenames.
</modulesummary>
<description>
@@ -94,7 +94,7 @@
<funcs>
<func>
- <name name="ensure_dir" arity="1"/>
+ <name name="ensure_dir" arity="1" since=""/>
<fsummary>Ensure that all parent directories for a file or directory
exist.</fsummary>
<desc>
@@ -108,7 +108,7 @@
</func>
<func>
- <name name="file_size" arity="1"/>
+ <name name="file_size" arity="1" since=""/>
<fsummary>Return the size in bytes of a file.</fsummary>
<desc>
<p>Returns the size of the specified file.</p>
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="fold_files" arity="5"/>
+ <name name="fold_files" arity="5" since=""/>
<fsummary>Fold over all files matching a regular expression.</fsummary>
<desc>
<p>Folds function <c><anno>Fun</anno></c> over all (regular) files
@@ -142,7 +142,7 @@
</func>
<func>
- <name name="is_dir" arity="1"/>
+ <name name="is_dir" arity="1" since=""/>
<fsummary>Test whether <c>Name</c> refers to a directory.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno></c>
@@ -151,7 +151,7 @@
</func>
<func>
- <name name="is_file" arity="1"/>
+ <name name="is_file" arity="1" since=""/>
<fsummary>Test whether <c>Name</c> refers to a file or directory.
</fsummary>
<desc>
@@ -161,7 +161,7 @@
</func>
<func>
- <name name="is_regular" arity="1"/>
+ <name name="is_regular" arity="1" since=""/>
<fsummary>Test whether <c>Name</c> refers to a (regular) file.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Name</anno></c>
@@ -170,7 +170,7 @@
</func>
<func>
- <name name="last_modified" arity="1"/>
+ <name name="last_modified" arity="1" since=""/>
<fsummary>Return the local date and time when a file was last modified.
</fsummary>
<desc>
@@ -180,7 +180,7 @@
</func>
<func>
- <name name="wildcard" arity="1"/>
+ <name name="wildcard" arity="1" since=""/>
<fsummary>Match filenames using Unix-style wildcards.</fsummary>
<desc>
<p>Returns a list of all files that match Unix-style wildcard string
@@ -252,7 +252,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</func>
<func>
- <name name="wildcard" arity="2"/>
+ <name name="wildcard" arity="2" since=""/>
<fsummary>Match filenames using Unix-style wildcards starting at a
specified directory.</fsummary>
<desc>
@@ -263,8 +263,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</func>
<func>
- <name name="find_file" arity="2"/>
- <name name="find_file" arity="3"/>
+ <name name="find_file" arity="2" since="OTP 20.0"/>
+ <name name="find_file" arity="3" since="OTP 20.0"/>
<fsummary>Find a file relative to a given directory.</fsummary>
<desc>
<p>Looks for a file of the given name by applying suffix rules to
@@ -278,7 +278,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</desc>
</func>
<func>
- <name name="find_source" arity="1"/>
+ <name name="find_source" arity="1" since="OTP 20.0"/>
<fsummary>Find the source file for a given object file.</fsummary>
<desc>
<p>Equivalent to <c>find_source(Base, Dir)</c>, where <c>Dir</c> is
@@ -287,8 +287,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code>
</desc>
</func>
<func>
- <name name="find_source" arity="2"/>
- <name name="find_source" arity="3"/>
+ <name name="find_source" arity="2" since="OTP 20.0"/>
+ <name name="find_source" arity="3" since="OTP 20.0"/>
<fsummary>Find a source file relative to a given directory.</fsummary>
<desc>
<p>Applies file extension specific rules to find the source file for
diff --git a/lib/stdlib/doc/src/filename.xml b/lib/stdlib/doc/src/filename.xml
index 36254c2d00..ae42846c6b 100644
--- a/lib/stdlib/doc/src/filename.xml
+++ b/lib/stdlib/doc/src/filename.xml
@@ -28,7 +28,7 @@
<date>1997-11-13</date>
<rev>B</rev>
</header>
- <module>filename</module>
+ <module since="">filename</module>
<modulesummary>Filename manipulation functions.</modulesummary>
<description>
<p>This module provides functions
@@ -87,7 +87,7 @@
<funcs>
<func>
- <name name="absname" arity="1"/>
+ <name name="absname" arity="1" since=""/>
<fsummary>Convert a filename to an absolute name, relative the working
directory.</fsummary>
<desc>
@@ -119,7 +119,7 @@
</func>
<func>
- <name name="absname" arity="2"/>
+ <name name="absname" arity="2" since=""/>
<fsummary>Convert a filename to an absolute name, relative a specified
directory.</fsummary>
<desc>
@@ -130,7 +130,7 @@
</func>
<func>
- <name name="absname_join" arity="2"/>
+ <name name="absname_join" arity="2" since=""/>
<fsummary>Join an absolute directory with a relative filename.</fsummary>
<desc>
<p>Joins an absolute directory with a relative filename. Similar to
@@ -144,8 +144,8 @@
</func>
<func>
- <name name="basedir" arity="2" clause_i="1"/>
- <name name="basedir" arity="2" clause_i="2"/>
+ <name name="basedir" arity="2" clause_i="1" since="OTP 19.0"/>
+ <name name="basedir" arity="2" clause_i="2" since="OTP 19.0"/>
<fsummary>Equivalent to <c>basedir(<anno>PathType</anno>,
<anno>Application</anno>,#{})</c> or
<c>basedir(<anno>PathsType</anno>, <anno>Application</anno>,#{})</c>.
@@ -165,8 +165,8 @@ basedir(<anno>PathsType</anno>, <anno>Application</anno>, #{})</seealso>.
</desc>
</func>
<func>
- <name name="basedir" arity="3" clause_i="1" anchor="basedir_3_1"/>
- <name name="basedir" arity="3" clause_i="2" anchor="basedir_3_2"/>
+ <name name="basedir" arity="3" clause_i="1" anchor="basedir_3_1" since="OTP 19.0"/>
+ <name name="basedir" arity="3" clause_i="2" anchor="basedir_3_2" since="OTP 19.0"/>
<fsummary></fsummary>
<type variable="PathType" name_i="1"/>
<type name="basedir_path_type"/>
@@ -314,7 +314,7 @@ true
</desc>
</func>
<func>
- <name name="basename" arity="1"/>
+ <name name="basename" arity="1" since=""/>
<fsummary>Return the last component of a filename.</fsummary>
<desc>
<p>Returns the last component of <c><anno>Filename</anno></c>, or
@@ -332,7 +332,7 @@ true
</func>
<func>
- <name name="basename" arity="2"/>
+ <name name="basename" arity="2" since=""/>
<fsummary>Return the last component of a filename, stripped of the
specified extension.</fsummary>
<desc>
@@ -357,7 +357,7 @@ true
</func>
<func>
- <name name="dirname" arity="1"/>
+ <name name="dirname" arity="1" since=""/>
<fsummary>Return the directory part of a path name.</fsummary>
<desc>
<p>Returns the directory part of <c><anno>Filename</anno></c>.</p>
@@ -374,7 +374,7 @@ true
</func>
<func>
- <name name="extension" arity="1"/>
+ <name name="extension" arity="1" since=""/>
<fsummary>Return the file extension.</fsummary>
<desc>
<p>Returns the file extension of <c><anno>Filename</anno></c>,
@@ -390,8 +390,8 @@ true
</func>
<func>
- <name name="find_src" arity="1"/>
- <name name="find_src" arity="2"/>
+ <name name="find_src" arity="1" since=""/>
+ <name name="find_src" arity="2" since=""/>
<fsummary>Find the filename and compiler options for a module.</fsummary>
<desc>
<p>Finds the source filename and compiler options for a module.
@@ -438,7 +438,7 @@ true
</func>
<func>
- <name name="flatten" arity="1"/>
+ <name name="flatten" arity="1" since=""/>
<fsummary>Convert a filename to a flat string.</fsummary>
<desc>
<p>Converts a possibly deep list filename consisting of
@@ -448,7 +448,7 @@ true
</func>
<func>
- <name name="join" arity="1"/>
+ <name name="join" arity="1" since=""/>
<fsummary>Join a list of filename components with directory separators.
</fsummary>
<desc>
@@ -476,7 +476,7 @@ true
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join two filename components with directory separators.
</fsummary>
<desc>
@@ -487,7 +487,7 @@ true
</func>
<func>
- <name name="nativename" arity="1"/>
+ <name name="nativename" arity="1" since=""/>
<fsummary>Return the native form of a file path.</fsummary>
<desc>
<p>Converts <c><anno>Path</anno></c> to a form accepted by the command
@@ -506,7 +506,7 @@ true
</func>
<func>
- <name name="pathtype" arity="1"/>
+ <name name="pathtype" arity="1" since=""/>
<fsummary>Return the path type.</fsummary>
<desc>
<p>Returns the path type, which is one of the following:</p>
@@ -536,8 +536,8 @@ true
</func>
<func>
- <name name="rootname" arity="1"/>
- <name name="rootname" arity="2"/>
+ <name name="rootname" arity="1" since=""/>
+ <name name="rootname" arity="2" since=""/>
<fsummary>Remove a filename extension.</fsummary>
<desc>
<p>Removes a filename extension. <c>rootname/2</c> works as
@@ -557,7 +557,7 @@ true
</func>
<func>
- <name name="safe_relative_path" arity="1"/>
+ <name name="safe_relative_path" arity="1" since="OTP 19.3"/>
<fsummary>Sanitize a relative path to avoid directory traversal attacks.</fsummary>
<desc>
<p>Sanitizes the relative path by eliminating ".." and "."
@@ -584,7 +584,7 @@ unsafe</pre>
</func>
<func>
- <name name="split" arity="1"/>
+ <name name="split" arity="1" since=""/>
<fsummary>Split a filename into its path components.</fsummary>
<desc>
<p>Returns a list whose elements are the path components of
diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml
index 03397b4503..a9596c6e4d 100644
--- a/lib/stdlib/doc/src/gb_sets.xml
+++ b/lib/stdlib/doc/src/gb_sets.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gb_sets</module>
+ <module since="">gb_sets</module>
<modulesummary>General balanced trees.</modulesummary>
<description>
<p>This module provides ordered sets using Prof. Arne Andersson's
@@ -123,8 +123,8 @@
<funcs>
<func>
- <name name="add" arity="2"/>
- <name name="add_element" arity="2"/>
+ <name name="add" arity="2" since=""/>
+ <name name="add_element" arity="2" since=""/>
<fsummary>Add a (possibly existing) element to a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -135,7 +135,7 @@
</func>
<func>
- <name name="balance" arity="1"/>
+ <name name="balance" arity="1" since=""/>
<fsummary>Rebalance tree representation of a set.</fsummary>
<desc>
<p>Rebalances the tree representation of <c><anno>Set1</anno></c>.
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="del_element" arity="2"/>
+ <name name="del_element" arity="2" since=""/>
<fsummary>Remove a (possibly non-existing) element from a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -160,7 +160,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Remove an element from a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="delete_any" arity="2"/>
+ <name name="delete_any" arity="2" since=""/>
<fsummary>Remove a (possibly non-existing) element from a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="difference" arity="2"/>
+ <name name="difference" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Set1</anno></c> that are not
@@ -191,7 +191,7 @@
</func>
<func>
- <name name="empty" arity="0"/>
+ <name name="empty" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty set.</p>
@@ -199,7 +199,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter set elements.</fsummary>
<desc>
<p>Filters elements in <c><anno>Set1</anno></c> using predicate function
@@ -208,7 +208,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold over set elements.</fsummary>
<desc>
<p>Folds <c><anno>Function</anno></c> over every element in
@@ -218,7 +218,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list into a set.</fsummary>
<desc>
<p>Returns a set of the elements in <c><anno>List</anno></c>, where
@@ -227,7 +227,7 @@
</func>
<func>
- <name name="from_ordset" arity="1"/>
+ <name name="from_ordset" arity="1" since=""/>
<fsummary>Make a set from an ordset list.</fsummary>
<desc>
<p>Turns an ordered-set list <c><anno>List</anno></c> into a set.
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="insert" arity="2"/>
+ <name name="insert" arity="2" since=""/>
<fsummary>Add a new element to a set.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -247,7 +247,7 @@
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a list of sets.</fsummary>
<desc>
<p>Returns the intersection of the non-empty list of sets.</p>
@@ -255,7 +255,7 @@
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two sets.</fsummary>
<desc>
<p>Returns the intersection of <c><anno>Set1</anno></c> and
@@ -264,7 +264,7 @@
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Check whether two sets are disjoint.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> and
@@ -274,7 +274,7 @@
</func>
<func>
- <name name="is_element" arity="2"/>
+ <name name="is_element" arity="2" since=""/>
<fsummary>Test for membership of a set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -283,7 +283,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since=""/>
<fsummary>Test for empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set,
@@ -292,7 +292,7 @@
</func>
<func>
- <name name="is_member" arity="2"/>
+ <name name="is_member" arity="2" since=""/>
<fsummary>Test for membership of a set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -301,7 +301,7 @@
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for a set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> appears to be a set,
@@ -310,7 +310,7 @@
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test for subset.</fsummary>
<desc>
<p>Returns <c>true</c> when every element of <c><anno>Set1</anno></c> is
@@ -319,7 +319,7 @@
</func>
<func>
- <name name="iterator" arity="1"/>
+ <name name="iterator" arity="1" since=""/>
<fsummary>Return an iterator for a set.</fsummary>
<desc>
<p>Returns an iterator that can be used for traversing the entries of
@@ -336,7 +336,7 @@
</func>
<func>
- <name name="iterator_from" arity="2"/>
+ <name name="iterator_from" arity="2" since="OTP 18.0"/>
<fsummary>Return an iterator for a set starting from a specified element.
</fsummary>
<desc>
@@ -351,7 +351,7 @@
</func>
<func>
- <name name="largest" arity="1"/>
+ <name name="largest" arity="1" since=""/>
<fsummary>Return largest element.</fsummary>
<desc>
<p>Returns the largest element in <c><anno>Set</anno></c>. Assumes that
@@ -360,7 +360,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty set.</p>
@@ -368,7 +368,7 @@
</func>
<func>
- <name name="next" arity="1"/>
+ <name name="next" arity="1" since=""/>
<fsummary>Traverse a set with an iterator.</fsummary>
<desc>
<p>Returns <c>{<anno>Element</anno>, <anno>Iter2</anno>}</c>, where
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="singleton" arity="1"/>
+ <name name="singleton" arity="1" since=""/>
<fsummary>Return a set with one element.</fsummary>
<desc>
<p>Returns a set containing only element <c><anno>Element</anno></c>.
@@ -390,7 +390,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a set.</fsummary>
<desc>
<p>Returns the number of elements in <c><anno>Set</anno></c>.</p>
@@ -398,7 +398,7 @@
</func>
<func>
- <name name="smallest" arity="1"/>
+ <name name="smallest" arity="1" since=""/>
<fsummary>Return smallest element.</fsummary>
<desc>
<p>Returns the smallest element in <c><anno>Set</anno></c>. Assumes that
@@ -407,7 +407,7 @@
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Set1</anno></c> that are not
@@ -416,7 +416,7 @@
</func>
<func>
- <name name="take_largest" arity="1"/>
+ <name name="take_largest" arity="1" since=""/>
<fsummary>Extract largest element.</fsummary>
<desc>
<p>Returns <c>{<anno>Element</anno>, <anno>Set2</anno>}</c>, where
@@ -428,7 +428,7 @@
</func>
<func>
- <name name="take_smallest" arity="1"/>
+ <name name="take_smallest" arity="1" since=""/>
<fsummary>Extract smallest element.</fsummary>
<desc>
<p>Returns <c>{<anno>Element</anno>, <anno>Set2</anno>}</c>, where
@@ -440,7 +440,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a set into a list.</fsummary>
<desc>
<p>Returns the elements of <c><anno>Set</anno></c> as a list.</p>
@@ -448,7 +448,7 @@
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a list of sets.</fsummary>
<desc>
<p>Returns the merged (union) set of the list of sets.</p>
@@ -456,7 +456,7 @@
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two sets.</fsummary>
<desc>
<p>Returns the merged (union) set of <c><anno>Set1</anno></c> and
diff --git a/lib/stdlib/doc/src/gb_trees.xml b/lib/stdlib/doc/src/gb_trees.xml
index 5cfff021c1..570c9c7cb6 100644
--- a/lib/stdlib/doc/src/gb_trees.xml
+++ b/lib/stdlib/doc/src/gb_trees.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gb_trees</module>
+ <module since="">gb_trees</module>
<modulesummary>General balanced trees.</modulesummary>
<description>
<p>This module provides Prof. Arne Andersson's General
@@ -75,7 +75,7 @@
<funcs>
<func>
- <name name="balance" arity="1"/>
+ <name name="balance" arity="1" since=""/>
<fsummary>Rebalance a tree.</fsummary>
<desc>
<p>Rebalances <c><anno>Tree1</anno></c>. Notice that this is
@@ -88,7 +88,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Remove a node from a tree.</fsummary>
<desc>
<p>Removes the node with key <c><anno>Key</anno></c> from
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="delete_any" arity="2"/>
+ <name name="delete_any" arity="2" since=""/>
<fsummary>Remove a (possibly non-existing) node from a tree.</fsummary>
<desc>
<p>Removes the node with key <c><anno>Key</anno></c> from
@@ -109,7 +109,7 @@
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
<fsummary>Returns a value and new tree without node with key <c>Key</c>.</fsummary>
<desc>
<p>Returns a value <c><anno>Value</anno></c> from node with key <c><anno>Key</anno></c>
@@ -120,7 +120,7 @@
</func>
<func>
- <name name="take_any" arity="2"/>
+ <name name="take_any" arity="2" since="OTP 20.0"/>
<fsummary>Returns a value and new tree without node with key <c>Key</c>.</fsummary>
<desc>
<p>Returns a value <c><anno>Value</anno></c> from node with key <c><anno>Key</anno></c>
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="empty" arity="0"/>
+ <name name="empty" arity="0" since=""/>
<fsummary>Return an empty tree.</fsummary>
<desc>
<p>Returns a new empty tree.</p>
@@ -139,7 +139,7 @@
</func>
<func>
- <name name="enter" arity="3"/>
+ <name name="enter" arity="3" since=""/>
<fsummary>Insert or update key with value in a tree.</fsummary>
<desc>
<p>Inserts <c><anno>Key</anno></c> with value <c><anno>Value</anno></c>
@@ -151,7 +151,7 @@
</func>
<func>
- <name name="from_orddict" arity="1"/>
+ <name name="from_orddict" arity="1" since=""/>
<fsummary>Make a tree from an orddict.</fsummary>
<desc>
<p>Turns an ordered list <c><anno>List</anno></c> of key-value tuples
@@ -160,7 +160,7 @@
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since=""/>
<fsummary>Look up a key in a tree, if present.</fsummary>
<desc>
<p>Retrieves the value stored with <c><anno>Key</anno></c> in
@@ -171,7 +171,7 @@
</func>
<func>
- <name name="insert" arity="3"/>
+ <name name="insert" arity="3" since=""/>
<fsummary>Insert a new key and value in a tree.</fsummary>
<desc>
<p>Inserts <c><anno>Key</anno></c> with value <c><anno>Value</anno></c>
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="is_defined" arity="2"/>
+ <name name="is_defined" arity="2" since=""/>
<fsummary>Test for membership of a tree.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Key</anno></c> is present in
@@ -191,7 +191,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since=""/>
<fsummary>Test for empty tree.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Tree</anno></c> is an empty tree,
@@ -200,7 +200,7 @@
</func>
<func>
- <name name="iterator" arity="1"/>
+ <name name="iterator" arity="1" since=""/>
<fsummary>Return an iterator for a tree.</fsummary>
<desc>
<p>Returns an iterator that can be used for traversing the
@@ -218,7 +218,7 @@
</func>
<func>
- <name name="iterator_from" arity="2"/>
+ <name name="iterator_from" arity="2" since="OTP 18.0"/>
<fsummary>Return an iterator for a tree starting from a specified key.
</fsummary>
<desc>
@@ -233,7 +233,7 @@
</func>
<func>
- <name name="keys" arity="1"/>
+ <name name="keys" arity="1" since=""/>
<fsummary>Return a list of the keys in a tree.</fsummary>
<desc>
<p>Returns the keys in <c><anno>Tree</anno></c> as an ordered list.</p>
@@ -241,7 +241,7 @@
</func>
<func>
- <name name="largest" arity="1"/>
+ <name name="largest" arity="1" since=""/>
<fsummary>Return largest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>}</c>, where
@@ -253,7 +253,7 @@
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary>Look up a key in a tree.</fsummary>
<desc>
<p>Looks up <c><anno>Key</anno></c> in <c><anno>Tree</anno></c>.
@@ -263,7 +263,7 @@
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Return largest key and value.</fsummary>
<desc>
<p>Maps function F(<anno>K</anno>, <anno>V1</anno>) -> <anno>V2</anno>
@@ -275,7 +275,7 @@
</func>
<func>
- <name name="next" arity="1"/>
+ <name name="next" arity="1" since=""/>
<fsummary>Traverse a tree with an iterator.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>,
@@ -288,7 +288,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of nodes in a tree.</fsummary>
<desc>
<p>Returns the number of nodes in <c><anno>Tree</anno></c>.</p>
@@ -296,7 +296,7 @@
</func>
<func>
- <name name="smallest" arity="1"/>
+ <name name="smallest" arity="1" since=""/>
<fsummary>Return smallest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>}</c>, where
@@ -308,7 +308,7 @@
</func>
<func>
- <name name="take_largest" arity="1"/>
+ <name name="take_largest" arity="1" since=""/>
<fsummary>Extract largest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>,
@@ -321,7 +321,7 @@
</func>
<func>
- <name name="take_smallest" arity="1"/>
+ <name name="take_smallest" arity="1" since=""/>
<fsummary>Extract smallest key and value.</fsummary>
<desc>
<p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>,
@@ -334,7 +334,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a tree into a list.</fsummary>
<desc>
<p>Converts a tree into an ordered list of key-value tuples.</p>
@@ -342,7 +342,7 @@
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since=""/>
<fsummary>Update a key to new value in a tree.</fsummary>
<desc>
<p>Updates <c><anno>Key</anno></c> to value <c><anno>Value</anno></c>
@@ -352,7 +352,7 @@
</func>
<func>
- <name name="values" arity="1"/>
+ <name name="values" arity="1" since=""/>
<fsummary>Return a list of the values in a tree.</fsummary>
<desc>
<p>Returns the values in <c><anno>Tree</anno></c> as an ordered list,
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
index fc34e51216..2915c4f507 100644
--- a/lib/stdlib/doc/src/gen_event.xml
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gen_event</module>
+ <module since="">gen_event</module>
<modulesummary>Generic event handling behavior.</modulesummary>
<description>
<p>This behavior module provides event handling functionality. It
@@ -130,7 +130,7 @@ gen_event:stop -----> Module:terminate/2
<funcs>
<func>
- <name>add_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <name since="">add_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Add an event handler to a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -178,7 +178,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>add_sup_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <name since="">add_sup_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Add a supervised event handler to a generic event manager.
</fsummary>
<type>
@@ -241,8 +241,8 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>call(EventMgrRef, Handler, Request) -> Result</name>
- <name>call(EventMgrRef, Handler, Request, Timeout) -> Result</name>
+ <name since="">call(EventMgrRef, Handler, Request) -> Result</name>
+ <name since="">call(EventMgrRef, Handler, Request, Timeout) -> Result</name>
<fsummary>Make a synchronous call to a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -285,7 +285,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>delete_handler(EventMgrRef, Handler, Args) -> Result</name>
+ <name since="">delete_handler(EventMgrRef, Handler, Args) -> Result</name>
<fsummary>Delete an event handler from a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -318,8 +318,8 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>notify(EventMgrRef, Event) -> ok</name>
- <name>sync_notify(EventMgrRef, Event) -> ok</name>
+ <name since="">notify(EventMgrRef, Event) -> ok</name>
+ <name since="">sync_notify(EventMgrRef, Event) -> ok</name>
<fsummary>Notify an event manager about an event.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -349,9 +349,9 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>start() -> Result</name>
- <name>start(EventMgrName | Options) -> Result</name>
- <name>start(EventMgrName, Options) -> Result</name>
+ <name since="">start() -> Result</name>
+ <name since="">start(EventMgrName | Options) -> Result</name>
+ <name since="OTP 20.0">start(EventMgrName, Options) -> Result</name>
<fsummary>Create a stand-alone event manager process.</fsummary>
<type>
<v>EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}</v>
@@ -375,9 +375,9 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>start_link() -> Result</name>
- <name>start_link(EventMgrName | Options) -> Result</name>
- <name>start_link(EventMgrName, Options) -> Result</name>
+ <name since="">start_link() -> Result</name>
+ <name since="">start_link(EventMgrName | Options) -> Result</name>
+ <name since="OTP 20.0">start_link(EventMgrName, Options) -> Result</name>
<fsummary>Create a generic event manager process in a supervision tree.
</fsummary>
<type>
@@ -436,8 +436,8 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>stop(EventMgrRef) -> ok</name>
- <name>stop(EventMgrRef, Reason, Timeout) -> ok</name>
+ <name since="">stop(EventMgrRef) -> ok</name>
+ <name since="OTP 18.0">stop(EventMgrRef, Reason, Timeout) -> ok</name>
<fsummary>Terminate a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -474,7 +474,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
+ <name since="">swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
<fsummary>Replace an event handler in a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -521,7 +521,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
+ <name since="">swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name>
<fsummary>Replace an event handler in a generic event manager.</fsummary>
<type>
<v>EventMgrRef = Name | {Name,Node} | {global,GlobalName}
@@ -546,7 +546,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>which_handlers(EventMgrRef) -> [Handler]</name>
+ <name since="">which_handlers(EventMgrRef) -> [Handler]</name>
<fsummary>Return all event handlers installed in a generic event manager.
</fsummary>
<type>
@@ -575,7 +575,7 @@ gen_event:stop -----> Module:terminate/2
<funcs>
<func>
- <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
+ <name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
<type>
<v>OldVsn = Vsn | {down, Vsn}</v>
@@ -611,7 +611,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:format_status(Opt, [PDict, State]) -> Status</name>
+ <name since="OTP R14B">Module:format_status(Opt, [PDict, State]) -> Status</name>
<fsummary>Optional function for providing a term describing the
current event handler state.</fsummary>
<type>
@@ -667,7 +667,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:handle_call(Request, State) -> Result</name>
+ <name since="">Module:handle_call(Request, State) -> Result</name>
<fsummary>Handle a synchronous request.</fsummary>
<type>
<v>Request = term()</v>
@@ -698,7 +698,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:handle_event(Event, State) -> Result</name>
+ <name since="">Module:handle_event(Event, State) -> Result</name>
<fsummary>Handle an event.</fsummary>
<type>
<v>Event = term()</v>
@@ -756,7 +756,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:handle_info(Info, State) -> Result</name>
+ <name since="">Module:handle_info(Info, State) -> Result</name>
<fsummary>Handle an incoming message.</fsummary>
<type>
<v>Info = term()</v>
@@ -788,7 +788,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}</name>
+ <name since="">Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}</name>
<fsummary>Initialize an event handler.</fsummary>
<type>
<v>InitArgs = Args | {Args,Term}</v>
@@ -825,7 +825,7 @@ gen_event:stop -----> Module:terminate/2
</func>
<func>
- <name>Module:terminate(Arg, State) -> term()</name>
+ <name since="">Module:terminate(Arg, State) -> term()</name>
<fsummary>Clean up before deletion.</fsummary>
<type>
<v>Arg = Args | {stop,Reason} | stop | remove_handler</v>
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
index 106bda85f5..a4554d7657 100644
--- a/lib/stdlib/doc/src/gen_server.xml
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gen_server</module>
+ <module since="">gen_server</module>
<modulesummary>Generic server behavior.</modulesummary>
<description>
<p>This behavior module provides the server of a client-server
@@ -101,8 +101,8 @@ gen_server:abcast -----> Module:handle_cast/2
<funcs>
<func>
- <name>abcast(Name, Request) -> abcast</name>
- <name>abcast(Nodes, Name, Request) -> abcast</name>
+ <name since="">abcast(Name, Request) -> abcast</name>
+ <name since="">abcast(Nodes, Name, Request) -> abcast</name>
<fsummary>Send an asynchronous request to many generic servers.</fsummary>
<type>
<v>Nodes = [Node]</v>
@@ -124,8 +124,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>call(ServerRef, Request) -> Reply</name>
- <name>call(ServerRef, Request, Timeout) -> Reply</name>
+ <name since="">call(ServerRef, Request) -> Reply</name>
+ <name since="">call(ServerRef, Request, Timeout) -> Reply</name>
<fsummary>Make a synchronous call to a generic server.</fsummary>
<type>
<v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
@@ -175,7 +175,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>cast(ServerRef, Request) -> ok</name>
+ <name since="">cast(ServerRef, Request) -> ok</name>
<fsummary>Send an asynchronous request to a generic server.</fsummary>
<type>
<v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
@@ -200,10 +200,10 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>enter_loop(Module, Options, State)</name>
- <name>enter_loop(Module, Options, State, ServerName)</name>
- <name>enter_loop(Module, Options, State, Timeout)</name>
- <name>enter_loop(Module, Options, State, ServerName, Timeout)</name>
+ <name since="">enter_loop(Module, Options, State)</name>
+ <name since="">enter_loop(Module, Options, State, ServerName)</name>
+ <name since="">enter_loop(Module, Options, State, Timeout)</name>
+ <name since="">enter_loop(Module, Options, State, ServerName, Timeout)</name>
<fsummary>Enter the <c>gen_server</c> receive loop.</fsummary>
<type>
<v>Module = atom()</v>
@@ -248,9 +248,9 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>multi_call(Name, Request) -> Result</name>
- <name>multi_call(Nodes, Name, Request) -> Result</name>
- <name>multi_call(Nodes, Name, Request, Timeout) -> Result</name>
+ <name since="">multi_call(Name, Request) -> Result</name>
+ <name since="">multi_call(Nodes, Name, Request) -> Result</name>
+ <name since="">multi_call(Nodes, Name, Request, Timeout) -> Result</name>
<fsummary>Make a synchronous call to many generic servers.</fsummary>
<type>
<v>Nodes = [Node]</v>
@@ -307,7 +307,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>reply(Client, Reply) -> Result</name>
+ <name since="">reply(Client, Reply) -> Result</name>
<fsummary>Send a reply to a client.</fsummary>
<type>
<v>Client - see below</v>
@@ -332,8 +332,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>start(Module, Args, Options) -> Result</name>
- <name>start(ServerName, Module, Args, Options) -> Result</name>
+ <name since="">start(Module, Args, Options) -> Result</name>
+ <name since="">start(ServerName, Module, Args, Options) -> Result</name>
<fsummary>Create a standalone <c>gen_server</c> process.</fsummary>
<type>
<v>ServerName = {local,Name} | {global,GlobalName}</v>
@@ -361,8 +361,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>start_link(Module, Args, Options) -> Result</name>
- <name>start_link(ServerName, Module, Args, Options) -> Result</name>
+ <name since="">start_link(Module, Args, Options) -> Result</name>
+ <name since="">start_link(ServerName, Module, Args, Options) -> Result</name>
<fsummary>Create a <c>gen_server</c> process in a supervision tree.
</fsummary>
<type>
@@ -466,8 +466,8 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>stop(ServerRef) -> ok</name>
- <name>stop(ServerRef, Reason, Timeout) -> ok</name>
+ <name since="OTP 18.0">stop(ServerRef) -> ok</name>
+ <name since="OTP 18.0">stop(ServerRef, Reason, Timeout) -> ok</name>
<fsummary>Synchronously stop a generic server.</fsummary>
<type>
<v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v>
@@ -508,7 +508,7 @@ gen_server:abcast -----> Module:handle_cast/2
<funcs>
<func>
- <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name>
+ <name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
<type>
<v>OldVsn = Vsn | {down, Vsn}</v>
@@ -550,7 +550,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:format_status(Opt, [PDict, State]) -> Status</name>
+ <name since="OTP R13B04">Module:format_status(Opt, [PDict, State]) -> Status</name>
<fsummary>Optional function for providing a term describing the
current <c>gen_server</c> status.</fsummary>
<type>
@@ -610,7 +610,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_call(Request, From, State) -> Result</name>
+ <name since="">Module:handle_call(Request, From, State) -> Result</name>
<fsummary>Handle a synchronous request.</fsummary>
<type>
<v>Request = term()</v>
@@ -677,7 +677,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_cast(Request, State) -> Result</name>
+ <name since="">Module:handle_cast(Request, State) -> Result</name>
<fsummary>Handle an asynchronous request.</fsummary>
<type>
<v>Request = term()</v>
@@ -703,7 +703,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_continue(Continue, State) -> Result</name>
+ <name since="OTP 21.0">Module:handle_continue(Continue, State) -> Result</name>
<fsummary>Handle a continue instruction.</fsummary>
<type>
<v>Continue = term()</v>
@@ -738,7 +738,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:handle_info(Info, State) -> Result</name>
+ <name since="">Module:handle_info(Info, State) -> Result</name>
<fsummary>Handle an incoming message.</fsummary>
<type>
<v>Info = timeout | term()</v>
@@ -770,7 +770,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:init(Args) -> Result</name>
+ <name since="">Module:init(Args) -> Result</name>
<fsummary>Initialize process and internal state.</fsummary>
<type>
<v>Args = term()</v>
@@ -811,7 +811,7 @@ gen_server:abcast -----> Module:handle_cast/2
</func>
<func>
- <name>Module:terminate(Reason, State)</name>
+ <name since="">Module:terminate(Reason, State)</name>
<fsummary>Clean up before termination.</fsummary>
<type>
<v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index dfecd235c9..d678d0436b 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>gen_statem</module>
+ <module since="OTP 19.0">gen_statem</module>
<modulesummary>Generic state machine behavior.</modulesummary>
<description>
<p>
@@ -1416,8 +1416,8 @@ handle_event(_, _, State, Data) ->
<funcs>
<func>
- <name name="call" arity="2"/>
- <name name="call" arity="3"/>
+ <name name="call" arity="2" since="OTP 19.0"/>
+ <name name="call" arity="3" since="OTP 19.0"/>
<fsummary>Make a synchronous call to a <c>gen_statem</c>.</fsummary>
<desc>
<p>
@@ -1493,7 +1493,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="cast" arity="2"/>
+ <name name="cast" arity="2" since="OTP 19.0"/>
<fsummary>Send an asynchronous event to a <c>gen_statem</c>.</fsummary>
<desc>
<p>
@@ -1513,7 +1513,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="enter_loop" arity="4"/>
+ <name name="enter_loop" arity="4" since="OTP 19.1"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
@@ -1527,7 +1527,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="enter_loop" arity="5"/>
+ <name name="enter_loop" arity="5" since="OTP 19.0"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
@@ -1551,7 +1551,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="enter_loop" arity="6"/>
+ <name name="enter_loop" arity="6" since="OTP 19.0"/>
<fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary>
<desc>
<p>
@@ -1608,8 +1608,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="reply" arity="1"/>
- <name name="reply" arity="2"/>
+ <name name="reply" arity="1" since="OTP 19.0"/>
+ <name name="reply" arity="2" since="OTP 19.0"/>
<fsummary>Reply to a caller.</fsummary>
<desc>
<p>
@@ -1641,8 +1641,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="start" arity="3"/>
- <name name="start" arity="4"/>
+ <name name="start" arity="3" since="OTP 19.0"/>
+ <name name="start" arity="4" since="OTP 19.0"/>
<fsummary>Create a standalone <c>gen_statem</c> process.</fsummary>
<desc>
<p>
@@ -1662,8 +1662,8 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="start_link" arity="3"/>
- <name name="start_link" arity="4"/>
+ <name name="start_link" arity="3" since="OTP 19.0"/>
+ <name name="start_link" arity="4" since="OTP 19.0"/>
<fsummary>Create a linked <c>gen_statem</c> process.</fsummary>
<desc>
<p>
@@ -1770,7 +1770,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since="OTP 19.0"/>
<fsummary>Synchronously stop a generic server.</fsummary>
<desc>
<p>
@@ -1781,7 +1781,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name name="stop" arity="3"/>
+ <name name="stop" arity="3" since="OTP 19.0"/>
<fsummary>Synchronously stop a generic server.</fsummary>
<desc>
<p>
@@ -1827,7 +1827,7 @@ handle_event(_, _, State, Data) ->
<funcs>
<func>
- <name>Module:callback_mode() -> CallbackMode</name>
+ <name since="OTP 19.1">Module:callback_mode() -> CallbackMode</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
<type>
<v>
@@ -1878,7 +1878,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name>Module:code_change(OldVsn, OldState, OldData, Extra) ->
+ <name since="OTP 19.0">Module:code_change(OldVsn, OldState, OldData, Extra) ->
Result
</name>
<fsummary>Update the internal state during upgrade/downgrade.</fsummary>
@@ -1967,7 +1967,7 @@ handle_event(_, _, State, Data) ->
</func>
<func>
- <name>Module:init(Args) -> Result(StateType)</name>
+ <name since="OTP 19.0">Module:init(Args) -> Result(StateType)</name>
<fsummary>
Initializing process and internal state.
</fsummary>
@@ -2009,7 +2009,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</func>
<func>
- <name>Module:format_status(Opt, [PDict,State,Data]) ->
+ <name since="OTP 19.0">Module:format_status(Opt, [PDict,State,Data]) ->
Status
</name>
<fsummary>Optional function for providing a term describing the
@@ -2108,16 +2108,16 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</func>
<func>
- <name>Module:StateName(enter, OldState, Data) ->
+ <name since="OTP 19.0">Module:StateName(enter, OldState, Data) ->
StateEnterResult(StateName)
</name>
- <name>Module:StateName(EventType, EventContent, Data) ->
+ <name since="OTP 19.0">Module:StateName(EventType, EventContent, Data) ->
StateFunctionResult
</name>
- <name>Module:handle_event(enter, OldState, State, Data) ->
+ <name since="OTP 19.0">Module:handle_event(enter, OldState, State, Data) ->
StateEnterResult(State)
</name>
- <name>Module:handle_event(EventType, EventContent, State, Data) ->
+ <name since="OTP 19.0">Module:handle_event(EventType, EventContent, State, Data) ->
HandleEventResult
</name>
<fsummary>Handle an event.</fsummary>
@@ -2236,7 +2236,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</func>
<func>
- <name>Module:terminate(Reason, State, Data) -> Ignored</name>
+ <name since="OTP 19.0">Module:terminate(Reason, State, Data) -> Ignored</name>
<fsummary>Clean up before termination.</fsummary>
<type>
<v>Reason = normal | shutdown | {shutdown,term()} | term()</v>
diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml
index d4a2713840..d69e808586 100644
--- a/lib/stdlib/doc/src/io.xml
+++ b/lib/stdlib/doc/src/io.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>io</module>
+ <module since="">io</module>
<modulesummary>Standard I/O server interface functions.</modulesummary>
<description>
<p>This module provides an interface to standard Erlang I/O servers.
@@ -104,8 +104,8 @@
<funcs>
<func>
- <name name="columns" arity="0"/>
- <name name="columns" arity="1"/>
+ <name name="columns" arity="0" since=""/>
+ <name name="columns" arity="1" since=""/>
<fsummary>Get the number of columns of an I/O device.</fsummary>
<desc>
<p>Retrieves the number of columns of the
@@ -116,12 +116,12 @@
</func>
<func>
- <name name="format" arity="1"/>
- <name name="format" arity="2"/>
- <name name="format" arity="3"/>
- <name name="fwrite" arity="1"/>
- <name name="fwrite" arity="2"/>
- <name name="fwrite" arity="3"/>
+ <name name="format" arity="1" since=""/>
+ <name name="format" arity="2" since=""/>
+ <name name="format" arity="3" since=""/>
+ <name name="fwrite" arity="1" since=""/>
+ <name name="fwrite" arity="2" since=""/>
+ <name name="fwrite" arity="3" since=""/>
<fsummary>Write formatted output.</fsummary>
<desc>
<p>Writes the items in <c><anno>Data</anno></c> (<c>[]</c>) on the
@@ -523,8 +523,8 @@ ok
</func>
<func>
- <name name="fread" arity="2"/>
- <name name="fread" arity="3"/>
+ <name name="fread" arity="2" since=""/>
+ <name name="fread" arity="3" since=""/>
<fsummary>Read formatted input.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -690,8 +690,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="get_chars" arity="2"/>
- <name name="get_chars" arity="3"/>
+ <name name="get_chars" arity="2" since=""/>
+ <name name="get_chars" arity="3" since=""/>
<fsummary>Read a specified number of characters.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -722,8 +722,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="get_line" arity="1"/>
- <name name="get_line" arity="2"/>
+ <name name="get_line" arity="1" since=""/>
+ <name name="get_line" arity="2" since=""/>
<fsummary>Read a line.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -754,8 +754,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="getopts" arity="0"/>
- <name name="getopts" arity="1"/>
+ <name name="getopts" arity="0" since=""/>
+ <name name="getopts" arity="1" since=""/>
<fsummary>Get the supported options and values from an I/O server.
</fsummary>
<desc>
@@ -781,8 +781,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="nl" arity="0"/>
- <name name="nl" arity="1"/>
+ <name name="nl" arity="0" since=""/>
+ <name name="nl" arity="1" since=""/>
<fsummary>Write a newline.</fsummary>
<desc>
<p>Writes new line to the standard output
@@ -791,10 +791,10 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in
</func>
<func>
- <name name="parse_erl_exprs" arity="1"/>
- <name name="parse_erl_exprs" arity="2"/>
- <name name="parse_erl_exprs" arity="3"/>
- <name name="parse_erl_exprs" arity="4"/>
+ <name name="parse_erl_exprs" arity="1" since=""/>
+ <name name="parse_erl_exprs" arity="2" since=""/>
+ <name name="parse_erl_exprs" arity="3" since=""/>
+ <name name="parse_erl_exprs" arity="4" since="OTP R16B"/>
<fsummary>Read, tokenize, and parse Erlang expressions.</fsummary>
<type name="parse_ret"/>
<type name="server_no_data"/>
@@ -844,10 +844,10 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="parse_erl_form" arity="1"/>
- <name name="parse_erl_form" arity="2"/>
- <name name="parse_erl_form" arity="3"/>
- <name name="parse_erl_form" arity="4"/>
+ <name name="parse_erl_form" arity="1" since=""/>
+ <name name="parse_erl_form" arity="2" since=""/>
+ <name name="parse_erl_form" arity="3" since=""/>
+ <name name="parse_erl_form" arity="4" since="OTP R16B"/>
<fsummary>Read, tokenize, and parse an Erlang form.</fsummary>
<type name="parse_form_ret"/>
<type name="server_no_data"/>
@@ -888,7 +888,7 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="printable_range" arity="0"/>
+ <name name="printable_range" arity="0" since="OTP R16B"/>
<fsummary>Get user-requested printable character range.</fsummary>
<desc>
<p>Returns the user-requested range of printable Unicode characters.</p>
@@ -918,8 +918,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="put_chars" arity="1"/>
- <name name="put_chars" arity="2"/>
+ <name name="put_chars" arity="1" since=""/>
+ <name name="put_chars" arity="2" since=""/>
<fsummary>Write a list of characters.</fsummary>
<desc>
<p>Writes the characters of <c><anno>CharData</anno></c> to the I/O
@@ -928,8 +928,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="read" arity="1"/>
- <name name="read" arity="2"/>
+ <name name="read" arity="1" since=""/>
+ <name name="read" arity="2" since=""/>
<fsummary>Read a term.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -960,8 +960,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="read" arity="3"/>
- <name name="read" arity="4"/>
+ <name name="read" arity="3" since=""/>
+ <name name="read" arity="4" since="OTP R16B"/>
<fsummary>Read a term.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -997,8 +997,8 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="rows" arity="0"/>
- <name name="rows" arity="1"/>
+ <name name="rows" arity="0" since=""/>
+ <name name="rows" arity="1" since=""/>
<fsummary>Get the number of rows of an I/O device.</fsummary>
<desc>
<p>Retrieves the number of rows of <c><anno>IoDevice</anno></c>
@@ -1009,10 +1009,10 @@ enter><input>abc("hey".</input>
</func>
<func>
- <name name="scan_erl_exprs" arity="1"/>
- <name name="scan_erl_exprs" arity="2"/>
- <name name="scan_erl_exprs" arity="3"/>
- <name name="scan_erl_exprs" arity="4"/>
+ <name name="scan_erl_exprs" arity="1" since=""/>
+ <name name="scan_erl_exprs" arity="2" since=""/>
+ <name name="scan_erl_exprs" arity="3" since=""/>
+ <name name="scan_erl_exprs" arity="4" since="OTP R16B"/>
<fsummary>Read and tokenize Erlang expressions.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -1060,10 +1060,10 @@ enter><input>1.0er.</input>
</func>
<func>
- <name name="scan_erl_form" arity="1"/>
- <name name="scan_erl_form" arity="2"/>
- <name name="scan_erl_form" arity="3"/>
- <name name="scan_erl_form" arity="4"/>
+ <name name="scan_erl_form" arity="1" since=""/>
+ <name name="scan_erl_form" arity="2" since=""/>
+ <name name="scan_erl_form" arity="3" since=""/>
+ <name name="scan_erl_form" arity="4" since="OTP R16B"/>
<fsummary>Read and tokenize an Erlang form.</fsummary>
<type name="server_no_data"/>
<desc>
@@ -1083,8 +1083,8 @@ enter><input>1.0er.</input>
</func>
<func>
- <name name="setopts" arity="1"/>
- <name name="setopts" arity="2"/>
+ <name name="setopts" arity="1" since=""/>
+ <name name="setopts" arity="2" since=""/>
<fsummary>Set options.</fsummary>
<desc>
<p>Set options for the standard I/O device
@@ -1198,8 +1198,8 @@ fun("") -> {yes, "quit", []};
</func>
<func>
- <name name="write" arity="1"/>
- <name name="write" arity="2"/>
+ <name name="write" arity="1" since=""/>
+ <name name="write" arity="2" since=""/>
<fsummary>Write a term.</fsummary>
<desc>
<p>Writes term <c><anno>Term</anno></c> to the standard output
diff --git a/lib/stdlib/doc/src/io_lib.xml b/lib/stdlib/doc/src/io_lib.xml
index a3df2897ac..4d527f8ed3 100644
--- a/lib/stdlib/doc/src/io_lib.xml
+++ b/lib/stdlib/doc/src/io_lib.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>io_lib</module>
+ <module since="">io_lib</module>
<modulesummary>I/O library functions.</modulesummary>
<description>
<p>This module contains functions for converting to and from
@@ -99,7 +99,7 @@
<funcs>
<func>
- <name name="build_text" arity="1"/>
+ <name name="build_text" arity="1" since="OTP 18.0"/>
<fsummary>Build the output text for a preparsed format list.</fsummary>
<desc>
<p>For details, see
@@ -108,7 +108,7 @@
</func>
<func>
- <name name="char_list" arity="1"/>
+ <name name="char_list" arity="1" since=""/>
<fsummary>Test for a list of characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -117,7 +117,7 @@
</func>
<func>
- <name name="deep_char_list" arity="1"/>
+ <name name="deep_char_list" arity="1" since=""/>
<fsummary>Test for a deep list of characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a, possibly deep,
@@ -126,7 +126,7 @@
</func>
<func>
- <name name="deep_latin1_char_list" arity="1"/>
+ <name name="deep_latin1_char_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a deep list of characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a, possibly deep,
@@ -136,8 +136,8 @@
</func>
<func>
- <name name="format" arity="2"/>
- <name name="fwrite" arity="2"/>
+ <name name="format" arity="2" since=""/>
+ <name name="fwrite" arity="2" since=""/>
<fsummary>Write formatted output.</fsummary>
<desc>
<p>Returns a character list that represents <c><anno>Data</anno></c>
@@ -156,8 +156,8 @@
</func>
<func>
- <name name="format" arity="3"/>
- <name name="fwrite" arity="3"/>
+ <name name="format" arity="3" since="OTP 21.0"/>
+ <name name="fwrite" arity="3" since="OTP 21.0"/>
<fsummary>Write formatted output.</fsummary>
<desc>
<p>Returns a character list that represents <c><anno>Data</anno></c>
@@ -181,7 +181,7 @@
</func>
<func>
- <name name="fread" arity="2"/>
+ <name name="fread" arity="2" since=""/>
<fsummary>Read formatted input.</fsummary>
<desc>
<p>Tries to read <c><anno>String</anno></c> in accordance with the
@@ -222,7 +222,7 @@
</func>
<func>
- <name name="fread" arity="3"/>
+ <name name="fread" arity="3" since=""/>
<fsummary>Re-entrant formatted reader</fsummary>
<desc>
<p>This is the re-entrant formatted reader. The continuation of
@@ -268,7 +268,7 @@
</func>
<func>
- <name name="indentation" arity="2"/>
+ <name name="indentation" arity="2" since=""/>
<fsummary>Indentation after printing string.</fsummary>
<desc>
<p>Returns the indentation if <c><anno>String</anno></c> has been
@@ -277,7 +277,7 @@
</func>
<func>
- <name name="latin1_char_list" arity="1"/>
+ <name name="latin1_char_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a list of ISO Latin-1 characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -286,7 +286,7 @@
</func>
<func>
- <name name="nl" arity="0"/>
+ <name name="nl" arity="0" since=""/>
<fsummary>Write a newline.</fsummary>
<desc>
<p>Returns a character list that represents a new line character.</p>
@@ -294,8 +294,8 @@
</func>
<func>
- <name name="print" arity="1"/>
- <name name="print" arity="4"/>
+ <name name="print" arity="1" since=""/>
+ <name name="print" arity="4" since=""/>
<fsummary>Pretty print a term.</fsummary>
<desc>
<p>Returns a list of characters that represents
@@ -315,7 +315,7 @@
</func>
<func>
- <name name="printable_latin1_list" arity="1"/>
+ <name name="printable_latin1_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a list of printable ISO Latin-1 characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -324,7 +324,7 @@
</func>
<func>
- <name name="printable_list" arity="1"/>
+ <name name="printable_list" arity="1" since=""/>
<fsummary>Test for a list of printable characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -338,7 +338,7 @@
</func>
<func>
- <name name="printable_unicode_list" arity="1"/>
+ <name name="printable_unicode_list" arity="1" since="OTP R16B"/>
<fsummary>Test for a list of printable Unicode characters.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of
@@ -347,7 +347,7 @@
</func>
<func>
- <name name="scan_format" arity="2"/>
+ <name name="scan_format" arity="2" since="OTP 18.0"/>
<fsummary>Parse all control sequences in the format string.</fsummary>
<desc>
<p>Returns a list corresponding to the specified format string,
@@ -373,7 +373,7 @@
</func>
<func>
- <name name="unscan_format" arity="1"/>
+ <name name="unscan_format" arity="1" since="OTP 18.0"/>
<fsummary>Revert a preparsed format list to a plain character list
and a list of arguments.</fsummary>
<desc>
@@ -383,9 +383,9 @@
</func>
<func>
- <name name="write" arity="1"/>
- <name name="write" arity="2" clause_i="1"/>
- <name name="write" arity="2" clause_i="2"/>
+ <name name="write" arity="1" since=""/>
+ <name name="write" arity="2" clause_i="1" since=""/>
+ <name name="write" arity="2" clause_i="2" since="OTP 20.0"/>
<fsummary>Write a term.</fsummary>
<desc>
<p>Returns a character list that represents <c><anno>Term</anno></c>.
@@ -411,7 +411,7 @@
</func>
<func>
- <name name="write_atom" arity="1"/>
+ <name name="write_atom" arity="1" since=""/>
<fsummary>Write an atom.</fsummary>
<desc>
<p>Returns the list of characters needed to print atom
@@ -420,7 +420,7 @@
</func>
<func>
- <name name="write_atom_as_latin1" arity="1"/>
+ <name name="write_atom_as_latin1" arity="1" since="OTP 20.0"/>
<fsummary>Write an atom.</fsummary>
<desc>
<p>Returns the list of characters needed to print atom
@@ -430,7 +430,7 @@
</func>
<func>
- <name name="write_char" arity="1"/>
+ <name name="write_char" arity="1" since=""/>
<fsummary>Write a character.</fsummary>
<desc>
<p>Returns the list of characters needed to print a character
@@ -439,7 +439,7 @@
</func>
<func>
- <name name="write_char_as_latin1" arity="1"/>
+ <name name="write_char_as_latin1" arity="1" since="OTP R16B"/>
<fsummary>Write a character.</fsummary>
<desc>
<p>Returns the list of characters needed to print a character
@@ -449,7 +449,7 @@
</func>
<func>
- <name name="write_latin1_char" arity="1"/>
+ <name name="write_latin1_char" arity="1" since="OTP R16B"/>
<fsummary>Write an ISO Latin-1 character.</fsummary>
<desc>
<p>Returns the list of characters needed to print a character
@@ -458,7 +458,7 @@
</func>
<func>
- <name name="write_latin1_string" arity="1"/>
+ <name name="write_latin1_string" arity="1" since="OTP R16B"/>
<fsummary>Write an ISO Latin-1 string.</fsummary>
<desc>
<p>Returns the list of characters needed to print
@@ -467,7 +467,7 @@
</func>
<func>
- <name name="write_string" arity="1"/>
+ <name name="write_string" arity="1" since=""/>
<fsummary>Write a string.</fsummary>
<desc>
<p>Returns the list of characters needed to print
@@ -476,7 +476,7 @@
</func>
<func>
- <name name="write_string_as_latin1" arity="1"/>
+ <name name="write_string_as_latin1" arity="1" since="OTP R16B"/>
<fsummary>Write a string.</fsummary>
<desc>
<p>Returns the list of characters needed to print
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index e4215a5336..2755fb3dce 100644
--- a/lib/stdlib/doc/src/lists.xml
+++ b/lib/stdlib/doc/src/lists.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2018</year>
+ <year>1996</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -28,7 +28,7 @@
<date>1996-09-28</date>
<rev>A</rev>
</header>
- <module>lists</module>
+ <module since="">lists</module>
<modulesummary>List processing functions.</modulesummary>
<description>
<p>This module contains functions for list processing.</p>
@@ -63,7 +63,7 @@
<funcs>
<func>
- <name name="all" arity="2"/>
+ <name name="all" arity="2" since=""/>
<fsummary>Return <c>true</c> if all elements in a list satisfy
<c>Pred</c>.</fsummary>
<desc>
@@ -74,7 +74,7 @@
</func>
<func>
- <name name="any" arity="2"/>
+ <name name="any" arity="2" since=""/>
<fsummary>Return <c>true</c> if any of the elements in a list
satisfies <c>Pred</c>.</fsummary>
<desc>
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="append" arity="1"/>
+ <name name="append" arity="1" since=""/>
<fsummary>Append a list of lists.</fsummary>
<desc>
<p>Returns a list in which all the sublists of
@@ -98,7 +98,7 @@
</func>
<func>
- <name name="append" arity="2"/>
+ <name name="append" arity="2" since=""/>
<fsummary>Append two lists.</fsummary>
<desc>
<p>Returns a new list <c><anno>List3</anno></c>, which is made from
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="concat" arity="1"/>
+ <name name="concat" arity="1" since=""/>
<fsummary>Concatenate a list of atoms.</fsummary>
<desc>
<p>Concatenates the text representation of the elements of
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary>Delete an element from a list.</fsummary>
<desc>
<p>Returns a copy of <c><anno>List1</anno></c> where the first element
@@ -137,7 +137,7 @@
</func>
<func>
- <name name="droplast" arity="1"/>
+ <name name="droplast" arity="1" since="OTP 17.0"/>
<fsummary>Drop the last element of a list.</fsummary>
<desc>
<p>Drops the last element of a <c><anno>List</anno></c>. The list is to
@@ -147,7 +147,7 @@
</func>
<func>
- <name name="dropwhile" arity="2"/>
+ <name name="dropwhile" arity="2" since=""/>
<fsummary>Drop elements from a list while a predicate is <c>true</c>.
</fsummary>
<desc>
@@ -159,7 +159,7 @@
</func>
<func>
- <name name="duplicate" arity="2"/>
+ <name name="duplicate" arity="2" since=""/>
<fsummary>Make <c>N</c> copies of element.</fsummary>
<desc>
<p>Returns a list containing <c><anno>N</anno></c> copies of term
@@ -172,7 +172,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Select elements that satisfy a predicate.</fsummary>
<desc>
<p><c><anno>List2</anno></c> is a list of all elements
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="filtermap" arity="2"/>
+ <name name="filtermap" arity="2" since="OTP R16B01"/>
<fsummary>Filter and map elements that satisfy a function.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> on successive
@@ -211,7 +211,7 @@ filtermap(Fun, List1) ->
</func>
<func>
- <name name="flatlength" arity="1"/>
+ <name name="flatlength" arity="1" since=""/>
<fsummary>Length of flattened deep list.</fsummary>
<desc>
<p>Equivalent to <c>length(flatten(<anno>DeepList</anno>))</c>, but
@@ -220,7 +220,7 @@ filtermap(Fun, List1) ->
</func>
<func>
- <name name="flatmap" arity="2"/>
+ <name name="flatmap" arity="2" since=""/>
<fsummary>Map and flatten in one pass.</fsummary>
<desc>
<p>Takes a function from <c><anno>A</anno></c>s to lists of
@@ -241,7 +241,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="flatten" arity="1"/>
+ <name name="flatten" arity="1" since=""/>
<fsummary>Flatten a deep list.</fsummary>
<desc>
<p>Returns a flattened version of <c><anno>DeepList</anno></c>.</p>
@@ -249,7 +249,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="flatten" arity="2"/>
+ <name name="flatten" arity="2" since=""/>
<fsummary>Flatten a deep list.</fsummary>
<desc>
<p>Returns a flattened version of <c><anno>DeepList</anno></c> with tail
@@ -258,7 +258,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since=""/>
<fsummary>Fold a function over a list.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>, <anno>AccIn</anno>)</c>
@@ -278,7 +278,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="foldr" arity="3"/>
+ <name name="foldr" arity="3" since=""/>
<fsummary>Fold a function over a list.</fsummary>
<desc>
<p>Like <seealso marker="#foldl/3"><c>foldl/3</c></seealso>, but the
@@ -297,7 +297,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since="OTP 19.0"/>
<fsummary>Insert an element between elements in a list</fsummary>
<desc>
<p>Inserts <c><anno>Sep</anno></c> between each element in <c><anno>List1</anno></c>. Has no
@@ -312,7 +312,7 @@ flatmap(Fun, List1) ->
</desc>
</func>
<func>
- <name name="foreach" arity="2"/>
+ <name name="foreach" arity="2" since=""/>
<fsummary>Apply a function to each element of a list.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> for each element
@@ -324,7 +324,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keydelete" arity="3"/>
+ <name name="keydelete" arity="3" since=""/>
<fsummary>Delete an element from a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -336,7 +336,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keyfind" arity="3"/>
+ <name name="keyfind" arity="3" since=""/>
<fsummary>Search for an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -349,7 +349,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keymap" arity="3"/>
+ <name name="keymap" arity="3" since=""/>
<fsummary>Map a function over a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -368,7 +368,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keymember" arity="3"/>
+ <name name="keymember" arity="3" since=""/>
<fsummary>Test for membership of a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -379,7 +379,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keymerge" arity="3"/>
+ <name name="keymerge" arity="3" since=""/>
<fsummary>Merge two key-sorted lists of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -395,7 +395,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keyreplace" arity="4"/>
+ <name name="keyreplace" arity="4" since=""/>
<fsummary>Replace an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -407,7 +407,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keysearch" arity="3"/>
+ <name name="keysearch" arity="3" since=""/>
<fsummary>Search for an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -425,7 +425,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keysort" arity="2"/>
+ <name name="keysort" arity="2" since=""/>
<fsummary>Sort a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -436,7 +436,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keystore" arity="4"/>
+ <name name="keystore" arity="4" since=""/>
<fsummary>Store an element in a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -452,7 +452,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="keytake" arity="3"/>
+ <name name="keytake" arity="3" since=""/>
<fsummary>Extract an element from a list of tuples.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -467,7 +467,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since=""/>
<fsummary>Return last element in a list.</fsummary>
<desc>
<p>Returns the last element in <c><anno>List</anno></c>.</p>
@@ -475,7 +475,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map a function over a list.</fsummary>
<desc>
<p>Takes a function from <c><anno>A</anno></c>s to
@@ -488,7 +488,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="mapfoldl" arity="3"/>
+ <name name="mapfoldl" arity="3" since=""/>
<fsummary>Map and fold in one pass.</fsummary>
<desc>
<p>Combines the operations of
@@ -504,7 +504,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="mapfoldr" arity="3"/>
+ <name name="mapfoldr" arity="3" since=""/>
<fsummary>Map and fold in one pass.</fsummary>
<desc>
<p>Combines the operations of
@@ -514,7 +514,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="max" arity="1"/>
+ <name name="max" arity="1" since=""/>
<fsummary>Return maximum element of a list.</fsummary>
<desc>
<p>Returns the first element of <c><anno>List</anno></c> that compares
@@ -524,7 +524,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Test for membership of a list.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Elem</anno></c> matches some element
@@ -533,7 +533,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge" arity="1"/>
+ <name name="merge" arity="1" since=""/>
<fsummary>Merge a list of sorted lists.</fsummary>
<desc>
<p>Returns the sorted list formed by merging all the sublists of
@@ -546,7 +546,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge" arity="2"/>
+ <name name="merge" arity="2" since=""/>
<fsummary>Merge two sorted lists.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -559,7 +559,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge two sorted list.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -577,7 +577,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="merge3" arity="3"/>
+ <name name="merge3" arity="3" since=""/>
<fsummary>Merge three sorted lists.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>,
@@ -593,7 +593,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="min" arity="1"/>
+ <name name="min" arity="1" since=""/>
<fsummary>Return minimum element of a list.</fsummary>
<desc>
<p>Returns the first element of <c><anno>List</anno></c> that compares
@@ -603,7 +603,7 @@ flatmap(Fun, List1) ->
</func>
<func>
- <name name="nth" arity="2"/>
+ <name name="nth" arity="2" since=""/>
<fsummary>Return the <c>N</c>th element of a list.</fsummary>
<type_desc variable="N">1..length(<anno>List</anno>)</type_desc>
<desc>
@@ -617,7 +617,7 @@ c</pre>
</func>
<func>
- <name name="nthtail" arity="2"/>
+ <name name="nthtail" arity="2" since=""/>
<fsummary>Return the <c>N</c>th tail of a list.</fsummary>
<type_desc variable="N">0..length(<anno>List</anno>)</type_desc>
<desc>
@@ -638,7 +638,7 @@ c</pre>
</func>
<func>
- <name name="partition" arity="2"/>
+ <name name="partition" arity="2" since=""/>
<fsummary>Partition a list into two lists based on a predicate.</fsummary>
<desc>
<p>Partitions <c><anno>List</anno></c> into two lists, where the first
@@ -658,7 +658,7 @@ c</pre>
</func>
<func>
- <name name="prefix" arity="2"/>
+ <name name="prefix" arity="2" since=""/>
<fsummary>Test for list prefix.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>List1</anno></c> is a prefix of
@@ -667,7 +667,7 @@ c</pre>
</func>
<func>
- <name name="reverse" arity="1"/>
+ <name name="reverse" arity="1" since=""/>
<fsummary>Reverse a list.</fsummary>
<desc>
<p>Returns a list with the elements in <c><anno>List1</anno></c>
@@ -676,7 +676,7 @@ c</pre>
</func>
<func>
- <name name="reverse" arity="2"/>
+ <name name="reverse" arity="2" since=""/>
<fsummary>Reverse a list appending a tail.</fsummary>
<desc>
<p>Returns a list with the elements in <c><anno>List1</anno></c>
@@ -689,8 +689,20 @@ c</pre>
</func>
<func>
- <name name="seq" arity="2"/>
- <name name="seq" arity="3"/>
+ <name name="search" arity="2" since="OTP 21.0"/>
+ <fsummary>Find the first element that satisfies a predicate.</fsummary>
+ <desc>
+ <p>If there is a <c><anno>Value</anno></c> in <c><anno>List</anno></c>
+ such that <c><anno>Pred</anno>(<anno>Value</anno>)</c> returns
+ <c>true</c>, returns <c>{value, <anno>Value</anno>}</c>
+ for the first such <c><anno>Value</anno></c>,
+ otherwise returns <c>false</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name name="seq" arity="2" since=""/>
+ <name name="seq" arity="3" since=""/>
<fsummary>Generate a sequence of integers.</fsummary>
<desc>
<p>Returns a sequence of integers that starts with
@@ -736,7 +748,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="sort" arity="1"/>
+ <name name="sort" arity="1" since=""/>
<fsummary>Sort a list.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -745,7 +757,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="sort" arity="2"/>
+ <name name="sort" arity="2" since=""/>
<fsummary>Sort a list.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -759,7 +771,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary>Split a list into two lists.</fsummary>
<type_desc variable="N">0..length(<anno>List1</anno>)</type_desc>
<desc>
@@ -771,19 +783,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code>
</func>
<func>
- <name name="search" arity="2"/>
- <fsummary>Find the first element that satisfies a predicate.</fsummary>
- <desc>
- <p>If there is a <c><anno>Value</anno></c> in <c><anno>List</anno></c>
- such that <c><anno>Pred</anno>(<anno>Value</anno>)</c> returns
- <c>true</c>, returns <c>{value, <anno>Value</anno>}</c>
- for the first such <c><anno>Value</anno></c>,
- otherwise returns <c>false</c>.</p>
- </desc>
- </func>
-
- <func>
- <name name="splitwith" arity="2"/>
+ <name name="splitwith" arity="2" since=""/>
<fsummary>Split a list into two lists based on a predicate.</fsummary>
<desc>
<p>Partitions <c><anno>List</anno></c> into two lists according to
@@ -804,7 +804,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="sublist" arity="2"/>
+ <name name="sublist" arity="2" since=""/>
<fsummary>Return a sublist of a certain length, starting at the first
position.</fsummary>
<desc>
@@ -816,7 +816,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="sublist" arity="3"/>
+ <name name="sublist" arity="3" since=""/>
<fsummary>Return a sublist starting at a specified position and with a
specified number of elements.</fsummary>
<type_desc variable="Start">1..(length(<anno>List1</anno>)+1)</type_desc>
@@ -838,7 +838,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Subtract the element in one list from another list.</fsummary>
<desc>
<p>Returns a new list <c><anno>List3</anno></c> that is a copy of
@@ -854,7 +854,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="suffix" arity="2"/>
+ <name name="suffix" arity="2" since=""/>
<fsummary>Test for list suffix.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>List1</anno></c> is a suffix of
@@ -863,7 +863,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="sum" arity="1"/>
+ <name name="sum" arity="1" since=""/>
<fsummary>Return the sum of elements in a list.</fsummary>
<desc>
<p>Returns the sum of the elements in <c><anno>List</anno></c>.</p>
@@ -871,7 +871,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="takewhile" arity="2"/>
+ <name name="takewhile" arity="2" since=""/>
<fsummary>Take elements from a list while a predicate is <c>true</c>.
</fsummary>
<desc>
@@ -884,7 +884,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="ukeymerge" arity="3"/>
+ <name name="ukeymerge" arity="3" since=""/>
<fsummary>Merge two key-sorted lists of tuples, removing duplicates.
</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
@@ -902,7 +902,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="ukeysort" arity="2"/>
+ <name name="ukeysort" arity="2" since=""/>
<fsummary>Sort a list of tuples, removing duplicates.</fsummary>
<type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc>
<desc>
@@ -914,7 +914,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge" arity="1"/>
+ <name name="umerge" arity="1" since=""/>
<fsummary>Merge a list of sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging all the sublists
@@ -927,7 +927,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge" arity="2"/>
+ <name name="umerge" arity="2" since=""/>
<fsummary>Merge two sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -941,7 +941,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge" arity="3"/>
+ <name name="umerge" arity="3" since=""/>
<fsummary>Merge two sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>
@@ -958,7 +958,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="umerge3" arity="3"/>
+ <name name="umerge3" arity="3" since=""/>
<fsummary>Merge three sorted lists, removing duplicates.</fsummary>
<desc>
<p>Returns the sorted list formed by merging <c><anno>List1</anno></c>,
@@ -973,7 +973,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="unzip" arity="1"/>
+ <name name="unzip" arity="1" since=""/>
<fsummary>Unzip a list of two-tuples into two lists.</fsummary>
<desc>
<p>"Unzips" a list of two-tuples into two lists, where the first
@@ -983,7 +983,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="unzip3" arity="1"/>
+ <name name="unzip3" arity="1" since=""/>
<fsummary>Unzip a list of three-tuples into three lists.</fsummary>
<desc>
<p>"Unzips" a list of three-tuples into three lists, where
@@ -994,7 +994,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="usort" arity="1"/>
+ <name name="usort" arity="1" since=""/>
<fsummary>Sort a list, removing duplicates.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -1004,7 +1004,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="usort" arity="2"/>
+ <name name="usort" arity="2" since=""/>
<fsummary>Sort a list, removing duplicates.</fsummary>
<desc>
<p>Returns a list containing the sorted elements of
@@ -1019,7 +1019,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zip" arity="2"/>
+ <name name="zip" arity="2" since=""/>
<fsummary>Zip two lists into a list of two-tuples.</fsummary>
<desc>
<p>"Zips" two lists of equal length into one list of two-tuples,
@@ -1030,7 +1030,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zip3" arity="3"/>
+ <name name="zip3" arity="3" since=""/>
<fsummary>Zip three lists into a list of three-tuples.</fsummary>
<desc>
<p>"Zips" three lists of equal length into one list of
@@ -1042,7 +1042,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zipwith" arity="3"/>
+ <name name="zipwith" arity="3" since=""/>
<fsummary>Zip two lists into one list according to a fun.</fsummary>
<desc>
<p>Combines the elements of two lists of equal length into one list.
@@ -1059,7 +1059,7 @@ splitwith(Pred, List) ->
</func>
<func>
- <name name="zipwith3" arity="4"/>
+ <name name="zipwith3" arity="4" since=""/>
<fsummary>Zip three lists into one list according to a fun.</fsummary>
<desc>
<p>Combines the elements of three lists of equal length into one
diff --git a/lib/stdlib/doc/src/log_mf_h.xml b/lib/stdlib/doc/src/log_mf_h.xml
index edc3d31025..b922006cc0 100644
--- a/lib/stdlib/doc/src/log_mf_h.xml
+++ b/lib/stdlib/doc/src/log_mf_h.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>log_mf_h.xml</file>
</header>
- <module>log_mf_h</module>
+ <module since="">log_mf_h</module>
<modulesummary>An event handler that logs events to disk.</modulesummary>
<description>
<p>This module is a <c>gen_event</c> handler module that can be installed
@@ -60,8 +60,8 @@
<funcs>
<func>
- <name name="init" arity="3"/>
- <name name="init" arity="4"/>
+ <name name="init" arity="3" since=""/>
+ <name name="init" arity="4" since=""/>
<fsummary>Initiate the event handler.</fsummary>
<desc>
<p>Initiates the event handler. Returns <c><anno>Args</anno></c>, which
diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml
index 4c5199ca2b..8e88882b56 100644
--- a/lib/stdlib/doc/src/maps.xml
+++ b/lib/stdlib/doc/src/maps.xml
@@ -27,7 +27,7 @@
<date>2014-02-28</date>
<rev>A</rev>
</header>
- <module>maps</module>
+ <module since="OTP 17.0">maps</module>
<modulesummary>Maps processing functions.</modulesummary>
<description>
<p>This module contains functions for maps processing.</p>
@@ -54,7 +54,7 @@
<funcs>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since="OTP 18.0"/>
<fsummary>Select pairs that satisfy a predicate.</fsummary>
<desc>
<p>Returns a map <c><anno>Map</anno></c> for which predicate
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="find" arity="2"/>
+ <name name="find" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a tuple <c>{ok, Value}</c>, where <c><anno>Value</anno></c>
@@ -92,7 +92,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Calls <c>F(Key, Value, AccIn)</c> for every <c><anno>Key</anno></c>
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Takes a list of key-value tuples elements and builds a map. The
@@ -133,7 +133,7 @@
</func>
<func>
- <name name="get" arity="2"/>
+ <name name="get" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns value <c><anno>Value</anno></c> associated with
@@ -152,7 +152,7 @@
</func>
<func>
- <name name="get" arity="3"/>
+ <name name="get" arity="3" since="OTP 17.1"/>
<fsummary></fsummary>
<desc>
<p>Returns value <c><anno>Value</anno></c> associated with
@@ -173,7 +173,7 @@ val1
</func>
<func>
- <name name="is_key" arity="2"/>
+ <name name="is_key" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns <c>true</c> if map <c><anno>Map</anno></c> contains
@@ -193,7 +193,7 @@ false</code>
</func>
<func>
- <name name="iterator" arity="1"/>
+ <name name="iterator" arity="1" since="OTP 21.0"/>
<fsummary>Create a map iterator.</fsummary>
<desc>
<p>Returns a map iterator <c><anno>Iterator</anno></c> that can
@@ -207,19 +207,19 @@ false</code>
<code type="none">
> M = #{ a => 1, b => 2 }.
#{a => 1,b => 2}
-> I = maps:iterator(M).
-[{a,1},{b,2}]
-> {K1, V1, I2} = maps:next(I).
-{a,1,[{b,2}]}
-> {K2, V2, I3} = maps:next(I2).
-{b,2,[]}
+> I = maps:iterator(M), ok.
+ok
+> {K1, V1, I2} = maps:next(I), {K1, V1}.
+{a,1}
+> {K2, V2, I3} = maps:next(I2),{K2, V2}.
+{b,2}
> maps:next(I3).
none</code>
</desc>
</func>
<func>
- <name name="keys" arity="1"/>
+ <name name="keys" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a complete list of keys, in any order, which resides
@@ -235,7 +235,7 @@ none</code>
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Produces a new map <c><anno>Map</anno></c> by calling function
@@ -259,7 +259,7 @@ none</code>
</func>
<func>
- <name name="merge" arity="2"/>
+ <name name="merge" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Merges two maps into a single map <c><anno>Map3</anno></c>. If two
@@ -277,7 +277,7 @@ none</code>
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a new empty map.</p>
@@ -289,7 +289,7 @@ none</code>
</func>
<func>
- <name name="next" arity="1"/>
+ <name name="next" arity="1" since="OTP 21.0"/>
<fsummary>Get the next key and value from an iterator.</fsummary>
<desc>
<p>Returns the next key-value association in
@@ -304,21 +304,21 @@ none</code>
<code type="none">
> Map = #{a => 1, b => 2, c => 3}.
#{a => 1,b => 2,c => 3}
-> Iter = maps:iterator(Map).
-[{a,1},{b,2},{c,3}]
-> {_, _, Iter1} = maps:next(Iter).
-{a,1,[{b,2},{c,3}]}
-> {_, _, Iter2} = maps:next(Iter1).
-{b,2,[{c,3}]}
-> {_, _, Iter3} = maps:next(Iter2).
-{c,3,[]}
-> maps:next(Iter3).
+> I = maps:iterator(Map), ok.
+ok
+> {K1, V1, I1} = maps:next(I), {K1, V1}.
+{a,1}
+> {K2, V2, I2} = maps:next(I1), {K2, V2}.
+{b,2}
+> {K3, V3, I3} = maps:next(I2), {K3, V3}.
+{c,3}
+> maps:next(I3).
none</code>
</desc>
</func>
<func>
- <name name="put" arity="3"/>
+ <name name="put" arity="3" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Associates <c><anno>Key</anno></c> with value
@@ -342,7 +342,7 @@ none</code>
</func>
<func>
- <name name="remove" arity="2"/>
+ <name name="remove" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Removes the <c><anno>Key</anno></c>, if it exists, and its
@@ -362,7 +362,7 @@ none</code>
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns the number of key-value associations in
@@ -376,7 +376,7 @@ none</code>
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>The function removes the <c><anno>Key</anno></c>, if it
@@ -401,7 +401,7 @@ error</code>
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a list of pairs representing the key-value associations of
@@ -418,7 +418,7 @@ error</code>
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>If <c><anno>Key</anno></c> exists in <c><anno>Map1</anno></c>, the
@@ -438,7 +438,7 @@ error</code>
</func>
<func>
- <name name="update_with" arity="3"/>
+ <name name="update_with" arity="3" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Update a value in a <c><anno>Map1</anno></c> associated
@@ -457,7 +457,7 @@ error</code>
</func>
<func>
- <name name="update_with" arity="4"/>
+ <name name="update_with" arity="4" since="OTP 19.0"/>
<fsummary></fsummary>
<desc>
<p>Update a value in a <c><anno>Map1</anno></c> associated
@@ -477,7 +477,7 @@ error</code>
</func>
<func>
- <name name="values" arity="1"/>
+ <name name="values" arity="1" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a complete list of values, in arbitrary order, contained in
@@ -493,7 +493,7 @@ error</code>
</func>
<func>
- <name name="with" arity="2"/>
+ <name name="with" arity="2" since="OTP 17.3"/>
<fsummary></fsummary>
<desc>
<p>Returns a new map <c><anno>Map2</anno></c> with the keys <c>K1</c>
@@ -510,7 +510,7 @@ error</code>
</func>
<func>
- <name name="without" arity="2"/>
+ <name name="without" arity="2" since="OTP 17.0"/>
<fsummary></fsummary>
<desc>
<p>Returns a new map <c><anno>Map2</anno></c> without keys <c>K1</c>
diff --git a/lib/stdlib/doc/src/math.xml b/lib/stdlib/doc/src/math.xml
index b4f096217a..d89310e2c8 100644
--- a/lib/stdlib/doc/src/math.xml
+++ b/lib/stdlib/doc/src/math.xml
@@ -34,7 +34,7 @@
<rev>B</rev>
<file>math.xml</file>
</header>
- <module>math</module>
+ <module since="">math</module>
<modulesummary>Mathematical functions.</modulesummary>
<description>
<p>This module provides an interface to a number of mathematical
@@ -50,28 +50,28 @@
<funcs>
<func>
- <name name="acos" arity="1"/>
- <name name="acosh" arity="1"/>
- <name name="asin" arity="1"/>
- <name name="asinh" arity="1"/>
- <name name="atan" arity="1"/>
- <name name="atan2" arity="2"/>
- <name name="atanh" arity="1"/>
- <name name="ceil" arity="1"/>
- <name name="cos" arity="1"/>
- <name name="cosh" arity="1"/>
- <name name="exp" arity="1"/>
- <name name="floor" arity="1"/>
- <name name="fmod" arity="2"/>
- <name name="log" arity="1"/>
- <name name="log10" arity="1"/>
- <name name="log2" arity="1"/>
- <name name="pow" arity="2"/>
- <name name="sin" arity="1"/>
- <name name="sinh" arity="1"/>
- <name name="sqrt" arity="1"/>
- <name name="tan" arity="1"/>
- <name name="tanh" arity="1"/>
+ <name name="acos" arity="1" since=""/>
+ <name name="acosh" arity="1" since=""/>
+ <name name="asin" arity="1" since=""/>
+ <name name="asinh" arity="1" since=""/>
+ <name name="atan" arity="1" since=""/>
+ <name name="atan2" arity="2" since=""/>
+ <name name="atanh" arity="1" since=""/>
+ <name name="ceil" arity="1" since="OTP 20.0"/>
+ <name name="cos" arity="1" since=""/>
+ <name name="cosh" arity="1" since=""/>
+ <name name="exp" arity="1" since=""/>
+ <name name="floor" arity="1" since="OTP 20.0"/>
+ <name name="fmod" arity="2" since="OTP 20.0"/>
+ <name name="log" arity="1" since=""/>
+ <name name="log10" arity="1" since=""/>
+ <name name="log2" arity="1" since="OTP 18.0"/>
+ <name name="pow" arity="2" since=""/>
+ <name name="sin" arity="1" since=""/>
+ <name name="sinh" arity="1" since=""/>
+ <name name="sqrt" arity="1" since=""/>
+ <name name="tan" arity="1" since=""/>
+ <name name="tanh" arity="1" since=""/>
<fsummary>Diverse math functions.</fsummary>
<type variable="X" name_i="6"/>
<type variable="Y" name_i="6"/>
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="erf" arity="1"/>
+ <name name="erf" arity="1" since=""/>
<fsummary>Error function.</fsummary>
<desc>
<p>Returns the error function of <c><anno>X</anno></c>, where:</p>
@@ -92,7 +92,7 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre>
</func>
<func>
- <name name="erfc" arity="1"/>
+ <name name="erfc" arity="1" since=""/>
<fsummary>Another error function.</fsummary>
<desc>
<p><c>erfc(X)</c> returns <c>1.0</c> - <c>erf(X)</c>, computed by
@@ -101,7 +101,7 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre>
</func>
<func>
- <name name="pi" arity="0"/>
+ <name name="pi" arity="0" since=""/>
<fsummary>A useful number.</fsummary>
<desc>
<p>A useful number.</p>
diff --git a/lib/stdlib/doc/src/ms_transform.xml b/lib/stdlib/doc/src/ms_transform.xml
index 0a05fa37c5..65cc150507 100644
--- a/lib/stdlib/doc/src/ms_transform.xml
+++ b/lib/stdlib/doc/src/ms_transform.xml
@@ -32,7 +32,7 @@
<rev>C</rev>
<file>ms_transform.xml</file>
</header>
- <module>ms_transform</module>
+ <module since="">ms_transform</module>
<modulesummary>A parse transformation that translates fun syntax into match
specifications.</modulesummary>
<description>
@@ -731,7 +731,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
<funcs>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Error formatting function as required by the parse transformation interface.</fsummary>
<desc>
<p>Takes an error code returned by one of the other functions
@@ -741,7 +741,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
</func>
<func>
- <name name="parse_transform" arity="2"/>
+ <name name="parse_transform" arity="2" since=""/>
<fsummary>Transforms Erlang abstract format containing calls to
ets/dbg:fun2ms/1 into literal match specifications.</fsummary>
<type_desc variable="Options">Option list, required but not used.
@@ -762,7 +762,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code>
</func>
<func>
- <name name="transform_from_shell" arity="3"/>
+ <name name="transform_from_shell" arity="3" since=""/>
<fsummary>Used when transforming funs created in the shell into
match_specifications.</fsummary>
<type_desc variable="BoundEnvironment">List of variable bindings in the
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 64b97fad7f..a682811c6d 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,85 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Document <c>bit_size</c> in match specifications and
+ allow it in <c>ets:fun2ms</c>.</p>
+ <p>
+ Own Id: OTP-15343 Aux Id: PR-1962 </p>
+ </item>
+ <item>
+ <p>The <c>beam()</c> type in <c>beam_lib</c> is defined
+ as <c>module() | file:filename() | binary()</c>. The
+ <c>module()</c> is misleading. Giving the module name as
+ an atom will only work if the BEAM file is in a current
+ directory.</p>
+ <p>To avoid confusion, <c>module()</c> has been removed
+ from the type. That means that there will be a Dialyzer
+ warning for code that call <c>beam_lib</c> with an atom
+ as filename, but the calls will still work.</p>
+ <p>
+ Own Id: OTP-15378 Aux Id: ERL-696 </p>
+ </item>
+ <item>
+ <p>
+ <c>unicode_util</c> crashed on certain emoji grapheme
+ clusters in binary strings.</p>
+ <p>
+ Own Id: OTP-15428 Aux Id: ERL-777 </p>
+ </item>
+ <item>
+ <p>When an external fun was used, warnings for unused
+ variables could be suppressed.</p>
+ <p>
+ Own Id: OTP-15437 Aux Id: ERL-762 </p>
+ </item>
+ <item>
+ <p>
+ Fix reduction count in lists:member/2</p>
+ <p>
+ Own Id: OTP-15474 Aux Id: ERIERL-229 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>When specified, the <c>+{source,Name}</c> option will
+ now override the actual file name in stack traces,
+ instead of only affecting the return value of
+ <c>Mod:module_info()</c>.</p>
+ <p>The <c>+deterministic</c> flag will also affect stack
+ traces now, omitting all path information except the file
+ name, fixing a long-standing issue where deterministic
+ builds required deterministic paths.</p>
+ <p>
+ Own Id: OTP-15245 Aux Id: ERL-706 </p>
+ </item>
+ <item>
+ <p>List subtraction (The <c>--</c> operator) will now
+ yield properly on large inputs.</p>
+ <p>
+ Own Id: OTP-15371</p>
+ </item>
+ <item>
+ <p>
+ <c>calendar:system_time_to_rfc3339/1,2</c> no longer
+ remove trailing zeros from fractions.</p>
+ <p>
+ Own Id: OTP-15464</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/orddict.xml b/lib/stdlib/doc/src/orddict.xml
index 26bbf499c6..27ccccee7e 100644
--- a/lib/stdlib/doc/src/orddict.xml
+++ b/lib/stdlib/doc/src/orddict.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>orddict.xml</file>
</header>
- <module>orddict</module>
+ <module since="">orddict</module>
<modulesummary>Key-value dictionary as ordered list.</modulesummary>
<description>
<p>This module provides a <c>Key</c>-<c>Value</c> dictionary.
@@ -61,7 +61,7 @@
<funcs>
<func>
- <name name="append" arity="3"/>
+ <name name="append" arity="3" since=""/>
<fsummary>Append a value to keys in a dictionary.</fsummary>
<desc>
<p>Appends a new <c><anno>Value</anno></c> to the current list
@@ -73,7 +73,7 @@
</func>
<func>
- <name name="append_list" arity="3"/>
+ <name name="append_list" arity="3" since=""/>
<fsummary>Append new values to keys in a dictionary.</fsummary>
<desc>
<p>Appends a list of values <c><anno>ValList</anno></c> to
@@ -85,7 +85,7 @@
</func>
<func>
- <name name="erase" arity="2"/>
+ <name name="erase" arity="2" since=""/>
<fsummary>Erase a key from a dictionary.</fsummary>
<desc>
<p>Erases all items with a specified key from a dictionary.</p>
@@ -93,7 +93,7 @@
</func>
<func>
- <name name="fetch" arity="2"/>
+ <name name="fetch" arity="2" since=""/>
<fsummary>Look up values in a dictionary.</fsummary>
<desc>
<p>Returns the value associated with <c><anno>Key</anno></c>
@@ -105,7 +105,7 @@
</func>
<func>
- <name name="fetch_keys" arity="1"/>
+ <name name="fetch_keys" arity="1" since=""/>
<fsummary>Return all keys in a dictionary.</fsummary>
<desc>
<p>Returns a list of all keys in a dictionary.</p>
@@ -113,7 +113,7 @@
</func>
<func>
- <name name="take" arity="2"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
<fsummary>Return value and new dictionary without element with this value.</fsummary>
<desc>
<p>This function returns value from dictionary and new dictionary without this value.
@@ -122,7 +122,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Select elements that satisfy a predicate.</fsummary>
<desc>
<p><c><anno>Orddict2</anno></c> is a dictionary of all keys and values
@@ -133,7 +133,7 @@
</func>
<func>
- <name name="find" arity="2"/>
+ <name name="find" arity="2" since=""/>
<fsummary>Search for a key in a dictionary.</fsummary>
<desc>
<p>Searches for a key in a dictionary. Returns
@@ -145,7 +145,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
@@ -157,7 +157,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list of pairs to a dictionary.</fsummary>
<desc>
<p>Converts the <c><anno>Key</anno></c>-<c><anno>Value</anno></c> list
@@ -166,7 +166,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 17.0"/>
<fsummary>Return true if the dictionary is empty.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Orddict</anno></c> has no elements,
@@ -175,7 +175,7 @@
</func>
<func>
- <name name="is_key" arity="2"/>
+ <name name="is_key" arity="2" since=""/>
<fsummary>Test if a key is in a dictionary.</fsummary>
<desc>
<p>Tests if <c><anno>Key</anno></c> is contained in
@@ -184,7 +184,7 @@
</func>
<func>
- <name name="map" arity="2"/>
+ <name name="map" arity="2" since=""/>
<fsummary>Map a function over a dictionary.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno></c> on successive keys and values of
@@ -193,7 +193,7 @@
</func>
<func>
- <name name="merge" arity="3"/>
+ <name name="merge" arity="3" since=""/>
<fsummary>Merge two dictionaries.</fsummary>
<desc>
<p>Merges two dictionaries, <c><anno>Orddict1</anno></c> and
@@ -212,7 +212,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create a dictionary.</fsummary>
<desc>
<p>Creates a new dictionary.</p>
@@ -220,7 +220,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in an ordered dictionary.
</fsummary>
<desc>
@@ -229,7 +229,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="store" arity="3"/>
+ <name name="store" arity="3" since=""/>
<fsummary>Store a value in a dictionary.</fsummary>
<desc>
<p>Stores a <c><anno>Key</anno></c>-<c><anno>Value</anno></c> pair in a
@@ -240,7 +240,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a dictionary to a list of pairs.</fsummary>
<desc>
<p>Converts a dictionary to a list representation.</p>
@@ -248,7 +248,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="3"/>
+ <name name="update" arity="3" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c>
@@ -258,7 +258,7 @@ merge(Fun, D1, D2) ->
</func>
<func>
- <name name="update" arity="4"/>
+ <name name="update" arity="4" since=""/>
<fsummary>Update a value in a dictionary.</fsummary>
<desc>
<p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c>
@@ -273,7 +273,7 @@ append(Key, Val, D) ->
</func>
<func>
- <name name="update_counter" arity="3"/>
+ <name name="update_counter" arity="3" since=""/>
<fsummary>Increment a value in a dictionary.</fsummary>
<desc>
<p>Adds <c><anno>Increment</anno></c> to the value associated with
diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml
index 11f98c8fb7..fbe334c009 100644
--- a/lib/stdlib/doc/src/ordsets.xml
+++ b/lib/stdlib/doc/src/ordsets.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>ordsets.xml</file>
</header>
- <module>ordsets</module>
+ <module since="">ordsets</module>
<modulesummary>Functions for manipulating sets as ordered lists.
</modulesummary>
<description>
@@ -60,7 +60,7 @@
<funcs>
<func>
- <name name="add_element" arity="2"/>
+ <name name="add_element" arity="2" since=""/>
<fsummary>Add an element to an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns a new ordered set formed from <c><anno>Ordset1</anno></c>
@@ -69,7 +69,7 @@
</func>
<func>
- <name name="del_element" arity="2"/>
+ <name name="del_element" arity="2" since=""/>
<fsummary>Remove an element from an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns <c><anno>Ordset1</anno></c>, but with
@@ -78,7 +78,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter set elements.</fsummary>
<desc>
<p>Filters elements in <c><anno>Ordset1</anno></c> with boolean function
@@ -87,7 +87,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold over set elements.</fsummary>
<desc>
<p>Folds <c><anno>Function</anno></c> over every element in
@@ -97,7 +97,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list into an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns an ordered set of the elements in <c><anno>List</anno></c>.
@@ -106,7 +106,7 @@
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a list of <c>Ordsets</c></fsummary>
<desc>
<p>Returns the intersection of the non-empty list of sets.</p>
@@ -114,7 +114,7 @@
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns the intersection of <c><anno>Ordset1</anno></c> and
@@ -123,7 +123,7 @@
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Check whether two <c>Ordsets</c> are disjoint.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Ordset1</anno></c> and
@@ -133,7 +133,7 @@
</func>
<func>
- <name name="is_element" arity="2"/>
+ <name name="is_element" arity="2" since=""/>
<fsummary>Test for membership of an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -142,7 +142,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 21.0"/>
<fsummary>Test for empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an empty set,
@@ -151,7 +151,7 @@
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for an <c>Ordset</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an ordered set
@@ -160,7 +160,7 @@
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test for subset.</fsummary>
<desc>
<p>Returns <c>true</c> when every element of <c><anno>Ordset1</anno></c>
@@ -170,7 +170,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty ordered set.</p>
@@ -178,7 +178,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a set.</fsummary>
<desc>
<p>Returns the number of elements in <c><anno>Ordset</anno></c>.</p>
@@ -186,7 +186,7 @@
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Return the difference of two <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Ordset1</anno></c> that are not
@@ -195,7 +195,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert an <c>Ordset</c> into a list.</fsummary>
<desc>
<p>Returns the elements of <c><anno>Ordset</anno></c> as a list.</p>
@@ -203,7 +203,7 @@
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a list of <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of the list of sets.</p>
@@ -211,7 +211,7 @@
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two <c>Ordsets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of <c><anno>Ordset1</anno></c> and
diff --git a/lib/stdlib/doc/src/pool.xml b/lib/stdlib/doc/src/pool.xml
index 05d12ade28..675ee08bfb 100644
--- a/lib/stdlib/doc/src/pool.xml
+++ b/lib/stdlib/doc/src/pool.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>pool</module>
+ <module since="">pool</module>
<modulesummary>Load distribution facility.</modulesummary>
<description>
<p>This module can be used to run a set of Erlang nodes as a pool
@@ -54,7 +54,7 @@
<funcs>
<func>
- <name name="attach" arity="1"/>
+ <name name="attach" arity="1" since=""/>
<fsummary>Ensure that a pool master is running.</fsummary>
<desc>
<p>Ensures that a pool master is running and includes
@@ -63,7 +63,7 @@
</func>
<func>
- <name name="get_node" arity="0"/>
+ <name name="get_node" arity="0" since=""/>
<fsummary>Return the node with the expected lowest future load.</fsummary>
<desc>
<p>Returns the node with the expected lowest future load.</p>
@@ -71,7 +71,7 @@
</func>
<func>
- <name name="get_nodes" arity="0"/>
+ <name name="get_nodes" arity="0" since=""/>
<fsummary>Return a list of the current member nodes of the pool.
</fsummary>
<desc>
@@ -80,7 +80,7 @@
</func>
<func>
- <name name="pspawn" arity="3"/>
+ <name name="pspawn" arity="3" since=""/>
<fsummary>Spawn a process on the pool node with expected lowest future
load.</fsummary>
<desc>
@@ -90,7 +90,7 @@
</func>
<func>
- <name name="pspawn_link" arity="3"/>
+ <name name="pspawn_link" arity="3" since=""/>
<fsummary>Spawn and link to a process on the pool node with expected
lowest future load.</fsummary>
<desc>
@@ -100,8 +100,8 @@
</func>
<func>
- <name name="start" arity="1"/>
- <name name="start" arity="2"/>
+ <name name="start" arity="1" since=""/>
+ <name name="start" arity="2" since=""/>
<fsummary>>Start a new pool.</fsummary>
<desc>
<p>Starts a new pool. The file <c>.hosts.erlang</c> is read to
@@ -122,7 +122,7 @@
</func>
<func>
- <name name="stop" arity="0"/>
+ <name name="stop" arity="0" since=""/>
<fsummary>Stop the pool and kill all the slave nodes.</fsummary>
<desc>
<p>Stops the pool and kills all the slave nodes.</p>
diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml
index b85fab67d5..aeb9f48735 100644
--- a/lib/stdlib/doc/src/proc_lib.xml
+++ b/lib/stdlib/doc/src/proc_lib.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>proc_lib</module>
+ <module since="">proc_lib</module>
<modulesummary>Functions for asynchronous and synchronous start of processes
adhering to the OTP design principles.</modulesummary>
<description>
@@ -102,7 +102,7 @@
<funcs>
<func>
- <name name="format" arity="1"/>
+ <name name="format" arity="1" since=""/>
<fsummary>Format a crash report.</fsummary>
<desc>
<p>Equivalent to <seealso marker="#format/2">
@@ -111,7 +111,7 @@
</func>
<func>
- <name name="format" arity="2"/>
+ <name name="format" arity="2" since="OTP R16B"/>
<fsummary>Format a crash report.</fsummary>
<desc>
<note>
@@ -138,7 +138,7 @@
</func>
<func>
- <name name="format" arity="3"/>
+ <name name="format" arity="3" since="OTP 18.1"/>
<fsummary>Format a crash report.</fsummary>
<desc>
<note>
@@ -162,7 +162,7 @@
</func>
<func>
- <name name="hibernate" arity="3"/>
+ <name name="hibernate" arity="3" since=""/>
<fsummary>Hibernate a process until a message is sent to it.</fsummary>
<desc>
<p>This function does the same as (and does call) the
@@ -176,8 +176,8 @@
</func>
<func>
- <name name="init_ack" arity="1"/>
- <name name="init_ack" arity="2"/>
+ <name name="init_ack" arity="1" since=""/>
+ <name name="init_ack" arity="2" since=""/>
<fsummary>Used by a process when it has started.</fsummary>
<desc>
<p>This function must be used by a process that has been started by
@@ -214,7 +214,7 @@ init(Parent) ->
</func>
<func>
- <name name="initial_call" arity="1"/>
+ <name name="initial_call" arity="1" since=""/>
<fsummary>Extract the initial call of a <c>proc_lib</c>spawned process.
</fsummary>
<desc>
@@ -244,10 +244,10 @@ init(Parent) ->
</func>
<func>
- <name name="spawn" arity="1"/>
- <name name="spawn" arity="2"/>
- <name name="spawn" arity="3"/>
- <name name="spawn" arity="4"/>
+ <name name="spawn" arity="1" since=""/>
+ <name name="spawn" arity="2" since=""/>
+ <name name="spawn" arity="3" since=""/>
+ <name name="spawn" arity="4" since=""/>
<fsummary>Spawn a new process.</fsummary>
<type variable="Node"/>
<type variable="Fun" name_i="1"/>
@@ -262,10 +262,10 @@ init(Parent) ->
</func>
<func>
- <name name="spawn_link" arity="1"/>
- <name name="spawn_link" arity="2"/>
- <name name="spawn_link" arity="3"/>
- <name name="spawn_link" arity="4"/>
+ <name name="spawn_link" arity="1" since=""/>
+ <name name="spawn_link" arity="2" since=""/>
+ <name name="spawn_link" arity="3" since=""/>
+ <name name="spawn_link" arity="4" since=""/>
<fsummary>Spawn and link to a new process.</fsummary>
<type variable="Node"/>
<type variable="Fun" name_i="1"/>
@@ -281,10 +281,10 @@ init(Parent) ->
</func>
<func>
- <name name="spawn_opt" arity="2"/>
- <name name="spawn_opt" arity="3"/>
- <name name="spawn_opt" arity="4"/>
- <name name="spawn_opt" arity="5"/>
+ <name name="spawn_opt" arity="2" since=""/>
+ <name name="spawn_opt" arity="3" since=""/>
+ <name name="spawn_opt" arity="4" since=""/>
+ <name name="spawn_opt" arity="5" since=""/>
<fsummary>Spawn a new process with specified options.</fsummary>
<type variable="Node"/>
<type variable="Fun" name_i="1"/>
@@ -306,12 +306,12 @@ init(Parent) ->
</func>
<func>
- <name name="start" arity="3"/>
- <name name="start" arity="4"/>
- <name name="start" arity="5"/>
- <name name="start_link" arity="3"/>
- <name name="start_link" arity="4"/>
- <name name="start_link" arity="5"/>
+ <name name="start" arity="3" since=""/>
+ <name name="start" arity="4" since=""/>
+ <name name="start" arity="5" since=""/>
+ <name name="start_link" arity="3" since=""/>
+ <name name="start_link" arity="4" since=""/>
+ <name name="start_link" arity="5" since=""/>
<fsummary>Start a new process synchronously.</fsummary>
<desc>
<p>Starts a new process synchronously. Spawns the process and
@@ -341,7 +341,7 @@ init(Parent) ->
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since="OTP 18.0"/>
<fsummary>Terminate a process synchronously.</fsummary>
<type variable="Process"/>
<desc>
@@ -351,7 +351,7 @@ init(Parent) ->
</func>
<func>
- <name name="stop" arity="3"/>
+ <name name="stop" arity="3" since="OTP 18.0"/>
<fsummary>Terminate a process synchronously.</fsummary>
<type variable="Process"/>
<type variable="Reason"/>
@@ -375,7 +375,7 @@ init(Parent) ->
</func>
<func>
- <name name="translate_initial_call" arity="1"/>
+ <name name="translate_initial_call" arity="1" since=""/>
<fsummary>Extract and translate the initial call of a
<c>proc_lib</c>spawned process.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/proplists.xml b/lib/stdlib/doc/src/proplists.xml
index f9a54bf804..9d7eb55a7e 100644
--- a/lib/stdlib/doc/src/proplists.xml
+++ b/lib/stdlib/doc/src/proplists.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>proplists.xml</file>
</header>
- <module>proplists</module>
+ <module since="">proplists</module>
<modulesummary>Support functions for property lists.</modulesummary>
<description>
<p>Property lists are ordinary lists containing entries in the form
@@ -61,7 +61,7 @@
<funcs>
<func>
- <name name="append_values" arity="2"/>
+ <name name="append_values" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Similar to
@@ -79,7 +79,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
</func>
<func>
- <name name="compact" arity="1"/>
+ <name name="compact" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Minimizes the representation of all entries in the list. This is
@@ -91,7 +91,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
</func>
<func>
- <name name="delete" arity="2"/>
+ <name name="delete" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Deletes all entries associated with <c><anno>Key</anno></c> from
@@ -100,7 +100,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code>
</func>
<func>
- <name name="expand" arity="2"/>
+ <name name="expand" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Expands particular properties to corresponding sets of
@@ -133,7 +133,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_all_values" arity="2"/>
+ <name name="get_all_values" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Similar to
@@ -145,7 +145,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_bool" arity="2"/>
+ <name name="get_bool" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the value of a boolean key/value option. If
@@ -159,7 +159,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_keys" arity="1"/>
+ <name name="get_keys" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns an unordered list of the keys used in
@@ -168,7 +168,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_value" arity="2"/>
+ <name name="get_value" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Equivalent to
@@ -177,7 +177,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="get_value" arity="3"/>
+ <name name="get_value" arity="3" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the value of a simple key/value property in
@@ -194,7 +194,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="is_defined" arity="2"/>
+ <name name="is_defined" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>List</anno></c> contains at least
@@ -204,7 +204,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="lookup" arity="2"/>
+ <name name="lookup" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the first entry associated with <c><anno>Key</anno></c> in
@@ -219,7 +219,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="lookup_all" arity="2"/>
+ <name name="lookup_all" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Returns the list of all entries associated with
@@ -231,7 +231,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="normalize" arity="2"/>
+ <name name="normalize" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Passes <c><anno>ListIn</anno></c> through a sequence of
@@ -263,7 +263,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="property" arity="1"/>
+ <name name="property" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Creates a normal form (minimal) representation of a property. If
@@ -276,7 +276,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="property" arity="2"/>
+ <name name="property" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Creates a normal form (minimal) representation of a simple key/value
@@ -289,7 +289,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code>
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Partitions <c><anno>List</anno></c> into a list of sublists and a
@@ -310,7 +310,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
</func>
<func>
- <name name="substitute_aliases" arity="2"/>
+ <name name="substitute_aliases" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Substitutes keys of properties. For each entry in
@@ -332,7 +332,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
</func>
<func>
- <name name="substitute_negations" arity="2"/>
+ <name name="substitute_negations" arity="2" since=""/>
<fsummary></fsummary>
<desc>
<p>Substitutes keys of boolean-valued properties and
@@ -360,7 +360,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code>
</func>
<func>
- <name name="unfold" arity="1"/>
+ <name name="unfold" arity="1" since=""/>
<fsummary></fsummary>
<desc>
<p>Unfolds all occurrences of atoms in <c><anno>ListIn</anno></c> to
diff --git a/lib/stdlib/doc/src/qlc.xml b/lib/stdlib/doc/src/qlc.xml
index fe14a6334c..fe60c2e9bb 100644
--- a/lib/stdlib/doc/src/qlc.xml
+++ b/lib/stdlib/doc/src/qlc.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>qlc.xml</file>
</header>
- <module>qlc</module>
+ <module since="">qlc</module>
<modulesummary>Query interface to Mnesia, ETS, Dets, and so on.
</modulesummary>
<description>
@@ -720,7 +720,7 @@ ets:match_spec_run(ets:lookup(86033, {2,2}),
<funcs>
<func>
- <name name="append" arity="1"/>
+ <name name="append" arity="1" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -731,7 +731,7 @@ ets:match_spec_run(ets:lookup(86033, {2,2}),
</func>
<func>
- <name name="append" arity="2"/>
+ <name name="append" arity="2" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -744,8 +744,8 @@ ets:match_spec_run(ets:lookup(86033, {2,2}),
</func>
<func>
- <name name="cursor" arity="1"/>
- <name name="cursor" arity="2"/>
+ <name name="cursor" arity="1" since=""/>
+ <name name="cursor" arity="2" since=""/>
<fsummary>Create a query cursor.</fsummary>
<desc>
<p>Creates a query cursor and
@@ -777,7 +777,7 @@ ok</pre>
</func>
<func>
- <name name="delete_cursor" arity="1"/>
+ <name name="delete_cursor" arity="1" since=""/>
<fsummary>Delete a query cursor.</fsummary>
<desc>
<p>Deletes a query cursor. Only the owner of the cursor can
@@ -786,10 +786,10 @@ ok</pre>
</func>
<func>
- <name name="e" arity="1"/>
- <name name="e" arity="2"/>
- <name name="eval" arity="1"/>
- <name name="eval" arity="2"/>
+ <name name="e" arity="1" since=""/>
+ <name name="e" arity="2" since=""/>
+ <name name="eval" arity="1" since=""/>
+ <name name="eval" arity="2" since=""/>
<fsummary>Return all answers to a query.</fsummary>
<desc>
<p>Evaluates a query handle in the
@@ -805,8 +805,8 @@ ok</pre>
</func>
<func>
- <name name="fold" arity="3"/>
- <name name="fold" arity="4"/>
+ <name name="fold" arity="3" since=""/>
+ <name name="fold" arity="4" since=""/>
<fsummary>Fold a function over the answers to a query.</fsummary>
<desc>
<p>Calls <c><anno>Function</anno></c> on successive answers to
@@ -830,7 +830,7 @@ ok</pre>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Return an English description of a an error tuple.</fsummary>
<desc>
<p>Returns a descriptive string in English of an error tuple
@@ -841,8 +841,8 @@ ok</pre>
</func>
<func>
- <name name="info" arity="1"/>
- <name name="info" arity="2"/>
+ <name name="info" arity="1" since=""/>
+ <name name="info" arity="2" since=""/>
<fsummary>Return code describing a query handle.</fsummary>
<desc>
<p>Returns information about a
@@ -946,8 +946,8 @@ end</pre>
</func>
<func>
- <name name="keysort" arity="2"/>
- <name name="keysort" arity="3"/>
+ <name name="keysort" arity="2" since=""/>
+ <name name="keysort" arity="3" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -967,8 +967,8 @@ end</pre>
</func>
<func>
- <name name="next_answers" arity="1"/>
- <name name="next_answers" arity="2"/>
+ <name name="next_answers" arity="1" since=""/>
+ <name name="next_answers" arity="2" since=""/>
<fsummary>Return some or all answers to a query.</fsummary>
<desc>
<p>Returns some or all of the remaining answers to a query
@@ -983,8 +983,8 @@ end</pre>
</func>
<func>
- <name name="q" arity="1"/>
- <name name="q" arity="2"/>
+ <name name="q" arity="1" since=""/>
+ <name name="q" arity="2" since=""/>
<fsummary>Return a handle for a query list comprehension.</fsummary>
<desc>
<p>Returns a query handle for a QLC.
@@ -1188,8 +1188,8 @@ ets:match_spec_run(
</func>
<func>
- <name name="sort" arity="1"/>
- <name name="sort" arity="2"/>
+ <name name="sort" arity="1" since=""/>
+ <name name="sort" arity="2" since=""/>
<fsummary>Return a query handle.</fsummary>
<desc>
<p>Returns a query handle. When evaluating query handle
@@ -1208,9 +1208,9 @@ ets:match_spec_run(
</func>
<func>
- <name name="string_to_handle" arity="1"/>
- <name name="string_to_handle" arity="2"/>
- <name name="string_to_handle" arity="3"/>
+ <name name="string_to_handle" arity="1" since=""/>
+ <name name="string_to_handle" arity="2" since=""/>
+ <name name="string_to_handle" arity="3" since=""/>
<fsummary>Return a handle for a query list comprehension.</fsummary>
<desc>
<p>A string version of <seealso marker="#q/1"><c>q/1,2</c></seealso>.
@@ -1238,7 +1238,7 @@ ets:match_spec_run(
</func>
<func>
- <name name="table" arity="2"/>
+ <name name="table" arity="2" since=""/>
<fsummary>Return a query handle for a table.</fsummary>
<desc>
<p>Returns a query handle for a QLC table.
diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml
index 9f3aff03a3..83a8afea81 100644
--- a/lib/stdlib/doc/src/queue.xml
+++ b/lib/stdlib/doc/src/queue.xml
@@ -32,7 +32,7 @@
<rev>B</rev>
<file>queue.xml</file>
</header>
- <module>queue</module>
+ <module since="">queue</module>
<modulesummary>Abstract data type for FIFO queues.</modulesummary>
<description>
<p>This module provides (double-ended) FIFO queues
@@ -113,7 +113,7 @@
<funcs>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of calling
@@ -134,7 +134,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list to a queue.</fsummary>
<desc>
<p>Returns a queue containing the items in <c><anno>L</anno></c> in the
@@ -144,7 +144,7 @@
</func>
<func>
- <name name="in" arity="2"/>
+ <name name="in" arity="2" since=""/>
<fsummary>Insert an item at the rear of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> at the rear of queue
@@ -154,7 +154,7 @@
</func>
<func>
- <name name="in_r" arity="2"/>
+ <name name="in_r" arity="2" since=""/>
<fsummary>Insert an item at the front of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> at the front of queue
@@ -164,7 +164,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since=""/>
<fsummary>Test if a queue is empty.</fsummary>
<desc>
<p>Tests if <c><anno>Q</anno></c> is empty and returns <c>true</c> if
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="is_queue" arity="1"/>
+ <name name="is_queue" arity="1" since=""/>
<fsummary>Test if a term is a queue.</fsummary>
<desc>
<p>Tests if <c><anno>Term</anno></c> is a queue and returns <c>true</c>
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join two queues.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q3</anno></c> that is the result of joining
@@ -192,7 +192,7 @@
</func>
<func>
- <name name="len" arity="1"/>
+ <name name="len" arity="1" since=""/>
<fsummary>Get the length of a queue.</fsummary>
<desc>
<p>Calculates and returns the length of queue <c><anno>Q</anno></c>.</p>
@@ -200,7 +200,7 @@
</func>
<func>
- <name name="member" arity="2"/>
+ <name name="member" arity="2" since=""/>
<fsummary>Test if an item is in a queue.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Item</anno></c> matches some element
@@ -209,7 +209,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Create an empty queue.</fsummary>
<desc>
<p>Returns an empty queue.</p>
@@ -217,7 +217,7 @@
</func>
<func>
- <name name="out" arity="1"/>
+ <name name="out" arity="1" since=""/>
<fsummary>Remove the front item from a queue.</fsummary>
<desc>
<p>Removes the item at the front of queue <c><anno>Q1</anno></c>.
@@ -230,7 +230,7 @@
</func>
<func>
- <name name="out_r" arity="1"/>
+ <name name="out_r" arity="1" since=""/>
<fsummary>Remove the rear item from a queue.</fsummary>
<desc>
<p>Removes the item at the rear of queue <c><anno>Q1</anno></c>.
@@ -242,7 +242,7 @@
</func>
<func>
- <name name="reverse" arity="1"/>
+ <name name="reverse" arity="1" since=""/>
<fsummary>Reverse a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> containing the items of
@@ -251,7 +251,7 @@
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary>Split a queue in two.</fsummary>
<desc>
<p>Splits <c><anno>Q1</anno></c> in two. The <c><anno>N</anno></c>
@@ -261,7 +261,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a queue to a list.</fsummary>
<desc>
<p>Returns a list of the items in the queue in the same order;
@@ -276,7 +276,7 @@
<funcs>
<func>
- <name name="drop" arity="1"/>
+ <name name="drop" arity="1" since=""/>
<fsummary>Remove the front item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -286,7 +286,7 @@
</func>
<func>
- <name name="drop_r" arity="1"/>
+ <name name="drop_r" arity="1" since=""/>
<fsummary>Remove the rear item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -296,7 +296,7 @@
</func>
<func>
- <name name="get" arity="1"/>
+ <name name="get" arity="1" since=""/>
<fsummary>Return the front item of a queue.</fsummary>
<desc>
<p>Returns <c><anno>Item</anno></c> at the front of queue
@@ -306,7 +306,7 @@
</func>
<func>
- <name name="get_r" arity="1"/>
+ <name name="get_r" arity="1" since=""/>
<fsummary>Return the rear item of a queue.</fsummary>
<desc>
<p>Returns <c><anno>Item</anno></c> at the rear of queue
@@ -316,7 +316,7 @@
</func>
<func>
- <name name="peek" arity="1"/>
+ <name name="peek" arity="1" since=""/>
<fsummary>Return the front item of a queue.</fsummary>
<desc>
<p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where
@@ -326,7 +326,7 @@
</func>
<func>
- <name name="peek_r" arity="1"/>
+ <name name="peek_r" arity="1" since=""/>
<fsummary>Return the rear item of a queue.</fsummary>
<desc>
<p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where
@@ -342,7 +342,7 @@
<funcs>
<func>
- <name name="cons" arity="2"/>
+ <name name="cons" arity="2" since=""/>
<fsummary>Insert an item at the head of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> at the head of queue
@@ -352,7 +352,7 @@
</func>
<func>
- <name name="daeh" arity="1"/>
+ <name name="daeh" arity="1" since=""/>
<fsummary>Return the tail item of a queue.</fsummary>
<desc>
<p>Returns the tail item of queue <c><anno>Q</anno></c>.</p>
@@ -361,7 +361,7 @@
</func>
<func>
- <name name="head" arity="1"/>
+ <name name="head" arity="1" since=""/>
<fsummary>Return the item at the head of a queue.</fsummary>
<desc>
<p>Returns <c><anno>Item</anno></c> from the head of queue
@@ -371,7 +371,7 @@
</func>
<func>
- <name name="init" arity="1"/>
+ <name name="init" arity="1" since=""/>
<fsummary>Remove the tail item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -381,7 +381,7 @@
</func>
<func>
- <name name="lait" arity="1"/>
+ <name name="lait" arity="1" since=""/>
<fsummary>Remove the tail item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -392,7 +392,7 @@
</func>
<func>
- <name name="last" arity="1"/>
+ <name name="last" arity="1" since=""/>
<fsummary>Return the tail item of a queue.</fsummary>
<desc>
<p>Returns the tail item of queue <c><anno>Q</anno></c>.</p>
@@ -401,7 +401,7 @@
</func>
<func>
- <name name="liat" arity="1"/>
+ <name name="liat" arity="1" since=""/>
<fsummary>Remove the tail item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
@@ -411,7 +411,7 @@
</func>
<func>
- <name name="snoc" arity="2"/>
+ <name name="snoc" arity="2" since=""/>
<fsummary>Insert an item at the tail of a queue.</fsummary>
<desc>
<p>Inserts <c><anno>Item</anno></c> as the tail item of queue
@@ -421,7 +421,7 @@
</func>
<func>
- <name name="tail" arity="1"/>
+ <name name="tail" arity="1" since=""/>
<fsummary>Remove the head item from a queue.</fsummary>
<desc>
<p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing
diff --git a/lib/stdlib/doc/src/rand.xml b/lib/stdlib/doc/src/rand.xml
index 8e657698c6..b4737b48f8 100644
--- a/lib/stdlib/doc/src/rand.xml
+++ b/lib/stdlib/doc/src/rand.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>rand.xml</file>
</header>
- <module>rand</module>
+ <module since="OTP 18.0">rand</module>
<modulesummary>Pseudo random number generation.</modulesummary>
<description>
<p>
@@ -349,7 +349,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
<funcs>
<func>
- <name name="export_seed" arity="0"/>
+ <name name="export_seed" arity="0" since="OTP 18.0"/>
<fsummary>Export the random number generation state.</fsummary>
<desc><marker id="export_seed-0"/>
<p>Returns the random number state in an external format.
@@ -358,7 +358,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="export_seed_s" arity="1"/>
+ <name name="export_seed_s" arity="1" since="OTP 18.0"/>
<fsummary>Export the random number generation state.</fsummary>
<desc><marker id="export_seed_s-1"/>
<p>Returns the random number generator state in an external format.
@@ -367,7 +367,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="jump" arity="0"/>
+ <name name="jump" arity="0" since="OTP 20.0"/>
<fsummary>Return the seed after performing jump calculation
to the state in the process dictionary.</fsummary>
<desc><marker id="jump-0" />
@@ -382,7 +382,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="jump" arity="1"/>
+ <name name="jump" arity="1" since="OTP 20.0"/>
<fsummary>Return the seed after performing jump calculation.</fsummary>
<desc><marker id="jump-1" />
<p>Returns the state after performing jump calculation
@@ -394,7 +394,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal" arity="0"/>
+ <name name="normal" arity="0" since="OTP 18.0"/>
<fsummary>Return a standard normal distributed random float.</fsummary>
<desc>
<p>Returns a standard normal deviate float (that is, the mean
@@ -404,7 +404,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal" arity="2"/>
+ <name name="normal" arity="2" since="OTP 20.0"/>
<fsummary>Return a normal distributed random float.</fsummary>
<desc>
<p>Returns a normal N(Mean, Variance) deviate float
@@ -413,7 +413,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal_s" arity="1"/>
+ <name name="normal_s" arity="1" since="OTP 18.0"/>
<fsummary>Return a standard normal distributed random float.</fsummary>
<desc>
<p>Returns, for a specified state, a standard normal
@@ -423,7 +423,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="normal_s" arity="3"/>
+ <name name="normal_s" arity="3" since="OTP 20.0"/>
<fsummary>Return a normal distributed random float.</fsummary>
<desc>
<p>Returns, for a specified state, a normal N(Mean, Variance)
@@ -432,7 +432,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed" arity="1"/>
+ <name name="seed" arity="1" since="OTP 18.0"/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<marker id="seed-1"/>
@@ -448,7 +448,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed" arity="2"/>
+ <name name="seed" arity="2" since="OTP 18.0"/>
<fsummary>Seed the random number generation.</fsummary>
<desc>
<p>Seeds random number generation with the specified algorithm and
@@ -457,7 +457,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed_s" arity="1"/>
+ <name name="seed_s" arity="1" since="OTP 18.0"/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<p>
@@ -472,7 +472,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="seed_s" arity="2"/>
+ <name name="seed_s" arity="2" since="OTP 18.0"/>
<fsummary>Seed the random number generation.</fsummary>
<desc>
<p>Seeds random number generation with the specified algorithm and
@@ -481,7 +481,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre>
</func>
<func>
- <name name="uniform" arity="0"/>
+ <name name="uniform" arity="0" since="OTP 18.0"/>
<fsummary>Return a random float.</fsummary>
<desc><marker id="uniform-0"/>
<p>
@@ -517,7 +517,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_real" arity="0"/>
+ <name name="uniform_real" arity="0" since="OTP 21.0"/>
<fsummary>Return a random float.</fsummary>
<desc><marker id="uniform_real-0"/>
<p>
@@ -553,7 +553,7 @@ end.</pre>
</func>
<func>
- <name name="uniform" arity="1"/>
+ <name name="uniform" arity="1" since="OTP 18.0"/>
<fsummary>Return a random integer.</fsummary>
<desc><marker id="uniform-1"/>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>,
@@ -564,7 +564,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_s" arity="1"/>
+ <name name="uniform_s" arity="1" since="OTP 18.0"/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>
@@ -600,7 +600,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_real_s" arity="1"/>
+ <name name="uniform_real_s" arity="1" since="OTP 21.0"/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>
@@ -662,7 +662,7 @@ end.</pre>
</func>
<func>
- <name name="uniform_s" arity="2"/>
+ <name name="uniform_s" arity="2" since="OTP 18.0"/>
<fsummary>Return a random integer.</fsummary>
<desc>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>
diff --git a/lib/stdlib/doc/src/random.xml b/lib/stdlib/doc/src/random.xml
index 8d090d20b3..f0261ed009 100644
--- a/lib/stdlib/doc/src/random.xml
+++ b/lib/stdlib/doc/src/random.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>random.xml</file>
</header>
- <module>random</module>
+ <module since="">random</module>
<modulesummary>Pseudo-random number generation.</modulesummary>
<description>
<p>This module provides a random number generator. The method is attributed
@@ -73,7 +73,7 @@
<funcs>
<func>
- <name name="seed" arity="0"/>
+ <name name="seed" arity="0" since=""/>
<fsummary>Seed random number generation with default values.</fsummary>
<desc>
<p>Seeds random number generation with default (fixed) values
@@ -82,7 +82,7 @@
</func>
<func>
- <name name="seed" arity="1"/>
+ <name name="seed" arity="1" since=""/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<p><c>seed({<anno>A1</anno>, <anno>A2</anno>, <anno>A3</anno>})</c>
@@ -92,7 +92,7 @@
</func>
<func>
- <name name="seed" arity="3"/>
+ <name name="seed" arity="3" since=""/>
<fsummary>Seed random number generator.</fsummary>
<desc>
<p>Seeds random number generation with integer values in the process
@@ -116,7 +116,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="seed0" arity="0"/>
+ <name name="seed0" arity="0" since=""/>
<fsummary>Return default state for random number generation.</fsummary>
<desc>
<p>Returns the default state.</p>
@@ -124,7 +124,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform" arity="0"/>
+ <name name="uniform" arity="0" since=""/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>Returns a random float uniformly distributed between <c>0.0</c>
@@ -133,7 +133,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform" arity="1"/>
+ <name name="uniform" arity="1" since=""/>
<fsummary>Return a random integer.</fsummary>
<desc>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>,
@@ -144,7 +144,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform_s" arity="1"/>
+ <name name="uniform_s" arity="1" since=""/>
<fsummary>Return a random float.</fsummary>
<desc>
<p>Returns, for a specified state, a random float uniformly
@@ -153,7 +153,7 @@ random:seed(erlang:phash2([node()]),
</func>
<func>
- <name name="uniform_s" arity="2"/>
+ <name name="uniform_s" arity="2" since=""/>
<fsummary>Return a random integer.</fsummary>
<desc>
<p>Returns, for a specified integer <c><anno>N</anno> >= 1</c> and a
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index 078ca0e38c..b04434492d 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -34,7 +34,7 @@
<rev>A</rev>
<file>re.xml</file>
</header>
- <module>re</module>
+ <module since="">re</module>
<modulesummary>Perl-like regular expressions for Erlang.</modulesummary>
<description>
<p>This module contains regular expression matching functions for
@@ -79,7 +79,7 @@
<funcs>
<func>
- <name name="version" arity="0"/>
+ <name name="version" arity="0" since="OTP 20.0"/>
<fsummary>Gives the PCRE version of the system in a string format</fsummary>
<desc>
<p>The return of this function is a string with the PCRE version of the system that was used in the Erlang/OTP compilation.</p>
@@ -87,7 +87,7 @@
</func>
<func>
- <name name="compile" arity="1"/>
+ <name name="compile" arity="1" since=""/>
<fsummary>Compile a regular expression into a match program</fsummary>
<desc>
<p>The same as <c>compile(<anno>Regexp</anno>,[])</c></p>
@@ -95,7 +95,7 @@
</func>
<func>
- <name name="compile" arity="2"/>
+ <name name="compile" arity="2" since=""/>
<fsummary>Compile a regular expression into a match program.</fsummary>
<desc>
<p>Compiles a regular expression, with the syntax
@@ -304,7 +304,7 @@
</func>
<func>
- <name name="inspect" arity="2"/>
+ <name name="inspect" arity="2" since="OTP 17.0"/>
<fsummary>Inspects a compiled regular expression.</fsummary>
<desc>
<p>Takes a compiled regular expression and an item, and returns the
@@ -348,7 +348,7 @@
</func>
<func>
- <name name="replace" arity="3"/>
+ <name name="replace" arity="3" since=""/>
<fsummary>Match a subject against regular expression and replace matching
elements with Replacement.</fsummary>
<desc>
@@ -358,7 +358,7 @@
</func>
<func>
- <name name="replace" arity="4"/>
+ <name name="replace" arity="4" since=""/>
<fsummary>Match a subject against regular expression and replace matching
elements with Replacement.</fsummary>
<desc>
@@ -408,7 +408,7 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
</func>
<func>
- <name name="run" arity="2"/>
+ <name name="run" arity="2" since=""/>
<fsummary>Match a subject against regular expression and capture
subpatterns.</fsummary>
<desc>
@@ -417,7 +417,7 @@ re:replace("abcd","c","[\\&amp;]",[{return,list}]).</code>
</func>
<func>
- <name name="run" arity="3"/>
+ <name name="run" arity="3" since=""/>
<fsummary>Match a subject against regular expression and capture
subpatterns.</fsummary>
<type_desc variable="CompileOpt">See <seealso marker="#compile_options">
@@ -992,7 +992,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
</func>
<func>
- <name name="split" arity="2"/>
+ <name name="split" arity="2" since=""/>
<fsummary>Split a string by tokens specified as a regular expression.
</fsummary>
<desc>
@@ -1001,7 +1001,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code>
</func>
<func>
- <name name="split" arity="3"/>
+ <name name="split" arity="3" since=""/>
<fsummary>Split a string by tokens specified as a regular expression</fsummary>
<type_desc variable="CompileOpt">See <seealso marker="#compile_options">
<c>compile/2</c></seealso>.</type_desc>
diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml
index 8db3e1e623..07ce41b7a7 100644
--- a/lib/stdlib/doc/src/sets.xml
+++ b/lib/stdlib/doc/src/sets.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>sets.xml</file>
</header>
- <module>sets</module>
+ <module since="">sets</module>
<modulesummary>Functions for set manipulation.</modulesummary>
<description>
<p>Sets are collections of elements with no duplicate elements.
@@ -59,7 +59,7 @@
<funcs>
<func>
- <name name="add_element" arity="2"/>
+ <name name="add_element" arity="2" since=""/>
<fsummary>Add an element to a <c>Set</c>.</fsummary>
<desc>
<p>Returns a new set formed from <c><anno>Set1</anno></c> with
@@ -68,7 +68,7 @@
</func>
<func>
- <name name="del_element" arity="2"/>
+ <name name="del_element" arity="2" since=""/>
<fsummary>Remove an element from a <c>Set</c>.</fsummary>
<desc>
<p>Returns <c><anno>Set1</anno></c>, but with
@@ -77,7 +77,7 @@
</func>
<func>
- <name name="filter" arity="2"/>
+ <name name="filter" arity="2" since=""/>
<fsummary>Filter set elements.</fsummary>
<desc>
<p>Filters elements in <c><anno>Set1</anno></c> with boolean function
@@ -86,7 +86,7 @@
</func>
<func>
- <name name="fold" arity="3"/>
+ <name name="fold" arity="3" since=""/>
<fsummary>Fold over set elements.</fsummary>
<desc>
<p>Folds <c><anno>Function</anno></c> over every element in
@@ -96,7 +96,7 @@
</func>
<func>
- <name name="from_list" arity="1"/>
+ <name name="from_list" arity="1" since=""/>
<fsummary>Convert a list into a <c>Set</c>.</fsummary>
<desc>
<p>Returns a set of the elements in <c><anno>List</anno></c>.</p>
@@ -104,7 +104,7 @@
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a list of <c>Sets</c>.</fsummary>
<desc>
<p>Returns the intersection of the non-empty list of sets.</p>
@@ -112,7 +112,7 @@
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two <c>Sets</c>.</fsummary>
<desc>
<p>Returns the intersection of <c><anno>Set1</anno></c> and
@@ -121,7 +121,7 @@
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Check whether two <c>Sets</c> are disjoint.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> and
@@ -131,7 +131,7 @@
</func>
<func>
- <name name="is_element" arity="2"/>
+ <name name="is_element" arity="2" since=""/>
<fsummary>Test for membership of a <c>Set</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of
@@ -140,7 +140,7 @@
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 21.0"/>
<fsummary>Test for empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set,
@@ -149,7 +149,7 @@
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for a <c>Set</c>.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set</anno></c> is a set of
@@ -158,7 +158,7 @@
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test for subset.</fsummary>
<desc>
<p>Returns <c>true</c> when every element of <c><anno>Set1</anno></c> is
@@ -167,7 +167,7 @@
</func>
<func>
- <name name="new" arity="0"/>
+ <name name="new" arity="0" since=""/>
<fsummary>Return an empty set.</fsummary>
<desc>
<p>Returns a new empty set.</p>
@@ -175,7 +175,7 @@
</func>
<func>
- <name name="size" arity="1"/>
+ <name name="size" arity="1" since=""/>
<fsummary>Return the number of elements in a set.</fsummary>
<desc>
<p>Returns the number of elements in <c><anno>Set</anno></c>.</p>
@@ -183,7 +183,7 @@
</func>
<func>
- <name name="subtract" arity="2"/>
+ <name name="subtract" arity="2" since=""/>
<fsummary>Return the difference of two <c>Sets</c>.</fsummary>
<desc>
<p>Returns only the elements of <c><anno>Set1</anno></c> that are not
@@ -192,7 +192,7 @@
</func>
<func>
- <name name="to_list" arity="1"/>
+ <name name="to_list" arity="1" since=""/>
<fsummary>Convert a <c>Set</c>into a list.</fsummary>
<desc>
<p>Returns the elements of <c><anno>Set</anno></c> as a list.
@@ -201,7 +201,7 @@
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a list of <c>Sets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of the list of sets.</p>
@@ -209,7 +209,7 @@
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two <c>Sets</c>.</fsummary>
<desc>
<p>Returns the merged (union) set of <c><anno>Set1</anno></c> and
diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml
index 2593d3690b..50a0968531 100644
--- a/lib/stdlib/doc/src/shell.xml
+++ b/lib/stdlib/doc/src/shell.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>shell.xml</file>
</header>
- <module>shell</module>
+ <module since="">shell</module>
<modulesummary>The Erlang shell.</modulesummary>
<description>
<p>This module provides an Erlang shell.</p>
@@ -874,7 +874,7 @@ q - quit erlang
<funcs>
<func>
- <name>catch_exception(Bool) -> boolean()</name>
+ <name since="">catch_exception(Bool) -> boolean()</name>
<fsummary>Set the exception handling of the shell.</fsummary>
<type>
<v>Bool = boolean()</v>
@@ -892,7 +892,7 @@ q - quit erlang
</func>
<func>
- <name name="history" arity="1"/>
+ <name name="history" arity="1" since=""/>
<fsummary>Set the number of previous commands to keep.</fsummary>
<desc>
<p>Sets the number of previous commands to keep in the
@@ -902,7 +902,7 @@ q - quit erlang
</func>
<func>
- <name name="prompt_func" arity="1"/>
+ <name name="prompt_func" arity="1" since="OTP R13B04"/>
<fsummary>Set the shell prompt.</fsummary>
<desc>
<p>Sets the shell prompt function to <c><anno>PromptFunc</anno></c>.
@@ -911,7 +911,7 @@ q - quit erlang
</func>
<func>
- <name name="results" arity="1"/>
+ <name name="results" arity="1" since=""/>
<fsummary>Set the number of previous results to keep.</fsummary>
<desc>
<p>Sets the number of results from previous commands to keep in
@@ -921,7 +921,7 @@ q - quit erlang
</func>
<func>
- <name name="start_restricted" arity="1"/>
+ <name name="start_restricted" arity="1" since=""/>
<fsummary>Exit a normal shell and starts a restricted shell.</fsummary>
<desc>
<p>Exits a normal shell and starts a restricted shell.
@@ -936,7 +936,7 @@ q - quit erlang
</func>
<func>
- <name name="stop_restricted" arity="0"/>
+ <name name="stop_restricted" arity="0" since=""/>
<fsummary>Exit a restricted shell and starts a normal shell.</fsummary>
<desc>
<p>Exits a restricted shell and starts a normal shell. The function
@@ -945,7 +945,7 @@ q - quit erlang
</func>
<func>
- <name name="strings" arity="1"/>
+ <name name="strings" arity="1" since="OTP R16B"/>
<fsummary>Set the shell's string recognition flag.</fsummary>
<desc>
<p>Sets pretty printing of lists to <c><anno>Strings</anno></c>.
diff --git a/lib/stdlib/doc/src/slave.xml b/lib/stdlib/doc/src/slave.xml
index e53ec8231b..80fb28b548 100644
--- a/lib/stdlib/doc/src/slave.xml
+++ b/lib/stdlib/doc/src/slave.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>slave</module>
+ <module since="">slave</module>
<modulesummary>Functions for starting and controlling slave nodes.
</modulesummary>
<description>
@@ -68,7 +68,7 @@
<funcs>
<func>
- <name>pseudo([Master | ServerList]) -> ok</name>
+ <name since="">pseudo([Master | ServerList]) -> ok</name>
<fsummary>Start a number of pseudo servers.</fsummary>
<type>
<v>Master = node()</v>
@@ -84,7 +84,7 @@
</func>
<func>
- <name name="pseudo" arity="2"/>
+ <name name="pseudo" arity="2" since=""/>
<fsummary>Start a number of pseudo servers.</fsummary>
<desc>
<p>Starts a number of pseudo servers. A pseudo server is a
@@ -102,7 +102,7 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code>
</func>
<func>
- <name name="relay" arity="1"/>
+ <name name="relay" arity="1" since=""/>
<fsummary>Run a pseudo server.</fsummary>
<desc>
<p>Runs a pseudo server. This function never returns any value
@@ -113,9 +113,9 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code>
</func>
<func>
- <name name="start" arity="1"/>
- <name name="start" arity="2"/>
- <name name="start" arity="3"/>
+ <name name="start" arity="1" since=""/>
+ <name name="start" arity="2" since=""/>
+ <name name="start" arity="3" since=""/>
<fsummary>Start a slave node on a host.</fsummary>
<desc>
<p>Starts a slave node on host <c><anno>Host</anno></c>. Host names
@@ -178,9 +178,9 @@ slave:start(H, Name, Arg).</code>
</func>
<func>
- <name name="start_link" arity="1"/>
- <name name="start_link" arity="2"/>
- <name name="start_link" arity="3"/>
+ <name name="start_link" arity="1" since=""/>
+ <name name="start_link" arity="2" since=""/>
+ <name name="start_link" arity="3" since=""/>
<fsummary>Start and link to a slave node on a host.</fsummary>
<desc>
<p>Starts a slave node in the same way as <c>start/1,2,3</c>,
@@ -193,7 +193,7 @@ slave:start(H, Name, Arg).</code>
</func>
<func>
- <name name="stop" arity="1"/>
+ <name name="stop" arity="1" since=""/>
<fsummary>Stop (kill) a node.</fsummary>
<desc>
<p>Stops (kills) a node.</p>
diff --git a/lib/stdlib/doc/src/sofs.xml b/lib/stdlib/doc/src/sofs.xml
index 4cf1984d46..a0759d2f52 100644
--- a/lib/stdlib/doc/src/sofs.xml
+++ b/lib/stdlib/doc/src/sofs.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>sofs.xml</file>
</header>
- <module>sofs</module>
+ <module since="">sofs</module>
<modulesummary>Functions for manipulating sets of sets.</modulesummary>
<description>
<p>This module provides operations on finite sets and
@@ -456,8 +456,8 @@ fun(S) -> sofs:partition(1, S) end
<funcs>
<func>
- <name name="a_function" arity="1"/>
- <name name="a_function" arity="2"/>
+ <name name="a_function" arity="1" since=""/>
+ <name name="a_function" arity="2" since=""/>
<fsummary>Create a function.</fsummary>
<desc>
<p>Creates a <seealso marker="#function">function</seealso>.
@@ -470,7 +470,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="canonical_relation" arity="1"/>
+ <name name="canonical_relation" arity="1" since=""/>
<fsummary>Return the canonical map.</fsummary>
<desc>
<p>Returns the binary relation containing the elements
@@ -490,7 +490,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="composite" arity="2"/>
+ <name name="composite" arity="2" since=""/>
<fsummary>Return the composite of two functions.</fsummary>
<desc>
<p>Returns the <seealso marker="#composite">composite</seealso> of
@@ -506,7 +506,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="constant_function" arity="2"/>
+ <name name="constant_function" arity="2" since=""/>
<fsummary>Create the function that maps each element of a
set onto another set.</fsummary>
<desc>
@@ -522,7 +522,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="converse" arity="1"/>
+ <name name="converse" arity="1" since=""/>
<fsummary>Return the converse of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#converse">converse</seealso>
@@ -536,7 +536,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="difference" arity="2"/>
+ <name name="difference" arity="2" since=""/>
<fsummary>Return the difference of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#difference">difference</seealso> of
@@ -545,8 +545,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="digraph_to_family" arity="1"/>
- <name name="digraph_to_family" arity="2"/>
+ <name name="digraph_to_family" arity="1" since=""/>
+ <name name="digraph_to_family" arity="2" since=""/>
<fsummary>Create a family from a directed graph.</fsummary>
<desc>
<p>Creates a <seealso marker="#family">family</seealso> from
@@ -565,7 +565,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="domain" arity="1"/>
+ <name name="domain" arity="1" since=""/>
<fsummary>Return the domain of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#domain">domain</seealso> of
@@ -579,7 +579,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="drestriction" arity="2"/>
+ <name name="drestriction" arity="2" since=""/>
<fsummary>Return a restriction of a binary relation.</fsummary>
<desc>
<p>Returns the difference between the binary relation
@@ -598,7 +598,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="drestriction" arity="3"/>
+ <name name="drestriction" arity="3" since=""/>
<fsummary>Return a restriction of a relation.</fsummary>
<desc>
<p>Returns a subset of <c><anno>Set1</anno></c> containing those
@@ -618,7 +618,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="empty_set" arity="0"/>
+ <name name="empty_set" arity="0" since=""/>
<fsummary>Return the untyped empty set.</fsummary>
<desc>
<p>Returns the <seealso marker="#sets_definition">untyped empty
@@ -628,7 +628,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="extension" arity="3"/>
+ <name name="extension" arity="3" since=""/>
<fsummary>Extend the domain of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#extension">extension</seealso> of
@@ -648,8 +648,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family" arity="1"/>
- <name name="family" arity="2"/>
+ <name name="family" arity="1" since=""/>
+ <name name="family" arity="2" since=""/>
<fsummary>Create a family of subsets.</fsummary>
<desc>
<p>Creates a <seealso marker="#family">family of subsets</seealso>.
@@ -662,7 +662,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_difference" arity="2"/>
+ <name name="family_difference" arity="2" since=""/>
<fsummary>Return the difference of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
@@ -683,7 +683,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_domain" arity="1"/>
+ <name name="family_domain" arity="1" since=""/>
<fsummary>Return a family of domains.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -704,7 +704,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_field" arity="1"/>
+ <name name="family_field" arity="1" since=""/>
<fsummary>Return a family of fields.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -728,7 +728,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_intersection" arity="1"/>
+ <name name="family_intersection" arity="1" since=""/>
<fsummary>Return the intersection of a family
of sets of sets.</fsummary>
<desc>
@@ -752,7 +752,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_intersection" arity="2"/>
+ <name name="family_intersection" arity="2" since=""/>
<fsummary>Return the intersection of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
@@ -772,7 +772,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_projection" arity="2"/>
+ <name name="family_projection" arity="2" since=""/>
<fsummary>Return a family of modified subsets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -791,7 +791,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_range" arity="1"/>
+ <name name="family_range" arity="1" since=""/>
<fsummary>Return a family of ranges.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -812,7 +812,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_specification" arity="2"/>
+ <name name="family_specification" arity="2" since=""/>
<fsummary>Select a subset of a family using a predicate.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -837,8 +837,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_to_digraph" arity="1"/>
- <name name="family_to_digraph" arity="2"/>
+ <name name="family_to_digraph" arity="1" since=""/>
+ <name name="family_to_digraph" arity="2" since=""/>
<fsummary>Create a directed graph from a family.</fsummary>
<desc>
<p>Creates a directed graph from
@@ -863,7 +863,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_to_relation" arity="1"/>
+ <name name="family_to_relation" arity="1" since=""/>
<fsummary>Create a binary relation from a family.</fsummary>
<desc>
<p>If <c><anno>Family</anno></c> is
@@ -881,7 +881,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_union" arity="1"/>
+ <name name="family_union" arity="1" since=""/>
<fsummary>Return the union of a family of sets of sets.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> is
@@ -904,7 +904,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="family_union" arity="2"/>
+ <name name="family_union" arity="2" since=""/>
<fsummary>Return the union of two families.</fsummary>
<desc>
<p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c>
@@ -926,7 +926,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="field" arity="1"/>
+ <name name="field" arity="1" since=""/>
<fsummary>Return the field of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#field">field</seealso> of the
@@ -942,7 +942,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_external" arity="2"/>
+ <name name="from_external" arity="2" since=""/>
<fsummary>Create a set.</fsummary>
<desc>
<p>Creates a set from the <seealso marker="#external_set">external
@@ -955,7 +955,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_sets" arity="1" clause_i="1"/>
+ <name name="from_sets" arity="1" clause_i="1" since=""/>
<fsummary>Create a set out of a list of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#sets_definition">unordered
@@ -971,7 +971,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_sets" arity="1" clause_i="2"/>
+ <name name="from_sets" arity="1" clause_i="2" since=""/>
<fsummary>Create an ordered set out of a tuple of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#sets_definition">ordered
@@ -981,8 +981,8 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="from_term" arity="1"/>
- <name name="from_term" arity="2"/>
+ <name name="from_term" arity="1" since=""/>
+ <name name="from_term" arity="2" since=""/>
<fsummary>Create a set.</fsummary>
<desc>
<p><marker id="from_term"></marker>Creates an element
@@ -1031,7 +1031,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="image" arity="2"/>
+ <name name="image" arity="2" since=""/>
<fsummary>Return the image of a set under a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#image">image</seealso> of
@@ -1047,7 +1047,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="intersection" arity="1"/>
+ <name name="intersection" arity="1" since=""/>
<fsummary>Return the intersection of a set of sets.</fsummary>
<desc>
<p>Returns
@@ -1059,7 +1059,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="intersection" arity="2"/>
+ <name name="intersection" arity="2" since=""/>
<fsummary>Return the intersection of two sets.</fsummary>
<desc>
<p>Returns
@@ -1069,7 +1069,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="intersection_of_family" arity="1"/>
+ <name name="intersection_of_family" arity="1" since=""/>
<fsummary>Return the intersection of a family.</fsummary>
<desc>
<p>Returns the intersection of
@@ -1086,7 +1086,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="inverse" arity="1"/>
+ <name name="inverse" arity="1" since=""/>
<fsummary>Return the inverse of a function.</fsummary>
<desc>
<p>Returns the <seealso marker="#inverse">inverse</seealso>
@@ -1100,7 +1100,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="inverse_image" arity="2"/>
+ <name name="inverse_image" arity="2" since=""/>
<fsummary>Return the inverse image of a set under
a binary relation.</fsummary>
<desc>
@@ -1117,7 +1117,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_a_function" arity="1"/>
+ <name name="is_a_function" arity="1" since=""/>
<fsummary>Test for a function.</fsummary>
<desc>
<p>Returns <c>true</c> if the binary relation <c><anno>BinRel</anno></c>
@@ -1127,7 +1127,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_disjoint" arity="2"/>
+ <name name="is_disjoint" arity="2" since=""/>
<fsummary>Test for disjoint sets.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c>
@@ -1138,7 +1138,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_empty_set" arity="1"/>
+ <name name="is_empty_set" arity="1" since=""/>
<fsummary>Test for an empty set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is an empty
@@ -1147,7 +1147,7 @@ fun(S) -> sofs:partition(1, S) end
</func>
<func>
- <name name="is_equal" arity="2"/>
+ <name name="is_equal" arity="2" since=""/>
<fsummary>Test two sets for equality.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet1</anno></c>
@@ -1164,7 +1164,7 @@ true</pre>
</func>
<func>
- <name name="is_set" arity="1"/>
+ <name name="is_set" arity="1" since=""/>
<fsummary>Test for an unordered set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is
@@ -1175,7 +1175,7 @@ true</pre>
</func>
<func>
- <name name="is_sofs_set" arity="1"/>
+ <name name="is_sofs_set" arity="1" since=""/>
<fsummary>Test for an unordered set.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Term</anno></c> is
@@ -1185,7 +1185,7 @@ true</pre>
</func>
<func>
- <name name="is_subset" arity="2"/>
+ <name name="is_subset" arity="2" since=""/>
<fsummary>Test two sets for subset.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>Set1</anno></c> is
@@ -1195,7 +1195,7 @@ true</pre>
</func>
<func>
- <name name="is_type" arity="1"/>
+ <name name="is_type" arity="1" since=""/>
<fsummary>Test for a type.</fsummary>
<desc>
<p>Returns <c>true</c> if term <c><anno>Term</anno></c> is
@@ -1204,7 +1204,7 @@ true</pre>
</func>
<func>
- <name name="join" arity="4"/>
+ <name name="join" arity="4" since=""/>
<fsummary>Return the join of two relations.</fsummary>
<desc>
<p>Returns the <seealso marker="#natural_join">natural
@@ -1221,7 +1221,7 @@ true</pre>
</func>
<func>
- <name name="multiple_relative_product" arity="2"/>
+ <name name="multiple_relative_product" arity="2" since=""/>
<fsummary>Return the multiple relative product of a tuple of binary
relations and a relation.</fsummary>
<desc>
@@ -1242,7 +1242,7 @@ true</pre>
</func>
<func>
- <name name="no_elements" arity="1"/>
+ <name name="no_elements" arity="1" since=""/>
<fsummary>Return the number of elements of a set.</fsummary>
<desc>
<p>Returns the number of elements of the ordered or unordered
@@ -1251,7 +1251,7 @@ true</pre>
</func>
<func>
- <name name="partition" arity="1"/>
+ <name name="partition" arity="1" since=""/>
<fsummary>Return the coarsest partition given a set of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#partition">partition</seealso> of
@@ -1268,7 +1268,7 @@ true</pre>
</func>
<func>
- <name name="partition" arity="2"/>
+ <name name="partition" arity="2" since=""/>
<fsummary>Return a partition of a set.</fsummary>
<desc>
<p>Returns the <seealso marker="#partition">partition</seealso> of
@@ -1284,7 +1284,7 @@ true</pre>
</func>
<func>
- <name name="partition" arity="3"/>
+ <name name="partition" arity="3" since=""/>
<fsummary>Return a partition of a set.</fsummary>
<desc>
<p>Returns a pair of sets that, regarded as constituting a
@@ -1307,7 +1307,7 @@ true</pre>
</func>
<func>
- <name name="partition_family" arity="2"/>
+ <name name="partition_family" arity="2" since=""/>
<fsummary>Return a family indexing a partition.</fsummary>
<desc>
<p>Returns <seealso marker="#family">family</seealso>
@@ -1328,7 +1328,7 @@ true</pre>
</func>
<func>
- <name name="product" arity="1"/>
+ <name name="product" arity="1" since=""/>
<fsummary>Return the Cartesian product of a tuple of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#Cartesian_product_tuple">Cartesian
@@ -1347,7 +1347,7 @@ true</pre>
</func>
<func>
- <name name="product" arity="2"/>
+ <name name="product" arity="2" since=""/>
<fsummary>Return the Cartesian product of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#Cartesian_product">Cartesian
@@ -1365,7 +1365,7 @@ true</pre>
</func>
<func>
- <name name="projection" arity="2"/>
+ <name name="projection" arity="2" since=""/>
<fsummary>Return a set of substituted elements.</fsummary>
<desc>
<p>Returns the set created by substituting each element of
@@ -1384,7 +1384,7 @@ true</pre>
</func>
<func>
- <name name="range" arity="1"/>
+ <name name="range" arity="1" since=""/>
<fsummary>Return the range of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#range">range</seealso> of the
@@ -1398,8 +1398,8 @@ true</pre>
</func>
<func>
- <name name="relation" arity="1"/>
- <name name="relation" arity="2"/>
+ <name name="relation" arity="1" since=""/>
+ <name name="relation" arity="2" since=""/>
<fsummary>Create a relation.</fsummary>
<desc>
<p>Creates a <seealso marker="#relation">relation</seealso>.
@@ -1417,7 +1417,7 @@ true</pre>
</func>
<func>
- <name name="relation_to_family" arity="1"/>
+ <name name="relation_to_family" arity="1" since=""/>
<fsummary>Create a family from a binary relation.</fsummary>
<desc>
<p>Returns <seealso marker="#family">family</seealso>
@@ -1435,8 +1435,8 @@ true</pre>
</func>
<func>
- <name name="relative_product" arity="1"/>
- <name name="relative_product" arity="2" clause_i="1"/>
+ <name name="relative_product" arity="1" since=""/>
+ <name name="relative_product" arity="2" clause_i="1" since=""/>
<fsummary>Return the relative product of a list of binary relations
and a binary relation.</fsummary>
<desc>
@@ -1466,7 +1466,7 @@ true</pre>
</func>
<func>
- <name name="relative_product" arity="2" clause_i="2"/>
+ <name name="relative_product" arity="2" clause_i="2" since=""/>
<fsummary>Return the relative product of
two binary relations.</fsummary>
<desc>
@@ -1477,7 +1477,7 @@ true</pre>
</func>
<func>
- <name name="relative_product1" arity="2"/>
+ <name name="relative_product1" arity="2" since=""/>
<fsummary>Return the relative_product of
two binary relations.</fsummary>
<desc>
@@ -1498,7 +1498,7 @@ true</pre>
</func>
<func>
- <name name="restriction" arity="2"/>
+ <name name="restriction" arity="2" since=""/>
<fsummary>Return a restriction of a binary relation.</fsummary>
<desc>
<p>Returns the <seealso marker="#restriction">restriction</seealso> of
@@ -1514,7 +1514,7 @@ true</pre>
</func>
<func>
- <name name="restriction" arity="3"/>
+ <name name="restriction" arity="3" since=""/>
<fsummary>Return a restriction of a set.</fsummary>
<desc>
<p>Returns a subset of <c><anno>Set1</anno></c> containing those
@@ -1530,8 +1530,8 @@ true</pre>
</func>
<func>
- <name name="set" arity="1"/>
- <name name="set" arity="2"/>
+ <name name="set" arity="1" since=""/>
+ <name name="set" arity="2" since=""/>
<fsummary>Create a set of atoms or any type of sets.</fsummary>
<desc>
<p>Creates an <seealso marker="#sets_definition">unordered
@@ -1543,7 +1543,7 @@ true</pre>
</func>
<func>
- <name name="specification" arity="2"/>
+ <name name="specification" arity="2" since=""/>
<fsummary>Select a subset using a predicate.</fsummary>
<desc>
<p>Returns the set containing every element
@@ -1564,7 +1564,7 @@ true</pre>
</func>
<func>
- <name name="strict_relation" arity="1"/>
+ <name name="strict_relation" arity="1" since=""/>
<fsummary>Return the strict relation corresponding to
a given relation.</fsummary>
<desc>
@@ -1580,7 +1580,7 @@ true</pre>
</func>
<func>
- <name name="substitution" arity="2"/>
+ <name name="substitution" arity="2" since=""/>
<fsummary>Return a function with a given set as domain.</fsummary>
<desc>
<p>Returns a function, the domain of which
@@ -1629,7 +1629,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="symdiff" arity="2"/>
+ <name name="symdiff" arity="2" since=""/>
<fsummary>Return the symmetric difference of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#symmetric_difference">symmetric
@@ -1645,7 +1645,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="symmetric_partition" arity="2"/>
+ <name name="symmetric_partition" arity="2" since=""/>
<fsummary>Return a partition of two sets.</fsummary>
<desc>
<p>Returns a triple of sets:</p>
@@ -1666,7 +1666,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="to_external" arity="1"/>
+ <name name="to_external" arity="1" since=""/>
<fsummary>Return the elements of a set.</fsummary>
<desc>
<p>Returns the <seealso marker="#external_set">external
@@ -1675,7 +1675,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="to_sets" arity="1"/>
+ <name name="to_sets" arity="1" since=""/>
<fsummary>Return a list or a tuple of the elements of a set.</fsummary>
<desc>
<p>Returns the elements of the ordered set <c><anno>ASet</anno></c>
@@ -1686,7 +1686,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="type" arity="1"/>
+ <name name="type" arity="1" since=""/>
<fsummary>Return the type of a set.</fsummary>
<desc>
<p>Returns the <seealso marker="#type">type</seealso> of an
@@ -1695,7 +1695,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="union" arity="1"/>
+ <name name="union" arity="1" since=""/>
<fsummary>Return the union of a set of sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#union_n">union</seealso> of the
@@ -1704,7 +1704,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="union" arity="2"/>
+ <name name="union" arity="2" since=""/>
<fsummary>Return the union of two sets.</fsummary>
<desc>
<p>Returns the <seealso marker="#union">union</seealso> of
@@ -1713,7 +1713,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="union_of_family" arity="1"/>
+ <name name="union_of_family" arity="1" since=""/>
<fsummary>Return the union of a family.</fsummary>
<desc>
<p>Returns the union of <seealso marker="#family">family</seealso>
@@ -1727,7 +1727,7 @@ images2(SetOfSets, BinRel) ->
</func>
<func>
- <name name="weak_relation" arity="1"/>
+ <name name="weak_relation" arity="1" since=""/>
<fsummary>Return the weak relation corresponding to
a given relation.</fsummary>
<desc>
diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml
index 3348464eba..d102191a57 100644
--- a/lib/stdlib/doc/src/string.xml
+++ b/lib/stdlib/doc/src/string.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>string.xml</file>
</header>
- <module>string</module>
+ <module since="">string</module>
<modulesummary>String processing functions.</modulesummary>
<description>
<p>This module provides functions for string processing.</p>
@@ -130,7 +130,7 @@
<funcs>
<func>
- <name name="casefold" arity="1"/>
+ <name name="casefold" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to a comparable string.</fsummary>
<desc>
<p>
@@ -147,7 +147,7 @@
</func>
<func>
- <name name="chomp" arity="1"/>
+ <name name="chomp" arity="1" since="OTP 20.0"/>
<fsummary>Remove trailing end of line control characters.</fsummary>
<desc>
<p>
@@ -164,9 +164,9 @@
</func>
<func>
- <name name="equal" arity="2"/>
- <name name="equal" arity="3"/>
- <name name="equal" arity="4"/>
+ <name name="equal" arity="2" since=""/>
+ <name name="equal" arity="3" since="OTP 20.0"/>
+ <name name="equal" arity="4" since="OTP 20.0"/>
<fsummary>Test string equality.</fsummary>
<desc>
<p>
@@ -201,8 +201,8 @@ true</pre>
</func>
<func>
- <name name="find" arity="2"/>
- <name name="find" arity="3"/>
+ <name name="find" arity="2" since="OTP 20.0"/>
+ <name name="find" arity="3" since="OTP 20.0"/>
<fsummary>Find start of substring.</fsummary>
<desc>
<p>
@@ -230,7 +230,7 @@ nomatch</pre>
</func>
<func>
- <name name="is_empty" arity="1"/>
+ <name name="is_empty" arity="1" since="OTP 20.0"/>
<fsummary>Check if the string is empty.</fsummary>
<desc>
<p>Returns <c>true</c> if <c><anno>String</anno></c> is the
@@ -245,7 +245,7 @@ true</pre>
</func>
<func>
- <name name="length" arity="1"/>
+ <name name="length" arity="1" since="OTP 20.0"/>
<fsummary>Calculate length of the string.</fsummary>
<desc>
<p>
@@ -261,7 +261,7 @@ true</pre>
</func>
<func>
- <name name="lexemes" arity="2"/>
+ <name name="lexemes" arity="2" since="OTP 20.0"/>
<fsummary>Split string into lexemes.</fsummary>
<desc>
<p>
@@ -287,7 +287,7 @@ true</pre>
</func>
<func>
- <name name="lowercase" arity="1"/>
+ <name name="lowercase" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to lowercase</fsummary>
<desc>
<p>
@@ -306,7 +306,7 @@ true</pre>
</func>
<func>
- <name name="next_codepoint" arity="1"/>
+ <name name="next_codepoint" arity="1" since="OTP 20.0"/>
<fsummary>Pick the first codepoint.</fsummary>
<desc>
<p>
@@ -323,7 +323,7 @@ true</pre>
</func>
<func>
- <name name="next_grapheme" arity="1"/>
+ <name name="next_grapheme" arity="1" since="OTP 20.0"/>
<fsummary>Pick the first grapheme cluster.</fsummary>
<desc>
<p>
@@ -340,7 +340,7 @@ true</pre>
</func>
<func>
- <name name="nth_lexeme" arity="3"/>
+ <name name="nth_lexeme" arity="3" since="OTP 20.0"/>
<fsummary>Pick the nth lexeme.</fsummary>
<desc>
<p>Returns lexeme number <c><anno>N</anno></c> in
@@ -355,9 +355,9 @@ true</pre>
</func>
<func>
- <name name="pad" arity="2"/>
- <name name="pad" arity="3"/>
- <name name="pad" arity="4"/>
+ <name name="pad" arity="2" since="OTP 20.0"/>
+ <name name="pad" arity="3" since="OTP 20.0"/>
+ <name name="pad" arity="4" since="OTP 20.0"/>
<fsummary>Pad a string to given length.</fsummary>
<desc>
<p>
@@ -381,7 +381,7 @@ true</pre>
</func>
<func>
- <name name="prefix" arity="2"/>
+ <name name="prefix" arity="2" since="OTP 20.0"/>
<fsummary>Remove prefix from string.</fsummary>
<desc>
<p>
@@ -400,8 +400,8 @@ nomatch</pre>
</func>
<func>
- <name name="replace" arity="3"/>
- <name name="replace" arity="4"/>
+ <name name="replace" arity="3" since="OTP 20.0"/>
+ <name name="replace" arity="4" since="OTP 20.0"/>
<fsummary>Replace a pattern in string.</fsummary>
<desc>
<p>
@@ -423,7 +423,7 @@ nomatch</pre>
</func>
<func>
- <name name="reverse" arity="1"/>
+ <name name="reverse" arity="1" since="OTP 20.0"/>
<fsummary>Reverses a string</fsummary>
<desc>
<p>
@@ -439,8 +439,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="slice" arity="2"/>
- <name name="slice" arity="3"/>
+ <name name="slice" arity="2" since="OTP 20.0"/>
+ <name name="slice" arity="3" since="OTP 20.0"/>
<fsummary>Extract a part of string</fsummary>
<desc>
<p>Returns a substring of <c><anno>String</anno></c> of
@@ -459,8 +459,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="split" arity="2"/>
- <name name="split" arity="3"/>
+ <name name="split" arity="2" since="OTP 20.0"/>
+ <name name="split" arity="3" since="OTP 20.0"/>
<fsummary>Split a string into substrings.</fsummary>
<desc>
<p>
@@ -482,9 +482,9 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="take" arity="2"/>
- <name name="take" arity="3"/>
- <name name="take" arity="4"/>
+ <name name="take" arity="2" since="OTP 20.0"/>
+ <name name="take" arity="3" since="OTP 20.0"/>
+ <name name="take" arity="4" since="OTP 20.0"/>
<fsummary>Take leading or trailing parts.</fsummary>
<desc>
<p>Takes characters from <c><anno>String</anno></c> as long as
@@ -508,7 +508,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="titlecase" arity="1"/>
+ <name name="titlecase" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to titlecase.</fsummary>
<desc>
<p>
@@ -522,7 +522,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="to_float" arity="1"/>
+ <name name="to_float" arity="1" since=""/>
<fsummary>Return a float whose text representation is the integers
(ASCII values) of a string.</fsummary>
<desc>
@@ -544,7 +544,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="to_integer" arity="1"/>
+ <name name="to_integer" arity="1" since=""/>
<fsummary>Return an integer whose text representation is the integers
(ASCII values) of a string.</fsummary>
<desc>
@@ -566,7 +566,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="to_graphemes" arity="1"/>
+ <name name="to_graphemes" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to a list of grapheme clusters.</fsummary>
<desc>
<p>
@@ -582,9 +582,9 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="trim" arity="1"/>
- <name name="trim" arity="2"/>
- <name name="trim" arity="3"/>
+ <name name="trim" arity="1" since="OTP 20.0"/>
+ <name name="trim" arity="2" since="OTP 20.0"/>
+ <name name="trim" arity="3" since="OTP 20.0"/>
<fsummary>Trim leading or trailing, or both, characters.</fsummary>
<desc>
<p>
@@ -616,7 +616,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="uppercase" arity="1"/>
+ <name name="uppercase" arity="1" since="OTP 20.0"/>
<fsummary>Convert a string to uppercase.</fsummary>
<desc>
<p>
@@ -649,8 +649,8 @@ ÖÄÅ</pre>
<funcs>
<func>
- <name name="centre" arity="2"/>
- <name name="centre" arity="3"/>
+ <name name="centre" arity="2" since=""/>
+ <name name="centre" arity="3" since=""/>
<fsummary>Center a string.</fsummary>
<desc>
<p>Returns a string, where <c><anno>String</anno></c> is centered in the
@@ -664,8 +664,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="chars" arity="2"/>
- <name name="chars" arity="3"/>
+ <name name="chars" arity="2" since=""/>
+ <name name="chars" arity="3" since=""/>
<fsummary>Return a string consisting of numbers of characters.</fsummary>
<desc>
<p>Returns a string consisting of <c><anno>Number</anno></c> characters
@@ -678,7 +678,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="chr" arity="2"/>
+ <name name="chr" arity="2" since=""/>
<fsummary>Return the index of the first occurrence of
a character in a string.</fsummary>
<desc>
@@ -692,7 +692,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="concat" arity="2"/>
+ <name name="concat" arity="2" since=""/>
<fsummary>Concatenate two strings.</fsummary>
<desc>
<p>Concatenates <c><anno>String1</anno></c> and
@@ -712,7 +712,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="copies" arity="2"/>
+ <name name="copies" arity="2" since=""/>
<fsummary>Copy a string.</fsummary>
<desc>
<p>Returns a string containing <c><anno>String</anno></c> repeated
@@ -724,7 +724,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="cspan" arity="2"/>
+ <name name="cspan" arity="2" since=""/>
<fsummary>Span characters at start of a string.</fsummary>
<desc>
<p>Returns the length of the maximum initial segment of
@@ -741,7 +741,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="join" arity="2"/>
+ <name name="join" arity="2" since=""/>
<fsummary>Join a list of strings with separator.</fsummary>
<desc>
<p>Returns a string with the elements of <c><anno>StringList</anno></c>
@@ -757,8 +757,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="left" arity="2"/>
- <name name="left" arity="3"/>
+ <name name="left" arity="2" since=""/>
+ <name name="left" arity="3" since=""/>
<fsummary>Adjust left end of a string.</fsummary>
<desc>
<p>Returns <c><anno>String</anno></c> with the length adjusted in
@@ -778,7 +778,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="len" arity="1"/>
+ <name name="len" arity="1" since=""/>
<fsummary>Return the length of a string.</fsummary>
<desc>
<p>Returns the number of characters in <c><anno>String</anno></c>.</p>
@@ -789,7 +789,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="rchr" arity="2"/>
+ <name name="rchr" arity="2" since=""/>
<fsummary>Return the index of the last occurrence of
a character in a string.</fsummary>
<desc>
@@ -803,8 +803,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="right" arity="2"/>
- <name name="right" arity="3"/>
+ <name name="right" arity="2" since=""/>
+ <name name="right" arity="3" since=""/>
<fsummary>Adjust right end of a string.</fsummary>
<desc>
<p>Returns <c><anno>String</anno></c> with the length adjusted in
@@ -823,7 +823,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="rstr" arity="2"/>
+ <name name="rstr" arity="2" since=""/>
<fsummary>Find the index of a substring.</fsummary>
<desc>
<p>Returns the position where the last occurrence of
@@ -841,7 +841,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="span" arity="2"/>
+ <name name="span" arity="2" since=""/>
<fsummary>Span characters at start of a string.</fsummary>
<desc>
<p>Returns the length of the maximum initial segment of
@@ -858,7 +858,7 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="str" arity="2"/>
+ <name name="str" arity="2" since=""/>
<fsummary>Find the index of a substring.</fsummary>
<desc>
<p>Returns the position where the first occurrence of
@@ -876,9 +876,9 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="strip" arity="1"/>
- <name name="strip" arity="2"/>
- <name name="strip" arity="3"/>
+ <name name="strip" arity="1" since=""/>
+ <name name="strip" arity="2" since=""/>
+ <name name="strip" arity="3" since=""/>
<fsummary>Strip leading or trailing characters.</fsummary>
<desc>
<p>Returns a string, where leading or trailing, or both, blanks or a
@@ -898,8 +898,8 @@ ÖÄÅ</pre>
</func>
<func>
- <name name="sub_string" arity="2"/>
- <name name="sub_string" arity="3"/>
+ <name name="sub_string" arity="2" since=""/>
+ <name name="sub_string" arity="3" since=""/>
<fsummary>Extract a substring.</fsummary>
<desc>
<p>Returns a substring of <c><anno>String</anno></c>, starting at
@@ -916,8 +916,8 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="substr" arity="2"/>
- <name name="substr" arity="3"/>
+ <name name="substr" arity="2" since=""/>
+ <name name="substr" arity="3" since=""/>
<fsummary>Return a substring of a string.</fsummary>
<desc>
<p>Returns a substring of <c><anno>String</anno></c>, starting at
@@ -934,8 +934,8 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="sub_word" arity="2"/>
- <name name="sub_word" arity="3"/>
+ <name name="sub_word" arity="2" since=""/>
+ <name name="sub_word" arity="3" since=""/>
<fsummary>Extract subword.</fsummary>
<desc>
<p>Returns the word in position <c><anno>Number</anno></c> of
@@ -952,10 +952,10 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="to_lower" arity="1" clause_i="1"/>
- <name name="to_lower" arity="1" clause_i="2"/>
- <name name="to_upper" arity="1" clause_i="1"/>
- <name name="to_upper" arity="1" clause_i="2"/>
+ <name name="to_lower" arity="1" clause_i="1" since=""/>
+ <name name="to_lower" arity="1" clause_i="2" since=""/>
+ <name name="to_upper" arity="1" clause_i="1" since=""/>
+ <name name="to_upper" arity="1" clause_i="2" since=""/>
<fsummary>Convert case of string (ISO/IEC 8859-1).</fsummary>
<type variable="String" name_i="1"/>
<type variable="Result" name_i="1"/>
@@ -974,7 +974,7 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="tokens" arity="2"/>
+ <name name="tokens" arity="2" since=""/>
<fsummary>Split string into tokens.</fsummary>
<desc>
<p>Returns a list of tokens in <c><anno>String</anno></c>, separated
@@ -994,8 +994,8 @@ sub_string("Hello World", 4, 8).
</func>
<func>
- <name name="words" arity="1"/>
- <name name="words" arity="2"/>
+ <name name="words" arity="1" since=""/>
+ <name name="words" arity="2" since=""/>
<fsummary>Count blank separated words.</fsummary>
<desc>
<p>Returns the number of words in <c><anno>String</anno></c>, separated
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml
index 5fd5760499..f15b1a2dd3 100644
--- a/lib/stdlib/doc/src/supervisor.xml
+++ b/lib/stdlib/doc/src/supervisor.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>supervisor</module>
+ <module since="">supervisor</module>
<modulesummary>Generic supervisor behavior.</modulesummary>
<description>
<p>This behavior module provides a supervisor, a process that
@@ -318,7 +318,7 @@ child_spec() = #{id => child_id(), % mandatory
<funcs>
<func>
- <name name="check_childspecs" arity="1"/>
+ <name name="check_childspecs" arity="1" since=""/>
<fsummary>Check if children specifications are syntactically correct.
</fsummary>
<desc>
@@ -329,7 +329,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="count_children" arity="1"/>
+ <name name="count_children" arity="1" since="OTP R13B04"/>
<fsummary>Return counts for the number of child specifications,
active children, supervisors, and workers.</fsummary>
<desc>
@@ -366,7 +366,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="delete_child" arity="2"/>
+ <name name="delete_child" arity="2" since=""/>
<fsummary>Delete a child specification from a supervisor.</fsummary>
<desc>
<p>Tells supervisor <c><anno>SupRef</anno></c> to delete the child
@@ -387,7 +387,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="get_childspec" arity="2"/>
+ <name name="get_childspec" arity="2" since="OTP 18.0"/>
<fsummary>Return the child specification map for the specified
child.</fsummary>
<desc>
@@ -400,7 +400,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="restart_child" arity="2"/>
+ <name name="restart_child" arity="2" since=""/>
<fsummary>Restart a terminated child process belonging to a supervisor.
</fsummary>
<desc>
@@ -436,7 +436,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="start_child" arity="2"/>
+ <name name="start_child" arity="2" since=""/>
<fsummary>Dynamically add a child process to a supervisor.</fsummary>
<type name="startchild_ret"/>
<type name="startchild_err"/>
@@ -503,8 +503,8 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="start_link" arity="2"/>
- <name name="start_link" arity="3"/>
+ <name name="start_link" arity="2" since=""/>
+ <name name="start_link" arity="3" since=""/>
<fsummary>Create a supervisor process.</fsummary>
<type name="startlink_ret"/>
<type name="startlink_err"/>
@@ -584,7 +584,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="terminate_child" arity="2"/>
+ <name name="terminate_child" arity="2" since=""/>
<fsummary>Terminate a child process belonging to a supervisor.</fsummary>
<desc>
<p>Tells supervisor <c><anno>SupRef</anno></c> to terminate the
@@ -621,7 +621,7 @@ child_spec() = #{id => child_id(), % mandatory
</func>
<func>
- <name name="which_children" arity="1"/>
+ <name name="which_children" arity="1" since=""/>
<fsummary>Return information about all children specifications and
child processes belonging to a supervisor.</fsummary>
<desc>
@@ -666,7 +666,7 @@ child_spec() = #{id => child_id(), % mandatory
<funcs>
<func>
- <name>Module:init(Args) -> Result</name>
+ <name since="">Module:init(Args) -> Result</name>
<fsummary>Return a supervisor specification.</fsummary>
<type>
<v>Args = term()</v>
diff --git a/lib/stdlib/doc/src/supervisor_bridge.xml b/lib/stdlib/doc/src/supervisor_bridge.xml
index c4c1b37548..ee5d97fea1 100644
--- a/lib/stdlib/doc/src/supervisor_bridge.xml
+++ b/lib/stdlib/doc/src/supervisor_bridge.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>supervisor_bridge</module>
+ <module since="">supervisor_bridge</module>
<modulesummary>Generic supervisor bridge behavior.</modulesummary>
<description>
<p>This behavior module provides a supervisor bridge, a process
@@ -57,8 +57,8 @@
<funcs>
<func>
- <name name="start_link" arity="2"/>
- <name name="start_link" arity="3"/>
+ <name name="start_link" arity="2" since=""/>
+ <name name="start_link" arity="3" since=""/>
<fsummary>Create a supervisor bridge process.</fsummary>
<desc>
<p>Creates a supervisor bridge process, linked to the calling process,
@@ -133,7 +133,7 @@
<funcs>
<func>
- <name>Module:init(Args) -> Result</name>
+ <name since="">Module:init(Args) -> Result</name>
<fsummary>Initialize process and start subsystem.</fsummary>
<type>
<v>Args = term()</v>
@@ -164,7 +164,7 @@
</func>
<func>
- <name>Module:terminate(Reason, State)</name>
+ <name since="">Module:terminate(Reason, State)</name>
<fsummary>Clean up and stop subsystem.</fsummary>
<type>
<v>Reason = shutdown | term()</v>
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
index 9fe816e33a..c5075f31c5 100644
--- a/lib/stdlib/doc/src/sys.xml
+++ b/lib/stdlib/doc/src/sys.xml
@@ -32,7 +32,7 @@
<rev></rev>
<file>sys.xml</file>
</header>
- <module>sys</module>
+ <module since="">sys</module>
<modulesummary>A functional interface to system messages.</modulesummary>
<description>
<p>This module contains functions for sending system messages used by
@@ -114,6 +114,154 @@
</datatype>
<datatype>
<name name="system_event"/>
+ <desc>
+ <taglist>
+ <tag><c>{in,<anno>Msg</anno>}</c></tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c> and <c>gen_event</c>
+ when the message <c>Msg</c> arrives.
+ </p>
+ </item>
+ <tag><c>{in,<anno>Msg</anno>,<anno>State</anno>}</c></tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c>Msg</c> arrives in state <c>State</c>.
+ </p>
+ <p>
+ For <c>gen_statem</c> the <c><anno>Msg</anno></c> term is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag><c>{out,<anno>Msg</anno>,<anno>To</anno>}</c></tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c> when the reply <c>Msg</c>
+ is sent back to <c>To</c> by returning
+ a <c>{reply,To,Msg}</c> action from the callback module.
+ </p>
+ <p>
+ <c><anno>To</anno></c> is of the same type
+ as the first argument to <c>gen_statem:reply/2</c>.
+ </p>
+ </item>
+ <tag>
+ <c>{out,<anno>Msg</anno>,<anno>To</anno>,<anno>State</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c>
+ when the reply <c><anno>Msg</anno></c>
+ is sent back to <c><anno>To</anno></c>
+ by returning a <c>{reply,...}</c> tuple
+ from the callback module.
+ </p>
+ <p>
+ <c><anno>To</anno></c> is of the same type
+ as the first argument to <c>gen_server:reply/2</c>.
+ </p>
+ <p>
+ <c><anno>State</anno></c> is the new server state.
+ </p>
+ </item>
+ <tag>
+ <c>{noreply,<anno>State</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c>
+ when a <c>{noreply,...}</c> tuple is returned
+ from the callback module.
+ </p>
+ <p>
+ <c><anno>State</anno></c> is the new server state.
+ </p>
+ </item>
+ <tag>
+ <c>{continue,<anno>Continuation</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_server</c>
+ when a <c>{continue,<anno>Continuation</anno>}</c>
+ tuple is returned from the callback module.
+ </p>
+ </item>
+ <tag>
+ <c>{code_change,<anno>Event</anno>,<anno>State</anno>}</c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c><anno>Event</anno></c>
+ arrives in state <c><anno>State</anno></c>
+ as the first event after a code change.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {postpone,<anno>Event</anno>,<anno>State</anno>,<anno>NextState</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c><anno>Event</anno></c>
+ is postponed in state <c><anno>State</anno></c>.
+ <c><anno>NextState</anno></c> is the new state.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {consume,<anno>Event</anno>,<anno>State</anno>,<anno>NextState</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the message <c><anno>Event</anno></c>
+ is consumed in state <c><anno>State</anno></c>.
+ <c><anno>NextState</anno></c> is the new state.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {enter,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the first state <c><anno>State</anno></c> is entered.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {terminate,<anno>Reason</anno>,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when it terminates with reason <c><anno>Reason</anno></c>
+ in state <c><anno>State</anno></c>.
+ </p>
+ </item>
+ </taglist>
+ </desc>
</datatype>
<datatype>
<name name="dbg_opt"/>
@@ -129,8 +277,8 @@
<funcs>
<func>
- <name name="change_code" arity="4"/>
- <name name="change_code" arity="5"/>
+ <name name="change_code" arity="4" since=""/>
+ <name name="change_code" arity="5" since=""/>
<fsummary>Send the code change system message to the process.</fsummary>
<desc>
<p>Tells the process to change code. The process must be
@@ -143,8 +291,8 @@
</func>
<func>
- <name name="get_state" arity="1"/>
- <name name="get_state" arity="2"/>
+ <name name="get_state" arity="1" since="OTP R16B01"/>
+ <name name="get_state" arity="2" since="OTP R16B01"/>
<fsummary>Get the state of the process.</fsummary>
<desc>
<p>Gets the state of the process.</p>
@@ -227,8 +375,8 @@
</func>
<func>
- <name name="get_status" arity="1"/>
- <name name="get_status" arity="2"/>
+ <name name="get_status" arity="1" since=""/>
+ <name name="get_status" arity="2" since=""/>
<fsummary>Get the status of the process.</fsummary>
<desc>
<p>Gets the status of the process.</p>
@@ -265,8 +413,8 @@
</func>
<func>
- <name name="install" arity="2"/>
- <name name="install" arity="3"/>
+ <name name="install" arity="2" since=""/>
+ <name name="install" arity="3" since=""/>
<fsummary>Install a debug function in the process.</fsummary>
<desc>
<p>Enables installation of alternative debug functions. An example of
@@ -283,8 +431,8 @@
</func>
<func>
- <name name="log" arity="2"/>
- <name name="log" arity="3"/>
+ <name name="log" arity="2" since=""/>
+ <name name="log" arity="3" since=""/>
<fsummary>Log system events in memory.</fsummary>
<desc>
<p>Turns the logging of system events on or off. If on, a
@@ -302,8 +450,8 @@
</func>
<func>
- <name name="log_to_file" arity="2"/>
- <name name="log_to_file" arity="3"/>
+ <name name="log_to_file" arity="2" since=""/>
+ <name name="log_to_file" arity="3" since=""/>
<fsummary>Log system events to the specified file.</fsummary>
<desc>
<p>Enables or disables the logging of all system events in text
@@ -315,8 +463,8 @@
</func>
<func>
- <name name="no_debug" arity="1"/>
- <name name="no_debug" arity="2"/>
+ <name name="no_debug" arity="1" since=""/>
+ <name name="no_debug" arity="2" since=""/>
<fsummary>Turn off debugging.</fsummary>
<desc>
<p>Turns off all debugging for the process. This includes
@@ -327,8 +475,8 @@
</func>
<func>
- <name name="remove" arity="2"/>
- <name name="remove" arity="3"/>
+ <name name="remove" arity="2" since=""/>
+ <name name="remove" arity="3" since=""/>
<fsummary>Remove a debug function from the process.</fsummary>
<desc>
<p>Removes an installed debug function from the
@@ -338,8 +486,8 @@
</func>
<func>
- <name name="replace_state" arity="2"/>
- <name name="replace_state" arity="3"/>
+ <name name="replace_state" arity="2" since="OTP R16B01"/>
+ <name name="replace_state" arity="3" since="OTP R16B01"/>
<fsummary>Replace the state of the process.</fsummary>
<desc>
<p>Replaces the state of the process, and returns the new state.</p>
@@ -451,8 +599,8 @@
</func>
<func>
- <name name="resume" arity="1"/>
- <name name="resume" arity="2"/>
+ <name name="resume" arity="1" since=""/>
+ <name name="resume" arity="2" since=""/>
<fsummary>Resume a suspended process.</fsummary>
<desc>
<p>Resumes a suspended process.</p>
@@ -460,8 +608,8 @@
</func>
<func>
- <name name="statistics" arity="2"/>
- <name name="statistics" arity="3"/>
+ <name name="statistics" arity="2" since=""/>
+ <name name="statistics" arity="3" since=""/>
<fsummary>Enable or disable the collections of statistics.</fsummary>
<desc>
<p>Enables or disables the collection of statistics. If
@@ -471,8 +619,8 @@
</func>
<func>
- <name name="suspend" arity="1"/>
- <name name="suspend" arity="2"/>
+ <name name="suspend" arity="1" since=""/>
+ <name name="suspend" arity="2" since=""/>
<fsummary>Suspend the process.</fsummary>
<desc>
<p>Suspends the process. When the process is suspended, it
@@ -482,8 +630,8 @@
</func>
<func>
- <name name="terminate" arity="2"/>
- <name name="terminate" arity="3"/>
+ <name name="terminate" arity="2" since="OTP 18.0"/>
+ <name name="terminate" arity="3" since="OTP 18.0"/>
<fsummary>Terminate the process.</fsummary>
<desc>
<p>Orders the process to terminate with the
@@ -494,8 +642,8 @@
</func>
<func>
- <name name="trace" arity="2"/>
- <name name="trace" arity="3"/>
+ <name name="trace" arity="2" since=""/>
+ <name name="trace" arity="3" since=""/>
<fsummary>Print all system events on <c>standard_io</c>.</fsummary>
<desc>
<p>Prints all system events on <c>standard_io</c>. The events are
@@ -518,7 +666,7 @@
<funcs>
<func>
- <name name="debug_options" arity="1"/>
+ <name name="debug_options" arity="1" since=""/>
<fsummary>Convert a list of options to a debug structure.</fsummary>
<desc>
<p>Can be used by a process that initiates a debug
@@ -529,9 +677,15 @@
</func>
<func>
- <name name="get_debug" arity="3"/>
+ <name name="get_debug" arity="3" since=""/>
<fsummary>Get the data associated with a debug option.</fsummary>
<desc>
+ <warning>
+ <p>
+ <c>get_debug/3</c> is deprecated since it returns
+ data of an internal type only useful for debugging.
+ </p>
+ </warning>
<p>Gets the data associated with a debug option.
<c><anno>Default</anno></c>
is returned if <c><anno>Item</anno></c> is not found. Can be
@@ -541,7 +695,7 @@
</func>
<func>
- <name name="handle_debug" arity="4"/>
+ <name name="handle_debug" arity="4" since=""/>
<fsummary>Generate a system event.</fsummary>
<desc>
<p>This function is called by a process when it generates a
@@ -556,7 +710,7 @@
</func>
<func>
- <name name="handle_system_msg" arity="6"/>
+ <name name="handle_system_msg" arity="6" since=""/>
<fsummary>Take care of system messages.</fsummary>
<desc>
<p>This function is used by a process module to take care of system
@@ -594,7 +748,7 @@
</func>
<func>
- <name name="print_log" arity="1"/>
+ <name name="print_log" arity="1" since=""/>
<fsummary>Print the logged events in the debug structure.</fsummary>
<desc>
<p>Prints the logged system events in the debug structure,
@@ -605,7 +759,19 @@
</func>
<func>
- <name>Module:system_code_change(Misc, Module, OldVsn, Extra) ->
+ <name name="get_log" arity="1" since="OTP-22.0"/>
+ <fsummary>Return the logged events in the debug structure.</fsummary>
+ <desc>
+ <p>
+ Returns the logged system events in the debug structure,
+ that is the last argument to
+ <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name since="">Module:system_code_change(Misc, Module, OldVsn, Extra) ->
{ok, NMisc}</name>
<fsummary>Called when the process is to perform a code change.</fsummary>
<type>
@@ -628,7 +794,7 @@
</func>
<func>
- <name>Module:system_continue(Parent, Debug, Misc) -> none()</name>
+ <name since="">Module:system_continue(Parent, Debug, Misc) -> none()</name>
<fsummary>Called when the process is to continue its execution.</fsummary>
<type>
<v>Parent = pid()</v>
@@ -644,7 +810,7 @@
</func>
<func>
- <name>Module:system_get_state(Misc) -> {ok, State}</name>
+ <name since="OTP 17.0">Module:system_get_state(Misc) -> {ok, State}</name>
<fsummary>Called when the process is to return its current state.
</fsummary>
<type>
@@ -661,7 +827,7 @@
</func>
<func>
- <name>Module:system_replace_state(StateFun, Misc) ->
+ <name since="OTP 17.0">Module:system_replace_state(StateFun, Misc) ->
{ok, NState, NMisc}</name>
<fsummary>Called when the process is to replace its current state.
</fsummary>
@@ -681,7 +847,7 @@
</func>
<func>
- <name>Module:system_terminate(Reason, Parent, Debug, Misc) -> none()</name>
+ <name since="">Module:system_terminate(Reason, Parent, Debug, Misc) -> none()</name>
<fsummary>Called when the process is to terminate.</fsummary>
<type>
<v>Reason = term()</v>
diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml
index e913e33589..165eecfbb0 100644
--- a/lib/stdlib/doc/src/timer.xml
+++ b/lib/stdlib/doc/src/timer.xml
@@ -32,7 +32,7 @@
<rev>D</rev>
<file>timer.xml</file>
</header>
- <module>timer</module>
+ <module since="">timer</module>
<modulesummary>Timer functions.</modulesummary>
<description>
<p>This module provides useful functions related to time. Unless otherwise
@@ -62,7 +62,7 @@
<funcs>
<func>
- <name name="apply_after" arity="4"/>
+ <name name="apply_after" arity="4" since=""/>
<fsummary>Apply <c>Module:Function(Arguments)</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -75,7 +75,7 @@
</func>
<func>
- <name name="apply_interval" arity="4"/>
+ <name name="apply_interval" arity="4" since=""/>
<fsummary>Evaluate <c>Module:Function(Arguments)</c> repeatedly at
intervals of <c>Time</c>.</fsummary>
<desc>
@@ -88,7 +88,7 @@
</func>
<func>
- <name name="cancel" arity="1"/>
+ <name name="cancel" arity="1" since=""/>
<fsummary>Cancel a previously requested time-out identified by
<c>TRef</c>.</fsummary>
<desc>
@@ -101,8 +101,8 @@
</func>
<func>
- <name name="exit_after" arity="2"/>
- <name name="exit_after" arity="3"/>
+ <name name="exit_after" arity="2" since=""/>
+ <name name="exit_after" arity="3" since=""/>
<fsummary>Send an exit signal with <c>Reason</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -117,7 +117,7 @@
</func>
<func>
- <name name="hms" arity="3"/>
+ <name name="hms" arity="3" since=""/>
<fsummary>Convert <c>Hours</c>+<c>Minutes</c>+<c>Seconds</c> to
<c>Milliseconds</c>.</fsummary>
<desc>
@@ -127,7 +127,7 @@
</func>
<func>
- <name name="hours" arity="1"/>
+ <name name="hours" arity="1" since=""/>
<fsummary>Convert <c>Hours</c> to <c>Milliseconds</c>.</fsummary>
<desc>
<p>Returns the number of milliseconds in <c><anno>Hours</anno></c>.</p>
@@ -135,8 +135,8 @@
</func>
<func>
- <name name="kill_after" arity="1"/>
- <name name="kill_after" arity="2"/>
+ <name name="kill_after" arity="1" since=""/>
+ <name name="kill_after" arity="2" since=""/>
<fsummary>Send an exit signal with <c>Reason</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -148,7 +148,7 @@
</func>
<func>
- <name name="minutes" arity="1"/>
+ <name name="minutes" arity="1" since=""/>
<fsummary>Converts <c>Minutes</c> to <c>Milliseconds</c>.</fsummary>
<desc>
<p>Returns the number of milliseconds in
@@ -157,7 +157,7 @@
</func>
<func>
- <name name="now_diff" arity="2"/>
+ <name name="now_diff" arity="2" since=""/>
<fsummary>Calculate time difference between time stamps.</fsummary>
<type_desc variable="Tdiff">In microseconds</type_desc>
<desc>
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="seconds" arity="1"/>
+ <name name="seconds" arity="1" since=""/>
<fsummary>Convert <c>Seconds</c> to <c>Milliseconds</c>.</fsummary>
<desc>
<p>Returns the number of milliseconds in
@@ -182,8 +182,8 @@
</func>
<func>
- <name name="send_after" arity="2"/>
- <name name="send_after" arity="3"/>
+ <name name="send_after" arity="2" since=""/>
+ <name name="send_after" arity="3" since=""/>
<fsummary>Send <c>Message</c> to <c>Pid</c> after a specified
<c>Time</c>.</fsummary>
<desc>
@@ -206,8 +206,8 @@
</func>
<func>
- <name name="send_interval" arity="2"/>
- <name name="send_interval" arity="3"/>
+ <name name="send_interval" arity="2" since=""/>
+ <name name="send_interval" arity="3" since=""/>
<fsummary>Send <c>Message</c> repeatedly at intervals of <c>Time</c>.
</fsummary>
<desc>
@@ -231,7 +231,7 @@
</func>
<func>
- <name name="sleep" arity="1"/>
+ <name name="sleep" arity="1" since=""/>
<fsummary>Suspend the calling process for <c>Time</c> milliseconds.
</fsummary>
<desc>
@@ -244,7 +244,7 @@
</func>
<func>
- <name name="start" arity="0"/>
+ <name name="start" arity="0" since=""/>
<fsummary>Start a global timer server (named <c>timer_server</c>).
</fsummary>
<desc>
@@ -258,9 +258,9 @@
</func>
<func>
- <name name="tc" arity="1"/>
- <name name="tc" arity="2"/>
- <name name="tc" arity="3"/>
+ <name name="tc" arity="1" since="OTP R14B03"/>
+ <name name="tc" arity="2" since="OTP R14B"/>
+ <name name="tc" arity="3" since=""/>
<fsummary>Measure the real time it takes to evaluate <c>apply(Module,
Function, Arguments)</c> or <c>apply(Fun, Arguments)</c>.</fsummary>
<type_desc variable="Time">In microseconds</type_desc>
diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml
index d822aca89c..b7696a4b7e 100644
--- a/lib/stdlib/doc/src/unicode.xml
+++ b/lib/stdlib/doc/src/unicode.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>unicode</module>
+ <module since="">unicode</module>
<modulesummary>Functions for converting Unicode characters.</modulesummary>
<description>
<p>This module contains functions for converting between different character
@@ -139,7 +139,7 @@
<funcs>
<func>
- <name name="bom_to_encoding" arity="1"/>
+ <name name="bom_to_encoding" arity="1" since=""/>
<fsummary>Identify UTF byte order marks in a binary.</fsummary>
<type name="endian"/>
<type_desc variable="Bin">
@@ -156,7 +156,7 @@
</func>
<func>
- <name name="characters_to_binary" arity="1"/>
+ <name name="characters_to_binary" arity="1" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
<p>Same as <c>characters_to_binary(<anno>Data</anno>, unicode,
@@ -165,7 +165,7 @@
</func>
<func>
- <name name="characters_to_binary" arity="2"/>
+ <name name="characters_to_binary" arity="2" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
<p>Same as <c>characters_to_binary(<anno>Data</anno>,
@@ -174,7 +174,7 @@
</func>
<func>
- <name name="characters_to_binary" arity="3"/>
+ <name name="characters_to_binary" arity="3" since=""/>
<fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary>
<desc>
<p>Behaves as <seealso marker="#characters_to_list/2">
@@ -211,7 +211,7 @@
</func>
<func>
- <name name="characters_to_list" arity="1"/>
+ <name name="characters_to_list" arity="1" since=""/>
<fsummary>Convert a collection of characters to a list of Unicode
characters.</fsummary>
<desc>
@@ -220,7 +220,7 @@
</func>
<func>
- <name name="characters_to_list" arity="2"/>
+ <name name="characters_to_list" arity="2" since=""/>
<fsummary>Convert a collection of characters to a list of Unicode
characters.</fsummary>
<desc>
@@ -367,7 +367,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfc_list" arity="1"/>
+ <name name="characters_to_nfc_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of canonical equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -386,7 +386,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfc_binary" arity="1"/>
+ <name name="characters_to_nfc_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of canonical equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -404,7 +404,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfd_list" arity="1"/>
+ <name name="characters_to_nfd_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of canonical equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -423,7 +423,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfd_binary" arity="1"/>
+ <name name="characters_to_nfd_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of canonical equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -441,7 +441,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkc_list" arity="1"/>
+ <name name="characters_to_nfkc_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of canonical equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -460,7 +460,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkc_binary" arity="1"/>
+ <name name="characters_to_nfkc_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of compatibly equivalent
composed Unicode characters.</fsummary>
<desc>
@@ -478,7 +478,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkd_list" arity="1"/>
+ <name name="characters_to_nfkd_list" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a list of compatibly equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -497,7 +497,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="characters_to_nfkd_binary" arity="1"/>
+ <name name="characters_to_nfkd_binary" arity="1" since="OTP 20.0"/>
<fsummary>Normalize characters to a utf8 binary of compatibly equivalent
decomposed Unicode characters.</fsummary>
<desc>
@@ -515,7 +515,7 @@ decode_data(Data) ->
</func>
<func>
- <name name="encoding_to_bom" arity="1"/>
+ <name name="encoding_to_bom" arity="1" since=""/>
<fsummary>Create a binary UTF byte order mark from encoding.</fsummary>
<type_desc variable="Bin">
A <c>binary()</c> such that <c>byte_size(<anno>Bin</anno>) >= 4</c>.
diff --git a/lib/stdlib/doc/src/uri_string.xml b/lib/stdlib/doc/src/uri_string.xml
index 88d4600611..ad443486c5 100644
--- a/lib/stdlib/doc/src/uri_string.xml
+++ b/lib/stdlib/doc/src/uri_string.xml
@@ -27,7 +27,7 @@
<date>2018-02-07</date>
<rev>A</rev>
</header>
- <module>uri_string</module>
+ <module since="OTP 21.0">uri_string</module>
<modulesummary>URI processing functions.</modulesummary>
<description>
<p>This module contains functions for parsing and handling URIs
@@ -150,7 +150,7 @@
<funcs>
<func>
- <name name="compose_query" arity="1"/>
+ <name name="compose_query" arity="1" since="OTP 21.0"/>
<fsummary>Compose urlencoded query string.</fsummary>
<desc>
<p>Composes a form-urlencoded <c><anno>QueryString</anno></c> based on a
@@ -176,7 +176,7 @@
</func>
<func>
- <name name="compose_query" arity="2"/>
+ <name name="compose_query" arity="2" since="OTP 21.0"/>
<fsummary>Compose urlencoded query string.</fsummary>
<desc>
<p>Same as <c>compose_query/1</c> but with an additional
@@ -210,7 +210,7 @@
</func>
<func>
- <name name="dissect_query" arity="1"/>
+ <name name="dissect_query" arity="1" since="OTP 21.0"/>
<fsummary>Dissect query string.</fsummary>
<desc>
<p>Dissects an urlencoded <c><anno>QueryString</anno></c> and returns a
@@ -236,7 +236,7 @@
</func>
<func>
- <name name="normalize" arity="1"/>
+ <name name="normalize" arity="1" since="OTP 21.0"/>
<fsummary>Syntax-based normalization.</fsummary>
<desc>
<p>Transforms an <c><anno>URI</anno></c> into a normalized form
@@ -261,7 +261,7 @@
</func>
<func>
- <name name="normalize" arity="2"/>
+ <name name="normalize" arity="2" since="OTP 21.0"/>
<fsummary>Syntax-based normalization.</fsummary>
<desc>
<p>Same as <c>normalize/1</c> but with an additional
@@ -285,7 +285,7 @@
</func>
<func>
- <name name="parse" arity="1"/>
+ <name name="parse" arity="1" since="OTP 21.0"/>
<fsummary>Parse URI into a map.</fsummary>
<desc>
<p>Parses an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>
@@ -309,7 +309,7 @@
</func>
<func>
- <name name="recompose" arity="1"/>
+ <name name="recompose" arity="1" since="OTP 21.0"/>
<fsummary>Recompose URI.</fsummary>
<desc>
<p>Creates an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> compliant
@@ -332,7 +332,7 @@
</func>
<func>
- <name name="transcode" arity="2"/>
+ <name name="transcode" arity="2" since="OTP 21.0"/>
<fsummary>Transcode URI.</fsummary>
<desc>
<p>Transcodes an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url>
diff --git a/lib/stdlib/doc/src/win32reg.xml b/lib/stdlib/doc/src/win32reg.xml
index f4a4fa1626..5e2aed6062 100644
--- a/lib/stdlib/doc/src/win32reg.xml
+++ b/lib/stdlib/doc/src/win32reg.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>win32reg.xml</file>
</header>
- <module>win32reg</module>
+ <module since="">win32reg</module>
<modulesummary>Provides access to the registry on Windows.</modulesummary>
<description>
<p>This module provides read and write access to the
@@ -112,7 +112,7 @@ hkdd HKEY_DYN_DATA</pre>
<funcs>
<func>
- <name name="change_key" arity="2"/>
+ <name name="change_key" arity="2" since=""/>
<fsummary>Move to a key in the registry.</fsummary>
<desc>
<p>Changes the current key to another key. Works like <c>cd</c>.
@@ -122,7 +122,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="change_key_create" arity="2"/>
+ <name name="change_key_create" arity="2" since=""/>
<fsummary>Move to a key, create it if it is not there.</fsummary>
<desc>
<p>Creates a key, or just changes to it, if it is already there. Works
@@ -133,7 +133,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="close" arity="1"/>
+ <name name="close" arity="1" since=""/>
<fsummary>Close the registry.</fsummary>
<desc>
<p>Closes the registry. After that, the <c><anno>RegHandle</anno></c>
@@ -142,7 +142,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="current_key" arity="1"/>
+ <name name="current_key" arity="1" since=""/>
<fsummary>Return the path to the current key.</fsummary>
<desc>
<p>Returns the path to the current key. This is the equivalent of
@@ -153,7 +153,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="delete_key" arity="1"/>
+ <name name="delete_key" arity="1" since=""/>
<fsummary>Delete the current key.</fsummary>
<desc>
<p>Deletes the current key, if it is valid. Calls the Win32 API
@@ -166,7 +166,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="delete_value" arity="2"/>
+ <name name="delete_value" arity="2" since=""/>
<fsummary>Delete the named value on the current key.</fsummary>
<desc>
<p>Deletes a named value on the current key. The atom <c>default</c> is
@@ -176,7 +176,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="expand" arity="1"/>
+ <name name="expand" arity="1" since=""/>
<fsummary>Expand a string with environment variables.</fsummary>
<desc>
<p>Expands a string containing environment variables between percent
@@ -189,7 +189,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="format_error" arity="1"/>
+ <name name="format_error" arity="1" since=""/>
<fsummary>Convert a POSIX error code to a string.</fsummary>
<desc>
<p>Converts a POSIX error code to a string
@@ -198,7 +198,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="open" arity="1"/>
+ <name name="open" arity="1" since=""/>
<fsummary>Open the registry for reading or writing.</fsummary>
<desc>
<p>Opens the registry for reading or writing. The current key is the
@@ -211,7 +211,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="set_value" arity="3"/>
+ <name name="set_value" arity="3" since=""/>
<fsummary>Set value at the current registry key with specified name.
</fsummary>
<desc>
@@ -230,7 +230,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="sub_keys" arity="1"/>
+ <name name="sub_keys" arity="1" since=""/>
<fsummary>Get subkeys to the current key.</fsummary>
<desc>
<p>Returns a list of subkeys to the current key. Calls the Win32
@@ -240,7 +240,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="value" arity="2"/>
+ <name name="value" arity="2" since=""/>
<fsummary>Get the named value on the current key.</fsummary>
<desc>
<p>Retrieves the named value (or default) on the current key.
@@ -251,7 +251,7 @@ hkdd HKEY_DYN_DATA</pre>
</func>
<func>
- <name name="values" arity="1"/>
+ <name name="values" arity="1" since=""/>
<fsummary>Get all values on the current key.</fsummary>
<desc>
<p>Retrieves a list of all values on the current key. The values
diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml
index 0b5eac1e16..bb2ed7727a 100644
--- a/lib/stdlib/doc/src/zip.xml
+++ b/lib/stdlib/doc/src/zip.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>zip.xml</file>
</header>
- <module>zip</module>
+ <module since="">zip</module>
<modulesummary>Utility for reading and creating 'zip' archives.
</modulesummary>
<description>
@@ -180,7 +180,7 @@
<funcs>
<func>
- <name name="foldl" arity="3"/>
+ <name name="foldl" arity="3" since="OTP R14B"/>
<fsummary>Fold a function over all files in a zip archive.</fsummary>
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>FileInArchive</anno>, <anno>GetInfo
@@ -234,10 +234,10 @@
</func>
<func>
- <name name="list_dir" arity="1"/>
- <name name="list_dir" arity="2"/>
- <name name="table" arity="1" />
- <name name="table" arity="2"/>
+ <name name="list_dir" arity="1" since=""/>
+ <name name="list_dir" arity="2" since=""/>
+ <name name="table" arity="1" since=""/>
+ <name name="table" arity="2" since=""/>
<fsummary>Retrieve the name of all files in a zip archive.</fsummary>
<desc>
<p><c>list_dir/1</c> retrieves all filenames in the zip archive
@@ -263,7 +263,7 @@
</func>
<func>
- <name name="t" arity="1"/>
+ <name name="t" arity="1" since=""/>
<fsummary>Print the name of each file in a zip archive.</fsummary>
<desc>
<p>Prints all filenames in the zip archive <c><anno>Archive</anno></c>
@@ -272,7 +272,7 @@
</func>
<func>
- <name name="tt" arity="1"/>
+ <name name="tt" arity="1" since=""/>
<fsummary>Print name and information for each file in a zip archive.
</fsummary>
<desc>
@@ -283,10 +283,10 @@
</func>
<func>
- <name name="unzip" arity="1"/>
- <name name="unzip" arity="2"/>
- <name name="extract" arity="1"/>
- <name name="extract" arity="2"/>
+ <name name="unzip" arity="1" since=""/>
+ <name name="unzip" arity="2" since=""/>
+ <name name="extract" arity="1" since=""/>
+ <name name="extract" arity="2" since=""/>
<fsummary>Extract files from a zip archive.</fsummary>
<desc>
<p><c>unzip/1</c> extracts all files from a zip archive.</p>
@@ -353,10 +353,10 @@
</func>
<func>
- <name name="zip" arity="2"/>
- <name name="zip" arity="3"/>
- <name name="create" arity="2"/>
- <name name="create" arity="3"/>
+ <name name="zip" arity="2" since=""/>
+ <name name="zip" arity="3" since=""/>
+ <name name="create" arity="2" since=""/>
+ <name name="create" arity="3" since=""/>
<fsummary>Create a zip archive with options.</fsummary>
<desc>
<p>Creates a zip archive containing the files specified in
@@ -481,7 +481,7 @@
</func>
<func>
- <name name="zip_close" arity="1"/>
+ <name name="zip_close" arity="1" since=""/>
<fsummary>Close an open archive.</fsummary>
<desc>
<p>Closes a zip archive, previously opened with
@@ -492,8 +492,8 @@
</func>
<func>
- <name name="zip_get" arity="1"/>
- <name name="zip_get" arity="2"/>
+ <name name="zip_get" arity="1" since=""/>
+ <name name="zip_get" arity="2" since=""/>
<fsummary>Extract files from an open archive.</fsummary>
<desc>
<p>Extracts one or all files from an open archive.</p>
@@ -505,7 +505,7 @@
</func>
<func>
- <name name="zip_list_dir" arity="1"/>
+ <name name="zip_list_dir" arity="1" since=""/>
<fsummary>Return a table of files in open zip archive.</fsummary>
<desc>
<p>Returns the file list of an open zip archive. The first returned
@@ -514,8 +514,8 @@
</func>
<func>
- <name name="zip_open" arity="1"/>
- <name name="zip_open" arity="2"/>
+ <name name="zip_open" arity="1" since=""/>
+ <name name="zip_open" arity="2" since=""/>
<fsummary>Open an archive and return a handle to it.</fsummary>
<desc>
<p>Opens a zip archive, and reads and saves its directory. This
diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl
index 9a600c1972..bb5d450cd6 100644
--- a/lib/stdlib/src/calendar.erl
+++ b/lib/stdlib/src/calendar.erl
@@ -693,14 +693,11 @@ local_offset(SystemTime, Unit) ->
UniversalSecs = datetime_to_gregorian_seconds(UniversalTime),
LocalSecs - UniversalSecs.
+fraction_str(1, _Time) ->
+ "";
fraction_str(Factor, Time) ->
- case Time rem Factor of
- 0 ->
- "";
- Fraction ->
- FS = io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]),
- string:trim(FS, trailing, "0")
- end.
+ Fraction = Time rem Factor,
+ io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]).
fraction(second, _) ->
0;
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index e9ac2fcdff..3ec78a2667 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -831,13 +831,15 @@ bif_clashes(Forms, #lint{nowarn_bif_clash=Nowarn} = St) ->
%% not_deprecated(Forms, State0) -> State
-not_deprecated(Forms, St0) ->
+not_deprecated(Forms, #lint{compile=Opts}=St0) ->
%% There are no line numbers in St0#lint.compile.
MFAsL = [{MFA,L} ||
{attribute, L, compile, Args} <- Forms,
{nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]),
MFA <- lists:flatten([MFAs0])],
- Nowarn = [MFA || {MFA,_L} <- MFAsL],
+ Nowarn = [MFA ||
+ {nowarn_deprecated_function, MFAs0} <- Opts,
+ MFA <- lists:flatten([MFAs0])],
ML = [{M,L} || {{M,_F,_A},L} <- MFAsL, is_atom(M)],
St1 = foldl(fun ({M,L}, St2) ->
check_module_name(M, L, St2)
@@ -2262,8 +2264,7 @@ expr({'fun',Line,Body}, Vt, St) ->
{[],St};
{function,M,F,A} ->
%% New in R15.
- {Bvt, St1} = expr_list([M,F,A], Vt, St),
- {vtupdate(Bvt, Vt),St1}
+ expr_list([M,F,A], Vt, St)
end;
expr({named_fun,_,'_',Cs}, Vt, St) ->
fun_clauses(Cs, Vt, St);
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 9602f0bcd9..4ad94f2507 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -841,7 +841,7 @@ Erlang code.
-type af_record_field(T) :: {'record_field', anno(), af_field_name(), T}.
-type af_map_pattern() ::
- {'map', anno(), [af_assoc_exact(abstract_expr)]}.
+ {'map', anno(), [af_assoc_exact(abstract_expr())]}.
-type abstract_type() :: af_annotated_type()
| af_atom()
@@ -872,7 +872,7 @@ Erlang code.
-type af_fun_type() :: {'type', anno(), 'fun', []}
| {'type', anno(), 'fun', [{'type', anno(), 'any'} |
abstract_type()]}
- | {'type', anno(), 'fun', af_function_type()}.
+ | af_function_type().
-type af_integer_range_type() ::
{'type', anno(), 'range', [af_singleton_integer_type()]}.
@@ -924,10 +924,11 @@ Erlang code.
-type af_function_constraint() :: [af_constraint()].
-type af_constraint() :: {'type', anno(), 'constraint',
- af_lit_atom('is_subtype'),
- [af_type_variable() | abstract_type()]}. % [V, T]
+ [af_lit_atom('is_subtype') |
+ [af_type_variable() | abstract_type()]]}. % [IsSubtype, [V, T]]
-type af_singleton_integer_type() :: af_integer()
+ | af_character()
| af_unary_op(af_singleton_integer_type())
| af_binary_op(af_singleton_integer_type()).
diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl
index caaaf8fa2e..1e18710738 100644
--- a/lib/stdlib/src/gen_fsm.erl
+++ b/lib/stdlib/src/gen_fsm.erl
@@ -411,12 +411,13 @@ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTime
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
[Name, StateName, StateData, Mod, Time, HibernateAfterTimeout], Hib);
{'EXIT', Parent, Reason} ->
- terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug);
+ terminate(
+ Reason, Name, undefined, Msg, Mod, StateName, StateData, Debug);
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout);
_Msg ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
- {Name, StateName}, {in, Msg}),
+ Name, {in, Msg, StateName}),
handle_msg(Msg, Parent, Name, StateName, StateData,
Mod, Time, HibernateAfterTimeout, Debug1)
end.
@@ -431,7 +432,7 @@ system_continue(Parent, Debug, [Name, StateName, StateData, Mod, Time, Hibernate
system_terminate(Reason, _Parent, Debug,
[Name, StateName, StateData, Mod, _Time, _HibernateAfterTimeout]) ->
- terminate(Reason, Name, [], Mod, StateName, StateData, Debug).
+ terminate(Reason, Name, undefined, [], Mod, StateName, StateData, Debug).
system_code_change([Name, StateName, StateData, Mod, Time, HibernateAfterTimeout],
_Module, OldVsn, Extra) ->
@@ -452,7 +453,7 @@ system_replace_state(StateFun, [Name, StateName, StateData, Mod, Time, Hibernate
%% Format debug messages. Print them as the call-back module sees
%% them, not as the real erlang messages. Use trace for that.
%%-----------------------------------------------------------------
-print_event(Dev, {in, Msg}, {Name, StateName}) ->
+print_event(Dev, {in, Msg, StateName}, Name) ->
case Msg of
{'$gen_event', Event} ->
io:format(Dev, "*DBG* ~tp got event ~tp in state ~tw~n",
@@ -461,6 +462,16 @@ print_event(Dev, {in, Msg}, {Name, StateName}) ->
io:format(Dev,
"*DBG* ~tp got all_state_event ~tp in state ~tw~n",
[Name, Event, StateName]);
+ {'$gen_sync_event', {From,_Tag}, Event} ->
+ io:format(Dev,
+ "*DBG* ~tp got sync_event ~tp "
+ "from ~tw in state ~tw~n",
+ [Name, Event, From, StateName]);
+ {'$gen_sync_all_state_event', {From,_Tag}, Event} ->
+ io:format(Dev,
+ "*DBG* ~tp got sync_all_state_event ~tp "
+ "from ~tw in state ~tw~n",
+ [Name, Event, From, StateName]);
{timeout, Ref, {'$gen_timer', Message}} ->
io:format(Dev,
"*DBG* ~tp got timer ~tp in state ~tw~n",
@@ -473,11 +484,11 @@ print_event(Dev, {in, Msg}, {Name, StateName}) ->
io:format(Dev, "*DBG* ~tp got ~tp in state ~tw~n",
[Name, Msg, StateName])
end;
-print_event(Dev, {out, Msg, To, StateName}, Name) ->
+print_event(Dev, {out, Msg, {To,_Tag}, StateName}, Name) ->
io:format(Dev, "*DBG* ~tp sent ~tp to ~tw~n"
" and switched to state ~tw~n",
[Name, Msg, To, StateName]);
-print_event(Dev, return, {Name, StateName}) ->
+print_event(Dev, {noreply, StateName}, Name) ->
io:format(Dev, "*DBG* ~tp switched to state ~tw~n",
[Name, StateName]).
@@ -495,9 +506,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
reply(From, Reply),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, []);
{stop, Reason, NStateData} ->
- terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
+ terminate(Reason, Name, From, Msg, Mod, StateName, NStateData, []);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
- {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
+ {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod,
StateName, NStateData, [])),
reply(From, Reply),
exit(R);
@@ -510,10 +521,10 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
error_logger=>#{tag=>warning_msg}}),
loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, []);
{'EXIT', What} ->
- terminate(What, Name, Msg, Mod, StateName, StateData, []);
+ terminate(What, Name, From, Msg, Mod, StateName, StateData, []);
Reply ->
terminate({bad_return_value, Reply},
- Name, Msg, Mod, StateName, StateData, [])
+ Name, From, Msg, Mod, StateName, StateData, [])
end.
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTimeout, Debug) ->
@@ -521,11 +532,11 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
case catch dispatch(Msg, Mod, StateName, StateData) of
{next_state, NStateName, NStateData} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
- {Name, NStateName}, return),
+ Name, {noreply, NStateName}),
loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, Debug1);
{next_state, NStateName, NStateData, Time1} ->
Debug1 = sys:handle_debug(Debug, fun print_event/3,
- {Name, NStateName}, return),
+ Name, {noreply, NStateName}),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1);
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
Debug1 = reply(Name, From, Reply, Debug, NStateName),
@@ -534,17 +545,18 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi
Debug1 = reply(Name, From, Reply, Debug, NStateName),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1);
{stop, Reason, NStateData} ->
- terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
+ terminate(
+ Reason, Name, From, Msg, Mod, StateName, NStateData, Debug);
{stop, Reason, Reply, NStateData} when From =/= undefined ->
- {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
+ {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod,
StateName, NStateData, Debug)),
_ = reply(Name, From, Reply, Debug, StateName),
exit(R);
{'EXIT', What} ->
- terminate(What, Name, Msg, Mod, StateName, StateData, Debug);
+ terminate(What, Name, From, Msg, Mod, StateName, StateData, Debug);
Reply ->
terminate({bad_return_value, Reply},
- Name, Msg, Mod, StateName, StateData, Debug)
+ Name, From, Msg, Mod, StateName, StateData, Debug)
end.
dispatch({'$gen_event', Event}, Mod, StateName, StateData) ->
@@ -571,24 +583,25 @@ from(_) -> undefined.
reply({To, Tag}, Reply) ->
catch To ! {Tag, Reply}.
-reply(Name, {To, Tag}, Reply, Debug, StateName) ->
- reply({To, Tag}, Reply),
+reply(Name, From, Reply, Debug, StateName) ->
+ reply(From, Reply),
sys:handle_debug(Debug, fun print_event/3, Name,
- {out, Reply, To, StateName}).
+ {out, Reply, From, StateName}).
%%% ---------------------------------------------------
%%% Terminate the server.
%%% ---------------------------------------------------
--spec terminate(term(), _, _, atom(), _, _, _) -> no_return().
+-spec terminate(term(), _, _, _, atom(), _, _, _) -> no_return().
-terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
+terminate(Reason, Name, From, Msg, Mod, StateName, StateData, Debug) ->
case erlang:function_exported(Mod, terminate, 3) of
true ->
case catch Mod:terminate(Reason, StateName, StateData) of
{'EXIT', R} ->
FmtStateData = format_status(terminate, Mod, get(), StateData),
- error_info(R, Name, Msg, StateName, FmtStateData, Debug),
+ error_info(
+ R, Name, From, Msg, StateName, FmtStateData, Debug),
exit(R);
_ ->
ok
@@ -605,29 +618,51 @@ terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
exit(Shutdown);
_ ->
FmtStateData1 = format_status(terminate, Mod, get(), StateData),
- error_info(Reason,Name,Msg,StateName,FmtStateData1,Debug),
+ error_info(
+ Reason, Name, From, Msg, StateName, FmtStateData1, Debug),
exit(Reason)
end.
-error_info(Reason, Name, Msg, StateName, StateData, Debug) ->
+error_info(Reason, Name, From, Msg, StateName, StateData, Debug) ->
+ Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_fsm,terminate},
name=>Name,
last_message=>Msg,
state_name=>StateName,
state_data=>StateData,
- reason=>Reason},
+ log=>Log,
+ reason=>Reason,
+ client_info=>client_stacktrace(From)},
#{domain=>[otp],
report_cb=>fun gen_fsm:format_log/1,
error_logger=>#{tag=>error}}),
- sys:print_log(Debug),
ok.
+client_stacktrace(undefined) ->
+ undefined;
+client_stacktrace({Pid,_Tag}) ->
+ client_stacktrace(Pid);
+client_stacktrace(Pid) when is_pid(Pid), node(Pid) =:= node() ->
+ case process_info(Pid, [current_stacktrace, registered_name]) of
+ undefined ->
+ {Pid,dead};
+ [{current_stacktrace, Stacktrace}, {registered_name, []}] ->
+ {Pid,{Pid,Stacktrace}};
+ [{current_stacktrace, Stacktrace}, {registered_name, Name}] ->
+ {Pid,{Name,Stacktrace}}
+ end;
+client_stacktrace(Pid) when is_pid(Pid) ->
+ {Pid,remote}.
+
+
format_log(#{label:={gen_fsm,terminate},
name:=Name,
last_message:=Msg,
state_name:=StateName,
state_data:=StateData,
- reason:=Reason}) ->
+ log:=Log,
+ reason:=Reason,
+ client_info:=ClientInfo}) ->
Reason1 =
case Reason of
{undef,[{M,F,A,L}|MFAs]} ->
@@ -645,27 +680,39 @@ format_log(#{label:={gen_fsm,terminate},
_ ->
Reason
end,
+ {ClientFmt,ClientArgs} = format_client_log(ClientInfo),
{"** State machine ~tp terminating \n" ++
get_msg_str(Msg) ++
"** When State == ~tp~n"
"** Data == ~tp~n"
- "** Reason for termination = ~n** ~tp~n",
- [Name, get_msg(Msg), StateName, StateData, Reason1]};
+ "** Reason for termination ==~n** ~tp~n" ++
+ case Log of
+ [] -> [];
+ _ -> "** Log ==~n** ~tp~n"
+ end ++ ClientFmt,
+ [Name|error_logger:limit_term(get_msg(Msg))] ++
+ [StateName,
+ error_logger:limit_term(StateData),
+ error_logger:limit_term(Reason1) |
+ case Log of
+ [] -> [];
+ _ -> [[error_logger:limit_term(D) || D <- Log]]
+ end] ++ ClientArgs};
format_log(#{label:={gen_fsm,no_handle_info},
module:=Mod,
message:=Msg}) ->
{"** Undefined handle_info in ~p~n"
"** Unhandled message: ~tp~n",
- [Mod, Msg]}.
+ [Mod, error_logger:limit_term(Msg)]}.
get_msg_str({'$gen_event', _Event}) ->
"** Last event in was ~tp~n";
-get_msg_str({'$gen_sync_event', _Event}) ->
- "** Last sync event in was ~tp~n";
+get_msg_str({'$gen_sync_event', _From, _Event}) ->
+ "** Last sync event in was ~tp from ~tw~n";
get_msg_str({'$gen_all_state_event', _Event}) ->
"** Last event in was ~tp (for all states)~n";
-get_msg_str({'$gen_sync_all_state_event', _Event}) ->
- "** Last sync event in was ~tp (for all states)~n";
+get_msg_str({'$gen_sync_all_state_event', _From, _Event}) ->
+ "** Last sync event in was ~tp (for all states) from ~tw~n";
get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) ->
"** Last timer event in was ~tp~n";
get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
@@ -673,13 +720,24 @@ get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
get_msg_str(_Msg) ->
"** Last message in was ~tp~n".
-get_msg({'$gen_event', Event}) -> Event;
-get_msg({'$gen_sync_event', Event}) -> Event;
-get_msg({'$gen_all_state_event', Event}) -> Event;
-get_msg({'$gen_sync_all_state_event', Event}) -> Event;
-get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> {timeout, Ref, Msg};
-get_msg({timeout, _Ref, {'$gen_event', Event}}) -> Event;
-get_msg(Msg) -> Msg.
+get_msg({'$gen_event', Event}) -> [Event];
+get_msg({'$gen_sync_event', {From,_Tag}, Event}) -> [Event,From];
+get_msg({'$gen_all_state_event', Event}) -> [Event];
+get_msg({'$gen_sync_all_state_event', {From,_Tag}, Event}) -> [Event,From];
+get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> [{timeout, Ref, Msg}];
+get_msg({timeout, _Ref, {'$gen_event', Event}}) -> [Event];
+get_msg(Msg) -> [Msg].
+
+format_client_log(undefined) ->
+ {"", []};
+format_client_log({From,dead}) ->
+ {"** Client ~p is dead~n", [From]};
+format_client_log({From,remote}) ->
+ {"** Client ~p is remote on node ~p~n", [From, node(From)]};
+format_client_log({_From,{Name,Stacktrace}}) ->
+ {"** Client ~tp stacktrace~n"
+ "** ~tp~n",
+ [Name, error_logger:limit_term(Stacktrace)]}.
%%-----------------------------------------------------------------
%% Status information
@@ -689,18 +747,18 @@ format_status(Opt, StatusData) ->
StatusData,
Header = gen:format_status_header("Status for state machine",
Name),
- Log = sys:get_debug(log, Debug, []),
- Specfic = format_status(Opt, Mod, PDict, StateData),
- Specfic = case format_status(Opt, Mod, PDict, StateData) of
- S when is_list(S) -> S;
- S -> [S]
- end,
+ Log = sys:get_log(Debug),
+ Specific =
+ case format_status(Opt, Mod, PDict, StateData) of
+ S when is_list(S) -> S;
+ S -> [S]
+ end,
[{header, Header},
{data, [{"Status", SysState},
{"Parent", Parent},
{"Logged events", Log},
{"StateName", StateName}]} |
- Specfic].
+ Specific].
format_status(Opt, Mod, PDict, State) ->
DefStatus = case Opt of
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index 44e9231ebe..c7b6406f54 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -773,10 +773,10 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout,
terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, Debug)
end.
-reply(Name, {To, Tag}, Reply, State, Debug) ->
- reply({To, Tag}, Reply),
+reply(Name, From, Reply, State, Debug) ->
+ reply(From, Reply),
sys:handle_debug(Debug, fun print_event/3, Name,
- {out, Reply, To, State} ).
+ {out, Reply, From, State} ).
%%-----------------------------------------------------------------
@@ -810,7 +810,7 @@ system_replace_state(StateFun, [Name, State, Mod, Time, HibernateAfterTimeout])
print_event(Dev, {in, Msg}, Name) ->
case Msg of
{'$gen_call', {From, _Tag}, Call} ->
- io:format(Dev, "*DBG* ~tp got call ~tp from ~w~n",
+ io:format(Dev, "*DBG* ~tp got call ~tp from ~tw~n",
[Name, Call, From]);
{'$gen_cast', Cast} ->
io:format(Dev, "*DBG* ~tp got cast ~tp~n",
@@ -818,8 +818,8 @@ print_event(Dev, {in, Msg}, Name) ->
_ ->
io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg])
end;
-print_event(Dev, {out, Msg, To, State}, Name) ->
- io:format(Dev, "*DBG* ~tp sent ~tp to ~w, new state ~tp~n",
+print_event(Dev, {out, Msg, {To,_Tag}, State}, Name) ->
+ io:format(Dev, "*DBG* ~tp sent ~tp to ~tw, new state ~tp~n",
[Name, Msg, To, State]);
print_event(Dev, {noreply, State}, Name) ->
io:format(Dev, "*DBG* ~tp new state ~tp~n", [Name, State]);
@@ -885,16 +885,17 @@ error_info(_Reason, application_controller, _From, _Msg, _Mod, _State, _Debug) -
%% of it instead
ok;
error_info(Reason, Name, From, Msg, Mod, State, Debug) ->
+ Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_server,terminate},
name=>Name,
last_message=>Msg,
state=>format_status(terminate, Mod, get(), State),
+ log=>format_log_state(Mod, Log),
reason=>Reason,
client_info=>client_stacktrace(From)},
#{domain=>[otp],
report_cb=>fun gen_server:format_log/1,
error_logger=>#{tag=>error}}),
- sys:print_log(Debug),
ok.
client_stacktrace(undefined) ->
@@ -917,6 +918,7 @@ format_log(#{label:={gen_server,terminate},
name:=Name,
last_message:=Msg,
state:=State,
+ log:=Log,
reason:=Reason,
client_info:=Client}) ->
Reason1 =
@@ -934,20 +936,30 @@ format_log(#{label:={gen_server,terminate},
end
end;
_ ->
- error_logger:limit_term(Reason)
+ Reason
end,
{ClientFmt,ClientArgs} = format_client_log(Client),
+ [LimitedMsg,LimitedState,LimitedReason|LimitedLog] =
+ [error_logger:limit_term(D) || D <- [Msg,State,Reason1|Log]],
{"** Generic server ~tp terminating \n"
"** Last message in was ~tp~n"
"** When Server state == ~tp~n"
- "** Reason for termination == ~n** ~tp~n" ++ ClientFmt,
- [Name, Msg, error_logger:limit_term(State), Reason1] ++ ClientArgs};
+ "** Reason for termination ==~n** ~tp~n" ++
+ case LimitedLog of
+ [] -> [];
+ _ -> "** Log ==~n** ~tp~n"
+ end ++ ClientFmt,
+ [Name, LimitedMsg, LimitedState, LimitedReason] ++
+ case LimitedLog of
+ [] -> [];
+ _ -> [LimitedLog]
+ end ++ ClientArgs};
format_log(#{label:={gen_server,no_handle_info},
module:=Mod,
message:=Msg}) ->
{"** Undefined handle_info in ~p~n"
"** Unhandled message: ~tp~n",
- [Mod, Msg]}.
+ [Mod, error_logger:limit_term(Msg)]}.
format_client_log(undefined) ->
{"", []};
@@ -958,7 +970,7 @@ format_client_log({From,remote}) ->
format_client_log({_From,{Name,Stacktrace}}) ->
{"** Client ~tp stacktrace~n"
"** ~tp~n",
- [Name, Stacktrace]}.
+ [Name, error_logger:limit_term(Stacktrace)]}.
%%-----------------------------------------------------------------
%% Status information
@@ -966,16 +978,25 @@ format_client_log({_From,{Name,Stacktrace}}) ->
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]] = StatusData,
Header = gen:format_status_header("Status for generic server", Name),
- Log = sys:get_debug(log, Debug, []),
- Specfic = case format_status(Opt, Mod, PDict, State) of
+ Log = sys:get_log(Debug),
+ Specific = case format_status(Opt, Mod, PDict, State) of
S when is_list(S) -> S;
S -> [S]
end,
[{header, Header},
{data, [{"Status", SysState},
{"Parent", Parent},
- {"Logged events", Log}]} |
- Specfic].
+ {"Logged events", format_log_state(Mod, Log)}]} |
+ Specific].
+
+format_log_state(Mod, Log) ->
+ [case Event of
+ {out,Msg,From,State} ->
+ {out,Msg,From,format_status(terminate, Mod, get(), State)};
+ {noreply,State} ->
+ {noreply,format_status(terminate, Mod, get(), State)};
+ _ -> Event
+ end || Event <- Log].
format_status(Opt, Mod, PDict, State) ->
DefStatus = case Opt of
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 24b268cd38..513118a874 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2016-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.
@@ -374,50 +374,51 @@ timeout_event_type(Type) ->
-define(
+ relative_timeout(T),
+ ((is_integer(T) andalso 0 =< (T)) orelse (T) =:= infinity)).
+
+-define(
+ absolute_timeout(T),
+ (is_integer(T) orelse (T) =:= infinity)).
+
+-define(
STACKTRACE(),
element(2, erlang:process_info(self(), current_stacktrace))).
-define(not_sys_debug, []).
%%
%% This is a macro to only evaluate arguments if Debug =/= [].
-%% Debug is evaluated multiple times.
+%% Debug is evaluated 2 times.
-define(
- sys_debug(Debug, NameState, Entry),
+ sys_debug(Debug, Extra, SystemEvent),
case begin Debug end of
?not_sys_debug ->
begin Debug end;
_ ->
- sys_debug(begin Debug end, begin NameState end, begin Entry end)
+ sys_debug(
+ begin Debug end, begin Extra end, begin SystemEvent end)
end).
--record(state,
+-record(params,
{callback_mode = undefined :: callback_mode() | undefined,
state_enter = false :: boolean(),
+ parent :: pid(),
module :: atom(),
- name :: atom(),
- state :: term(),
- data :: term(),
+ name :: atom() | pid(),
+ hibernate_after = infinity :: timeout()
+ }).
+
+-record(state,
+ {state_data = {undefined,undefined} ::
+ {State :: term(),Data :: term()},
postponed = [] :: [{event_type(),term()}],
- %%
- timer_refs = #{} :: % timer ref => the timer's event type
- #{reference() => timeout_event_type()},
- timer_types = #{} :: % timer's event type => timer ref
- #{timeout_event_type() => reference()},
- cancel_timers = 0 :: non_neg_integer(),
- %% We add a timer to both timer_refs and timer_types
- %% when we start it. When we request an asynchronous
- %% timer cancel we remove it from timer_types. When
- %% the timer cancel message arrives we remove it from
- %% timer_refs.
- %%
- hibernate = false :: boolean(),
- hibernate_after = infinity :: timeout()}).
-
--record(trans_opts,
- {hibernate = false,
- postpone = false,
- timeouts_r = [],
- next_events_r = []}).
+ timers = {#{},#{}} ::
+ {%% TimerRef => TimeoutType
+ TimerRefs :: #{reference() => timeout_event_type()},
+ %% TimeoutType => TimerRef
+ TimeoutTypes :: #{timeout_event_type() => reference()}},
+ hibernate = false :: boolean()
+ }).
%%%==========================================================================
%%% API
@@ -586,7 +587,12 @@ enter_loop(Module, Opts, State, Data, Server_or_Actions) ->
enter_loop(Module, Opts, State, Data, Server, Actions) ->
is_atom(Module) orelse error({atom,Module}),
Parent = gen:get_parent(),
- enter(Module, Opts, State, Data, Server, Actions, Parent).
+ Name = gen:get_proc_name(Server),
+ Debug = gen:debug_options(Name, Opts),
+ HibernateAfterTimeout = gen:hibernate_after(Opts),
+ enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, Actions).
%%---------------------------------------------------------------------------
%% API helpers
@@ -658,36 +664,29 @@ send(Proc, Msg) ->
ok.
%% Here the init_it/6 and enter_loop/5,6,7 functions converge
-enter(Module, Opts, State, Data, Server, Actions, Parent) ->
+enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, Actions) ->
%% The values should already have been type checked
- Name = gen:get_proc_name(Server),
- Debug = gen:debug_options(Name, Opts),
- HibernateAfterTimeout = gen:hibernate_after(Opts),
- Events = [],
- Event = {internal,init_state},
+ Q = [{internal,init_state}],
%% We enforce {postpone,false} to ensure that
%% our fake Event gets discarded, thought it might get logged
- NewActions = listify(Actions) ++ [{postpone,false}],
- S =
- #state{
+ Actions_1 = listify(Actions) ++ [{postpone,false}],
+ P =
+ #params{
+ parent = Parent,
module = Module,
name = Name,
- state = State,
- data = Data,
hibernate_after = HibernateAfterTimeout},
- CallEnter = true,
- NewDebug = ?sys_debug(Debug, {Name,State}, {enter,Event,State}),
- case call_callback_mode(S) of
- #state{} = NewS ->
- loop_event_actions_list(
- Parent, NewDebug, NewS,
- Events, Event, State, Data, false,
- NewActions, CallEnter);
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, NewDebug,
- S, [Event|Events])
- end.
+ S = #state{state_data = {State,Data}},
+ Debug_1 = ?sys_debug(Debug, Name, {enter,State}),
+ loop_callback_mode(
+ P, Debug_1, S, Q, {State,Data},
+ %% Tunneling Actions through CallbackEvent here...
+ %% Special path to go to action handling, after first
+ %% finding out the callback mode. CallbackEvent is
+ %% a 2-tuple and Actions a list, which achieves this distinction.
+ Actions_1).
%%%==========================================================================
%%% gen callbacks
@@ -695,34 +694,46 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
init_it(Starter, self, ServerRef, Module, Args, Opts) ->
init_it(Starter, self(), ServerRef, Module, Args, Opts);
init_it(Starter, Parent, ServerRef, Module, Args, Opts) ->
+ Name = gen:get_proc_name(ServerRef),
+ Debug = gen:debug_options(Name, Opts),
+ HibernateAfterTimeout = gen:hibernate_after(Opts),
try Module:init(Args) of
Result ->
- init_result(Starter, Parent, ServerRef, Module, Result, Opts)
+ init_result(
+ Starter, Parent, ServerRef, Module, Result,
+ Name, Debug, HibernateAfterTimeout)
catch
Result ->
- init_result(Starter, Parent, ServerRef, Module, Result, Opts);
+ init_result(
+ Starter, Parent, ServerRef, Module, Result,
+ Name, Debug, HibernateAfterTimeout);
Class:Reason:Stacktrace ->
- Name = gen:get_proc_name(ServerRef),
gen:unregister_name(ServerRef),
proc_lib:init_ack(Starter, {error,Reason}),
error_info(
- Class, Reason, Stacktrace,
- #state{name = Name},
- []),
+ Class, Reason, Stacktrace, Debug,
+ #params{parent = Parent, name = Name, module = Module},
+ #state{}, []),
erlang:raise(Class, Reason, Stacktrace)
end.
%%---------------------------------------------------------------------------
%% gen callbacks helpers
-init_result(Starter, Parent, ServerRef, Module, Result, Opts) ->
+init_result(
+ Starter, Parent, ServerRef, Module, Result,
+ Name, Debug, HibernateAfterTimeout) ->
case Result of
{ok,State,Data} ->
proc_lib:init_ack(Starter, {ok,self()}),
- enter(Module, Opts, State, Data, ServerRef, [], Parent);
+ enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, []);
{ok,State,Data,Actions} ->
proc_lib:init_ack(Starter, {ok,self()}),
- enter(Module, Opts, State, Data, ServerRef, Actions, Parent);
+ enter(
+ Parent, Debug, Module, Name, HibernateAfterTimeout,
+ State, Data, Actions);
{stop,Reason} ->
gen:unregister_name(ServerRef),
proc_lib:init_ack(Starter, {error,Reason}),
@@ -732,31 +743,34 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) ->
proc_lib:init_ack(Starter, ignore),
exit(normal);
_ ->
- Name = gen:get_proc_name(ServerRef),
gen:unregister_name(ServerRef),
Error = {bad_return_from_init,Result},
proc_lib:init_ack(Starter, {error,Error}),
error_info(
- error, Error, ?STACKTRACE(),
- #state{name = Name},
- []),
+ error, Error, ?STACKTRACE(), Debug,
+ #params{parent = Parent, name = Name, module = Module},
+ #state{}, []),
exit(Error)
end.
%%%==========================================================================
%%% sys callbacks
+%%%
+%%% We use {P,S} as state (Misc) for the sys module,
+%%% wrap/unwrap it for the server loop* and update
+%%% P#params{parent = Parent}.
-system_continue(Parent, Debug, S) ->
- loop(Parent, Debug, S).
+system_continue(Parent, Debug, {P,S}) ->
+ loop(update_parent(P, Parent), Debug, S).
-system_terminate(Reason, _Parent, Debug, S) ->
- terminate(exit, Reason, ?STACKTRACE(), Debug, S, []).
+system_terminate(Reason, Parent, Debug, {P,S}) ->
+ terminate(
+ exit, Reason, ?STACKTRACE(),
+ update_parent(P, Parent), Debug, S, []).
system_code_change(
- #state{
- module = Module,
- state = State,
- data = Data} = S,
+ {#params{module = Module} = P,
+ #state{state_data = {State,Data}} = S},
_Mod, OldVsn, Extra) ->
case
try Module:code_change(OldVsn, State, Data, Extra)
@@ -766,44 +780,54 @@ system_code_change(
of
{ok,NewState,NewData} ->
{ok,
- S#state{
- callback_mode = undefined,
- state = NewState,
- data = NewData}};
+ {P#params{callback_mode = undefined},
+ S#state{state_data = {NewState,NewData}}}};
{ok,_} = Error ->
error({case_clause,Error});
Error ->
Error
end.
-system_get_state(#state{state = State, data = Data}) ->
- {ok,{State,Data}}.
+system_get_state({_P,#state{state_data = State_Data}}) ->
+ {ok,State_Data}.
system_replace_state(
- StateFun,
- #state{
- state = State,
- data = Data} = S) ->
- {NewState,NewData} = Result = StateFun({State,Data}),
- {ok,Result,S#state{state = NewState, data = NewData}}.
+ StateFun, {P,#state{state_data = State_Data} = S}) ->
+ %%
+ NewState_NewData = StateFun(State_Data),
+ {ok,NewState_NewData,{P,S#state{state_data = NewState_NewData}}}.
format_status(
Opt,
[PDict,SysState,Parent,Debug,
- #state{name = Name, postponed = P} = S]) ->
+ {#params{name = Name} = P,
+ #state{postponed = Postponed} = S}]) ->
Header = gen:format_status_header("Status for state machine", Name),
- Log = sys:get_debug(log, Debug, []),
+ Log = sys:get_log(Debug),
[{header,Header},
{data,
[{"Status",SysState},
{"Parent",Parent},
{"Logged Events",Log},
- {"Postponed",P}]} |
- case format_status(Opt, PDict, S) of
+ {"Postponed",Postponed}]} |
+ case format_status(Opt, PDict, update_parent(P, Parent), S) of
L when is_list(L) -> L;
T -> [T]
end].
+%% Update #params.parent only if it differs. This should not
+%% be possible today (OTP-22.0), but could happen for example
+%% if someone implements changing a server's parent
+%% in a new sys call.
+-compile({inline, update_parent/2}).
+update_parent(P, Parent) ->
+ case P of
+ #params{parent = Parent} ->
+ P;
+ #params{} ->
+ P#params{parent = Parent}
+ end.
+
%%---------------------------------------------------------------------------
%% Format debug messages. Print them as the call-back module sees
%% them, not as the real erlang messages. Use trace for that.
@@ -812,34 +836,46 @@ format_status(
sys_debug(Debug, NameState, Entry) ->
sys:handle_debug(Debug, fun print_event/3, NameState, Entry).
-print_event(Dev, {in,Event}, {Name,State}) ->
- io:format(
- Dev, "*DBG* ~tp receive ~ts in state ~tp~n",
- [Name,event_string(Event),State]);
-print_event(Dev, {out,Reply,{To,_Tag}}, {Name,State}) ->
- io:format(
- Dev, "*DBG* ~tp send ~tp to ~p from state ~tp~n",
- [Name,Reply,To,State]);
-print_event(Dev, {terminate,Reason}, {Name,State}) ->
- io:format(
- Dev, "*DBG* ~tp terminate ~tp in state ~tp~n",
- [Name,Reason,State]);
-print_event(Dev, {Tag,Event,NextState}, {Name,State}) ->
- StateString =
- case NextState of
- State ->
- io_lib:format("~tp", [State]);
- _ ->
- io_lib:format("~tp => ~tp", [State,NextState])
- end,
- io:format(
- Dev, "*DBG* ~tp ~tw ~ts in state ~ts~n",
- [Name,Tag,event_string(Event),StateString]).
+print_event(Dev, SystemEvent, Name) ->
+ case SystemEvent of
+ {in,Event,State} ->
+ io:format(
+ Dev, "*DBG* ~tp receive ~ts in state ~tp~n",
+ [Name,event_string(Event),State]);
+ {code_change,Event,State} ->
+ io:format(
+ Dev, "*DBG* ~tp receive ~ts after code change in state ~tp~n",
+ [Name,event_string(Event),State]);
+ {out,Reply,{To,_Tag}} ->
+ io:format(
+ Dev, "*DBG* ~tp send ~tp to ~tw~n",
+ [Name,Reply,To]);
+ {enter,State} ->
+ io:format(
+ Dev, "*DBG* ~tp enter in state ~tp~n",
+ [Name,State]);
+ {terminate,Reason,State} ->
+ io:format(
+ Dev, "*DBG* ~tp terminate ~tp in state ~tp~n",
+ [Name,Reason,State]);
+ {Tag,Event,State,NextState}
+ when Tag =:= postpone; Tag =:= consume ->
+ StateString =
+ case NextState of
+ State ->
+ io_lib:format("~tp", [State]);
+ _ ->
+ io_lib:format("~tp => ~tp", [State,NextState])
+ end,
+ io:format(
+ Dev, "*DBG* ~tp ~tw ~ts in state ~ts~n",
+ [Name,Tag,event_string(Event),StateString])
+ end.
event_string(Event) ->
case Event of
{{call,{Pid,_Tag}},Request} ->
- io_lib:format("call ~tp from ~w", [Request,Pid]);
+ io_lib:format("call ~tp from ~tw", [Request,Pid]);
{EventType,EventContent} ->
io_lib:format("~tw ~tp", [EventType,EventContent])
end.
@@ -847,964 +883,1169 @@ event_string(Event) ->
%%%==========================================================================
%%% Internal callbacks
-wakeup_from_hibernate(Parent, Debug, S) ->
+wakeup_from_hibernate(P, Debug, S) ->
%% It is a new message that woke us up so we have to receive it now
- loop_receive(Parent, Debug, S).
+ loop_receive(P, Debug, S).
%%%==========================================================================
-%%% State Machine engine implementation of proc_lib/gen server
+%%% State Machine engine implementation on proc_lib/gen
%% Server loop, consists of all loop* functions
%% and detours through sys:handle_system_message/7 and proc_lib:hibernate/3
+%%
+%% The loop tries to keep all temporary values in arguments
+%% and takes shortcuts for ?not_sys_debug, empty lists, etc.
+%% The engine state #state{} is picked apart during the loop,
+%% new values are kept in arguments, and a new #state{} is
+%% composed at the end of the loop. #params{} collect engine
+%% state fields that rarely changes.
+%%
+%% The loop is optimized a bit for staying in the loop, assuming that
+%% system events are rare. So a detour to sys requires re-packing
+%% of the engine state.
%% Entry point for system_continue/3
-loop(Parent, Debug, #state{hibernate = true, cancel_timers = 0} = S) ->
- loop_hibernate(Parent, Debug, S);
-loop(Parent, Debug, S) ->
- loop_receive(Parent, Debug, S).
+%%
+loop(P, Debug, #state{hibernate = true} = S) ->
+ loop_hibernate(P, Debug, S);
+loop(P, Debug, S) ->
+ loop_receive(P, Debug, S).
-loop_hibernate(Parent, Debug, S) ->
+%% Go to hibernation
+%%
+loop_hibernate(P, Debug, S) ->
%%
%% Does not return but restarts process at
%% wakeup_from_hibernate/3 that jumps to loop_receive/3
%%
- proc_lib:hibernate(
- ?MODULE, wakeup_from_hibernate, [Parent,Debug,S]),
+ proc_lib:hibernate(?MODULE, wakeup_from_hibernate, [P, Debug, S]),
error(
{should_not_have_arrived_here_but_instead_in,
- {wakeup_from_hibernate,3}}).
+ {?MODULE,wakeup_from_hibernate,3}}).
+
%% Entry point for wakeup_from_hibernate/3
+%%
+%% Receive a new process message
+%%
loop_receive(
- Parent, Debug, #state{hibernate_after = HibernateAfterTimeout} = S) ->
+ #params{hibernate_after = HibernateAfterTimeout} = P, Debug, S) ->
%%
receive
Msg ->
case Msg of
+ {'$gen_call',From,Request} ->
+ loop_receive_result(P, Debug, S, {{call,From},Request});
+ {'$gen_cast',Cast} ->
+ loop_receive_result(P, Debug, S, {cast,Cast});
+ %%
+ {timeout,TimerRef,TimeoutMsg} ->
+ {TimerRefs,TimeoutTypes} = S#state.timers,
+ case TimerRefs of
+ #{TimerRef := TimeoutType} ->
+ %% Our timer
+ Timers =
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)},
+ S_1 = S#state{timers = Timers},
+ loop_receive_result(
+ P, Debug, S_1, {TimeoutType,TimeoutMsg});
+ #{} ->
+ loop_receive_result(P, Debug, S, {info,Msg})
+ end;
+ %%
{system,Pid,Req} ->
%% Does not return but tail recursively calls
%% system_continue/3 that jumps to loop/3
sys:handle_system_msg(
- Req, Pid, Parent, ?MODULE, Debug, S,
+ Req, Pid, P#params.parent, ?MODULE, Debug,
+ {P,S},
S#state.hibernate);
- {'EXIT',Parent,Reason} = EXIT ->
- %% EXIT is not a 2-tuple therefore
- %% not an event but this will stand out
- %% in the crash report...
- Q = [EXIT],
- terminate(exit, Reason, ?STACKTRACE(), Debug, S, Q);
- {timeout,TimerRef,TimerMsg} ->
- #state{
- timer_refs = TimerRefs,
- timer_types = TimerTypes} = S,
- case TimerRefs of
- #{TimerRef := TimerType} ->
- %% We know of this timer; is it a running
- %% timer or a timer being cancelled that
- %% managed to send a late timeout message?
- case TimerTypes of
- #{TimerType := TimerRef} ->
- %% The timer type maps back to this
- %% timer ref, so it was a running timer
- %% Unregister the triggered timeout
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewTimerTypes =
- maps:remove(TimerType, TimerTypes),
- loop_receive_result(
- Parent, Debug,
- S#state{
- timer_refs = NewTimerRefs,
- timer_types = NewTimerTypes},
- TimerType, TimerMsg);
- _ ->
- %% This was a late timeout message
- %% from timer being cancelled, so
- %% ignore it and expect a cancel_timer
- %% msg shortly
- loop_receive(Parent, Debug, S)
- end;
- _ ->
- %% Not our timer; present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
- {cancel_timer,TimerRef,_} ->
- #state{
- timer_refs = TimerRefs,
- cancel_timers = CancelTimers,
- hibernate = Hibernate} = S,
- case TimerRefs of
- #{TimerRef := _} ->
- %% We must have requested a cancel
- %% of this timer so it is already
- %% removed from TimerTypes
- NewTimerRefs =
- maps:remove(TimerRef, TimerRefs),
- NewCancelTimers = CancelTimers - 1,
- NewS =
- S#state{
- timer_refs = NewTimerRefs,
- cancel_timers = NewCancelTimers},
- if
- Hibernate =:= true, NewCancelTimers =:= 0 ->
- %% No more cancel_timer msgs to expect;
- %% we can hibernate
- loop_hibernate(Parent, Debug, NewS);
- NewCancelTimers >= 0 -> % Assert
- loop_receive(Parent, Debug, NewS)
- end;
- _ ->
- %% Not our cancel_timer msg;
- %% present it as an event
- loop_receive_result(Parent, Debug, S, info, Msg)
- end;
- _ ->
- %% External msg
- case Msg of
- {'$gen_call',From,Request} ->
- loop_receive_result(
- Parent, Debug, S, {call,From}, Request);
- {'$gen_cast',Cast} ->
- loop_receive_result(Parent, Debug, S, cast, Cast);
+ {'EXIT',Pid,Reason} ->
+ case P#params.parent of
+ Pid ->
+ terminate(
+ exit, Reason, ?STACKTRACE(), P, Debug, S, []);
_ ->
- loop_receive_result(Parent, Debug, S, info, Msg)
- end
+ loop_receive_result(P, Debug, S, {info,Msg})
+ end;
+ %%
+ _ ->
+ loop_receive_result(P, Debug, S, {info,Msg})
end
after
- HibernateAfterTimeout ->
- loop_hibernate(Parent, Debug, S)
+ HibernateAfterTimeout ->
+ loop_hibernate(P, Debug, S)
end.
-loop_receive_result(Parent, ?not_sys_debug, S, Type, Content) ->
+%% We have received an event
+%%
+loop_receive_result(P, ?not_sys_debug = Debug, S, Event) ->
%% Here is the queue of not yet handled events created
Events = [],
- loop_event(Parent, ?not_sys_debug, S, Events, Type, Content);
+ loop_event(P, Debug, S, Event, Events);
loop_receive_result(
- Parent, Debug, #state{name = Name, state = State} = S, Type, Content) ->
- NewDebug = sys_debug(Debug, {Name,State}, {in,{Type,Content}}),
+ #params{name = Name, callback_mode = CallbackMode} = P, Debug,
+ #state{state_data = {State,_Data}} = S, Event) ->
+ Debug_1 =
+ case CallbackMode of
+ undefined ->
+ sys_debug(Debug, Name, {code_change,Event,State});
+ _ ->
+ sys_debug(Debug, Name, {in,Event,State})
+ end,
%% Here is the queue of not yet handled events created
Events = [],
- loop_event(Parent, NewDebug, S, Events, Type, Content).
+ loop_event(P, Debug_1, S, Event, Events).
-%% Entry point for handling an event, received or enqueued
+%% Handle one event; received or enqueued
+%%
loop_event(
- Parent, Debug, #state{hibernate = Hibernate} = S,
- Events, Type, Content) ->
+ P, Debug, #state{hibernate = true} = S, Event, Events) ->
%%
- case Hibernate of
- true ->
- %%
- %% If (this old) Hibernate is true here it can only be
- %% because it was set from an event action
- %% and we did not go into hibernation since there were
- %% events in queue, so we do what the user
- %% might rely on i.e collect garbage which
- %% would have happened if we actually hibernated
- %% and immediately was awakened.
- %%
- _ = garbage_collect(),
- loop_event_state_function(
- Parent, Debug, S, Events, Type, Content);
- false ->
- loop_event_state_function(
- Parent, Debug, S, Events, Type, Content)
- end.
+ %% If (this old) Hibernate is true here it can only be
+ %% because it was set from an event action
+ %% and we did not go into hibernation since there were
+ %% events in queue, so we do what the user
+ %% might rely on i.e collect garbage which
+ %% would have happened if we actually hibernated
+ %% and immediately was awakened.
+ %%
+ _ = garbage_collect(),
+ loop_event_handler(P, Debug, S, Event, Events);
+loop_event(P, Debug, S, Event, Events) ->
+ loop_event_handler(P, Debug, S, Event, Events).
-%% Call the state function
-loop_event_state_function(
- Parent, Debug,
- #state{state = State, data = Data} = S,
- Events, Type, Content) ->
+%% Call the state function, eventually
+%%
+-compile({inline, [loop_event_handler/5]}).
+loop_event_handler(
+ P, Debug, #state{state_data = State_Data} = S, Event, Events) ->
%%
%% The field 'hibernate' in S is now invalid and will be
- %% restored when looping back to loop/3 or loop_event/6.
+ %% restored when looping back to loop/3 or loop_event/5.
%%
- Event = {Type,Content},
- TransOpts = false,
- case call_state_function(S, Type, Content, State, Data) of
- {Result, NewS} ->
- loop_event_result(
- Parent, Debug, NewS,
- Events, Event, State, Data, TransOpts, Result);
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
- end.
+ Q = [Event|Events],
+ loop_callback_mode(P, Debug, S, Q, State_Data, Event).
-%% Make a state enter call to the state function
-loop_event_state_enter(
- Parent, Debug, #state{state = PrevState} = S,
- Events, Event, NextState, NewData, TransOpts) ->
+%% Figure out the callback mode
+%%
+loop_callback_mode(
+ #params{callback_mode = undefined} = P, Debug, S,
+ Q, State_Data, CallbackEvent) ->
%%
- case call_state_function(S, enter, PrevState, NextState, NewData) of
- {Result, NewS} ->
- loop_event_result(
- Parent, Debug, NewS,
- Events, Event, NextState, NewData, TransOpts, Result);
- [Class,Reason,Stacktrace] ->
+ Module = P#params.module,
+ try Module:callback_mode() of
+ CallbackMode ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, CallbackEvent,
+ CallbackMode, listify(CallbackMode), undefined, false)
+ catch
+ CallbackMode ->
+ loop_callback_mode_result(
+ P, Debug, S,
+ Q, State_Data, CallbackEvent,
+ CallbackMode, listify(CallbackMode), undefined, false);
+ Class:Reason:Stacktrace ->
terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
+ Class, Reason, Stacktrace, P, Debug, S, Q)
+ end;
+loop_callback_mode(P, Debug, S, Q, State_Data, CallbackEvent) ->
+ loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent).
+
+%% Check the result of Module:callback_mode()
+%%
+loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, [H|T], NewCallbackMode, NewStateEnter) ->
+ %%
+ case callback_mode(H) of
+ true ->
+ loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, T, H, NewStateEnter);
+ false ->
+ case state_enter(H) of
+ true ->
+ loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, T, NewCallbackMode, true);
+ false ->
+ terminate(
+ error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE(),
+ P, Debug, S, Q)
+ end
+ end;
+loop_callback_mode_result(
+ P, Debug, S, Q, State_Data, CallbackEvent,
+ CallbackMode, [], NewCallbackMode, NewStateEnter) ->
+ %%
+ case NewCallbackMode of
+ undefined ->
+ terminate(
+ error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE(),
+ P, Debug, S, Q);
+ _ ->
+ P_1 =
+ P#params{
+ callback_mode = NewCallbackMode,
+ state_enter = NewStateEnter},
+ loop_state_callback(
+ P_1, Debug, S, Q, State_Data, CallbackEvent)
end.
-%% Process the result from the state function.
-%% When TransOpts =:= false it was a state function call,
-%% otherwise it is an option tuple and it was a state enter call.
+
+%% Make a state enter call to the state function, we loop back here
+%% from further down if state enter calls are enabled
+%%
+loop_state_enter(
+ P, Debug, #state{state_data = {PrevState,_PrevData}} = S,
+ Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone) ->
+ %%
+ StateCall = false,
+ CallbackEvent = {enter,PrevState},
+ loop_state_callback(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, CallbackEvent).
+
+%% Make a state call (not state enter call) to the state function
+%%
+loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent) ->
+ NextEventsR = [],
+ Hibernate = false,
+ TimeoutsR = [],
+ Postpone = false,
+ StateCall = true,
+ loop_state_callback(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, CallbackEvent).
+%%
+loop_state_callback(
+ #params{callback_mode = CallbackMode, module = Module} = P,
+ Debug, S, Q, {State,Data} = State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, {Type,Content}) ->
+ try
+ case CallbackMode of
+ state_functions ->
+ Module:State(Type, Content, Data);
+ handle_event_function ->
+ Module:handle_event(Type, Content, State, Data)
+ end
+ of
+ Result ->
+ loop_state_callback_result(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Result)
+ catch
+ Result ->
+ loop_state_callback_result(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Result);
+ Class:Reason:Stacktrace ->
+ terminate(Class, Reason, Stacktrace, P, Debug, S, Q)
+ end;
+loop_state_callback(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Actions) when is_list(Actions) ->
+ %% Tunneled actions from enter/8
+ CallEnter = true,
+ loop_actions_list(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions).
+
+%% Process the result from the state function
%%
-loop_event_result(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts, Result) ->
+loop_state_callback_result(
+ P, Debug, S, Q, {State,_Data} = State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ StateCall, Result) ->
%%
case Result of
{next_state,State,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- [], false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false);
{next_state,NextState,NewData}
- when TransOpts =:= false ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewData, TransOpts,
- [], true);
+ when StateCall ->
+ loop_actions(
+ P, Debug, S, Q, {NextState,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true);
{next_state,_NextState,_NewData} ->
terminate(
error,
{bad_state_enter_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
{next_state,State,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- Actions, false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false, StateCall, Actions);
{next_state,NextState,NewData,Actions}
- when TransOpts =:= false ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewData, TransOpts,
- Actions, true);
+ when StateCall ->
+ loop_actions(
+ P, Debug, S, Q, {NextState,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true, StateCall, Actions);
{next_state,_NextState,_NewData,_Actions} ->
terminate(
error,
{bad_state_enter_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
%%
{keep_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- [], false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false);
{keep_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- Actions, false);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false, StateCall, Actions);
%%
keep_state_and_data ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- [], false);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false);
{keep_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- Actions, false);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ false, StateCall, Actions);
%%
{repeat_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- [], true);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true);
{repeat_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, NewData, TransOpts,
- Actions, true);
+ loop_actions(
+ P, Debug, S, Q, {State,NewData},
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true, StateCall, Actions);
%%
repeat_state_and_data ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- [], true);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true);
{repeat_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S,
- Events, Event, State, Data, TransOpts,
- Actions, true);
+ loop_actions(
+ P, Debug, S, Q, State_Data,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ true, StateCall, Actions);
%%
stop ->
terminate(
- exit, normal, ?STACKTRACE(), Debug,
+ exit, normal, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
{stop,Reason} ->
terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q);
{stop,Reason,NewData} ->
terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = NewData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events]);
+ state_data = {State,NewData},
+ hibernate = Hibernate},
+ Q);
%%
{stop_and_reply,Reason,Replies} ->
reply_then_terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events], Replies);
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q, Replies);
{stop_and_reply,Reason,Replies,NewData} ->
reply_then_terminate(
- exit, Reason, ?STACKTRACE(), Debug,
+ exit, Reason, ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = NewData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events], Replies);
+ state_data = {State,NewData},
+ hibernate = Hibernate},
+ Q, Replies);
%%
_ ->
terminate(
error,
{bad_return_from_state_function,Result},
- ?STACKTRACE(), Debug,
+ ?STACKTRACE(), P, Debug,
S#state{
- state = State, data = Data,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events])
+ state_data = State_Data,
+ hibernate = Hibernate},
+ Q)
end.
%% Ensure that Actions are a list
-loop_event_actions(
- Parent, Debug, S,
- Events, Event, NextState, NewerData, TransOpts,
- Actions, CallEnter) ->
- loop_event_actions_list(
- Parent, Debug, S,
- Events, Event, NextState, NewerData, TransOpts,
- listify(Actions), CallEnter).
-
-%% Process actions from the state function
-loop_event_actions_list(
- Parent, Debug, #state{state_enter = StateEnter} = S,
- Events, Event, NextState, NewerData, TransOpts,
- Actions, CallEnter) ->
+%%
+loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, _StateCall, []) ->
+ loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter);
+loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions) ->
%%
- case parse_actions(TransOpts, Debug, S, Actions) of
- {NewDebug,NewTransOpts}
- when StateEnter, CallEnter ->
- loop_event_state_enter(
- Parent, NewDebug, S,
- Events, Event, NextState, NewerData, NewTransOpts);
- {NewDebug,NewTransOpts} ->
- loop_event_done(
- Parent, NewDebug, S,
- Events, Event, NextState, NewerData, NewTransOpts);
- [Class,Reason,Stacktrace,NewDebug] ->
- terminate(
- Class, Reason, Stacktrace, NewDebug,
- S#state{
- state = NextState,
- data = NewerData,
- hibernate = hibernate_in_trans_opts(TransOpts)},
- [Event|Events])
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, listify(Actions)).
+%%
+%% Shortcut for no actions
+-compile({inline, [loop_actions/10]}).
+loop_actions(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter) ->
+ %%
+ %% Shortcut for no actions
+ case CallEnter andalso P#params.state_enter of
+ true ->
+ loop_state_enter(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone);
+ false ->
+ loop_state_transition(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone)
end.
--compile({inline, [hibernate_in_trans_opts/1]}).
-hibernate_in_trans_opts(false) ->
- (#trans_opts{})#trans_opts.hibernate;
-hibernate_in_trans_opts(#trans_opts{hibernate = Hibernate}) ->
- Hibernate.
-
-parse_actions(false, Debug, S, Actions) ->
- parse_actions(true, Debug, S, Actions, #trans_opts{});
-parse_actions(TransOpts, Debug, S, Actions) ->
- parse_actions(false, Debug, S, Actions, TransOpts).
+%% Process the returned actions
%%
-parse_actions(_StateCall, Debug, _S, [], TransOpts) ->
- {Debug,TransOpts};
-parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
+loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, _StateCall, []) ->
+ %%
+ case P#params.state_enter of
+ true when CallEnter ->
+ loop_state_enter(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone);
+ _ ->
+ loop_state_transition(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone)
+ end;
+loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, [Action|Actions]) ->
+ %%
case Action of
%% Actual actions
{reply,From,Reply} ->
- parse_actions_reply(
- StateCall, Debug, S, Actions, TransOpts, From, Reply);
+ loop_actions_reply(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ From, Reply);
%%
%% Actions that set options
- {hibernate,NewHibernate} when is_boolean(NewHibernate) ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{hibernate = NewHibernate});
+ {hibernate,Hibernate_1} when is_boolean(Hibernate_1) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate_1, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
hibernate ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{hibernate = true});
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, true, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
%%
- {postpone,NewPostpone} when not NewPostpone orelse StateCall ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{postpone = NewPostpone});
+ {postpone,Postpone_1} when not Postpone_1 orelse StateCall ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone_1,
+ CallEnter, StateCall, Actions);
postpone when StateCall ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{postpone = true});
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, true,
+ CallEnter, StateCall, Actions);
postpone ->
- [error,
- {bad_state_enter_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug];
+ terminate(
+ error,
+ {bad_state_enter_action_from_state_function,Action},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q);
%%
{next_event,Type,Content} ->
- parse_actions_next_event(
- StateCall, Debug, S, Actions, TransOpts, Type, Content);
+ loop_actions_next_event(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Type, Content);
%%
- _ ->
- parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts, Action)
+ Timeout ->
+ loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Timeout)
end.
-parse_actions_reply(
- StateCall, ?not_sys_debug = Debug, S, Actions, TransOpts,
+%% Process a reply action
+%%
+loop_actions_reply(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
From, Reply) ->
%%
case from(From) of
true ->
+ %% No need for a separate ?not_sys_debug clause here
+ %% since the external call to erlang:'!'/2 in reply/2
+ %% will cause swap out of all live registers anyway
reply(From, Reply),
- parse_actions(StateCall, ?not_sys_debug, S, Actions, TransOpts);
- false ->
- [error,
- {bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(), Debug]
- end;
-parse_actions_reply(
- StateCall, Debug, #state{name = Name, state = State} = S,
- Actions, TransOpts, From, Reply) ->
- %%
- case from(From) of
- true ->
- reply(From, Reply),
- NewDebug = sys_debug(Debug, {Name,State}, {out,Reply,From}),
- parse_actions(StateCall, NewDebug, S, Actions, TransOpts);
+ Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}),
+ loop_actions_list(
+ P, Debug_1, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
false ->
- [error,
- {bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(), Debug]
+ terminate(
+ error,
+ {bad_action_from_state_function,{reply,From,Reply}},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end.
-parse_actions_next_event(
- StateCall, ?not_sys_debug = Debug, S,
- Actions, TransOpts, Type, Content) ->
- case event_type(Type) of
- true when StateCall ->
- NextEventsR = TransOpts#trans_opts.next_events_r,
- parse_actions(
- StateCall, ?not_sys_debug, S, Actions,
- TransOpts#trans_opts{
- next_events_r = [{Type,Content}|NextEventsR]});
- _ ->
- [error,
- {bad_state_enter_action_from_state_function,
- {next_event,Type,Content}},
- ?STACKTRACE(), Debug]
- end;
-parse_actions_next_event(
- StateCall, Debug, #state{name = Name, state = State} = S,
- Actions, TransOpts, Type, Content) ->
+%% Process a next_event action
+%%
+loop_actions_next_event(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Type, Content) ->
case event_type(Type) of
true when StateCall ->
- NewDebug = sys_debug(Debug, {Name,State}, {in,{Type,Content}}),
- NextEventsR = TransOpts#trans_opts.next_events_r,
- parse_actions(
- StateCall, NewDebug, S, Actions,
- TransOpts#trans_opts{
- next_events_r = [{Type,Content}|NextEventsR]});
+ NextEvent = {Type,Content},
+ case Debug of
+ ?not_sys_debug ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ [NextEvent|NextEventsR],
+ Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions);
+ _ ->
+ Name = P#params.name,
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(Debug, Name, {in,{Type,Content},State}),
+ loop_actions_list(
+ P, Debug_1, S, Q, NextState_NewData,
+ [NextEvent|NextEventsR],
+ Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions)
+ end;
_ ->
- [error,
- {bad_state_enter_action_from_state_function,
- {next_event,Type,Content}},
- ?STACKTRACE(),
- Debug]
+ terminate(
+ error,
+ {if
+ StateCall ->
+ bad_action_from_state_function;
+ true ->
+ bad_state_enter_action_from_state_function
+ end,
+ {next_event,Type,Content}},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end.
-parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- {TimeoutType,Time,TimerMsg,TimerOpts} = AbsoluteTimeout) ->
+%% Process a timeout action, or also any unrecognized action
+%%
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = Timeout) ->
%%
- case classify_timeout(TimeoutType, Time, listify(TimerOpts)) of
- absolute ->
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, AbsoluteTimeout);
- relative ->
- RelativeTimeout = {TimeoutType,Time,TimerMsg},
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
- [error,
- {bad_action_from_state_function,AbsoluteTimeout},
- ?STACKTRACE(),
- Debug]
+ case timeout_event_type(TimeoutType) of
+ true ->
+ case listify(TimeoutOpts) of
+ %% Optimization cases
+ [] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ [{abs,true}] when ?absolute_timeout(Time) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ [{abs,false}] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ %% Generic case
+ TimeoutOptsList ->
+ case parse_timeout_opts_abs(TimeoutOptsList) of
+ true when ?absolute_timeout(Time) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ false when ?relative_timeout(Time) ->
+ RelativeTimeout =
+ {TimeoutType,Time,TimeoutMsg},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ badarg ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end
+ end;
+ false ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end;
-parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- {TimeoutType,Time,_} = RelativeTimeout) ->
- case classify_timeout(TimeoutType, Time, []) of
- relative ->
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
- [error,
- {bad_action_from_state_function,RelativeTimeout},
- ?STACKTRACE(),
- Debug]
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ {TimeoutType,Time,_} = Timeout) ->
+ %%
+ case timeout_event_type(TimeoutType) of
+ true when ?relative_timeout(Time) ->
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ _ ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end;
-parse_actions_timeout(
- StateCall, Debug, S, Actions, TransOpts,
- Time) ->
- case classify_timeout(timeout, Time, []) of
- relative ->
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions, Time) ->
+ %%
+ if
+ ?relative_timeout(Time) ->
RelativeTimeout = {timeout,Time,Time},
- parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- TransOpts, RelativeTimeout);
- badarg ->
- [error,
- {bad_action_from_state_function,Time},
- ?STACKTRACE(),
- Debug]
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [RelativeTimeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ true ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Time},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
end.
-parse_actions_timeout_add(
- StateCall, Debug, S, Actions,
- #trans_opts{timeouts_r = TimeoutsR} = TransOpts, Timeout) ->
- parse_actions(
- StateCall, Debug, S, Actions,
- TransOpts#trans_opts{timeouts_r = [Timeout|TimeoutsR]}).
-
%% Do the state transition
-loop_event_done(
- Parent, ?not_sys_debug,
- #state{postponed = P} = S,
-%% #state{postponed = will_not_happen = P} = S,
- Events, Event, NextState, NewData,
- #trans_opts{
- postpone = Postpone, hibernate = Hibernate,
- timeouts_r = [], next_events_r = NextEventsR}) ->
- %%
- %% Optimize the simple cases i.e no debug and no timer changes,
- %% by duplicate stripped down code
- %%
- %% Fast path
- %%
- case Postpone of
- true ->
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, [Event|P], NextState, NewData, NextEventsR);
- false ->
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR)
- end;
-loop_event_done(
- Parent, Debug_0,
- #state{
- state = State, postponed = P_0,
- timer_refs = TimerRefs_0, timer_types = TimerTypes_0,
- cancel_timers = CancelTimers_0} = S,
- Events_0, Event_0, NextState, NewData,
- #trans_opts{
- hibernate = Hibernate, timeouts_r = TimeoutsR,
- postpone = Postpone, next_events_r = NextEventsR}) ->
+%%
+loop_state_transition(
+ P, Debug, #state{state_data = {State,_Data}, postponed = Postponed} = S,
+ [Event|Events], {NextState,_NewData} = NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone) ->
%%
%% All options have been collected and next_events are buffered.
%% Do the actual state transition.
%%
- %% Full feature path
- %%
- [Debug_1|P_1] = % Move current event to postponed if Postpone
+ Postponed_1 = % Move current event to postponed if Postpone
case Postpone of
true ->
- [?sys_debug(
- Debug_0,
- {S#state.name,State},
- {postpone,Event_0,NextState}),
- Event_0|P_0];
+ [Event|Postponed];
false ->
- [?sys_debug(
- Debug_0,
- {S#state.name,State},
- {consume,Event_0,NextState})|P_0]
+ Postponed
end,
- {Events_2,P_2,
- Timers_2} =
- %% Cancel the event timeout
- if
- NextState =:= State ->
- {Events_0,P_1,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0})};
- true ->
- %% Move all postponed events to queue
- %% and cancel the state timeout
- {lists:reverse(P_1, Events_0),[],
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes_0,CancelTimers_0}))}
- end,
- {TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} =
- %% Stop and start timers
- parse_timers(TimerRefs_0, Timers_2, TimeoutsR),
- %% Place next events last in reversed queue
- Events_3R = lists:reverse(Events_2, NextEventsR),
- %% Enqueue immediate timeout events
- Events_4R = prepend_timeout_events(TimeoutEvents, Events_3R),
- loop_event_done(
- Parent, Debug_1,
- S#state{
- state = NextState,
- data = NewData,
- postponed = P_2,
- timer_refs = TimerRefs_3,
- timer_types = TimerTypes_3,
- cancel_timers = CancelTimers_3,
- hibernate = Hibernate},
- lists:reverse(Events_4R)).
-
-loop_event_done(Parent, Debug, S, Q) ->
-%% io:format(
-%% "loop_event_done:~n"
-%% " state = ~p, data = ~p, postponed = ~p~n "
-%% " timer_refs = ~p, timer_types = ~p, cancel_timers = ~p.~n"
-%% " Q = ~p.~n",
-%% [S#state.state,S#state.data,S#state.postponed,
-%% S#state.timer_refs,S#state.timer_types,S#state.cancel_timers,
-%% Q]),
- case Q of
- [] ->
- %% Get a new event
- loop(Parent, Debug, S);
- [{Type,Content}|Events] ->
- %% Loop until out of enqueued events
- loop_event(Parent, Debug, S, Events, Type, Content)
+ case Debug of
+ ?not_sys_debug ->
+ %% Optimization for no sys_debug
+ %% - avoid calling sys_debug/3
+ if
+ NextState =:= State ->
+ loop_keep_state(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1);
+ true ->
+ loop_state_change(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1)
+ end;
+ _ ->
+ %% With sys_debug
+ Name = P#params.name,
+ Debug_1 =
+ case Postpone of
+ true ->
+ sys_debug(
+ Debug, Name,
+ {postpone,Event,State,NextState});
+ false ->
+ sys_debug(
+ Debug, Name,
+ {consume,Event,State,NextState})
+ end,
+ if
+ NextState =:= State ->
+ loop_keep_state(
+ P, Debug_1, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1);
+ true ->
+ loop_state_change(
+ P, Debug_1, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed_1)
+ end
end.
+%% State transition to the same state
+%%
+loop_keep_state(
+ P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed) ->
+ %%
+ %% Cancel event timeout
+ %%
+ case TimeoutTypes of
+ %% Optimization
+ %% - only cancel timer when there is a timer to cancel
+ #{timeout := TimerRef} ->
+ %% Event timeout active
+ loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ cancel_timer_by_ref_and_type(
+ TimerRef, timeout, TimerRefs, TimeoutTypes));
+ _ ->
+ %% No event timeout active
+ loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers)
+ end.
-%% Fast path
-%%
-%% Cancel event timer and state timer only if running
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- state = NextState,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData, NextEventsR) ->
- %% Same state
- case TimerTypes of
- #{timeout := _} ->
- %% Event timeout active
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers}));
- _ ->
- %% No event timeout active
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- {TimerTypes,CancelTimers})
- end;
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- timer_types = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData, NextEventsR) ->
- %% State change
- case TimerTypes of
- #{timeout := _} ->
- %% Event timeout active
- %% - cancel state_timeout too since it is faster than inspecting
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
- #{state_timeout := _} ->
- %% State_timeout active but not event timeout
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- cancel_timer_by_type(
- state_timeout, {TimerTypes,CancelTimers}));
+%% State transition to a different state
+%%
+loop_state_change(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed) ->
+ %%
+ %% Retry postponed events
+ %%
+ case Postponed of
+ [] ->
+ loop_state_change(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR);
+ [E1] ->
+ loop_state_change(
+ P, Debug, S,
+ [E1|Events], NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR);
+ [E2,E1] ->
+ loop_state_change(
+ P, Debug, S,
+ [E1,E2|Events], NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR);
_ ->
- %% No event nor state_timeout active
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- {TimerTypes,CancelTimers})
+ loop_state_change(
+ P, Debug, S,
+ lists:reverse(Postponed, Events), NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR)
end.
%%
-%% Retry postponed events
-loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR, TimerTypes_CancelTimers) ->
- case P of
- %% Handle 0..2 postponed events without list reversal since
- %% that will move out all live registers and back again
- [] ->
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers);
- [E] ->
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- [E|Events], [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers);
- [E1,E2] ->
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- [E2,E1|Events], [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers);
+loop_state_change(
+ P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR) ->
+ %%
+ %% Cancel state and event timeout
+ %%
+ case TimeoutTypes of
+ %% Optimization
+ %% - only cancel timeout when there is an active timeout
+ %%
+ #{state_timeout := TimerRef} ->
+ %% State timeout active
+ %% - cancel event timeout too since it is faster than inspecting
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, [],
+ cancel_timer_by_type(
+ timeout,
+ cancel_timer_by_ref_and_type(
+ TimerRef, state_timeout, TimerRefs, TimeoutTypes)));
+ #{timeout := TimerRef} ->
+ %% Event timeout active but not state timeout
+ %% - cancel event timeout only
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, [],
+ cancel_timer_by_ref_and_type(
+ TimerRef, timeout, TimerRefs, TimeoutTypes));
_ ->
- %% A bit slower path
- loop_event_done_fast_2(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData, NextEventsR,
- TimerTypes_CancelTimers)
+ %% No state nor event timeout active.
+ loop_next_events(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, [],
+ Timers)
end.
+
+%% Continue state transition with processing of
+%% inserted events and timeout events
%%
-%% Fast path
+loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, [], Postponed,
+ Timers) ->
+ %%
+ %% Optimization when there are no timeout actions
+ %% hence no timeout zero events to append to Events
+ %% - avoid loop_timeouts
+ loop_done(
+ P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ postponed = Postponed,
+ timers = Timers,
+ hibernate = Hibernate},
+ NextEventsR, Events);
+loop_next_events(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers) ->
+ %%
+ Seen = #{},
+ TimeoutEvents = [],
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents).
+
+%% Continue state transition with processing of timeout events
%%
-loop_event_done_fast_2(
- Parent, Hibernate, S,
- Events, P, NextState, NewData, NextEventsR,
- {TimerTypes,CancelTimers}) ->
+loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, [], Postponed,
+ Timers, _Seen, TimeoutEvents) ->
%%
- NewS =
+ S_1 =
S#state{
- state = NextState,
- data = NewData,
- postponed = P,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers,
+ state_data = NextState_NewData,
+ postponed = Postponed,
+ timers = Timers,
hibernate = Hibernate},
- case NextEventsR of
- %% Handle 0..2 next events without list reversal since
- %% that will move out all live registers and back again
+ case TimeoutEvents of
[] ->
- loop_event_done(Parent, ?not_sys_debug, NewS, Events);
- [E] ->
- loop_event_done(Parent, ?not_sys_debug, NewS, [E|Events]);
- [E2,E1] ->
- loop_event_done(Parent, ?not_sys_debug, NewS, [E1,E2|Events]);
+ loop_done(P, Debug, S_1, NextEventsR, Events);
_ ->
- %% A bit slower path
- loop_event_done(
- Parent, ?not_sys_debug, NewS, lists:reverse(NextEventsR, Events))
+ case Events of
+ [] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ NextEventsR);
+ [E1] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ [E1|NextEventsR]);
+ [E2,E1] ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ [E1,E2|NextEventsR]);
+ _ ->
+ loop_prepend_timeout_events(
+ P, Debug, S_1, TimeoutEvents,
+ lists:reverse(Events, NextEventsR))
+ end
+ end;
+loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, [Timeout|TimeoutsR], Postponed,
+ Timers, Seen, TimeoutEvents) ->
+ %%
+ case Timeout of
+ {TimeoutType,Time,TimeoutMsg} ->
+ %% Relative timeout
+ case Seen of
+ #{TimeoutType := _} ->
+ %% Type seen before - ignore
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents);
+ #{} ->
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, [])
+ end;
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts} ->
+ %% Absolute timeout
+ case Seen of
+ #{TimeoutType := _} ->
+ %% Type seen before - ignore
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents);
+ #{} ->
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts))
+ end
end.
-
-%%---------------------------------------------------------------------------
-%% Server loop helpers
-
-call_callback_mode(#state{module = Module} = S) ->
- try Module:callback_mode() of
- CallbackMode ->
- callback_mode_result(S, CallbackMode)
- catch
- CallbackMode ->
- callback_mode_result(S, CallbackMode);
- Class:Reason:Stacktrace ->
- [Class,Reason,Stacktrace]
+%%
+loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, Time, TimeoutMsg, TimeoutOpts) ->
+ %%
+ case Time of
+ infinity ->
+ %% Cancel any running timer
+ loop_timeouts_cancel(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType);
+ 0 when TimeoutOpts =:= [] ->
+ %% Relative timeout zero
+ %% - cancel any running timer
+ %% handle timeout zero events later
+ %%
+ loop_timeouts_cancel(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, [{TimeoutType,TimeoutMsg}|TimeoutEvents],
+ TimeoutType);
+ _ ->
+ %% (Re)start the timer
+ TimerRef =
+ erlang:start_timer(Time, self(), TimeoutMsg, TimeoutOpts),
+ {TimerRefs,TimeoutTypes} = Timers,
+ case TimeoutTypes of
+ #{TimeoutType := OldTimerRef} ->
+ %% Cancel the running timer,
+ %% update the timeout type,
+ %% insert the new timer ref,
+ %% and remove the old timer ref
+ Timers_1 =
+ {maps:remove(
+ OldTimerRef,
+ TimerRefs#{TimerRef => TimeoutType}),
+ TimeoutTypes#{TimeoutType := TimerRef}},
+ cancel_timer(OldTimerRef),
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
+ #{} ->
+ %% Insert the new timer type and ref
+ Timers_1 =
+ {TimerRefs#{TimerRef => TimeoutType},
+ TimeoutTypes#{TimeoutType => TimerRef}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents)
+ end
end.
-callback_mode_result(S, CallbackMode) ->
- callback_mode_result(
- S, CallbackMode, listify(CallbackMode), undefined, false).
-%%
-callback_mode_result(_S, CallbackMode, [], undefined, _StateEnter) ->
- [error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()];
-callback_mode_result(S, _CallbackMode, [], CBMode, StateEnter) ->
- S#state{callback_mode = CBMode, state_enter = StateEnter};
-callback_mode_result(S, CallbackMode, [H|T], CBMode, StateEnter) ->
- case callback_mode(H) of
- true ->
- callback_mode_result(S, CallbackMode, T, H, StateEnter);
- false ->
- case state_enter(H) of
- true ->
- callback_mode_result(S, CallbackMode, T, CBMode, true);
- false ->
- [error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()]
- end
+%% Loop helper to cancel a timeout
+%%
+loop_timeouts_cancel(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents,
+ TimeoutType) ->
+ %% This function body should have been:
+ %% Timers_1 = cancel_timer_by_type(TimeoutType, Timers),
+ %% loop_timeouts(
+ %% P, Debug, S,
+ %% Events, NextState_NewData,
+ %% NextEventsR, Hibernate, TimeoutsR, Postponed,
+ %% Timers_1, Seen#{TimeoutType => true}, TimeoutEvents).
+ %%
+ %% Explicitly separate cases to get separate code paths for when
+ %% the map key exists vs. not, since otherwise the external call
+ %% to erlang:cancel_timer/1 and to map:remove/2 within
+ %% cancel_timer_by_type/2 would cause all live registers
+ %% to be saved to and restored from the stack also for
+ %% the case when the map key TimeoutType does not exist
+ case TimeoutTypes of
+ #{TimeoutType := TimerRef} ->
+ Timers_1 =
+ cancel_timer_by_ref_and_type(
+ TimerRef, TimeoutType, TimerRefs, TimeoutTypes),
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
+ #{} ->
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen#{TimeoutType => true}, TimeoutEvents)
end.
+%% Continue state transition with prepending timeout zero events
+%% before event queue reversal i.e appending timeout zero events
+%%
+loop_prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR) ->
+ {Debug_1,Events_1R} =
+ prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR),
+ loop_done(P, Debug_1, S, Events_1R, []).
-call_state_function(
- #state{callback_mode = undefined} = S, Type, Content, State, Data) ->
- case call_callback_mode(S) of
- #state{} = NewS ->
- call_state_function(NewS, Type, Content, State, Data);
- Error ->
- Error
- end;
-call_state_function(
- #state{callback_mode = CallbackMode, module = Module} = S,
- Type, Content, State, Data) ->
- try
- case CallbackMode of
- state_functions ->
- Module:State(Type, Content, Data);
- handle_event_function ->
- Module:handle_event(Type, Content, State, Data)
- end
- of
- Result ->
- {Result,S}
- catch
- Result ->
- {Result,S};
- Class:Reason:Stacktrace ->
- [Class,Reason,Stacktrace]
+%% Place inserted events first in the event queue
+%%
+loop_done(P, Debug, S, NextEventsR, Events) ->
+ case NextEventsR of
+ [] ->
+ loop_done(P, Debug, S, Events);
+ [E1] ->
+ loop_done(P, Debug, S, [E1|Events]);
+ [E2,E1] ->
+ loop_done(P, Debug, S, [E1,E2|Events]);
+ _ ->
+ loop_done(P, Debug, S, lists:reverse(NextEventsR, Events))
end.
-
-
-%% -> absolute | relative | badarg
-classify_timeout(TimeoutType, Time, Opts) ->
- case timeout_event_type(TimeoutType) of
- true ->
- classify_time(false, Time, Opts);
- false ->
- badarg
+%%
+%% State transition is done, keep looping if there are
+%% enqueued events, otherwise get a new event
+%%
+loop_done(P, Debug, S, Q) ->
+%%% io:format(
+%%% "loop_done: state_data = ~p,~n"
+%%% " postponed = ~p, Q = ~p,~n",
+%%% " timers = ~p.~n"
+%%% [S#state.state_data,,S#state.postponed,Q,S#state.timers]),
+ case Q of
+ [] ->
+ %% Get a new event
+ loop(P, Debug, S);
+ [Event|Events] ->
+ %% Loop until out of enqueued events
+ loop_event(P, Debug, S, Event, Events)
end.
-classify_time(Abs, Time, []) ->
- case Abs of
- true when
- is_integer(Time);
- Time =:= infinity ->
- absolute;
- false when
- is_integer(Time), 0 =< Time;
- Time =:= infinity ->
- relative;
- _ ->
- badarg
- end;
-classify_time(_, Time, [{abs,Abs}|Opts]) when is_boolean(Abs) ->
- classify_time(Abs, Time, Opts);
-classify_time(_, _, Opts) when is_list(Opts) ->
- badarg.
+%%---------------------------------------------------------------------------
+%% Server loop helpers
-%% Stop and start timers as well as create timeout zero events
-%% and pending event timer
+%% Parse an option list for erlang:start_timer/4 to figure out
+%% if the timeout will be absolute or relative
%%
-%% Stop and start timers non-event timers
-parse_timers(TimerRefs, Timers, TimeoutsR) ->
- parse_timers(TimerRefs, Timers, TimeoutsR, #{}, []).
+parse_timeout_opts_abs(Opts) ->
+ parse_timeout_opts_abs(Opts, false).
%%
-parse_timers(
- TimerRefs, Timers, [], _Seen, TimeoutEvents) ->
- %%
- {TimerRefs,Timers,TimeoutEvents};
-parse_timers(
- TimerRefs, Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) ->
- %%
- case Timeout of
- {TimerType,Time,TimerMsg,TimerOpts} ->
- %% Absolute timer
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, listify(TimerOpts));
- %% Relative timers below
- {TimerType,0,TimerMsg} ->
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, zero, TimerMsg, []);
- {TimerType,Time,TimerMsg} ->
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, [])
- end.
-
-parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, TimerOpts) ->
- case Seen of
- #{TimerType := _} ->
- %% Type seen before - ignore
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents);
- #{} ->
- %% Unseen type - handle
- NewSeen = Seen#{TimerType => true},
- case Time of
- infinity ->
- %% Cancel any running timer
- parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
- TimeoutsR, NewSeen, TimeoutEvents);
- zero ->
- %% Cancel any running timer
- %% Handle zero time timeouts later
- parse_timers(
- TimerRefs, cancel_timer_by_type(TimerType, Timers),
- TimeoutsR, NewSeen,
- [{TimerType,TimerMsg}|TimeoutEvents]);
- _ ->
- %% (Re)start the timer
- TimerRef =
- erlang:start_timer(
- Time, self(), TimerMsg, TimerOpts),
- case Timers of
- {#{TimerType := OldTimerRef} = TimerTypes,
- CancelTimers} ->
- %% Cancel the running timer
- cancel_timer(OldTimerRef),
- NewCancelTimers = CancelTimers + 1,
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
- parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- NewCancelTimers},
- TimeoutsR, NewSeen, TimeoutEvents);
- {#{} = TimerTypes,CancelTimers} ->
- %% Insert the new timer into
- %% both TimerRefs and TimerTypes
- parse_timers(
- TimerRefs#{TimerRef => TimerType},
- {TimerTypes#{TimerType => TimerRef},
- CancelTimers},
- TimeoutsR, NewSeen, TimeoutEvents)
- end
- end
+parse_timeout_opts_abs(Opts, Abs) ->
+ case Opts of
+ [] ->
+ Abs;
+ [{abs,Abs_1}|Opts] when is_boolean(Abs_1) ->
+ parse_timeout_opts_abs(Opts, Abs_1);
+ _ ->
+ badarg
end.
%% Enqueue immediate timeout events (timeout 0 events)
@@ -1814,62 +2055,94 @@ parse_timers(
%% so if there are enqueued events before the event timer
%% timeout 0 event - the event timer is cancelled hence no event.
%%
-%% Other (state_timeout) timeout 0 events that are after
-%% the event timer timeout 0 events are considered to
+%% Other (state_timeout and {timeout,Name}) timeout 0 events
+%% that are after an event timer timeout 0 event are considered to
%% belong to timers that were started after the event timer
%% timeout 0 event fired, so they do not cancel the event timer.
%%
-prepend_timeout_events([], EventsR) ->
- EventsR;
-prepend_timeout_events([{timeout,_} = TimeoutEvent|TimeoutEvents], []) ->
- prepend_timeout_events(TimeoutEvents, [TimeoutEvent]);
-prepend_timeout_events([{timeout,_}|TimeoutEvents], EventsR) ->
+prepend_timeout_events(_P, Debug, _S, [], EventsR) ->
+ {Debug,EventsR};
+prepend_timeout_events(
+ P, Debug, S, [{timeout,_} = TimeoutEvent|TimeoutEvents], []) ->
+ %% Prepend this since there are no other events in queue
+ case Debug of
+ ?not_sys_debug ->
+ prepend_timeout_events(
+ P, Debug, S, TimeoutEvents, [TimeoutEvent]);
+ _ ->
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name, {in,TimeoutEvent,State}),
+ prepend_timeout_events(
+ P, Debug_1, S, TimeoutEvents, [TimeoutEvent])
+ end;
+prepend_timeout_events(
+ P, Debug, S, [{timeout,_}|TimeoutEvents], EventsR) ->
%% Ignore since there are other events in queue
%% so they have cancelled the event timeout 0.
- prepend_timeout_events(TimeoutEvents, EventsR);
-prepend_timeout_events([TimeoutEvent|TimeoutEvents], EventsR) ->
+ prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR);
+prepend_timeout_events(
+ P, Debug, S, [TimeoutEvent|TimeoutEvents], EventsR) ->
%% Just prepend all others
- prepend_timeout_events(TimeoutEvents, [TimeoutEvent|EventsR]).
+ case Debug of
+ ?not_sys_debug ->
+ prepend_timeout_events(
+ P, Debug, S, TimeoutEvents, [TimeoutEvent|EventsR]);
+ _ ->
+ {State,_Data} = S#state.state_data,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name, {in,TimeoutEvent,State}),
+ prepend_timeout_events(
+ P, Debug_1, S, TimeoutEvents, [TimeoutEvent|EventsR])
+ end.
%%---------------------------------------------------------------------------
%% Server helpers
-reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) ->
+reply_then_terminate(Class, Reason, Stacktrace, P, Debug, S, Q, Replies) ->
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, listify(Replies)).
+ Class, Reason, Stacktrace, P, Debug, S, Q, listify(Replies)).
%%
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, []) ->
- terminate(Class, Reason, Stacktrace, Debug, S, Q);
+ Class, Reason, Stacktrace, P, Debug, S, Q, []) ->
+ terminate(Class, Reason, Stacktrace, P, Debug, S, Q);
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) ->
+ Class, Reason, Stacktrace, P, Debug, S, Q, [R|Rs]) ->
case R of
- {reply,{_To,_Tag}=From,Reply} ->
- reply(From, Reply),
- NewDebug =
- ?sys_debug(
- Debug,
- begin
- #state{name = Name, state = State} = S,
- {Name,State}
- end,
- {out,Reply,From}),
- do_reply_then_terminate(
- Class, Reason, Stacktrace, NewDebug, S, Q, Rs);
+ {reply,From,Reply} ->
+ case from(From) of
+ true ->
+ reply(From, Reply),
+ Debug_1 =
+ ?sys_debug(
+ Debug,
+ P#params.name,
+ {out,Reply,From}),
+ do_reply_then_terminate(
+ Class, Reason, Stacktrace, P, Debug_1, S, Q, Rs);
+ false ->
+ terminate(
+ error,
+ {bad_reply_action_from_state_function,R},
+ ?STACKTRACE(),
+ P, Debug, S, Q)
+ end;
_ ->
terminate(
error,
{bad_reply_action_from_state_function,R},
?STACKTRACE(),
- Debug, S, Q)
+ P, Debug, S, Q)
end.
terminate(
- Class, Reason, Stacktrace, Debug,
- #state{module = Module, state = State, data = Data} = S,
- Q) ->
+ Class, Reason, Stacktrace,
+ #params{module = Module} = P, Debug,
+ #state{state_data = {State,Data}} = S, Q) ->
case erlang:function_exported(Module, terminate, 3) of
true ->
try Module:terminate(Reason, State, Data) of
@@ -1877,8 +2150,7 @@ terminate(
catch
_ -> ok;
C:R:ST ->
- error_info(C, R, ST, S, Q),
- sys:print_log(Debug),
+ error_info(C, R, ST, Debug, P, S, Q),
erlang:raise(C, R, ST)
end;
false ->
@@ -1887,14 +2159,13 @@ terminate(
_ =
case Reason of
normal ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
shutdown ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
{shutdown,_} ->
- terminate_sys_debug(Debug, S, State, Reason);
+ terminate_sys_debug(Debug, P, State, Reason);
_ ->
- error_info(Class, Reason, Stacktrace, S, Q),
- sys:print_log(Debug)
+ error_info(Class, Reason, Stacktrace, Debug, P, S, Q)
end,
case Stacktrace of
[] ->
@@ -1903,38 +2174,67 @@ terminate(
erlang:raise(Class, Reason, Stacktrace)
end.
-terminate_sys_debug(Debug, S, State, Reason) ->
- ?sys_debug(Debug, {S#state.name,State}, {terminate,Reason}).
+terminate_sys_debug(Debug, P, State, Reason) ->
+ ?sys_debug(Debug, P#params.name, {terminate,Reason,State}).
error_info(
- Class, Reason, Stacktrace,
- #state{
+ Class, Reason, Stacktrace, Debug,
+ #params{
name = Name,
callback_mode = CallbackMode,
- state_enter = StateEnter,
- postponed = P} = S,
+ state_enter = StateEnter} = P,
+ #state{postponed = Postponed} = S,
Q) ->
+ Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_statem,terminate},
name=>Name,
queue=>Q,
- postponed=>P,
+ postponed=>Postponed,
callback_mode=>CallbackMode,
state_enter=>StateEnter,
- state=>format_status(terminate, get(), S),
- reason=>{Class,Reason,Stacktrace}},
+ state=>format_status(terminate, get(), P, S),
+ log=>Log,
+ reason=>{Class,Reason,Stacktrace},
+ client_info=>client_stacktrace(Q)},
#{domain=>[otp],
report_cb=>fun gen_statem:format_log/1,
error_logger=>#{tag=>error}}).
+client_stacktrace([]) ->
+ undefined;
+client_stacktrace([{{call,{Pid,_Tag}},_Req}|_]) when is_pid(Pid) ->
+ if
+ node(Pid) =:= node() ->
+ case
+ process_info(Pid, [current_stacktrace, registered_name])
+ of
+ undefined ->
+ {Pid,dead};
+ [{current_stacktrace, Stacktrace},
+ {registered_name, []}] ->
+ {Pid,{Pid,Stacktrace}};
+ [{current_stacktrace, Stacktrace},
+ {registered_name, Name}] ->
+ {Pid,{Name,Stacktrace}}
+ end;
+ true ->
+ {Pid,remote}
+ end;
+client_stacktrace([_|_]) ->
+ undefined.
+
+
format_log(#{label:={gen_statem,terminate},
name:=Name,
queue:=Q,
- postponed:=P,
+ postponed:=Postponed,
callback_mode:=CallbackMode,
state_enter:=StateEnter,
state:=FmtData,
- reason:={Class,Reason,Stacktrace}}) ->
+ log:=Log,
+ reason:={Class,Reason,Stacktrace},
+ client_info:=ClientInfo}) ->
{FixedReason,FixedStacktrace} =
case Stacktrace of
[{M,F,Args,_}|ST]
@@ -1954,14 +2254,12 @@ format_log(#{label:={gen_statem,terminate},
true ->
{Reason,Stacktrace};
false ->
- {{'function not exported',{M,F,Arity}},
- ST}
+ {{'function not exported',{M,F,Arity}},ST}
end
end;
_ -> {Reason,Stacktrace}
end,
- [LimitedP, LimitedFmtData, LimitedFixedReason] =
- [error_logger:limit_term(D) || D <- [P, FmtData, FixedReason]],
+ {ClientFmt,ClientArgs} = format_client_log(ClientInfo),
CBMode =
case StateEnter of
true ->
@@ -1981,39 +2279,60 @@ format_log(#{label:={gen_statem,terminate},
[_,_|_] -> "** Queued = ~tp~n";
_ -> ""
end ++
- case P of
+ case Postponed of
[] -> "";
_ -> "** Postponed = ~tp~n"
end ++
case FixedStacktrace of
[] -> "";
_ -> "** Stacktrace =~n** ~tp~n"
- end,
+ end ++
+ case Log of
+ [] -> "";
+ _ -> "** Log =~n** ~tp~n"
+ end ++ ClientFmt,
[Name |
case Q of
[] -> [];
- [Event|_] -> [Event]
+ [Event|_] -> [error_logger:limit_term(Event)]
end] ++
- [LimitedFmtData,
- Class,LimitedFixedReason,
+ [error_logger:limit_term(FmtData),
+ Class,error_logger:limit_term(FixedReason),
CBMode] ++
case Q of
- [_|[_|_] = Events] -> [Events];
+ [_|[_|_] = Events] -> [error_logger:limit_term(Events)];
_ -> []
end ++
- case P of
+ case Postponed of
[] -> [];
- _ -> [LimitedP]
+ _ -> [error_logger:limit_term(Postponed)]
end ++
case FixedStacktrace of
[] -> [];
- _ -> [FixedStacktrace]
- end}.
+ _ -> [error_logger:limit_term(FixedStacktrace)]
+ end ++
+ case Log of
+ [] -> [];
+ _ -> [[error_logger:limit_term(T) || T <- Log]]
+ end ++ ClientArgs}.
+
+format_client_log(undefined) ->
+ {"", []};
+format_client_log({Pid,dead}) ->
+ {"** Client ~p is dead~n", [Pid]};
+format_client_log({Pid,remote}) ->
+ {"** Client ~p is remote on node ~p~n", [Pid, node(Pid)]};
+format_client_log({_Pid,{Name,Stacktrace}}) ->
+ {"** Client ~tp stacktrace~n"
+ "** ~tp~n",
+ [Name, error_logger:limit_term(Stacktrace)]}.
+
%% Call Module:format_status/2 or return a default value
format_status(
Opt, PDict,
- #state{module = Module, state = State, data = Data}) ->
+ #params{module = Module},
+ #state{state_data = {State,Data} = State_Data}) ->
case erlang:function_exported(Module, format_status, 2) of
true ->
try Module:format_status(Opt, [PDict,State,Data])
@@ -2021,21 +2340,21 @@ format_status(
Result -> Result;
_:_ ->
format_status_default(
- Opt, State,
- atom_to_list(Module) ++ ":format_status/2 crashed")
+ Opt,
+ {State,
+ atom_to_list(Module) ++ ":format_status/2 crashed"})
end;
false ->
- format_status_default(Opt, State, Data)
+ format_status_default(Opt, State_Data)
end.
-%% The default Module:format_status/2
-format_status_default(Opt, State, Data) ->
- StateData = {State,Data},
+%% The default Module:format_status/3
+format_status_default(Opt, State_Data) ->
case Opt of
terminate ->
- StateData;
+ State_Data;
_ ->
- [{data,[{"State",StateData}]}]
+ [{data,[{"State",State_Data}]}]
end.
-compile({inline, [listify/1]}).
@@ -2044,24 +2363,41 @@ listify(Item) when is_list(Item) ->
listify(Item) ->
[Item].
+
+-define(cancel_timer(TimerRef),
+ case erlang:cancel_timer(TimerRef) of
+ false ->
+ %% No timer found and we have not seen the timeout message
+ receive
+ {timeout,(TimerRef),_} ->
+ ok
+ end;
+ _ ->
+ %% Timer was running
+ ok
+ end).
+
+-compile({inline, [cancel_timer/1]}).
+cancel_timer(TimerRef) ->
+ ?cancel_timer(TimerRef).
+
%% Cancel timer if running, otherwise no op
%%
-%% This is an asynchronous cancel so the timer is not really cancelled
-%% until we get a cancel_timer msg i.e {cancel_timer,TimerRef,_}.
-%% In the mean time we might get a timeout message.
-%%
-%% Remove the timer from TimerTypes.
-%% When we get the cancel_timer msg we remove it from TimerRefs.
+%% Remove the timer from Timers
-compile({inline, [cancel_timer_by_type/2]}).
-cancel_timer_by_type(TimerType, {TimerTypes,CancelTimers} = TT_CT) ->
- case TimerTypes of
- #{TimerType := TimerRef} ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]),
- {maps:remove(TimerType, TimerTypes),CancelTimers + 1};
- #{} ->
- TT_CT
+cancel_timer_by_type(TimeoutType, {TimerRefs,TimeoutTypes} = Timers) ->
+ case TimeoutTypes of
+ #{TimeoutType := TimerRef} ->
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)};
+ #{} ->
+ Timers
end.
--compile({inline, [cancel_timer/1]}).
-cancel_timer(TimerRef) ->
- ok = erlang:cancel_timer(TimerRef, [{async,true}]).
+-compile({inline, [cancel_timer_by_ref_and_type/4]}).
+cancel_timer_by_ref_and_type(
+ TimerRef, TimeoutType, TimerRefs, TimeoutTypes) ->
+ ?cancel_timer(TimerRef),
+ {maps:remove(TimerRef, TimerRefs),
+ maps:remove(TimeoutType, TimeoutTypes)}.
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index 6d243e1bec..97ec785c62 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -556,8 +556,8 @@ tg({call, Line, {remote,_,{atom,_,erlang},{atom, Line2, FunName}},ParaList},
FunName,length(ParaList)}})
end;
tg({call, Line, {remote,_,{atom,_,ModuleName},
- {atom, _, FunName}},_ParaList},B) ->
- throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName}});
+ {atom, _, FunName}},ParaList},B) ->
+ throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName,length(ParaList)}});
tg({cons,Line, H, T},B) ->
{cons, Line, tg(H,B), tg(T,B)};
tg({nil, Line},_B) ->
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index 2cfc702b53..f2b50c354a 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -55,6 +55,14 @@ obsolete_1(erlang, now, 0) ->
obsolete_1(calendar, local_time_to_universal_time, 1) ->
{deprecated, {calendar, local_time_to_universal_time_dst, 1}};
+%% *** STDLIB added in OTP 22 ***
+
+obsolete_1(sys, get_debug, 3) ->
+ {deprecated,
+ "Deprecated function. "
+ "Incorrectly documented and in fact only for internal use. "
+ "Can often be replaced with sys:get_log/1."};
+
%% *** STDLIB added in OTP 20 ***
obsolete_1(gen_fsm, start, 3) ->
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 8c0b186288..2a324aef82 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -16,13 +16,38 @@
%% limitations under the License.
%%
%% %CopyrightEnd%
+%%
+%% We allow upgrade from, and downgrade to all previous
+%% versions from the following OTP releases:
+%% - OTP 20
+%% - OTP 21
+%%
+%% We also allow upgrade from, and downgrade to all
+%% versions that have branched off from the above
+%% stated previous versions.
+%%
{"%VSN%",
- %% Up from - max one major revision back
- [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0
- {<<"3\\.6(\\.[0-9]+)*">>,[restart_new_emulator]}],% OTP-21.1
- %% Down to - max one major revision back
- [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.*
- {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0
- {<<"3\\.6(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.1
-}.
+ [{<<"^3\\.4$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.5(?:\\.[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]},
+ {<<"^3\\.6$">>,[restart_new_emulator]},
+ {<<"^3\\.6\\.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\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.4\\.5(?:\\.[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]},
+ {<<"^3\\.6$">>,[restart_new_emulator]},
+ {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
index 0064414d6f..a04195c9ed 100644
--- a/lib/stdlib/src/sys.erl
+++ b/lib/stdlib/src/sys.erl
@@ -30,7 +30,8 @@
log_to_file/2, log_to_file/3, no_debug/1, no_debug/2,
install/2, install/3, remove/2, remove/3]).
-export([handle_system_msg/6, handle_system_msg/7, handle_debug/4,
- print_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]).
+ print_log/1, get_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]).
+-deprecated([{get_debug,3,eventually}]).
%%-----------------------------------------------------------------
%% Types
@@ -42,10 +43,17 @@
| {'global', term()}
| {'via', module(), term()}.
-type system_event() :: {'in', Msg :: _}
- | {'in', Msg :: _, From :: _}
+ | {'in', Msg :: _, State :: _}
| {'out', Msg :: _, To :: _}
| {'out', Msg :: _, To :: _, State :: _}
- | term().
+ | {'noreply', State :: _}
+ | {'continue', Continuation :: _}
+ | {'code_change', Event :: _, State :: _}
+ | {'postpone', Event :: _, State :: _, NextState :: _}
+ | {'consume', Event :: _, State :: _, NextState :: _}
+ | {'enter', State :: _}
+ | {'terminate', Reason :: _, State :: _}
+ | term().
-opaque dbg_opt() :: {'trace', 'true'}
| {'log',
{N :: non_neg_integer(),
@@ -385,31 +393,41 @@ handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib) ->
FormFunc :: format_fun(),
Extra :: term(),
Event :: system_event().
-handle_debug([{trace, true} | T], FormFunc, State, Event) ->
+handle_debug([{trace, true} = DbgOpt | T], FormFunc, State, Event) ->
print_event({Event, State, FormFunc}),
- [{trace, true} | handle_debug(T, FormFunc, State, Event)];
-handle_debug([{log, {N, LogData}} | T], FormFunc, State, Event) ->
- NLogData = [{Event, State, FormFunc} | trim(N, LogData)],
- [{log, {N, NLogData}} | handle_debug(T, FormFunc, State, Event)];
-handle_debug([{log_to_file, Fd} | T], FormFunc, State, Event) ->
+ [DbgOpt | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{log, NLog} | T], FormFunc, State, Event) ->
+ Item = {Event, State, FormFunc},
+ [{log, nlog_put(Item, NLog)} | handle_debug(T, FormFunc, State, Event)];
+handle_debug([{log_to_file, Fd} = DbgOpt | T], FormFunc, State, Event) ->
print_event(Fd, {Event, State, FormFunc}),
- [{log_to_file, Fd} | handle_debug(T, FormFunc, State, Event)];
+ [DbgOpt | handle_debug(T, FormFunc, State, Event)];
handle_debug([{statistics, StatData} | T], FormFunc, State, Event) ->
NStatData = stat(Event, StatData),
[{statistics, NStatData} | handle_debug(T, FormFunc, State, Event)];
handle_debug([{FuncId, {Func, FuncState}} | T], FormFunc, State, Event) ->
- case catch Func(FuncState, Event, State) of
+ try Func(FuncState, Event, State) of
done -> handle_debug(T, FormFunc, State, Event);
- {'EXIT', _} -> handle_debug(T, FormFunc, State, Event);
NFuncState ->
- [{FuncId, {Func, NFuncState}} | handle_debug(T, FormFunc, State, Event)]
+ [{FuncId, {Func, NFuncState}} |
+ handle_debug(T, FormFunc, State, Event)]
+ catch
+ done -> handle_debug(T, FormFunc, State, Event);
+ NFuncState ->
+ [{FuncId, {Func, NFuncState}} |
+ handle_debug(T, FormFunc, State, Event)];
+ _:_ -> handle_debug(T, FormFunc, State, Event)
end;
handle_debug([{Func, FuncState} | T], FormFunc, State, Event) ->
- case catch Func(FuncState, Event, State) of
+ try Func(FuncState, Event, State) of
done -> handle_debug(T, FormFunc, State, Event);
- {'EXIT', _} -> handle_debug(T, FormFunc, State, Event);
- NFuncState ->
+ NFuncState ->
[{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)]
+ catch
+ done -> handle_debug(T, FormFunc, State, Event);
+ NFuncState ->
+ [{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)];
+ _:_ -> handle_debug(T, FormFunc, State, Event)
end;
handle_debug([], _FormFunc, _State, _Event) ->
[].
@@ -526,19 +544,19 @@ debug_cmd({trace, true}, Debug) ->
debug_cmd({trace, false}, Debug) ->
{ok, remove_debug(trace, Debug)};
debug_cmd({log, true}, Debug) ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- {ok, install_debug(log, {10, trim(10, Logs)}, Debug)};
-debug_cmd({log, {true, N}}, Debug) when is_integer(N), N > 0 ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- {ok, install_debug(log, {N, trim(N, Logs)}, Debug)};
+ NLog = get_debug(log, Debug, nlog_new()),
+ {ok, install_debug(log, nlog_new(NLog), Debug)};
+debug_cmd({log, {true, N}}, Debug) when is_integer(N), 1 =< N ->
+ NLog = get_debug(log, Debug, nlog_new(N)),
+ {ok, install_debug(log, nlog_new(N, NLog), Debug)};
debug_cmd({log, false}, Debug) ->
{ok, remove_debug(log, Debug)};
debug_cmd({log, print}, Debug) ->
print_log(Debug),
{ok, Debug};
debug_cmd({log, get}, Debug) ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- {{ok, lists:reverse(Logs)}, Debug};
+ NLog = get_debug(log, Debug, nlog_new()),
+ {{ok, [Event || {Event, _State, _FormFunc} <- nlog_get(NLog)]}, Debug};
debug_cmd({log_to_file, false}, Debug) ->
NDebug = close_log_file(Debug),
{ok, NDebug};
@@ -595,9 +613,6 @@ stat({out, _Msg, _To}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1};
stat({out, _Msg, _To, _State}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1};
stat(_, StatData) -> StatData.
-trim(N, LogData) ->
- lists:sublist(LogData, 1, N-1).
-
%%-----------------------------------------------------------------
%% Debug structure manipulating functions
%%-----------------------------------------------------------------
@@ -625,9 +640,14 @@ get_debug2(Item, Debug, Default) ->
-spec print_log(Debug) -> 'ok' when
Debug :: [dbg_opt()].
print_log(Debug) ->
- {_N, Logs} = get_debug(log, Debug, {0, []}),
- lists:foreach(fun print_event/1,
- lists:reverse(Logs)).
+ NLog = get_debug(log, Debug, nlog_new()),
+ lists:foreach(fun print_event/1, nlog_get(NLog)).
+
+-spec get_log(Debug) -> [system_event()] when
+ Debug :: [dbg_opt()].
+get_log(Debug) ->
+ NLog = get_debug(log, Debug, nlog_new()),
+ [Event || {Event, _State, _FormFunc} <- nlog_get(NLog)].
close_log_file(Debug) ->
case get_debug2(log_to_file, Debug, []) of
@@ -639,6 +659,74 @@ close_log_file(Debug) ->
end.
%%-----------------------------------------------------------------
+%% Keep the last N Log functions
+%%-----------------------------------------------------------------
+%%
+%% Streamlined Okasaki queue as base for "keep the last N" log.
+%%
+%% To the reverse list head we cons new items.
+%% The forward list contains elements in insertion order,
+%% so the head is the oldest and the one to drop off
+%% when the log is full.
+%%
+%% Here is how we can get away with only using one cons cell
+%% to wrap the forward and reverse list, and the log size:
+%%
+%% A full log does not need a counter; we just cons one
+%% and drop one:
+%%
+%% [ReverseList|ForwardList]
+%%
+%% A non-full log is filling up to N elements;
+%% use a down counter instead of a list as first element:
+%%
+%% [RemainingToFullCount|ReverseList]
+
+nlog_new() ->
+ nlog_new(10).
+%%
+nlog_new([_|_] = NLog) ->
+ nlog_new(10, NLog);
+nlog_new(N) ->
+ [N]. % Empty log size N >= 1
+%%
+nlog_new(N, NLog) ->
+ lists:foldl(
+ fun (Item, NL) -> nlog_put(Item, NL) end,
+ nlog_new(N),
+ nlog_get(NLog)).
+
+%%
+nlog_put(Item, NLog) ->
+ case NLog of
+ [R|FF] when is_list(R) ->
+ %% Full log
+ case FF of
+ [_|F] ->
+ %% Cons to reverse list, drop from forward list
+ [[Item|R]|F];
+ [] ->
+ %% Create new forward list from reverse list,
+ %% create new empty reverse list
+ [_|F] = lists:reverse(R, [Item]),
+ [[]|F]
+ end;
+ [1|R] ->
+ %% Log now gets full
+ [[Item|R]];
+ [J|R] ->
+ %% Filling up to N elements
+ [J - 1,Item|R]
+ end.
+
+nlog_get([[]|F]) ->
+ F;
+nlog_get([[_|_] = R|F]) ->
+ F ++ lists:reverse(R);
+nlog_get([_J|R]) ->
+ lists:reverse(R).
+
+%%-----------------------------------------------------------------
%% Func: debug_options/1
%% Purpose: Initiate a debug structure. Called by a process that
%% wishes to initiate the debug structure without the
@@ -665,9 +753,9 @@ debug_options(Options) ->
debug_options([trace | T], Debug) ->
debug_options(T, install_debug(trace, true, Debug));
debug_options([log | T], Debug) ->
- debug_options(T, install_debug(log, {10, []}, Debug));
+ debug_options(T, install_debug(log, nlog_new(), Debug));
debug_options([{log, N} | T], Debug) when is_integer(N), N > 0 ->
- debug_options(T, install_debug(log, {N, []}, Debug));
+ debug_options(T, install_debug(log, nlog_new(N), Debug));
debug_options([statistics | T], Debug) ->
debug_options(T, install_debug(statistics, init_stat(), Debug));
debug_options([{log_to_file, FileName} | T], Debug) ->
diff --git a/lib/stdlib/test/calendar_SUITE.erl b/lib/stdlib/test/calendar_SUITE.erl
index 55118e251c..df62c0921d 100644
--- a/lib/stdlib/test/calendar_SUITE.erl
+++ b/lib/stdlib/test/calendar_SUITE.erl
@@ -183,14 +183,15 @@ rfc3339(Config) when is_list(Config) ->
D = [{time_designator, $\s}],
Z = [{offset, "Z"}],
- "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12T23:20:50.52Z", Ms),
- "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12t23:20:50.52z", Ms),
- "1985-04-12T21:20:50.52Z" =
+ "1985-04-12T23:20:50.520Z" = test_parse("1985-04-12T23:20:50.52Z", Ms),
+ "1985-04-12T23:20:50.520Z" = test_parse("1985-04-12t23:20:50.52z", Ms),
+ "1985-04-12T21:20:50.520Z" =
test_parse("1985-04-12T23:20:50.52+02:00", Ms),
"1985-04-12T23:20:50Z" = test_parse("1985-04-12T23:20:50.52Z", S),
- "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12T23:20:50.52Z", Ms),
- "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12t23:20:50.52z", Mys),
- "1985-04-12 21:20:50.52Z" =
+ "1985-04-12T23:20:50.520Z" = test_parse("1985-04-12T23:20:50.52Z", Ms),
+ "1985-04-12T23:20:50.520000Z" =
+ test_parse("1985-04-12t23:20:50.52z", Mys),
+ "1985-04-12 21:20:50.520000000Z" =
test_parse("1985-04-12 23:20:50.52+02:00", Ns++D),
"1985-04-12T23:20:50Z" = test_parse("1985-04-12T23:20:50.52Z"),
"1996-12-20T00:39:57Z" = test_parse("1996-12-19T16:39:57-08:00"),
@@ -221,17 +222,20 @@ rfc3339(Config) when is_list(Config) ->
"1970-01-02T00:00:00Z" = test_parse("1970-01-01T23:59:60Z"),
"1970-01-02T00:00:00Z" = test_parse("1970-01-01T23:59:60.5Z"),
"1970-01-02T00:00:00Z" = test_parse("1970-01-01T23:59:60.55Z"),
- "1970-01-02T00:00:00.55Z" = test_parse("1970-01-01T23:59:60.55Z", Ms),
- "1970-01-02T00:00:00.55Z" = test_parse("1970-01-01T23:59:60.55Z", Mys),
- "1970-01-02T00:00:00.55Z" = test_parse("1970-01-01T23:59:60.55Z", Ns),
+ "1970-01-02T00:00:00.550Z" = test_parse("1970-01-01T23:59:60.55Z", Ms),
+ "1970-01-02T00:00:00.550000Z" =
+ test_parse("1970-01-01T23:59:60.55Z", Mys),
+ "1970-01-02T00:00:00.550000000Z" =
+ test_parse("1970-01-01T23:59:60.55Z", Ns),
"1970-01-02T00:00:00.999999Z" =
test_parse("1970-01-01T23:59:60.999999Z", Mys),
- "1970-01-02T00:00:01Z" =
+ "1970-01-02T00:00:01.000Z" =
test_parse("1970-01-01T23:59:60.999999Z", Ms),
"1970-01-01T00:00:00Z" = test_parse("1970-01-01T00:00:00+00:00"),
"1970-01-01T00:00:00Z" = test_parse("1970-01-01T00:00:00-00:00"),
"1969-12-31T00:01:00Z" = test_parse("1970-01-01T00:00:00+23:59"),
- "1918-11-11T09:00:00Z" = test_parse("1918-11-11T11:00:00+02:00", Mys),
+ "1918-11-11T09:00:00.000000Z" =
+ test_parse("1918-11-11T11:00:00+02:00", Mys),
"1970-01-01T00:00:00.000001Z" =
test_parse("1970-01-01T00:00:00.000001Z", Mys),
@@ -242,26 +246,26 @@ rfc3339(Config) when is_list(Config) ->
test_time(erlang:system_time(millisecond), Ms),
test_time(erlang:system_time(microsecond), Mys++[{offset, "-02:20"}]),
- T = erlang:system_time(second),
- TS = do_format(T, []),
- TS = do_format(T * 1000, Ms),
- TS = do_format(T * 1000 * 1000, Mys),
- TS = do_format(T * 1000 * 1000 * 1000, Ns),
-
946720800 = TO = do_parse("2000-01-01 10:00:00Z", []),
Str = "2000-01-01T10:02:00+00:02",
Str = do_format(TO, [{offset, 120}]),
- Str = do_format(TO * 1000, [{offset, 120 * 1000}]++Ms),
- Str = do_format(TO * 1000 * 1000, [{offset, 120 * 1000 * 1000}]++Mys),
- Str = do_format(TO * 1000 * 1000 * 1000,
- [{offset, 120 * 1000 * 1000 * 1000}]++Ns),
+ "2000-01-01T10:02:00.000+00:02" =
+ do_format(TO * 1000, [{offset, 120 * 1000}]++Ms),
+ "2000-01-01T10:02:00.000000+00:02" =
+ do_format(TO * 1000 * 1000, [{offset, 120 * 1000 * 1000}]++Mys),
+ "2000-01-01T10:02:00.000000000+00:02" =
+ do_format(TO * 1000 * 1000 * 1000,
+ [{offset, 120 * 1000 * 1000 * 1000}]++Ns),
NStr = "2000-01-01T09:58:00-00:02",
NStr = do_format(TO, [{offset, -120}]),
- NStr = do_format(TO * 1000, [{offset, -120 * 1000}]++Ms),
- NStr = do_format(TO * 1000 * 1000, [{offset, -120 * 1000 * 1000}]++Mys),
- NStr = do_format(TO * 1000 * 1000 * 1000,
- [{offset, -120 * 1000 * 1000 * 1000}]++Ns),
+ "2000-01-01T09:58:00.000-00:02" =
+ do_format(TO * 1000, [{offset, -120 * 1000}]++Ms),
+ "2000-01-01T09:58:00.000000-00:02" =
+ do_format(TO * 1000 * 1000, [{offset, -120 * 1000 * 1000}]++Mys),
+ "2000-01-01T09:58:00.000000000-00:02" =
+ do_format(TO * 1000 * 1000 * 1000,
+ [{offset, -120 * 1000 * 1000 * 1000}]++Ns),
543210000 = do_parse("1970-01-01T00:00:00.54321Z", Ns),
54321000 = do_parse("1970-01-01T00:00:00.054321Z", Ns),
@@ -278,18 +282,18 @@ rfc3339(Config) when is_list(Config) ->
-1613833200000000 = do_parse("1918-11-11T11:00:00+02:00", Mys),
-1613833200000000 = do_parse("1918-11-11T09:00:00Z", Mys),
- "1970-01-01T00:00:00Z" = do_format_z(0, Mys),
+ "1970-01-01T00:00:00.000000Z" = do_format_z(0, Mys),
"1970-01-01T00:00:01Z" = do_format_z(1, S),
"1970-01-01T00:00:00.001Z" = do_format_z(1, Ms),
"1970-01-01T00:00:00.000001Z" = do_format_z(1, Mys),
"1970-01-01T00:00:00.000000001Z" = do_format_z(1, Ns),
- "1970-01-01T00:00:01Z" = do_format_z(1000000, Mys),
- "1970-01-01T00:00:00.54321Z" = do_format_z(543210, Mys),
+ "1970-01-01T00:00:01.000000Z" = do_format_z(1000000, Mys),
+ "1970-01-01T00:00:00.543210Z" = do_format_z(543210, Mys),
"1970-01-01T00:00:00.543Z" = do_format_z(543, Ms),
- "1970-01-01T00:00:00.54321Z" = do_format_z(543210000, Ns),
- "1970-01-01T00:00:06.54321Z" = do_format_z(6543210, Mys),
- "1979-06-21T12:12:12Z" = do_format_z(298815132000000, Mys),
- "1918-11-11T13:00:00Z" = do_format_z(-1613818800000000, Mys),
+ "1970-01-01T00:00:00.543210000Z" = do_format_z(543210000, Ns),
+ "1970-01-01T00:00:06.543210Z" = do_format_z(6543210, Mys),
+ "1979-06-21T12:12:12.000000Z" = do_format_z(298815132000000, Mys),
+ "1918-11-11T13:00:00.000000Z" = do_format_z(-1613818800000000, Mys),
ok.
%%
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index c1613a7273..e791da48cf 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -67,7 +67,8 @@
record_errors/1, otp_11879_cont/1,
non_latin1_module/1, otp_14323/1,
stacktrace_syntax/1,
- otp_14285/1, otp_14378/1]).
+ otp_14285/1, otp_14378/1,
+ external_funs/1,otp_15456/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -88,7 +89,8 @@ all() ->
maps, maps_type, maps_parallel_match,
otp_11851, otp_11879, otp_13230,
record_errors, otp_11879_cont, non_latin1_module, otp_14323,
- stacktrace_syntax, otp_14285, otp_14378].
+ stacktrace_syntax, otp_14285, otp_14378, external_funs,
+ otp_15456].
groups() ->
[{unused_vars_warn, [],
@@ -2118,6 +2120,61 @@ otp_5362(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.
+%% OTP-15456. All compiler options can now be given in the option list
+%% (as opposed to only in files).
+otp_15456(Config) when is_list(Config) ->
+ Ts = [
+ %% {nowarn_deprecated_function,[{M,F,A}]} can now be given
+ %% in the option list as well as in an attribute.
+ %% Wherever it occurs, it is not affected by
+ %% warn_deprecated_function.
+ {otp_15456_1,
+ <<"-compile({nowarn_deprecated_function,{erlang,now,0}}).
+ -export([foo/0]).
+
+ foo() ->
+ {erlang:now(), random:seed0(), random:seed(1, 2, 3),
+ random:uniform(), random:uniform(42)}.
+ ">>,
+ {[{nowarn_deprecated_function,{random,seed0,0}},
+ {nowarn_deprecated_function,[{random,uniform,0},
+ {random,uniform,1}]},
+ %% There should be no warnings when attempting to
+ %% turn of warnings for functions that are not
+ %% deprecated or not used in the module.
+ {nowarn_deprecated_function,{random,uniform_s,1}},
+ {nowarn_deprecated_function,{erlang,abs,1}},
+ warn_deprecated_function]},
+ {warnings,[{5,erl_lint,
+ {deprecated,{random,seed,3},
+ "the 'random' module is deprecated; "
+ "use the 'rand' module instead"}}]}},
+
+ %% {nowarn_unused_function,[{M,F,A}]} can be given
+ %% in the option list as well as in an attribute.
+ %% It was incorrectly documented to only work when
+ %% given in an attribute.
+ {otp_15456_2,
+ <<"-compile({nowarn_unused_function,foo/0}).
+ foo() -> ok.
+ bar() -> ok.
+ foobar() -> ok.
+ barf(_) -> ok.
+ other() -> ok.
+ ">>,
+ {[{nowarn_unused_function,[{bar,0},{foobar,0}]},
+ {nowarn_unused_function,{barf,1}},
+ %% There should be no warnings when attempting to
+ %% turn of warnings for unused functions that are not
+ %% defined in the module.
+ {nowarn_unused_function,{not_defined_in_module,1}},
+ warn_unused_function]},
+ {warnings,[{6,erl_lint,{unused_function,{other,0}}}]}
+ }],
+
+ [] = run(Config, Ts),
+ ok.
+
%% OTP-5371. Aliases for bit syntax expressions are no longer allowed.
otp_5371(Config) when is_list(Config) ->
Ts = [{otp_5371_1,
@@ -4134,6 +4191,21 @@ otp_14285(Config) ->
run(Config, Ts),
ok.
+external_funs(Config) when is_list(Config) ->
+ Ts = [{external_funs_1,
+ %% ERL-762: Unused variable warning not being emitted.
+ <<"f() ->
+ BugVar = process_info(self()),
+ if true -> fun m:f/1 end.
+ f(M, F) ->
+ BugVar = process_info(self()),
+ if true -> fun M:F/1 end.">>,
+ [],
+ {warnings,[{2,erl_lint,{unused_var,'BugVar'}},
+ {5,erl_lint,{unused_var,'BugVar'}}]}}],
+ run(Config, Ts),
+ ok.
+
format_error(E) ->
lists:flatten(erl_lint:format_error(E)).
diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl
index a8264e5a84..62d9d0e0ae 100644
--- a/lib/stdlib/test/gen_fsm_SUITE.erl
+++ b/lib/stdlib/test/gen_fsm_SUITE.erl
@@ -521,14 +521,16 @@ error_format_status(Config) when is_list(Config) ->
error_logger_forwarder:register(),
OldFl = process_flag(trap_exit, true),
StateData = "called format_status",
+ Parent = self(),
{ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
%% bad return value in the gen_fsm loop
{'EXIT',{{bad_return_value, badreturn},_}} =
(catch gen_fsm:sync_send_event(Pid, badreturn)),
receive
{error,_GroupLeader,{Pid,
- "** State machine"++_,
- [Pid,{_,_,badreturn},idle,{formatted,StateData},_]}} ->
+ "** State machine "++_,
+ [Pid,badreturn,Parent,idle,{formatted,StateData},
+ {bad_return_value,badreturn}|_]}} ->
ok;
Other ->
io:format("Unexpected: ~p", [Other]),
@@ -541,12 +543,14 @@ terminate_crash_format(Config) when is_list(Config) ->
error_logger_forwarder:register(),
OldFl = process_flag(trap_exit, true),
StateData = crash_terminate,
+ Parent = self(),
{ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
stop_it(Pid),
receive
{error,_GroupLeader,{Pid,
- "** State machine"++_,
- [Pid,{_,_,_},idle,{formatted, StateData},_]}} ->
+ "** State machine "++_,
+ [Pid,stop,Parent,idle,{formatted, StateData},
+ {crash,terminate}|_]}} ->
ok;
Other ->
io:format("Unexpected: ~p", [Other]),
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 017939fdd6..16cf8f43f9 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -513,7 +513,9 @@ abnormal1dirty(Config) ->
%% trap exit since we must link to get the real bad_return_ error
abnormal2(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
{{{bad_return_from_state_function,badreturn},_},_} =
@@ -531,7 +533,9 @@ abnormal2(Config) ->
%% trap exit since we must link to get the real bad_return_ error
abnormal3(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
{{{bad_action_from_state_function,badaction},_},_} =
@@ -549,7 +553,9 @@ abnormal3(Config) ->
%% trap exit since we must link to get the real bad_return_ error
abnormal4(Config) ->
OldFl = process_flag(trap_exit, true),
- {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid} =
+ gen_statem:start_link(
+ ?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
BadTimeout = {badtimeout,4711,ouch},
@@ -689,7 +695,10 @@ state_enter(_Config) ->
end},
{ok,STM} =
gen_statem:start_link(
- ?MODULE, {map_statem,Machine,[state_enter]}, [{debug,[trace]}]),
+ ?MODULE, {map_statem,Machine,[state_enter]},
+ [{debug,[trace,{log,17}]}]),
+ ok = sys:log(STM, false),
+ ok = sys:log(STM, true),
[{1,enter,start,start}] = flush(),
{2,echo,start} = gen_statem:call(STM, echo),
@@ -708,6 +717,11 @@ state_enter(_Config) ->
{13,internal,start,wait}] = flush(),
{14,repeat,start} = gen_statem:call(STM, repeat),
[{15,enter,start,start}] = flush(),
+
+ {ok,Log} = sys:log(STM, get),
+ io:format("sys:log ~p~n", [Log]),
+ ok = sys:log(STM, print),
+
{16,stop} = gen_statem:call(STM, {stop,bye}),
[{'EXIT',STM,bye}] = flush(),
@@ -1470,7 +1484,8 @@ enter_loop(_Config) ->
%% Locally registered process + {local,Name}
{ok,Pid1a} =
- proc_lib:start_link(?MODULE, enter_loop, [local,local]),
+ proc_lib:start_link(
+ ?MODULE, enter_loop, [local,local,[{debug,[{log,7}]}]]),
yes = gen_statem:call(Pid1a, 'alive?'),
stopped = gen_statem:call(Pid1a, stop),
receive
@@ -1482,7 +1497,8 @@ enter_loop(_Config) ->
%% Unregistered process + {local,Name}
{ok,Pid1b} =
- proc_lib:start_link(?MODULE, enter_loop, [anon,local]),
+ proc_lib:start_link(
+ ?MODULE, enter_loop, [anon,local,[{debug,[log]}]]),
receive
{'EXIT',Pid1b,process_not_registered} ->
ok
@@ -1591,6 +1607,9 @@ enter_loop(_Config) ->
ok = verify_empty_msgq().
enter_loop(Reg1, Reg2) ->
+ enter_loop(Reg1, Reg2, []).
+%%
+enter_loop(Reg1, Reg2, Opts) ->
process_flag(trap_exit, true),
case Reg1 of
local -> register(armitage, self());
@@ -1602,15 +1621,15 @@ enter_loop(Reg1, Reg2) ->
case Reg2 of
local ->
gen_statem:enter_loop(
- ?MODULE, [], state0, [], {local,armitage});
+ ?MODULE, Opts, state0, [], {local,armitage});
global ->
gen_statem:enter_loop(
- ?MODULE, [], state0, [], {global,armitage});
+ ?MODULE, Opts, state0, [], {global,armitage});
via ->
gen_statem:enter_loop(
- ?MODULE, [], state0, [], {via, dummy_via, armitage});
+ ?MODULE, Opts, state0, [], {via, dummy_via, armitage});
anon ->
- gen_statem:enter_loop(?MODULE, [], state0, [])
+ gen_statem:enter_loop(?MODULE, Opts, state0, [])
end.
undef_code_change(_Config) ->
@@ -1642,7 +1661,9 @@ undef_terminate2(_Config) ->
undef_in_terminate(_Config) ->
Data = {undef_in_terminate, {?MODULE, terminate}},
- {ok, Statem} = gen_statem:start(?MODULE, {data, Data}, []),
+ {ok, Statem} =
+ gen_statem:start(
+ ?MODULE, {data, Data}, [{debug,[log]}]),
try
gen_statem:stop(Statem),
ct:fail(should_crash)
diff --git a/lib/stdlib/test/stdlib_bench_SUITE.erl b/lib/stdlib/test/stdlib_bench_SUITE.erl
index b937eeb06a..3456442088 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE.erl
@@ -38,7 +38,9 @@ groups() ->
[norm_nfc_list, norm_nfc_deep_l, norm_nfc_binary,
string_lexemes_list, string_lexemes_binary
]},
- {binary, [{repeat, 5}],
+ %% Only run 1 binary match repeat as it is very slow pre OTP-22.
+ %% The results seem to be stable enough anyway
+ {binary, [{repeat, 1}],
[match_single_pattern_no_match,
matches_single_pattern_no_match,
matches_single_pattern_eventual_match,
@@ -65,9 +67,9 @@ cases(gen_server) ->
[simple, simple_timer, simple_mon, simple_timer_mon,
generic, generic_timer];
cases(gen_statem) ->
- [generic, generic_fsm, generic_fsm_transit,
- generic_statem, generic_statem_transit,
- generic_statem_complex].
+ [generic, generic_log, generic_log100, generic_fsm, generic_fsm_transit,
+ generic_statem, generic_statem_log, generic_statem_log100,
+ generic_statem_transit, generic_statem_complex].
init_per_group(gen_server, Config) ->
compile_servers(Config),
@@ -164,19 +166,19 @@ norm_data(Config) ->
match_single_pattern_no_match(_Config) ->
Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsalp">>, 1000000),
- comment(test(binary, match, [Binary, <<"o">>])).
+ comment(test(100, binary, match, [Binary, <<"o">>])).
matches_single_pattern_no_match(_Config) ->
Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsalp">>, 1000000),
- comment(test(binary, matches, [Binary, <<"o">>])).
+ comment(test(100, binary, matches, [Binary, <<"o">>])).
matches_single_pattern_eventual_match(_Config) ->
Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsal\n">>, 1000000),
- comment(test(binary, matches, [Binary, <<"\n">>])).
+ comment(test(100, binary, matches, [Binary, <<"\n">>])).
matches_single_pattern_frequent_match(_Config) ->
Binary = binary:copy(<<"abc\n">>, 1000000),
- comment(test(binary, matches, [Binary, <<"abc">>])).
+ comment(test(100, binary, matches, [Binary, <<"abc">>])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -233,9 +235,11 @@ list() ->
random_byte_list(?SIZE).
test(Mod, Fun, Args) ->
- F = fun() -> loop(?N, Mod, Fun, Args) end,
+ test(?N, Mod, Fun, Args).
+test(Iter, Mod, Fun, Args) ->
+ F = fun() -> loop(Iter, Mod, Fun, Args) end,
{Time, ok} = timer:tc(fun() -> lspawn(F) end),
- report_mfa(Time, Mod).
+ report_mfa(Iter, Time, Mod).
loop(0, _M, _F, _A) -> garbage_collect(), ok;
loop(N, M, F, A) ->
@@ -248,8 +252,8 @@ lspawn(Fun) ->
{'DOWN', Ref, process, Pid, Rep} -> Rep
end.
-report_mfa(Time, Mod) ->
- Tps = round((?N*1000000)/Time),
+report_mfa(Iter, Time, Mod) ->
+ Tps = round((Iter*1000000)/Time),
ct_event:notify(#event{name = benchmark_data,
data = [{suite, "stdlib_" ++ atom_to_list(Mod)},
{value, Tps}]}),
@@ -290,12 +294,24 @@ simple_timer_mon(Config) when is_list(Config) ->
generic(Config) when is_list(Config) ->
comment(do_tests(generic, single_small, Config)).
+generic_log(Config) when is_list(Config) ->
+ comment(do_tests(generic_log, single_small, Config)).
+
+generic_log100(Config) when is_list(Config) ->
+ comment(do_tests(generic_log100, single_small, Config)).
+
generic_timer(Config) when is_list(Config) ->
comment(do_tests(generic_timer, single_small, Config)).
generic_statem(Config) when is_list(Config) ->
comment(do_tests(generic_statem, single_small, Config)).
+generic_statem_log(Config) when is_list(Config) ->
+ comment(do_tests(generic_statem_log, single_small, Config)).
+
+generic_statem_log100(Config) when is_list(Config) ->
+ comment(do_tests(generic_statem_log100, single_small, Config)).
+
generic_statem_transit(Config) when is_list(Config) ->
comment(do_tests(generic_statem_transit, single_small, Config)).
@@ -367,9 +383,9 @@ norm(T, Ref) ->
do_tests(Test, ParamSet, Config) ->
BenchmarkSuite = ?config(benchmark_suite, Config),
- {Client, ServerMod} = bench(Test),
+ {Client, ServerMod, ServerArg} = bench(Test),
{Parallelism, Message} = bench_params(ParamSet),
- Fun = create_clients(Message, ServerMod, Client, Parallelism),
+ Fun = create_clients(Message, ServerMod, ServerArg, Client, Parallelism),
{TotalLoops, AllPidTime} = run_test(Fun),
try ?CALLS_PER_LOOP * round((1000 * TotalLoops) / AllPidTime) of
PerSecond ->
@@ -484,27 +500,35 @@ generic_fsm_transit_client(N, M, P) ->
generic_fsm_transit_client(N+1, M, P).
bench(simple) ->
- {fun simple_client/3, simple_server};
+ {fun simple_client/3, simple_server, term};
bench(simple_timer) ->
- {fun simple_client_timer/3, simple_server_timer};
+ {fun simple_client_timer/3, simple_server_timer, term};
bench(simple_mon) ->
- {fun simple_client_mon/3, simple_server_mon};
+ {fun simple_client_mon/3, simple_server_mon, term};
bench(simple_timer_mon) ->
- {fun simple_client_timer_mon/3, simple_server_timer_mon};
+ {fun simple_client_timer_mon/3, simple_server_timer_mon, term};
bench(generic) ->
- {fun generic_client/3, generic_server};
+ {fun generic_client/3, generic_server, [term]};
+bench(generic_log) ->
+ {fun generic_client/3, generic_server, [term,{debug,[log]}]};
+bench(generic_log100) ->
+ {fun generic_client/3, generic_server, [term,{debug,[{log,100}]}]};
bench(generic_timer) ->
- {fun generic_timer_client/3, generic_server_timer};
+ {fun generic_timer_client/3, generic_server_timer, term};
bench(generic_statem) ->
- {fun generic_statem_client/3, generic_statem};
+ {fun generic_statem_client/3, generic_statem, [term]};
+bench(generic_statem_log) ->
+ {fun generic_statem_client/3, generic_statem, [term,{debug,[log]}]};
+bench(generic_statem_log100) ->
+ {fun generic_statem_client/3, generic_statem, [term,{debug,[{log,100}]}]};
bench(generic_statem_transit) ->
- {fun generic_statem_transit_client/3, generic_statem};
+ {fun generic_statem_transit_client/3, generic_statem, [term]};
bench(generic_statem_complex) ->
- {fun generic_statem_complex_client/3, generic_statem_complex};
+ {fun generic_statem_complex_client/3, generic_statem_complex, term};
bench(generic_fsm) ->
- {fun generic_fsm_client/3, generic_fsm};
+ {fun generic_fsm_client/3, generic_fsm, term};
bench(generic_fsm_transit) ->
- {fun generic_fsm_transit_client/3, generic_fsm}.
+ {fun generic_fsm_transit_client/3, generic_fsm, term}.
%% -> {Parallelism, MessageTerm}
bench_params(single_small) -> {1, small()};
@@ -532,10 +556,9 @@ parallelism() ->
_ -> 1
end.
-create_clients(M, ServerMod, Client, Parallel) ->
+create_clients(M, ServerMod, ServerArg, Client, Parallel) ->
fun() ->
- State = term,
- ServerPid = ServerMod:start(State),
+ ServerPid = ServerMod:start(ServerArg),
PidRefs = [spawn_monitor(fun() -> Client(0, M, ServerPid) end) ||
_ <- lists:seq(1, Parallel)],
timer:sleep(?MAX_TIME),
diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl
index abd61dcdef..3707d00dee 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl
@@ -8,8 +8,8 @@
-define(GEN_SERVER, gen_server).
-start(State) ->
- {ok, Pid} = ?GEN_SERVER:start(?MODULE, State, []),
+start([State|Opts]) ->
+ {ok, Pid} = ?GEN_SERVER:start(?MODULE, State, Opts),
Pid.
init(State) ->
diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl
index 2e0491f060..b106619568 100644
--- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl
+++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl
@@ -28,8 +28,8 @@
%% API
-start(Data) ->
- {ok, Pid} = gen_statem:start(?MODULE, Data, []),
+start([Data|Opts]) ->
+ {ok, Pid} = gen_statem:start(?MODULE, Data, Opts),
Pid.
stop(P) ->
@@ -52,7 +52,9 @@ init(Data) ->
state1({call, From}, {reply, M}, Data) ->
{keep_state, Data, {reply, From, M}};
state1({call, From}, {transit, M}, Data) ->
- {next_state, state2, Data, {reply, From, M}}.
+ {next_state, state2, Data,
+ [{reply, From, M},{state_timeout,5000,5000}]}.
state2({call, From}, {transit, M}, Data) ->
- {next_state, state1, Data, {reply, From, M}}.
+ {next_state, state1, Data,
+ [{reply, From, M},{state_timeout,5000,5000}]}.
diff --git a/lib/stdlib/test/unicode_util_SUITE.erl b/lib/stdlib/test/unicode_util_SUITE.erl
index 962b307b07..044b4e5834 100644
--- a/lib/stdlib/test/unicode_util_SUITE.erl
+++ b/lib/stdlib/test/unicode_util_SUITE.erl
@@ -126,17 +126,30 @@ verify_gc(Line0, N, Acc) ->
%io:format("Line: ~s~n",[Line]),
[Data|_Comments] = string:tokens(Line, "#"),
- %io:format("Data: ~w~n",[string:tokens(Data, " \t")]),
+ %% io:format("Data: ~w~n",[string:tokens(Data, " \t")]),
{Str,Res} = gc_test_data(string:tokens(Data, " \t"), [], [[]]),
- try
- Res = fetch(Str, fun unicode_util:gc/1),
- Acc
- catch _Cl:{badmatch, Other} ->
+ %% io:format("InputStr: ~w ~w~n",[Str,unicode:characters_to_binary(Str)]),
+ case verify_gc(Str, Res, N, Line) andalso
+ verify_gc(unicode:characters_to_binary(Str), Res, N, Line0) of
+ true -> Acc;
+ false -> Acc+1
+ end.
+
+verify_gc({error,_,[CP|_]}=Err, _Res, N, Line) ->
+ IsSurrogate = 16#D800 =< CP andalso CP =< 16#DFFF,
+ %% Surrogat is not valid in utf8 encoding only utf16
+ IsSurrogate orelse
+ io:format("~w: ~ts~n Error in unicode:characters_to_binary ~w~n", [N, Line, Err]),
+ IsSurrogate;
+verify_gc(Str, Res, N, Line) ->
+ try fetch(Str, fun unicode_util:gc/1) of
+ Res -> true;
+ Other ->
io:format("Failed: ~p~nInput: ~ts~n\t=> ~w |~ts|~n",[N, Line, Str, Str]),
io:format("Expected: ~p~n", [Res]),
io:format("Got: ~w~n", [Other]),
- Acc+1;
- Cl:R:Stacktrace ->
+ false
+ catch Cl:R:Stacktrace ->
io:format("~p: ~ts => |~tp|~n",[N, Line, Str]),
io:format("Expected: ~p~n", [Res]),
erlang:raise(Cl,R,Stacktrace)
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index caf5ecdbb4..e0217418fe 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.6
+STDLIB_VSN = 3.7
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 6906ef1553..6ad9bec2e6 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -1101,8 +1101,9 @@ lay_2(Node, Ctxt) ->
Ctxt1 = reset_prec(Ctxt),
D1 = lay(erl_syntax:constrained_function_type_body(Node),
Ctxt1),
+ Ctxt2 = Ctxt1#ctxt{clause = undefined},
D2 = lay(erl_syntax:constrained_function_type_argument(Node),
- Ctxt1),
+ Ctxt2),
beside(D1,
beside(floating(text(" when ")), D2));
@@ -1113,7 +1114,7 @@ lay_2(Node, Ctxt) ->
_ ->
{"fun(", ")"}
end,
- Ctxt1 = reset_prec(Ctxt),
+ Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined},
D1 = case erl_syntax:function_type_arguments(Node) of
any_arity ->
text("(...)");
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl
index 9dbd0e302a..6b42f7a0a1 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl
@@ -26,14 +26,14 @@
-export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1,
revert_map_type/1,
t_abstract_type/1,t_erl_parse_type/1,t_type/1, t_epp_dodger/1,
- t_comment_scan/1,t_igor/1,t_erl_tidy/1]).
+ t_comment_scan/1,t_igor/1,t_erl_tidy/1,t_prettypr/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app_test,appup_test,smoke_test,revert,revert_map,revert_map_type,
t_abstract_type,t_erl_parse_type,t_type,t_epp_dodger,
- t_comment_scan,t_igor,t_erl_tidy].
+ t_comment_scan,t_igor,t_erl_tidy,t_prettypr].
groups() ->
[].
@@ -300,6 +300,14 @@ t_comment_scan(Config) when is_list(Config) ->
ok = test_comment_scan(Filenames,DataDir),
ok.
+t_prettypr(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ Filenames = ["type_specs.erl",
+ "specs_and_funs.erl"],
+ ok = test_prettypr(Filenames,DataDir,PrivDir),
+ ok.
+
test_files(Config) ->
DataDir = ?config(data_dir, Config),
[ filename:join(DataDir,Filename) || Filename <- test_files() ].
@@ -307,7 +315,8 @@ test_files(Config) ->
test_files() ->
["syntax_tools_SUITE_test_module.erl",
"syntax_tools_test.erl",
- "type_specs.erl"].
+ "type_specs.erl",
+ "specs_and_funs.erl"].
t_igor(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
@@ -359,6 +368,27 @@ test_comment_scan([File|Files],DataDir) ->
test_comment_scan(Files,DataDir).
+test_prettypr([],_,_) -> ok;
+test_prettypr([File|Files],DataDir,PrivDir) ->
+ Filename = filename:join(DataDir,File),
+ io:format("Parsing ~p~n", [Filename]),
+ {ok, Fs0} = epp:parse_file(Filename, [], []),
+ Fs = erl_syntax:form_list(Fs0),
+ PP = erl_prettypr:format(Fs, [{paper, 120}, {ribbon, 110}]),
+ io:put_chars(PP),
+ OutFile = filename:join(PrivDir, File),
+ ok = file:write_file(OutFile,iolist_to_binary(PP)),
+ io:format("Parsing OutFile: ~s~n", [OutFile]),
+ {ok, Fs2} = epp:parse_file(OutFile, [], []),
+ case [Error || {error, _} = Error <- Fs2] of
+ [] ->
+ ok;
+ Errors ->
+ ?t:fail(Errors)
+ end,
+ test_prettypr(Files,DataDir,PrivDir).
+
+
test_epp_dodger([], _, _) -> ok;
test_epp_dodger([Filename|Files],DataDir,PrivDir) ->
io:format("Parsing ~p~n", [Filename]),
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl
new file mode 100644
index 0000000000..8dfeaf5a6b
--- /dev/null
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl
@@ -0,0 +1,18 @@
+-module(specs_and_funs).
+
+-export([my_apply/3, two/1]).
+
+%% OTP-15519, ERL-815
+
+-spec my_apply(Fun, Arg, fun((A) -> A)) -> Result when
+ Fun :: fun((Arg) -> Result),
+ Arg :: any(),
+ Result :: any().
+
+my_apply(Fun, Arg, _) ->
+ Fun(Arg).
+
+-spec two(fun((A) -> A)) -> fun((B) -> B).
+
+two(F) ->
+ F(fun(X) -> X end).
diff --git a/lib/tftp/doc/src/tftp.xml b/lib/tftp/doc/src/tftp.xml
index 4ed54bc462..57d64b7379 100644
--- a/lib/tftp/doc/src/tftp.xml
+++ b/lib/tftp/doc/src/tftp.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>tftp</module>
+ <module since="">tftp</module>
<modulesummary>Trivial FTP.</modulesummary>
<description>
<p>Interface module for the <c>tftp</c> application.</p>
@@ -172,7 +172,7 @@
<funcs>
<func>
- <name>change_config(daemons, Options) -> [{Pid, Result}]</name>
+ <name since="">change_config(daemons, Options) -> [{Pid, Result}]</name>
<fsummary>Changes configuration for all daemons.
</fsummary>
<type>
@@ -187,7 +187,7 @@
</func>
<func>
- <name>change_config(servers, Options) -> [{Pid, Result}]</name>
+ <name since="">change_config(servers, Options) -> [{Pid, Result}]</name>
<fsummary>Changes configuration for all servers.
</fsummary>
<type>
@@ -202,7 +202,7 @@
</func>
<func>
- <name>change_config(Pid, Options) -> Result</name>
+ <name since="">change_config(Pid, Options) -> Result</name>
<fsummary>Changes configuration for a TFTP daemon, server,
or client process.</fsummary>
<type>
@@ -217,7 +217,7 @@
</func>
<func>
- <name>info(daemons) -> [{Pid, Options}]</name>
+ <name since="">info(daemons) -> [{Pid, Options}]</name>
<fsummary>Returns information about all daemons.</fsummary>
<type>
<v>Pid = [pid()]</v>
@@ -230,7 +230,7 @@
</func>
<func>
- <name>info(servers) -> [{Pid, Options}]</name>
+ <name since="">info(servers) -> [{Pid, Options}]</name>
<fsummary>Returns information about all servers.</fsummary>
<type>
<v>Pid = [pid()]</v>
@@ -243,7 +243,7 @@
</func>
<func>
- <name>info(Pid) -> {ok, Options} | {error, Reason}</name>
+ <name since="">info(Pid) -> {ok, Options} | {error, Reason}</name>
<fsummary>Returns information about a daemon, server, or client process.</fsummary>
<type>
<v>Options = [option()]</v>
@@ -255,7 +255,7 @@
</func>
<func>
- <name>read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
+ <name since="">read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
<fsummary>Reads a (virtual) file from a TFTP server.</fsummary>
<type>
<v>RemoteFilename = string()</v>
@@ -285,7 +285,7 @@
</func>
<func>
- <name>start(Options) -> {ok, Pid} | {error, Reason}</name>
+ <name since="">start(Options) -> {ok, Pid} | {error, Reason}</name>
<fsummary>Starts a daemon process.</fsummary>
<type>
<v>Options = [option()]</v>
@@ -301,7 +301,7 @@
</func>
<func>
- <name>write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
+ <name since="">write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name>
<fsummary>Writes a (virtual) file to a TFTP server.</fsummary>
<type>
<v>RemoteFilename = string()</v>
@@ -389,7 +389,7 @@
<funcs>
<func>
- <name>Module:abort(Code, Text, State) -> ok</name>
+ <name since="OTP 18.1">Module:abort(Code, Text, State) -> ok</name>
<fsummary>Aborts the file transfer.</fsummary>
<type>
<v>Code = undef | enoent | eacces | enospc</v>
@@ -413,7 +413,7 @@
</func>
<func>
- <name>Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
<fsummary>Opens a file for read or write access.</fsummary>
<type>
<v>Peer = {PeerType, PeerHost, PeerPort}</v>
@@ -448,7 +448,7 @@
</func>
<func>
- <name>Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name>
<fsummary>Prepares to open a file on the client side.</fsummary>
<type>
<v>Peer = {PeerType, PeerHost, PeerPort}</v>
@@ -483,7 +483,7 @@
</func>
<func>
- <name>Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name>
<fsummary>Reads a chunk from the file.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -510,7 +510,7 @@
</func>
<func>
- <name>Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name>
+ <name since="OTP 18.1">Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name>
<fsummary>Writes a chunk to the file.</fsummary>
<type>
<v>Bin = binary()</v>
@@ -549,7 +549,7 @@
<funcs>
<func>
- <name>Logger:error_msg(Format, Data) -> ok | exit(Reason)</name>
+ <name since="OTP 18.1">Logger:error_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs an error message.</fsummary>
<type>
<v>Format = string()</v>
@@ -565,7 +565,7 @@
</func>
<func>
- <name>Logger:info_msg(Format, Data) -> ok | exit(Reason)</name>
+ <name since="OTP 18.1">Logger:info_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs an info message.</fsummary>
<type>
<v>Format = string()</v>
@@ -579,7 +579,7 @@
</func>
<func>
- <name>Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name>
+ <name since="OTP 18.1">Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name>
<fsummary>Logs a warning message.</fsummary>
<type>
<v>Format = string()</v>
diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml
index 6c6b20aad8..31bf7266c7 100644
--- a/lib/tools/doc/src/cover.xml
+++ b/lib/tools/doc/src/cover.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>cover</module>
+ <module since="">cover</module>
<modulesummary>A Coverage Analysis Tool for Erlang</modulesummary>
<description>
<p>The module <c>cover</c> provides a set of functions for coverage
@@ -115,7 +115,7 @@
</description>
<funcs>
<func>
- <name>start() -> {ok,Pid} | {error,Reason}</name>
+ <name since="">start() -> {ok,Pid} | {error,Reason}</name>
<fsummary>Start Cover.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -128,7 +128,7 @@
</desc>
</func>
<func>
- <name>start(Nodes) -> {ok,StartedNodes} | {error,not_main_node}</name>
+ <name since="">start(Nodes) -> {ok,StartedNodes} | {error,not_main_node}</name>
<fsummary>Start Cover on remote nodes.</fsummary>
<type>
<v>Nodes = StartedNodes = [atom()]</v>
@@ -139,10 +139,10 @@
</desc>
</func>
<func>
- <name>compile(ModFiles) -> Result | [Result]</name>
- <name>compile(ModFiles, Options) -> Result | [Result]</name>
- <name>compile_module(ModFiles) -> Result | [Result]</name>
- <name>compile_module(ModFiles, Options) -> Result | [Result]</name>
+ <name since="">compile(ModFiles) -> Result | [Result]</name>
+ <name since="">compile(ModFiles, Options) -> Result | [Result]</name>
+ <name since="">compile_module(ModFiles) -> Result | [Result]</name>
+ <name since="">compile_module(ModFiles, Options) -> Result | [Result]</name>
<fsummary>Compile one or more modules for Cover analysis.</fsummary>
<type>
<v>ModFiles = ModFile | [ModFile]</v>
@@ -176,9 +176,9 @@
</desc>
</func>
<func>
- <name>compile_directory() -> [Result] | {error,Reason}</name>
- <name>compile_directory(Dir) -> [Result] | {error,Reason}</name>
- <name>compile_directory(Dir, Options) -> [Result] | {error,Reason}</name>
+ <name since="">compile_directory() -> [Result] | {error,Reason}</name>
+ <name since="">compile_directory(Dir) -> [Result] | {error,Reason}</name>
+ <name since="">compile_directory(Dir, Options) -> [Result] | {error,Reason}</name>
<fsummary>Compile all modules in a directory for Cover analysis.</fsummary>
<type>
<v>Dir = string()</v>
@@ -199,7 +199,7 @@
</desc>
</func>
<func>
- <name>compile_beam(ModFiles) -> Result | [Result]</name>
+ <name since="">compile_beam(ModFiles) -> Result | [Result]</name>
<fsummary>Compile one or more modules for Cover analysis, using existing beam(s).</fsummary>
<type>
<v>ModFiles = ModFile | [ModFile]</v>
@@ -241,8 +241,8 @@
</desc>
</func>
<func>
- <name>compile_beam_directory() -> [Result] | {error,Reason}</name>
- <name>compile_beam_directory(Dir) -> [Result] | {error,Reason}</name>
+ <name since="">compile_beam_directory() -> [Result] | {error,Reason}</name>
+ <name since="">compile_beam_directory(Dir) -> [Result] | {error,Reason}</name>
<fsummary>Compile all .beam files in a directory for Cover analysis.</fsummary>
<type>
<v>Dir = string()</v>
@@ -260,14 +260,14 @@
</desc>
</func>
<func>
- <name>analyse() -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Analysis) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules, Analysis) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Analysis, Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse(Modules, Analysis, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="OTP 18.0">analyse() -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Analysis) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules, Analysis) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Analysis, Level) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse(Modules, Analysis, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name>
<fsummary>Analyse one or more Cover compiled modules.</fsummary>
<type>
<v>Modules = Module | [Module]</v>
@@ -305,10 +305,10 @@
</desc>
</func>
<func>
- <name>analyse_to_file() -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse_to_file(Modules) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse_to_file(Options) -> {result,Ok,Fail} | {error,not_main_node}</name>
- <name>analyse_to_file(Modules,Options) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="OTP 18.0">analyse_to_file() -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse_to_file(Modules) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse_to_file(Options) -> {result,Ok,Fail} | {error,not_main_node}</name>
+ <name since="">analyse_to_file(Modules,Options) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name>
<fsummary>Detailed coverage analysis of one or more Cover compiled modules.</fsummary>
<type>
<v>Modules = Module | [Module]</v>
@@ -359,10 +359,10 @@
</desc>
</func>
<func>
- <name>async_analyse_to_file(Module) -> </name>
- <name>async_analyse_to_file(Module,Options) -> </name>
- <name>async_analyse_to_file(Module, OutFile) -> </name>
- <name>async_analyse_to_file(Module, OutFile, Options) -> pid()</name>
+ <name since="OTP R14B02">async_analyse_to_file(Module) -> </name>
+ <name since="OTP R14B02">async_analyse_to_file(Module,Options) -> </name>
+ <name since="OTP R14B02">async_analyse_to_file(Module, OutFile) -> </name>
+ <name since="OTP R14B02">async_analyse_to_file(Module, OutFile, Options) -> pid()</name>
<fsummary>Asynchronous call to analyse_to_file.</fsummary>
<type>
<v>Module = atom()</v>
@@ -384,7 +384,7 @@
</desc>
</func>
<func>
- <name>modules() -> [Module] | {error,not_main_node}</name>
+ <name since="">modules() -> [Module] | {error,not_main_node}</name>
<fsummary>Return all Cover compiled modules.</fsummary>
<type>
<v>Module = atom()</v>
@@ -395,7 +395,7 @@
</desc>
</func>
<func>
- <name>imported_modules() -> [Module] | {error,not_main_node}</name>
+ <name since="">imported_modules() -> [Module] | {error,not_main_node}</name>
<fsummary>Return all modules for which there are imported data.</fsummary>
<type>
<v>Module = atom()</v>
@@ -406,7 +406,7 @@
</desc>
</func>
<func>
- <name>imported() -> [File] | {error,not_main_node}</name>
+ <name since="">imported() -> [File] | {error,not_main_node}</name>
<fsummary>Return all imported files.</fsummary>
<type>
<v>File = string()</v>
@@ -416,7 +416,7 @@
</desc>
</func>
<func>
- <name>which_nodes() -> [Node] | {error,not_main_node}</name>
+ <name since="">which_nodes() -> [Node] | {error,not_main_node}</name>
<fsummary>Return all nodes that are part of the coverage analysis.</fsummary>
<type>
<v>Node = atom()</v>
@@ -428,7 +428,7 @@
</desc>
</func>
<func>
- <name>is_compiled(Module) -> {file,File} | false | {error,not_main_node}</name>
+ <name since="">is_compiled(Module) -> {file,File} | false | {error,not_main_node}</name>
<fsummary>Check if a module is Cover compiled.</fsummary>
<type>
<v>Module = atom()</v>
@@ -442,8 +442,8 @@
</desc>
</func>
<func>
- <name>reset(Module) -></name>
- <name>reset() -> ok | {error,not_main_node}</name>
+ <name since="">reset(Module) -></name>
+ <name since="">reset() -> ok | {error,not_main_node}</name>
<fsummary>Reset coverage data for Cover compiled modules.</fsummary>
<type>
<v>Module = atom()</v>
@@ -458,8 +458,8 @@
</desc>
</func>
<func>
- <name>export(ExportFile)</name>
- <name>export(ExportFile,Module) -> ok | {error,Reason}</name>
+ <name since="">export(ExportFile)</name>
+ <name since="">export(ExportFile,Module) -> ok | {error,Reason}</name>
<fsummary>Reset coverage data for Cover compiled modules.</fsummary>
<type>
<v>ExportFile = string()</v>
@@ -480,7 +480,7 @@
</desc>
</func>
<func>
- <name>import(ExportFile) -> ok | {error,Reason}</name>
+ <name since="">import(ExportFile) -> ok | {error,Reason}</name>
<fsummary>Reset coverage data for Cover compiled modules.</fsummary>
<type>
<v>ExportFile = string()</v>
@@ -504,14 +504,14 @@
</desc>
</func>
<func>
- <name>stop() -> ok | {error,not_main_node}</name>
+ <name since="">stop() -> ok | {error,not_main_node}</name>
<fsummary>Stop Cover.</fsummary>
<desc>
<p>Stops the Cover server and unloads all Cover compiled code.</p>
</desc>
</func>
<func>
- <name>stop(Nodes) -> ok | {error,not_main_node}</name>
+ <name since="">stop(Nodes) -> ok | {error,not_main_node}</name>
<fsummary>Stop Cover on remote nodes.</fsummary>
<type>
<v>Nodes = [atom()]</v>
@@ -523,7 +523,7 @@
</desc>
</func>
<func>
- <name>flush(Nodes) -> ok | {error,not_main_node}</name>
+ <name since="OTP R16B">flush(Nodes) -> ok | {error,not_main_node}</name>
<fsummary>Collect cover data from remote nodes.</fsummary>
<type>
<v>Nodes = [atom()]</v>
diff --git a/lib/tools/doc/src/cprof.xml b/lib/tools/doc/src/cprof.xml
index df0acbe617..b6af8b6d28 100644
--- a/lib/tools/doc/src/cprof.xml
+++ b/lib/tools/doc/src/cprof.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>cprof.sgml</file>
</header>
- <module>cprof</module>
+ <module since="">cprof</module>
<modulesummary>A simple Call Count Profiling Tool using breakpoints for minimal runtime performance impact.</modulesummary>
<description>
<p>The <c>cprof</c> module is used to profile a program
@@ -65,10 +65,10 @@
</description>
<funcs>
<func>
- <name>analyse() -> {AllCallCount, ModAnalysisList}</name>
- <name>analyse(Limit) -> {AllCallCount, ModAnalysisList}</name>
- <name>analyse(Mod) -> ModAnalysis</name>
- <name>analyse(Mod, Limit) -> ModAnalysis</name>
+ <name since="">analyse() -> {AllCallCount, ModAnalysisList}</name>
+ <name since="">analyse(Limit) -> {AllCallCount, ModAnalysisList}</name>
+ <name since="">analyse(Mod) -> ModAnalysis</name>
+ <name since="">analyse(Mod, Limit) -> ModAnalysis</name>
<fsummary>Collect and analyse call counters.</fsummary>
<type>
<v>Limit = integer()</v>
@@ -122,7 +122,7 @@
</desc>
</func>
<func>
- <name>pause() -> integer()</name>
+ <name since="">pause() -> integer()</name>
<fsummary>Pause running call count trace for all functions.</fsummary>
<desc>
<p>Pause call count tracing for all functions in all modules
@@ -137,9 +137,9 @@
</desc>
</func>
<func>
- <name>pause(FuncSpec) -> integer()</name>
- <name>pause(Mod, Func) -> integer()</name>
- <name>pause(Mod, Func, Arity) -> integer()</name>
+ <name since="">pause(FuncSpec) -> integer()</name>
+ <name since="">pause(Mod, Func) -> integer()</name>
+ <name since="">pause(Mod, Func, Arity) -> integer()</name>
<fsummary>Pause running call count trace for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
@@ -167,10 +167,10 @@
</desc>
</func>
<func>
- <name>restart() -> integer()</name>
- <name>restart(FuncSpec) -> integer()</name>
- <name>restart(Mod, Func) -> integer()</name>
- <name>restart(Mod, Func, Arity) -> integer()</name>
+ <name since="">restart() -> integer()</name>
+ <name since="">restart(FuncSpec) -> integer()</name>
+ <name since="">restart(Mod, Func) -> integer()</name>
+ <name since="">restart(Mod, Func, Arity) -> integer()</name>
<fsummary>Restart existing call counters for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
@@ -197,7 +197,7 @@
</desc>
</func>
<func>
- <name>start() -> integer()</name>
+ <name since="">start() -> integer()</name>
<fsummary>Start call count tracing for all functions.</fsummary>
<desc>
<p>Start call count tracing for all functions in all modules,
@@ -212,9 +212,9 @@
</desc>
</func>
<func>
- <name>start(FuncSpec) -> integer()</name>
- <name>start(Mod, Func) -> integer()</name>
- <name>start(Mod, Func, Arity) -> integer()</name>
+ <name since="">start(FuncSpec) -> integer()</name>
+ <name since="">start(Mod, Func) -> integer()</name>
+ <name since="">start(Mod, Func, Arity) -> integer()</name>
<fsummary>Start call count tracing for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
@@ -240,7 +240,7 @@
</desc>
</func>
<func>
- <name>stop() -> integer()</name>
+ <name since="">stop() -> integer()</name>
<fsummary>Stop call count tracing for all functions.</fsummary>
<desc>
<p>Stop call count tracing for all functions in all modules,
@@ -255,9 +255,9 @@
</desc>
</func>
<func>
- <name>stop(FuncSpec) -> integer()</name>
- <name>stop(Mod, Func) -> integer()</name>
- <name>stop(Mod, Func, Arity) -> integer()</name>
+ <name since="">stop(FuncSpec) -> integer()</name>
+ <name since="">stop(Mod, Func) -> integer()</name>
+ <name since="">stop(Mod, Func, Arity) -> integer()</name>
<fsummary>Stop call count tracing for matching functions.</fsummary>
<type>
<v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v>
diff --git a/lib/tools/doc/src/eprof.xml b/lib/tools/doc/src/eprof.xml
index f098b7d39e..c9e4edd991 100644
--- a/lib/tools/doc/src/eprof.xml
+++ b/lib/tools/doc/src/eprof.xml
@@ -28,7 +28,7 @@
<date></date>
<rev></rev>
</header>
- <module>eprof</module>
+ <module since="">eprof</module>
<modulesummary>A Time Profiling Tool for Erlang</modulesummary>
<description>
<p>The module <c>eprof</c> provides a set of functions for time
@@ -40,7 +40,7 @@
</description>
<funcs>
<func>
- <name>start() -> {ok,Pid} | {error,Reason}</name>
+ <name since="">start() -> {ok,Pid} | {error,Reason}</name>
<fsummary>Start Eprof.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -51,9 +51,9 @@
</desc>
</func>
<func>
- <name>start_profiling(Rootset) -> profiling | {error, Reason}</name>
- <name>start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name>
- <name>start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name>
+ <name since="">start_profiling(Rootset) -> profiling | {error, Reason}</name>
+ <name since="OTP R14B">start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name>
+ <name since="OTP R16B01">start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name>
<fsummary>Start profiling.</fsummary>
<type>
<v>Rootset = [atom() | pid()]</v>
@@ -79,7 +79,7 @@
</desc>
</func>
<func>
- <name>stop_profiling() -> profiling_stopped | profiling_already_stopped</name>
+ <name since="">stop_profiling() -> profiling_stopped | profiling_already_stopped</name>
<fsummary>Stop profiling.</fsummary>
<desc>
<p>Stops profiling started with <c>start_profiling/1</c> or
@@ -87,14 +87,14 @@
</desc>
</func>
<func>
- <name>profile(Fun) -> profiling | {error, Reason}</name>
- <name>profile(Fun, Options) -> profiling | {error, Reason}</name>
- <name>profile(Rootset) -> profiling | {error, Reason}</name>
- <name>profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name>
- <name>profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name>
- <name>profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name>
- <name>profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name>
- <name>profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name>
+ <name since="">profile(Fun) -> profiling | {error, Reason}</name>
+ <name since="">profile(Fun, Options) -> profiling | {error, Reason}</name>
+ <name since="">profile(Rootset) -> profiling | {error, Reason}</name>
+ <name since="">profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name>
+ <name since="OTP R14B">profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name>
+ <name since="">profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name>
+ <name since="OTP R14B">profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name>
+ <name since="OTP R16B01">profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name>
<fsummary>Start profiling.</fsummary>
<type>
<v>Rootset = [atom() | pid()]</v>
@@ -128,9 +128,9 @@
</desc>
</func>
<func>
- <name>analyze() -> ok</name>
- <name>analyze(Type) -> ok</name>
- <name>analyze(Type,Options) -> ok</name>
+ <name since="OTP R14B">analyze() -> ok</name>
+ <name since="OTP R14B">analyze(Type) -> ok</name>
+ <name since="OTP R14B">analyze(Type,Options) -> ok</name>
<fsummary>Display profiling results per process.</fsummary>
<type>
<v>Type = procs | total</v>
@@ -152,7 +152,7 @@
</desc>
</func>
<func>
- <name>log(File) -> ok</name>
+ <name since="">log(File) -> ok</name>
<fsummary>Activate logging of <c>eprof</c>printouts.</fsummary>
<type>
<v>File = atom() | string()</v>
@@ -164,7 +164,7 @@
</desc>
</func>
<func>
- <name>stop() -> stopped</name>
+ <name since="">stop() -> stopped</name>
<fsummary>Stop Eprof.</fsummary>
<desc>
<p>Stops the Eprof server.</p>
diff --git a/lib/tools/doc/src/fprof.xml b/lib/tools/doc/src/fprof.xml
index 1fd828d127..4bb8862016 100644
--- a/lib/tools/doc/src/fprof.xml
+++ b/lib/tools/doc/src/fprof.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>fprof.sgml</file>
</header>
- <module>fprof</module>
+ <module since="">fprof</module>
<modulesummary>A Time Profiling Tool using trace to file for minimal runtime performance impact.</modulesummary>
<description>
<p>This module is used to profile a program
@@ -101,7 +101,7 @@
</description>
<funcs>
<func>
- <name>start() -> {ok, Pid} | {error, {already_started, Pid}}</name>
+ <name since="">start() -> {ok, Pid} | {error, {already_started, Pid}}</name>
<fsummary>Starts the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -117,14 +117,14 @@
</desc>
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="">stop() -> ok</name>
<fsummary>Same as <c>stop(normal)</c>.</fsummary>
<desc>
<p>Same as <c>stop(normal)</c>.</p>
</desc>
</func>
<func>
- <name>stop(Reason) -> ok</name>
+ <name since="">stop(Reason) -> ok</name>
<fsummary>Stops the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Reason = term()</v>
@@ -149,7 +149,7 @@
</desc>
</func>
<func>
- <name>apply(Func, Args) -> term()</name>
+ <name since="">apply(Func, Args) -> term()</name>
<fsummary>Same as <c>apply(Func, Args, [])</c>.</fsummary>
<type>
<v>Func = function() | {Module, Function}</v>
@@ -162,7 +162,7 @@
</desc>
</func>
<func>
- <name>apply(Module, Function, Args) -> term()</name>
+ <name since="">apply(Module, Function, Args) -> term()</name>
<fsummary>Same as <c>apply({Module, Function}, Args, [])</c>.</fsummary>
<type>
<v>Args = [term()]</v>
@@ -174,7 +174,7 @@
</desc>
</func>
<func>
- <name>apply(Func, Args, OptionList) -> term()</name>
+ <name since="">apply(Func, Args, OptionList) -> term()</name>
<fsummary>Calls <c>erlang:apply(Func, Args)</c>surrounded by<c>trace([start | OptionList])</c>and<c>trace(stop)</c>.</fsummary>
<type>
<v>Func = function() | {Module, Function}</v>
@@ -210,7 +210,7 @@
</desc>
</func>
<func>
- <name>apply(Module, Function, Args, OptionList) -> term()</name>
+ <name since="">apply(Module, Function, Args, OptionList) -> term()</name>
<fsummary>Same as <c>apply({Module, Function}, Args, OptionList)</c>.</fsummary>
<type>
<v>Module = atom()</v>
@@ -228,7 +228,7 @@
</desc>
</func>
<func>
- <name>trace(start, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(start, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([start, {file, Filename}])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -238,7 +238,7 @@
</desc>
</func>
<func>
- <name>trace(verbose, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(verbose, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([start, verbose, {file, Filename}])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -249,7 +249,7 @@
</desc>
</func>
<func>
- <name>trace(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -262,7 +262,7 @@
</desc>
</func>
<func>
- <name>trace(verbose) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(verbose) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([start, verbose])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -272,7 +272,7 @@
</desc>
</func>
<func>
- <name>trace(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([OptionName])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -283,7 +283,7 @@
</desc>
</func>
<func>
- <name>trace({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>trace([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -296,7 +296,7 @@
</desc>
</func>
<func>
- <name>trace([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">trace([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Starts or stops tracing.</fsummary>
<type>
<v>Option = start | stop | {procs, PidSpec} | {procs, [PidSpec]} | verbose | {verbose, bool()} | file | {file, Filename} | {tracer, Tracer}</v>
@@ -360,7 +360,7 @@
</desc>
</func>
<func>
- <name>profile() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -370,7 +370,7 @@
</desc>
</func>
<func>
- <name>profile(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -383,7 +383,7 @@
</desc>
</func>
<func>
- <name>profile(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([OptionName])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -394,7 +394,7 @@
</desc>
</func>
<func>
- <name>profile({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>profile([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -407,7 +407,7 @@
</desc>
</func>
<func>
- <name>profile([Option]) -> ok | {ok, Tracer} | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">profile([Option]) -> ok | {ok, Tracer} | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Compiles a trace into raw profile data held by the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Option = file | {file, Filename} | dump | {dump, Dump} | append | start | stop</v>
@@ -465,7 +465,7 @@
</desc>
</func>
<func>
- <name>analyse() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([])</c>.</fsummary>
<type>
<v>Reason = term()</v>
@@ -475,7 +475,7 @@
</desc>
</func>
<func>
- <name>analyse(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -488,7 +488,7 @@
</desc>
</func>
<func>
- <name>analyse(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([OptionName])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -499,7 +499,7 @@
</desc>
</func>
<func>
- <name>analyse({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Same as <c>analyse([{OptionName, OptionValue}])</c>.</fsummary>
<type>
<v>OptionName = atom()</v>
@@ -512,7 +512,7 @@
</desc>
</func>
<func>
- <name>analyse([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
+ <name since="">analyse([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name>
<fsummary>Analyses raw profile data in the <c>fprof</c>&nbsp;server.</fsummary>
<type>
<v>Option = dest | {dest, Dest} | append | {cols, Cols} | callers | {callers, bool()} | no_callers | {sort, SortSpec} | totals | {totals, bool()} | details | {details, bool()} | no_details</v>
diff --git a/lib/tools/doc/src/instrument.xml b/lib/tools/doc/src/instrument.xml
index 79bacb2927..7e9cbaebb0 100644
--- a/lib/tools/doc/src/instrument.xml
+++ b/lib/tools/doc/src/instrument.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>instrument.sgml</file>
</header>
- <module>instrument</module>
+ <module since="">instrument</module>
<modulesummary>Analysis and Utility Functions for Instrumentation</modulesummary>
<description>
<p>The module <c>instrument</c> contains support for studying the resource
@@ -92,7 +92,7 @@
<funcs>
<func>
- <name name="allocations" arity="0"/>
+ <name name="allocations" arity="0" since="OTP 21.0"/>
<fsummary>Return a summary of all allocations in the system.</fsummary>
<desc>
<p>Shorthand for
@@ -101,7 +101,7 @@
</func>
<func>
- <name name="allocations" arity="1"/>
+ <name name="allocations" arity="1" since="OTP 21.0"/>
<fsummary>Return a summary of all allocations filtered by allocator type
and scheduler id.</fsummary>
<desc>
@@ -173,7 +173,7 @@
</func>
<func>
- <name name="carriers" arity="0"/>
+ <name name="carriers" arity="0" since="OTP 21.0"/>
<fsummary>Return a list of all carriers in the system.</fsummary>
<desc>
<p>Shorthand for
@@ -182,7 +182,7 @@
</func>
<func>
- <name name="carriers" arity="1"/>
+ <name name="carriers" arity="1" since="OTP 21.0"/>
<fsummary>Return a list of all carriers filtered by allocator type and
scheduler id.</fsummary>
<desc>
diff --git a/lib/tools/doc/src/lcnt.xml b/lib/tools/doc/src/lcnt.xml
index d2595cdb60..1d434decfc 100644
--- a/lib/tools/doc/src/lcnt.xml
+++ b/lib/tools/doc/src/lcnt.xml
@@ -34,7 +34,7 @@
<rev>PA1</rev>
<file>lcnt.xml</file>
</header>
- <module>lcnt</module>
+ <module since="OTP R13B04">lcnt</module>
<modulesummary>A runtime system Lock Profiling tool.</modulesummary>
<description>
<p>The <c>lcnt</c> module is used to profile the internal ethread locks in the
@@ -71,7 +71,7 @@
<funcs>
<func>
- <name>start() -> {ok, Pid} | {error, {already_started, Pid}} </name>
+ <name since="OTP R13B04">start() -> {ok, Pid} | {error, {already_started, Pid}} </name>
<fsummary>Starts the lock profiler server.</fsummary>
<type>
<v>Pid = pid()</v>
@@ -84,7 +84,7 @@
</func>
<func>
- <name>stop() -> ok</name>
+ <name since="OTP R13B04">stop() -> ok</name>
<fsummary>Stops the lock profiler server.</fsummary>
<desc>
<p>Stops the lock profiler server.</p>
@@ -92,13 +92,13 @@
</func>
<func>
- <name>collect() -> ok</name>
+ <name since="OTP R13B04">collect() -> ok</name>
<fsummary>Same as <c>collect(node())</c>.</fsummary>
<desc><p>Same as <c>collect(node())</c>.</p></desc>
</func>
<func>
- <name>collect(Node) -> ok</name>
+ <name since="OTP R13B04">collect(Node) -> ok</name>
<fsummary>Collects lock statistics from the runtime system.</fsummary>
<type>
<v>Node = node()</v>
@@ -113,13 +113,13 @@
</func>
<func>
- <name>clear() -> ok</name>
+ <name since="OTP R13B04">clear() -> ok</name>
<fsummary>Same as <c>clear(node())</c>.</fsummary>
<desc><p>Same as <c>clear(node())</c>.</p></desc>
</func>
<func>
- <name>clear(Node) -> ok</name>
+ <name since="OTP R13B04">clear(Node) -> ok</name>
<fsummary>Clears the internal lock statistics from runtime system.</fsummary>
<type>
<v>Node = node()</v>
@@ -133,12 +133,12 @@
</desc>
</func>
<func>
- <name>conflicts() -> ok</name>
+ <name since="OTP R13B04">conflicts() -> ok</name>
<fsummary>Same as <c>conflicts([])</c>.</fsummary>
<desc><p>Same as <c>conflicts([])</c>.</p></desc>
</func>
<func>
- <name>conflicts([Option]) -> ok</name>
+ <name since="OTP R13B04">conflicts([Option]) -> ok</name>
<fsummary>Prints a list of internal lock counters.</fsummary>
<type>
<v>Option = {sort, Sort} | {reverse, bool()} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v>
@@ -154,14 +154,14 @@
</func>
<func>
- <name>locations() -> ok</name>
+ <name since="OTP R13B04">locations() -> ok</name>
<fsummary>Same as <c>locations([])</c>.</fsummary>
<desc>
<p>Same as <c>locations([])</c>.</p>
</desc>
</func>
<func>
- <name>locations([Option]) -> ok</name>
+ <name since="OTP R13B04">locations([Option]) -> ok</name>
<fsummary>Prints a list of internal lock counters by source code locations.</fsummary>
<type>
<v>Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v>
@@ -177,12 +177,12 @@
</func>
<func>
- <name>inspect(Lock) -> ok</name>
+ <name since="OTP R13B04">inspect(Lock) -> ok</name>
<fsummary>Same as <c>inspect(Lock, [])</c>.</fsummary>
<desc><p>Same as <c>inspect(Lock, [])</c>.</p></desc>
</func>
<func>
- <name>inspect(Lock, [Option]) -> ok</name>
+ <name since="OTP R13B04">inspect(Lock, [Option]) -> ok</name>
<fsummary>Prints a list of internal lock counters for a specific lock.</fsummary>
<type>
<v>Lock = Name | {Name, Id | [Id]}</v>
@@ -268,7 +268,7 @@
</func>
<func>
- <name>information() -> ok</name>
+ <name since="OTP R13B04">information() -> ok</name>
<fsummary>Prints lcnt server state and generic information about collected lock statistics.</fsummary>
<desc>
<p>Prints lcnt server state and generic information about collected lock statistics.</p>
@@ -276,7 +276,7 @@
</func>
<func>
- <name>swap_pid_keys() -> ok</name>
+ <name since="OTP R13B04">swap_pid_keys() -> ok</name>
<fsummary>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</fsummary>
<desc>
<p>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</p>
@@ -284,7 +284,7 @@
</func>
<func>
- <name>load(Filename) -> ok</name>
+ <name since="OTP R13B04">load(Filename) -> ok</name>
<fsummary>Restores previously saved data to the server.</fsummary>
<type>
<v>Filename = filename()</v>
@@ -295,7 +295,7 @@
</func>
<func>
- <name>save(Filename) -> ok</name>
+ <name since="OTP R13B04">save(Filename) -> ok</name>
<fsummary>Saves the collected data to file.</fsummary>
<type>
<v>Filename = filename()</v>
@@ -312,7 +312,7 @@
</section>
<funcs>
<func>
- <name>apply(Fun) -> term()</name>
+ <name since="OTP R13B04">apply(Fun) -> term()</name>
<fsummary>Same as <c>apply(Fun, [])</c>.</fsummary>
<type>
<v>Fun = fun()</v>
@@ -322,7 +322,7 @@
</desc>
</func>
<func>
- <name>apply(Fun, Args) -> term()</name>
+ <name since="OTP R13B04">apply(Fun, Args) -> term()</name>
<fsummary>Same as <c>apply(Module, Function, Args)</c>.</fsummary>
<type>
<v>Fun = fun()</v>
@@ -333,7 +333,7 @@
</desc>
</func>
<func>
- <name>apply(Module, Function, Args) -> term()</name>
+ <name since="OTP R13B04">apply(Module, Function, Args) -> term()</name>
<fsummary>Clears counters, applies function and collects the profiling results.</fsummary>
<type>
<v>Module = atom()</v>
@@ -358,12 +358,12 @@
</func>
<func>
- <name>pid(Id, Serial) -> pid()</name>
+ <name since="OTP R13B04">pid(Id, Serial) -> pid()</name>
<fsummary>Same as <c>pid(node(), Id, Serial)</c>.</fsummary>
<desc><p>Same as <c>pid(node(), Id, Serial)</c>.</p></desc>
</func>
<func>
- <name>pid(Node, Id, Serial) -> pid()</name>
+ <name since="OTP R13B04">pid(Node, Id, Serial) -> pid()</name>
<fsummary>Creates a process id with creation 0.</fsummary>
<type>
<v>Node = node()</v>
@@ -376,12 +376,12 @@
</func>
<func>
- <name>port(Id) -> port()</name>
+ <name since="OTP R13B04">port(Id) -> port()</name>
<fsummary>Same as <c>port(node(), Id)</c>.</fsummary>
<desc><p>Same as <c>port(node(), Id)</c>.</p></desc>
</func>
<func>
- <name>port(Node, Id) -> port()</name>
+ <name since="OTP R13B04">port(Node, Id) -> port()</name>
<fsummary>Creates a port id with creation 0.</fsummary>
<type>
<v>Node = node()</v>
@@ -399,12 +399,12 @@
<funcs>
<func>
- <name>rt_collect() -> [lock_counter_data()]</name>
+ <name since="OTP R13B04">rt_collect() -> [lock_counter_data()]</name>
<fsummary>Same as <c>rt_collect(node())</c>.</fsummary>
<desc> <p>Same as <c>rt_collect(node())</c>.</p> </desc>
</func>
<func>
- <name>rt_collect(Node) -> [lock_counter_data()]</name>
+ <name since="OTP R13B04">rt_collect(Node) -> [lock_counter_data()]</name>
<fsummary>Returns a list of raw lock counter data.</fsummary>
<type>
<v>Node = node()</v>
@@ -413,12 +413,12 @@
</func>
<func>
- <name>rt_clear() -> ok</name>
+ <name since="OTP R13B04">rt_clear() -> ok</name>
<fsummary>Same as <c>rt_clear(node())</c>.</fsummary>
<desc> <p>Same as <c>rt_clear(node())</c>.</p> </desc>
</func>
<func>
- <name>rt_clear(Node) -> ok</name>
+ <name since="OTP R13B04">rt_clear(Node) -> ok</name>
<fsummary>Clears the internal counters.</fsummary>
<type>
<v>Node = node()</v>
@@ -427,13 +427,13 @@
</func>
<func>
- <name>rt_mask() -> [category_atom()]</name>
+ <name since="OTP 20.1">rt_mask() -> [category_atom()]</name>
<fsummary>Same as <c>rt_mask(node())</c>.</fsummary>
<desc><p>Same as <c>rt_mask(node())</c>.</p></desc>
</func>
<func>
- <name>rt_mask(Node) -> [category_atom()]</name>
+ <name since="OTP 20.1">rt_mask(Node) -> [category_atom()]</name>
<fsummary>Returns the current lock category mask.</fsummary>
<type>
<v>Node = node()</v>
@@ -447,7 +447,7 @@
</func>
<func>
- <name>rt_mask(Categories) -> ok | {error, copy_save_enabled}</name>
+ <name since="OTP 20.1">rt_mask(Categories) -> ok | {error, copy_save_enabled}</name>
<fsummary>Same as <c>rt_mask(node(), Categories)</c>.</fsummary>
<type>
<v>Categories = [atom()]</v>
@@ -456,7 +456,7 @@
</func>
<func>
- <name>rt_mask(Node, Categories) -> ok | {error, copy_save_enabled}</name>
+ <name since="OTP 20.1">rt_mask(Node, Categories) -> ok | {error, copy_save_enabled}</name>
<fsummary>Changes the lock category mask.</fsummary>
<type>
<v>Node = node()</v>
@@ -489,12 +489,12 @@
</func>
<func>
- <name>rt_opt({Type, bool()}) -> bool()</name>
+ <name since="OTP R13B04">rt_opt({Type, bool()}) -> bool()</name>
<fsummary>Same as <c>rt_opt(node(), {Type, Opt})</c>.</fsummary>
<desc> <p>Same as <c>rt_opt(node(), {Type, Opt})</c>.</p> </desc>
</func>
<func>
- <name>rt_opt(Node, {Type, bool()}) -> bool()</name>
+ <name since="OTP R13B04">rt_opt(Node, {Type, bool()}) -> bool()</name>
<fsummary>Changes the lock counter behavior and returns the previous behaviour.</fsummary>
<type>
<v>Node = node()</v>
diff --git a/lib/tools/doc/src/make.xml b/lib/tools/doc/src/make.xml
index 123fcd4afc..af2404707f 100644
--- a/lib/tools/doc/src/make.xml
+++ b/lib/tools/doc/src/make.xml
@@ -30,7 +30,7 @@
<date></date>
<rev></rev>
</header>
- <module>make</module>
+ <module since="">make</module>
<modulesummary>A Make Utility for Erlang</modulesummary>
<description>
<p>The module <c>make</c> provides a set of functions similar to
@@ -38,8 +38,8 @@
</description>
<funcs>
<func>
- <name>all() -> up_to_date | error</name>
- <name>all(Options) -> up_to_date | error</name>
+ <name since="">all() -> up_to_date | error</name>
+ <name since="">all(Options) -> up_to_date | error</name>
<fsummary>Compile a set of modules.</fsummary>
<type>
<v>Options = [Option]</v>
@@ -87,8 +87,8 @@
</desc>
</func>
<func>
- <name>files(ModFiles) -> up_to_date | error</name>
- <name>files(ModFiles, Options) -> up_to_date | error</name>
+ <name since="">files(ModFiles) -> up_to_date | error</name>
+ <name since="">files(ModFiles, Options) -> up_to_date | error</name>
<fsummary>Compile a set of modules.</fsummary>
<type>
<v>ModFiles = [Module | File]</v>
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index f10953774f..a6781dfdb3 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,21 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.0.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Remove emacs warnings and added more tests.</p>
+ <p>
+ Own Id: OTP-15476</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.0.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/tools/doc/src/tags.xml b/lib/tools/doc/src/tags.xml
index ea0ae5cc4d..90a8b28177 100644
--- a/lib/tools/doc/src/tags.xml
+++ b/lib/tools/doc/src/tags.xml
@@ -32,7 +32,7 @@
<rev>A</rev>
<file>tags.sgml</file>
</header>
- <module>tags</module>
+ <module since="">tags</module>
<modulesummary>Generate Emacs TAGS file from Erlang source files</modulesummary>
<description>
<p>A <c>TAGS</c> file is used by Emacs to find function and variable
@@ -42,14 +42,14 @@
</description>
<funcs>
<func>
- <name>file(File [, Options])</name>
+ <name since="">file(File [, Options])</name>
<fsummary>Create a <c>TAGS</c>file for the file <c>File</c>.</fsummary>
<desc>
<p>Create a <c>TAGS</c> file for the file <c>File</c>.</p>
</desc>
</func>
<func>
- <name>files(FileList [, Options])</name>
+ <name since="">files(FileList [, Options])</name>
<fsummary>Create a TAGS file for the files in the list<c>FileList</c>.</fsummary>
<desc>
<p>Create a TAGS file for the files in the list
@@ -57,7 +57,7 @@
</desc>
</func>
<func>
- <name>dir(Dir [, Options])</name>
+ <name since="">dir(Dir [, Options])</name>
<fsummary>Create a TAGS file for all files in directory<c>Dir</c>.</fsummary>
<desc>
<p>Create a TAGS file for all files in directory
@@ -65,7 +65,7 @@
</desc>
</func>
<func>
- <name>dirs(DirList [, Options])</name>
+ <name since="">dirs(DirList [, Options])</name>
<fsummary>Create a TAGS file for all files in any directory in<c>DirList</c>.</fsummary>
<desc>
<p>Create a TAGS file for all files in any directory in
@@ -73,7 +73,7 @@
</desc>
</func>
<func>
- <name>subdir(Dir [, Options])</name>
+ <name since="">subdir(Dir [, Options])</name>
<fsummary>Descend recursively down the directory <c>Dir</c>and create a <c>TAGS</c>file based on all files found.</fsummary>
<desc>
<p>Descend recursively down the directory <c>Dir</c> and
@@ -81,7 +81,7 @@
</desc>
</func>
<func>
- <name>subdirs(DirList [, Options])</name>
+ <name since="">subdirs(DirList [, Options])</name>
<fsummary>Descend recursively down all the directories in<c>DirList</c>and create a <c>TAGS</c>file based on all files found.</fsummary>
<desc>
<p>Descend recursively down all the directories in
@@ -90,7 +90,7 @@
</desc>
</func>
<func>
- <name>root([Options])</name>
+ <name since="">root([Options])</name>
<fsummary>Create a <c>TAGS</c>file covering all files in the Erlang distribution.</fsummary>
<desc>
<p>Create a <c>TAGS</c> file covering all files in
diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml
index 6f833246ad..ab3641a52f 100644
--- a/lib/tools/doc/src/xref.xml
+++ b/lib/tools/doc/src/xref.xml
@@ -32,7 +32,7 @@
<rev>PA1</rev>
<file>xref.sgml</file>
</header>
- <module>xref</module>
+ <module since="">xref</module>
<modulesummary>A Cross Reference Tool for analyzing dependencies between functions, modules, applications and releases.</modulesummary>
<description>
<p>Xref is a cross reference tool that can be used for finding
@@ -729,7 +729,7 @@ xref() = atom() | pid() </pre>
</description>
<funcs>
<func>
- <name>add_application(Xref, Directory [, Options]) -> {ok, application()} | Error</name>
+ <name since="">add_application(Xref, Directory [, Options]) -> {ok, application()} | Error</name>
<fsummary>Add the modules of an application.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -761,7 +761,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>add_directory(Xref, Directory [, Options]) -> {ok, Modules} | Error</name>
+ <name since="">add_directory(Xref, Directory [, Options]) -> {ok, Modules} | Error</name>
<fsummary>Add the modules in a directory.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -791,7 +791,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>add_module(Xref, File [, Options]) -> {ok, module()} | Error</name>
+ <name since="">add_module(Xref, File [, Options]) -> {ok, module()} | Error</name>
<fsummary>Add a module.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -814,7 +814,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>add_release(Xref, Directory [, Options]) -> {ok, release()} | Error</name>
+ <name since="">add_release(Xref, Directory [, Options]) -> {ok, release()} | Error</name>
<fsummary>Add the modules of a release.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -849,7 +849,7 @@ xref() = atom() | pid() </pre>
</desc>
</func>
<func>
- <name>analyze(Xref, Analysis [, Options]) -> {ok, Answer} | Error</name>
+ <name since="">analyze(Xref, Analysis [, Options]) -> {ok, Answer} | Error</name>
<fsummary>Evaluate a predefined analysis.</fsummary>
<type>
<v>Analysis = undefined_function_calls | undefined_functions | locals_not_used | exports_not_used | deprecated_function_calls | {deprecated_function_calls, DeprFlag} | deprecated_functions | {deprecated_functions, DeprFlag} | {call, FuncSpec} | {use, FuncSpec} | {module_call, ModSpec} | {module_use, ModSpec} | {application_call, AppSpec} | {application_use, AppSpec} | {release_call, RelSpec} | {release_use, RelSpec}</v>
@@ -939,7 +939,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>d(Directory) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
+ <name since="">d(Directory) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
<fsummary>Check the modules in a directory using the code path.</fsummary>
<type>
<v>Directory = directory()</v>
@@ -979,8 +979,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>forget(Xref) -> ok</name>
- <name>forget(Xref, Variables) -> ok | Error</name>
+ <name since="">forget(Xref) -> ok</name>
+ <name since="">forget(Xref, Variables) -> ok | Error</name>
<fsummary>Remove user variables and their values.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -994,7 +994,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>format_error(Error) -> Chars</name>
+ <name since="">format_error(Error) -> Chars</name>
<fsummary>Return an English description of an Xref error reply.</fsummary>
<type>
<v>Error = {error, module(), term()}</v>
@@ -1008,8 +1008,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>get_default(Xref) -> [{Option, Value}]</name>
- <name>get_default(Xref, Option) -> {ok, Value} | Error</name>
+ <name since="">get_default(Xref) -> [{Option, Value}]</name>
+ <name since="">get_default(Xref, Option) -> {ok, Value} | Error</name>
<fsummary>Return the default values of options.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1023,7 +1023,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>get_library_path(Xref) -> {ok, LibraryPath}</name>
+ <name since="">get_library_path(Xref) -> {ok, LibraryPath}</name>
<fsummary>Return the library path.</fsummary>
<type>
<v>LibraryPath = library_path()</v>
@@ -1034,9 +1034,9 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>info(Xref) -> [Info]</name>
- <name>info(Xref, Category) -> [{Item, [Info]}]</name>
- <name>info(Xref, Category, Items) -> [{Item, [Info]}]</name>
+ <name since="">info(Xref) -> [Info]</name>
+ <name since="">info(Xref, Category) -> [{Item, [Info]}]</name>
+ <name since="">info(Xref, Category, Items) -> [{Item, [Info]}]</name>
<fsummary>Return information about an Xref server.</fsummary>
<type>
<v>Application = [] | [application()]</v>
@@ -1220,8 +1220,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>m(Module) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
- <name>m(File) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
+ <name since="">m(Module) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
+ <name since="">m(File) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name>
<fsummary>Check a module using the code path.</fsummary>
<type>
<v>DebugInfoResult = {deprecated, [funcall()]} | {undefined, [funcall()]} | {unused, [mfa()]}</v>
@@ -1263,7 +1263,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>q(Xref, Query [, Options]) -> {ok, Answer} | Error</name>
+ <name since="">q(Xref, Query [, Options]) -> {ok, Answer} | Error</name>
<fsummary>Evaluate a query.</fsummary>
<type>
<v>Answer = false | [constant()] | [Call] | [Component] | int() | [DefineAt] | [CallAt] | [AllLines]</v>
@@ -1322,7 +1322,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>remove_application(Xref, Applications) -> ok | Error</name>
+ <name since="">remove_application(Xref, Applications) -> ok | Error</name>
<fsummary>Remove applications and their modules.</fsummary>
<type>
<v>Applications = application() | [application()]</v>
@@ -1335,7 +1335,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>remove_module(Xref, Modules) -> ok | Error</name>
+ <name since="">remove_module(Xref, Modules) -> ok | Error</name>
<fsummary>Remove analyzed modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1348,7 +1348,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>remove_release(Xref, Releases) -> ok | Error</name>
+ <name since="">remove_release(Xref, Releases) -> ok | Error</name>
<fsummary>Remove releases and their applications and modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1363,7 +1363,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>replace_application(Xref, Application, Directory [, Options]) -> {ok, application()} | Error</name>
+ <name since="">replace_application(Xref, Application, Directory [, Options]) -> {ok, application()} | Error</name>
<fsummary>Replace an application's modules.</fsummary>
<type>
<v>Application = application()</v>
@@ -1384,7 +1384,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>replace_module(Xref, Module, File [, Options]) -> {ok, module()} | Error</name>
+ <name since="">replace_module(Xref, Module, File [, Options]) -> {ok, module()} | Error</name>
<fsummary>Replace an analyzed module.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1409,8 +1409,8 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>set_default(Xref, Option, Value) -> {ok, OldValue} | Error</name>
- <name>set_default(Xref, OptionValues) -> ok | Error</name>
+ <name since="">set_default(Xref, Option, Value) -> {ok, OldValue} | Error</name>
+ <name since="">set_default(Xref, OptionValues) -> ok | Error</name>
<fsummary>Set the default values of options.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1435,7 +1435,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>set_library_path(Xref, LibraryPath [, Options]) -> ok | Error</name>
+ <name since="">set_library_path(Xref, LibraryPath [, Options]) -> ok | Error</name>
<fsummary>Set the library path and finds the library modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1469,7 +1469,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>start(NameOrOptions) -> Return</name>
+ <name since="">start(NameOrOptions) -> Return</name>
<fsummary>Create an Xref server.</fsummary>
<type>
<v>NameOrOptions = Name | Options</v>
@@ -1487,7 +1487,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>start(Name, Options) -> Return</name>
+ <name since="">start(Name, Options) -> Return</name>
<fsummary>Create an Xref server.</fsummary>
<type>
<v>Name = atom()</v>
@@ -1504,7 +1504,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>stop(Xref)</name>
+ <name since="">stop(Xref)</name>
<fsummary>Delete an Xref server.</fsummary>
<type>
<v>Xref = xref()</v>
@@ -1514,7 +1514,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>update(Xref [, Options]) -> {ok, Modules} | Error</name>
+ <name since="">update(Xref [, Options]) -> {ok, Modules} | Error</name>
<fsummary>Replace newly compiled analyzed modules.</fsummary>
<type>
<v>Error = {error, module(), Reason}</v>
@@ -1534,7 +1534,7 @@ Evaluates a predefined analysis.
</desc>
</func>
<func>
- <name>variables(Xref [, Options]) -> {ok, [VariableInfo]}</name>
+ <name since="">variables(Xref [, Options]) -> {ok, [VariableInfo]}</name>
<fsummary>Return the names of variables.</fsummary>
<type>
<v>Options = [Option] | Option</v>
diff --git a/lib/tools/priv/styles.css b/lib/tools/priv/styles.css
index e10e94e3ad..84f00be9fd 100644
--- a/lib/tools/priv/styles.css
+++ b/lib/tools/priv/styles.css
@@ -53,21 +53,25 @@ table thead {
display: none;
}
table td.line,
+table td.line a,
table td.hits {
width: 20px;
background: #eaeaea;
text-align: center;
+ text-decoration: none;
font-size: 11px;
padding: 0 10px;
color: #949494;
}
table td.hits {
width: 10px;
+ text-align: right;
padding: 2px 5px;
color: rgba(0, 0, 0, 0.6);
background-color: #f0f0f0;
}
tr.miss td.line,
+tr.miss td.line a,
tr.miss td.hits {
background-color: #ffdce0;
border-color: #fdaeb7;
@@ -76,6 +80,7 @@ tr.miss td {
background-color: #ffeef0;
}
tr.hit td.line,
+tr.hit td.line a,
tr.hit td.hits {
background-color: #cdffd8;
border-color: #bef5cb;
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index d7269e3f27..8d4561ca9e 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -2563,11 +2563,13 @@ table_row(Line, L) ->
table_data(Line, L, N) ->
LineNoNL = Line -- "\n",
["<td class=\"line\" id=\"L",integer_to_list(L),"\">",
+ "<a href=\"#L",integer_to_list(L),"\">",
integer_to_list(L),
- "</td>\n",
+ "</a></td>\n",
"<td class=\"hits\">",maybe_integer_to_list(N),"</td>\n",
"<td class=\"source\"><code>",LineNoNL,"</code></td>\n</tr>\n"].
+maybe_integer_to_list(0) -> "<pre style=\"display: inline;\">:-(</pre>";
maybe_integer_to_list(N) when is_integer(N) -> integer_to_list(N);
maybe_integer_to_list(_) -> "".
diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl
index 535ddbcd04..86e3d3a8b8 100644
--- a/lib/tools/src/eprof.erl
+++ b/lib/tools/src/eprof.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
+%% Copyright Ericsson AB 1996-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.
@@ -26,11 +26,11 @@
-export([start/0,
stop/0,
- dump/0,
+ dump/0, dump_data/0,
start_profiling/1, start_profiling/2, start_profiling/3,
profile/1, profile/2, profile/3, profile/4, profile/5,
stop_profiling/0,
- analyze/0, analyze/1, analyze/2,
+ analyze/0, analyze/1, analyze/2, analyze/4,
log/1]).
%% Internal exports
@@ -117,6 +117,9 @@ profile(Rootset, M, F, A, Pattern, Options) ->
dump() ->
gen_server:call(?MODULE, dump, infinity).
+dump_data() ->
+ gen_server:call(?MODULE, dump_data, infinity).
+
log(File) ->
gen_server:call(?MODULE, {logfile, File}, infinity).
@@ -151,22 +154,18 @@ init([]) ->
%% analyze
-handle_call({analyze, _, _}, _, #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0} = Bpd } = S) when is_record(Bpd, bpd) ->
+handle_call(
+ {analyze, _, _}, _,
+ #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0 } } = S) ->
{reply, nothing_to_analyze, S};
-handle_call({analyze, procs, Opts}, _, #state{ bpd = #bpd{ p = Ps, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) ->
- lists:foreach(fun
- ({Pid, Mfas}) ->
- {Pn, Pus} = sum_bp_total_n_us(Mfas),
- format(Fd, "~n****** Process ~w -- ~s % of profiled time *** ~n", [Pid, s("~.2f", [100.0*divide(Pus,Tus)])]),
- print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts),
- ok
- end, gb_trees:to_list(Ps)),
- {reply, ok, S};
+handle_call({analyze, procs, Opts}, _, #state{ bpd = Bpd, fd = Fd } = S)
+ when is_record(Bpd, bpd) ->
+ {reply, analyze(Fd, procs, Opts, Bpd), S};
-handle_call({analyze, total, Opts}, _, #state{ bpd = #bpd{ mfa = Mfas, n = Tn, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) ->
- print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts),
- {reply, ok, S};
+handle_call({analyze, total, Opts}, _, #state{ bpd = Bpd, fd = Fd } = S)
+ when is_record(Bpd, bpd) ->
+ {reply, analyze(Fd, total, Opts, Bpd), S};
handle_call({analyze, Type, _Opts}, _, S) ->
{reply, {error, {undefined, Type}}, S};
@@ -260,6 +259,10 @@ handle_call({logfile, File}, _From, #state{ fd = OldFd } = S) ->
handle_call(dump, _From, #state{ bpd = Bpd } = S) when is_record(Bpd, bpd) ->
{reply, gb_trees:to_list(Bpd#bpd.p), S};
+handle_call(dump_data, _, #state{ bpd = #bpd{} = Bpd } = S)
+ when is_record(Bpd, bpd) ->
+ {reply, Bpd, S};
+
handle_call(stop, _FromTag, S) ->
{stop, normal, stopped, S}.
@@ -438,6 +441,23 @@ collect_bpdfp(Mfa, Tree, Data) ->
{PTno + Ni, PTuso + Time, Ti1}
end, {0,0, Tree}, Data).
+
+
+analyze(Fd, procs, Opts, #bpd{ p = Ps, us = Tus }) ->
+ lists:foreach(
+ fun
+ ({Pid, Mfas}) ->
+ {Pn, Pus} = sum_bp_total_n_us(Mfas),
+ format(
+ Fd,
+ "~n****** Process ~w -- ~s % of profiled time *** ~n",
+ [Pid, s("~.2f", [100.0*divide(Pus, Tus)])]),
+ print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts),
+ ok
+ end, gb_trees:to_list(Ps));
+analyze(Fd, total, Opts, #bpd{ mfa = Mfas, n = Tn, us = Tus } ) ->
+ print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts).
+
%% manipulators
sort_mfa(Bpfs, mfa) when is_list(Bpfs) ->
lists:sort(fun
diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src
index f8c6aa22cb..f0e0fc4bec 100644
--- a/lib/tools/src/tools.app.src
+++ b/lib/tools/src/tools.app.src
@@ -21,11 +21,13 @@
[{description, "DEVTOOLS CXC 138 16"},
{vsn, "%VSN%"},
{modules, [cover,
+ cprof,
eprof,
fprof,
instrument,
lcnt,
make,
+ tags,
xref,
xref_base,
xref_compiler,
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 1bebb1c421..bb8305e9f1 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.0.1
+TOOLS_VSN = 3.0.2
diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl
index 70a3530526..f13d5873a0 100644
--- a/lib/wx/api_gen/wx_gen_cpp.erl
+++ b/lib/wx/api_gen/wx_gen_cpp.erl
@@ -995,8 +995,13 @@ build_ret_types(Type,Ps) ->
end,
lists:foldl(Calc, Free, Ps).
-build_ret(Name,_,#type{base={class,Class},single=true}) ->
- w(" rt.addRef(getRef((void *)~s,memenv), \"~s\");~n",[Name,Class]);
+build_ret(Name,_D,#type{base={class,Class},single=true}=_T) ->
+ case Class of
+ "wxGraphicsContext" ->
+ w(" rt.addRef(getRef((void *)~s,memenv,8), \"~s\");~n",[Name,Class]);
+ _ ->
+ w(" rt.addRef(getRef((void *)~s,memenv), \"~s\");~n",[Name,Class])
+ end;
build_ret(Name,_,#type{name="wxTreeItemId",single=true}) ->
w(" rt.add((wxUIntPtr *) ~s.m_pItem);~n",[Name]);
build_ret(Name,_,#type{name="wxTreeItemIdValue",single=true}) ->
diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp
index a7bac4cf9d..74961b2e5e 100644
--- a/lib/wx/c_src/gen/wxe_funcs.cpp
+++ b/lib/wx/c_src/gen/wxe_funcs.cpp
@@ -6147,18 +6147,18 @@ case wxGraphicsObject_IsNull: { // wxGraphicsObject::IsNull
case wxGraphicsContext_Create_1_1: { // wxGraphicsContext::Create
wxWindowDC * dc = (wxWindowDC *) getPtr(bp,memenv); bp += 4;
wxGraphicsContext * Result = (wxGraphicsContext*)wxGraphicsContext::Create(*dc);
- rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext");
+ rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext");
break;
}
case wxGraphicsContext_Create_1_0: { // wxGraphicsContext::Create
wxWindow *window = (wxWindow *) getPtr(bp,memenv); bp += 4;
wxGraphicsContext * Result = (wxGraphicsContext*)wxGraphicsContext::Create(window);
- rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext");
+ rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext");
break;
}
case wxGraphicsContext_Create_0: { // wxGraphicsContext::Create
wxGraphicsContext * Result = (wxGraphicsContext*)wxGraphicsContext::Create();
- rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext");
+ rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext");
break;
}
case wxGraphicsContext_CreatePen: { // wxGraphicsContext::CreatePen
@@ -6999,7 +6999,7 @@ case wxGraphicsRenderer_CreateContext_1_1: { // wxGraphicsRenderer::CreateContex
wxWindowDC * dc = (wxWindowDC *) getPtr(bp,memenv); bp += 4;
if(!This) throw wxe_badarg(0);
wxGraphicsContext * Result = (wxGraphicsContext*)This->CreateContext(*dc);
- rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext");
+ rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext");
break;
}
case wxGraphicsRenderer_CreateContext_1_0: { // wxGraphicsRenderer::CreateContext
@@ -7007,7 +7007,7 @@ case wxGraphicsRenderer_CreateContext_1_0: { // wxGraphicsRenderer::CreateContex
wxWindow *window = (wxWindow *) getPtr(bp,memenv); bp += 4;
if(!This) throw wxe_badarg(0);
wxGraphicsContext * Result = (wxGraphicsContext*)This->CreateContext(window);
- rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext");
+ rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext");
break;
}
diff --git a/lib/wx/c_src/wxe_helpers.cpp b/lib/wx/c_src/wxe_helpers.cpp
index d1f607d2af..47955494f9 100644
--- a/lib/wx/c_src/wxe_helpers.cpp
+++ b/lib/wx/c_src/wxe_helpers.cpp
@@ -101,7 +101,7 @@ wxeCommand * wxeFifo::Peek(unsigned int *i)
}
-void wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd)
+int wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd)
{
unsigned int pos;
wxeCommand *curr;
@@ -144,6 +144,7 @@ void wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd)
} else { // No-op only PING currently
curr->buffer = NULL;
}
+ return m_n;
}
void wxeFifo::Append(wxeCommand *orig)
diff --git a/lib/wx/c_src/wxe_helpers.h b/lib/wx/c_src/wxe_helpers.h
index 70ffccdc13..a6c00e5aca 100644
--- a/lib/wx/c_src/wxe_helpers.h
+++ b/lib/wx/c_src/wxe_helpers.h
@@ -63,7 +63,7 @@ class wxeFifo {
wxeFifo(unsigned int size);
virtual ~wxeFifo();
- void Add(int fc, char * cbuf,int buflen, wxe_data *);
+ int Add(int fc, char * cbuf,int buflen, wxe_data *);
void Append(wxeCommand *Other);
wxeCommand * Get();
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
index 1510866f09..bd22502d00 100644
--- a/lib/wx/c_src/wxe_impl.cpp
+++ b/lib/wx/c_src/wxe_impl.cpp
@@ -70,7 +70,7 @@ void push_command(int op,char * buf,int len, wxe_data *sd)
/* fprintf(stderr, "Op %d %d [%ld] %d\r\n", op, (int) driver_caller(sd->port_handle),
wxe_batch->size(), wxe_batch_caller),fflush(stderr); */
erl_drv_mutex_lock(wxe_batch_locker_m);
- wxe_queue->Add(op, buf, len, sd);
+ int n = wxe_queue->Add(op, buf, len, sd);
if(wxe_needs_signal) {
// wx-thread is waiting on batch end in cond_wait
@@ -79,7 +79,7 @@ void push_command(int op,char * buf,int len, wxe_data *sd)
} else {
// wx-thread is waiting gui-events
erl_drv_mutex_unlock(wxe_batch_locker_m);
- wxWakeUpIdle();
+ if(n < 2) wxWakeUpIdle();
}
}
@@ -591,7 +591,7 @@ int WxeApp::newPtr(void * ptr, int type, wxeMemEnv *memenv) {
return ref;
}
-int WxeApp::getRef(void * ptr, wxeMemEnv *memenv) {
+int WxeApp::getRef(void * ptr, wxeMemEnv *memenv, int type) {
if(!ptr) return 0; // NULL and zero is the same
ptrMap::iterator it = ptr2ref.find(ptr);
if(it != ptr2ref.end()) {
@@ -618,7 +618,7 @@ int WxeApp::getRef(void * ptr, wxeMemEnv *memenv) {
}
memenv->ref2ptr[ref] = ptr;
- ptr2ref[ptr] = new wxeRefData(ref, 0, false, memenv);
+ ptr2ref[ptr] = new wxeRefData(ref, type, false, memenv);
return ref;
}
diff --git a/lib/wx/c_src/wxe_impl.h b/lib/wx/c_src/wxe_impl.h
index 140a2bd36a..69cc81c429 100644
--- a/lib/wx/c_src/wxe_impl.h
+++ b/lib/wx/c_src/wxe_impl.h
@@ -84,7 +84,7 @@ public:
wxeMemEnv * getMemEnv(ErlDrvTermData port);
int newPtr(void * ptr, int type, wxeMemEnv *memenv);
- int getRef(void * ptr, wxeMemEnv *memenv);
+ int getRef(void * ptr, wxeMemEnv *memenv, int type = 0);
void * getPtr(char * bp, wxeMemEnv *memenv);
void clearPtr(void *ptr);
wxeRefData * getRefData(void *ptr);
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index a925cf30d4..1061e73138 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.8.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed delayed delete bug which caused wx applications to
+ crash on Mojave.</p>
+ <p>
+ Own Id: OTP-15426 Aux Id: ERL-755 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.8.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index e539ad36f6..d241a7a1b4 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.8.5
+WX_VSN = 1.8.6
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index a97036127e..7f6874e36b 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the Xmerl application.</p>
+<section><title>Xmerl 1.3.19</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The charset detection parsing crash in some cases when
+ the XML directive is not syntactic correct.</p>
+ <p>
+ Own Id: OTP-15492 Aux Id: ERIERL-283 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.18</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -62,6 +77,21 @@
</section>
+<section><title>Xmerl 1.3.16.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The charset detection parsing crash in some cases when
+ the XML directive is not syntactic correct.</p>
+ <p>
+ Own Id: OTP-15492 Aux Id: ERIERL-283 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.16</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1412,4 +1442,3 @@
</section>
</section>
</chapter>
-
diff --git a/lib/xmerl/doc/src/xmerl_sax_parser.xml b/lib/xmerl/doc/src/xmerl_sax_parser.xml
index 8ea197e209..2390779028 100644
--- a/lib/xmerl/doc/src/xmerl_sax_parser.xml
+++ b/lib/xmerl/doc/src/xmerl_sax_parser.xml
@@ -31,7 +31,7 @@
<rev></rev>
</header>
- <module>xmerl_sax_parser</module>
+ <module since="">xmerl_sax_parser</module>
<modulesummary>XML SAX parser API</modulesummary>
<description>
@@ -325,7 +325,7 @@
<funcs>
<func>
- <name>file(Filename, Options) -> Result</name>
+ <name since="">file(Filename, Options) -> Result</name>
<fsummary>Parse file containing an XML document.</fsummary>
<type>
<v>Filename = string()</v>
@@ -347,7 +347,7 @@
</func>
<func>
- <name>stream(Xml, Options) -> Result</name>
+ <name since="">stream(Xml, Options) -> Result</name>
<fsummary>Parse a stream containing an XML document.</fsummary>
<type>
<v>Xml = unicode_binary() | latin1_binary() | [unicode_char()]</v>
@@ -381,7 +381,7 @@
<funcs>
<func>
- <name>ContinuationFun(State) -> {NewBytes, NewState}</name>
+ <name since="">ContinuationFun(State) -> {NewBytes, NewState}</name>
<fsummary>Continuation call back function.</fsummary>
<type>
<v>State = NewState = term()</v>
@@ -402,7 +402,7 @@
</func>
<func>
- <name>EventFun(Event, Location, State) -> NewState</name>
+ <name since="">EventFun(Event, Location, State) -> NewState</name>
<fsummary>Event call back function.</fsummary>
<type>
<v>Event = event()</v>
diff --git a/lib/xmerl/src/xmerl_sax_parser.erl b/lib/xmerl/src/xmerl_sax_parser.erl
index e383c4c349..fe836fd8cd 100644
--- a/lib/xmerl/src/xmerl_sax_parser.erl
+++ b/lib/xmerl/src/xmerl_sax_parser.erl
@@ -1,8 +1,8 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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
@@ -14,13 +14,13 @@
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-%%
+%%
%% %CopyrightEnd%
%%----------------------------------------------------------------------
%% File : xmerl_sax_parser.erl
%% Description : XML SAX parse API module.
%%
-%% Created : 4 Jun 2008
+%% Created : 4 Jun 2008
%%----------------------------------------------------------------------
-module(xmerl_sax_parser).
@@ -72,9 +72,9 @@ file(Name,Options) ->
CL = filename:absname(Dir),
File = filename:basename(Name),
ContinuationFun = fun default_continuation_cb/1,
- Res = stream(<<>>,
+ Res = stream(<<>>,
[{continuation_fun, ContinuationFun},
- {continuation_state, FD},
+ {continuation_state, FD},
{current_location, CL},
{entity, File}
|Options],
@@ -101,39 +101,39 @@ stream(Xml, Options, InputType) when is_list(Xml), is_list(Options) ->
State = parse_options(Options, initial_state()),
case State#xmerl_sax_parser_state.file_type of
dtd ->
- xmerl_sax_parser_list:parse_dtd(Xml,
+ xmerl_sax_parser_list:parse_dtd(Xml,
State#xmerl_sax_parser_state{encoding = list,
input_type = InputType});
normal ->
- xmerl_sax_parser_list:parse(Xml,
+ xmerl_sax_parser_list:parse(Xml,
State#xmerl_sax_parser_state{encoding = list,
input_type = InputType})
end;
stream(Xml, Options, InputType) when is_binary(Xml), is_list(Options) ->
- case parse_options(Options, initial_state()) of
+ case parse_options(Options, initial_state()) of
{error, Reason} -> {error, Reason};
State ->
- ParseFunction =
+ ParseFunction =
case State#xmerl_sax_parser_state.file_type of
dtd ->
parse_dtd;
normal ->
parse
end,
- try
+ try
{Xml1, State1} = detect_charset(Xml, State),
parse_binary(Xml1,
State1#xmerl_sax_parser_state{input_type = InputType},
ParseFunction)
catch
throw:{fatal_error, {State2, Reason}} ->
- {fatal_error,
+ {fatal_error,
{
State2#xmerl_sax_parser_state.current_location,
- State2#xmerl_sax_parser_state.entity,
+ State2#xmerl_sax_parser_state.entity,
1
},
- Reason, [],
+ Reason, [],
State2#xmerl_sax_parser_state.event_state}
end
end.
@@ -157,7 +157,7 @@ parse_binary(Xml, #xmerl_sax_parser_state{encoding={utf16,big}}=State, F) ->
xmerl_sax_parser_utf16be:F(Xml, State);
parse_binary(Xml, #xmerl_sax_parser_state{encoding=latin1}=State, F) ->
xmerl_sax_parser_latin1:F(Xml, State);
-parse_binary(_, #xmerl_sax_parser_state{encoding=Enc}, State) ->
+parse_binary(_, #xmerl_sax_parser_state{encoding=Enc}, State) ->
?fatal_error(State, lists:flatten(io_lib:format("Charcter set ~p not supported", [Enc]))).
%%----------------------------------------------------------------------
@@ -177,9 +177,9 @@ initial_state() ->
%%----------------------------------------------------------------------
%% Function: parse_options(Options, State)
%% Input: Options = [Option]
-%% Option = {event_state, term()} | {event_fun, fun()} |
+%% Option = {event_state, term()} | {event_fun, fun()} |
%% {continuation_state, term()} | {continuation_fun, fun()} |
-%% {encoding, Encoding} | {file_type, FT}
+%% {encoding, Encoding} | {file_type, FT}
%% FT = normal | dtd
%% Encoding = utf8 | utf16le | utf16be | list | iso8859
%% State = #xmerl_sax_parser_state{}
@@ -200,7 +200,7 @@ parse_options([{file_type, FT} |Options], State) when FT==normal; FT==dtd ->
parse_options(Options, State#xmerl_sax_parser_state{file_type = FT});
parse_options([{encoding, E} |Options], State) ->
case check_encoding_option(E) of
- {error, Reason} ->
+ {error, Reason} ->
{error, Reason};
Enc ->
parse_options(Options, State#xmerl_sax_parser_state{encoding = Enc})
@@ -231,7 +231,7 @@ check_encoding_option(E) ->
%% Description: Detects which character set is used in a binary stream.
%%----------------------------------------------------------------------
detect_charset(<<>>, #xmerl_sax_parser_state{continuation_fun = undefined} = State) ->
- ?fatal_error(State, "Can't detect character encoding due to lack of indata");
+ ?fatal_error(State, "Can't detect character encoding due to lack of indata");
detect_charset(<<>>, State) ->
cf(<<>>, State, fun detect_charset/2);
detect_charset(Bytes, State) ->
@@ -269,22 +269,14 @@ detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D>> = Xml, State) ->
cf(Xml, State, fun detect_charset_1/2);
detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml2/binary>>, State) ->
{Xml3, State1} = read_until_end_of_xml_directive(Xml2, State),
- case parse_xml_directive(Xml3) of
- {error, Reason} ->
- ?fatal_error(State, Reason);
- AttrList ->
- case lists:keysearch("encoding", 1, AttrList) of
- {value, {_, E}} ->
- case convert_encoding(E) of
- {error, Reason} ->
- ?fatal_error(State, Reason);
- Enc ->
- {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>,
- State1#xmerl_sax_parser_state{encoding=Enc}}
- end;
- _ ->
- {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, State1}
- end
+ AttrList = parse_xml_directive(Xml3, State),
+ case lists:keysearch("encoding", 1, AttrList) of
+ {value, {_, E}} ->
+ Enc = convert_encoding(E, State),
+ {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>,
+ State1#xmerl_sax_parser_state{encoding=Enc}};
+ _ ->
+ {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, State1}
end;
detect_charset_1(Xml, State) ->
{Xml, State}.
@@ -295,7 +287,7 @@ detect_charset_1(Xml, State) ->
%% Output: utf8 | iso8859
%% Description: Converting 7,8 bit and utf8 encoding strings to internal format.
%%----------------------------------------------------------------------
-convert_encoding(Enc) -> %% Just for 7,8 bit + utf8
+convert_encoding(Enc, State) -> %% Just for 7,8 bit + utf8
case string:to_lower(Enc) of
"utf-8" -> utf8;
"us-ascii" -> utf8;
@@ -309,19 +301,19 @@ convert_encoding(Enc) -> %% Just for 7,8 bit + utf8
"iso-8859-7" -> latin1;
"iso-8859-8" -> latin1;
"iso-8859-9" -> latin1;
- _ -> {error, "Unknown encoding: " ++ Enc}
+ _ -> ?fatal_error(State, "Unknown encoding: " ++ Enc)
end.
%%----------------------------------------------------------------------
%% Function: parse_xml_directive(Xml)
%% Input: Xml = binary()
%% Acc = list()
-%% Output:
+%% Output:
%% Description: Parsing the xml declaration from the input stream.
%%----------------------------------------------------------------------
-parse_xml_directive(<<C, Rest/binary>>) when ?is_whitespace(C) ->
- parse_xml_directive_1(Rest, []).
-
+parse_xml_directive(<<C, Rest/binary>>, State) when ?is_whitespace(C) ->
+ parse_xml_directive_1(Rest, [], State).
+
%%----------------------------------------------------------------------
%% Function: parse_xml_directive_1(Xml, Acc) -> [{Name, Value}]
%% Input: Xml = binary()
@@ -331,20 +323,20 @@ parse_xml_directive(<<C, Rest/binary>>) when ?is_whitespace(C) ->
%% Output: see above
%% Description: Parsing the xml declaration from the input stream.
%%----------------------------------------------------------------------
-parse_xml_directive_1(<<C, Rest/binary>>, Acc) when ?is_whitespace(C) ->
- parse_xml_directive_1(Rest, Acc);
-parse_xml_directive_1(<<"?>", _/binary>>, Acc) ->
+parse_xml_directive_1(<<C, Rest/binary>>, Acc, State) when ?is_whitespace(C) ->
+ parse_xml_directive_1(Rest, Acc, State);
+parse_xml_directive_1(<<"?>", _/binary>>, Acc, _State) ->
Acc;
-parse_xml_directive_1(<<C, Rest/binary>>, Acc) when 97 =< C, C =< 122 ->
+parse_xml_directive_1(<<C, Rest/binary>>, Acc, State) when 97 =< C, C =< 122 ->
{Name, Rest1} = parse_name(Rest, [C]),
- Rest2 = parse_eq(Rest1),
- {Value, Rest3} = parse_value(Rest2),
- parse_xml_directive_1(Rest3, [{Name, Value} |Acc]);
-parse_xml_directive_1(_, _) ->
- {error, "Unknown attribute in xml directive"}.
+ Rest2 = parse_eq(Rest1, State),
+ {Value, Rest3} = parse_value(Rest2, State),
+ parse_xml_directive_1(Rest3, [{Name, Value} |Acc], State);
+parse_xml_directive_1(_, _, State) ->
+ ?fatal_error(State, "Unknown attribute in xml directive").
%%----------------------------------------------------------------------
-%% Function: parse_xml_directive_1(Xml, Acc) -> Name
+%% Function: parse_name(Xml, Acc) -> Name
%% Input: Xml = binary()
%% Acc = string()
%% Output: Name = string()
@@ -361,10 +353,12 @@ parse_name(Rest, Acc) ->
%% Output: Rest = binary()
%% Description: Reads an '=' from the stream.
%%----------------------------------------------------------------------
-parse_eq(<<C, Rest/binary>>) when ?is_whitespace(C) ->
- parse_eq(Rest);
-parse_eq(<<"=", Rest/binary>>) ->
- Rest.
+parse_eq(<<C, Rest/binary>>, State) when ?is_whitespace(C) ->
+ parse_eq(Rest, State);
+parse_eq(<<"=", Rest/binary>>, _State) ->
+ Rest;
+parse_eq(_, State) ->
+ ?fatal_error(State, "expecting = or whitespace").
%%----------------------------------------------------------------------
%% Function: parse_value(Xml) -> {Value, Rest}
@@ -373,10 +367,12 @@ parse_eq(<<"=", Rest/binary>>) ->
%% Rest = binary()
%% Description: Parsing an attribute value from the stream.
%%----------------------------------------------------------------------
-parse_value(<<C, Rest/binary>>) when ?is_whitespace(C) ->
- parse_value(Rest);
-parse_value(<<C, Rest/binary>>) when C == $'; C == $" ->
- parse_value_1(Rest, C, []).
+parse_value(<<C, Rest/binary>>, State) when ?is_whitespace(C) ->
+ parse_value(Rest, State);
+parse_value(<<C, Rest/binary>>, _State) when C == $'; C == $" ->
+ parse_value_1(Rest, C, []);
+parse_value(_, State) ->
+ ?fatal_error(State, "\', \" or whitespace expected").
%%----------------------------------------------------------------------
%% Function: parse_value_1(Xml, Stop, Acc) -> {Value, Rest}
@@ -431,7 +427,7 @@ read_until_end_of_xml_directive(Rest, State) ->
nomatch ->
case cf(Rest, State) of
{<<>>, _} ->
- ?fatal_error(State, "Can't detect character encoding due to lack of indata");
+ ?fatal_error(State, "Can't detect character encoding due to lack of indata");
{NewBytes, NewState} ->
read_until_end_of_xml_directive(NewBytes, NewState)
end;
@@ -450,9 +446,9 @@ read_until_end_of_xml_directive(Rest, State) ->
%% input stream and calls the fun in NextCall.
%%----------------------------------------------------------------------
cf(_Rest, #xmerl_sax_parser_state{continuation_fun = undefined} = State) ->
- ?fatal_error(State, "Continuation function undefined");
+ ?fatal_error(State, "Continuation function undefined");
cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State) ->
- Result =
+ Result =
try
CFun(CState)
catch
@@ -463,9 +459,9 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C
end,
case Result of
{<<>>, _} ->
- ?fatal_error(State, "Can't detect character encoding due to lack of indata");
+ ?fatal_error(State, "Can't detect character encoding due to lack of indata");
{NewBytes, NewContState} ->
- {<<Rest/binary, NewBytes/binary>>,
+ {<<Rest/binary, NewBytes/binary>>,
State#xmerl_sax_parser_state{continuation_state = NewContState}}
end.
@@ -479,10 +475,10 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C
%% input stream and calls the fun in NextCall.
%%----------------------------------------------------------------------
cf(_Rest, #xmerl_sax_parser_state{continuation_fun = undefined} = State, _) ->
- ?fatal_error(State, "Continuation function undefined");
-cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State,
+ ?fatal_error(State, "Continuation function undefined");
+cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State,
NextCall) ->
- Result =
+ Result =
try
CFun(CState)
catch
@@ -493,8 +489,8 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C
end,
case Result of
{<<>>, _} ->
- ?fatal_error(State, "Can't detect character encoding due to lack of indata");
+ ?fatal_error(State, "Can't detect character encoding due to lack of indata");
{NewBytes, NewContState} ->
- NextCall(<<Rest/binary, NewBytes/binary>>,
+ NextCall(<<Rest/binary, NewBytes/binary>>,
State#xmerl_sax_parser_state{continuation_state = NewContState})
end.
diff --git a/lib/xmerl/test/xmerl_xsd_lib.erl b/lib/xmerl/test/xmerl_xsd_lib.erl
index 6006cf500f..39300cd6cb 100644
--- a/lib/xmerl/test/xmerl_xsd_lib.erl
+++ b/lib/xmerl/test/xmerl_xsd_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-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.
@@ -94,8 +94,9 @@ return_results2({NewFail, NewSuccess, NewMal, NewNotMal}, NumSucc, SkippedN, Tot
_ -> io_lib:format("These ~p skipped tests were malicious, but succeeds now: ~p~n",
[length(NewNotMal), NewNotMal])
end,
- ct:comment(io_lib:format("~p successful tests, ~p skipped tests of totally ~p test cases. ~n" ++
- NFComm ++ NSComm ++ NMComm ++ NNMComm, [NumSucc, SkippedN, TotN])),
+ ct:comment(io_lib:format("~p successful tests, ~p skipped tests of totally ~p test cases. ~n~ts",
+ [NumSucc, SkippedN, TotN,
+ NFComm ++ NSComm ++ NMComm ++ NNMComm])),
[] = NewFail.
%% return_results2(Diff,{STErrs,STOther},{ITErrs,ITOther},TotN) ->
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 3a266a56bd..b6486681c2 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -1 +1 @@
-XMERL_VSN = 1.3.18
+XMERL_VSN = 1.3.19