diff options
Diffstat (limited to 'lib')
472 files changed, 68662 insertions, 6027 deletions
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml index ddc115c98c..0412d40423 100644 --- a/lib/asn1/doc/src/notes.xml +++ b/lib/asn1/doc/src/notes.xml @@ -32,6 +32,57 @@ <p>This document describes the changes made to the asn1 application.</p> +<section><title>Asn1 5.0.13</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Adhere to the ASN.1 specification for hstring & + bstring lexical items. That is they may include white + space.</p> + <p> + Own Id: OTP-16490</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p> + Improve handling of ellipsis in a CHOICE</p> + <p> + Own Id: OTP-16554 Aux Id: ERL-1189 </p> + </item> + </list> + </section> + +</section> + +<section><title>Asn1 5.0.12</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Dialyzer warnings of type <c>no_match</c> are now + suppressed in the generated files.</p> + <p> + Own Id: OTP-16636 Aux Id: ERIERL-145 </p> + </item> + </list> + </section> + +</section> + <section><title>Asn1 5.0.11</title> <section><title>Improvements and New Features</title> diff --git a/lib/asn1/src/asn1ct_gen.erl b/lib/asn1/src/asn1ct_gen.erl index 4f8b428b54..30396821b7 100644 --- a/lib/asn1/src/asn1ct_gen.erl +++ b/lib/asn1/src/asn1ct_gen.erl @@ -1314,7 +1314,9 @@ gen_head(#gen{options=Options}=Gen, Mod, Hrl) -> Mod,".",nl,nl, "-module('",Mod,"').",nl, "-compile(nowarn_unused_vars).",nl, - "-dialyzer(no_improper_lists).",nl]), + "-dialyzer(no_improper_lists).",nl, + "-dialyzer(no_match).",nl + ]), case Hrl of 0 -> ok; _ -> emit(["-include(\"",Mod,".hrl\").",nl]) diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl index ca7949fb37..f75a385cf5 100644 --- a/lib/asn1/test/asn1_SUITE.erl +++ b/lib/asn1/test/asn1_SUITE.erl @@ -45,10 +45,112 @@ all() -> [xref, xref_export_all, - {group, compile}, - {group, parallel}, + c_string, + constraint_equivalence, + + ber_decode_invalid_length, + ber_choiceinseq, + ber_optional, + tagdefault_automatic, + + cover, + + parse, + test_undecoded_rest, + specialized_decodes, + special_decode_performance, + + testMegaco, + testConstraints, + testCompactBitString, + default, + testPrim, + rtUI, + testPrimStrings, + + per, + ber, + der, + + h323test, + testExtensibilityImplied, + testChoice, + testDefaultOctetString, + testMultipleLevels, + testOpt, + testSeqDefault, + testMaps, + + testTypeValueNotation, + + testExternal, + + testSeqExtension, + testSeqOptional, + testSeqPrim, + testSeqTypeRefCho, + testSeqTypeRefPrim, + testSeqTypeRefSeq, + testSeqTypeRefSet, + + testSeqOf, + testSeqOfIndefinite, + testSeqOfCho, + testSeqOfChoExt, + + testExtensionAdditionGroup, + + testSet, + testSetOf, + + testEnumExt, + value_test, + testSeq2738, + constructed, + ber_decode_error, + otp_14440, + testSeqSetIndefinite, + testChoiceIndefinite, + per_open_type, + testInfObjectClass, + testUniqueObjectSets, + testInfObjExtract, + testParam, + testFragmented, + testMergeCompile, + testobj, + testDeepTConstr, + testImport, + testDER, + testDEFAULT, + testExtensionDefault, + testMvrasn6, + testContextSwitchingTypes, + testOpenTypeImplicitTag, + testROSE, + testINSTANCE_OF, + testTCAP, + test_ParamTypeInfObj, + test_Defed_ObjectIdentifier, + testSelectionType, + testSSLspecs, + testNortel, + test_WS_ParamClass, + test_modified_x420, + + %% Some heavy tests. + testTcapsystem, + testNBAPsystem, + testS1AP, + testRfcs, + + test_compile_options, + testDoubleEllipses, + test_x691, + ticket_6143, + test_OTP_9688, + testValueTest, - % TODO: Investigate parallel running of these: testComment, testName2Number, ticket_7407, @@ -57,129 +159,7 @@ all() -> {group, performance}]. groups() -> - Parallel = asn1_test_lib:parallel(), - [{compile, Parallel, - [c_string, - constraint_equivalence]}, - - {ber, Parallel, - [ber_decode_invalid_length, - ber_choiceinseq, - % Uses 'SOpttest' - ber_optional, - tagdefault_automatic]}, - - {parallel, Parallel, - [cover, - {group, ber}, - % Uses 'P-Record', 'Constraints', 'MEDIA-GATEWAY-CONTROL'... - {group, [], [parse, - test_undecoded_rest, - specialized_decodes, - special_decode_performance, - testMegaco, - testConstraints, - testCompactBitString]}, - default, - % Uses 'Def', 'MULTIMEDIA-SYSTEM-CONTROL', 'H323-MESSAGES', 'Prim', - % 'Real' - {group, [], [testPrim, - rtUI, - testPrimStrings, - per, - ber_other, - der, - h323test]}, - testExtensibilityImplied, - testChoPrim, - testChoExtension, - testChoOptional, - testChoRecursive, - testChoTypeRefCho, - testChoTypeRefPrim, - testChoTypeRefSeq, - testChoTypeRefSet, - testDefaultOctetString, - testMultipleLevels, - testOpt, - testSeqDefault, - testMaps, - % Uses 'External' - {group, [], [testExternal, - testSeqExtension]}, - testSeqOptional, - testSeqPrim, - testSeqTypeRefCho, - % Uses 'SeqTypeRefPrim' - {group, [], [testSeqTypeRefPrim, - testTypeValueNotation]}, - testSeqTypeRefSeq, - testSeqTypeRefSet, - % Uses 'SeqOf' - {group, [], [testSeqOf, - testSeqOfIndefinite]}, % Uses 'Mvrasn*' - testSeqOfCho, - testSeqOfChoExt, - testSetDefault, - testExtensionAdditionGroup, - testSetOptional, - testSetPrim, - testSetTypeRefCho, - testSetTypeRefPrim, - testSetTypeRefSeq, - testSetTypeRefSet, - testSetOf, - testSetOfCho, - testEnumExt, - value_test, - testSeq2738, - % Uses 'Constructed' - {group, [], [constructed, - ber_decode_error, - otp_14440]}, - testSeqSetIndefinite, - testChoiceIndefinite, - per_open_type, - testInfObjectClass, - testUniqueObjectSets, - testInfObjExtract, - testParam, - testFragmented, - testMergeCompile, - testobj, - testDeepTConstr, - testImport, - testDER, - testDEFAULT, - testExtensionDefault, - testMvrasn6, - testContextSwitchingTypes, - testOpenTypeImplicitTag, - testROSE, - testINSTANCE_OF, - testTCAP, - test_ParamTypeInfObj, - test_Defed_ObjectIdentifier, - testSelectionType, - testSSLspecs, - testNortel, - % Uses 'PKCS7', 'InformationFramework' - {group, [], [test_WS_ParamClass, - test_modified_x420]}, - %% Don't run all these at the same time. - {group, [], - [testTcapsystem, - testNBAPsystem, - testS1AP, - testRfcs]}, - test_compile_options, - testDoubleEllipses, - test_x691, - ticket_6143, - test_OTP_9688, - testValueTest]}, - - {performance, [], + [{performance, [], [testTimer_ber, testTimer_ber_maps, testTimer_per, @@ -326,30 +306,26 @@ do_test_prim(Rule, NoOkWrapper) -> testCompactBitString(Config) -> test(Config, fun testCompactBitString/3). testCompactBitString(Config, Rule, Opts) -> - asn1_test_lib:compile("PrimStrings", Config, - [Rule, compact_bit_string|Opts]), + Files = ["PrimStrings", "Constraints"], + asn1_test_lib:compile_all(Files, Config, [Rule, compact_bit_string|Opts]), testCompactBitString:compact_bit_string(Rule), testCompactBitString:bit_string_unnamed(Rule), testCompactBitString:bit_string_unnamed(Rule), testCompactBitString:ticket_7734(Rule), - asn1_test_lib:compile("Constraints", Config, - [Rule, compact_bit_string|Opts]), testCompactBitString:otp_4869(Rule). testPrimStrings(Config) -> test(Config, fun testPrimStrings/3, [ber,{ber,[der]},per,uper]). testPrimStrings(Config, Rule, Opts) -> LegacyOpts = [legacy_erlang_types|Opts], - asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config, - [Rule|LegacyOpts]), + Files = ["PrimStrings", "BitStr"], + asn1_test_lib:compile_all(Files, Config, [Rule|LegacyOpts]), testPrimStrings_cases(Rule, LegacyOpts), - asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config, [Rule|Opts]), + asn1_test_lib:compile_all(Files, Config, [Rule|Opts]), testPrimStrings_cases(Rule, Opts), - asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config, - [legacy_bit_string,Rule|Opts]), + asn1_test_lib:compile_all(Files, Config, [legacy_bit_string,Rule|Opts]), testPrimStrings:bit_string(Rule, Opts), - asn1_test_lib:compile_all(["PrimStrings", "BitStr"], Config, - [compact_bit_string,Rule|Opts]), + asn1_test_lib:compile_all(Files, Config, [compact_bit_string,Rule|Opts]), testPrimStrings:bit_string(Rule, Opts), testPrimStrings:more_strings(Rule). @@ -398,46 +374,26 @@ testExtensibilityImplied(Config, Rule, Opts) -> [Rule,no_ok_wrapper|Opts]), testExtensibilityImplied:main(Rule). -testChoPrim(Config) -> test(Config, fun testChoPrim/3). -testChoPrim(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoPrim", Config, [Rule|Opts]), +testChoice(Config) -> test(Config, fun testChoice/3). +testChoice(Config, Rule, Opts) -> + Files = ["ChoPrim", + "ChoExtension", + "ChoOptional", + "ChoOptionalImplicitTag", + "ChoRecursive", + "ChoTypeRefCho", + "ChoTypeRefPrim", + "ChoTypeRefSeq", + "ChoTypeRefSet"], + asn1_test_lib:compile_all(Files, Config, [Rule|Opts]), testChoPrim:bool(Rule), - testChoPrim:int(Rule). - -testChoExtension(Config) -> test(Config, fun testChoExtension/3). -testChoExtension(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoExtension", Config, [Rule|Opts]), - testChoExtension:extension(Rule). - -testChoOptional(Config) -> test(Config, fun testChoOptional/3). -testChoOptional(Config, Rule, Opts) -> - asn1_test_lib:compile_all(["ChoOptional", - "ChoOptionalImplicitTag"], Config, [Rule|Opts]), - testChoOptional:run(). - -testChoRecursive(Config) -> test(Config, fun testChoRecursive/3). -testChoRecursive(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoRecursive", Config, [Rule|Opts]), - testChoRecursive:recursive(Rule). - -testChoTypeRefCho(Config) -> test(Config, fun testChoTypeRefCho/3). -testChoTypeRefCho(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoTypeRefCho", Config, [Rule|Opts]), - testChoTypeRefCho:choice(Rule). - -testChoTypeRefPrim(Config) -> test(Config, fun testChoTypeRefPrim/3). -testChoTypeRefPrim(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoTypeRefPrim", Config, [Rule|Opts]), - testChoTypeRefPrim:prim(Rule). - -testChoTypeRefSeq(Config) -> test(Config, fun testChoTypeRefSeq/3). -testChoTypeRefSeq(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoTypeRefSeq", Config, [Rule|Opts]), - testChoTypeRefSeq:seq(Rule). - -testChoTypeRefSet(Config) -> test(Config, fun testChoTypeRefSet/3). -testChoTypeRefSet(Config, Rule, Opts) -> - asn1_test_lib:compile("ChoTypeRefSet", Config, [Rule|Opts]), + testChoPrim:int(Rule), + testChoExtension:extension(Rule), + testChoOptional:run(), + testChoRecursive:recursive(Rule), + testChoTypeRefCho:choice(Rule), + testChoTypeRefPrim:prim(Rule), + testChoTypeRefSeq:seq(Rule), testChoTypeRefSet:set(Rule). testDefaultOctetString(Config) -> test(Config, fun testDefaultOctetString/3). @@ -564,50 +520,33 @@ testSeqOfIndefinite(Config, Rule, Opts) -> asn1_test_lib:compile_all(Files, Config, [Rule|Opts]), testSeqOfIndefinite:main(). -testSetDefault(Config) -> test(Config, fun testSetDefault/3). -testSetDefault(Config, Rule, Opts) -> - asn1_test_lib:compile("SetDefault", Config, [Rule|Opts]), - testSetDefault:main(Rule). +testSet(Config) -> test(Config, fun testSet/3). +testSet(Config, Rule, Opts) -> + Files = ["SetDefault", + "SetOptional", + "SetPrim", + "SetTypeRefCho", + "SetTypeRefPrim", + "SetTypeRefSeq", + "SetTypeRefSet"], + asn1_test_lib:compile_all(Files, Config, [Rule|Opts]), -testSetOptional(Config) -> test(Config, fun testSetOptional/3). -testSetOptional(Config, Rule, Opts) -> - asn1_test_lib:compile("SetOptional", Config, [Rule|Opts]), + testSetDefault:main(Rule), testSetOptional:ticket_7533(Rule), - testSetOptional:main(Rule). - -testSetPrim(Config) -> test(Config, fun testSetPrim/3). -testSetPrim(Config, Rule, Opts) -> - asn1_test_lib:compile("SetPrim", Config, [Rule|Opts]), - testSetPrim:main(Rule). - -testSetTypeRefCho(Config) -> test(Config, fun testSetTypeRefCho/3). -testSetTypeRefCho(Config, Rule, Opts) -> - asn1_test_lib:compile("SetTypeRefCho", Config, [Rule|Opts]), - testSetTypeRefCho:main(Rule). - -testSetTypeRefPrim(Config) -> test(Config, fun testSetTypeRefPrim/3). -testSetTypeRefPrim(Config, Rule, Opts) -> - asn1_test_lib:compile("SetTypeRefPrim", Config, [Rule|Opts]), - testSetTypeRefPrim:main(Rule). - -testSetTypeRefSeq(Config) -> test(Config, fun testSetTypeRefSeq/3). -testSetTypeRefSeq(Config, Rule, Opts) -> - asn1_test_lib:compile("SetTypeRefSeq", Config, [Rule|Opts]), - testSetTypeRefSeq:main(Rule). - -testSetTypeRefSet(Config) -> test(Config, fun testSetTypeRefSet/3). -testSetTypeRefSet(Config, Rule, Opts) -> - asn1_test_lib:compile("SetTypeRefSet", Config, [Rule|Opts]), + testSetOptional:main(Rule), + + testSetPrim:main(Rule), + testSetTypeRefCho:main(Rule), + testSetTypeRefPrim:main(Rule), + testSetTypeRefSeq:main(Rule), testSetTypeRefSet:main(Rule). testSetOf(Config) -> test(Config, fun testSetOf/3). testSetOf(Config, Rule, Opts) -> - asn1_test_lib:compile("SetOf", Config, [Rule|Opts]), - testSetOf:main(Rule). - -testSetOfCho(Config) -> test(Config, fun testSetOfCho/3). -testSetOfCho(Config, Rule, Opts) -> - asn1_test_lib:compile("SetOfCho", Config, [Rule|Opts]), + Files = ["SetOf", + "SetOfCho"], + asn1_test_lib:compile_all(Files, Config, [Rule|Opts]), + testSetOf:main(Rule), testSetOfCho:main(Rule). c_string(Config) -> @@ -657,19 +596,23 @@ parse(Config) -> per(Config) -> test(Config, fun per/3, [per,uper,{per,[maps]},{uper,[maps]}]). per(Config, Rule, Opts) -> - [module_test(M, Config, Rule, Opts) || M <- per_modules()]. + module_test(per_modules(), Config, Rule, Opts). -ber_other(Config) -> - test(Config, fun ber_other/3, [ber,{ber,[maps]}]). +ber(Config) -> + test(Config, fun ber/3, [ber,{ber,[maps]}]). -ber_other(Config, Rule, Opts) -> - [module_test(M, Config, Rule, Opts) || M <- ber_modules()]. +ber(Config, Rule, Opts) -> + module_test(ber_modules(), Config, Rule, Opts). der(Config) -> asn1_test_lib:compile_all(ber_modules(), Config, [der]). -module_test(M0, Config, Rule, Opts) -> - asn1_test_lib:compile(M0, Config, [Rule,?NO_MAPS_MODULE|Opts]), +module_test(Modules, Config, Rule, Opts) -> + asn1_test_lib:compile_all(Modules, Config, [Rule,?NO_MAPS_MODULE|Opts]), + _ = [do_module_test(M, Config, Opts) || M <- Modules], + ok. + +do_module_test(M0, Config, Opts) -> case list_to_atom(M0) of 'LDAP' -> %% Because of the recursive definition of 'Filter' in @@ -815,8 +758,8 @@ per_open_type(Config, Rule, Opts) -> testConstraints(Config) -> test(Config, fun testConstraints/3). testConstraints(Config, Rule, Opts) -> - asn1_test_lib:compile("Constraints", Config, [Rule|Opts]), - asn1_test_lib:compile("LargeConstraints", Config, [Rule|Opts]), + Files = ["Constraints", "LargeConstraints"], + asn1_test_lib:compile_all(Files, Config, [Rule|Opts]), testConstraints:int_constraints(Rule), case Rule of ber -> ok; @@ -1163,9 +1106,6 @@ testExtensionAdditionGroup(Config, Rule, Opts) -> [Rule,{record_name_prefix,"RRC-"}|Opts]), extensionAdditionGroup:run(Rule). -% parse_modules() -> -% ["ImportsFrom"]. - per_modules() -> [X || X <- test_modules()]. diff --git a/lib/asn1/test/asn1_test_lib.erl b/lib/asn1/test/asn1_test_lib.erl index af8462f0c9..2c91256d6d 100644 --- a/lib/asn1/test/asn1_test_lib.erl +++ b/lib/asn1/test/asn1_test_lib.erl @@ -24,7 +24,6 @@ rm_dirs/1, hex_to_bin/1, match_value/2, - parallel/0, roundtrip/3,roundtrip/4,roundtrip_enc/3,roundtrip_enc/4, map_roundtrip/3]). @@ -48,12 +47,6 @@ compile_all(Files, Config, Options0) -> dialyze(Files, Options), ok. -parallel() -> - case erlang:system_info(schedulers) > 1 andalso not run_dialyzer() of - true -> [parallel]; - false -> [] - end. - dialyze(Files, Options) -> case not run_dialyzer() orelse lists:member(abs, Options) of true -> ok; diff --git a/lib/asn1/test/testEnumExt.erl b/lib/asn1/test/testEnumExt.erl index b30750f7fc..cb01ad481f 100644 --- a/lib/asn1/test/testEnumExt.erl +++ b/lib/asn1/test/testEnumExt.erl @@ -41,7 +41,7 @@ main(Rule) when Rule =:= per; Rule =:= uper -> B64 = roundtrip('Noext', red), common(Rule); main(Rule) when Rule =:= ber; Rule =:= jer -> - io:format("main(ber)~n",[]), + io:format("main(~p)~n",[Rule]), %% ENUMERATED with extensionmark (value is in root set) roundtrip('Ext', red), diff --git a/lib/asn1/test/testMegaco.erl b/lib/asn1/test/testMegaco.erl index 0be798b962..6a88117896 100644 --- a/lib/asn1/test/testMegaco.erl +++ b/lib/asn1/test/testMegaco.erl @@ -26,8 +26,8 @@ -include_lib("common_test/include/ct.hrl"). compile(Config, Erule, Options) -> - asn1_test_lib:compile("MEDIA-GATEWAY-CONTROL.asn", Config, [Erule|Options]), - asn1_test_lib:compile("OLD-MEDIA-GATEWAY-CONTROL.asn", Config, [Erule|Options]), + Files = ["MEDIA-GATEWAY-CONTROL.asn","OLD-MEDIA-GATEWAY-CONTROL.asn"], + asn1_test_lib:compile_all(Files, Config, [Erule|Options]), {ok,'OLD-MEDIA-GATEWAY-CONTROL','MEDIA-GATEWAY-CONTROL'}. main(no_module,_) -> ok; diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk index e1d3b65da2..8ebbe907bc 100644 --- a/lib/asn1/vsn.mk +++ b/lib/asn1/vsn.mk @@ -1 +1 @@ -ASN1_VSN = 5.0.11 +ASN1_VSN = 5.0.13 diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml index 70653c0711..9545d8352e 100644 --- a/lib/common_test/doc/src/notes.xml +++ b/lib/common_test/doc/src/notes.xml @@ -33,6 +33,44 @@ <file>notes.xml</file> </header> +<section><title>Common_Test 1.19</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The function <c>ct_property_test:init_tool/1</c> is added + for the cases when the user does not want + ct_property_test to compile properties. init_tool/1 can + be used to set the property_test_tool config value.</p> + <p> + Own Id: OTP-16029 Aux Id: PR-2145 </p> + </item> + <item> + <p> + The built-in Common Test Hook, <c>cth_log_redirect</c>, + has been updated to use the system <c>default</c> Logger + handler's configuration instead of its own. See the + section on <seeguide + marker="common_test:ct_hooks_chapter#built-in-cths">Built-in + Hooks</seeguide> in the Common Test User's Guide.</p> + <p> + Own Id: OTP-16273</p> + </item> + <item> + <p> + Calls of deprecated functions in the <seeguide + marker="crypto:new_api#the-old-api">Old Crypto + API</seeguide> are replaced by calls of their <seeguide + marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p> + <p> + Own Id: OTP-16346</p> + </item> + </list> + </section> + +</section> + <section><title>Common_Test 1.18.2</title> <section><title>Improvements and New Features</title> diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk index 1e6991d73a..b5fa287e1f 100644 --- a/lib/common_test/vsn.mk +++ b/lib/common_test/vsn.mk @@ -1 +1 @@ -COMMON_TEST_VSN = 1.18.2 +COMMON_TEST_VSN = 1.19 diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index 079c84b776..a37ac251de 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,129 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.6.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + In rare circumstances, a guard using 'not' could evaluate + to the wrong boolean value.</p> + <p> + Own Id: OTP-16652 Aux Id: ERL-1246 </p> + </item> + <item> + <p>A guard expression that referenced a variable bound to + a boolean expression could evaluate to the wrong + value.</p> + <p> + Own Id: OTP-16657 Aux Id: ERL-1253 </p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p><c>erlang:fun_info(fun foo/1, name/1)</c> used to + return a function name based on the name of the function + that <c>fun foo/1</c> was used in. The name returned is + now <c>-fun.foo/1-</c>.</p> + <p> + Own Id: OTP-15837</p> + </item> + <item> + <p> Initialization of record fields using <c>_</c> is no + longer allowed if the number of affected fields is zero. + </p> + <p> + Own Id: OTP-16516</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>EEP-52 has been implemented.</p> + <p>In binary matching, the size of the segment to be + matched is now allowed to be a guard expression, and + similarly in map matching the keys can now be guard + expressions. See the Erlang Reference Manual and + Programming Examples for more details.</p> + <p>Language compilers or code generators that generate + Core Erlang code may need to be updated to be compatible + with the compiler in OTP 23. For more details, see the + section Backwards Compatibility in <url + href="http://erlang.org/eeps/eep-0052.html">EEP + 52</url>.</p> + <p> + Own Id: OTP-14708</p> + </item> + <item> + <p> Allow underscores in numeric literals to improve + readability. Examples: <c>123_456_789</c>, + <c>16#1234_ABCD</c>. </p> + <p> + Own Id: OTP-16007 Aux Id: PR-2324 </p> + </item> + <item> + <p>Improved the type optimization pass' inference of + types that depend on themselves, giving us more accurate + types and letting us track the content types of + lists.</p> + <p> + Own Id: OTP-16214 Aux Id: PR-2460 </p> + </item> + <item> + <p> + Support message queue optimization also for references + returned from the new <seemfa + marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa> + BIFs.</p> + <p> + Own Id: OTP-16367 Aux Id: OTP-15251 </p> + </item> + <item> + <p>The compiler will now raise a warning when inlining is + used in modules that load NIFs.</p> + <p> + Own Id: OTP-16429 Aux Id: ERL-303 </p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p>Line information was sometimes incorrect for + floating-point math exceptions.</p> + <p> + Own Id: OTP-16505 Aux Id: ERL-1178 </p> + </item> + <item> + <p>The <c>debug_info</c> option can now be specified in + <c>-compile()</c> attributes.</p> + <p> + Own Id: OTP-16523 Aux Id: ERL-1058 </p> + </item> + <item> + <p>Reduced the resource usage of <c>erlc</c> in parallel + builds (e.g. <c>make -j128</c>).</p> + <p> + Own Id: OTP-16543 Aux Id: ERL-1186 </p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.5.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/beam_ssa_bool.erl b/lib/compiler/src/beam_ssa_bool.erl index 7ae9070df2..5b81ca2be1 100644 --- a/lib/compiler/src/beam_ssa_bool.erl +++ b/lib/compiler/src/beam_ssa_bool.erl @@ -922,9 +922,11 @@ opt_digraph_instr(#b_set{dst=Dst}=I, G0, St) -> %% Rewriting 'xor' is not practical. Fortunately, %% 'xor' is almost never used in practice. not_possible(); - #b_set{op={bif,'not'},args=[#b_var{}=Bool]} -> - G = convert_to_br_node(I, Fail, G1, St), - redirect_test(Bool, {fail,Succ}, G, St); + #b_set{op={bif,'not'}} -> + %% This is suprisingly rare. The previous attempt to + %% optimize it was broken, which wasn't noticed because + %% very few test cases triggered this code. + not_possible(); #b_set{op=phi,dst=Bool} -> Vtx = get_vertex(Bool, St), G2 = del_out_edges(Vtx, G1), @@ -1070,7 +1072,60 @@ redirect_phi(Phi, Args, SuccFail, G0, St) -> redirect_phi_1(PhiVtx, [{#b_literal{val=false},FalseExit}, {#b_var{}=SuccBool,_BoolExit}], SuccFail, G0, St) -> + %% This was most likely an `andalso` in the source code. BoolVtx = get_vertex(SuccBool, St), + + %% We must be careful when rewriting guards that reference boolean + %% expressions defined before the guard. Here is an example: + %% + %% Bool = Z =:= false, + %% if + %% X =:= Y andalso Bool -> ok; + %% true -> error + %% end. + %% + %% Slightly simplified, the SSA code will look like this: + %% + %% 10: Bool = bif:'=:=' _2, `false` + %% br ^11 + %% + %% 11: B = bif:'=:=' X, Y + %% br B, ^20, ^30 + %% + %% 20: br ^40 + %% 30: br ^40 + %% + %% 40: Phi = phi { `true`, ^20 }, { Bool, ^30 } + %% br Phi, ^100, ^200 + %% + %% 100: ret `ok` + %% 200: ret `error' + %% + %% The usual rewriting of the phi node will result in the following + %% SSA code: + %% + %% 10: Bool = bif:'=:=' _2, `false` + %% br Bool, ^100, ^200 + %% + %% 11: B = bif:'=:=' X, Y + %% br B, ^100, ^200 + %% + %% 20: br ^40 + %% 30: br ^40 + %% + %% 40: Phi = phi { `true`, ^20 }, { Bool, ^30 } + %% br Phi, ^100, ^200 + %% + %% 100: ret `ok` + %% 200: ret `error' + %% + %% Block 11 is no longer reachable; thus, the X =:= Y test has been dropped. + %% To avoid dropping tests, we should check whether if there is a path from + %% 10 to block 20. If there is, the optimization in its current form is not + %% safe. + %% + ensure_disjoint_paths(G0, BoolVtx, FalseExit), + [FalseOut] = beam_digraph:out_edges(G0, FalseExit), G1 = beam_digraph:del_edge(G0, FalseOut), case SuccFail of @@ -1088,6 +1143,11 @@ redirect_phi_1(PhiVtx, [{#b_literal{val=true},TrueExit}, {fail,Fail}, G0, St) -> %% This was probably an `orelse` in the source code. BoolVtx = get_vertex(SuccBool, St), + + %% See the previous clause for an explanation of why we + %% must ensure that paths are disjoint. + ensure_disjoint_paths(G0, BoolVtx, TrueExit), + [TrueOut] = beam_digraph:out_edges(G0, TrueExit), G1 = beam_digraph:del_edge(G0, TrueOut), G2 = beam_digraph:add_edge(G1, TrueExit, PhiVtx, next), @@ -1117,6 +1177,18 @@ digraph_bool_def(G) -> Ds = [{Dst,Vtx} || {Vtx,#b_set{dst=Dst}} <- Vs], maps:from_list(Ds). + +%% ensure_disjoint_paths(G, Vertex1, Vertex2) -> ok. +%% Ensure that there is no path from Vertex1 to Vertex2 in +%% either direction. (It is probably overkill to test both +%% directions, but better safe than sorry.) + +ensure_disjoint_paths(G, V1, V2) -> + case beam_digraph:is_path(G, V1, V2) orelse beam_digraph:is_path(G, V2, V1) of + true -> not_possible(); + false -> ok + end. + %%% %%% Shortcut branches that branch to other branches. %%% diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 1549b16cbc..d77b7756e8 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -40,7 +40,8 @@ -include("beam_ssa_opt.hrl"). -import(lists, [all/2,append/1,duplicate/2,flatten/1,foldl/3, - keyfind/3,last/1,member/2,reverse/1,reverse/2, + keyfind/3,last/1,mapfoldl/3,member/2, + partition/2,reverse/1,reverse/2, splitwith/2,sort/1,takewhile/2,unzip/1]). -define(MAX_REPETITIONS, 16). @@ -299,6 +300,7 @@ epilogue_passes(Opts) -> ?PASS(ssa_opt_blockify), ?PASS(ssa_opt_merge_blocks), ?PASS(ssa_opt_get_tuple_element), + ?PASS(ssa_opt_tail_calls), ?PASS(ssa_opt_trim_unreachable), ?PASS(ssa_opt_unfold_literals)], passes_1(Ps, Opts). @@ -2185,10 +2187,55 @@ opt_ne_single_use(Var, Uses) when is_map(Uses) -> %%% extracting tuple elements on execution paths that never use the %%% extracted values. %%% +%%% However, there is one caveat to be aware of. Sinking tuple elements +%%% will keep the entire tuple alive longer. In rare circumstance, that +%%% can be a problem. Here is an example: +%%% +%%% mapfoldl(F, Acc0, [Hd|Tail]) -> +%%% {R,Acc1} = F(Hd, Acc0), +%%% {Rs,Acc2} = mapfoldl(F, Acc1, Tail), +%%% {[R|Rs],Acc2}; +%%% mapfoldl(F, Acc, []) -> +%%% {[],Acc}. +%%% +%%% Sinking get_tuple_element instructions will generate code similar +%%% to this example: +%%% +%%% slow_mapfoldl(F, Acc0, [Hd|Tail]) -> +%%% Res1 = F(Hd, Acc0), +%%% Res2 = slow_mapfoldl(F, element(2, Res1), Tail), +%%% {[element(1, Res1)|element(1, Res2)],element(2, Res2)}; +%%% slow_mapfoldl(F, Acc, []) -> +%%% {[],Acc}. +%%% +%%% Here the entire tuple bound to `Res1` will be kept alive until +%%% `slow_mapfoldl/3` returns. That is, every intermediate accumulator +%%% will be kept alive. +%%% +%%% In this case, not sinking is clearly superior: +%%% +%%% fast_mapfoldl(F, Acc0, [Hd|Tail]) -> +%%% Res1 = F(Hd, Acc0), +%%% R = element(1, Res1), +%%% Res2 = fast_mapfoldl(F, element(2, Res1), Tail), +%%% {[R|element(1, Res2)],element(2, Res2)}; +%%% fast_mapfoldl(F, Acc, []) -> +%%% {[],Acc}. +%%% +%%% The `slow_mapfoldl/3` and `fast_mapfoldl/3` use the same number of +%%% stack slots. +%%% +%%% To avoid producing code similar to `slow_mapfoldl/3`, use an +%%% heuristic to only sink when sinking would reduce the number of +%%% stack slots, or if there can't possibly be a recursive call +%%% involved. This is a heuristic because it is difficult to exactly +%%% predict the number of stack slots that will be needed for a given +%%% piece of code. ssa_opt_sink({#opt_st{ssa=Linear}=St, FuncDb}) -> %% Create a map with all variables that define get_tuple_element - %% instructions. The variable name map to the block it is defined in. + %% instructions. The variable name maps to the block it is defined + %% in and the source tuple. case def_blocks(Linear) of [] -> %% No get_tuple_element instructions, so there is nothing to do. @@ -2199,32 +2246,36 @@ ssa_opt_sink({#opt_st{ssa=Linear}=St, FuncDb}) -> end. do_ssa_opt_sink(Defs, #opt_st{ssa=Linear}=St) -> - Blocks0 = maps:from_list(Linear), - - %% Now find all the blocks that use variables defined by get_tuple_element - %% instructions. + %% Find all the blocks that use variables defined by + %% get_tuple_element instructions. Used = used_blocks(Linear, Defs, []), %% Calculate dominators. + Blocks0 = maps:from_list(Linear), {Dom,Numbering} = beam_ssa:dominators(Blocks0), %% It is not safe to move get_tuple_element instructions to blocks %% that begin with certain instructions. It is also unsafe to move - %% the instructions into any part of a receive. To avoid such - %% unsafe moves, pretend that the unsuitable blocks are not - %% dominators. + %% the instructions into any part of a receive. Unsuitable = unsuitable(Linear, Blocks0), %% Calculate new positions for get_tuple_element instructions. The new %% position is a block that dominates all uses of the variable. - DefLoc = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable), + DefLocs0 = new_def_locations(Used, Defs, Dom, Numbering, Unsuitable), + + %% Avoid keeping tuples alive if only one element is accessed later and + %% if there is the chance of a recursive call being made. This is an + %% important precaution to avoid that lists:mapfoldl/3 keeps all previous + %% versions of the accumulator alive until the end of the input list. + Ps = partition_deflocs(DefLocs0, Defs, Blocks0), + DefLocs1 = filter_deflocs(Ps, Blocks0), + DefLocs = sort(DefLocs1), %% Now move all suitable get_tuple_element instructions to their %% new blocks. - Blocks = foldl(fun({V,To}, A) -> - From = map_get(V, Defs), + Blocks = foldl(fun({V,{From,To}}, A) -> move_defs(V, From, To, A) - end, Blocks0, DefLoc), + end, Blocks0, DefLocs), St#opt_st{ssa=beam_ssa:linearize(Blocks)}. @@ -2232,8 +2283,8 @@ def_blocks([{L,#b_blk{is=Is}}|Bs]) -> def_blocks_is(Is, L, def_blocks(Bs)); def_blocks([]) -> []. -def_blocks_is([#b_set{op=get_tuple_element,dst=Dst}|Is], L, Acc) -> - def_blocks_is(Is, L, [{Dst,L}|Acc]); +def_blocks_is([#b_set{op=get_tuple_element,args=[Tuple,_],dst=Dst}|Is], L, Acc) -> + def_blocks_is(Is, L, [{Dst,{L,Tuple}}|Acc]); def_blocks_is([_|Is], L, Acc) -> def_blocks_is(Is, L, Acc); def_blocks_is([], _, Acc) -> Acc. @@ -2245,6 +2296,179 @@ used_blocks([{L,Blk}|Bs], Def, Acc0) -> used_blocks([], _Def, Acc) -> rel2fam(Acc). +%% Partition sinks for get_tuple_element instructions in the same +%% clause extracting from the same tuple. Sort each partition in +%% execution order. +partition_deflocs(DefLoc, _Defs, Blocks) -> + {BlkNums0,_} = mapfoldl(fun(L, N) -> {{L,N},N+1} end, 0, beam_ssa:rpo(Blocks)), + BlkNums = maps:from_list(BlkNums0), + S = [{Tuple,{map_get(To, BlkNums),{V,{From,To}}}} || + {V,Tuple,{From,To}} <- DefLoc], + F = rel2fam(S), + partition_deflocs_1(F, Blocks). + +partition_deflocs_1([{Tuple,DefLocs0}|T], Blocks) -> + DefLocs1 = [DL || {_,DL} <- DefLocs0], + DefLocs = partition_dl(DefLocs1, Blocks), + [{Tuple,DL} || DL <- DefLocs] ++ partition_deflocs_1(T, Blocks); +partition_deflocs_1([], _) -> []. + +partition_dl([_]=DefLoc, _Blocks) -> + [DefLoc]; +partition_dl([{_,{_,First}}|_]=DefLoc0, Blocks) -> + RPO = beam_ssa:rpo([First], Blocks), + {P,DefLoc} = partition_dl_1(DefLoc0, RPO, []), + [P|partition_dl(DefLoc, Blocks)]; +partition_dl([], _Blocks) -> []. + +partition_dl_1([{_,{_,L}}=DL|DLs], [L|_]=Ls, Acc) -> + partition_dl_1(DLs, Ls, [DL|Acc]); +partition_dl_1([_|_]=DLs, [_|Ls], Acc) -> + partition_dl_1(DLs, Ls, Acc); +partition_dl_1([], _, Acc) -> + {reverse(Acc),[]}; +partition_dl_1([_|_]=DLs, [], Acc) -> + {reverse(Acc),DLs}. + +filter_deflocs([{Tuple,DefLoc0}|DLs], Blocks) -> + %% Input is a list of sinks of get_tuple_element instructions in + %% execution order from the same tuple in the same clause. + [{_,{_,First}}|_] = DefLoc0, + Paths = find_paths_to_check(DefLoc0, First), + WillGC0 = ordsets:from_list([FromTo || {{_,_}=FromTo,_} <- Paths]), + WillGC1 = [{{From,To},will_gc(From, To, Blocks, true)} || + {From,To} <- WillGC0], + WillGC = maps:from_list(WillGC1), + + %% Separate sinks that will force the reference to the tuple to be + %% saved on the stack from sinks that don't force. + {DefLocGC0,DefLocNoGC} = + partition(fun({{_,_}=FromTo,_}) -> + map_get(FromTo, WillGC) + end, Paths), + + %% Avoid potentially harmful sinks. + DefLocGC = filter_gc_deflocs(DefLocGC0, Tuple, First, Blocks), + + %% Construct the complete list of sink operations. + DefLoc1 = DefLocGC ++ DefLocNoGC, + [DL || {_,{_,{From,To}}=DL} <- DefLoc1, From =/= To] ++ + filter_deflocs(DLs, Blocks); +filter_deflocs([], _) -> []. + +%% Use an heuristic to avoid harmful sinking in lists:mapfold/3 and +%% similar functions. +filter_gc_deflocs(DefLocGC, Tuple, First, Blocks) -> + case DefLocGC of + [] -> + []; + [{_,{_,{From,To}}}] -> + %% There is only one get_tuple_element instruction that + %% can be sunk. That means that we may not gain any slots + %% by sinking it. + case is_on_stack(First, Tuple, Blocks) of + true -> + %% The tuple itself must be stored in a stack slot + %% (because it will be used later) in addition to + %% the tuple element being extracted. It is + %% probably a win to sink this instruction. + DefLocGC; + false -> + case will_gc(From, To, Blocks, false) of + false -> + %% There is no risk for recursive calls, + %% so it should be safe to + %% sink. Theoretically, we shouldn't win + %% any stack slots, but in practice it + %% seems that sinking makes it more likely + %% that the stack slot for a dying value + %% can be immediately reused for another + %% value. + DefLocGC; + true -> + %% This function could be involved in a + %% recursive call. Since there is no + %% obvious reduction in the number of + %% stack slots, we will not sink. + [] + end + end; + [_,_|_] -> + %% More than one get_tuple_element instruction can be + %% sunk. Sinking will almost certainly reduce the number + %% of stack slots. + DefLocGC + end. + +find_paths_to_check([{_,{_,To}}=Move|T], First) -> + [{{First,To},Move}|find_paths_to_check(T, First)]; +find_paths_to_check([], _First) -> []. + +will_gc(From, To, Blocks, All) -> + will_gc(beam_ssa:rpo([From], Blocks), To, Blocks, All, #{From => false}). + +will_gc([To|_], To, _Blocks, _All, WillGC) -> + map_get(To, WillGC); +will_gc([L|Ls], To, Blocks, All, WillGC0) -> + #b_blk{is=Is} = Blk = map_get(L, Blocks), + GC = map_get(L, WillGC0) orelse will_gc_is(Is, All), + WillGC = gc_update_successors(Blk, GC, WillGC0), + will_gc(Ls, To, Blocks, All, WillGC). + +will_gc_is([#b_set{op=call,args=Args}|Is], false) -> + case Args of + [#b_remote{mod=#b_literal{val=erlang}}|_] -> + %% Assume that remote calls to the erlang module can't cause a recursive + %% call. + will_gc_is(Is, false); + [_|_] -> + true + end; +will_gc_is([_|Is], false) -> + %% Instructions that clobber X registers may cause a GC, but will not cause + %% a recursive call. + will_gc_is(Is, false); +will_gc_is([I|Is], All) -> + beam_ssa:clobbers_xregs(I) orelse will_gc_is(Is, All); +will_gc_is([], _All) -> false. + +is_on_stack(From, Var, Blocks) -> + is_on_stack(beam_ssa:rpo([From], Blocks), Var, Blocks, #{From => false}). + +is_on_stack([L|Ls], Var, Blocks, WillGC0) -> + #b_blk{is=Is} = Blk = map_get(L, Blocks), + GC0 = map_get(L, WillGC0), + try is_on_stack_is(Is, Var, GC0) of + GC -> + WillGC = gc_update_successors(Blk, GC, WillGC0), + is_on_stack(Ls, Var, Blocks, WillGC) + catch + throw:{done,GC} -> + GC + end; +is_on_stack([], _Var, _, _) -> false. + +is_on_stack_is([#b_set{op=get_tuple_element}|Is], Var, GC) -> + is_on_stack_is(Is, Var, GC); +is_on_stack_is([I|Is], Var, GC0) -> + case GC0 andalso member(Var, beam_ssa:used(I)) of + true -> + throw({done,GC0}); + false -> + GC = GC0 orelse beam_ssa:clobbers_xregs(I), + is_on_stack_is(Is, Var, GC) + end; +is_on_stack_is([], _, GC) -> GC. + +gc_update_successors(Blk, GC, WillGC) -> + foldl(fun(L, Acc) -> + case Acc of + #{L := true} -> Acc; + #{L := false} when GC =:= false -> Acc; + #{} -> Acc#{L => GC} + end + end, WillGC, beam_ssa:successors(Blk)). + %% unsuitable(Linear, Blocks) -> Unsuitable. %% Return an ordset of block labels for the blocks that are not %% suitable for sinking of get_tuple_element instructions. @@ -2327,19 +2551,22 @@ is_loop_header(L, Blocks) -> %% of the variable and as near to the uses of as possible. new_def_locations([{V,UsedIn}|Vs], Defs, Dom, Numbering, Unsuitable) -> - DefIn = map_get(V, Defs), + {DefIn,Tuple} = map_get(V, Defs), Common = common_dominator(UsedIn, Dom, Numbering, Unsuitable), - case member(Common, map_get(DefIn, Dom)) of - true -> - %% The common dominator is either DefIn or an - %% ancestor of DefIn. - new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable); - false -> - %% We have found a suitable descendant of DefIn, - %% to which the get_tuple_element instruction can - %% be sunk. - [{V,Common}|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)] - end; + Sink = case member(Common, map_get(DefIn, Dom)) of + true -> + %% The common dominator is either DefIn or an + %% ancestor of DefIn. We'll need to consider all + %% get_tuple_element instructions so we will add + %% a dummy sink. + {V,Tuple,{DefIn,DefIn}}; + false -> + %% We have found a suitable descendant of DefIn, + %% to which the get_tuple_element instruction can + %% be sunk. + {V,Tuple,{DefIn,Common}} + end, + [Sink|new_def_locations(Vs, Defs, Dom, Numbering, Unsuitable)]; new_def_locations([], _, _, _, _) -> []. common_dominator(Ls0, Dom, Numbering, Unsuitable) -> @@ -2362,7 +2589,6 @@ move_defs(V, From, To, Blocks) -> {Def,FromBlk} = remove_def(V, FromBlk0), try insert_def(V, Def, ToBlk0) of ToBlk -> - %%io:format("~p: ~p => ~p\n", [V,From,To]), Blocks#{From:=FromBlk,To:=ToBlk} catch throw:not_possible -> @@ -2683,6 +2909,170 @@ unfold_arg(#b_literal{val=Val}=Lit, LitMap, X) -> unfold_arg(Expr, _LitMap, _X) -> Expr. %%% +%%% Optimize tail calls created as the result of optimizations. +%%% +%%% Consider the following example of a tail call in Erlang code: +%%% +%%% bar() -> +%%% foo(). +%%% +%%% The SSA code for the call will look like this: +%%% +%%% @ssa_ret = call (`foo`/0) +%%% ret @ssa_ret +%%% +%%% Sometimes optimizations create new tail calls. Consider this +%%% slight variation of the example: +%%% +%%% bar() -> +%%% {_,_} = foo(). +%%% +%%% foo() -> {a,b}. +%%% +%%% If beam_ssa_type can figure out that `foo/0` always returns a tuple +%%% of size two, the test for a tuple is no longer needed and the call +%%% to `foo/0` will become a tail call. However, the resulting SSA +%%% code will look like this: +%%% +%%% @ssa_ret = call (`foo`/0) +%%% @ssa_bool = succeeded:body @ssa_ret +%%% br @ssa_bool, ^999, ^1 +%%% +%%% 999: +%%% ret @ssa_ret +%%% +%%% The beam_ssa_codegen pass will not recognize this code as a tail +%%% call and will generate an unncessary stack frame. It may also +%%% generate unecessary `kill` instructions. +%%% +%%% To avoid those extra instructions, this optimization will +%%% eliminate the `succeeded:body` and `br` instructions and insert +%%% the `ret` in the same block as the call: +%%% +%%% @ssa_ret = call (`foo`/0) +%%% ret @ssa_ret +%%% +%%% Finally, consider this example: +%%% +%%% bar() -> +%%% foo_ok(), +%%% ok. +%%% +%%% foo_ok() -> ok. +%%% +%%% The SSA code for the call to `foo_ok/0` will look like: +%%% +%%% %% Result type: `ok` +%%% @ssa_ignored = call (`foo_ok`/0) +%%% @ssa_bool = succeeded:body @ssa_ignored +%%% br @ssa_bool, ^999, ^1 +%%% +%%% 999: +%%% ret `ok` +%%% +%%% Since the call to `foo_ok/0` has an annotation indicating that the +%%% call will always return the atom `ok`, the code can be simplified +%%% like this: +%%% +%%% @ssa_ignored = call (`foo_ok`/0) +%%% ret @ssa_ignored +%%% +%%% The beam_jump pass does the same optimization, but it does it too +%%% late to avoid creating an uncessary stack frame or unnecessary +%%% `kill` instructions. +%%% + +ssa_opt_tail_calls({St,FuncDb}) -> + #opt_st{ssa=Blocks0} = St, + Blocks = opt_tail_calls(beam_ssa:rpo(Blocks0), Blocks0), + {St#opt_st{ssa=Blocks},FuncDb}. + +opt_tail_calls([L|Ls], Blocks0) -> + #b_blk{is=Is0,last=Last} = Blk0 = map_get(L, Blocks0), + + %% Does this block end with a two-way branch whose success + %% label targets an empty block with a `ret` terminator? + case is_potential_tail_call(Last, Blocks0) of + {yes,Bool,Ret} -> + %% Yes, `Ret` is the value returned from that block + %% (either a variable or literal). Do the instructions + %% in this block end with a `call` instruction that + %% returns the same value as `Ret`, followed by a + %% `succeeded:body` instruction? + case is_tail_call_is(Is0, Bool, Ret, []) of + {yes,Is,Var} -> + %% Yes, this is a tail call. `Is` is the instructions + %% in the block with `succeeded:body` removed, and + %% `Var` is the destination variable for the return + %% value of the call. Rewrite this block to directly + %% return `Var`. + Blk = Blk0#b_blk{is=Is,last=#b_ret{arg=Var}}, + Blocks = Blocks0#{L:=Blk}, + opt_tail_calls(Ls, Blocks); + no -> + %% No, the block does not end with a call, or the + %% the call instruction has not the same value + %% as `Ret`. + opt_tail_calls(Ls, Blocks0) + end; + no -> + opt_tail_calls(Ls, Blocks0) + end; +opt_tail_calls([], Blocks) -> Blocks. + +is_potential_tail_call(#b_br{bool=#b_var{}=Bool,succ=Succ}, Blocks) -> + case Blocks of + #{Succ := #b_blk{is=[],last=#b_ret{arg=Arg}}} -> + %% This could be a tail call. + {yes,Bool,Arg}; + #{} -> + %% The block is not empty or does not have a `ret` terminator. + no + end; +is_potential_tail_call(_, _) -> + %% Not a two-way branch (a `succeeded:body` instruction must be + %% followed by a two-way branch). + no. + +is_tail_call_is([#b_set{op=call,dst=Dst}=Call, + #b_set{op={succeeded,body},dst=Bool}], + Bool, Ret, Acc) -> + IsTailCall = + case Ret of + #b_literal{val=Val} -> + %% The return value for this function is a literal. + %% Now test whether it is the same literal that the + %% `call` instruction returns. + Type = beam_ssa:get_anno(result_type, Call, any), + case beam_types:get_singleton_value(Type) of + {ok,Val} -> + %% Same value. + true; + {ok,_} -> + %% Wrong value. + false; + error -> + %% The type for the return value is not a singleton value. + false + end; + #b_var{} -> + %% It is a tail call if the variable set by the `call` instruction + %% is the same variable as the argument for the `ret` terminator. + Ret =:= Dst + end, + case IsTailCall of + true -> + %% Return the instructions in the block with `succeeded:body` removed. + Is = reverse(Acc, [Call]), + {yes,Is,Dst}; + false -> + no + end; +is_tail_call_is([I|Is], Bool, Ret, Acc) -> + is_tail_call_is(Is, Bool, Ret, [I|Acc]); +is_tail_call_is([], _Bool, _Ret, _Acc) -> no. + +%%% %%% Common utilities. %%% diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl index 09302b6a79..017d2b7d8c 100644 --- a/lib/compiler/src/beam_trim.erl +++ b/lib/compiler/src/beam_trim.erl @@ -63,7 +63,8 @@ trim([{kill,_}|_]=Is0, St, Acc) -> %% Calculate all recipes that are not worse in terms %% of estimated execution time. The recipes are ordered %% in descending order from how much they trim. - Recipes = trim_recipes(Layout), + IsNotRecursive = is_not_recursive(Is1), + Recipes = trim_recipes(Layout, IsNotRecursive), %% Try the recipes in order. A recipe may not work out because %% a register that was previously killed may be @@ -85,6 +86,21 @@ trim([I|Is], St, Acc) -> trim([], _, Acc) -> reverse(Acc). +%% is_not_recursive([Instruction]) -> true|false. +%% Test whether the next call or apply instruction may +%% do a recursive call. Return `true` if the call is +%% definitely not recursive, and `false` otherwise. +is_not_recursive([{call_ext,_,Ext}|_]) -> + case Ext of + {extfunc,M,F,A} -> + erl_bifs:is_pure(M, F, A); + _ -> + false + end; +is_not_recursive([{block,_}|Is]) -> is_not_recursive(Is); +is_not_recursive([{line,_}|Is]) -> is_not_recursive(Is); +is_not_recursive(_) -> false. + %% trim_recipes([{kill,R}|{live,R}|{dead,R}]) -> [Recipe]. %% Recipe = {Kills,NumberToTrim,Moves} %% Kills = [{kill,Y}] @@ -93,34 +109,34 @@ trim([], _, Acc) -> %% Calculate how to best trim the stack and kill the correct %% Y registers. Return a list of possible recipes. The best %% recipe (the one that trims the most) is first in the list. -%% All of the recipes are no worse in estimated execution time -%% than the original sequences of kill instructions. -trim_recipes(Layout) -> - Cost = length([I || {kill,_}=I <- Layout]), - trim_recipes_1(Layout, 0, [], {Cost,[]}). +trim_recipes(Layout, IsNotRecursive) -> + Recipes = construct_recipes(Layout, 0, [], []), + NumOrigKills = length([I || {kill,_}=I <- Layout]), + IsTooExpensive = is_too_expensive_fun(IsNotRecursive), + [R || R <- Recipes, + not is_too_expensive(R, NumOrigKills, IsTooExpensive)]. -trim_recipes_1([{kill,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) -> +construct_recipes([{kill,{y,Trim0}}|Ks], Trim0, Moves, Acc) -> Trim = Trim0 + 1, - Recipes = save_recipe(Ks, Trim, Moves, Recipes0), - trim_recipes_1(Ks, Trim, Moves, Recipes); -trim_recipes_1([{dead,{y,Trim0}}|Ks], Trim0, Moves, Recipes0) -> + Recipe = {Ks,Trim,Moves}, + construct_recipes(Ks, Trim, Moves, [Recipe|Acc]); +construct_recipes([{dead,{y,Trim0}}|Ks], Trim0, Moves, Acc) -> Trim = Trim0 + 1, - Recipes = save_recipe(Ks, Trim, Moves, Recipes0), - trim_recipes_1(Ks, Trim, Moves, Recipes); -trim_recipes_1([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Recipes0) -> + Recipe = {Ks,Trim,Moves}, + construct_recipes(Ks, Trim, Moves, [Recipe|Acc]); +construct_recipes([{live,{y,Trim0}=Src}|Ks0], Trim0, Moves0, Acc) -> case take_last_dead(Ks0) of none -> - {_,RecipesList} = Recipes0, - RecipesList; + %% No more recipes are possible. + Acc; {Dst,Ks} -> Trim = Trim0 + 1, Moves = [{move,Src,Dst}|Moves0], - Recipes = save_recipe(Ks, Trim, Moves, Recipes0), - trim_recipes_1(Ks, Trim, Moves, Recipes) + Recipe = {Ks,Trim,Moves}, + construct_recipes(Ks, Trim, Moves, [Recipe|Acc]) end; -trim_recipes_1([], _, _, {_,RecipesList}) -> - RecipesList. +construct_recipes([], _, _, Acc) -> Acc. take_last_dead(L) -> take_last_dead_1(reverse(L)). @@ -131,33 +147,47 @@ take_last_dead_1([{dead,Reg}|Is]) -> {Reg,reverse(Is)}; take_last_dead_1(_) -> none. -save_recipe(Ks, Trim, Moves, {MaxCost,Acc}=Recipes) -> - case recipe_cost(Ks, Moves) of - Cost when Cost =< MaxCost -> - %% The price is right. - {MaxCost,[{Ks,Trim,Moves}|Acc]}; - _Cost -> - %% Too expensive. - Recipes +%% Is trimming too expensive? +is_too_expensive({Ks,_,Moves}, NumOrigKills, IsTooExpensive) -> + NumKills = num_kills(Ks, 0), + NumMoves = length(Moves), + IsTooExpensive(NumKills, NumMoves, NumOrigKills). + +num_kills([{kill,_}|T], Acc) -> + num_kills(T, Acc+1); +num_kills([_|T], Acc) -> + num_kills(T, Acc); +num_kills([], Acc) -> Acc. + +is_too_expensive_fun(true) -> + %% This call is not recursive (because it is a call to a BIF). + %% Here we should avoid trimming if the trimming sequence is + %% likely to be more expensive than the original sequence. + fun(NumKills, NumMoves, NumOrigKills) -> + Penalty = + if + %% Slightly penalize the use of any `move` + %% instruction to avoid replacing two `kill` + %% instructions with a `move` and a `trim`. + NumMoves =/= 0 -> 1; + true -> 0 + end, + 1 + Penalty + NumKills + NumMoves > NumOrigKills + end; +is_too_expensive_fun(false) -> + %% This call **may** be recursive. In a recursive function that + %% builds up a huge stack, having unused stack slots will be very + %% expensive. Therefore, we want to be biased towards trimming. + %% We will do that by not counting the `trim` instruction in + %% the formula below. + fun(NumKills, NumMoves, NumOrigKills) -> + NumKills + NumMoves > NumOrigKills end. -recipe_cost(Ks, Moves) -> - %% We estimate that a {move,{y,_},{y,_}} instruction is roughly twice as - %% expensive as a {kill,{y,_}} instruction. A {trim,_} instruction is - %% roughly as expensive as a {kill,{y,_}} instruction. - - recipe_cost_1(Ks, 1+2*length(Moves)). - -recipe_cost_1([{kill,_}|Ks], Cost) -> - recipe_cost_1(Ks, Cost+1); -recipe_cost_1([_|Ks], Cost) -> - recipe_cost_1(Ks, Cost); -recipe_cost_1([], Cost) -> Cost. - %% try_remap([Recipe], [Instruction], FrameSize) -> %% {[Instruction],[TrimInstruction]}. %% Try to renumber Y registers in the instruction stream. The -%% first rececipe that works will be used. +%% first recipe that works will be used. %% %% This function will issue a `not_possible` exception if none %% of the recipes were possible to apply. diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 234f0b7780..92e9fa74e5 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -80,5 +80,5 @@ {registered, []}, {applications, [kernel, stdlib]}, {env, []}, - {runtime_dependencies, ["stdlib-@OTP-15251@","kernel-@OTP-15251@","hipe-3.12","erts-@OTP-15251@", + {runtime_dependencies, ["stdlib-3.13","kernel-7.0","hipe-3.12","erts-11.0", "crypto-3.6"]}]}. diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl index 03cda0c798..28cc0ae268 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -23,13 +23,15 @@ init_per_group/2,end_per_group/2, calls/1,tuple_matching/1,recv/1,maps/1, cover_ssa_dead/1,combine_sw/1,share_opt/1, - beam_ssa_dead_crash/1,stack_init/1,grab_bag/1, - coverage/1]). + beam_ssa_dead_crash/1,stack_init/1, + mapfoldl/0,mapfoldl/1, + grab_bag/1,coverage/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [{group,p}]. + [mapfoldl, + {group,p}]. groups() -> [{p,test_lib:parallel(), @@ -702,6 +704,74 @@ stack_init(Key, Map) -> %% (if the second clause was executed). id(Res). +%% Test that compiler "optimizations" don't rewrite mapfold/3 to the +%% equivalent of slow_mapfoldl/3. +mapfoldl() -> + {N,Size} = mapfoldl_limits(), + {Time,_} = timer:tc(fun() -> + mapfoldl(fun(Sz, _) -> + erlang:garbage_collect(), + {Sz,erlang:make_tuple(Sz, a)} + end, [], [Size]) + end), + Seconds = 15 + ceil(10 * Time * N / 1_000_000), + io:format("~p seconds timetrap\n", [Seconds]), + [{timetrap,{seconds,Seconds}}]. + +mapfoldl(_Config) -> + test_mapfoldl_implementations(), + F = fun(Sz, _) -> + erlang:garbage_collect(), + {Sz,erlang:make_tuple(Sz, a)} + end, + {N,Size} = mapfoldl_limits(), + List = lists:duplicate(N, Size), + {List,Tuple} = mapfoldl(F, [], List), + {List,Tuple} = fast_mapfoldl(F, [], List), + Size = tuple_size(Tuple), + ok. + +mapfoldl_limits() -> + {1_000,100_000}. + +test_mapfoldl_implementations() -> + Seq = lists:seq(1, 10), + F = fun(N, Sum) -> {N,Sum+N} end, + {Seq,55} = mapfoldl(F, 0, Seq), + {Seq,55} = fast_mapfoldl(F, 0, Seq), + {Seq,55} = slow_mapfoldl(F, 0, Seq), + ok. + +mapfoldl(F, Acc0, [Hd|Tail]) -> + {R,Acc1} = F(Hd, Acc0), + {Rs,Acc2} = mapfoldl(F, Acc1, Tail), + {[R|Rs],Acc2}; +mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}. + +%% Here is an illustration of how the compiler used to sink +%% get_tuple_element instructions in a way that would cause all +%% versions of the accumulator to be kept until the end. The compiler +%% now uses a heuristic to only sink get_tuple_element instructions if +%% that would cause fewer values to be saved in the stack frame. +slow_mapfoldl(F, Acc0, [Hd|Tail]) -> + Res1 = F(Hd, Acc0), + %% By saving the Res1 tuple, all intermediate accumulators will be + %% kept to the end. + Res2 = slow_mapfoldl(F, element(2, Res1), Tail), + {[element(1, Res1)|element(1, Res2)],element(2, Res2)}; +slow_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}. + +%% Here is an illustration how the compiler should compile mapfoldl/3 +%% to avoid keeping all intermediate accumulators. Note that +%% slow_mapfoldl/3 and fast_mapfoldl/3 use the same amount of stack +%% space. +fast_mapfoldl(F, Acc0, [Hd|Tail]) -> + Res1 = F(Hd, Acc0), + R = element(1, Res1), + Res2 = fast_mapfoldl(F, element(2, Res1), Tail), + {[R|element(1, Res2)],element(2, Res2)}; +fast_mapfoldl(F, Acc, []) when is_function(F, 2) -> {[],Acc}. + grab_bag(_Config) -> {'EXIT',_} = (catch grab_bag_1()), {'EXIT',_} = (catch grab_bag_2()), diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index 4a53698e15..0558b8f300 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -2300,6 +2300,8 @@ beam_bool_SUITE(_Config) -> in_catch(), recv_semi(), andalso_repeated_var(), + erl1246(), + erl1253(), ok. before_and_inside_if() -> @@ -2597,6 +2599,170 @@ andalso_repeated_var() -> andalso_repeated_var(B) when B andalso B -> ok; andalso_repeated_var(_) -> error. +-record(erl1246, {tran_stat = 0}). + +erl1246() -> + false = erl1246(#erl1246{tran_stat = 0}, #{cid => 1131}), + false = erl1246(#erl1246{tran_stat = 12}, #{cid => 1131}), + false = erl1246(#erl1246{tran_stat = 12}, #{cid => 9502}), + true = erl1246(#erl1246{tran_stat = 0}, #{cid => 9502}), + ok. + +erl1246(Rec, #{cid := CollID}) -> + {GiftCollID, _} = erl1246_conf(gift_coll), + IsTranStat = Rec#erl1246.tran_stat =:= erl1246_conf(transform_id), + if + %% Optimization of 'not' in a guard was broken. + CollID =:= GiftCollID andalso not IsTranStat -> + true; + true -> + false + end. + +erl1246_conf(gift_coll) -> {9502, {112, 45}}; +erl1246_conf(transform_id) -> 12; +erl1246_conf(_) -> undefined. + +erl1253() -> + ok = erl1253_orelse_false(a, a, any), + ok = erl1253_orelse_false(a, a, true), + ok = erl1253_orelse_false(a, a, false), + error = erl1253_orelse_false(a, b, any), + error = erl1253_orelse_false(a, b, true), + ok = erl1253_orelse_false(a, b, false), + + ok = erl1253_orelse_true(a, a, any), + ok = erl1253_orelse_true(a, a, true), + ok = erl1253_orelse_true(a, a, false), + error = erl1253_orelse_true(a, b, any), + ok = erl1253_orelse_true(a, b, true), + error = erl1253_orelse_true(a, b, false), + + error = erl1253_andalso_false(a, a, any), + error = erl1253_andalso_false(a, a, true), + ok = erl1253_andalso_false(a, a, false), + error = erl1253_andalso_false(a, b, any), + error = erl1253_andalso_false(a, b, true), + error = erl1253_andalso_false(a, b, false), + + error = erl1253_andalso_true(a, a, any), + ok = erl1253_andalso_true(a, a, true), + error = erl1253_andalso_true(a, a, false), + error = erl1253_andalso_true(a, b, any), + error = erl1253_andalso_true(a, b, true), + error = erl1253_andalso_true(a, b, false), + + ok. + +erl1253_orelse_false(X, Y, Z) -> + Res = erl1253_orelse_false_1(X, Y, Z), + Res = erl1253_orelse_false_2(X, Y, Z), + Res = erl1253_orelse_false_3(X, Y, Z). + +erl1253_orelse_false_1(X, Y, Z) -> + Bool = Z =:= false, + if + X =:= Y orelse Bool -> ok; + true -> error + end. + +erl1253_orelse_false_2(X, Y, Z) -> + Bool = Z =:= false, + if + Bool orelse X =:= Y -> ok; + true -> error + end. + +erl1253_orelse_false_3(X, Y, Z) -> + Bool1 = X =:= Y, + Bool2 = Z =:= false, + if + Bool1 orelse Bool2 -> ok; + true -> error + end. + +erl1253_orelse_true(X, Y, Z) -> + Res = erl1253_orelse_true_1(X, Y, Z), + Res = erl1253_orelse_true_2(X, Y, Z), + Res = erl1253_orelse_true_3(X, Y, Z). + +erl1253_orelse_true_1(X, Y, Z) -> + Bool = Z =:= true, + if + X =:= Y orelse Bool -> ok; + true -> error + end. + +erl1253_orelse_true_2(X, Y, Z) -> + Bool = Z =:= true, + if + Bool orelse X =:= Y -> ok; + true -> error + end. + +erl1253_orelse_true_3(X, Y, Z) -> + Bool1 = X =:= Y, + Bool2 = Z =:= true, + if + Bool1 orelse Bool2 -> ok; + true -> error + end. + +erl1253_andalso_false(X, Y, Z) -> + Res = erl1253_andalso_false_1(X, Y, Z), + Res = erl1253_andalso_false_2(X, Y, Z), + Res = erl1253_andalso_false_3(X, Y, Z). + +erl1253_andalso_false_1(X, Y, Z) -> + Bool = Z =:= false, + if + X =:= Y andalso Bool -> ok; + true -> error + end. + +erl1253_andalso_false_2(X, Y, Z) -> + Bool1 = X =:= Y, + Bool2 = Z =:= false, + if + Bool1 andalso Bool2 -> ok; + true -> error + end. + +erl1253_andalso_false_3(X, Y, Z) -> + Bool1 = X =:= Y, + Bool2 = Z =:= false, + if + Bool1 andalso Bool2 -> ok; + true -> error + end. + +erl1253_andalso_true(X, Y, Z) -> + Res = erl1253_andalso_true_1(X, Y, Z), + Res = erl1253_andalso_true_2(X, Y, Z), + Res = erl1253_andalso_true_3(X, Y, Z). + +erl1253_andalso_true_1(X, Y, Z) -> + Bool = Z =:= true, + if + X =:= Y andalso Bool -> ok; + true -> error + end. + +erl1253_andalso_true_2(X, Y, Z) -> + Bool = Z =:= true, + if + Bool andalso X =:= Y-> ok; + true -> error + end. + +erl1253_andalso_true_3(X, Y, Z) -> + Bool1 = X =:= Y, + Bool2 = Z =:= true, + if + Bool1 andalso Bool2 -> ok; + true -> error + end. + %%% %%% End of beam_bool_SUITE tests. %%% diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 87d821d7e8..e167c63a7d 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.5.4 +COMPILER_VSN = 7.6.1 diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml index b22b46d5e5..5903226f6e 100644 --- a/lib/crypto/doc/src/notes.xml +++ b/lib/crypto/doc/src/notes.xml @@ -31,6 +31,93 @@ </header> <p>This document describes the changes made to the Crypto application.</p> +<section><title>Crypto 4.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Crypto reported unsupported elliptic curves as supported + on e.g Fedora distros.</p> + <p> + Own Id: OTP-16579 Aux Id: ERL-825 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Support for ed25519 and ed448 added to + <c>crypto:generate_key</c>.</p> + <p> + Own Id: OTP-15967 Aux Id: PR-2329 </p> + </item> + <item> + <p> + The <seeguide marker="crypto:new_api#the-new-api">new + crypto functions api</seeguide> (crypto_init, + crypto_update and crypto_one_time) has been updated.</p> + <p> + There is now a function <seemfa + marker="crypto:crypto#crypto_final/1"><c>crypto_final/1</c></seemfa> + and a possibility to set options in <seemfa + marker="crypto:crypto#crypto_init/3"><c>crypto_init/3</c></seemfa> + and <seemfa + marker="crypto:crypto#crypto_init/4"><c>crypto_init/4</c></seemfa>. + See the manual for details.</p> + <p> + Own Id: OTP-16160</p> + </item> + <item> + <p> + As <seeguide + marker="crypto:notes#crypto-4.5">announced</seeguide> in + OTP 22.0, a New API was introduced in CRYPTO. See the + <seeguide marker="crypto:new_api"><i>New and Old + API</i></seeguide> chapter in the CRYPTO User's Guide for + more information and suggested replacement functions.</p> + <p> + <seeguide marker="crypto:new_api#the-old-api">The Old + API</seeguide> is now deprecated in OTP-23.0 and will be + removed in OTP-24.0.</p> + <p> + This deprecation includes cipher names. See the section + <seeguide + marker="crypto:new_api#retired-cipher-names">Retired + cipher names</seeguide> in the crypto User's Guide, + chapter <seeguide marker="crypto:new_api#the-old-api">The + Old API</seeguide>.</p> + <p> + Own Id: OTP-16232</p> + </item> + <item> + <p> + Fix C-compilation without deprecated OpenSSL cryptolib + APIs</p> + <p> + Own Id: OTP-16369 Aux Id: PR-2474 </p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p> + Added missing 'eddh' to <seemfa + marker="crypto:crypto#supports/1">crypto:supports(public_keys)</seemfa>.</p> + <p> + Own Id: OTP-16583</p> + </item> + </list> + </section> + +</section> + <section><title>Crypto 4.6.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index ca72601bef..df830b32f6 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -135,6 +135,7 @@ groups() -> {group, dh}, {group, ecdh}, + {group, eddh}, {group, srp}, {group, chacha20_poly1305}, @@ -217,7 +218,7 @@ groups() -> {dss, [], [sign_verify %% Does not work yet: ,public_encrypt, private_encrypt ]}, - {ecdsa, [], [sign_verify + {ecdsa, [], [sign_verify, use_all_ec_sign_verify %% Does not work yet: ,public_encrypt, private_encrypt ]}, {ed25519, [], [sign_verify, @@ -229,7 +230,8 @@ groups() -> generate ]}, {dh, [], [generate_compute, compute_bug]}, - {ecdh, [], [use_all_elliptic_curves, compute, generate]}, + {ecdh, [], [compute, generate, use_all_ecdh_generate_compute]}, + {eddh, [], [compute, generate, use_all_eddh_generate_compute]}, {srp, [], [generate_compute]}, {des_cbc, [], [block, api_ng, api_ng_one_shot, api_ng_tls]}, {des_cfb, [], [block, api_ng, api_ng_one_shot, api_ng_tls]}, @@ -330,7 +332,7 @@ init_per_suite(Config) -> {ok, _} = zip:unzip("cmactestvectors.zip"), {ok, _} = zip:unzip("gcmtestvectors.zip"), - try crypto:start() of + try is_ok(crypto:start()) of ok -> catch ct:comment("~s",[element(3,hd(crypto:info_lib()))]), catch ct:log("crypto:info_lib() -> ~p~n" @@ -353,10 +355,18 @@ init_per_suite(Config) -> crypto:rand_seed(<< <<Bin/binary>> || _ <- lists:seq(1,16) >>), Config end - catch _:_ -> + + catch C:E:S -> + ct:log("~p ~p~n~p", [C,E,S]), {fail, "Crypto did not start"} end. +is_ok(ok) -> ok; +is_ok({error, already_started}) -> ok; +is_ok({error,{already_started,crypto}}) -> ok. + + + end_per_suite(_Config) -> application:stop(crypto). @@ -471,8 +481,7 @@ hmac() -> [{doc, "Test hmac function"}]. hmac(Config) when is_list(Config) -> Tuples = lazy_eval(proplists:get_value(hmac, Config)), - lists:foreach(fun hmac_check/1, Tuples), - lists:foreach(fun hmac_check/1, mac_listify(Tuples)). + do_cipher_tests(fun hmac_check/1, Tuples++mac_listify(Tuples)). %%-------------------------------------------------------------------- no_hmac() -> @@ -500,8 +509,7 @@ cmac() -> [{doc, "Test all different cmac functions"}]. cmac(Config) when is_list(Config) -> Pairs = lazy_eval(proplists:get_value(cmac, Config)), - lists:foreach(fun cmac_check/1, Pairs), - lists:foreach(fun cmac_check/1, mac_listify(Pairs)). + do_cipher_tests(fun cmac_check/1, Pairs ++ mac_listify(Pairs)). %%-------------------------------------------------------------------- poly1305() -> @@ -531,8 +539,7 @@ block() -> [{doc, "Test block ciphers"}]. block(Config) when is_list(Config) -> [_|_] = Blocks = lazy_eval(proplists:get_value(cipher, Config)), - lists:foreach(fun block_cipher/1, Blocks), - lists:foreach(fun block_cipher/1, block_iolistify(Blocks)), + do_cipher_tests(fun block_cipher/1, Blocks++block_iolistify(Blocks)), lists:foreach(fun block_cipher_increment/1, block_iolistify(Blocks)). %%-------------------------------------------------------------------- @@ -604,7 +611,7 @@ api_ng_cipher_increment_loop(Ref, InTexts) -> Bin catch error:Error -> - ct:pal("Txt = ~p",[Txt]), + ct:log("Txt = ~p",[Txt]), ct:fail("~p",[Error]) end end, InTexts). @@ -676,17 +683,16 @@ api_ng_tls(Config) when is_list(Config) -> lists:foreach(fun do_api_ng_tls/1, Ciphers). -do_api_ng_tls({Type, Key, PlainTexts}=_X) -> - ct:log("~p",[_X]), +do_api_ng_tls({Type, Key, PlainTexts}) -> do_api_ng_tls({Type, Key, <<>>, PlainTexts}); -do_api_ng_tls({Type, Key, IV, PlainTexts}=_X) -> - ct:log("~p",[_X]), +do_api_ng_tls({Type, Key, IV, PlainTexts}) -> do_api_ng_tls({Type, Key, IV, PlainTexts, undefined}); -do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}=_X) -> - ct:log("~p",[_X]), +do_api_ng_tls({Type, Key, IV, PlainText0, ExpectedEncText}) -> PlainText = iolist_to_binary(lazy_eval(PlainText0)), + ct:log("Type = ~p~nKey = ~p~nIV = ~p~nPlainText = ~p~nExpectedEncText = ~p", + [Type, Key, IV, PlainText, ExpectedEncText]), Renc = crypto:crypto_dyn_iv_init(Type, Key, true), Rdec = crypto:crypto_dyn_iv_init(Type, Key, false), EncTxt = crypto:crypto_dyn_iv_update(Renc, PlainText, IV), @@ -832,8 +838,6 @@ no_stream_ivec(Config) when is_list(Config) -> notsup(fun crypto:stream_init/3, [Type, <<"Key">>, <<"Ivec">>]). %%-------------------------------------------------------------------- -aead() -> - [{doc, "Test AEAD ciphers"}]. aead(Config) when is_list(Config) -> [_|_] = AEADs = lazy_eval(proplists:get_value(cipher, Config)), FilteredAEADs = @@ -848,7 +852,7 @@ aead(Config) when is_list(Config) -> IVLen >= 12 end, AEADs) end, - lists:foreach(fun aead_cipher/1, FilteredAEADs). + do_cipher_tests(fun aead_cipher/1, FilteredAEADs). %%-------------------------------------------------------------------- aead_ng(Config) when is_list(Config) -> @@ -865,7 +869,7 @@ aead_ng(Config) when is_list(Config) -> IVLen >= 12 end, AEADs) end, - lists:foreach(fun aead_cipher_ng/1, FilteredAEADs ++ spec_0_bytes(Config)). + do_cipher_tests(fun aead_cipher_ng/1, FilteredAEADs ++ spec_0_bytes(Config)). %%-------------------------------------------------------------------- aead_bad_tag(Config) -> @@ -882,7 +886,7 @@ aead_bad_tag(Config) -> IVLen >= 12 end, AEADs) end, - lists:foreach(fun aead_cipher_bad_tag/1, FilteredAEADs). + do_cipher_tests(fun aead_cipher_bad_tag/1, FilteredAEADs). %%-------------------------------------------------------------------- sign_verify() -> @@ -902,6 +906,7 @@ no_sign_verify(Config) when is_list(Config) -> public_encrypt() -> [{doc, "Test public_encrypt/decrypt "}]. public_encrypt(Config) when is_list(Config) -> + ct:log("public_encrypt", []), Params = proplists:get_value(pub_pub_encrypt, Config, []), lists:foreach(fun do_public_encrypt/1, Params). @@ -961,9 +966,7 @@ compute(Config) when is_list(Config) -> Gen = proplists:get_value(compute, Config), lists:foreach(fun do_compute/1, Gen). %%-------------------------------------------------------------------- -use_all_elliptic_curves() -> - [{doc, " Test that all curves from crypto:ec_curves/0"}]. -use_all_elliptic_curves(_Config) -> +use_all_ec_sign_verify(_Config) -> Msg = <<"hello world!">>, Sups = crypto:supports(), Curves = proplists:get_value(curves, Sups), @@ -975,6 +978,7 @@ use_all_elliptic_curves(_Config) -> Results = [{{Curve,Hash}, try + ct:log("~p ~p",[Curve,Hash]), {Pub,Priv} = crypto:generate_key(ecdh, Curve), true = is_binary(Pub), true = is_binary(Priv), @@ -1000,6 +1004,57 @@ use_all_elliptic_curves(_Config) -> end. %%-------------------------------------------------------------------- +use_all_ecdh_generate_compute(Config) -> + Curves = crypto:supports(curves) -- [ed25519, ed448, x25519, x448], + do_dh_curves(Config, Curves). + +use_all_eddh_generate_compute(Config) -> + AllCurves = crypto:supports(curves), + Curves = [C || C <- [x25519, x448], + lists:member(C, AllCurves)], + do_dh_curves(Config, Curves). + +do_dh_curves(_Config, Curves) -> + ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~n", [crypto:info_lib(), + crypto:info_fips(), + Curves]), + Results = + [{Curve, + try + ct:log("~p",[Curve]), + {APub,APriv} = crypto:generate_key(ecdh, Curve), + {BPub,BPriv} = crypto:generate_key(ecdh, Curve), + true = is_binary(APub), + true = is_binary(APriv), + true = is_binary(BPub), + true = is_binary(BPriv), + + ACommonSecret = crypto:compute_key(ecdh, BPub, APriv, Curve), + BCommonSecret = crypto:compute_key(ecdh, APub, BPriv, Curve), + ACommonSecret == BCommonSecret + catch + C:E -> + {C,E} + end} + || Curve <- Curves + ], + + Fails = + lists:filter(fun({_,true}) -> false; + (_) -> true + end, Results), + + case Fails of + [] -> + ct:comment("All ~p passed",[length(Results)]), + ok; + _ -> + ct:comment("passed: ~p, failed: ~p",[length(Results),length(Fails)]), + ct:log("Fails:~n~p",[Fails]), + ct:fail("Bad curve(s)",[]) + end. + +%%-------------------------------------------------------------------- generate() -> [{doc, " Test crypto:generate_key"}]. generate(Config) when is_list(Config) -> @@ -1065,13 +1120,14 @@ cipher_info(Config) when is_list(Config) -> of _ -> Ok catch Cls:Exc -> - ct:pal("~p:~p ~p",[Cls,Exc,C]), + ct:log("~p:~p ~p",[Cls,Exc,C]), false end end, true, -crypto:supports(ciphers)) of -%% proplists:get_value(ciphers, crypto:supports())) of + crypto:supports(ciphers) + ) + of true -> ok; false -> @@ -1175,76 +1231,49 @@ hmac_increment(State0, [Increment | Rest]) -> hmac_increment(State, Rest). %%%---------------------------------------------------------------- -cmac_check({cmac, Type, Key, Text, CMac}) -> +cmac_check({cmac, Type, Key, Text, CMac}=T) -> ExpCMac = iolist_to_binary(CMac), - case crypto:cmac(Type, Key, Text) of - ExpCMac -> - ok; - Other -> - ct:fail({{crypto, cmac, [Type, Key, Text]}, {expected, ExpCMac}, {got, Other}}) - end; -cmac_check({cmac, Type, Key, Text, Size, CMac}) -> + cipher_test(T, + fun() -> crypto:cmac(Type, Key, Text) end, + ExpCMac); +cmac_check({cmac, Type, Key, Text, Size, CMac}=T) -> ExpCMac = iolist_to_binary(CMac), - case crypto:cmac(Type, Key, Text, Size) of - ExpCMac -> - ok; - Other -> - ct:fail({{crypto, cmac, [Type, Key, Text, Size]}, {expected, ExpCMac}, {got, Other}}) - end. - + cipher_test(T, + fun() -> crypto:cmac(Type, Key, Text, Size) end, + ExpCMac). -mac_check({MacType, SubType, Key, Text, Mac}) -> +mac_check({MacType, SubType, Key, Text, Mac}=T) -> ExpMac = iolist_to_binary(Mac), - case crypto:mac(MacType, SubType, Key, Text) of - ExpMac -> - ok; - Other -> - ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}}) - end; -mac_check({MacType, SubType, Key, Text, Size, Mac}) -> + cipher_test(T, + fun() -> crypto:mac(MacType, SubType, Key, Text) end, + ExpMac); +mac_check({MacType, SubType, Key, Text, Size, Mac}=T) -> ExpMac = iolist_to_binary(Mac), - case crypto:mac(MacType, SubType, Key, Text, Size) of - ExpMac -> - ok; - Other -> - ct:fail({{crypto, mac, [MacType, SubType, Key, Text]}, {expected, ExpMac}, {got, Other}}) - end. + cipher_test(T, + fun() -> crypto:mac(MacType, SubType, Key, Text, Size) end, + ExpMac). - -block_cipher({Type, Key, PlainText}) -> +block_cipher({Type, Key, PlainText}=T) -> Plain = iolist_to_binary(PlainText), CipherText = crypto:block_encrypt(Type, Key, PlainText), - case crypto:block_decrypt(Type, Key, CipherText) of - Plain -> - ok; - Other -> - ct:fail({{crypto, block_decrypt, [Type, Key, CipherText]}, {expected, Plain}, {got, Other}}) - end; + cipher_test(T, + fun() -> crypto:block_decrypt(Type, Key, CipherText) end, + Plain); -block_cipher({Type, Key, IV, PlainText}) -> +block_cipher({Type, Key, IV, PlainText}=T) -> Plain = iolist_to_binary(PlainText), CipherText = crypto:block_encrypt(Type, Key, IV, PlainText), - case crypto:block_decrypt(Type, Key, IV, CipherText) of - Plain -> - ok; - Other -> - ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other}}) - end; + cipher_test(T, + fun() -> crypto:block_decrypt(Type, Key, IV, CipherText) end, + Plain); -block_cipher({Type, Key, IV, PlainText, CipherText}) -> +block_cipher({Type, Key, IV, PlainText, CipherText}=T) -> Plain = iolist_to_binary(PlainText), - case crypto:block_encrypt(Type, Key, IV, Plain) of - CipherText -> - ok; - Other0 -> - ct:fail({{crypto, block_encrypt, [Type, Key, IV, Plain]}, {expected, CipherText}, {got, Other0}}) - end, - case crypto:block_decrypt(Type, Key, IV, CipherText) of - Plain -> - ok; - Other1 -> - ct:fail({{crypto, block_decrypt, [Type, Key, IV, CipherText]}, {expected, Plain}, {got, Other1}}) - end. + cipher_test(T, + fun() -> crypto:block_encrypt(Type, Key, IV, Plain) end, + CipherText, + fun() -> crypto:block_decrypt(Type, Key, IV, CipherText) end, + Plain). block_cipher_increment({Type, Key, IV, PlainTexts}) when Type == des_cbc ; Type == des3_cbc ; @@ -1365,124 +1394,99 @@ stream_cipher_incment_loop(State0, OrigState, [PlainText | PlainTexts], Acc, Pla {State, CipherText} = crypto:stream_encrypt(State0, PlainText), stream_cipher_incment_loop(State, OrigState, PlainTexts, [CipherText | Acc], Plain). -aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) -> +aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) -> Plain = iolist_to_binary(PlainText), - case crypto:block_encrypt(Type, Key, IV, {AAD, Plain}) of - {CipherText, CipherTag} -> - ok; - Other0 -> - ct:fail({{crypto, - block_encrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]}, - {expected, {CipherText, CipherTag}}, - {got, Other0}}) - end, - case crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, CipherTag}) of - Plain -> - ok; - Other1 -> - ct:fail({{crypto, - block_decrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]}, - {expected, Plain}, - {got, Other1}}) - end; -aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) -> + cipher_test(T, + fun() -> crypto:block_encrypt(Type, Key, IV, {AAD, Plain}) end, + {CipherText, CipherTag}, + fun() -> crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, CipherTag}) end, + Plain); +aead_cipher({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) -> <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag, Plain = iolist_to_binary(PlainText), - try crypto:block_encrypt(Type, Key, IV, {AAD, Plain, TagLen}) of - {CipherText, TruncatedCipherTag} -> - ok; - Other0 -> - ct:fail({{crypto, - block_encrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, {taglen,TagLen}]}, - {expected, {CipherText, TruncatedCipherTag}}, - {got, Other0}}) - catch - error:E -> - ct:log("~p",[{Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}]), - try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) - of - RR -> - ct:log("Works: ~p",[RR]) - catch - CC:EE -> - ct:log("~p:~p", [CC,EE]) - end, - ct:fail("~p",[E]) - end, - case crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, TruncatedCipherTag}) of - Plain -> - ok; - Other1 -> - ct:fail({{crypto, - block_decrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, - {truncated,TruncatedCipherTag}]}, - {expected, Plain}, - {got, Other1}}) - end. + cipher_test(T, + fun() -> crypto:block_encrypt(Type, Key, IV, {AAD, Plain, TagLen}) end, + {CipherText, TruncatedCipherTag}, + fun() -> crypto:block_decrypt(Type, Key, IV, {AAD, CipherText, TruncatedCipherTag}) end, + Plain). -aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) -> +aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) -> Plain = iolist_to_binary(PlainText), - case crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, true) of - {CipherText, CipherTag} -> - ok; - Other0 -> - ct:fail({{crypto, - block_encrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]}, - {expected, {CipherText, CipherTag}}, - {got, Other0}}) - end, - case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, CipherTag, false) of - Plain -> - ok; - Other1 -> - ct:fail({{crypto, - block_decrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}]}, - {expected, Plain}, - {got, Other1}}) - end; -aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) -> + cipher_test(T, + fun() -> crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, true) end, + {CipherText, CipherTag}, + fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, CipherTag, false) end, + Plain); +aead_cipher_ng({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) -> <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag, Plain = iolist_to_binary(PlainText), - try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) of - {CipherText, TruncatedCipherTag} -> - ok; - Other0 -> - ct:fail({{crypto, - block_encrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, {taglen,TagLen}]}, - {expected, {CipherText, TruncatedCipherTag}}, - {got, Other0}}) + cipher_test(T, + fun() -> crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) end, + {CipherText, TruncatedCipherTag}, + fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, TruncatedCipherTag, false) end, + Plain). + +aead_cipher_bad_tag({Type, Key, _PlainText, IV, AAD, CipherText, CipherTag, _Info}=T) -> + BadTag = mk_bad_tag(CipherTag), + cipher_test(T, + fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTag, false) end, + error); +aead_cipher_bad_tag({Type, Key, _PlainText, IV, AAD, CipherText, CipherTag, TagLen, _Info}=T) -> + <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag, + BadTruncatedTag = mk_bad_tag(TruncatedCipherTag), + cipher_test(T, + fun() -> crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTruncatedTag, false) end, + error). + + +cipher_test(T, Fe, Ee, Fd, Ed) -> + %% Test encrypt + Re = cipher_test(encrypt, T, Fe, Ee), + %% Test decrypt + Rd = cipher_test(decrypt, T, Fd, Ed), + case {Re, Rd} of + {ok,ok} -> ok; + {ok,_} -> Rd; + {_,ok} -> Re; + _ -> {Re,Rd} + end. + +cipher_test(T, F, E) -> + cipher_test(notag, T, F, E). + +cipher_test(Tag, T, F, E) -> + try F() of + E -> ok; + Other -> {other, {Tag,T,Other}} catch - error:E -> - ct:log("~p",[{Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}]), - try crypto:crypto_one_time_aead(Type, Key, IV, PlainText, AAD, TagLen, true) - of - RR -> - ct:log("Works: ~p",[RR]) - catch - CC:EE -> - ct:log("~p:~p", [CC,EE]) - end, - ct:fail("~p",[E]) - end, - case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, TruncatedCipherTag, false) of - Plain -> - ok; - Other1 -> - ct:fail({{crypto, - block_decrypt, - [{info,Info}, {key,Key}, {pt,PlainText}, {iv,IV}, {aad,AAD}, {ct,CipherText}, {tag,CipherTag}, - {truncated,TruncatedCipherTag}]}, - {expected, Plain}, - {got, Other1}}) + error:Error -> {error, {Tag,T,Error}} + end. + +do_cipher_tests(F, TestVectors) when is_function(F,1) -> + {Passed,Failed} = + lists:partition( + fun(R) -> R == ok end, + lists:map(F, TestVectors) + ), + BothFailed = lists:filter(fun({ok,_}) -> false; + ({_,ok}) -> false; + (ok) -> false; + (_) -> true + end, + Failed), + ct:log("Passed: ~p, BothFailed: ~p OnlyOneFailed: ~p", + [length(Passed), length(BothFailed), length(Failed)-length(BothFailed)]), + case Failed of + [] -> + ct:comment("All ~p passed", [length(Passed)]); + _ -> + ct:log("~p",[hd(Failed)]), + ct:comment("Passed: ~p, BothFailed: ~p OnlyOneFailed: ~p", + [length(Passed), length(BothFailed), length(Failed)-length(BothFailed)]), + ct:fail("Failed", []) end. + mk_bad_tag(CipherTag) -> case <<0:(size(CipherTag))/unit:8>> of CipherTag -> % The correct tag may happen to be a suite of zeroes @@ -1491,30 +1495,6 @@ mk_bad_tag(CipherTag) -> X end. -aead_cipher_bad_tag({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, Info}) -> - Plain = iolist_to_binary(PlainText), - BadTag = mk_bad_tag(CipherTag), - case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTag, false) of - error -> - ok; - Plain -> - ct:log("~p:~p~n info: ~p~n key: ~p~n pt: ~p~n iv: ~p~n aad: ~p~n ct: ~p~n tag: ~p~n bad tag: ~p~n", - [?MODULE,?LINE,Info, Key, PlainText, IV, AAD, CipherText, CipherTag, BadTag]), - ct:fail("Didn't fail on bad tag") - end; -aead_cipher_bad_tag({Type, Key, PlainText, IV, AAD, CipherText, CipherTag, TagLen, Info}) -> - Plain = iolist_to_binary(PlainText), - <<TruncatedCipherTag:TagLen/binary, _/binary>> = CipherTag, - BadTruncatedTag = mk_bad_tag(TruncatedCipherTag), - case crypto:crypto_one_time_aead(Type, Key, IV, CipherText, AAD, BadTruncatedTag, false) of - error -> - ok; - Plain -> - ct:log("~p:~p~n info: ~p~n key: ~p~n pt: ~p~n iv: ~p~n aad: ~p~n ct: ~p~n tag: ~p~n bad tag: ~p~n", - [Info, Key, PlainText, IV, AAD, CipherText, TruncatedCipherTag, BadTruncatedTag]), - ct:fail("Didn't fail on bad tag") - end. - do_sign_verify({Type, undefined=Hash, Private, Public, Msg, Signature}) -> case crypto:sign(eddsa, Hash, Msg, [Private,Type]) of Signature -> @@ -1598,45 +1578,65 @@ negative_verify(Type, Hash, Msg, Signature, Public, Options) -> end. do_public_encrypt({Type, Public, Private, Msg, Padding}) -> + ct:log("do_public_encrypt Type=~p, Padding=~p,~nPublic = ~p,~nPrivate = ~p,~nMsg = ~p.", + [Type, Padding, Public, Private, Msg]), + timer:sleep(100), try crypto:public_encrypt(Type, Msg, Public, Padding) of PublicEcn -> + ct:log("private_decrypt~nPublicEcn = ~p.", [PublicEcn]), + timer:sleep(100), try crypto:private_decrypt(Type, PublicEcn, Private, Padding) of Msg -> + ct:log("~p:~p ok", [?MODULE,?LINE]), + timer:sleep(100), ok; Other -> + ct:log("~p:~p Other = ~p", [?MODULE,?LINE,Other]), + timer:sleep(100), ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, Other}}) catch CC:EE -> + ct:log("~p:~p EXC. ~p:~p", [?MODULE,?LINE,CC,EE]), + timer:sleep(100), ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, {CC,EE}}}) end catch CC:EE -> + ct:log("~p:~p EXC 2. ~p:~p", [?MODULE,?LINE,CC,EE]), + timer:sleep(100), ct:fail({{crypto, public_encrypt, [Type, Msg, Public, Padding]}, {got, {CC,EE}}}) end. do_private_encrypt({Type, Public, Private, Msg, Padding}) -> + ct:log("do_private_encrypt Type=~p, Padding=~p,~nPublic = ~p,~nPrivate = ~p,~nMsg = ~p.", + [Type, Padding, Public, Private, Msg]), try crypto:private_encrypt(Type, Msg, Private, Padding) of PrivEcn -> try + ct:log("public_decrypt~nPrivEcn = ~p.", [PrivEcn]), crypto:public_decrypt(Type, PrivEcn, Public, Padding) of Msg -> + ct:log("~p:~p ok", [?MODULE,?LINE]), ok; Other -> + ct:log("~p:~p Other = ~p", [?MODULE,?LINE,Other]), ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, Other}}) catch CC:EE -> + ct:log("~p:~p EXC. ~p:~p", [?MODULE,?LINE,CC,EE]), ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, {CC,EE}}}) end catch CC:EE -> + ct:log("~p:~p EXC 2. ~p:~p", [?MODULE,?LINE,CC,EE]), ct:fail({{crypto, private_encrypt, [Type, Msg, Private, Padding]}, {got, {CC,EE}}}) end. @@ -1648,6 +1648,9 @@ do_generate_compute({srp = Type, UserPrivate, UserGenParams, UserComParams, UserComParams), SessionKey = crypto:compute_key(Type, UserPublic, {HostPublic, HostPrivate}, HostComParam); + + + do_generate_compute({dh, P, G}) -> {UserPub, UserPriv} = crypto:generate_key(dh, [P, G]), {HostPub, HostPriv} = crypto:generate_key(dh, [P, G]), @@ -1655,6 +1658,7 @@ do_generate_compute({dh, P, G}) -> SharedSecret = crypto:compute_key(dh, UserPub, HostPriv, [P, G]). do_compute({ecdh = Type, Pub, Priv, Curve, SharedSecret}) -> + ct:log("~p ~p", [Type,Curve]), Secret = crypto:compute_key(Type, Pub, Priv, Curve), case Secret of SharedSecret -> @@ -1664,6 +1668,7 @@ do_compute({ecdh = Type, Pub, Priv, Curve, SharedSecret}) -> end. do_generate({Type, Curve, Priv, Pub}) when Type == ecdh ; Type == eddsa -> + ct:log("~p ~p", [Type,Curve]), case crypto:generate_key(Type, Curve, Priv) of {Pub, _} -> ok; @@ -1671,6 +1676,7 @@ do_generate({Type, Curve, Priv, Pub}) when Type == ecdh ; Type == eddsa -> ct:fail({{crypto, generate_key, [Type, Priv, Curve]}, {expected, Pub}, {got, Other}}) end; do_generate({rsa = Type, Mod, Exp}) -> + ct:log("~p", [Type]), case crypto:info_fips() of enabled when Mod < 3072 -> ct:log("SKIP do_generate ~p FIPS=~p, Mod=~p Exp=~p", [Type, enabled, Mod, Exp]), @@ -2101,6 +2107,8 @@ group_config(ecdh, Config) -> Compute = ecdh(), Generate = ecc(), [{compute, Compute}, {generate, Generate} | Config]; +group_config(eddh, Config) -> + [{compute, []}, {generate, []} | Config]; group_config(dh, Config) -> GenerateCompute = [dh()], [{generate_compute, GenerateCompute} | Config]; @@ -2239,12 +2247,13 @@ gen_rsa_sign_verify_tests(Hashs, Msg, Public, Private, Opts) -> gen_rsa_pub_priv_tests(Public, Private, Msg, OptsToTry) -> - SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []), + SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []) -- + [rsa_x931_padding], lists:foldr(fun(Opt, Acc) -> case rsa_opt_is_supported(Opt, SupOpts) of true -> [{rsa, Public, Private, Msg, Opt} | Acc]; - false -> + false -> Acc end end, [], OptsToTry). @@ -3975,11 +3984,13 @@ eddsa(ed448) -> ecdh() -> %% http://csrc.nist.gov/groups/STM/cavp/ - Curves = crypto:ec_curves() ++ - [X || X <- proplists:get_value(curves, crypto:supports(), []), - lists:member(X, [x25519,x448])], - TestCases = - [{ecdh, hexstr2point("42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0", "dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523"), + Curves = crypto:supports(curves), + lists:filter( + fun ({_Type, _Pub, _Priv, Curve, _SharedSecret}) -> + lists:member(Curve, Curves) + end, + + [{ecdh, hexstr2point("42ea6dd9969dd2a61fea1aac7f8e98edcc896c6e55857cc0", "dfbe5d7c61fac88b11811bde328e8a0d12bf01a9d204b523"), hexstr2bin("f17d3fea367b74d340851ca4270dcb24c271f445bed9d527"), secp192r1, hexstr2bin("803d8ab2e5b6e6fca715737c3a82f7ce3c783124f6d51cd0")}, @@ -4085,11 +4096,8 @@ ecdh() -> 16#9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b, x448, hexstr2bin("07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d")} - ], - lists:filter(fun ({_Type, _Pub, _Priv, Curve, _SharedSecret}) -> - lists:member(Curve, Curves) - end, - TestCases). + ] + ). dh() -> {dh, 90970053988169282502023478715631717259407236400413906591937635666709823903223997309250405131675572047545403771567755831138144089197560332757755059848492919215391041119286178688014693040542889497092308638580104031455627238700168892909539193174537248629499995652186913900511641708112112482297874449292467498403, 2}. @@ -4143,8 +4151,11 @@ ecc() -> %% information about the curves see %% http://csrc.nist.gov/encryption/dss/ecdsa/NISTReCur.pdf %% - Curves = crypto:ec_curves(), - TestCases = + Curves = crypto:supports(curves), + lists:filter( + fun ({_Type, Curve, _Priv, _Pub}) -> + lists:member(Curve, Curves) + end, [{ecdh,secp192r1,1, hexstr2point("188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811")}, @@ -4174,12 +4185,9 @@ ecc() -> hexstr2bin("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")}, {ecdh, x25519, hexstr2bin("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"), - hexstr2bin("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")}], - lists:filter(fun ({_Type, Curve, _Priv, _Pub}) -> - lists:member(Curve, Curves) - end, - TestCases). - + hexstr2bin("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")} + ] + ). int_to_bin(X) when X < 0 -> int_to_bin_neg(X, []); int_to_bin(X) -> int_to_bin_pos(X, []). diff --git a/lib/crypto/test/crypto_property_test_SUITE.erl b/lib/crypto/test/crypto_property_test_SUITE.erl index bf137363e8..9c958007c7 100644 --- a/lib/crypto/test/crypto_property_test_SUITE.erl +++ b/lib/crypto/test/crypto_property_test_SUITE.erl @@ -35,6 +35,7 @@ init_per_suite(Config) -> try crypto:start() of ok -> true; {error, already_started} -> true; + {error,{already_started,crypto}} -> true; _ -> false catch _:_ -> false diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk index 72f3b9b792..477280d84b 100644 --- a/lib/crypto/vsn.mk +++ b/lib/crypto/vsn.mk @@ -1 +1 @@ -CRYPTO_VSN = 4.6.5 +CRYPTO_VSN = 4.7 diff --git a/lib/debugger/doc/src/notes.xml b/lib/debugger/doc/src/notes.xml index 64af47a4fb..5e3f2ce878 100644 --- a/lib/debugger/doc/src/notes.xml +++ b/lib/debugger/doc/src/notes.xml @@ -33,6 +33,43 @@ <p>This document describes the changes made to the Debugger application.</p> +<section><title>Debugger 5.0</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>EEP-52 has been implemented.</p> + <p>In binary matching, the size of the segment to be + matched is now allowed to be a guard expression, and + similarly in map matching the keys can now be guard + expressions. See the Erlang Reference Manual and + Programming Examples for more details.</p> + <p>Language compilers or code generators that generate + Core Erlang code may need to be updated to be compatible + with the compiler in OTP 23. For more details, see the + section Backwards Compatibility in <url + href="http://erlang.org/eeps/eep-0052.html">EEP + 52</url>.</p> + <p> + Own Id: OTP-14708</p> + </item> + <item> + <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now + returns an empty list instead of a stacktrace. To + retrieve the stacktrace, use the extended try/catch + syntax that was introduced in OTP 21. + <c>erlang:get_stacktrace/0</c> is scheduled for removal + in OTP 24.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16484</p> + </item> + </list> + </section> + +</section> + <section><title>Debugger 4.2.8</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/debugger/vsn.mk b/lib/debugger/vsn.mk index 06fc743270..8e334a00f5 100644 --- a/lib/debugger/vsn.mk +++ b/lib/debugger/vsn.mk @@ -1 +1 @@ -DEBUGGER_VSN = 4.2.8 +DEBUGGER_VSN = 5.0 diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index b5e6ffb485..675a2b43ef 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -32,6 +32,20 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 4.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> Improve handling of <c>maps:remove/2</c>. </p> + <p> + Own Id: OTP-16055 Aux Id: ERL-1002 </p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 4.1.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index ee680f3bcf..b5a3bbf2b4 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 4.1.1 +DIALYZER_VSN = 4.2 diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml index 48bc5d9c74..2871a09476 100644 --- a/lib/edoc/doc/src/notes.xml +++ b/lib/edoc/doc/src/notes.xml @@ -32,6 +32,32 @@ <p>This document describes the changes made to the EDoc application.</p> +<section><title>Edoc 0.12</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> Remove Inets dependency from EDoc. </p> + <p> + Own Id: OTP-15999 Aux Id: PR-2317 </p> + </item> + <item> + <p> + Add support for overloaded Erlang specifications.</p> + <p> + Own Id: OTP-16407 Aux Id: PR-2430 </p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + </list> + </section> + +</section> + <section><title>Edoc 0.11</title> <section><title>Improvements and New Features</title> diff --git a/lib/edoc/src/edoc_data.erl b/lib/edoc/src/edoc_data.erl index ed81c9f4c3..3075e47942 100644 --- a/lib/edoc/src/edoc_data.erl +++ b/lib/edoc/src/edoc_data.erl @@ -230,9 +230,11 @@ callback({N, A}, _Env, _Opts) -> %% <!ELEMENT throws (type, localdef*)> %% <!ELEMENT equiv (expr, see?)> %% <!ELEMENT expr (#PCDATA)> - -function({N, A}, As, Export, Ts, Env, Opts) -> - {Args, Ret, Spec} = signature(Ts, As, Env), +function({N, A}, []=As, Export, Ts, Env, Opts)-> + function({N, A}, [As], Export, Ts, Env, Opts); +function({N, A}, [HAs | _]=As, Export, Ts, Env, Opts) when not is_list(HAs) -> + function({N, A}, [As], Export, Ts, Env, Opts); +function({N, A}, As0, Export, Ts, Env, Opts) -> {function, [{name, atom_to_list(N)}, {arity, integer_to_list(A)}, {exported, case Export of @@ -240,13 +242,8 @@ function({N, A}, As, Export, Ts, Env, Opts) -> false -> "no" end}, {label, edoc_refs:to_label(edoc_refs:function(N, A))}], - [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)} - || {A, D} <- Args]}] - ++ Spec - ++ case Ret of - [] -> []; - _ -> [{returns, description(Ret)}] - end + lists:append([get_args(lists:nth(Clause, As0), Ts, Clause, Env) + || Clause <- lists:seq(1, length(As0))]) ++ get_throws(Ts, Env) ++ get_equiv(Ts, Env) ++ get_doc(Ts) @@ -256,6 +253,16 @@ function({N, A}, As, Export, Ts, Env, Opts) -> ++ todos(Ts, Opts) }. +get_args(As, Ts, Clause, Env) -> + {Args, Ret, Spec} = signature(Ts, As, Clause, Env), + [{args, [{arg, [{argName, [atom_to_list(A)]}] ++ description(D)} + || {A, D} <- Args]}] + ++ Spec + ++ case Ret of + [] -> []; + _ -> [{returns, description(Ret)}] + end. + get_throws(Ts, Env) -> case get_tags(throws, Ts) of [Throws] -> @@ -406,10 +413,10 @@ todos(Tags, Opts) -> [] end. -signature(Ts, As, Env) -> +signature(Ts, As, Clause, Env) -> case get_tags(spec, Ts) of [T] -> - Spec = T#tag.data, + Spec = maybe_nth(Clause, T#tag.data), R = merge_returns(Spec, Ts), As0 = edoc_types:arg_names(Spec), Ds0 = edoc_types:arg_descs(Spec), @@ -424,6 +431,11 @@ signature(Ts, As, Env) -> {[{A, ""} || A <- fix_argnames(As, S, 1)], [], []} end. +maybe_nth(N, List) when is_list(List) -> + lists:nth(N, List); +maybe_nth(1, Other) -> + Other. + params(Ts) -> [T#tag.data || T <- get_tags(param, Ts)]. diff --git a/lib/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl index 390851e9ef..f7e2c28b6f 100644 --- a/lib/edoc/src/edoc_extract.erl +++ b/lib/edoc/src/edoc_extract.erl @@ -634,7 +634,7 @@ select_spec(Ts, _Where, _Specs) -> selected_specs([], Ts) -> Ts; selected_specs([F], [_ | Ts]) -> - [edoc_specs:spec(F, _Clause=1) | Ts]. + [edoc_specs:spec(F) | Ts]. %% Macros for modules diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl index 47ff7b21fc..3643419ba1 100644 --- a/lib/edoc/src/edoc_layout.erl +++ b/lib/edoc/src/edoc_layout.erl @@ -357,10 +357,10 @@ label_href(Content, F) -> functions(Fs, Opts) -> Es = lists:flatmap(fun ({Name, E}) -> function(Name, E, Opts) end, Fs), if Es == [] -> []; - true -> - [?NL, - {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]}, - ?NL | Es] + true -> + [?NL, + {h2, [{a, [{name, ?FUNCTIONS_LABEL}], [?FUNCTIONS_TITLE]}]}, + ?NL | Es] end. function(Name, E=#xmlElement{content = Es}, Opts) -> @@ -369,22 +369,24 @@ function(Name, E=#xmlElement{content = Es}, Opts) -> label_anchor(function_header(Name, E, " *"), E)}, ?NL] ++ [{'div', [{class, "spec"}], - [?NL, - {p, - case typespec(get_content(typespec, Es), Opts) of + case [typespec(T, Opts) || T <- get_contents(typespec, Es)] of [] -> - signature(get_content(args, Es), - atom(get_attrval(name, E), Opts)); - Spec -> Spec - end}, - ?NL] - ++ case params(get_content(args, Es)) of + [?NL,{p, + signature(get_content(args, Es), + atom(get_attrval(name, E), Opts)) + },?NL]; + Specs -> + [?NL]++[{p, Spec} || Spec <- Specs]++[?NL] + end + ++ case [params(A) || A <- get_contents(args, Es)] of [] -> []; - Ps -> [{p, Ps}, ?NL] + As -> + lists:append([[{p, Ps}, ?NL] || Ps <- As]) end - ++ case returns(get_content(returns, Es)) of + ++ case [returns(Ret) || Ret <- get_contents(returns, Es)] of [] -> []; - Rs -> [{p, Rs}, ?NL] + Rets -> + lists:append([[{p, Rs}, ?NL] || Rs <- Rets]) end}] ++ throws(Es, Opts) ++ equiv_p(Es) @@ -968,12 +970,8 @@ seq(F, [E | Es], Sep, Tail) -> seq(_F, [], _Sep, Tail) -> Tail. -get_elem(Name, [#xmlElement{name = Name} = E | Es]) -> - [E | get_elem(Name, Es)]; -get_elem(Name, [_ | Es]) -> - get_elem(Name, Es); -get_elem(_, []) -> - []. +get_elem(Name, Es) -> + [E || #xmlElement{name=N}=E <- Es, N=:=Name]. get_attr(Name, [#xmlAttribute{name = Name} = A | As]) -> [A | get_attr(Name, As)]; @@ -989,6 +987,13 @@ get_attrval(Name, #xmlElement{attributes = As}) -> [] -> "" end. +get_contents(Name, Es) -> + case get_elem(Name, Es) of + [] -> []; + Elems -> + [Es1 || #xmlElement{content = Es1} <- Elems] + end. + get_content(Name, Es) -> case get_elem(Name, Es) of [#xmlElement{content = Es1}] -> diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl index 7b451c43f8..19f890ed8b 100644 --- a/lib/edoc/src/edoc_specs.erl +++ b/lib/edoc/src/edoc_specs.erl @@ -21,7 +21,7 @@ -module(edoc_specs). --export([type/2, spec/2, dummy_spec/1, docs/2]). +-export([type/2, spec/1, dummy_spec/1, docs/2]). -export([add_data/4, tag/1, is_tag/1]). @@ -67,15 +67,14 @@ type(Form, TypeDocs) -> type = d2e(opaque2abstr(Name, Type))}, Doc}}. --spec spec(Form::syntaxTree(), ClauseN::pos_integer()) -> #tag{}. +-spec spec(Form::syntaxTree()) -> #tag{}. %% @doc Convert an Erlang spec to EDoc representation. -spec(Form, Clause) -> +spec(Form) -> {Name, _Arity, TypeSpecs} = get_spec(Form), - TypeSpec = lists:nth(Clause, TypeSpecs), - #tag{name = spec, line = get_line(element(2, TypeSpec)), + #tag{name = spec, line = get_line(element(2, lists:nth(1, TypeSpecs))), origin = code, - data = aspec(d2e(TypeSpec), Name)}. + data = [aspec(d2e(TypeSpec), Name) || TypeSpec <- TypeSpecs]}. -spec dummy_spec(Form::syntaxTree()) -> #tag{}. @@ -264,8 +263,9 @@ use_tags([#tag{origin = code}=T | Ts], E, TypeTable, NTs) -> use_tags([T | Ts], E, TypeTable, NTs) -> use_tags(Ts, E, TypeTable, [T | NTs]). -params(#tag{name = spec, data=#t_spec{type = #t_fun{args = As}}}, Default) -> - parms(As, Default). + +params(#tag{name = spec, data=Data}, Default) when is_list(Data) -> + [parms(As, Default) || #t_spec{type = #t_fun{args = As}} <- Data]. parms([], []) -> []; @@ -485,13 +485,17 @@ entries([E0 | Es], P, Opts) -> entries([], _P, _Opts) -> []. -specs([#tag{line = L, name = spec, origin = code, data = Spec}=Tag0 | Tags], +specs([#tag{line = L, name = spec, origin = code, data = Specs}=Tag0 | Tags], P0) -> - #t_spec{type = Type0, defs = Defs0} = Spec, P = P0#parms{line = L}, - Type = xrecs(Type0, P), - Defs = xrecs(Defs0, P), - Tag = Tag0#tag{data = Spec#t_spec{type = Type, defs = Defs}}, + Data = + [ begin + #t_spec{type = Type0, defs = Defs0} = Spec, + Type = xrecs(Type0, P), + Defs = xrecs(Defs0, P), + Spec#t_spec{type = Type, defs = Defs} + end || Spec <- Specs], + Tag = Tag0#tag{data = Data}, [Tag | specs(Tags, P)]; specs([Tag | Tags], P) -> [Tag | specs(Tags, P)]; diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk index 3510fdfccf..5d2bbe769d 100644 --- a/lib/edoc/vsn.mk +++ b/lib/edoc/vsn.mk @@ -1 +1 @@ -EDOC_VSN = 0.11 +EDOC_VSN = 0.12 diff --git a/lib/erl_docgen/Makefile b/lib/erl_docgen/Makefile index 7e9cc824ec..acb1ea9776 100644 --- a/lib/erl_docgen/Makefile +++ b/lib/erl_docgen/Makefile @@ -36,6 +36,6 @@ SPECIAL_TARGETS = # include $(ERL_TOP)/make/otp_subdir.mk -DIA_PLT_APPS=edoc xmerl +DIA_PLT_APPS=edoc xmerl syntax_tools crypto include $(ERL_TOP)/make/app_targets.mk diff --git a/lib/erl_docgen/doc/src/doc_storage.xml b/lib/erl_docgen/doc/src/doc_storage.xml index 2fd804b522..2c719b3f20 100644 --- a/lib/erl_docgen/doc/src/doc_storage.xml +++ b/lib/erl_docgen/doc/src/doc_storage.xml @@ -21,7 +21,7 @@ limitations under the License. </legalnotice> - <title>Documentation Storage</title> + <title>EEP-48: Implementation in Erlang/OTP</title> <prepared></prepared> <docno></docno> <date></date> @@ -31,29 +31,103 @@ <section> <title>EEP-48: Documentation storage and format</title> - <p><url href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url> + <p><seeguide marker="kernel:eep48_chapter">EEP-48</seeguide> defines a common documentation storage format for module documentation in the Erlang/OTP ecosystem. Erl_Docgen can generate documentation in this format from XML files following the DTD's descibed in the other User's Guides in this application.</p> - <p></p> <p>Some special considerations have to be taken when writing documentation that should also be available through EEP-48 style storage.</p> <list> - <item>The <c>#PCDATA</c> within <c><name></c> tags must be parseable to figure out the arity of the function.</item> + <item>The <c>#PCDATA</c> within <c><name></c> tags must be parseable + to figure out the arity of the function.</item> <item>It is not allowed to mix <c><name></c> tags with #PCDATA and attributes.</item> - <item>All <c><name></c> tags within <c><func></c> has to have a <c>since</c> attribute.</item> + <item>All <c><name></c> tags within <c><func></c> + has to have a <c>since</c> attribute.</item> <item>All callback function documentations have to start with a <c>Module</c> prefix.</item> </list> </section> <section> <title>Erlang Documentation Format</title> - <p>When generating documentation for generic storage</p> + <p>When generating documentation for EEP-48 Erl_Docgen uses the format mime type + <<"application/erlang+html">>. The documentation content is an Erlang + term that represents an HTML like structure.</p> + <code> +-type chunk_elements() :: [chunk_element()]. +-type chunk_element() :: {chunk_element_type(),chunk_element_attrs(), + chunk_elements()} | unicode:unicode_binary(). +-type chunk_element_attrs() :: [chunk_element_attr()]. +-type chunk_element_attr() :: {atom(),unicode:unicode_binary()}. +-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type(). +-type chunk_element_inline_type() :: a | code | em | i. +-type chunk_element_block_type() :: p | 'div' | br | pre | ul | + ol | li | dl | dt | dd | h1 | h2 | h3. + </code> + <p>The different element types follow their HTML meaning when rendered. + The following are some general rules for how the chunk elements are allowed + to be generated.</p> + <list> + <item>Inline and <c>pre</c> elements are not allowed to contain block elements.</item> + <item><c>p</c> elements are not allowed to be nested.</item> + </list> + <p>The attributes on some elements have a special meaning.</p> + <taglist> + <tag><c>{'div',[{class,unicode:unicode_binary()}],_}</c></tag> + <item>The class name will be used to provide styling to the content in the div. + The types of classes used by Erlang/OTP are: <c>warning</c>, <c>note</c>, <c>do</c>, + <c>dont</c> and <c>quote</c>.</item> + <tag><c><![CDATA[{ul,[{class,<<"types">>}],_}]]></c></tag> + <item>This is a list containing type documentation.</item> + <tag><c><![CDATA[{li,[{name,TypeName :: unicode:unicode_binary()}],_}]]></c></tag> + <item>A list item with a type specification located in the metadata of this modules + EEP-48 documentation. The implementation should look for the AST representation of + the type under the <c>types</c> key. This attribute is only valid under a <c>ul</c> + with class <<"types">>.</item> + <tag><c><![CDATA[{li,[{class,<<"type">>}],_}]]></c></tag> + <item>A list item with the type described in the Erlang Documentation Format. + This attribute is only valid under a <c>ul</c> with class <<"types">>.</item> + <tag><c><![CDATA[{li,[{class,<<"description">>}],_}]]></c></tag> + <item>A list item with the description of the type previous in the list. + This attribute is only valid under a <c>ul</c> with class <<"types">>.</item> + </taglist> + <p>The <seemfa marker="stdlib:shell_docs#validate/1"><c>shell_docs:validate/1</c></seemfa> + function can be used to do a validation of the Erlang Documentation Format.</p> + </section> + + <section> + <title>Erlang Documentation extra Metadata</title> + <p>Erlang/OTP uses some extra metadata fields to embed more information into the EEP-48 docs.</p> + <list> + <item>Fields on module level: + <taglist> + <tag><c>otp_doc_vsn := {non_neg_integer(),non_neg_integer(),non_neg_integer()}</c></tag> + <item>Describes the version of the Erlang Documentation Format used + within this module</item> + <tag><c>types := #{ TypeName :: unicode:unicode_binary() => TypeAST }</c></tag> + <item>A map containing the AST of the types that are part of this module. + This map is used to by functions and callbacks to render the types inline + into their documentation.</item> + </taglist> + </item> + <item>Fields on functions and types: + <taglist> + <tag><c>signature := SpecAST</c></tag> + <item>The spec AST associated with this function. It is used to render a more + descriptive slogan for the documentation entry.</item> + <tag><c>equiv := {Type,Name,Arity}</c></tag> + <item>The current function/type shares documentation with another function/type. + This means that if this and the target function/type are to be shown at the + same time only the prototype of this function/type should will be displayed + and the documentation will use a common body of text.</item> + </taglist> + </item> + </list> </section> <section> <title>See Also</title> <p> + <seeguide marker="kernel:eep48_chapter"></seeguide> <seeerl marker="stdlib:shell_docs"><c>shell_docs(3)</c></seeerl>, <seemfa marker="kernel:code#get_doc/1"><c>code:get_doc(3)</c></seemfa> </p> diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml index e5076a4790..08bcc3d8da 100644 --- a/lib/erl_docgen/doc/src/notes.xml +++ b/lib/erl_docgen/doc/src/notes.xml @@ -31,7 +31,53 @@ </header> <p>This document describes the changes made to the <em>erl_docgen</em> application.</p> - <section><title>Erl_Docgen 0.11</title> + <section><title>Erl_Docgen 1.0</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Embedded documentation (also known as Documentation + Chunks) is now also available in the form of files + according to <url + href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>. + The Documentation Chunks are produced by default when + building the other Erlang/OTP documentation. If you want + to only build the embedded documentation you can pass the + <c>DOC_TARGETS=chunks</c> environment variable to make.</p> + <p> + Own Id: OTP-16406</p> + </item> + <item> + <p> + Minor DTD additions.</p> + <p> + Own Id: OTP-16497</p> + </item> + <item> + <p> + The <c>seealso</c> tag has been replaced with type aware + tags instead. The new tags are: + <c>seemfa|seeerl|seetype|seeapp|seecom|seecref|seefile|seeguide</c>.</p> + <p> + <c>fsdescription</c> has been added for adding a title to + groups of functions, for instance Module Callbacks.</p> + <p> + The <c>dtd</c>s of all documentation files have been + trimmed from all unused or rarely-used tags.</p> + <p> + Unused <c>dtd</c>s have been removed.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16503</p> + </item> + </list> + </section> + +</section> + +<section><title>Erl_Docgen 0.11</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/erl_docgen/priv/bin/specs_gen.escript b/lib/erl_docgen/priv/bin/specs_gen.escript index 96b63aa667..5b75a83a7e 100644 --- a/lib/erl_docgen/priv/bin/specs_gen.escript +++ b/lib/erl_docgen/priv/bin/specs_gen.escript @@ -89,8 +89,9 @@ call_edoc(FileSpec, InclFs, Dir) -> ok = write_text(Text, File, Dir), rename(Dir, File) catch - _:_ -> + E:R:ST -> io:format("EDoc could not process file '~s'\n", [File]), + io:format("~p:~p ~p\n", [E,R,ST]), clean_up(Dir), halt(3) end. diff --git a/lib/erl_docgen/priv/bin/validate_links.escript b/lib/erl_docgen/priv/bin/validate_links.escript index 41d533fb58..4d93d9c253 100755 --- a/lib/erl_docgen/priv/bin/validate_links.escript +++ b/lib/erl_docgen/priv/bin/validate_links.escript @@ -259,11 +259,6 @@ validate_link(Filename, LinkType = "seetype", Line, Link, CachedFiles) -> _ -> validate_type(Line,LinkType,read_link(Line, ParsedLink, CachedFiles)) end; -validate_link(Filename, "seeerl" = LinkType, Line, Link, CachedFiles) -> - ParsedLink = parse_link(Filename, maps:get(m2a,CachedFiles), Link), - TargetInfo = read_link(Line, ParsedLink, CachedFiles), - validate_type(Line,LinkType,TargetInfo), - validate_marker(Line,ParsedLink,TargetInfo); validate_link({"jinterface","jinterface_users_guide"},"seefile",_, _, _) -> %% Skip links to java documentation ok; diff --git a/lib/erl_docgen/priv/dtd/common.dtd b/lib/erl_docgen/priv/dtd/common.dtd index d4d5d989a5..cc186fe5d6 100644 --- a/lib/erl_docgen/priv/dtd/common.dtd +++ b/lib/erl_docgen/priv/dtd/common.dtd @@ -51,7 +51,7 @@ <!ELEMENT list (item+) > <!ATTLIST list type (ordered|bulleted) "bulleted" > -<!ELEMENT taglist (marker*,tag,item+)+ > +<!ELEMENT taglist (tag,item+)+ > <!ELEMENT tag (#PCDATA|c|i|em|br|%refs;|marker|anno)* > <!ELEMENT item (%inline;|%block;|warning|note|dont|do|quote|table)* > diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index 15c86adf2d..c14e7c9a71 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -2267,10 +2267,10 @@ </xsl:variable> <xsl:choose> <xsl:when test="string-length($fname2) > 0"> - <xsl:value-of select="$fname2"/> + <xsl:value-of select="normalize-space($fname2)"/> </xsl:when> <xsl:otherwise> - <xsl:value-of select="$fname1"/> + <xsl:value-of select="normalize-space($fname1)"/> </xsl:otherwise> </xsl:choose> </xsl:variable> diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl index 60d73c279d..47ca1690e2 100644 --- a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl +++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl @@ -757,23 +757,21 @@ functions(Fs) -> function(_Name, E=#xmlElement{content = Es}) -> TypeSpec = get_content(typespec, Es), - [?NL,{func, [ ?NL, - {name, [{since,""}], - case funcheader(TypeSpec) of - [] -> - signature(get_content(args, Es), - get_attrval(name, E)); - Spec -> Spec - end - }, - ?NL,{fsummary, fsummary(Es)}, - ?NL,local_types(TypeSpec), - ?NL,{desc, - label_anchor(E)++ - deprecated(Es)++ - fulldesc(Es)++ - seealso_function(Es)} - ]}]. + FuncHeaders = + case funcheader(TypeSpec) of + [] -> + [signature(get_content(args, Es), get_attrval(name, E))]; + Specs -> + Specs + end, + [?NL, {func, [?NL]++ + [{name, [{since,""}], Spec} || Spec <- FuncHeaders]++ + [?NL, {fsummary, fsummary(Es)}, + ?NL, local_types(TypeSpec), + ?NL, {desc, label_anchor(E)++ + deprecated(Es)++ + fulldesc(Es)++ + seealso_function(Es)}]}]. fsummary([]) -> ["\s"]; fsummary(Es) -> @@ -817,7 +815,9 @@ arg(#xmlElement{content = Es}) -> funcheader([]) -> []; funcheader(Es) -> - [t_name(get_elem(erlangName, Es))] ++ t_utype(get_elem(type, Es)). + Name = t_name(get_elem(erlangName, Es)), + [ [Name] ++ t_utype([E]) || E <- get_elem(type, Es)]. + local_types([]) -> []; local_types(Es) -> @@ -1035,7 +1035,7 @@ author(E=#xmlElement{}) -> end, [?NL,{aname,[Name]},?NL,{email,[Mail]}]. -t_name([E]) -> +t_name([E | _]) -> N = get_attrval(name, E), case get_attrval(module, E) of "" -> N; @@ -1246,12 +1246,15 @@ get_attrval(Name, #xmlElement{attributes = As}) -> %% get_content(Tag, Es1) -> Es2 %% If there is one element in Es1 with name Tag, returns its contents, -%% otherwise [] +%% if there are no tags, return [], +%% if there are multiple, merge their contents. get_content(Name, Es) -> case get_elem(Name, Es) of - [#xmlElement{content = Es1}] -> - Es1; - [] -> [] + [#xmlElement{content = Es1}] -> + Es1; + [] -> []; + Elems -> + lists:append([Es1 || #xmlElement{content = Es1} <- Elems]) end. %% get_text(Tag, Es) -> string() diff --git a/lib/erl_docgen/src/docgen_xml_to_chunk.erl b/lib/erl_docgen/src/docgen_xml_to_chunk.erl index c57ecac213..6c9ae59996 100644 --- a/lib/erl_docgen/src/docgen_xml_to_chunk.erl +++ b/lib/erl_docgen/src/docgen_xml_to_chunk.erl @@ -525,30 +525,60 @@ func2func({func,Attr,Contents}) -> _ = VerifyNameList(NameList,fun([]) -> ok end), FAs = [TagsToFA(FAttr) || {name,FAttr,[]} <- NameList ], + SortedFAs = lists:usort(FAs), FAClauses = lists:usort([{TagsToFA(FAttr),proplists:get_value(clause_i,FAttr)} || {name,FAttr,[]} <- NameList ]), - Signature = [iolist_to_binary([F,"/",A]) || {F,A} <- FAs], - lists:map( - fun({F,A}) -> - Specs = [{func_to_atom(CF),list_to_integer(CA),C} - || {{CF,CA},C} <- FAClauses, - F =:= CF, A =:= CA], - {function,[{name,F},{arity,list_to_integer(A)}, - {signature,Signature}, - {meta,SinceMD#{ signature => Specs }}], - ContentsNoName} - end, lists:usort(FAs)); + + MakeFunc = fun({F,A}, MD, Doc) -> + Specs = [begin + {function,Name} = func_to_atom(CF), + {Name,list_to_integer(CA),C} + end || {{CF,CA},C} <- FAClauses, + F =:= CF, A =:= CA], + {function,[{name,F},{arity,list_to_integer(A)}, + {signature,[iolist_to_binary([F,"/",A])]}, + {meta,MD#{ signature => Specs }}], + Doc} + end, + + Base = MakeFunc(hd(SortedFAs), SinceMD, ContentsNoName), + + {BaseF,BaseA} = hd(SortedFAs), + MD = SinceMD#{ equiv => {function,list_to_atom(BaseF),list_to_integer(BaseA)}}, + Equiv = lists:map( + fun(FA) -> + MakeFunc(FA, MD, []) + end, tl(SortedFAs)), + [Base | Equiv]; NameList -> %% Manual style function docs - FAs = lists:flatten([func_to_tuple(NameString) || {name, _Attr, NameString} <- NameList]), + FAs = lists:foldl( + fun({name,_,NameString}, Acc) -> + FAs = func_to_tuple(NameString), + lists:foldl( + fun(FA, FAAcc) -> + Slogan = maps:get(FA, FAAcc, []), + FAAcc#{ FA => [strip_tags(NameString)|Slogan] } + end, Acc, FAs) + end, #{}, NameList), _ = VerifyNameList(NameList,fun([_|_]) -> ok end), - Signature = [strip_tags(NameString) || {name, _Attr, NameString} <- NameList], - [{function,[{name,F},{arity,A}, - {signature,Signature}, - {meta,SinceMD}],ContentsNoName} - || {F,A} <- lists:usort(FAs)] + SortedFAs = lists:usort(maps:to_list(FAs)), + + {{BaseF, BaseA}, BaseSig} = hd(SortedFAs), + + Base = {function,[{name,BaseF},{arity,BaseA}, + {signature,BaseSig}, + {meta,SinceMD}], + ContentsNoName}, + + Equiv = [{function, + [{name,F},{arity,A}, + {signature,Signature}, + {meta,SinceMD#{ equiv => {function,list_to_atom(BaseF),BaseA}}}],[]} + || {{F,A},Signature} <- tl(SortedFAs)], + [Base | Equiv] end, transform(Functions,[]). @@ -644,7 +674,7 @@ to_chunk(Dom, Source, Module, AST) -> TypeEntries = lists:map( fun({datatype,Attr,Descr}) -> - TypeName = func_to_atom(proplists:get_value(name,Attr)), + {function, TypeName} = func_to_atom(proplists:get_value(name,Attr)), TypeArity = case proplists:get_value(n_vars,Attr) of undefined -> find_type_arity(TypeName, TypeMap); @@ -662,24 +692,48 @@ to_chunk(Dom, Source, Module, AST) -> Sig -> #{ signature => [Sig] } end, - docs_v1_entry(type, Anno, TypeName, TypeArity, TypeSignature, MetaSig, Descr) + + MetaDepr + = case otp_internal:obsolete_type(Module, TypeName, TypeArity) of + {deprecated, Text} -> + MetaSig#{ deprecated => + unicode:characters_to_binary( + erl_lint:format_error({deprecated_type,{Module,TypeName,TypeArity}, Text})) }; + %% Commented out to make dialyzer happy + %% {deprecated, Replacement, Rel} -> + %% MetaSig#{ deprecated => + %% unicode:characters_to_binary( + %% erl_lint:format_error({deprecated_type,{Module,TypeName,TypeArity}, Replacement, Rel})) }; + no -> + MetaSig + end, + + docs_v1_entry(type, Anno, TypeName, TypeArity, TypeSignature, MetaDepr, Descr) end, Types), Functions = lists:flatten([Functions || {functions,[],Functions} <- Mcontent]), FuncEntrys = - lists:flatmap( + lists:map( fun({function,Attr,Fdoc}) -> - case func_to_atom(proplists:get_value(name,Attr)) of - callback -> - []; - Name -> - Arity = proplists:get_value(arity,Attr), - Signature = proplists:get_value(signature,Attr), - FMeta = proplists:get_value(meta,Attr), - MetaWSpec = add_spec(AST,FMeta), - [docs_v1_entry(function, Anno, Name, Arity, Signature, MetaWSpec, Fdoc)] - end + {Type, Name} = func_to_atom(proplists:get_value(name,Attr)), + Arity = proplists:get_value(arity,Attr), + Signature = proplists:get_value(signature,Attr), + FMeta = proplists:get_value(meta,Attr), + MetaWSpec = add_spec(AST,FMeta), + MetaDepr + = case otp_internal:obsolete(Module, Name, Arity) of + {deprecated, Text} -> + MetaWSpec#{ deprecated => + unicode:characters_to_binary( + erl_lint:format_error({deprecated,{Module,Name,Arity}, Text})) }; + {deprecated, Replacement, Rel} -> + MetaWSpec#{ deprecated => + unicode:characters_to_binary( + erl_lint:format_error({deprecated,{Module,Name,Arity}, Replacement, Rel})) }; + _ -> MetaWSpec + end, + docs_v1_entry(Type, Anno, Name, Arity, Signature, MetaDepr, Fdoc) end, Functions), docs_v1(ModuleDocs, Anno, TypeMeta, FuncEntrys ++ TypeEntries). @@ -691,6 +745,7 @@ docs_v1(DocContents, Anno, Metadata, Docs) -> docs = Docs }. docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) -> + AnnoWLine = case Metadata of #{ signature := [Sig|_] } -> @@ -699,8 +754,16 @@ docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) -> _NoSignature -> Anno end, - {{Kind, Name, Arity}, AnnoWLine, lists:flatten(Signature), - #{ <<"en">> => shell_docs:normalize(DocContents)}, Metadata}. + + Doc = + case DocContents of + [] -> + #{}; + DocContents -> + #{ <<"en">> => shell_docs:normalize(DocContents) } + end, + + {{Kind, Name, Arity}, AnnoWLine, lists:flatten(Signature), Doc, Metadata}. %% A special list_to_atom that handles %% 'and' @@ -708,11 +771,16 @@ docs_v1_entry(Kind, Anno, Name, Arity, Signature, Metadata, DocContents) -> %% 'begin' func_to_atom(List) -> case erl_scan:string(List) of - {ok,[{atom,_,Fn}],_} -> Fn; - {ok,[{var,_,Fn}],_} -> Fn; - {ok,[{Fn,_}],_} -> Fn; - {ok,[{var,_,_},{':',_},_],_} -> - callback + {ok,[{atom,_,Fn}],_} -> + {function, Fn}; + {ok,[{var,_,Fn}],_} -> + {function, Fn}; + {ok,[{Fn,_}],_} -> + {function, Fn}; + {ok,[{var,_,_},{':',_},{atom,_,Fn}],_} -> + {callback, Fn}; + {ok,[{var,_,_},{':',_},{var,_,Fn}],_} -> + {callback, Fn} end. -define(IS_TYPE(TO),(TO =:= type orelse TO =:= opaque)). diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk index ebc9516da3..57b2fd10f4 100644 --- a/lib/erl_docgen/vsn.mk +++ b/lib/erl_docgen/vsn.mk @@ -1 +1 @@ -ERL_DOCGEN_VSN = 0.11 +ERL_DOCGEN_VSN = 1.0 diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml index 361021bf50..a47b344c4c 100644 --- a/lib/erl_interface/doc/src/ei.xml +++ b/lib/erl_interface/doc/src/ei.xml @@ -35,9 +35,6 @@ <lib>ei</lib> <libsummary>Routines for handling the Erlang binary term format.</libsummary> <description> - <note><p>The support for VxWorks is deprecated as of OTP 22, and - will be removed in OTP 23.</p></note> - <p>The library <c>ei</c> contains macros and functions to encode and decode the Erlang binary term format.</p> @@ -85,7 +82,9 @@ <p>There are also encode functions that use a dynamic buffer. It is often more convenient to use these to encode data. All encode functions comes in two versions; those starting with - <c>ei_x</c> use a dynamic buffer.</p> + <c>ei_x_</c> use a dynamic buffer of type + <seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref>. + </p> <p>All functions return <c>0</c> if successful, otherwise <c>-1</c> (for example, if a term is not of the expected @@ -93,7 +92,7 @@ <p>Some of the decode functions need a pre-allocated buffer. This buffer must be allocated large enough, and for non-compound types - the <c>ei_get_type()</c> + the <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref> function returns the size required (notice that for strings an extra byte is needed for the <c>NULL</c>-terminator).</p> </description> @@ -101,7 +100,55 @@ <section> <title>Data Types</title> <taglist> - <tag><marker id="erlang_char_encoding"/>erlang_char_encoding</tag> + <tag><marker id="ei_term"/><c>ei_term</c></tag> + <item> + <code type="none"> +typedef struct { + char ei_type; + int arity; + int size; + union { + long i_val; + double d_val; + char atom_name[MAXATOMLEN_UTF8]; + erlang_pid pid; + erlang_port port; + erlang_ref ref; + } value; +} ei_term;</code> + <p>Structure written by + <seecref marker="#ei_decode_ei_term"><c>ei_decode_ei_term()</c></seecref>. + The <c>ei_type</c> field is the type of the term which equals to + what <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref> + sets <c>*type</c> to. + </p> + </item> + <tag><marker id="ei_x_buff"/><c>ei_x_buff</c></tag> + <item> + <p>A dynamically resized buffer. It is a <c>struct</c> with + two fields of interest for the user: + </p> + <taglist> + <tag><c>char *buff</c></tag> + <item> + <p>Pointer to the dynamically allocated buffer.</p> + </item> + <tag><c>int index</c></tag> + <item> + <p>Offset to the next byte to write which also equals the + amount of bytes currently written.</p> + </item> + </taglist> + <p> + An <c>ei_x_buff</c> is initialized by calling either + <seecref marker="#ei_x_new"><c>ei_x_new()</c></seecref> or + <seecref marker="#ei_x_new_with_version"><c>ei_x_new_with_version()</c></seecref>. + The memory used by an initialized <c>ei_x_buff</c> is released + by calling + <seecref marker="#ei_x_free"><c>ei_x_free()</c></seecref>. + </p> + </item> + <tag><marker id="erlang_char_encoding"/><c>erlang_char_encoding</c></tag> <item> <code type="none"> typedef enum { @@ -117,11 +164,91 @@ typedef enum { Notice that these constants are bit-flags and can be combined with bitwise OR.</p> </item> + <tag><marker id="erlang_fun"/><c>erlang_fun</c></tag> + <item> + <p>Opaque data type representing an Erlang fun.</p> + </item> + <tag><marker id="erlang_pid"/><c>erlang_pid</c></tag> + <item> + <p>Opaque data type representing an Erlang process identifier.</p> + </item> + <tag><marker id="erlang_port"/><c>erlang_port</c></tag> + <item> + <p>Opaque data type representing an Erlang port identifier.</p> + </item> + <tag><marker id="erlang_ref"/><c>erlang_ref</c></tag> + <item> + <p>Opaque data type representing an Erlang reference.</p> + </item> + <tag><marker id="erlang_trace"/><c>erlang_trace</c></tag> + <item> + <p>Opaque data type representing an Erlang sequential trace token.</p> + </item> </taglist> </section> <funcs> <func> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_pids(erlang_pid *a, erlang_pid *b)</nametext></name> + <fsummary>Compare two pids.</fsummary> + <type> + <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> + <desc> + <p> + Compare two process identifiers. The comparison is done the same way + as Erlang does. + </p> + <p> + Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value + less than <c>0</c> if <c>a</c> compares as less than <c>b</c>. + Returns a value larger than <c>0</c> if <c>a</c> compares as larger + than <c>b</c>. + </p> + </desc> + </func> + + <func> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_ports(erlang_port *a, erlang_port *b)</nametext></name> + <fsummary>Compare two ports.</fsummary> + <type> + <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v> + </type> + <desc> + <p> + Compare two port identifiers. The comparison is done the same way as + Erlang does. + </p> + <p> + Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value + less than <c>0</c> if <c>a</c> compares as less than <c>b</c>. + Returns a value larger than <c>0</c> if <c>a</c> compares as larger + than <c>b</c>. + </p> + </desc> + </func> + + <func> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_cmp_refs(erlang_ref *a, erlang_ref *b)</nametext></name> + <fsummary>Compare two references.</fsummary> + <type> + <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v> + </type> + <desc> + <p> + Compare two references. The comparison is done the same way as Erlang + does. + </p> + <p> + Returns <c>0</c> if <c>a</c> and <c>b</c> are equal. Returns a value + less than <c>0</c> if <c>a</c> compares as less than <c>b</c>. + Returns a value larger than <c>0</c> if <c>a</c> compares as larger + than <c>b</c>. + </p> + </desc> + </func> + + <func> <name since=""><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name> <fsummary>Decode an atom.</fsummary> <desc> @@ -134,6 +261,9 @@ typedef enum { <func> <name since="OTP R16B"><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name> <fsummary>Decode an atom.</fsummary> + <type> + <v><seecref marker="#erlang_char_encoding"><c>erlang_char_encoding</c></seecref></v> + </type> <desc> <p>Decodes an atom from the binary format. The <c>NULL</c>-terminated name of the atom is placed in buffer at <c>p</c> of length <c>plen</c> @@ -173,7 +303,8 @@ typedef enum { <c>len</c> is set to the actual size of the binary. Notice that <c>ei_decode_binary()</c> assumes that there is enough room for the binary. The size required can be - fetched by <c>ei_get_type()</c>.</p> + fetched by + <seecref marker="#ei_get_type"><c>ei_get_type()</c></seecref>.</p> </desc> </func> @@ -249,6 +380,9 @@ typedef enum { <func> <name since=""><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name> <fsummary>Decode a term, without previous knowledge of type.</fsummary> + <type> + <v><seecref marker="#ei_term"><c>ei_term</c></seecref></v> + </type> <desc> <p>Decodes any term, or at least tries to. If the term pointed at by <c>*index</c> in <c>buf</c> fits @@ -271,6 +405,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name> <name since=""><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name> <fsummary>Decode a fun.</fsummary> + <type> + <v><seecref marker="#erlang_fun"><c>erlang_fun</c></seecref></v> + </type> <desc> <p>Decodes a fun from the binary format. Parameter <c>p</c> is to be <c>NULL</c> or point to an @@ -283,6 +420,37 @@ typedef enum { </func> <func> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_decode_iodata(const char *buf, int *index, int *size, char *outbuf)</nametext></name> + <fsummary>Decode iodata().</fsummary> + <desc> + <p>Decodes a term of the type <seeguide marker="system/reference_manual:typespec#builtin_types"><c>iodata()</c></seeguide>. The <c>iodata()</c> term will be + flattened an written into the buffer pointed to by the <c>outbuf</c> + argument. The byte size of the <c>iodata</c> is written into the + integer variable pointed to by the <c>size</c> argument. Both <c>size</c> + and <c>outbuf</c> can be set to <c>NULL</c>. The integer pointed to + by the <c>index</c> argument is updated to refer to the term + following after the <c>iodata()</c> term regardless of the the state + of the <c>size</c> and the <c>outbuf</c> arguments. + </p> + <p>Note that the buffer pointed to by the <c>outbuf</c> argument + must be large enough if a non <c>NULL</c> value is passed as + <c>outbuf</c>. You typically want to call <c>ei_decode_iodata()</c> + twice. First with a non <c>NULL</c> <c>size</c> argument and + a <c>NULL</c> <c>outbuf</c> argument in order to determine the + size of the buffer needed, and then once again in order to do + the actual decoding. Note that the integer pointed to by <c>index</c> + will be updated by the call determining the size as well, so you + need to reset it before the second call doing the actual decoding. + </p> + <p>Returns <c>0</c> on success and <c>-1</c> on failure. Failure + might be either due to invalid encoding of the term or due to + the term not being of the type <c>iodata()</c>. On failure, the + integer pointed to by the <c>index</c> argument will be updated + to refer to the sub term where the failure was detected.</p> + </desc> + </func> + + <func> <name since=""><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name> <fsummary>Decode a list.</fsummary> <desc> @@ -315,8 +483,7 @@ typedef enum { <desc> <p>Decodes a GCC <c>long long</c> or Visual C++ <c>__int64</c> - (64-bit) integer from the binary format. This - function is missing in the VxWorks port.</p> + (64-bit) integer from the binary format.</p> </desc> </func> @@ -336,6 +503,9 @@ typedef enum { <func> <name since=""><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name> <fsummary>Decode a <c>pid</c>.</fsummary> + <type> + <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Decodes a process identifier (pid) from the binary format.</p> </desc> @@ -344,6 +514,9 @@ typedef enum { <func> <name since=""><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name> <fsummary>Decode a port.</fsummary> + <type> + <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v> + </type> <desc> <p>Decodes a port identifier from the binary format.</p> </desc> @@ -352,6 +525,9 @@ typedef enum { <func> <name since=""><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name> <fsummary>Decode a reference.</fsummary> + <type> + <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v> + </type> <desc> <p>Decodes a reference from the binary format.</p> </desc> @@ -375,6 +551,9 @@ typedef enum { <func> <name since=""><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name> <fsummary>Decode a trace token.</fsummary> + <type> + <v><seecref marker="#erlang_trace"><c>erlang_trace</c></seecref></v> + </type> <desc> <p>Decodes an Erlang trace token from the binary format.</p> </desc> @@ -406,7 +585,7 @@ typedef enum { <desc> <p>Decodes a GCC <c>unsigned long long</c> or Visual C++ <c>unsigned __int64</c> (64-bit) integer from the binary - format. This function is missing in the VxWorks port.</p> + format.</p> </desc> </func> @@ -426,6 +605,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name> <fsummary>Encode an atom.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom in Latin-1 encoding. Only up to @@ -441,6 +623,10 @@ typedef enum { <name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> <name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> <fsummary>Encode an atom.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_char_encoding"><c>erlang_char_encoding</c></seecref></v> + </type> <desc> <p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom with character encoding @@ -459,6 +645,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name> <fsummary>Encode an arbitrary precision integer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a GMP <c>mpz_t</c> integer to binary format. To use this function, the <c>ei</c> library must be configured and @@ -470,6 +659,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name> <fsummary>Encode a binary.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a binary in the binary format. The data is at <c>p</c>, of <c>len</c> bytes length.</p> @@ -482,6 +674,9 @@ typedef enum { <name since="OTP 22.0"><ret>int</ret> <nametext>ei_x_encode_bitstring(ei_x_buff* x, const char *p, size_t bitoffs, size_t nbits)</nametext></name> <fsummary>Encode a bitstring.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a bit string in the binary format.</p> <p>The data is at <c>p</c>. The length of the bit string is <c>nbits</c> @@ -502,6 +697,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name> <fsummary>Encode a boolean.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a boolean value as the atom <c>true</c> if <c>p</c> is not zero, or <c>false</c> if <c>p</c> is @@ -513,6 +711,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name> <fsummary>Encode an 8-bit integer between 0-255.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a char (8-bit) as an integer between 0-255 in the binary format. For historical reasons the integer argument is of @@ -527,6 +728,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name> <fsummary>Encode a double float.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a double-precision (64-bit) floating point number in the binary format.</p> @@ -539,6 +743,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name> <fsummary>Encode an empty list (<c>nil</c>).</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes an empty list. It is often used at the tail of a list.</p> </desc> @@ -548,6 +755,10 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name> <fsummary>Encode a fun.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_fun"><c>erlang_fun</c></seecref></v> + </type> <desc> <p>Encodes a fun in the binary format. Parameter <c>p</c> points to an <c>erlang_fun</c> structure. The @@ -561,6 +772,9 @@ typedef enum { <name since=""><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name> <fsummary>Encode a list.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a list header, with a specified arity. The next <c>arity+1</c> terms are the elements @@ -598,6 +812,9 @@ ei_x_encode_empty_list(&x);</pre> <name since=""><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name> <fsummary>Encode integer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a long integer in the binary format. If the code is 64 bits, the function <c>ei_encode_long()</c> is @@ -609,10 +826,12 @@ ei_x_encode_empty_list(&x);</pre> <name since=""><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name> <fsummary>Encode integer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a GCC <c>long long</c> or Visual C++ - <c>__int64</c> (64-bit) integer in the binary format. - This function is missing in the VxWorks port.</p> + <c>__int64</c> (64-bit) integer in the binary format.</p> </desc> </func> @@ -620,6 +839,9 @@ ei_x_encode_empty_list(&x);</pre> <name since="OTP 17.0"><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name> <name since="OTP 17.0"><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name> <fsummary>Encode a map.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a map header, with a specified arity. The next <c>arity*2</c> terms encoded will be the keys and values of the map @@ -641,11 +863,20 @@ ei_x_encode_string(&x, "Banana");</pre> <name since=""><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name> <fsummary>Encode a pid.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Encodes an Erlang process identifier (pid) in the binary format. Parameter <c>p</c> points to an - <c>erlang_pid</c> structure (which should have been - obtained earlier with <c>ei_decode_pid()</c>).</p> + <c>erlang_pid</c> structure which should either have been + obtained earlier with + <seecref marker="#ei_decode_pid"><c>ei_decode_pid()</c></seecref>, + <seecref marker="ei_connect#ei_self"><c>ei_self()</c></seecref> or + created by + <seecref marker="ei_connect#ei_make_pid"><c>ei_make_pid()</c></seecref>. + </p> </desc> </func> @@ -653,11 +884,16 @@ ei_x_encode_string(&x, "Banana");</pre> <name since=""><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name> <fsummary>Encode a port.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_port"><c>erlang_port</c></seecref></v> + </type> <desc> <p>Encodes an Erlang port in the binary format. Parameter - <c>p</c> points to a <c>erlang_port</c> - structure (which should have been obtained earlier with - <c>ei_decode_port()</c>).</p> + <c>p</c> points to an <c>erlang_port</c> structure which + should have been obtained earlier with + <seecref marker="#ei_decode_port"><c>ei_decode_port()</c></seecref>, + </p> </desc> </func> @@ -665,11 +901,17 @@ ei_x_encode_string(&x, "Banana");</pre> <name since=""><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name> <fsummary>Encode a ref.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_ref"><c>erlang_ref</c></seecref></v> + </type> <desc> <p>Encodes an Erlang reference in the binary format. Parameter - <c>p</c> points to a <c>erlang_ref</c> - structure (which should have been obtained earlier with - <c>ei_decode_ref()</c>).</p> + <c>p</c> points to an <c>erlang_ref</c> + structure which either should have been obtained earlier with + <seecref marker="#ei_decode_ref"><c>ei_decode_ref()</c></seecref>, or + created by + <seecref marker="ei_connect#ei_make_ref"><c>ei_make_ref()</c></seecref>.</p> </desc> </func> @@ -679,6 +921,9 @@ ei_x_encode_string(&x, "Banana");</pre> <name since=""><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name> <fsummary>Encode a string.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a string in the binary format. (A string in Erlang is a list, but is encoded as a character array in the binary @@ -690,11 +935,16 @@ ei_x_encode_string(&x, "Banana");</pre> <name since=""><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name> <fsummary>Encode a trace token.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_trace"><c>erlang_trace</c></seecref></v> + </type> <desc> <p>Encodes an Erlang trace token in the binary format. Parameter <c>p</c> points to a - <c>erlang_trace</c> structure (which should have been - obtained earlier with <c>ei_decode_trace()</c>).</p> + <c>erlang_trace</c> structure which should have been + obtained earlier with + <seecref marker="#ei_decode_trace"><c>ei_decode_trace()</c></seecref>.</p> </desc> </func> @@ -702,6 +952,9 @@ ei_x_encode_string(&x, "Banana");</pre> <name since=""><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name> <fsummary>Encode a tuple.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a tuple header, with a specified arity. The next <c>arity</c> terms encoded will be the @@ -721,6 +974,9 @@ ei_encode_tuple_header(buf, &i, 0);</pre> <name since=""><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name> <fsummary>Encode unsigned integer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes an unsigned long integer in the binary format. If the code is 64 bits, the function <c>ei_encode_ulong()</c> is @@ -732,10 +988,13 @@ ei_encode_tuple_header(buf, &i, 0);</pre> <name since=""><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name> <fsummary>Encode unsigned integer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a GCC <c>unsigned long long</c> or Visual C++ <c>unsigned __int64</c> (64-bit) integer in the binary - format. This function is missing in the VxWorks port.</p> + format.</p> </desc> </func> @@ -743,6 +1002,9 @@ ei_encode_tuple_header(buf, &i, 0);</pre> <name since=""><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name> <fsummary>Encode version.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Encodes a version magic number for the binary format. Must be the first token in a binary term.</p> @@ -760,6 +1022,105 @@ ei_encode_tuple_header(buf, &i, 0);</pre> the number of bytes. For lists, tuples and maps, <c>*size</c> is the arity of the object. For other types, <c>*size</c> is 0. In all cases, <c>index</c> is left unchanged.</p> + <p>Currently <c>*type</c> is one of:</p> + <taglist> + <tag>ERL_ATOM_EXT</tag> + <item><p> + Decode using either + <seecref marker="#ei_decode_atom"><c>ei_decode_atom()</c></seecref>, + <seecref marker="#ei_decode_atom_as"><c>ei_decode_atom_as()</c></seecref>, + or + <seecref marker="#ei_decode_boolean"><c>ei_decode_boolean()</c></seecref>. + </p></item> + + <tag>ERL_BINARY_EXT</tag> + <item><p> + Decode using either + <seecref marker="#ei_decode_binary"><c>ei_decode_binary()</c></seecref>, + <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring()</c></seecref>, + or + <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>. + </p></item> + + <tag>ERL_BIT_BINARY_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_bitstring"><c>ei_decode_bitstring()</c></seecref>. + </p></item> + + <tag>ERL_FLOAT_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_double"><c>ei_decode_double()</c></seecref>. + </p></item> + + <tag>ERL_NEW_FUN_EXT<br/>ERL_FUN_EXT<br/>ERL_EXPORT_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_fun"><c>ei_decode_fun()</c></seecref>. + </p></item> + + <tag>ERL_SMALL_INTEGER_EXT<br/>ERL_INTEGER_EXT<br/>ERL_SMALL_BIG_EXT<br/>ERL_LARGE_BIG_EXT</tag> + <item><p> + Decode using either + <seecref marker="#ei_decode_char"><c>ei_decode_char()</c></seecref>, + <seecref marker="#ei_decode_long"><c>ei_decode_long()</c></seecref>, + <seecref marker="#ei_decode_longlong"><c>ei_decode_longlong()</c></seecref>, + <seecref marker="#ei_decode_ulong"><c>ei_decode_ulong()</c></seecref>, + <seecref marker="#ei_decode_ulonglong"><c>ei_decode_ulonglong()</c></seecref>, + or + <seecref marker="#ei_decode_bignum"><c>ei_decode_bignum()</c></seecref>. + </p></item> + + <tag>ERL_LIST_EXT<br/>ERL_NIL_EXT</tag> + <item><p> + Decode using either + <seecref marker="#ei_decode_list_header"><c>ei_decode_list_header()</c></seecref>, + or + <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>. + </p></item> + + <tag>ERL_STRING_EXT</tag> + <item><p> + Decode using either + <seecref marker="#ei_decode_string"><c>ei_decode_string()</c></seecref>, + or + <seecref marker="#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref>. + </p></item> + + <tag>ERL_MAP_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_map_header"><c>ei_decode_map_header()</c></seecref>. + </p></item> + + <tag>ERL_PID_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_pid"><c>ei_decode_pid()</c></seecref>. + </p></item> + + <tag>ERL_PORT_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_port"><c>ei_decode_port()</c></seecref>. + </p></item> + + <tag>ERL_NEW_REFERENCE_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_ref"><c>ei_decode_ref()</c></seecref>. + </p></item> + + <tag>ERL_SMALL_TUPLE_EXT<br/>ERL_LARGE_TUPLE_EXT</tag> + <item><p> + Decode using + <seecref marker="#ei_decode_tuple_header"><c>ei_decode_tuple_header()</c></seecref>. + </p></item> + </taglist> + <p>Instead of decoding a term you can also skipped past it if you are + not interested in the data by usage of + <seecref marker="#ei_skip_term"><c>ei_skip_term()</c></seecref>.</p> </desc> </func> @@ -801,11 +1162,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name since=""><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name> + <name since=""><ret>void</ret><nametext>ei_set_compat_rel(unsigned release_number)</nametext></name> <fsummary>Set the ei library in compatibility mode.</fsummary> - <type> - <v>unsigned release_number;</v> - </type> <desc> <marker id="ei_set_compat_rel"></marker> <p>In general, the <c>ei</c> library is guaranteed @@ -878,6 +1236,9 @@ ei_encode_tuple_header(buf, &i, 0);</pre> <name since=""><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name> <fsummary>Append a buffer at the end.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> <p>Appends data at the end of buffer <c>x</c>.</p> </desc> @@ -887,6 +1248,10 @@ ei_encode_tuple_header(buf, &i, 0);</pre> <name since=""><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name> <fsummary>Format a term from a format string and parameters.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Formats a term, given as a string, to a buffer. Works like a sprintf for Erlang terms. @@ -915,9 +1280,13 @@ encodes the tuple {numbers,12,3.14159}</pre> <func> <name since=""><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name> <fsummary>Free a buffer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> - <p>Frees an <c>ei_x_buff</c> buffer. - The memory used by the buffer is returned to the OS.</p> + <p>Deallocates the dynamically allocated content of the buffer + referred by <c>x</c>. After deallocation, the <c>buff</c> field + is set to <c>NULL</c>.</p> </desc> </func> @@ -925,10 +1294,14 @@ encodes the tuple {numbers,12,3.14159}</pre> <name since=""><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name> <name since=""><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name> <fsummary>Allocate a new buffer.</fsummary> + <type> + <v><seecref marker="#ei_x_buff"><c>ei_x_buff</c></seecref></v> + </type> <desc> - <p>Allocates a new <c>ei_x_buff</c> buffer. The - fields of the structure pointed to by parameter <c>x</c> - is filled in, and a default buffer is allocated. + <p>Initialize the dynamically realizable buffer referred to + by <c>x</c>. The fields of the structure pointed to by + parameter <c>x</c> is filled in, and a default buffer is + allocated. <c>ei_x_new_with_version()</c> also puts an initial version byte, which is used in the binary format (so that <c>ei_x_encode_version()</c> will not be needed.)</p> diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml index f991165df7..c5ef9440c5 100644 --- a/lib/erl_interface/doc/src/ei_connect.xml +++ b/lib/erl_interface/doc/src/ei_connect.xml @@ -34,9 +34,6 @@ <lib>ei_connect</lib> <libsummary>Communicate with distributed Erlang.</libsummary> <description> - <note><p>The support for VxWorks is deprecated as of OTP 22, and - will be removed in OTP 23.</p></note> - <p>This module enables C-programs to communicate with Erlang nodes, using the Erlang distribution over TCP/IP.</p> @@ -112,27 +109,10 @@ only supports IPv4. That is, at this time <c>addr</c> always points to a <c>struct sockaddr_in</c> structure.</p> - <p>The <c>ei_socket_callbacks</c> structure may be enlarged in - the future. All fields not set, <em>needs</em> to be zeroed out.</p> - - <marker id="ei_socket_callbacks"/> - <code type="none"><![CDATA[ -typedef struct { - int flags; - int (*socket)(void **ctx, void *setup_ctx); - int (*close)(void *ctx); - int (*listen)(void *ctx, void *addr, int *len, int backlog); - int (*accept)(void **ctx, void *addr, int *len, unsigned tmo); - int (*connect)(void *ctx, void *addr, int len, unsigned tmo); - int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo); - int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo); - int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo); - int (*handshake_packet_header_size)(void *ctx, int *sz); - int (*connect_handshake_complete)(void *ctx); - int (*accept_handshake_complete)(void *ctx); - int (*get_fd)(void *ctx, int *fd); -} ei_socket_callbacks; - ]]></code> + <p><marker id="ei_socket_callbacks_fields"/>The + <seecref marker="#ei_socket_callbacks"><c>ei_socket_callbacks</c></seecref> + structure may be enlarged in the future. All fields not set, <em>needs</em> + to be zeroed out. Currently the following fields exist:</p> <taglist> @@ -355,6 +335,79 @@ typedef struct { </item> </taglist> </section> + <section> + <title>Data Types</title> + <taglist> + <tag><marker id="ei_cnode"/><c>ei_cnode</c></tag> + <item><p> + Opaque data type representing a C-node. A <c>ei_cnode</c> + structure is initialized by calling + <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref> + or friends. + </p></item> + + <tag><marker id="ei_socket_callbacks"/><c>ei_socket_callbacks</c></tag> + <item><code type="none"> +typedef struct { + int flags; + int (*socket)(void **ctx, void *setup_ctx); + int (*close)(void *ctx); + int (*listen)(void *ctx, void *addr, int *len, int backlog); + int (*accept)(void **ctx, void *addr, int *len, unsigned tmo); + int (*connect)(void *ctx, void *addr, int len, unsigned tmo); + int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo); + int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo); + int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo); + int (*handshake_packet_header_size)(void *ctx, int *sz); + int (*connect_handshake_complete)(void *ctx); + int (*accept_handshake_complete)(void *ctx); + int (*get_fd)(void *ctx, int *fd); +} ei_socket_callbacks;</code> + <p> + Callbacks functions for a <seecref marker="#ussi"><i>User + Supplied Socket Implementation</i></seecref>. + <seecref marker="#ei_socket_callbacks_fields">Documentation + of each field</seecref> can be found in the + <i>User Supplied Socket Implementation</i> section + above. + </p> + + </item> + + <tag><marker id="ErlConnect"/><c>ErlConnect</c></tag> + <item><code type="none"> +typedef struct { + char ipadr[4]; /* Ip v4 address in network byte order */ + char nodename[MAXNODELEN]; +} ErlConnect;</code> + <p>IP v4 address and nodename.</p> + </item> + + <tag><marker id="Erl_IpAddr"/><c>Erl_IpAddr</c></tag> + <item><code type="none"> +typedef struct { + unsigned s_addr; /* Ip v4 address in network byte order */ +} Erl_IpAddr;</code> + <p>IP v4 address.</p> + </item> + + <tag><marker id="erlang_msg"/><c>erlang_msg</c></tag> + <item> + <code type="none"> +typedef struct { + long msgtype; + erlang_pid from; + erlang_pid to; + char toname[MAXATOMLEN+1]; + char cookie[MAXATOMLEN+1]; + erlang_trace token; +} erlang_msg;</code> + <p>Information about a message received via + <seecref marker="#ei_receive_msg"><c>ei_receive_msg()</c></seecref> + or friends.</p> + </item> + </taglist> + </section> <funcs> <func> <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name> @@ -371,6 +424,10 @@ typedef struct { <func> <name since=""><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name> <fsummary>Accept a connection from another node.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="#ErlConnect"><c>ErlConnect</c></seecref></v> + </type> <desc> <p>Used by a server process to accept a connection from a client process.</p> @@ -384,13 +441,8 @@ typedef struct { </item> <item> <p><c>conp</c> is a pointer to an - <c>ErlConnect</c> struct, described as follows:</p> - <code type="none"><![CDATA[ -typedef struct { - char ipadr[4]; - char nodename[MAXNODELEN]; -} ErlConnect; - ]]></code> + <seecref marker="#ErlConnect"><c>ErlConnect</c></seecref> + struct.</p> </item> </list> <p>On success, <c>conp</c> is filled in with the address and @@ -404,6 +456,10 @@ typedef struct { <name since=""><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name> <fsummary>Accept a connection from another node with optional time-out.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="#ErlConnect"><c>ErlConnect</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_accept</c> with an optional time-out argument, @@ -422,9 +478,13 @@ typedef struct { <func> <name since=""><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name> <name since=""><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name> - <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_connect_host_port(ei_cnode* ec, char *hostname, int port)</nametext></name> - <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr adr, int port)</nametext></name> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_connect_host_port(ei_cnode* ec, char *hostname, int port)</nametext></name> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_xconnect_host_port(ei_cnode* ec, Erl_IpAddr adr, int port)</nametext></name> <fsummary>Establish a connection to an Erlang node.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v> + </type> <desc> <p>Sets up a connection to an Erlang node.</p> <p><c>ei_xconnect()</c> requires the IP address of the @@ -486,6 +546,11 @@ fd = ei_xconnect(&ec, &addr, ALIVE); <name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name> <name since="OTP 21.3"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name> <fsummary>Initialize for a connection.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v> + <v><seecref marker="#ei_socket_callbacks"><c>ei_socket_callbacks</c></seecref></v> + </type> <desc> <p>Initializes the <c>ec</c> structure, to identify the node name and cookie of the server. One of them @@ -583,10 +648,14 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <func> <name since=""><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name> <name since=""><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name> - <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_connect_host_port_tmo(ei_cnode* ec, char *hostname, int port, unsigned ms)</nametext></name> - <name since="OTP @OTP-16251@"><ret>int</ret><nametext>ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr adr, int port, unsigned ms)</nametext></name> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_connect_host_port_tmo(ei_cnode* ec, char *hostname, int port, unsigned ms)</nametext></name> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_xconnect_host_port_tmo(ei_cnode* ec, Erl_IpAddr adr, int port, unsigned ms)</nametext></name> <fsummary>Establish a connection to an Erlang node with optional time-out.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_connect</c>, <c>ei_xconnect</c>, <c>ei_connect_host_port</c> and @@ -613,6 +682,10 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <name since="OTP 21.3"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name> <name since="OTP 21.3"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name> <fsummary>Create a listen socket.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="#Erl_IpAddr"><c>Erl_IpAddr</c></seecref></v> + </type> <desc> <p>Used by a server process to setup a listen socket which later can be used for accepting connections from client processes. @@ -649,8 +722,60 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_make_pid(ei_cnode *ec, erlang_pid *pid)</nametext></name> + <fsummary>Create a new process identifier</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> + <desc> + <p> + Creates a new process identifier in the argument <c>pid</c>. This process identifier + refers to a conseptual process residing on the C-node identified by the argument + <c>ec</c>. On success <c>0</c> is returned. On failure <c>ERL_ERROR</c> is + returned and <c>erl_errno</c> is set. + </p> + <p> + The C-node identified by <c>ec</c> must have been initialized and must have + received a name prior to the call to <c>ei_make_pid()</c>. Initialization + of the C-node is done by a call to + <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref> + or friends. If the name is dynamically assigned from the peer node, the + C-node also has to be connected. + </p> + </desc> + </func> + + <func> + <name since="OTP 23.0"><ret>int</ret><nametext>ei_make_ref(ei_cnode *ec, erlang_ref *ref)</nametext></name> + <fsummary>Create a new reference</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="ei#erlang_ref"><c>erlang_ref</c></seecref></v> + </type> + <desc> + <p> + Creates a new reference in the argument <c>ref</c>. This reference originates + from the C-node identified by the argument <c>ec</c>. On success <c>0</c> is + returned. On failure <c>ERL_ERROR</c> is returned and <c>erl_errno</c> is set. + </p> + <p> + The C-node identified by <c>ec</c> must have been initialized and must have + received a name prior to the call to <c>ei_make_ref()</c>. Initialization + of the C-node is done by a call to + <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref> + or friends. If the name is dynamically assigned from the peer node, the + C-node also has to be connected. + </p> + </desc> + </func> + + <func> <name since=""><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name> <fsummary>Publish a node name.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Used by a server process to register with the local name server EPMD, thereby allowing @@ -688,6 +813,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <func> <name since=""><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name> <fsummary>Publish a node name with optional time-out.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_publish</c> with an optional time-out argument, @@ -739,6 +867,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <func> <name since=""><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name> <fsummary>Obsolete function for receiving a message.</fsummary> + <type> + <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v> + </type> <desc> <p>This function is retained for compatibility with code generated by the interface compiler and with code following @@ -770,6 +901,9 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <name since=""><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name> <fsummary>Obsolete function for receiving a message with time-out. </fsummary> + <type> + <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_receive_encoded</c> with an optional time-out argument, @@ -781,6 +915,10 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <name since=""><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name> <name since=""><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name> <fsummary>Receive a message.</fsummary> + <type> + <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v> + </type> <desc> <p>Receives a message to the buffer in <c>x</c>. <c>ei_xreceive_msg</c> allows the buffer in @@ -797,18 +935,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { <c>ei_x_new</c>.</item> </list> <p>On success, the functions return <c>ERL_MSG</c> and the - <c>msg</c> struct is initialized. - <c>erlang_msg</c> is defined as follows:</p> - <code type="none"><![CDATA[ -typedef struct { - long msgtype; - erlang_pid from; - erlang_pid to; - char toname[MAXATOMLEN+1]; - char cookie[MAXATOMLEN+1]; - erlang_trace token; -} erlang_msg; - ]]></code> + <seecref marker="#erlang_msg"><c>msg</c></seecref> struct + is initialized.</p> <p><c>msgtype</c> identifies the type of message, and is one of the following:</p> <taglist> @@ -846,6 +974,10 @@ typedef struct { <name since=""><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name> <name since=""><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name> <fsummary>Receive a message with optional time-out.</fsummary> + <type> + <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_receive_msg</c> and <c>ei_xreceive_msg</c> with an optional time-out argument, @@ -866,6 +998,9 @@ typedef struct { <func> <name since=""><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name> <fsummary>Send a message to a registered name.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Sends an Erlang term to a registered process.</p> <list type="bulleted"> @@ -899,6 +1034,9 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0) <name since=""><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name> <fsummary>Send a message to a registered name with optional time-out </fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_reg_send</c> with an optional time-out argument, @@ -911,6 +1049,11 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0) <name since=""><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name> <name since=""><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name> <fsummary>Remote Procedure Call from C to Erlang.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="ei#ei_x_buff"><c>ei_x_buff</c></seecref></v> + <v><seecref marker="#erlang_msg"><c>erlang_msg</c></seecref></v> + </type> <desc> <p>Supports calling Erlang functions on remote nodes. <c>ei_rpc_to()</c> sends an RPC request to a remote node @@ -1010,19 +1153,40 @@ if (ei_decode_version(result.buff, &index) < 0 <func> <name since=""><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name> <fsummary>Retrieve the pid of the C-node.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> - <p>Retrieves the pid of the C-node. Every C-node + <p>Retrieves a generic pid of the C-node. Every C-node has a (pseudo) pid used in <c>ei_send_reg</c>, - <c>ei_rpc</c>, + <c>ei_rpc()</c>, and others. This is contained in a field in the <c>ec</c> - structure. It will be safe for a long time to fetch this - field directly from the <c>ei_cnode</c> structure.</p> + structure. Do <em>not</em> modify this structure. + </p> + <p> + On success a pointer to the process identifier is returned. + On failure <c>NULL</c> is returned and <c>erl_errno</c> is + set. + </p> + <p> + The C-node identified by <c>ec</c> must have been initialized + and must have received a name prior to the call to <c>ei_self()</c>. + Initialization of the C-node is done by a call to + <seecref marker="#ei_connect_init"><c>ei_connect_init()</c></seecref> + or friends. If the name is dynamically assigned from the peer node, the + C-node also has to be connected. + </p> + </desc> </func> <func> <name since=""><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name> <fsummary>Send a message.</fsummary> + <type> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Sends an Erlang term to a process.</p> <list type="bulleted"> @@ -1044,6 +1208,9 @@ if (ei_decode_version(result.buff, &index) < 0 <func> <name since=""><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name> <fsummary>Obsolete function to send a message.</fsummary> + <type> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Works exactly as <c>ei_send</c>, the alternative name is retained for backward compatibility. The function will <em>not</em> be @@ -1055,6 +1222,9 @@ if (ei_decode_version(result.buff, &index) < 0 <name since=""><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name> <fsummary>Obsolete function to send a message with optional time-out. </fsummary> + <type> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_send_encoded</c> with an optional time-out argument, @@ -1066,6 +1236,9 @@ if (ei_decode_version(result.buff, &index) < 0 <name since=""><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name> <fsummary>Obsolete function to send a message to a registered name. </fsummary> + <type> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>This function is retained for compatibility with code generated by the interface compiler and with code following @@ -1076,17 +1249,8 @@ if (ei_decode_version(result.buff, &index) < 0 <c>erlang_pid</c>, which is to be the process identifier of the sending process (in the Erlang distribution protocol).</p> - <p>A suitable <c>erlang_pid</c> can be constructed from the - <c>ei_cnode</c> structure by the following example - code:</p> - <code type="none"><![CDATA[ -ei_cnode ec; -erlang_pid *self; -int fd; /* the connection fd */ -... -self = ei_self(&ec); -self->num = fd; - ]]></code> + <p>A suitable <c>erlang_pid</c> can be retrieved from the + <c>ei_cnode</c> structure by calling <c>ei_self(cnode_pointer)</c>.</p> </desc> </func> @@ -1094,6 +1258,9 @@ self->num = fd; <name since=""><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name> <fsummary>Obsolete function to send a message to a registered name with time-out.</fsummary> + <type> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_send_reg_encoded</c> with an optional time-out argument, @@ -1104,6 +1271,9 @@ self->num = fd; <func> <name since=""><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name> <fsummary>Send a message with optional time-out.</fsummary> + <type> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_send</c> with an optional time-out argument, @@ -1116,6 +1286,9 @@ self->num = fd; <name since=""><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name> <name since=""><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name> <fsummary>Retrieve some values.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Can be used to retrieve information about the C-node. These values are initially set with @@ -1131,6 +1304,9 @@ self->num = fd; <func> <name since=""><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name> <fsummary>Forcefully unpublish a node name.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Can be called by a process to unregister a specified node from EPMD on the local host. This is, however, usually @@ -1154,6 +1330,9 @@ self->num = fd; <func> <name since=""><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name> <fsummary>Unpublish a node name with optional time-out.</fsummary> + <type> + <v><seecref marker="#ei_cnode"><c>ei_cnode</c></seecref></v> + </type> <desc> <p>Equivalent to <c>ei_unpublish</c> with an optional time-out argument, diff --git a/lib/erl_interface/doc/src/ei_global.xml b/lib/erl_interface/doc/src/ei_global.xml index ecca1aad68..148068490e 100644 --- a/lib/erl_interface/doc/src/ei_global.xml +++ b/lib/erl_interface/doc/src/ei_global.xml @@ -35,9 +35,6 @@ <lib>ei_global</lib> <libsummary>Access globally registered names.</libsummary> <description> - <note><p>The support for VxWorks is deprecated as of OTP 22, and - will be removed in OTP 23.</p></note> - <p>This module provides support for registering, looking up, and unregistering names in the <c>global</c> module. For more information, see @@ -54,14 +51,14 @@ <name since=""><ret>char **</ret><nametext>ei_global_names(ec,fd,count)</nametext></name> <fsummary>Obtain list of global names.</fsummary> <type> - <v>ei_cnode *ec;</v> + <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v> <v>int fd;</v> <v>int *count;</v> </type> <desc> <p>Retrieves a list of all known global names.</p> <list type="bulleted"> - <item><c>ec</c> is the ei_cnode representing the current cnode.</item> + <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item> <item><c>fd</c> is an open descriptor to an Erlang connection.</item> <item><c>count</c> is the address of an integer, or @@ -89,7 +86,7 @@ <type> <v>int fd;</v> <v>const char *name;</v> - <v>erlang_pid *pid;</v> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref> *pid;</v> </type> <desc> <p>Registers a name in <c>global</c>.</p> @@ -111,14 +108,14 @@ <name since=""><ret>int</ret><nametext>ei_global_unregister(ec,fd,name)</nametext></name> <fsummary>Unregister a name from global.</fsummary> <type> - <v>ei_cnode *ec;</v> + <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v> <v>int fd;</v> <v>const char *name;</v> </type> <desc> <p>Unregisters a name from <c>global</c>.</p> <list type="bulleted"> - <item><c>ec</c> is the ei_cnode representing the current cnode.</item> + <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item> <item><c>fd</c> is an open descriptor to an Erlang connection.</item> <item><c>name</c> is the name to unregister from @@ -132,16 +129,16 @@ <name since=""><ret>int</ret><nametext>ei_global_whereis(ec,fd,name,pid,node)</nametext></name> <fsummary>Look up a name in global.</fsummary> <type> - <v>ei_cnode *ec;</v> + <v><seecref marker="ei_connect#ei_cnode"><c>ei_cnode</c></seecref> *ec;</v> <v>int fd;</v> <v>const char *name;</v> - <v>erlang_pid* pid;</v> + <v><seecref marker="ei#erlang_pid"><c>erlang_pid</c></seecref> *pid;</v> <v>char *node;</v> </type> <desc> <p>Looks up a name in <c>global</c>.</p> <list type="bulleted"> - <item><c>ec</c> is the ei_cnode representing the current cnode.</item> + <item><c>ec</c> is the <c>ei_cnode</c> representing the current cnode.</item> <item><c>fd</c> is an open descriptor to an Erlang connection.</item> <item><c>name</c> is the name that is to be looked up in diff --git a/lib/erl_interface/doc/src/ei_users_guide.xml b/lib/erl_interface/doc/src/ei_users_guide.xml index 25952c2025..5dfaf556da 100644 --- a/lib/erl_interface/doc/src/ei_users_guide.xml +++ b/lib/erl_interface/doc/src/ei_users_guide.xml @@ -34,12 +34,6 @@ </header> <section> - <title>Deprecation and Removal</title> - <note><p>The support for VxWorks is deprecated as of OTP 22, and - will be removed in OTP 23.</p></note> - </section> - - <section> <title>Introduction</title> <p>The <c>Erl_Interface</c> library contains functions that help you integrate programs written in C and Erlang. The functions in @@ -536,6 +530,12 @@ ei_global_unregister(ec,fd,servicename); ]]></code> <section> <title>Using the Registry</title> + + <note><p>This functionality is deprecated as of OTP 23, and will be + removed in OTP 24. Reasonably new <c>gcc</c> compilers will issue + deprecation warnings. In order to disable these warnings, define the + macro <c>EI_NO_DEPR_WARN</c>.</p></note> + <p>This section describes the use of the registry, a simple mechanism for storing key-value pairs in a C-node, as well as backing them up or restoring them from an <c>Mnesia</c> table on an Erlang node. For more diff --git a/lib/erl_interface/doc/src/erl_call_cmd.xml b/lib/erl_interface/doc/src/erl_call_cmd.xml index 4a77aa954e..04b5ec74bf 100644 --- a/lib/erl_interface/doc/src/erl_call_cmd.xml +++ b/lib/erl_interface/doc/src/erl_call_cmd.xml @@ -183,6 +183,13 @@ specified, an Erlang node is started (if necessary) with <c>erl -sname</c>.</p> </item> + <tag><c>-timeout Seconds</c></tag> + <item> + <p>(<em>Optional.</em>) Aborts the <c>erl_call</c> process after + the timeout expires. Note that this does not abort commands that + have already been started with <c>-a</c>, <c>-e</c>, or similar. + </p> + </item> <tag><c>-v</c></tag> <item> <p>(<em>Optional.</em>) Prints a lot of <c>verbose</c> diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml index ba5f501e85..37eb379d95 100644 --- a/lib/erl_interface/doc/src/notes.xml +++ b/lib/erl_interface/doc/src/notes.xml @@ -31,6 +31,178 @@ </header> <p>This document describes the changes made to the Erl_interface application.</p> +<section><title>Erl_Interface 4.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix various compiler warnings on 64-bit Windows.</p> + <p> + Own Id: OTP-15800</p> + </item> + <item> + <p><c>erl_call</c> will now work properly on systems that + cannot resolve their own hostname.</p> + <p> + Own Id: OTP-16604</p> + </item> + <item> + <p>Various bug fixes:</p> <list> <item>Internal error + checking in various functions.</item> <item><seecref + marker="ei_connect#ei_rpc"><c>ei_rpc()</c></seecref> + accepted any 2-tuple message as an rpc response.</item> + <item><seecref + marker="ei#ei_decode_ref"><c>ei_decode_ref()</c></seecref> + now refuse to write outside of allocated memory in case a + huge reference is decoded.</item> <item><seecref + marker="ei#ei_decode_ei_term"><c>ei_decode_ei_term()</c></seecref> + now reports the same term types as <seecref + marker="ei#ei_get_type"><c>ei_get_type()</c></seecref>.</item> + </list> + <p> + Own Id: OTP-16623</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>A client node can receive its node name dynamically + from the node that it first connects to. This featured + can by used by</p> <list> <item><p>starting with <c>erl + -sname undefined</c></p></item> <item><p>erl_interface + functions <c>ei_connect_init</c> and friends</p></item> + <item><p><c>erl_call -R</c></p></item> </list> + <p> + Own Id: OTP-13812</p> + </item> + <item> + <p> + Increased size of node incarnation numbers (aka + "creation"), from 2 bits to 32 bits. This will reduce the + risk of pids/ports/refs, from different node incarnation + with the same name, being mixed up.</p> + <p> + Own Id: OTP-15603</p> + </item> + <item> + <p> + Fix various build issues when compiling Erlang/OTP to the + IBM AIX platform.</p> + <p> + Own Id: OTP-15866 Aux Id: PR-2110 </p> + </item> + <item> + <p> + Improved node connection setup handshake protocol. Made + possible to agree on protocol version without dependence + on <c>epmd</c> or other prior knowledge of peer node + version. Also added exchange of node incarnation + ("creation") values and expanded the distribution + capability flag field from 32 to 64 bits.</p> + <p> + Own Id: OTP-16229</p> + </item> + <item> + <p> + New <c>erl_call</c> option <c>-address [Host]:Port</c> to + connect directly to a node without being dependent on + <c>epmd</c> to resolve the node name.</p> + <p> + Own Id: OTP-16251</p> + </item> + <item> + <p> + As announced in OTP 22.0, the deprecated parts of + <c>erl_interface</c> have now been removed (essentially + all C functions with prefix <c>erl_</c>).</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16328</p> + </item> + <item> + <p> + As announced in OTP 22.0, the previously existing limited + support for VxWorks has now been removed.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16329 Aux Id: OTP-15621 </p> + </item> + <item> + <p> + New function <c>ei_connect_host_port</c> and friends to + allow node connection without being dependent on + <c>epmd</c> for node name resolution.</p> + <p> + Own Id: OTP-16496 Aux Id: OTP-16251 </p> + </item> + <item> + <p>A number of new functions have been added to the + <c>erl_interface</c> API:</p> <list> <item><seecref + marker="erl_interface:ei#ei_cmp_pids"><c>ei_cmp_pids()</c></seecref></item> + <item><seecref + marker="erl_interface:ei#ei_cmp_ports"><c>ei_cmp_ports()</c></seecref></item> + <item><seecref + marker="erl_interface:ei#ei_cmp_refs"><c>ei_cmp_refs()</c></seecref></item> + <item><seecref + marker="erl_interface:ei#ei_decode_iodata"><c>ei_decode_iodata()</c></seecref></item> + <item><seecref + marker="erl_interface:ei_connect#ei_make_pid"><c>ei_make_pid()</c></seecref></item> + <item><seecref + marker="erl_interface:ei_connect#ei_make_ref"><c>ei_make_ref()</c></seecref></item> + </list> + <p> + Own Id: OTP-16594</p> + </item> + <item> + <p>Added a <c>-timeout</c> option to <c>erl_call</c>.</p> + <p> + Own Id: OTP-16624</p> + </item> + <item> + <p>The <c>erl_interface</c> <seecref + marker="erl_interface:registry"><c>registry</c></seecref> + functionality is deprecated as of OTP 23, and will be + removed in OTP 24. Reasonably new <c>gcc</c> compilers + will issue deprecation warnings when using this + functionality. In order to disable these warnings, define + the macro <c>EI_NO_DEPR_WARN</c>.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16630</p> + </item> + <item> + <p> + Documentation improvements.</p> + <p> + Own Id: OTP-16633</p> + </item> + </list> + </section> + + + <section><title>Known Bugs and Problems</title> + <list> + <item> + <p> + The <c>ei</c> API for decoding/encoding terms is not + fully 64-bit compatible since terms that have a + representation on the external term format larger than 2 + GB cannot be handled.</p> + <p> + Own Id: OTP-16607 Aux Id: OTP-16608 </p> + </item> + </list> + </section> + +</section> + <section><title>Erl_Interface 3.13.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/erl_interface/doc/src/ref_man.xml b/lib/erl_interface/doc/src/ref_man.xml index 4900c0f5f3..064a83a4ba 100644 --- a/lib/erl_interface/doc/src/ref_man.xml +++ b/lib/erl_interface/doc/src/ref_man.xml @@ -29,8 +29,6 @@ <file>ref_man.xml</file> </header> <description> - <note><p>The support for VxWorks is deprecated as of OTP 22, and - will be removed in OTP 23.</p></note> </description> <xi:include href="ei.xml"/> <xi:include href="ei_connect.xml"/> diff --git a/lib/erl_interface/doc/src/registry.xml b/lib/erl_interface/doc/src/registry.xml index 28c79ad0fe..92858516d9 100644 --- a/lib/erl_interface/doc/src/registry.xml +++ b/lib/erl_interface/doc/src/registry.xml @@ -35,6 +35,11 @@ <lib>registry</lib> <libsummary>Store and back up key-value pairs.</libsummary> <description> + <note><p>This functionality is deprecated as of OTP 23, and will be + removed in OTP 24. Reasonably new <c>gcc</c> compilers will issue + deprecation warnings. In order to disable these warnings, define the + macro <c>EI_NO_DEPR_WARN</c>.</p></note> + <p>This module provides support for storing key-value pairs in a table known as a registry, backing up registries to <seeerl marker="mnesia:mnesia">Mnesia</seeerl> diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h index 6b75a213d0..803e22d4b2 100644 --- a/lib/erl_interface/include/ei.h +++ b/lib/erl_interface/include/ei.h @@ -366,6 +366,7 @@ typedef struct ei_cnode_s { erlang_pid self; ei_socket_callbacks *cbs; void *setup_context; + unsigned int pidsn; } ei_cnode; typedef struct in_addr *Erl_IpAddr; @@ -446,6 +447,8 @@ const char *ei_thishostname(const ei_cnode* ec); const char *ei_thisalivename(const ei_cnode* ec); erlang_pid *ei_self(ei_cnode* ec); +int ei_make_pid(ei_cnode *ec, erlang_pid *pid); +int ei_make_ref(ei_cnode *ec, erlang_ref *ref); /* * settings @@ -568,6 +571,7 @@ int ei_decode_trace(const char *buf, int *index, erlang_trace *p); int ei_decode_tuple_header(const char *buf, int *index, int *arity); int ei_decode_list_header(const char *buf, int *index, int *arity); int ei_decode_map_header(const char *buf, int *index, int *arity); +int ei_decode_iodata(const char *buf, int* index, int *szp, char *out_buf); /* * ei_decode_ei_term() returns 1 if term is decoded, 0 if term is OK, @@ -599,6 +603,10 @@ int ei_x_append(ei_x_buff* x, const ei_x_buff* x2); int ei_x_append_buf(ei_x_buff* x, const char* buf, int len); int ei_skip_term(const char* buf, int* index); +int ei_cmp_refs(erlang_ref *a, erlang_ref *b); +int ei_cmp_pids(erlang_pid *a, erlang_pid *b); +int ei_cmp_ports(erlang_port *a, erlang_port *b); + /*************************************************************************** * * Hash types needed by registry types @@ -691,9 +699,9 @@ int ei_init(void); * be specified in all subsequent calls to registry functions. You can * open as many registries as you like. */ -ei_reg *ei_reg_open(int size); -int ei_reg_resize(ei_reg *oldreg, int newsize); -int ei_reg_close(ei_reg *reg); +ei_reg *ei_reg_open(int size) EI_DEPRECATED_ATTR; +int ei_reg_resize(ei_reg *oldreg, int newsize) EI_DEPRECATED_ATTR; +int ei_reg_close(ei_reg *reg) EI_DEPRECATED_ATTR; /* set values... these routines assign values to keys. If the key * exists, the previous value is discarded and the new one replaces @@ -710,10 +718,10 @@ int ei_reg_close(ei_reg *reg); * On success the function returns 0, otherwise a value * indicating the reason for failure will be returned. */ -int ei_reg_setival(ei_reg *reg, const char *key, long i); -int ei_reg_setfval(ei_reg *reg, const char *key, double f); -int ei_reg_setsval(ei_reg *reg, const char *key, const char *s); -int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size); +int ei_reg_setival(ei_reg *reg, const char *key, long i) EI_DEPRECATED_ATTR; +int ei_reg_setfval(ei_reg *reg, const char *key, double f) EI_DEPRECATED_ATTR; +int ei_reg_setsval(ei_reg *reg, const char *key, const char *s) EI_DEPRECATED_ATTR; +int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size) EI_DEPRECATED_ATTR; /* general set function (specifiy type via flags) * optional arguments are as for equivalent type-specific function, @@ -723,16 +731,16 @@ int ei_reg_setpval(ei_reg *reg, const char *key, const void *p, int size); * ei_reg_setval(fd, path, EI_STR, const char *s); * ei_reg_setval(fd, path, EI_BIN, const void *p, int size); */ -int ei_reg_setval(ei_reg *reg, const char *key, int flags, ...); +int ei_reg_setval(ei_reg *reg, const char *key, int flags, ...) EI_DEPRECATED_ATTR; /* get value of specific type object */ /* warning: it may be difficult to detect errors when using these * functions, since the error values are returned "in band" */ -long ei_reg_getival(ei_reg *reg, const char *key); -double ei_reg_getfval(ei_reg *reg, const char *key); -const char *ei_reg_getsval(ei_reg *reg, const char *key); -const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size); +long ei_reg_getival(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR; +double ei_reg_getfval(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR; +const char *ei_reg_getsval(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR; +const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size) EI_DEPRECATED_ATTR; /* get value of any type object (must specify) * Retrieve a value from an object. The type of value expected and a @@ -748,7 +756,7 @@ const void *ei_reg_getpval(ei_reg *reg, const char *key, int *size); * for BIN objects an int* is needed to return the size of the object, i.e. * int ei_reg_getval(ei_reg *reg, const char *path, int flags, void **p, int *size); */ -int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...); +int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...) EI_DEPRECATED_ATTR; /* mark the object as dirty. Normally this operation will not be * necessary, as it is done automatically by all of the above 'set' @@ -758,26 +766,26 @@ int ei_reg_getval(ei_reg *reg, const char *key, int flags, ...); * backup operation. Use this function to set the dirty bit on the * object. */ -int ei_reg_markdirty(ei_reg *reg, const char *key); +int ei_reg_markdirty(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR; /* remove objects. The value, if any, is discarded. For STR and BIN * objects, the object itself is removed using free(). */ -int ei_reg_delete(ei_reg *reg, const char *key); +int ei_reg_delete(ei_reg *reg, const char *key) EI_DEPRECATED_ATTR; /* get information about an object */ -int ei_reg_stat(ei_reg *reg, const char *key, struct ei_reg_stat *obuf); +int ei_reg_stat(ei_reg *reg, const char *key, struct ei_reg_stat *obuf) EI_DEPRECATED_ATTR; /* get information about table */ -int ei_reg_tabstat(ei_reg *reg, struct ei_reg_tabstat *obuf); +int ei_reg_tabstat(ei_reg *reg, struct ei_reg_tabstat *obuf) EI_DEPRECATED_ATTR; /* dump to / restore from backup */ /* fd is open descriptor to Erlang, mntab is Mnesia table name */ /* flags here: */ #define EI_FORCE 0x1 /* dump all records (not just dirty ones) */ #define EI_NOPURGE 0x2 /* don't purge deleted records */ -int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags); -int ei_reg_restore(int fd, ei_reg *reg, const char *mntab); -int ei_reg_purge(ei_reg *reg); +int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) EI_DEPRECATED_ATTR; +int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) EI_DEPRECATED_ATTR; +int ei_reg_purge(ei_reg *reg) EI_DEPRECATED_ATTR; /* -------------------------------------------------------------------- */ /* The ei_global functions */ diff --git a/lib/erl_interface/src/Makefile.in b/lib/erl_interface/src/Makefile.in index 55827ce097..7ff3f09abb 100644 --- a/lib/erl_interface/src/Makefile.in +++ b/lib/erl_interface/src/Makefile.in @@ -331,6 +331,7 @@ DECODESRC = \ decode/decode_double.c \ decode/decode_fun.c \ decode/decode_intlist.c \ + decode/decode_iodata.c \ decode/decode_list_header.c \ decode/decode_long.c \ decode/decode_pid.c \ @@ -393,7 +394,8 @@ MISCSRC = \ misc/get_type.c \ misc/show_msg.c \ misc/ei_compat.c \ - misc/ei_init.c + misc/ei_init.c \ + misc/ei_cmp_nc.c REGISTRYSRC = \ registry/hash_dohash.c \ @@ -744,9 +746,7 @@ release: opt $(INSTALL_DATA) $(HEADERS) "$(RELEASE_PATH)/usr/include" $(INSTALL_DATA) $(OBJ_TARGETS) "$(RELSYSDIR)/lib" $(INSTALL_DATA) $(OBJ_TARGETS) "$(RELEASE_PATH)/usr/lib" -ifneq ($(EXE_TARGETS),) $(INSTALL_PROGRAM) $(EXE_TARGETS) "$(RELSYSDIR)/bin" -endif $(INSTALL_DATA) $(EXTRA) "$(RELSYSDIR)/src" $(INSTALL_DATA) connect/*.[ch] "$(RELSYSDIR)/src/connect" $(INSTALL_DATA) decode/*.[ch] "$(RELSYSDIR)/src/decode" diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c index 40efdd101a..a5db73799d 100644 --- a/lib/erl_interface/src/connect/ei_connect.c +++ b/lib/erl_interface/src/connect/ei_connect.c @@ -525,11 +525,272 @@ const char *ei_thiscookie(const ei_cnode* ec) return (const char *)ec->ei_connect_cookie; } +static int +check_initialized_node(ei_cnode *ec) +{ + /* + * Try to guard against returning garbage pids and refs + * by verifying that the node has got its name... + */ + int i, at, end; + char *nodename = &ec->thisnodename[0]; + + for (i = at = end = 0; i < sizeof(ec->thisnodename); i++) { + if (!nodename[i]) { + end = !0; + break; + } + if (nodename[i] == '@') + at = !0; + } + + if (!at || !end) { + erl_errno = EINVAL; + return ERL_ERROR; + } + + return 0; +} + erlang_pid *ei_self(ei_cnode* ec) { + int err = check_initialized_node(ec); + if (err) + return NULL; return &ec->self; } +/* + * ei_make_pid() + */ + +#undef EI_MAKE_PID_ATOMIC__ +#ifdef _REENTRANT +# if (SIZEOF_INT == 4 \ + && (ETHR_HAVE___atomic_compare_exchange_n & 4) \ + && (ETHR_HAVE___atomic_load_n & 4)) +# define EI_MAKE_PID_ATOMIC__ +# else /* !EI_MAKE_PID_ATOMIC__ */ +static ei_mutex_t *pid_mtx = NULL; +# endif /* !EI_MAKE_PID_ATOMIC__ */ +#endif /* _REENTRANT */ + +static int +init_make_pid(int late) +{ +#if defined(_REENTRANT) && !defined(EI_MAKE_PID_ATOMIC__) + + if (late) + return ENOTSUP; /* Refuse doing unsafe initialization... */ + + pid_mtx = ei_mutex_create(); + if (!pid_mtx) + return ENOMEM; + +#endif /* _REENTRANT */ + + return 0; +} + +int ei_make_pid(ei_cnode *ec, erlang_pid *pid) +{ + unsigned int new; + int err; + + if (!ei_connect_initialized) { + fprintf(stderr,"<ERROR> erl_interface not initialized\n"); + exit(1); + } + + err = check_initialized_node(ec); + if (err) { + /* + * write invalid utf8 in nodename which will make + * ei_encode_pid() fail if used... + */ + pid->node[0] = 0xff; + pid->node[1] = 0; + pid->serial = -1; + pid->num = -1; + return err; + } + + strcpy(pid->node, ec->thisnodename); + pid->creation = ec->creation; + + /* + * We avoid creating pids with serial set to 0 since the + * documentation previously gave some really bad advise + * of modifying the 'num' field in the pid returned by + * ei_self(). Since 'serial' field in pid returned by + * ei_self() is initialized to 0, pids created by + * ei_make_pid() wont clash with such badly created pids + * using ei_self() unless user also modified serial, but + * that has at least never been suggested by the + * documentation. + */ + +#ifdef EI_MAKE_PID_ATOMIC__ + { + unsigned int xchg = __atomic_load_n(&ec->pidsn, __ATOMIC_RELAXED); + do { + new = xchg + 1; + if ((new & 0x0fff8000) == 0) + new = 0x8000; /* serial==0 -> serial=1 num=0 */ + } while(!__atomic_compare_exchange_n(&ec->pidsn, &xchg, new, 0, + __ATOMIC_ACQ_REL, + __ATOMIC_RELAXED)); + } +#else /* !EI_MAKE_PID_ATOMIC__ */ + +#ifdef _REENTRANT + ei_mutex_lock(pid_mtx, 0); +#endif + + new = ec->pidsn + 1; + if ((new & 0x0fff8000) == 0) + new = 0x8000; /* serial==0 -> serial=1 num=0 */ + + ec->pidsn = new; + +#ifdef _REENTRANT + ei_mutex_unlock(pid_mtx); +#endif + +#endif /* !EI_MAKE_PID_ATOMIC__ */ + + pid->num = new & 0x7fff; /* 15-bits */ + pid->serial = (new >> 15) & 0x1fff; /* 13-bits */ + + return 0; +} + +/* + * ei_make_ref() + */ + +#undef EI_MAKE_REF_ATOMIC__ +#ifdef _REENTRANT +# if ((SIZEOF_LONG == 8 || SIZEOF_LONGLONG == 8) \ + && (ETHR_HAVE___atomic_compare_exchange_n & 8) \ + && (ETHR_HAVE___atomic_load_n & 8)) +# define EI_MAKE_REF_ATOMIC__ +# if SIZEOF_LONG == 8 +typedef unsigned long ei_atomic_ref__; +# else +typedef unsigned long long ei_atomic_ref__; +# endif +# else /* !EI_MAKE_REF_ATOMIC__ */ +static ei_mutex_t *ref_mtx = NULL; +# endif /* !EI_MAKE_REF_ATOMIC__ */ +#endif /* _REENTRANT */ + +/* + * We use a global counter for all c-nodes in this process. + * We wont wrap anyway due to the enormous amount of values + * available. + */ +#ifdef EI_MAKE_REF_ATOMIC__ +static ei_atomic_ref__ ref_count; +#else +static unsigned int ref_count[3]; +#endif + +static int +init_make_ref(int late) +{ + +#ifdef EI_MAKE_REF_ATOMIC__ + ref_count = 0; +#else /* !EI_MAKE_REF_ATOMIC__ */ + +#ifdef _REENTRANT + + if (late) + return ENOTSUP; /* Refuse doing unsafe initialization... */ + + ref_mtx = ei_mutex_create(); + if (!ref_mtx) + return ENOMEM; + +#endif /* _REENTRANT */ + + ref_count[0] = 0; + ref_count[1] = 0; + ref_count[2] = 0; + +#endif /* !EI_MAKE_REF_ATOMIC__ */ + + return 0; +} + +int ei_make_ref(ei_cnode *ec, erlang_ref *ref) +{ + int err; + if (!ei_connect_initialized) { + fprintf(stderr,"<ERROR> erl_interface not initialized\n"); + exit(1); + } + + err = check_initialized_node(ec); + if (err) { + /* + * write invalid utf8 in nodename which will make + * ei_encode_ref() fail if used... + */ + ref->node[0] = 0xff; + ref->node[1] = 0; + ref->len = -1; + return err; + } + + strcpy(ref->node, ec->thisnodename); + ref->creation = ec->creation; + ref->len = 3; + +#ifdef EI_MAKE_REF_ATOMIC__ + { + ei_atomic_ref__ xchg, new; + xchg = __atomic_load_n(&ref_count, __ATOMIC_RELAXED); + do { + new = xchg + 1; + } while(!__atomic_compare_exchange_n(&ref_count, &xchg, new, 0, + __ATOMIC_ACQ_REL, + __ATOMIC_RELAXED)); + ref->n[0] = (unsigned int) (new & 0x3ffff); + ref->n[1] = (unsigned int) ((new >> 18) & 0xffffffff); + ref->n[2] = (unsigned int) ((new >> (18+32)) & 0xffffffff); + } +#else /* !EI_MAKE_REF_ATOMIC__ */ + +#ifdef _REENTRANT + ei_mutex_lock(ref_mtx, 0); +#endif + + ref->n[0] = ref_count[0]; + ref->n[1] = ref_count[1]; + ref->n[2] = ref_count[2]; + + ref_count[0]++; + ref_count[0] &= 0x3ffff; + if (ref_count[0] == 0) { + ref_count[1]++; + ref_count[1] &= 0xffffffff; + if (ref_count[1] == 0) { + ref_count[2]++; + ref_count[2] &= 0xffffffff; + } + } + +#ifdef _REENTRANT + ei_mutex_unlock(ref_mtx); +#endif + +#endif /* !EI_MAKE_REF_ATOMIC__ */ + + return 0; +} + /* two internal functions that will let us support different cookies * (to be able to connect to other nodes that don't have the same * cookie as each other or us) @@ -616,6 +877,18 @@ static int init_connect(int late) return error; } + error = init_make_ref(late); + if (error) { + EI_TRACE_ERR0("ei_init_connect","can't initiate ei_make_ref()"); + return error; + } + + error = init_make_pid(late); + if (error) { + EI_TRACE_ERR0("ei_init_connect","can't initiate ei_make_pid()"); + return error; + } + ei_connect_initialized = !0; return 0; } @@ -650,6 +923,7 @@ int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, } ec->creation = creation; + ec->pidsn = 0; if (cookie) { if (strlen(cookie) >= sizeof(ec->ei_connect_cookie)) { @@ -1475,11 +1749,8 @@ int ei_receive(int fd, unsigned char *bufp, int bufsize) int ei_reg_send_tmo(ei_cnode* ec, int fd, char *server_name, char* buf, int len, unsigned ms) { - erlang_pid *self = ei_self(ec); - self->num = fd; - /* erl_errno and return code is set by ei_reg_send_encoded_tmo() */ - return ei_send_reg_encoded_tmo(fd, self, server_name, buf, len, ms); + return ei_send_reg_encoded_tmo(fd, ei_self(ec), server_name, buf, len, ms); } @@ -1588,27 +1859,45 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, ei_x_buff x; erlang_pid *self = ei_self(ec); - self->num = fd; + int err = ERL_ERROR; /* encode header */ - ei_x_new_with_version(&x); - ei_x_encode_tuple_header(&x, 2); /* A */ - - self->num = fd; - ei_x_encode_pid(&x, self); /* A 1 */ - - ei_x_encode_tuple_header(&x, 5); /* B A 2 */ - ei_x_encode_atom(&x, "call"); /* B 1 */ - ei_x_encode_atom(&x, mod); /* B 2 */ - ei_x_encode_atom(&x, fun); /* B 3 */ - ei_x_append_buf(&x, buf, len); /* B 4 */ - ei_x_encode_atom(&x, "user"); /* B 5 */ - - /* ei_x_encode_atom(&x,"user"); */ - ei_send_reg_encoded(fd, self, "rex", x.buff, x.index); - ei_x_free(&x); - + if (ei_x_new_with_version(&x) < 0) + goto einval; + if (ei_x_encode_tuple_header(&x, 2) < 0) /* A */ + goto einval; + + if (ei_x_encode_pid(&x, self) < 0) /* A 1 */ + goto einval; + + if (ei_x_encode_tuple_header(&x, 5) < 0) /* B A 2 */ + goto einval; + if (ei_x_encode_atom(&x, "call") < 0) /* B 1 */ + goto einval; + if (ei_x_encode_atom(&x, mod) < 0) /* B 2 */ + goto einval; + if (ei_x_encode_atom(&x, fun) < 0) /* B 3 */ + goto einval; + if (ei_x_append_buf(&x, buf, len) < 0) /* B 4 */ + goto einval; + if (ei_x_encode_atom(&x, "user") < 0) /* B 5 */ + goto einval; + + err = ei_send_reg_encoded(fd, self, "rex", x.buff, x.index); + if (err) + goto error; + + ei_x_free(&x); + return 0; + +einval: + EI_CONN_SAVE_ERRNO__(EINVAL); + +error: + if (x.buff != NULL) + ei_x_free(&x); + return err; } /* rpc_to */ /* @@ -1626,11 +1915,6 @@ int ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, return res; } /* rpc_from */ - /* - * A true RPC. It return a NULL pointer - * in case of failure, otherwise a valid - * (ETERM *) pointer containing the reply - */ int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun, const char* inbuf, int inbuflen, ei_x_buff* x) { @@ -1640,27 +1924,45 @@ int ei_rpc(ei_cnode* ec, int fd, char *mod, char *fun, char rex[MAXATOMLEN]; if (ei_rpc_to(ec, fd, mod, fun, inbuf, inbuflen) < 0) { - return -1; + return ERL_ERROR; } - /* FIXME are we not to reply to the tick? */ + + /* ei_rpc_from() responds with a tick if it gets one... */ while ((i = ei_rpc_from(ec, fd, ERL_NO_TIMEOUT, &msg, x)) == ERL_TICK) ; - if (i == ERL_ERROR) return -1; - /*ep = 'erl'_element(2,emsg.msg);*/ /* {RPC_Tag, RPC_Reply} */ + if (i == ERL_ERROR) return i; + + /* Expect: {rex, RPC_Reply} */ + index = 0; - if (ei_decode_version(x->buff, &index, &i) < 0 - || ei_decode_ei_term(x->buff, &index, &t) < 0) - return -1; /* FIXME ei_decode_version don't set erl_errno as before */ - /* FIXME this is strange, we don't check correct "rex" atom - and we let it pass if not ERL_SMALL_TUPLE_EXT and arity == 2 */ - if (t.ei_type == ERL_SMALL_TUPLE_EXT && t.arity == 2) - if (ei_decode_atom(x->buff, &index, rex) < 0) - return -1; + if (ei_decode_version(x->buff, &index, &i) < 0) + goto ebadmsg; + + if (ei_decode_ei_term(x->buff, &index, &t) < 0) + goto ebadmsg; + + if (t.ei_type != ERL_SMALL_TUPLE_EXT && t.ei_type != ERL_LARGE_TUPLE_EXT) + goto ebadmsg; + + if (t.arity != 2) + goto ebadmsg; + + if (ei_decode_atom(x->buff, &index, rex) < 0) + goto ebadmsg; + + if (strcmp("rex", rex) != 0) + goto ebadmsg; + /* remove header */ x->index -= index; memmove(x->buff, &x->buff[index], x->index); return 0; + +ebadmsg: + + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } diff --git a/lib/erl_interface/src/connect/eirecv.c b/lib/erl_interface/src/connect/eirecv.c index 0673d25d38..96732db5b6 100644 --- a/lib/erl_interface/src/connect/eirecv.c +++ b/lib/erl_interface/src/connect/eirecv.c @@ -70,7 +70,7 @@ ei_recv_internal (int fd, err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } /* get length field */ @@ -80,7 +80,7 @@ ei_recv_internal (int fd, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } len = get32be(s); @@ -91,7 +91,7 @@ ei_recv_internal (int fd, ssize_t wlen = sizeof(tock); ei_write_fill_ctx_t__(cbs, ctx, tock, &wlen, tmo); /* Failure no problem */ *msglenp = 0; - return 0; /* maybe flag ERL_EAGAIN [sverkerw] */ + return ERL_TICK; } /* turn off tracing on each receive. it will be turned back on if @@ -106,7 +106,7 @@ ei_recv_internal (int fd, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } /* now decode header */ @@ -119,8 +119,8 @@ ei_recv_internal (int fd, || ei_decode_tuple_header(header,&index,&arity) || ei_decode_long(header,&index,&msg->msgtype)) { - erl_errno = EIO; /* Maybe another code for decoding errors */ - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } switch (msg->msgtype) { @@ -129,8 +129,8 @@ ei_recv_internal (int fd, if (ei_decode_atom_as(header,&index,msg->cookie,sizeof(msg->cookie),ERLANG_UTF8,NULL,NULL) || ei_decode_pid(header,&index,&msg->to)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } break; @@ -141,8 +141,8 @@ ei_recv_internal (int fd, || ei_decode_atom_as(header,&index,msg->cookie,sizeof(msg->cookie),ERLANG_UTF8,NULL,NULL) || ei_decode_atom_as(header,&index,msg->toname,sizeof(msg->toname),ERLANG_UTF8,NULL,NULL)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } /* actual message is remaining part of headerbuf, plus any unread bytes */ @@ -155,8 +155,8 @@ ei_recv_internal (int fd, if (ei_decode_pid(header,&index,&msg->from) || ei_decode_pid(header,&index,&msg->to)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } break; @@ -167,8 +167,8 @@ ei_recv_internal (int fd, if (ei_decode_pid(header,&index,&msg->from) || ei_decode_pid(header,&index,&msg->to)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } break; @@ -179,8 +179,8 @@ ei_recv_internal (int fd, || ei_decode_pid(header,&index,&msg->to) || ei_decode_trace(header,&index,&msg->token)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } ei_trace(1,&msg->token); /* turn on tracing */ @@ -193,8 +193,8 @@ ei_recv_internal (int fd, || ei_decode_atom_as(header,&index,msg->toname,sizeof(msg->toname),ERLANG_UTF8,NULL,NULL) || ei_decode_trace(header,&index,&msg->token)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } ei_trace(1,&msg->token); /* turn on tracing */ @@ -207,8 +207,8 @@ ei_recv_internal (int fd, || ei_decode_pid(header,&index,&msg->to) || ei_decode_trace(header,&index,&msg->token)) { - erl_errno = EIO; - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } ei_trace(1,&msg->token); /* turn on tracing */ @@ -235,14 +235,14 @@ ei_recv_internal (int fd, err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo); if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } if (rlen == 0) break; remain -= rlen; } erl_errno = EMSGSIZE; - return -1; + return ERL_ERROR; } else { /* Dynamic buffer --- grow it. */ @@ -253,7 +253,7 @@ ei_recv_internal (int fd, if ((mbuf = realloc(*mbufp, msglen)) == NULL) { erl_errno = ENOMEM; - return -1; + return ERL_ERROR; } *mbufp = mbuf; @@ -276,7 +276,7 @@ ei_recv_internal (int fd, if (err) { *msglenp = bytesread-index+1; /* actual bytes in users buffer */ EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } } diff --git a/lib/erl_interface/src/connect/send.c b/lib/erl_interface/src/connect/send.c index f9337187ab..df2e3e023d 100644 --- a/lib/erl_interface/src/connect/send.c +++ b/lib/erl_interface/src/connect/send.c @@ -67,19 +67,31 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to, /* check that he can receive trace tokens first */ if (ei_distversion(fd) > 0) token = ei_trace(0,NULL); + EI_CONN_SAVE_ERRNO__(EINVAL); + /* header = SEND, cookie, to max sizes: */ - ei_encode_version(header,&index); /* 1 */ + if (ei_encode_version(header,&index) < 0) /* 1 */ + return ERL_ERROR; if (token) { - ei_encode_tuple_header(header,&index,4); /* 2 */ - ei_encode_long(header,&index,ERL_SEND_TT); /* 2 */ + if (ei_encode_tuple_header(header,&index,4) < 0) /* 2 */ + return ERL_ERROR; + if (ei_encode_long(header,&index,ERL_SEND_TT) < 0) /* 2 */ + return ERL_ERROR; } else { - ei_encode_tuple_header(header,&index,3); - ei_encode_long(header,&index,ERL_SEND); + if (ei_encode_tuple_header(header,&index,3) < 0) + return ERL_ERROR; + if (ei_encode_long(header,&index,ERL_SEND) < 0) + return ERL_ERROR; } - ei_encode_atom(header,&index,ei_getfdcookie(fd)); /* 258 */ - ei_encode_pid(header,&index,to); /* 268 */ + if (ei_encode_atom(header,&index,ei_getfdcookie(fd)) < 0) /* 258 */ + return ERL_ERROR; + if (ei_encode_pid(header,&index,to) < 0) /* 268 */ + return ERL_ERROR; - if (token) ei_encode_trace(header,&index,token); /* 534 */ + if (token) { + if (ei_encode_trace(header,&index,token) < 0) /* 534 */ + return ERL_ERROR; + } /* control message (precedes header actually) */ /* length = 1 ('p') + header len + message len */ @@ -107,9 +119,11 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } + erl_errno = 0; + return 0; } #endif /* EI_HAVE_STRUCT_IOVEC__ */ @@ -121,7 +135,7 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } len = tot_len = (ssize_t) msglen; @@ -130,9 +144,10 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } + erl_errno = 0; return 0; } diff --git a/lib/erl_interface/src/connect/send_exit.c b/lib/erl_interface/src/connect/send_exit.c index af0fb2af88..231aad0ae0 100644 --- a/lib/erl_interface/src/connect/send_exit.c +++ b/lib/erl_interface/src/connect/send_exit.c @@ -67,9 +67,12 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to, return ERL_ERROR; } - if (len > EISMALLBUF) - if (!(dbuf = malloc(len))) - return -1; + if (len > EISMALLBUF) { + if (!(dbuf = malloc(len))) { + EI_CONN_SAVE_ERRNO__(ENOMEM); + return ERL_ERROR; + } + } msgbuf = (dbuf ? dbuf : sbuf); @@ -77,31 +80,46 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to, /* check that he can receive trace tokens first */ if (ei_distversion(fd) > 0) token = ei_trace(0,NULL); + EI_CONN_SAVE_ERRNO__(EINVAL); + index = 5; /* max sizes: */ - ei_encode_version(msgbuf,&index); /* 1 */ + if (ei_encode_version(msgbuf,&index) < 0) /* 1 */ + return ERL_ERROR; if (token) { - ei_encode_tuple_header(msgbuf,&index,5); /* 2 */ - ei_encode_long(msgbuf,&index,ERL_EXIT_TT); /* 2 */ + if (ei_encode_tuple_header(msgbuf,&index,5) < 0) /* 2 */ + return ERL_ERROR; + if (ei_encode_long(msgbuf,&index,ERL_EXIT_TT) < 0)/* 2 */ + return ERL_ERROR; } else { - ei_encode_tuple_header(msgbuf,&index,4); - ei_encode_long(msgbuf,&index,ERL_EXIT); + if (ei_encode_tuple_header(msgbuf,&index,4) < 0) + return ERL_ERROR; + if (ei_encode_long(msgbuf,&index,ERL_EXIT) < 0) + return ERL_ERROR; } - ei_encode_pid(msgbuf,&index,from); /* 268 */ - ei_encode_pid(msgbuf,&index,to); /* 268 */ + if (ei_encode_pid(msgbuf,&index,from) < 0) /* 268 */ + return ERL_ERROR; + if (ei_encode_pid(msgbuf,&index,to) < 0) /* 268 */ + return ERL_ERROR; - if (token) ei_encode_trace(msgbuf,&index,token); /* 534 */ + if (token) { + if (ei_encode_trace(msgbuf,&index,token) < 0) /* 534 */ + return ERL_ERROR; + } /* Reason */ - ei_encode_string(msgbuf,&index,reason); /* len */ + if (ei_encode_string(msgbuf,&index,reason) < 0) /* len */ + return ERL_ERROR; /* 5 byte header missing */ s = msgbuf; put32be(s, index - 4); /* 4 */ - put8(s, ERL_PASS_THROUGH); /* 1 */ + put8(s, ERL_PASS_THROUGH); /* 1 */ /*** sum: len + 1080 */ - if (ei_tracelevel >= 4) - ei_show_sendmsg(stderr,msgbuf,NULL); + if (ei_tracelevel >= 4) { + if (ei_show_sendmsg(stderr,msgbuf,NULL) < 0) + return ERL_ERROR; + } wlen = (ssize_t) index; err = ei_write_fill_ctx_t__(cbs, ctx, msgbuf, &wlen, tmo); @@ -113,6 +131,8 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to, EI_CONN_SAVE_ERRNO__(err); return ERL_ERROR; } + + erl_errno = 0; return 0; } diff --git a/lib/erl_interface/src/connect/send_reg.c b/lib/erl_interface/src/connect/send_reg.c index b5c9e6a6f8..1d9c85f210 100644 --- a/lib/erl_interface/src/connect/send_reg.c +++ b/lib/erl_interface/src/connect/send_reg.c @@ -64,21 +64,33 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, if (ei_distversion(fd) > 0) token = ei_trace(0,NULL); + EI_CONN_SAVE_ERRNO__(EINVAL); + /* header = REG_SEND, from, cookie, toname max sizes: */ - ei_encode_version(header,&index); /* 1 */ + if (ei_encode_version(header,&index) < 0) /* 1 */ + return ERL_ERROR; if (token) { - ei_encode_tuple_header(header,&index,5); /* 2 */ - ei_encode_long(header,&index,ERL_REG_SEND_TT); /* 2 */ + if (ei_encode_tuple_header(header,&index,5) < 0) /* 2 */ + return ERL_ERROR; + if (ei_encode_long(header,&index,ERL_REG_SEND_TT) < 0) /* 2 */ + return ERL_ERROR; } else { - ei_encode_tuple_header(header,&index,4); - ei_encode_long(header,&index,ERL_REG_SEND); + if (ei_encode_tuple_header(header,&index,4) < 0) + return ERL_ERROR; + if (ei_encode_long(header,&index,ERL_REG_SEND) < 0) + return ERL_ERROR; } - ei_encode_pid(header, &index, from); /* 268 */ - ei_encode_atom(header, &index, ei_getfdcookie(fd)); /* 258 */ - ei_encode_atom(header, &index, to); /* 268 */ - - if (token) ei_encode_trace(header,&index,token); /* 534 */ + if (ei_encode_pid(header, &index, from) < 0) /* 268 */ + return ERL_ERROR; + if (ei_encode_atom(header, &index, ei_getfdcookie(fd)) < 0) /* 258 */ + return ERL_ERROR; + if (ei_encode_atom(header, &index, to) < 0) /* 268 */ + return ERL_ERROR; + if (token) { + if (ei_encode_trace(header,&index,token) < 0) /* 534 */ + return ERL_ERROR; + } /* control message (precedes header actually) */ /* length = 1 ('p') + header len + message len */ s = header; @@ -103,8 +115,11 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } + + erl_errno = 0; + return 0; } #endif /* EI_HAVE_STRUCT_IOVEC__ */ @@ -116,7 +131,7 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } len = tot_len = (ssize_t) msglen; @@ -125,8 +140,10 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, err = EIO; if (err) { EI_CONN_SAVE_ERRNO__(err); - return -1; + return ERL_ERROR; } + + erl_errno = 0; return 0; } diff --git a/lib/erl_interface/src/decode/decode_iodata.c b/lib/erl_interface/src/decode/decode_iodata.c new file mode 100644 index 0000000000..88d23d8a1e --- /dev/null +++ b/lib/erl_interface/src/decode/decode_iodata.c @@ -0,0 +1,166 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2020. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + */ + +#include <string.h> +#include "eidef.h" +#include "eiext.h" +#include "putget.h" + +static int decode_list_ext_iodata(const char *buf, int* index, + int *szp, unsigned char **pp); +static void decode_string(const char *buf, int* index, + int *szp, unsigned char **pp); + +int ei_decode_iodata(const char *buf, int* index, int *szp, char *out_buf) +{ + int type, len; + + if (szp) + *szp = 0; + + if (ei_get_type(buf, index, &type, &len) < 0) + return -1; + + /* Top level of iodata is either a list or a binary... */ + + switch (type) { + + case ERL_BINARY_EXT: { + long llen; + if (ei_decode_binary(buf, index, out_buf, &llen) < 0) + return -1; + if (llen != (long) len) + return -1; /* general 64-bit issue with ei api... */ + if (szp) + *szp += len; + return 0; + } + + case ERL_STRING_EXT: { + unsigned char *p = (unsigned char *) out_buf; + decode_string(buf, index, szp, p ? &p : NULL); + return 0; + } + case ERL_NIL_EXT: + return ei_decode_list_header(buf, index, NULL); + + case ERL_LIST_EXT: { + unsigned char *ptr = (unsigned char *) out_buf; + len = 0; + return decode_list_ext_iodata(buf, index, + szp ? szp : &len, + ptr ? &ptr : NULL); + } + + default: + return -1; /* Not a list nor a binary... */ + } +} + +static int decode_list_ext_iodata(const char *buf, int* index, + int *szp, unsigned char **pp) +{ + int type, len, i, conses; + + if (ei_decode_list_header(buf, index, &conses) < 0) + return -1; + + for (i = 0; i <= conses; i++) { + + if (ei_get_type(buf, index, &type, &len) < 0) + return -1; + + switch (type) { + case ERL_SMALL_INTEGER_EXT: + case ERL_INTEGER_EXT: { + long val; + if (i == conses) + return -1; /* int not allowed in cdr of cons */ + if (ei_decode_long(buf, index, &val) < 0) + return -1; + if (val < 0 || 255 < val) + return -1; + if (pp) + *((*pp)++) = (unsigned char) val; + *szp += 1; + break; + } + case ERL_BINARY_EXT: { + void *p = pp ? *pp : NULL; + long llen; + if (ei_decode_binary(buf, index, p, &llen) < 0) + return -1; + if (llen != (long) len) + return -1; /* general 64-bit issue with ei api... */ + if (pp) + *pp += len; + *szp += len; + break; + } + case ERL_STRING_EXT: + decode_string(buf, index, szp, pp); + break; + case ERL_LIST_EXT: + if (decode_list_ext_iodata(buf, index, szp, pp) < 0) + return -1; + break; + case ERL_NIL_EXT: + if (ei_decode_list_header(buf, index, NULL) < 0) + return -1; + break; + default: + /* Not a list, a binary, nor a byte sized integer... */ + return -1; + } + } + + return 0; +} + +static void +decode_string(const char *buf, int* index, int *szp, unsigned char **pp) +{ + /* + * ei_decode_string() null-terminates the string + * which we do not want, so we decode it ourselves + * here instead... + */ + int len; + char *s = (char *) buf + *index; + char *s0 = s; + + /* ASSERT(*s == ERL_STRING_EXT); */ + s++; + + len = get16be(s); + + if (pp) { + memcpy(*pp, s, len); + *pp += len; + } + + if (szp) + *szp += len; + + s += len; + *index += s-s0; + +} diff --git a/lib/erl_interface/src/decode/decode_ref.c b/lib/erl_interface/src/decode/decode_ref.c index c9b38c1c3b..c10a02094e 100644 --- a/lib/erl_interface/src/decode/decode_ref.c +++ b/lib/erl_interface/src/decode/decode_ref.c @@ -54,6 +54,9 @@ int ei_decode_ref(const char *buf, int *index, erlang_ref *p) /* first the integer count */ count = get16be(s); + if (count > sizeof(p->n)/sizeof(p->n[0])) + return -1; /* Not enough space in struct... */ + if (p) { p->len = count; if (get_atom(&s, p->node, NULL) < 0) return -1; diff --git a/lib/erl_interface/src/encode/encode_trace.c b/lib/erl_interface/src/encode/encode_trace.c index d0e6aec474..4c51e3d84b 100644 --- a/lib/erl_interface/src/encode/encode_trace.c +++ b/lib/erl_interface/src/encode/encode_trace.c @@ -23,12 +23,18 @@ int ei_encode_trace(char *buf, int *index, const erlang_trace *p) { /* { Flags, Label, Serial, FromPid, Prev } */ - ei_encode_tuple_header(buf,index,5); - ei_encode_long(buf,index,p->flags); - ei_encode_long(buf,index,p->label); - ei_encode_long(buf,index,p->serial); - ei_encode_pid(buf,index,&p->from); - ei_encode_long(buf,index,p->prev); + if (ei_encode_tuple_header(buf,index,5) < 0) + return -1; + if (ei_encode_long(buf,index,p->flags) < 0) + return -1; + if (ei_encode_long(buf,index,p->label) < 0) + return -1; + if (ei_encode_long(buf,index,p->serial) < 0) + return -1; + if (ei_encode_pid(buf,index,&p->from) < 0) + return -1; + if (ei_encode_long(buf,index,p->prev) < 0) + return -1; /* index is updated by the functions we called */ diff --git a/lib/erl_interface/src/global/global_names.c b/lib/erl_interface/src/global/global_names.c index bcbe740223..4524439467 100644 --- a/lib/erl_interface/src/global/global_names.c +++ b/lib/erl_interface/src/global/global_names.c @@ -26,6 +26,7 @@ #include "ei_connect_int.h" #include "ei.h" #include "ei_connect.h" +#include "ei_internal.h" #define GLOBALNAMEBUF (16*1024) /* not very small actually */ @@ -52,16 +53,18 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count) char **names; char *s; - self->num = fd; - ei_encode_version(buf,&index); - ei_encode_tuple_header(buf,&index,2); - ei_encode_pid(buf,&index,self); /* PidFrom */ - ei_encode_tuple_header(buf,&index,5); - ei_encode_atom(buf,&index,"call"); /* call */ - ei_encode_atom(buf,&index,"global"); /* Mod */ - ei_encode_atom(buf,&index,"registered_names"); /* Fun */ - ei_encode_list_header(buf,&index,0); /* Args: [ ] */ - ei_encode_atom(buf,&index,"user"); /* user */ + if (ei_encode_version(buf,&index) + || ei_encode_tuple_header(buf,&index,2) + || ei_encode_pid(buf,&index,self) /* PidFrom */ + || ei_encode_tuple_header(buf,&index,5) + || ei_encode_atom(buf,&index,"call") /* call */ + || ei_encode_atom(buf,&index,"global") /* Mod */ + || ei_encode_atom(buf,&index,"registered_names")/* Fun */ + || ei_encode_list_header(buf,&index,0) /* Args: [ ] */ + || ei_encode_atom(buf,&index,"user")) { /* user */ + EI_CONN_SAVE_ERRNO__(EINVAL); + return NULL; + } /* make the rpc call */ if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return NULL; @@ -72,7 +75,10 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count) else break; } - if (i != ERL_SEND) return NULL; + if (i != ERL_SEND) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return NULL; + } /* expecting { rex, [name1, name2, ...] } */ size = msglen; @@ -83,7 +89,10 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count) || (arity != 2) || ei_decode_atom(buf,&index,tmpbuf) || strcmp(tmpbuf,"rex") - || ei_decode_list_header(buf,&index,&arity)) return NULL; + || ei_decode_list_header(buf,&index,&arity)) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return NULL; + } /* we use the size of the rest of the received message to estimate @@ -92,7 +101,10 @@ char **ei_global_names(ei_cnode *ec, int fd, int *count) * a little less than the atoms themselves needed in the reply. */ arity++; /* we will need a terminating NULL as well */ - if (!(names = malloc((arity * sizeof(char**)) + (size-index)))) return NULL; + if (!(names = malloc((arity * sizeof(char**)) + (size-index)))) { + EI_CONN_SAVE_ERRNO__(ENOMEM); + return NULL; + } /* arity pointers first, followed by s */ s = (char *)(names+arity); diff --git a/lib/erl_interface/src/global/global_register.c b/lib/erl_interface/src/global/global_register.c index c260ce091c..dbdab8dbc5 100644 --- a/lib/erl_interface/src/global/global_register.c +++ b/lib/erl_interface/src/global/global_register.c @@ -23,6 +23,7 @@ #include "eisend.h" #include "eirecv.h" #include "ei.h" +#include "ei_internal.h" int ei_global_register(int fd, const char *name, erlang_pid *self) { @@ -40,24 +41,27 @@ int ei_global_register(int fd, const char *name, erlang_pid *self) /* set up rpc arguments */ /* { PidFrom, { call, Mod, Fun, Args, user }} */ index = 0; - ei_encode_version(buf,&index); - ei_encode_tuple_header(buf,&index,2); - ei_encode_pid(buf,&index,self); /* PidFrom */ - ei_encode_tuple_header(buf,&index,5); - ei_encode_atom(buf,&index,"call"); /* call */ - ei_encode_atom(buf,&index,"global"); /* Mod */ - ei_encode_atom(buf,&index,"register_name_external"); /* Fun */ - ei_encode_list_header(buf,&index,3); /* Args: [ name, self(), cnode ] */ - ei_encode_atom(buf,&index,name); - ei_encode_pid(buf,&index,self); - ei_encode_tuple_header(buf,&index,2); - ei_encode_atom(buf,&index,"global"); /* special "resolve" treatment */ - ei_encode_atom(buf,&index,"cnode"); /* i.e. we get a SEND when conflict */ - ei_encode_empty_list(buf,&index); - ei_encode_atom(buf,&index,"user"); /* user */ + if (ei_encode_version(buf,&index) + || ei_encode_tuple_header(buf,&index,2) + || ei_encode_pid(buf,&index,self) /* PidFrom */ + || ei_encode_tuple_header(buf,&index,5) + || ei_encode_atom(buf,&index,"call") /* call */ + || ei_encode_atom(buf,&index,"global") /* Mod */ + || ei_encode_atom(buf,&index,"register_name_external")/* Fun */ + || ei_encode_list_header(buf,&index,3) /* Args: [ name, self(), cnode ] */ + || ei_encode_atom(buf,&index,name) + || ei_encode_pid(buf,&index,self) + || ei_encode_tuple_header(buf,&index,2) + || ei_encode_atom(buf,&index,"global") /* special "resolve" treatment */ + || ei_encode_atom(buf,&index,"cnode") /* i.e. we get a SEND when conflict */ + || ei_encode_empty_list(buf,&index) + || ei_encode_atom(buf,&index,"user")) { /* user */ + EI_CONN_SAVE_ERRNO__(EINVAL); + return ERL_ERROR; + } /* make the rpc call */ - if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1; + if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR; /* get the reply: expect link and an atom, or just an atom */ needlink = needatom = needmonitor = 1; @@ -72,19 +76,28 @@ int ei_global_register(int fd, const char *name, erlang_pid *self) switch (i) { case ERL_LINK: /* got link */ - if (!needlink) return -1; + if (!needlink) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; + } needlink = 0; break; case ERL_MONITOR_P-10: /* got monitor */ - if (!needmonitor) { return -1;} + if (!needmonitor) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; + } needmonitor = 0; break; case ERL_SEND: /* got message - does it contain our atom? */ - if (!needatom) return -1; + if (!needatom) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; + } else { /* expecting { rex, yes } */ index = 0; @@ -94,8 +107,10 @@ int ei_global_register(int fd, const char *name, erlang_pid *self) || ei_decode_atom(buf,&index,tmpbuf) || strcmp(tmpbuf,"rex") || ei_decode_atom(buf,&index,tmpbuf) - || strcmp(tmpbuf,"yes")) - return -1; /* bad response from other side */ + || strcmp(tmpbuf,"yes")) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; /* bad response from other side */ + } /* we're done */ return 0; @@ -103,7 +118,8 @@ int ei_global_register(int fd, const char *name, erlang_pid *self) break; default: - return -1; /* something else */ + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; /* something else */ } } return 0; diff --git a/lib/erl_interface/src/global/global_unregister.c b/lib/erl_interface/src/global/global_unregister.c index ee785a2abd..4743fe65d7 100644 --- a/lib/erl_interface/src/global/global_unregister.c +++ b/lib/erl_interface/src/global/global_unregister.c @@ -25,6 +25,7 @@ #include "ei_connect_int.h" #include "ei_connect.h" #include "ei.h" +#include "ei_internal.h" /* remove the association between name and its pid */ /* global:unregister_name(name) -> ok */ @@ -40,22 +41,23 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name) int version,arity,msglen; int needunlink, needatom, needdemonitor; - /* make a self pid */ - self->num = fd; - ei_encode_version(buf,&index); - ei_encode_tuple_header(buf,&index,2); - ei_encode_pid(buf,&index,self); /* PidFrom */ - ei_encode_tuple_header(buf,&index,5); - ei_encode_atom(buf,&index,"call"); /* call */ - ei_encode_atom(buf,&index,"global"); /* Mod */ - ei_encode_atom(buf,&index,"unregister_name_external"); /* Fun */ - ei_encode_list_header(buf,&index,1); /* Args: [ name ] */ - ei_encode_atom(buf,&index,name); - ei_encode_empty_list(buf,&index); - ei_encode_atom(buf,&index,"user"); /* user */ + if (ei_encode_version(buf,&index) + || ei_encode_tuple_header(buf,&index,2) + || ei_encode_pid(buf,&index,self) /* PidFrom */ + || ei_encode_tuple_header(buf,&index,5) + || ei_encode_atom(buf,&index,"call") /* call */ + || ei_encode_atom(buf,&index,"global") /* Mod */ + || ei_encode_atom(buf,&index,"unregister_name_external") /* Fun */ + || ei_encode_list_header(buf,&index,1) /* Args: [ name ] */ + || ei_encode_atom(buf,&index,name) + || ei_encode_empty_list(buf,&index) + || ei_encode_atom(buf,&index,"user")) { /* user */ + EI_CONN_SAVE_ERRNO__(EINVAL); + return ERL_ERROR; + } /* make the rpc call */ - if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1; + if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR; /* get the reply: expect unlink and an atom, or just an atom */ needunlink = needatom = needdemonitor = 1; @@ -70,19 +72,28 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name) switch (i) { case ERL_UNLINK: /* got unlink */ - if (!needunlink) return -1; + if (!needunlink) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; + } needunlink = 0; break; case ERL_DEMONITOR_P-10: /* got demonitor */ - if (!needdemonitor) return -1; + if (!needdemonitor) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; + } needdemonitor = 0; break; case ERL_SEND: /* got message - does it contain our atom? */ - if (!needatom) return -1; + if (!needatom) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; + } else { /* expecting { rex, ok } */ index = 0; @@ -92,8 +103,10 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name) || ei_decode_atom(buf,&index,tmpbuf) || strcmp(tmpbuf,"rex") || ei_decode_atom(buf,&index,tmpbuf) - || strcmp(tmpbuf,"ok")) - return -1; /* bad response from other side */ + || strcmp(tmpbuf,"ok")) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; /* bad response from other side */ + } /* we're done here */ return 0; @@ -101,7 +114,8 @@ int ei_global_unregister(ei_cnode *ec, int fd, const char *name) break; default: - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } } diff --git a/lib/erl_interface/src/global/global_whereis.c b/lib/erl_interface/src/global/global_whereis.c index afedc98030..5a6134c2ab 100644 --- a/lib/erl_interface/src/global/global_whereis.c +++ b/lib/erl_interface/src/global/global_whereis.c @@ -26,6 +26,7 @@ #include "ei_connect_int.h" #include "ei.h" #include "ei_connect.h" +#include "ei_internal.h" /* return the ETERM pid corresponding to name. If caller * provides non-NULL node, nodename will be returned there @@ -44,22 +45,22 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c int i; int version,arity,msglen; - self->num = fd; /* FIXME looks strange to change something?! */ - - ei_encode_version(buf,&index); - ei_encode_tuple_header(buf,&index,2); - ei_encode_pid(buf,&index,self); /* PidFrom */ - ei_encode_tuple_header(buf,&index,5); - ei_encode_atom(buf,&index,"call"); /* call */ - ei_encode_atom(buf,&index,"global"); /* Mod */ - ei_encode_atom(buf,&index,"whereis_name"); /* Fun */ - ei_encode_list_header(buf,&index,1); /* Args: [ name ] */ - ei_encode_atom(buf,&index,name); - ei_encode_empty_list(buf,&index); - ei_encode_atom(buf,&index,"user"); /* user */ - + if (ei_encode_version(buf,&index) + || ei_encode_tuple_header(buf,&index,2) + || ei_encode_pid(buf,&index,self) /* PidFrom */ + || ei_encode_tuple_header(buf,&index,5) + || ei_encode_atom(buf,&index,"call") /* call */ + || ei_encode_atom(buf,&index,"global") /* Mod */ + || ei_encode_atom(buf,&index,"whereis_name") /* Fun */ + || ei_encode_list_header(buf,&index,1) /* Args: [ name ] */ + || ei_encode_atom(buf,&index,name) + || ei_encode_empty_list(buf,&index) + || ei_encode_atom(buf,&index,"user")) { /* user */ + EI_CONN_SAVE_ERRNO__(EINVAL); + return ERL_ERROR; + } /* make the rpc call */ - if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return -1; + if (ei_send_reg_encoded(fd,self,"rex",buf,index)) return ERL_ERROR; while (1) { index = EISMALLBUF; @@ -67,7 +68,7 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c else break; } - if (i != ERL_SEND) return -1; + if (i != ERL_SEND) return ERL_ERROR; /* expecting { rex, pid } */ index = 0; @@ -76,8 +77,10 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c || (arity != 2) || ei_decode_atom(buf,&index,tmpbuf) || strcmp(tmpbuf,"rex") - || ei_decode_pid(buf,&index,&epid)) - return -1; /* bad response from other side */ + || ei_decode_pid(buf,&index,&epid)) { + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; /* bad response from other side */ + } /* extract the nodename for the caller */ if (node) { @@ -86,7 +89,8 @@ int ei_global_whereis(ei_cnode *ec, int fd, const char *name, erlang_pid* pid, c strcpy(node, node_str); } else { - return -1; + EI_CONN_SAVE_ERRNO__(EBADMSG); + return ERL_ERROR; } } *pid = epid; diff --git a/lib/erl_interface/src/misc/ei_cmp_nc.c b/lib/erl_interface/src/misc/ei_cmp_nc.c new file mode 100644 index 0000000000..73650f429f --- /dev/null +++ b/lib/erl_interface/src/misc/ei_cmp_nc.c @@ -0,0 +1,117 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2020. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + */ + +#include <string.h> +#include "eidef.h" + +/* + * Comparison of node container types (pid, port, ref). Comparison + * done the same way as ERTS compare them. In ref and port case, + * node info is compared before other data, and in pid case node + * info is compared after other data. + */ + +static int cmp_nodes(char *aname, unsigned int acreation, + char *bname, unsigned int bcreation) +{ + int differ = strcmp(aname, bname); + if (differ) + return differ; + if (acreation == bcreation) + return 0; + if (acreation < bcreation) + return -1; + return 1; +} + +int +ei_cmp_refs(erlang_ref *a, erlang_ref *b) +{ + int differ; + int i, alen, blen; + unsigned int *anum, *bnum; + + differ = cmp_nodes(a->node, a->creation, b->node, b->creation); + if (differ) + return differ; + + alen = a->len; + blen = b->len; + + anum = &a->n[0]; + bnum = &b->n[0]; + + if (alen != blen) { + if (alen > blen) { + do { + if (anum[alen - 1] != 0) + return 1; + alen--; + } while (alen > blen); + } + else { + do { + if (bnum[blen - 1] != 0) + return -1; + blen--; + } while (alen < blen); + } + } + + for (i = alen - 1; i >= 0; i--) + if (anum[i] != bnum[i]) + return anum[i] < bnum[i] ? -1 : 1; + + return 0; +} + +int +ei_cmp_pids(erlang_pid *a, erlang_pid *b) +{ + int differ; + + if (a->serial != b->serial) + return a->serial < b->serial ? -1 : 1; + + if (a->num != b->num) + return a->num < b->num ? -1 : 1; + + differ = cmp_nodes(a->node, a->creation, b->node, b->creation); + if (differ) + return differ; + + return 0; +} + +int +ei_cmp_ports(erlang_port *a, erlang_port *b) +{ + int differ; + + differ = cmp_nodes(a->node, a->creation, b->node, b->creation); + if (differ) + return differ; + + if (a->id != b->id) + return a->id < b->id ? -1 : 1; + + return 0; +} diff --git a/lib/erl_interface/src/misc/ei_decode_term.c b/lib/erl_interface/src/misc/ei_decode_term.c index 79c8fb4388..72f1e0d511 100644 --- a/lib/erl_interface/src/misc/ei_decode_term.c +++ b/lib/erl_interface/src/misc/ei_decode_term.c @@ -47,25 +47,30 @@ int ei_decode_ei_term(const char* buf, int* index, ei_term* term) break; case ERL_FLOAT_EXT: case NEW_FLOAT_EXT: + term->ei_type = ERL_FLOAT_EXT; return (ei_decode_double(buf, index, &term->value.d_val) < 0 ? -1 : 1); case ERL_ATOM_EXT: case ERL_ATOM_UTF8_EXT: case ERL_SMALL_ATOM_EXT: case ERL_SMALL_ATOM_UTF8_EXT: + term->ei_type = ERL_ATOM_EXT; return (ei_decode_atom(buf, index, term->value.atom_name) < 0 ? -1 : 1); case ERL_REFERENCE_EXT: case ERL_NEW_REFERENCE_EXT: case ERL_NEWER_REFERENCE_EXT: + term->ei_type = ERL_NEW_REFERENCE_EXT; return (ei_decode_ref(buf, index, &term->value.ref) < 0 ? -1 : 1); case ERL_PORT_EXT: case ERL_NEW_PORT_EXT: + term->ei_type = ERL_PORT_EXT; return (ei_decode_port(buf, index, &term->value.port) < 0 ? -1 : 1); case ERL_PID_EXT: case ERL_NEW_PID_EXT: + term->ei_type = ERL_PID_EXT; return (ei_decode_pid(buf, index, &term->value.pid) < 0 ? -1 : 1); case ERL_SMALL_TUPLE_EXT: diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h index d881a51398..1805411804 100644 --- a/lib/erl_interface/src/misc/ei_portio.h +++ b/lib/erl_interface/src/misc/ei_portio.h @@ -22,6 +22,8 @@ #ifndef _EI_PORTIO_H #define _EI_PORTIO_H +#include <stdint.h> + #undef EI_HAVE_STRUCT_IOVEC__ #if !defined(__WIN32__) && defined(HAVE_SYS_UIO_H) /* Declaration of struct iovec *iov should be visible in this scope. */ @@ -50,12 +52,12 @@ int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs); extern ei_socket_callbacks ei_default_socket_callbacks; #define EI_FD_AS_CTX__(FD) \ - ((void *) (long) (FD)) + ((void *) (intptr_t) (FD)) #define EI_DFLT_CTX_TO_FD__(CTX, FD) \ - ((int) (long) (CTX) < 0 \ + ((intptr_t) (CTX) < 0 \ ? EBADF \ - : (*(FD) = (int) (long) (CTX), 0)) + : (*(FD) = (int) (intptr_t) (CTX), 0)) #define EI_GET_FD__(CBS, CTX, FD) \ ((CBS) == &ei_default_socket_callbacks \ diff --git a/lib/erl_interface/src/misc/show_msg.c b/lib/erl_interface/src/misc/show_msg.c index 518d9dd595..c87e73c545 100644 --- a/lib/erl_interface/src/misc/show_msg.c +++ b/lib/erl_interface/src/misc/show_msg.c @@ -129,9 +129,9 @@ int ei_show_sendmsg(FILE *stream, const char *header, const char *msgbuf) /* skip five bytes */ index = 5; - ei_decode_version(header,&index,&version); - ei_decode_tuple_header(header,&index,&arity); - ei_decode_long(header,&index,&msg.msgtype); + if (ei_decode_version(header,&index,&version) + || ei_decode_tuple_header(header,&index,&arity) + || ei_decode_long(header,&index,&msg.msgtype)) return -1; switch (msg.msgtype) { case ERL_SEND: diff --git a/lib/erl_interface/src/prog/erl_call.c b/lib/erl_interface/src/prog/erl_call.c index b0566a0db7..1c9bd69a96 100644 --- a/lib/erl_interface/src/prog/erl_call.c +++ b/lib/erl_interface/src/prog/erl_call.c @@ -80,6 +80,7 @@ struct call_flags { int randomp; int dynamic_name; int use_long_name; /* indicates if -name was used, else -sname or -n */ + int use_localhost_fallback; int debugp; int verbosep; int haltp; @@ -105,6 +106,23 @@ static void* ei_chk_calloc(size_t nmemb, size_t size); static void* ei_chk_realloc(void *old, size_t size); static char* ei_chk_strdup(char *s); +/* Converts the given hostname to a shortname, if required. */ +static void format_node_hostname(const struct call_flags *flags, + const char *hostname, + char dst[EI_MAXHOSTNAMELEN + 1]) +{ + char *ct; + + strncpy(dst, hostname, EI_MAXHOSTNAMELEN); + dst[EI_MAXHOSTNAMELEN] = '\0'; + + /* If shortnames, cut off the name at first '.' */ + if (flags->use_long_name == 0 && (ct = strchr(dst, '.'))) { + *ct = '\0'; + } +} + +static void start_timeout(int timeout); /*************************************************************************** * @@ -119,7 +137,6 @@ int main(int argc, char *argv[]) char host_name[EI_MAXHOSTNAMELEN+1]; char nodename[MAXNODELEN+1]; char *p = NULL; - char *ct = NULL; /* temporary used when truncating nodename */ int modsize = 0; char *host = NULL; char *module = NULL; @@ -177,6 +194,24 @@ int main(int argc, char *argv[]) } i++; } + } else if (strcmp(argv[i], "-timeout") == 0) { + long timeout; + + if (i+1 >= argc) { + usage_arg(progname, "-timeout "); + } + + timeout = strtol(argv[i+1], NULL, 10); + if (timeout <= 0 || timeout >= (1 << 20)) { + usage_error(progname, "-timeout"); + } + + start_timeout(timeout); + i++; + } else if (strcmp(argv[i], "-__uh_test__") == 0) { + /* Fakes a failure in the call to ei_gethostbyname(h_hostname) so + * we can test the localhost fallback. */ + flags.use_localhost_fallback = 1; } else { if (strlen(argv[i]) != 2) { usage_error(progname, argv[i]); @@ -312,23 +347,24 @@ int main(int argc, char *argv[]) char *h_nodename = h_nodename_buf; char *h_alivename = flags.hidden; struct in_addr h_ipadr; - char* ct; /* gethostname requires len to be max(hostname) + 1 */ if (gethostname(h_hostname, EI_MAXHOSTNAMELEN+1) < 0) { - fprintf(stderr,"erl_call: failed to get host name: %d\n", errno); - exit(1); + fprintf(stderr,"erl_call: failed to get host name: %d\n", errno); + exit(1); } - if ((hp = ei_gethostbyname(h_hostname)) == 0) { - fprintf(stderr,"erl_call: can't resolve hostname %s\n", h_hostname); - exit(1); - } - /* If shortnames, cut off the name at first '.' */ - if (flags.use_long_name == 0 && (ct = strchr(hp->h_name, '.')) != NULL) { - *ct = '\0'; + + if (flags.use_localhost_fallback || (hp = ei_gethostbyname(h_hostname)) == 0) { + /* Failed to resolve our own hostname; try binding to loopback and + * hope for the best. */ + hp = ei_gethostbyname("localhost"); + flags.use_localhost_fallback = 1; + + format_node_hostname(&flags, h_hostname, h_hostname); + } else { + format_node_hostname(&flags, hp->h_name, h_hostname); } - strncpy(h_hostname, hp->h_name, EI_MAXHOSTNAMELEN); - h_hostname[EI_MAXHOSTNAMELEN] = '\0'; + memcpy(&h_ipadr.s_addr, *hp->h_addr_list, sizeof(struct in_addr)); if (h_alivename) { if (strlen(h_alivename) + strlen(h_hostname) + 2 > sizeof(h_nodename_buf)) { @@ -364,20 +400,21 @@ int main(int argc, char *argv[]) host = p+1; } - /* - * Expand name to a real name (may be ip-address) - */ - /* FIXME better error string */ - if ((hp = ei_gethostbyname(host)) == 0) { - fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host); - exit(1); - } - /* If shortnames, cut off the name at first '.' */ - if (flags.use_long_name == 0 && (ct = strchr(hp->h_name, '.')) != NULL) { - *ct = '\0'; + if (flags.use_localhost_fallback && strcmp(host, ei_thishostname(&ec)) == 0) { + /* We're on the same host *and* have used the localhost fallback, so we + * skip canonical name resolution since it's bound to fail. + * + * `ei_connect` will do the right thing later on. */ + strcpy(host_name, ei_thishostname(&ec)); + } else { + if ((hp = ei_gethostbyname(host)) == 0) { + fprintf(stderr,"erl_call: can't ei_gethostbyname(%s)\n", host); + exit(1); + } + + format_node_hostname(&flags, hp->h_name, host_name); } - strncpy(host_name, hp->h_name, EI_MAXHOSTNAMELEN); - host_name[EI_MAXHOSTNAMELEN] = '\0'; + if (flags.port == -1) { if (strlen(flags.node) + strlen(host_name) + 2 > sizeof(nodename)) { fprintf(stderr,"erl_call: nodename too long: %s\n", flags.node); @@ -832,6 +869,25 @@ static int get_module(char **mbuf, char **mname) } /* get_module */ +#ifdef __WIN32__ +static DWORD WINAPI timer_thread(void *data) { + DWORD_PTR timeout = (DWORD_PTR)data * 1000; + + Sleep(timeout); + exit(1); +} + +static void start_timeout(int timeout) { + if (CreateThread(NULL, 0, timer_thread, (void*)timeout, 0, NULL) == NULL) { + fprintf(stderr,"erl_call: Failed to start timer thread\n"); + exit(1); + } +} +#else +static void start_timeout(int timeout) { + alarm(timeout); +} +#endif /*************************************************************************** * @@ -841,7 +897,7 @@ static int get_module(char **mbuf, char **mname) static void usage_noexit(const char *progname) { fprintf(stderr,"\nUsage: %s [-[demqrsv]] [-c Cookie] [-h HiddenName] \n", progname); - fprintf(stderr," [-x ErlScript] [-a [Mod [Fun [Args]]]]\n"); + fprintf(stderr," [-x ErlScript] [-a [Mod [Fun [Args]]]] [-timeout Secs]\n"); fprintf(stderr," (-n Node | -sname Node | -name Node | -address [HOSTNAME:]PORT)\n\n"); #ifdef __WIN32__ fprintf(stderr," where: -a apply(Mod,Fun,Args) (e.g -a \"erlang length [[a,b,c]]\"\n"); @@ -862,6 +918,7 @@ static void usage_noexit(const char *progname) { " (e.g., %s -address my_host:36303 ...)\n" " (cannot be combinated with -s, -n, -name and -sname)\n", progname); + fprintf(stderr," -timeout command timeout, in seconds\n"); fprintf(stderr," -q halt the Erlang node (overrides the -s switch)\n"); fprintf(stderr," -r use a random name for the erl_call client node\n"); fprintf(stderr," -s start a new Erlang node if necessary\n"); diff --git a/lib/erl_interface/src/prog/erl_start.c b/lib/erl_interface/src/prog/erl_start.c index 8eca2dfa10..9c876feb5e 100644 --- a/lib/erl_interface/src/prog/erl_start.c +++ b/lib/erl_interface/src/prog/erl_start.c @@ -80,7 +80,7 @@ static struct in_addr *get_addr(const char *hostname, struct in_addr *oaddr); static int wait_for_erlang(int sockd, int magic, struct timeval *timeout); #if defined(__WIN32__) static int unique_id(void); -static unsigned long spawn_erlang_epmd(ei_cnode *ec, +static HANDLE spawn_erlang_epmd(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags, @@ -116,9 +116,9 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags, int sockd = 0; int one = 1; #if defined(__WIN32__) - unsigned long pid = 0; + HANDLE pid; #else - int pid = 0; + int pid; #endif int r = 0; @@ -145,8 +145,8 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags, listen(sockd,5); #if defined(__WIN32__) - if((pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1)) - == 0) + pid = spawn_erlang_epmd(ec,alive,adr,flags,erl,args,port,1); + if (pid == INVALID_HANDLE_VALUE) return ERL_SYS_ERROR; timeout.tv_usec = 0; timeout.tv_sec = 10; /* ignoring ERL_START_TIME */ @@ -154,7 +154,7 @@ int erl_start_sys(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags, == ERL_TIMEOUT) { /* Well, this is not a nice way to do it, and it does not always kill the emulator, but the alternatives are few.*/ - TerminateProcess((HANDLE) pid,1); + TerminateProcess(pid,1); } #else /* Unix */ switch ((pid = fork())) { @@ -269,7 +269,7 @@ static void free_args(char **args){ /* In NT we cannot fork(), Erlang and Epmd gets spawned by this function instead. */ -static unsigned long spawn_erlang_epmd(ei_cnode *ec, +static HANDLE spawn_erlang_epmd(ei_cnode *ec, char *alive, Erl_IpAddr adr, int flags, @@ -290,18 +290,18 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec, struct in_addr myaddr; struct in_addr *hisaddr = (struct in_addr *)adr; char iaddrbuf[IP_ADDR_CHARS + 1]; - int ret; + HANDLE ret; if(is_erlang){ get_addr(ei_thishostname(ec), &myaddr); if((ptr = inet_ntoa(myaddr)) == NULL) - return 0; + return INVALID_HANDLE_VALUE; else strcpy(iaddrbuf,ptr); } if ((flags & ERL_START_REMOTE) || (is_erlang && (hisaddr->s_addr != myaddr.s_addr))) { - return 0; + return INVALID_HANDLE_VALUE; } else { num_args = enquote_args(args, &args); for(cmdlen = i = 0; args[i] != NULL; ++i) @@ -364,9 +364,9 @@ static unsigned long spawn_erlang_epmd(ei_cnode *ec, NULL, &sinfo, &pinfo)) - ret = 0; + ret = INVALID_HANDLE_VALUE; else - ret = (unsigned long) pinfo.hProcess; + ret = pinfo.hProcess; free(cmdbuf); return ret; } diff --git a/lib/erl_interface/src/registry/reg_dump.c b/lib/erl_interface/src/registry/reg_dump.c index a027c4cdf5..f90fd4d4b6 100644 --- a/lib/erl_interface/src/registry/reg_dump.c +++ b/lib/erl_interface/src/registry/reg_dump.c @@ -209,11 +209,11 @@ static int mn_send_write(int fd, erlang_pid *mnesia, const char *key, ei_reg_obj break; case EI_STR: if (obj->size > 0) ei_encode_string(msgbuf,&index,obj->val.s); - else ei_encode_long(msgbuf,&index, (long)NULL); /* just the NULL pointer */ + else ei_encode_long(msgbuf,&index, 0); /* just the NULL pointer */ break; case EI_BIN: if (obj->size > 0) ei_encode_binary(msgbuf,&index,obj->val.p,obj->size); - else ei_encode_long(msgbuf,&index,(long)(obj->val.p)); /* just the pointer */ + else ei_encode_long(msgbuf,&index, obj->val.i); /* just the pointer */ break; default: if (dbuf) free(dbuf); @@ -255,7 +255,7 @@ static int mn_get_unlink(int fd) int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) { ei_hash *tab; - erlang_pid self; + erlang_pid *self; erlang_pid mnesia; ei_bucket *b; ei_reg_obj *obj; @@ -271,12 +271,10 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) if ((ec = ei_fd_to_cnode(fd)) == NULL) { return -1; } - strcpy(self.node,ei_thisnodename(ec)); - self.num = fd; - self.serial = 0; - self.creation = ei_thiscreation(ec); - if (mn_start_dump(fd,&self,&mnesia,mntab)) return -1; + self = ei_self(ec); + + if (mn_start_dump(fd,self,&mnesia,mntab)) return -1; /* traverse the table, passing objects to mnesia */ for (i=0; i<tab->size; i++) { @@ -288,13 +286,13 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) if ((flags & EI_FORCE) || (obj->attr & EI_DIRTY)) { if (obj->attr & EI_DELET) { if (mn_send_delete(fd,&mnesia,key)) { - ei_send_exit(fd,&self,&mnesia,"delete failed"); + ei_send_exit(fd,self,&mnesia,"delete failed"); return -1; } } else { if (mn_send_write(fd,&mnesia,key,obj)) { - ei_send_exit(fd,&self,&mnesia,"update failed"); + ei_send_exit(fd,self,&mnesia,"update failed"); return -1; } } @@ -304,8 +302,8 @@ int ei_reg_dump(int fd, ei_reg *reg, const char *mntab, int flags) } /* end the transaction */ - if (mn_send_commit(fd,&mnesia,&self)) { - ei_send_exit(fd,&self,&mnesia,"commit failed"); + if (mn_send_commit(fd,&mnesia,self)) { + ei_send_exit(fd,self,&mnesia,"commit failed"); return -1; } diff --git a/lib/erl_interface/src/registry/reg_restore.c b/lib/erl_interface/src/registry/reg_restore.c index 75d073303f..030bab19b9 100644 --- a/lib/erl_interface/src/registry/reg_restore.c +++ b/lib/erl_interface/src/registry/reg_restore.c @@ -227,7 +227,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) char *dbuf = NULL; char *msgbuf = NULL; char *keybuf = NULL; - erlang_pid self; + erlang_pid *self; erlang_pid mnesia = {"",0,0,0}; erlang_msg msg; int index = 0; @@ -247,20 +247,18 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) if ((ec = ei_fd_to_cnode(fd)) == NULL) { return -1; } - strcpy(self.node,ei_thisnodename(ec)); - self.num = fd; - self.serial = 0; - self.creation = ei_thiscreation(ec); + + self = ei_self(ec); - if (mn_start_restore(fd,&self,&mnesia,mntab,&count,&maxkey,&maxobj)) { + if (mn_start_restore(fd,self,&mnesia,mntab,&count,&maxkey,&maxobj)) { /* send exit *only* if we have pid */ - if (mnesia.node[0]) ei_send_exit(fd,&self,&mnesia,"bad response from rpc start"); + if (mnesia.node[0]) ei_send_exit(fd,self,&mnesia,"bad response from rpc start"); return -1; } if (count <= 0) { - ei_send_exit(fd,&self,&mnesia,"nothing to do"); + ei_send_exit(fd,self,&mnesia,"nothing to do"); return 0; } @@ -268,7 +266,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) len = maxkey + maxobj + 512; if (len > EISMALLBUF) if (!(dbuf = malloc(len))) { - ei_send_exit(fd,&self,&mnesia,"cannot allocate space for incoming data"); + ei_send_exit(fd,self,&mnesia,"cannot allocate space for incoming data"); return -1; } msgbuf = (dbuf ? dbuf : sbuf); @@ -281,7 +279,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) ei_encode_version(msgbuf,&index); ei_encode_tuple_header(msgbuf,&index,2); ei_encode_atom(msgbuf,&index,"send_records"); - ei_encode_pid(msgbuf,&index,&self); + ei_encode_pid(msgbuf,&index,self); if (ei_send_encoded(fd,&mnesia,msgbuf,index)) goto restore_failure; /* read as much as possible, until count or EXIT */ @@ -317,7 +315,7 @@ int ei_reg_restore(int fd, ei_reg *reg, const char *mntab) return 0; restore_failure: - ei_send_exit(fd,&self,&mnesia,"restore failure"); + ei_send_exit(fd,self,&mnesia,"restore failure"); if (keybuf) free(keybuf); if (dbuf) free(dbuf); return -1; diff --git a/lib/erl_interface/test/Makefile b/lib/erl_interface/test/Makefile index 1816c73d4b..bdfedecc66 100644 --- a/lib/erl_interface/test/Makefile +++ b/lib/erl_interface/test/Makefile @@ -24,7 +24,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # Target Specs # ---------------------------------------------------- -MODULES= \ +EI_MODULES= \ ei_accept_SUITE \ ei_connect_SUITE \ ei_decode_SUITE \ @@ -38,12 +38,16 @@ MODULES= \ port_call_SUITE \ runner +ERTS_MODULES= erts_test_utils + +MODULES=$(EI_MODULES) $(ERTS_MODULES) + SPEC_FILES = \ erl_interface.spec erl_interface_smoke.spec COVER_FILE = erl_interface.cover -ERL_FILES = $(MODULES:%=%.erl) +ERL_FILES = $(EI_MODULES:%=%.erl) $(ERTS_MODULES:%=$(ERL_TOP)/erts/emulator/test/%.erl) # ---------------------------------------------------- # Release directory specification diff --git a/lib/erl_interface/test/ei_connect_SUITE.erl b/lib/erl_interface/test/ei_connect_SUITE.erl index 224608ba71..0506359b71 100644 --- a/lib/erl_interface/test/ei_connect_SUITE.erl +++ b/lib/erl_interface/test/ei_connect_SUITE.erl @@ -34,7 +34,9 @@ ei_send_funs/1, ei_threaded_send/1, ei_set_get_tracelevel/1, - ei_connect_host_port_test/1]). + ei_connect_host_port_test/1, + ei_make_ref/1, + ei_make_pid/1]). -import(runner, [get_term/1,send_term/2]). @@ -54,7 +56,9 @@ groups() -> ei_send_funs, ei_set_get_tracelevel, ei_reg_send, - ei_rpc], + ei_rpc, + ei_make_ref, + ei_make_pid], [{default, [], Members}, {ussi, [], Members}]. @@ -207,6 +211,100 @@ ei_connect_host_port_test(Config) when is_list(Config) -> runner:recv_eot(P), ok. +ei_make_ref(Config) when is_list(Config) -> + P = runner:start(Config, ?interpret), + 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)), + {ok,Fd} = ei_connect(P, node()), + + %% Call ei_make_ref() enough times for it to + %% wrap the first internal integer.. + + N = 270, + {CNode, Refs} = make_refs(N, undefined, P, Fd, []), + io:format("Last Ref ~p~n", [hd(Refs)]), + + io:format("CNode = ~p", [CNode]), + + true = lists:member(CNode, nodes(hidden)), + + %% Ensure that all references are + %% unique... + RefsLen = N*1000, + RefsLen = length(lists:usort(Refs)), + + runner:send_eot(P), + runner:recv_eot(P), + ok. + +make_refs(0, CNode, _P, _Fd, Refs) -> + {CNode, Refs}; +make_refs(N, CNode, P, Fd, Refs) -> + ok = ei_make_refs(P, Fd, self()), + receive + {Node, NewRefs} -> + NewNode = if CNode == undefined -> + Node; + true -> + CNode = Node + end, + make_refs(N-1, NewNode, P, Fd, + chk_refs(NewRefs, NewNode, Refs)) + end. + +chk_refs([], _CNode, Refs) -> + Refs; +chk_refs([NewRef|NewRefs], CNode, Refs) -> + true = is_reference(NewRef), + CNode = node(NewRef), + chk_refs(NewRefs, CNode, [NewRef|Refs]). + +ei_make_pid(Config) when is_list(Config) -> + P = runner:start(Config, ?interpret), + 0 = ei_connect_init(P, 42, erlang:get_cookie(), 0, get_group(Config)), + {ok,Fd} = ei_connect(P, node()), + + %% + %% Ensure to wrap all num values... + %% + N = 200, + {CNode, Pids} = make_pids(N, undefined, P, Fd, []), + io:format("Last Pid ~p~n", [hd(Pids)]), + + io:format("CNode = ~p", [CNode]), + + true = lists:member(CNode, nodes(hidden)), + + %% Ensure that all pid created by ei_make_pid() + %% are unique. Note that ei_self() is passed + %% along in each call as well... + PidsLen = N*1000 + 1, + PidsLen = length(lists:usort(Pids)), + + runner:send_eot(P), + runner:recv_eot(P), + ok. + +make_pids(0, CNode, _P, _Fd, Pids) -> + {CNode, Pids}; +make_pids(N, CNode, P, Fd, Pids) -> + ok = ei_make_pids(P, Fd, self()), + receive + {Node, NewPids} -> + NewNode = if CNode == undefined -> + Node; + true -> + CNode = Node + end, + make_pids(N-1, NewNode, P, Fd, + chk_pids(NewPids, NewNode, Pids)) + end. + +chk_pids([], _CNode, Pids) -> + Pids; +chk_pids([NewPid|NewPids], CNode, Pids) -> + true = is_pid(NewPid), + CNode = node(NewPid), + chk_pids(NewPids, CNode, [NewPid|Pids]). %%% Interface functions for ei (erl_interface) functions. @@ -256,6 +354,14 @@ ei_rpc(P, Fd, To, Func, Msg) -> send_command(P, ei_rpc, [Fd, To, Func, Msg]), get_term(P). +ei_make_refs(P, Fd, To) -> + send_command(P, ei_make_refs, [Fd,To]), + get_send_result(P). + +ei_make_pids(P, Fd, To) -> + send_command(P, ei_make_pids, [Fd,To]), + get_send_result(P). + get_send_result(P) -> case get_term(P) of diff --git a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c index 1dcd62400a..0aa6879adf 100644 --- a/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c +++ b/lib/erl_interface/test/ei_connect_SUITE_data/ei_connect_test.c @@ -40,6 +40,8 @@ static void cmd_ei_send_funs(char* buf, int len); static void cmd_ei_reg_send(char* buf, int len); static void cmd_ei_rpc(char* buf, int len); static void cmd_ei_set_get_tracelevel(char* buf, int len); +static void cmd_ei_make_refs(char* buf, int len); +static void cmd_ei_make_pids(char* buf, int len); static void send_errno_result(int value); @@ -60,6 +62,8 @@ static struct { "ei_rpc", 4, cmd_ei_rpc, "ei_set_get_tracelevel", 1, cmd_ei_set_get_tracelevel, "ei_format_pid", 2, cmd_ei_format_pid, + "ei_make_refs", 2, cmd_ei_make_refs, + "ei_make_pids", 2, cmd_ei_make_pids, }; @@ -210,6 +214,80 @@ static void cmd_ei_send(char* buf, int len) ei_x_free(&x); } +static void cmd_ei_make_refs(char* buf, int len) +{ + int index = 0; + long fd; + erlang_pid pid; + ei_x_buff x; + int i; + int nref = 1000; + + if (ei_decode_long(buf, &index, &fd) < 0) + fail("expected long"); + if (ei_decode_pid(buf, &index, &pid) < 0) + fail("expected pid (node)"); + if (ei_x_new_with_version(&x) < 0) + fail("ei_x_new_with_version"); + if (ei_x_encode_tuple_header(&x, 2) < 0) + fail("ei_x_encode_tuple_header() failed"); + if (ei_x_encode_atom(&x, ei_thisnodename(&ec)) < 0) + fail("ei_x_encode_atom() failed"); + if (ei_x_encode_list_header(&x, nref) < 0) + fail("ei_x_encode_list_header() failed"); + for (i = 0; i < nref; i++) { + erlang_ref ref; + if (ei_make_ref(&ec, &ref)) + fail("ei_make_ref() failed"); + if (ei_x_encode_ref(&x, &ref)) + fail("ei_x_encode_ref() failed"); + } + if (ei_x_encode_empty_list(&x) < 0) + fail("ei_x_encode_empty_list() failed"); + send_errno_result(ei_send(fd, &pid, x.buff, x.index)); + ei_x_free(&x); +} + +static void cmd_ei_make_pids(char* buf, int len) +{ + int index = 0; + long fd; + erlang_pid from_pid; + erlang_pid *self; + ei_x_buff x; + int i; + int npid = 1000; + + if (ei_decode_long(buf, &index, &fd) < 0) + fail("expected long"); + if (ei_decode_pid(buf, &index, &from_pid) < 0) + fail("expected pid (node)"); + if (ei_x_new_with_version(&x) < 0) + fail("ei_x_new_with_version"); + if (ei_x_encode_tuple_header(&x, 2) < 0) + fail("ei_x_encode_tuple_header() failed"); + if (ei_x_encode_atom(&x, ei_thisnodename(&ec)) < 0) + fail("ei_x_encode_atom() failed"); + if (ei_x_encode_list_header(&x, 1+npid) < 0) + fail("ei_x_encode_list_header() failed"); + self = ei_self(&ec); + if (!self) + fail("ei_self() failed"); + if (ei_x_encode_pid(&x, self)) + fail("ei_x_encode_pid() failed"); + for (i = 0; i < npid; i++) { + erlang_pid pid; + if (ei_make_pid(&ec, &pid)) + fail("ei_make_pid() failed"); + if (ei_x_encode_pid(&x, &pid)) + fail("ei_x_encode_pid() failed"); + } + if (ei_x_encode_empty_list(&x) < 0) + fail("ei_x_encode_empty_list() failed"); + send_errno_result(ei_send(fd, &from_pid, x.buff, x.index)); + ei_x_free(&x); +} + static void cmd_ei_format_pid(char* buf, int len) { int index = 0; diff --git a/lib/erl_interface/test/ei_decode_SUITE.erl b/lib/erl_interface/test/ei_decode_SUITE.erl index 475e68ced2..fee99aba7c 100644 --- a/lib/erl_interface/test/ei_decode_SUITE.erl +++ b/lib/erl_interface/test/ei_decode_SUITE.erl @@ -33,7 +33,9 @@ test_ei_decode_char/1, test_ei_decode_nonoptimal/1, test_ei_decode_misc/1, - test_ei_decode_utf8_atom/1]). + test_ei_decode_utf8_atom/1, + test_ei_decode_iodata/1, + test_ei_cmp_nc/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -41,7 +43,8 @@ all() -> [test_ei_decode_long, test_ei_decode_ulong, test_ei_decode_longlong, test_ei_decode_ulonglong, test_ei_decode_char, test_ei_decode_nonoptimal, - test_ei_decode_misc, test_ei_decode_utf8_atom]. + test_ei_decode_misc, test_ei_decode_utf8_atom, + test_ei_decode_iodata, test_ei_cmp_nc]. init_per_testcase(Case, Config) -> runner:init_per_testcase(?MODULE, Case, Config). @@ -215,6 +218,160 @@ test_ei_decode_utf8_atom(Config) -> %% ######################################################################## %% +test_ei_decode_iodata(Config) when is_list(Config) -> + P = runner:start(Config, ?test_ei_decode_iodata), + + check_decode_iodata(P, [], true), + check_decode_iodata(P, $a, false), + check_decode_iodata(P, an_atom, false), + check_decode_iodata(P, self(), false), + check_decode_iodata(P, [$a,$a], true), + check_decode_iodata(P, [$a|$a], false), + check_decode_iodata(P, [[$a|$a],$a], false), + check_decode_iodata(P, "hej", true), + check_decode_iodata(P, ["hej", " ", "hopp"], true), + check_decode_iodata(P, <<"hopp san sa">>, true), + check_decode_iodata(P, [$a | <<"a">>], true), + check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , "san" | <<" sa">>]], true), + check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, 0, "san" | <<" sa">>]], true), + check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, 256, "san" | <<" sa">>]], false), + check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, -2, "san" | <<" sa">>]], false), + check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , san | <<" sa">>]], false), + check_decode_iodata(P, [[[["hej"]]], [$ , <<"hopp">>, $ , "san s" | $a], " "], false), + check_decode_iodata(P, [[[[[[[]|<<"a">>]]]]]], true), + check_decode_iodata(P, [[[[[[[[]]]]]]],[[[],[],[]]]], true), + + send_raw(P, <<"done">>), + runner:recv_eot(P), + ok. + +check_decode_iodata(P, Data, Valid) -> + io:format("~n~nChecking: ~p~n", [Data]), + Expect = case Valid of + true -> + io:format("Expecting decode SUCCESS... ", []), + iolist_to_binary(Data); + false -> + io:format("Expecting decode FAILURE... ", []), + badarg = try + iolist_to_binary(Data) + catch + error:badarg -> + badarg + end, + decode_size_failed + end, + send_term_as_binary(P, Data), + Actual = case runner:get_term(P) of + {bytes, B} when is_binary(B) -> + B; + {bytes, L} when is_list(L) -> + list_to_binary(L); + {term, T} -> + T + end, + case Expect =:= Actual of + true -> + io:format("Expected result!~n",[]), + ok; + false -> + io:format("Expect: ~w~nActual: ~w~n", [Expect, Actual]), + ct:fail(unexpected_result) + end. + +%% ######################################################################## %% + +%% Should be moved to its own suite... + +test_ei_cmp_nc(Config) when is_list(Config) -> + P = runner:start(Config, ?test_ei_cmp_nc), + R0 = make_ref(), + R1 = make_ref(), + check_cmp(P, R0, R0), + check_cmp(P, R0, R1), + check_cmp(P, R1, R0), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@c, 4711}, [17, 17, 17])), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@d, 4711}, [17, 17, 17])), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@bc, 4711}, [17, 17, 17])), + check_cmp(P, mk_ref({a@b, 4712}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 17, 17])), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [18, 17, 17])), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 18, 17])), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17, 17]), mk_ref({a@b, 4711}, [17, 17, 18])), + check_cmp(P, mk_ref({a@b, 4711}, [0, 17]), mk_ref({a@b, 4711}, [18])), + check_cmp(P, mk_ref({a@b, 4711}, [17, 17]), mk_ref({a@b, 4711}, [17, 18])), + check_cmp(P, mk_ref({a@b, 4711}, [0, 0, 0]), mk_ref({a@b, 4711}, [17])), + check_cmp(P, mk_ref({a@b, 4711}, [0, 0, 17]), mk_ref({a@b, 4711}, [18, 17])), + check_cmp(P, mk_ref({a@b, 4711}, [0, 17, 17]), mk_ref({a@b, 4711}, [18])), + + check_cmp(P, self(), self()), + check_cmp(P, self(), whereis(file_server_2)), + check_cmp(P, whereis(file_server_2), self()), + check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@c, 4711}, 17, 17)), + check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@d, 4711}, 17, 17)), + check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@bc, 4711}, 17, 17)), + check_cmp(P, mk_pid({a@b, 4712}, 17, 17), mk_pid({a@b, 4711}, 17, 17)), + check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@b, 4711}, 18, 17)), + check_cmp(P, mk_pid({a@b, 4711}, 17, 17), mk_pid({a@b, 4711}, 17, 18)), + + Prt0 = open_port({spawn, "true"},[]), + Prt1 = open_port({spawn, "true"},[]), + + check_cmp(P, Prt0, Prt0), + check_cmp(P, Prt1, Prt0), + check_cmp(P, Prt0, Prt1), + check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@b, 4711}, 17)), + check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@d, 4711}, 17)), + check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@bc, 4711}, 17)), + check_cmp(P, mk_port({a@b, 4712}, 17), mk_port({a@b, 4711}, 17)), + check_cmp(P, mk_port({a@b, 4711}, 17), mk_port({a@b, 4711}, 18)), + + send_raw(P, <<"done">>), + runner:recv_eot(P), + ok. + +mk_pid(Node, Num, Ser) -> + erts_test_utils:mk_ext_pid(Node, Num, Ser). + +mk_port(Node, Id) -> + erts_test_utils:mk_ext_port(Node, Id). + +mk_ref(Node, Numbers) -> + erts_test_utils:mk_ext_ref(Node, Numbers). + +check_cmp(P, A, B) when is_pid(A), is_pid(B) -> + check_cmp(P, {cmp_pids, A, B}); +check_cmp(P, A, B) when is_port(A), is_port(B) -> + check_cmp(P, {cmp_ports, A, B}); +check_cmp(P, A, B) when is_reference(A), is_reference(B) -> + check_cmp(P, {cmp_refs, A, B}). + +check_cmp(P, {_, A, B} = Data) -> + io:format("~n~nChecking: ~p~n", [Data]), + send_term_as_binary(P, Data), + {term, Res} = runner:get_term(P), + io:format("Res = ~p~n", [Res]), + case {{ei_cmp, Res}, {erlang, cmp_nc(A, B)}} of + {{ei_cmp, 0}, {erlang, equal}} -> + ok; + {{ei_cmp, Cmp}, {erlang, less_than}} when is_integer(Cmp), + Cmp < 0 -> + ok; + {{ei_cmp, Cmp}, {erlang, larger_than}} when is_integer(Cmp), + Cmp > 0 -> ok; + Fail -> + ct:fail(Fail) + end. + +cmp_nc(A, A) -> + equal; +cmp_nc(A, B) when A < B -> + less_than; +cmp_nc(_, _) -> + larger_than. + + +%% ######################################################################## %% + send_term_as_binary(Port, Term) when is_port(Port) -> Port ! {self(), {command, term_to_binary(Term)}}. diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c index 29542b1c6c..9eed34b0dd 100644 --- a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c +++ b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c @@ -19,6 +19,7 @@ */ #include <string.h> +#include <stdlib.h> #include "ei_runner.h" @@ -753,6 +754,192 @@ TESTCASE(test_ei_decode_utf8_atom) /* ******************************************************************** */ +TESTCASE(test_ei_decode_iodata) +{ + char *buf = NULL, *data = NULL; + ei_init(); + + while (1) { + int unexpected_write = 0; + int i; + int len, index, saved_index, err; + + if (buf) + free_packet(buf); + buf = read_packet(&len); + + if (len == 4 + && buf[0] == 'd' + && buf[1] == 'o' + && buf[2] == 'n' + && buf[3] == 'e') { + break; + } + + index = 0; + err = ei_decode_version(buf, &index, NULL); + if (err != 0) { + free_packet(buf); + fail1("ei_decode_version returned %d", err); + } + saved_index = index; + err = ei_decode_iodata(buf, &index, &len, NULL); + if (err != 0) { + ei_x_buff x; + ei_x_new_with_version(&x); + ei_x_encode_atom(&x, "decode_size_failed"); + send_bin_term(&x); + ei_x_free(&x); + continue; + } + if (data) { + data -= 100; + free(data); + } + data = malloc(len + 200); + if (!data) { + ei_x_buff x; + ei_x_new_with_version(&x); + ei_x_encode_atom(&x, "malloc_failed"); + send_bin_term(&x); + ei_x_free(&x); + continue; + } + for (i = 0; i < len + 200; i++) + data[i] = 'Y'; + data += 100; + err = ei_decode_iodata(buf, &saved_index, NULL, (unsigned char *) data); + if (err != 0) { + ei_x_buff x; + ei_x_new_with_version(&x); + ei_x_encode_atom(&x, "decode_data_failed"); + send_bin_term(&x); + ei_x_free(&x); + continue; + } + + for (i = -100; i < 0; i++) { + if (data[i] != 'Y') { + ei_x_buff x; + ei_x_new_with_version(&x); + ei_x_encode_atom(&x, "unexpected_write_before_data"); + send_bin_term(&x); + ei_x_free(&x); + unexpected_write = !0; + break; + } + } + + if (!unexpected_write) { + for (i = len; i < len + 100; i++) { + if (data[i] != 'Y') { + ei_x_buff x; + ei_x_new_with_version(&x); + ei_x_encode_atom(&x, "unexpected_write_after_data"); + send_bin_term(&x); + ei_x_free(&x); + unexpected_write = !0; + break; + } + } + } + + if (!unexpected_write) + send_buffer(data, len); + } + + if (buf) + free_packet(buf); + if (data) { + data -= 100; + free(data); + } + report(1); +} + +/* ******************************************************************** */ + +/* + * Does not belong here move to its own suite... + */ +TESTCASE(test_ei_cmp_nc) +{ + char *buf = NULL; + ei_init(); + + while (1) { + int len, index, arity; + char atom[MAXATOMLEN_UTF8]; + ei_x_buff x; + + if (buf) + free_packet(buf); + buf = read_packet(&len); + + if (len == 4 + && buf[0] == 'd' + && buf[1] == 'o' + && buf[2] == 'n' + && buf[3] == 'e') { + break; + } + + ei_x_new_with_version(&x); + index = 0; + if (ei_decode_version(buf, &index, NULL) + || ei_decode_tuple_header(buf, &index, &arity) + || (arity != 3) + || ei_decode_atom(buf, &index, atom)) { + ei_x_encode_atom(&x, "decode_tuple_failed"); + } + else if (strcmp(atom, "cmp_pids") == 0) { + erlang_pid a, b; + if (ei_decode_pid(buf, &index, &a) + || ei_decode_pid(buf, &index, &b)) { + ei_x_encode_atom(&x, "decode_pids_failed"); + } + else { + long res = (long) ei_cmp_pids(&a, &b); + ei_x_encode_long(&x, res); + } + } + else if (strcmp(atom, "cmp_ports") == 0) { + erlang_port a, b; + if (ei_decode_port(buf, &index, &a) + || ei_decode_port(buf, &index, &b)) { + ei_x_encode_atom(&x, "decode_ports_failed"); + } + else { + long res = (long) ei_cmp_ports(&a, &b); + ei_x_encode_long(&x, res); + } + } + else if (strcmp(atom, "cmp_refs") == 0) { + erlang_ref a, b; + if (ei_decode_ref(buf, &index, &a) + || ei_decode_ref(buf, &index, &b)) { + ei_x_encode_atom(&x, "decode_refs_failed"); + } + else { + long res = (long) ei_cmp_refs(&a, &b); + ei_x_encode_long(&x, res); + } + } + else { + ei_x_encode_atom(&x, "unexpected_operation"); + } + + send_bin_term(&x); + ei_x_free(&x); + } + + if (buf) + free_packet(buf); + report(1); +} + +/* ******************************************************************** */ + int ei_decode_my_atom_as(const char *buf, int *index, char *to, struct my_atom *atom) { erlang_char_encoding was,result; diff --git a/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c b/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c index 4c018667fe..dd41afe77e 100644 --- a/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c +++ b/lib/erl_interface/test/ei_global_SUITE_data/ei_global_test.c @@ -211,6 +211,7 @@ cmd_ei_global_names(char* buf, int len) send_bin_term(&x); ei_x_free(&x); } + free(names); } static void diff --git a/lib/erl_interface/test/erl_call_SUITE.erl b/lib/erl_interface/test/erl_call_SUITE.erl index a35e027cd7..339131fed6 100644 --- a/lib/erl_interface/test/erl_call_SUITE.erl +++ b/lib/erl_interface/test/erl_call_SUITE.erl @@ -25,12 +25,16 @@ -export([all/0, smoke/1, random_cnode_name/1, - test_connect_to_host_port/1]). + test_connect_to_host_port/1, + unresolvable_hostname/1, + timeout/1]). all() -> [smoke, random_cnode_name, - test_connect_to_host_port]. + test_connect_to_host_port, + unresolvable_hostname, + timeout]. smoke(Config) when is_list(Config) -> Name = atom_to_list(?MODULE) @@ -115,6 +119,40 @@ test_connect_to_host_port_do(Name) -> end, ok. +%% OTP-16604: Tests that erl_call works even when the local hostname cannot be +%% resolved. +unresolvable_hostname(_Config) -> + Name = atom_to_list(?MODULE) + ++ "-" + ++ integer_to_list(erlang:system_time(microsecond)), + Opt = "-__uh_test__", + + try + CNodeName = start_node_and_get_c_node_name(Name, [Opt]), + [_, Hostname] = string:lexemes(atom_to_list(node()), "@"), + DefaultName = list_to_atom("c17@" ++ Hostname), + check_eq(CNodeName, DefaultName) + after + halt_node(Name) + end, + + ok. + +%% OTP-16604: Test the -timeout option +timeout(_Config) -> + Name = atom_to_list(?MODULE) + ++ "-" + ++ integer_to_list(erlang:system_time(microsecond)), + Opts = ["-timeout", "3"], + + try + [] = start_node_and_apply(Name, "timer sleep [10000]", Opts) + after + halt_node(Name) + end, + + ok. + % % Utility functions... % diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk index 31e3d1c0a0..67aac42e4e 100644 --- a/lib/erl_interface/vsn.mk +++ b/lib/erl_interface/vsn.mk @@ -1,2 +1,2 @@ -EI_VSN = 3.13.2 +EI_VSN = 4.0 ERL_INTERFACE_VSN = $(EI_VSN) diff --git a/lib/eunit/doc/src/notes.xml b/lib/eunit/doc/src/notes.xml index 200ffc3714..b015fe1f89 100644 --- a/lib/eunit/doc/src/notes.xml +++ b/lib/eunit/doc/src/notes.xml @@ -33,6 +33,35 @@ </header> <p>This document describes the changes made to the EUnit application.</p> +<section><title>Eunit 2.5</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> Let <c>eunit_surefire</c> skip invalid XML 1.0 + characters. </p> + <p> + Own Id: OTP-15950 Aux Id: PR-2316, ERL-991 </p> + </item> + <item> + <p> + Add new macro ?capturedOutput for enabling to write test + cases that verify data printed to standard out</p> + <p> + Own Id: OTP-16275 Aux Id: PR-2424 </p> + </item> + <item> + <p> + Add option to limit print depth of exceptions generated + by eunit test suites.</p> + <p> + Own Id: OTP-16549 Aux Id: PR-2532 </p> + </item> + </list> + </section> + +</section> + <section><title>Eunit 2.4.1</title> <section><title>Improvements and New Features</title> diff --git a/lib/eunit/vsn.mk b/lib/eunit/vsn.mk index f96db657cf..b8410e4071 100644 --- a/lib/eunit/vsn.mk +++ b/lib/eunit/vsn.mk @@ -1 +1 @@ -EUNIT_VSN = 2.4.1 +EUNIT_VSN = 2.5 diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml index f13551db83..1f9ab57727 100644 --- a/lib/hipe/doc/src/notes.xml +++ b/lib/hipe/doc/src/notes.xml @@ -31,6 +31,38 @@ </header> <p>This document describes the changes made to HiPE.</p> +<section><title>Hipe 4.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed a rare miss-compilation of tuple matching.</p> + <p> + Own Id: OTP-16470</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>The deprecated <c>erlang:get_stacktrace/0</c> BIF now + returns an empty list instead of a stacktrace. To + retrieve the stacktrace, use the extended try/catch + syntax that was introduced in OTP 21. + <c>erlang:get_stacktrace/0</c> is scheduled for removal + in OTP 24.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16484</p> + </item> + </list> + </section> + +</section> + <section><title>Hipe 3.19.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk index 5d34c61169..9a612e9063 100644 --- a/lib/hipe/vsn.mk +++ b/lib/hipe/vsn.mk @@ -1 +1 @@ -HIPE_VSN = 3.19.3 +HIPE_VSN = 4.0 diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index fe6d7c8c83..0dfd01eab3 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,31 @@ <file>notes.xml</file> </header> - <section><title>Inets 7.1.3</title> + <section><title>Inets 7.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove support for deprecated functionality. Support for + mod_esi eval scheme, mod_htacess, mod_browser, apache + config files and deprecated httpd_conf functions are + dropped. Module http_uri is deprecated.</p> + <p> + Own Id: OTP-16252</p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 7.1.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index b0ae8655d2..2eaef40a7a 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 7.1.3 +INETS_VSN = 7.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/jinterface/doc/src/notes.xml b/lib/jinterface/doc/src/notes.xml index 9eeca8e963..fca0b46878 100644 --- a/lib/jinterface/doc/src/notes.xml +++ b/lib/jinterface/doc/src/notes.xml @@ -31,6 +31,35 @@ </header> <p>This document describes the changes made to the Jinterface application.</p> +<section><title>Jinterface 1.11</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Increased size of node incarnation numbers (aka + "creation"), from 2 bits to 32 bits. This will reduce the + risk of pids/ports/refs, from different node incarnation + with the same name, being mixed up.</p> + <p> + Own Id: OTP-15603</p> + </item> + <item> + <p> + Improved node connection setup handshake protocol. Made + possible to agree on protocol version without dependence + on <c>epmd</c> or other prior knowledge of peer node + version. Also added exchange of node incarnation + ("creation") values and expanded the distribution + capability flag field from 32 to 64 bits.</p> + <p> + Own Id: OTP-16229</p> + </item> + </list> + </section> + +</section> + <section><title>Jinterface 1.10.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/jinterface/vsn.mk b/lib/jinterface/vsn.mk index f15a3f323b..f253cf7431 100644 --- a/lib/jinterface/vsn.mk +++ b/lib/jinterface/vsn.mk @@ -1 +1 @@ -JINTERFACE_VSN = 1.10.1 +JINTERFACE_VSN = 1.11 diff --git a/lib/kernel/doc/src/Makefile b/lib/kernel/doc/src/Makefile index 9b004b3781..d02e954ba9 100644 --- a/lib/kernel/doc/src/Makefile +++ b/lib/kernel/doc/src/Makefile @@ -67,9 +67,11 @@ XML_REF3_FILES = application.xml \ pg2.xml \ rpc.xml \ seq_trace.xml \ + socket.xml \ wrap_log_reader.xml \ user.xml \ - zlib_stub.xml + zlib_stub.xml \ + $(XML_REF3_ESOCK_EFILES) XML_REF4_FILES = app.xml config.xml @@ -79,8 +81,10 @@ XML_PART_FILES = part.xml XML_CHAPTER_FILES = \ notes.xml \ introduction_chapter.xml \ + socket_usage.xml \ logger_chapter.xml \ - logger_cookbook.xml + logger_cookbook.xml \ + eep48_chapter.xml BOOK_FILES = book.xml diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml index 421c1ca254..81186b6876 100644 --- a/lib/kernel/doc/src/code.xml +++ b/lib/kernel/doc/src/code.xml @@ -705,7 +705,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="all_available" arity="0" since="OTP @OTP-16494@"/> + <name name="all_available" arity="0" since="OTP 23.0"/> <fsummary>Get all available modules.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -769,14 +769,13 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="get_doc" arity="1" since="OTP @OTP-16406@"/> + <name name="get_doc" arity="1" since="OTP 23.0"/> <fsummary>Gets the documentation for a module.</fsummary> <desc> - <p>Searches the code path for a documentation chunk - and returns ut if available. If no documentation chunk - can be found the function tries to generate documentation - from the debug information in the module. If no debug - information is available, this function will return + <p>Searches the code path for EEP-48 style documentation and returns it + if available. If no documentation can be found the function tries to + generate documentation from the debug information in the module. + If no debug information is available, this function will return <c>{error,missing}</c>. </p> <p>For more information about the documentation chunk see diff --git a/lib/kernel/doc/src/eep48_chapter.xml b/lib/kernel/doc/src/eep48_chapter.xml new file mode 100644 index 0000000000..db5d13eb7a --- /dev/null +++ b/lib/kernel/doc/src/eep48_chapter.xml @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2020</year><year>2020</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>EEP-48: Documentation storage and format</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>eep48_chapter.xml</file> + </header> + + <p>This User's Guide describes the documentation storage format initially described in + <url href="https://www.erlang.org/erlang-enhancement-proposals/eep-0048.html">EEP-48</url>. + By standardizing how API documentation is stored, it will be possible to write tools that + work across languages.</p> + + <p>To fetch the EEP-48 documentation for a module you can use + <seemfa marker="code#get_doc/1"><c>code:get_doc/1</c></seemfa>.</p> + + <section> + <title>the "Docs" storage</title> + <p>To look for documentation for a module name example, a tool should:</p> + + <p>Look for <c>example.beam</c> in the code path, parse the BEAM file and + retrieve the <c>Docs</c> chunk. If the chunk is not available, it should look + for "example.beam" in the code path and find the <c>doc/chunks/example.chunk</c> + file in the application that defines the <c>example</c> module. If a .chunk file is not + available, then documentation is not available.</p> + + <p>The choice of using a chunk or the filesystem is completely up to the language or library. + In both cases, the documentation can be added or removed at any moment by stripping + the <c>Docs</c> chunk or by removing the doc/chunks directory.</p> + + <p>For example, languages like Elixir and LFE attach the <c>Docs</c> chunk at + compilation time, which can be controlled via a compiler flag. On the other hand, + projects like OTP itself will likely generate the doc/chunks entries on a separate + command, completely unrelated from code compilation.</p> + </section> + + <section> + <title>the "Docs" format</title> + <p>In both storages, the documentation is written in the exactly same format: + an Erlang term serialized to binary via <seemfa marker="erts:erlang#term_to_binary/1"> + <c>term_to_binary/1</c></seemfa>. The term may be optionally compressed when serialized. + It must follow the type specification below:</p> + + <code> +{docs_v1, + Anno :: erl_anno:anno(), + BeamLanguage :: atom(), + Format :: binary(), + ModuleDoc :: #{DocLanguage := DocValue} | none | hidden, + Metadata :: map(), + Docs :: + [{{Kind, Name, Arity}, + Anno :: erl_anno:anno(), + Signature :: [binary()], + Doc :: #{DocLanguage := DocValue} | none | hidden, + Metadata :: map() + }]} when DocLanguage :: binary(), + DocValue :: binary() | term() + </code> + <p>where in the root tuple we have:</p> + <taglist> + <tag>Anno</tag> + <item>annotation (line, column, file) of the definition itself (see + <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>)</item> + + <tag>BeamLanguage</tag> + <item>an atom representing the language, for example: erlang, elixir, lfe, alpaca, etc</item> + + <tag>Format</tag> + <item>the mime type of the documentation, such as <<"text/markdown">> or + <<"application/erlang+html">>. For details of the format used by Erlang + see the <seeguide marker="erl_docgen:doc_storage"><c>EEP-48 Chapter</c></seeguide> + in Erl_Docgen's User's Guide.</item> + + <tag>ModuleDoc</tag> + <item>a map with the documentation language as key, such as <c><<"en">></c> or + <c><<"pt_BR">></c>, and the documentation as a binary value. It may be the + atom <c>none</c> in case there is no documentation or the atom <c>hidden</c> if documentation + has been explicitly disabled for this entry.</item> + + <tag>Metadata</tag> + <item>a map of atom keys with any term as value. This can be used to add annotations like the + <c>authors</c> of a module, <c>deprecated</c>, or anything else a language or documentation + tool may find relevant.</item> + + <tag>Docs</tag> + <item>a list of documentation for other entities (such as functions and types) + in the module.</item> + </taglist> + + <p>For each entry in Docs, we have:</p> + + <taglist> + <tag>{Kind, Name, Arity}</tag> + <item>the kind, name and arity identifying the function, callback, type, etc. + The official entities are: <c>function</c>, <c>type</c> and <c>callback</c>. + Other languages will add their own. For instance, Elixir and LFE may add macro.</item> + + <tag>Anno</tag> + <item>annotation (line, column, file) of the module documentation or of the definition itself + (see <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>).</item> + + <tag>Signature</tag> + <item>the signature of the entity. It is is a list of binaries. Each entry represents a + binary in the signature that can be joined with a whitespace or a newline. For example, + <c>[<<"binary_to_atom(Binary, Encoding)">>, + <<"when is_binary(Binary)">>]</c> may be rendered as + a single line or two lines. It exists exclusively for exhibition purposes.</item> + + <tag>Doc</tag> + <item>a map with the documentation language as key, such as <<"en">> or + <<"pt_BR">>, and the documentation as a value. The documentation may + either be a binary or any Erlang term, both described by <c>Format</c>. If it is an + Erlang term, then the Format must be <<"application/erlang+SUFFIX",>> + such as <<"application/erlang+html">> when the documentation is an Erlang + representation of an HTML document. The Doc may also be atom <c>none</c> in case there is + no documentation or the atom <c>hidden</c> if documentation has been explicitly + disabled for this entry.</item> + + <tag>Metadata</tag> + <item>a map of atom keys with any term as value.</item> + </taglist> + + <p>This shared format is the heart of the EEP as it is what effectively + allows cross-language collaboration.</p> + + <p>The Metadata field exists to allow languages, tools and libraries to add + custom information to each entry. This EEP documents the following metadata keys:</p> + + <taglist> + <tag>authors := [binary()]</tag> + <item>a list of authors as binaries.</item> + + <tag>cross_references := [module() | {module(), {Kind, Name, Arity}}]</tag> + <item>a list of modules or module entries that can be used as cross references + when generating documentation.</item> + + <tag>deprecated := binary()</tag> + <item>when present, it means the current entry is deprecated with a binary + that represents the reason for deprecation and a recommendation to replace + the deprecated code.</item> + + <tag>since := binary()</tag> + <item>a binary representing the version such entry was added, such + as <<"1.3.0">> or <<"20.0">>.</item> + + <tag>edit_url := binary()</tag> + <item>a binary representing a URL to change to change the documentation itself.</item> + </taglist> + + <p>Any key may be added to Metadata at any time. Keys that are frequently + used by the community can be standardized in future versions.</p> + </section> + + <section> + <title>See Also</title> + <p> + <seeerl marker="stdlib:erl_anno"><c>erl_anno(3)</c></seeerl>, + <seeerl marker="stdlib:shell_docs"><c>shell_docs(3)</c></seeerl>, + <seeguide marker="erl_docgen:doc_storage"><c>EEP-48 Chapter in Erl_Docgen's User's Guide</c></seeguide>, + <seemfa marker="code#get_doc/1"><c>code:get_doc/1</c></seemfa> + </p> + </section> +</chapter> diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml index fbb316bbfc..03aa949516 100644 --- a/lib/kernel/doc/src/erl_epmd.xml +++ b/lib/kernel/doc/src/erl_epmd.xml @@ -73,7 +73,7 @@ </func> <func> - <name name="listen_port_please" arity="2" since="OTP @OTP-16250@"/> + <name name="listen_port_please" arity="2" since="OTP 23.0"/> <fsummary>Returns the port number for the local node.</fsummary> <desc> <p>Called by the distribution module to get which port the diff --git a/lib/kernel/doc/src/erpc.xml b/lib/kernel/doc/src/erpc.xml index d609bc3fd9..64032a7f94 100644 --- a/lib/kernel/doc/src/erpc.xml +++ b/lib/kernel/doc/src/erpc.xml @@ -28,7 +28,7 @@ <date>2020-02-20</date> <rev>A</rev> </header> - <module since="OTP @OTP-13450@">erpc</module> + <module since="OTP 23.0">erpc</module> <modulesummary>Enhanced Remote Procedure Call</modulesummary> <description> <p> @@ -75,8 +75,8 @@ <funcs> <func> - <name name="call" arity="2" since="OTP @OTP-13450@"/> - <name name="call" arity="3" since="OTP @OTP-13450@"/> + <name name="call" arity="2" since="OTP 23.0"/> + <name name="call" arity="3" since="OTP 23.0"/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p> @@ -96,8 +96,8 @@ </func> <func> - <name name="call" arity="4" since="OTP @OTP-13450@"/> - <name name="call" arity="5" since="OTP @OTP-13450@"/> + <name name="call" arity="4" since="OTP 23.0"/> + <name name="call" arity="5" since="OTP 23.0"/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p> @@ -249,7 +249,7 @@ </func> <func> - <name name="cast" arity="2" since="OTP @OTP-13450@"/> + <name name="cast" arity="2" since="OTP 23.0"/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p> @@ -266,7 +266,7 @@ </func> <func> - <name name="cast" arity="4" since="OTP @OTP-13450@"/> + <name name="cast" arity="4" since="OTP 23.0"/> <fsummary>Evaluate a function call on a node ignoring the result.</fsummary> <desc> <p> @@ -298,7 +298,7 @@ </func> <func> - <name name="check_response" arity="2" since="OTP @OTP-13450@"/> + <name name="check_response" arity="2" since="OTP 23.0"/> <fsummary>Check if a message is a response corresponding to a previously sent call request.</fsummary> <desc> @@ -341,8 +341,8 @@ </func> <func> - <name name="multicall" arity="2" since="OTP @OTP-13450@"/> - <name name="multicall" arity="3" since="OTP @OTP-13450@"/> + <name name="multicall" arity="2" since="OTP 23.0"/> + <name name="multicall" arity="3" since="OTP 23.0"/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p> @@ -362,8 +362,8 @@ </func> <func> - <name name="multicall" arity="4" since="OTP @OTP-13450@"/> - <name name="multicall" arity="5" since="OTP @OTP-13450@"/> + <name name="multicall" arity="4" since="OTP 23.0"/> + <name name="multicall" arity="5" since="OTP 23.0"/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <type name="caught_call_exception"/> <type name="stack_item"/> @@ -466,7 +466,7 @@ my_multicall(Nodes, Module, Function, Args) -> </func> <func> - <name name="multicast" arity="2" since="OTP @OTP-13450@"/> + <name name="multicast" arity="2" since="OTP 23.0"/> <fsummary>Evaluate a function call on a set nodes.</fsummary> <desc> <p> @@ -483,7 +483,7 @@ my_multicall(Nodes, Module, Function, Args) -> </func> <func> - <name name="multicast" arity="4" since="OTP @OTP-13450@"/> + <name name="multicast" arity="4" since="OTP 23.0"/> <fsummary>Evaluate a function call on a set of nodes ignoring the result.</fsummary> <desc> <p> @@ -517,8 +517,8 @@ my_multicall(Nodes, Module, Function, Args) -> </func> <func> - <name name="receive_response" arity="1" since="OTP @OTP-13450@"/> - <name name="receive_response" arity="2" since="OTP @OTP-13450@"/> + <name name="receive_response" arity="1" since="OTP 23.0"/> + <name name="receive_response" arity="2" since="OTP 23.0"/> <fsummary>Receive a call response corresponding to a previously sent call request.</fsummary> <desc> @@ -581,7 +581,7 @@ my_call(Node, Module, Function, Args, Timeout) -> </func> <func> - <name name="send_request" arity="2" since="OTP @OTP-13450@"/> + <name name="send_request" arity="2" since="OTP 23.0"/> <fsummary>Send a request to evaluate a function call on a node.</fsummary> <desc> <p> @@ -606,7 +606,7 @@ my_call(Node, Module, Function, Args, Timeout) -> </func> <func> - <name name="send_request" arity="4" since="OTP @OTP-13450@"/> + <name name="send_request" arity="4" since="OTP 23.0"/> <fsummary>Send a request to evaluate a function call on a node.</fsummary> <desc> <p> @@ -633,8 +633,8 @@ my_call(Node, Module, Function, Args, Timeout) -> </func> <func> - <name name="wait_response" arity="1" since="OTP @OTP-13450@"/> - <name name="wait_response" arity="2" since="OTP @OTP-13450@"/> + <name name="wait_response" arity="1" since="OTP 23.0"/> + <name name="wait_response" arity="2" since="OTP 23.0"/> <fsummary>Wait or poll for a call response corresponding to a previously sent call request.</fsummary> <desc> diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml index fdde6e40b4..b90cc9d104 100644 --- a/lib/kernel/doc/src/gen_tcp.xml +++ b/lib/kernel/doc/src/gen_tcp.xml @@ -292,7 +292,7 @@ do_recv(Sock, Bs) -> If any other process is interacting with the socket while the transfer is happening, the transfer may not work correctly and messages may remain in the caller's mailbox. For instance - changing the sockets active mode before the transfere is complete + changing the sockets active mode before the transfer is complete may cause this.</p> </desc> </func> diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml index cdfafd90eb..bbe4526f0c 100644 --- a/lib/kernel/doc/src/kernel_app.xml +++ b/lib/kernel/doc/src/kernel_app.xml @@ -134,7 +134,7 @@ <p>The parameter is described in <seemfa marker="application#load/2"><c>application:load/2</c></seemfa>.</p> </item> - <tag><c>dist_auto_connect = Value</c></tag> + <tag><marker id="dist_auto_connect"/><c>dist_auto_connect = Value</c></tag> <item> <p>Specifies when nodes are automatically connected. If this parameter is not specified, a node is always @@ -152,6 +152,12 @@ <seeerl marker="net_kernel"><c>net_kernel(3)</c></seeerl>.</p></item> </taglist> </item> + <tag><marker id="dist_listen"/><c>dist_listen = boolean()</c></tag> + <item> + <p>Specifies whether this node should be listening for incoming + distribution connections. Using this option implies that the node + also is <seecom marker="erts:erl#hidden"><c>-hidden</c></seecom>.</p> + </item> <tag><c>permissions = [Perm]</c></tag> <item> <p>Specifies the default permission for applications when they diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index 624ccb6591..478607d0c8 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,406 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 7.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix race condition during shutdown when + <c>shell_history</c> is enabled. The race condition would + trigger crashes in <c>disk_log</c>.</p> + <p> + Own Id: OTP-16008 Aux Id: PR-2302 </p> + </item> + <item> + <p> + Fix the Erlang distribution to handle the scenario when a + node connects that can handle message fragmentation but + can not handle the atom cache. This bug only affects + users that have implemented a custom distribution + carrier. It has been present since OTP-21.</p> + <p> + The <c>DFLAG_FRAGMENT</c> distribution flag was added to + the set of flags that can be rejected by a distribution + implementation.</p> + <p> + Own Id: OTP-16284</p> + </item> + <item> + <p> + Fix bug where a binary was not allowed to be the format + string in calls to <c>logger:log</c>.</p> + <p> + Own Id: OTP-16395 Aux Id: PR-2444 </p> + </item> + <item> + <p> + Fix bug where <c>logger</c> would end up in an infinite + loop when trying to log the crash of a handler or + formatter.</p> + <p> + Own Id: OTP-16489 Aux Id: ERL-1134 </p> + </item> + <item> + <p> + <c>code:lib_dir/1</c> has been fixed to also return the + lib dir for <c>erts</c>.</p> + <p> + This is been marked as an incompatibility for any + application that depended on <c>{error,bad_name}</c> to + be returned for <c>erts</c>.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16502</p> + </item> + <item> + <p> + The application <c>stop/1</c> callback was not called if + the application master of the application terminated.</p> + <p> + Own Id: OTP-16504 Aux Id: PR-2328 </p> + </item> + <item> + <p> + Fix bug in <c>application:loaded_applications/0</c> that + could cause it to fail with <c>badarg</c> if for example + a concurrent upgrade/downgrade is running.</p> + <p> + Own Id: OTP-16627 Aux Id: PR-2601 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>A new module <seeerl + marker="kernel:erpc"><c>erpc</c></seeerl> has been + introduced in the <c>kernel</c> application. The + <c>erpc</c> module implements an enhanced subset of the + operations provided by the <seeerl + marker="kernel:rpc"><c>rpc</c></seeerl> module. Enhanced + in the sense that it makes it possible to distinguish + between returned value, raised exceptions, and other + errors. <c>erpc</c> also has better performance and + scalability than the original <c>rpc</c> implementation. + This by utilizing the newly introduced <seemfa + marker="erts:erlang#spawn_request/5"><c>spawn_request()</c></seemfa> + BIF. Also the <c>rpc</c> module benefits from these + improvements by utilizing <c>erpc</c> when it is + possible. </p><p> This change has been marked as a + potential incompatibility since <seemfa + marker="kernel:rpc#block_call/5"><c>rpc:block_call()</c></seemfa> + now only is guaranteed to block other <c>block_call()</c> + operations. The documentation previously claimed that it + would block all <c>rpc</c> operations. This has however + never been the case. It previously did not block + node-local <c>block_call()</c> operations.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-13450 Aux Id: OTP-15251 </p> + </item> + <item> + <p>A client node can receive its node name dynamically + from the node that it first connects to. This featured + can by used by</p> <list> <item><p>starting with <c>erl + -sname undefined</c></p></item> <item><p>erl_interface + functions <c>ei_connect_init</c> and friends</p></item> + <item><p><c>erl_call -R</c></p></item> </list> + <p> + Own Id: OTP-13812</p> + </item> + <item> + <p> + Improved the printout of single line logger events for + most of the OTP behaviours in STDLIB and Kernel. This + includes <c>proc_lib</c>, <c>gen_server</c>, + <c>gen_event</c>, <c>gen_statem</c>, <c>gen_fsm</c>, + <c>supervisor</c>, <c>supervisor_bridge</c> and + <c>application</c>.</p> + <p> + Improved the <seeerl + marker="kernel:logger_formatter#chars_limit"><c>chars_limit</c></seeerl> + and <seeerl + marker="kernel:logger_formatter#depth"><c>depth</c></seeerl> + handling in <c>proc_lib</c> and when formatting of + exceptions.</p> + <p> + Own Id: OTP-15299</p> + </item> + <item> + <p> + Remove usage and documentation of old requests of the + I/O-protocol.</p> + <p> + Own Id: OTP-15695</p> + </item> + <item> + <p>Directories can now be opened by <c>file:open/2</c> + when passing the <c>directory</c> option.</p> + <p> + Own Id: OTP-15835 Aux Id: PR-2212 </p> + </item> + <item> + <p> + The check of whether to log or not based on the log level + in <c>logger</c> has been optimized by using + <c>persistent_term</c> to store the log level.</p> + <p> + Own Id: OTP-15948 Aux Id: PR-2356 </p> + </item> + <item> + <p><c>file:read_file_info/2</c> can now be used on opened + files and directories.</p> + <p> + Own Id: OTP-15956 Aux Id: PR-2231 </p> + </item> + <item> + <p> + The <c>-config</c> option to <c>erl</c> now can take + multiple config files without repeating the + <c>-config</c> option. Example:</p> + <p> + erl -config sys local</p> + <p> + Own Id: OTP-16148 Aux Id: PR-2373 </p> + </item> + <item> + <p> + Improved node connection setup handshake protocol. Made + possible to agree on protocol version without dependence + on <c>epmd</c> or other prior knowledge of peer node + version. Also added exchange of node incarnation + ("creation") values and expanded the distribution + capability flag field from 32 to 64 bits.</p> + <p> + Own Id: OTP-16229</p> + </item> + <item> + <p>The possibility to run Erlang distribution without + relying on EPMD has been extended. To achieve this a + couple of new options to the inet distribution has been + added.</p> <taglist> <tag>-dist_listen false</tag> + <item>Setup the distribution channel, but do not listen + for incoming connection. This is useful when you want to + use the current node to interact with another node on the + same machine without it joining the entire + cluster.</item> <tag>-erl_epmd_port Port</tag> + <item>Configure a default port that the built-in EPMD + client should return. This allows the local node to know + the port to connect to for any other node in the + cluster.</item> </taglist> <p>The <c>erl_epmd</c> + callback API has also been extended to allow returning + <c>-1</c> as the creation which means that a random + creation will be created by the node.</p> + <p>In addition a new callback function called + <c>listen_port_please</c> has been added that allows the + callback to return which listen port the distribution + should use. This can be used instead of + <c>inet_dist_listen_min/max</c> if the listen port is to + be fetched from an external service.</p> + <p> + Own Id: OTP-16250</p> + </item> + <item> + <p> + A first EXPERIMENTAL module that is a <c>socket</c> + backend to <c>gen_tcp</c> and <c>inet</c> has been + implemented. Others will follow. Feedback will be + appreciated.</p> + <p> + Own Id: OTP-16260 Aux Id: OTP-15403 </p> + </item> + <item> + <p> + The new experimental <c>socket</c> module has been moved + to the Kernel application.</p> + <p> + Own Id: OTP-16312</p> + </item> + <item> + <p> + Replace usage of deprecated function in the <c>group</c> + module.</p> + <p> + Own Id: OTP-16345</p> + </item> + <item> + <p> + Minor updates due to the new spawn improvements made.</p> + <p> + Own Id: OTP-16368 Aux Id: OTP-15251 </p> + </item> + <item> + <p> + Update of <seeerl + marker="kernel:seq_trace#whatis">sequential + tracing</seeerl> to also support other information + transfers than message passing.</p> + <p> + Own Id: OTP-16370 Aux Id: OTP-15251, OTP-15232 </p> + </item> + <item> + <p><c>code:module_status/1</c> now accepts a list of + modules. <c>code:module_status/0</c>, which returns the + statuses for all loaded modules, has been added.</p> + <p> + Own Id: OTP-16402</p> + </item> + <item> + <p><c>filelib:wildcard/1,2</c> is now twice as fast when + a double star (<c>**</c>) is part of the pattern.</p> + <p> + Own Id: OTP-16419</p> + </item> + <item> + <p> A new implementation of distributed named process + groups has been introduced. It is available in the + <seeerl marker="kernel:pg"><c>pg</c></seeerl> module. + </p><p> Note that this <c>pg</c> module only has the name + in common with the experimental <c>pg</c> module that was + present in <c>stdlib</c> up until OTP 17. </p><p> Thanks + to Maxim Fedorov for the implementation. </p> + <p> + Own Id: OTP-16453 Aux Id: PR-2524 </p> + </item> + <item> + <p> The <seeerl marker="kernel:pg2"><c>pg2</c></seeerl> + module has been deprecated. It has also been scheduled + for removal in OTP 24. </p><p> You are advised to replace + the usage of <c>pg2</c> with the newly introduced <seeerl + marker="kernel:pg"><c>pg</c></seeerl> module. <c>pg</c> + has a similar API, but with a more scalable + implementation. </p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16455</p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p> + The internal hosts file resolver cache <c>inet_hosts</c> + has been rewritten to behave better when the hosts file + changes. For example the cache is updated per entry + instead of cleared and reloaded so lookups do not + temporarily fail during reloading, and; when multiple + processes simultaneously request reload these are now + folded into one instead of all done in sequence. Reported + and first solution suggestion by Maxim Fedorov.</p> + <p> + Own Id: OTP-16487 Aux Id: PR-2516 </p> + </item> + <item> + <p> + Add <c>code:all_available/0</c> that can be used to get + all available modules.</p> + <p> + Own Id: OTP-16494</p> + </item> + <item> + <p> + As of OTP 23, the distributed <seeerl + marker="kernel:disk_log"><c>disk_log</c></seeerl> feature + has been deprecated. It has also been scheduled for + removal in OTP 24.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16495</p> + </item> + <item> + <p> + Add the function <c>code:fetch_docs/1</c> for fetching + embedded documentation for aa Erlang module.</p> + <p> + Own Id: OTP-16499</p> + </item> + <item> + <p> + Improve configure for the net nif, which should increase + portability.</p> + <p> + Own Id: OTP-16530 Aux Id: OTP-16464 </p> + </item> + <item> + <p> + socket: Socket counters and socket global counters are + now represented as maps (instead of property lists).</p> + <p> + Own Id: OTP-16535</p> + </item> + <item> + <p> + The experimental socket module has gotten restrictions + removed so now the 'seqpacket' socket type should work + for any communication domain (protocol family) where the + OS supports it, typically the Unix Domain.</p> + <p> + Own Id: OTP-16550 Aux Id: ERIERL-476 </p> + </item> + <item> + <p> + Allow using custom IO devices in <c>logger_std_h</c>.</p> + <p> + Own Id: OTP-16563 Aux Id: PR-2523 </p> + </item> + <item> + <p>Added <c>file:del_dir_r/1</c> which deletes a + directory together with all of its contents, similar to + <c>rm -rf</c> on Unix systems.</p> + <p> + Own Id: OTP-16570 Aux Id: PR-2565 </p> + </item> + <item> + <p> + socket: By default the socket options rcvtimeo and + sndtimeo are now disabled. To enable these, OTP now has + to be built with the configure option + --enable-esock-rcvsndtimeo</p> + <p> + Own Id: OTP-16620</p> + </item> + <item> + <p> + The experimental gen_tcp compatibility code utilizing the + socket module could loose buffered data when receiving a + specified number of bytes. This bug has been fixed. + Reported by Maksim Lapshin on bugs.erlang.org ERL-1234</p> + <p> + Own Id: OTP-16632 Aux Id: ERL-1234 </p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 6.5.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix bug in <c>application:loaded_applications/0</c> that + could cause it to fail with <c>badarg</c> if for example + a concurrent upgrade/downgrade is running.</p> + <p> + Own Id: OTP-16627 Aux Id: PR-2601 </p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 6.5.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/doc/src/part.xml b/lib/kernel/doc/src/part.xml index 7a7a92b067..dd5d955a78 100644 --- a/lib/kernel/doc/src/part.xml +++ b/lib/kernel/doc/src/part.xml @@ -32,6 +32,8 @@ <p></p> </description> <xi:include href="introduction_chapter.xml"/> + <xi:include href="socket_usage.xml"/> <xi:include href="logger_chapter.xml"/> <xi:include href="logger_cookbook.xml"/> + <xi:include href="eep48_chapter.xml"/> </part> diff --git a/lib/kernel/doc/src/ref_man.xml b/lib/kernel/doc/src/ref_man.xml index 9127157eb5..333cb83bee 100644 --- a/lib/kernel/doc/src/ref_man.xml +++ b/lib/kernel/doc/src/ref_man.xml @@ -69,6 +69,7 @@ <xi:include href="pg2.xml"/> <xi:include href="rpc.xml"/> <xi:include href="seq_trace.xml"/> + <xi:include href="socket.xml"/> <xi:include href="user.xml"/> <xi:include href="wrap_log_reader.xml"/> <xi:include href="zlib_stub.xml"/> diff --git a/lib/kernel/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml new file mode 100644 index 0000000000..5a9fdf98f7 --- /dev/null +++ b/lib/kernel/doc/src/socket.xml @@ -0,0 +1,1204 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2018</year><year>2020</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>socket</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>socket.xml</file> + </header> + <module since="OTP 22.0">socket</module> + <modulesummary>Socket interface.</modulesummary> + <description> + <p>This module provides an API for network socket. + Functions are provided to create, delete and manupilate the sockets + aswell as sending and reciving data on them. </p> + <p>The intent is that it shall be as "close as possible" to the OS + level socket interface. The only significant addition is that some of + the functions, + e.g. <seemfa marker="#recv/3"><c>recv/3</c></seemfa>, + has a timeout argument. </p> + <note> + <p>Some functions allow for an <i>asynchronous</i> call. + This is achieved by setting the <c>Timeout</c> argument to + <c>nowait</c>. For instance, if calling the + <seeerl marker="#recv_async"><c>recv/3</c></seeerl> + function with Timeout set to <c>nowait</c> (<c>recv(Sock, 0, nowait)</c>) + when there is actually nothing to read, it will return with + <c>{select, </c> + <seetype marker="#select_info"><c>SelectInfo</c></seetype><c>}</c> + (<c>SelectInfo</c> contains the + <seetype marker="socket#select_ref">SelectRef</seetype>). + When data eventually arrives a 'select' message + will be sent to the caller: </p> + <taglist> + <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL --> + <tag></tag> + <item><c>{'$socket', socket(), select, SelectRef}</c></item> + </taglist> + <p>The caller can now make another + call to the recv function and now expect data.</p> + <p>Note that all other users are <em>locked out</em> until the + 'current user' has called the function (recv in this case).</p> + <p>Another message the user must be prepared for (when making asynchronous + calls) is the <c>abort</c> message:</p> + <taglist> + <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL --> + <tag></tag> + <item><c>{'$socket', socket(), abort, Info}</c></item> + </taglist> + <p>This message indicates + that the (asynchronous) operation has been aborted. + If, for instance, the socket has been closed (by another process), + <c>Info</c> will be <c>{SelectRef, closed}</c>. </p> + </note> + <note> + <p>There is currently <em>no</em> support for Windows. </p> + <p>Support for IPv6 has been implemented but <em>not</em> tested. </p> + <p>SCTP has only been partly implemented (and not tested). </p> + </note> + </description> + + <datatypes> + <datatype> + <name name="domain"/> + </datatype> + <datatype> + <name name="type"/> + </datatype> + <datatype> + <name name="protocol"/> + </datatype> + <datatype> + <name>socket()</name> + <desc><p>As returned by + <seemfa marker="#open/1"><c>open/1,2,3,4</c></seemfa> and + <seemfa marker="#accept/1"><c>accept/1,2</c></seemfa>.</p> + </desc> + </datatype> + <datatype> + <name name="select_tag"/> + <desc> + <p>A tag that describes the (select) operation.</p> + </desc> + </datatype> + <datatype> + <name name="select_ref"/> + <desc> + <p>A reference that uniquely identifies the (select) operation.</p> + </desc> + </datatype> + <datatype> + <name name="select_info"/> + </datatype> + <datatype> + <name name="socket_counters"/> + </datatype> + <datatype> + <name name="socket_info"/> + </datatype> + <datatype> + <name name="ip4_address"/> + </datatype> + <datatype> + <name name="ip6_address"/> + </datatype> + <datatype> + <name name="sockaddr"/> + </datatype> + <datatype> + <name name="sockaddr_in4"/> + </datatype> + <datatype> + <name name="sockaddr_in6"/> + </datatype> + <datatype> + <name name="sockaddr_un"/> + </datatype> + <datatype> + <name name="sockaddr_ll"/> + </datatype> + <datatype> + <name name="packet_type"/> + </datatype> + <datatype> + <name name="port_number"/> + </datatype> + <datatype> + <name name="in6_flow_info"/> + </datatype> + <datatype> + <name name="in6_scope_id"/> + </datatype> + <datatype> + <name name="send_flags"/> + </datatype> + <datatype> + <name name="send_flag"/> + </datatype> + <datatype> + <name name="recv_flags"/> + </datatype> + <datatype> + <name name="recv_flag"/> + </datatype> + <datatype> + <name name="shutdown_how"/> + </datatype> + <datatype> + <name name="sockopt_level"/> + </datatype> + <datatype> + <name name="otp_socket_option"/> + </datatype> + <datatype> + <name name="socket_option"/> + </datatype> + <datatype> + <name name="ip_socket_option"/> + </datatype> + <datatype> + <name name="ipv6_socket_option"/> + </datatype> + <datatype> + <name name="tcp_socket_option"/> + </datatype> + <datatype> + <name name="udp_socket_option"/> + </datatype> + <datatype> + <name name="sctp_socket_option"/> + </datatype> + <datatype> + <name name="timeval"/> + </datatype> + <datatype> + <name name="ip_tos"/> + </datatype> + <datatype> + <name name="ip_mreq"/> + </datatype> + <datatype> + <name name="ip_mreq_source"/> + </datatype> + <datatype> + <name name="ip_pmtudisc"/> + </datatype> + <datatype> + <name name="ip_msfilter_mode"/> + </datatype> + <datatype> + <name name="ip_msfilter"/> + </datatype> + <datatype> + <name name="ip_pktinfo"/> + </datatype> + <datatype> + <name name="ipv6_mreq"/> + </datatype> + <datatype> + <name name="ipv6_pmtudisc"/> + </datatype> + <datatype> + <name name="ipv6_pktinfo"/> + </datatype> + <datatype> + <name name="sctp_assoc_id"/> + </datatype> + <datatype> + <name name="sctp_sndrcvinfo"/> + </datatype> + <datatype> + <name name="sctp_event_subscribe"/> + </datatype> + <datatype> + <name name="sctp_assocparams"/> + </datatype> + <datatype> + <name name="sctp_initmsg"/> + </datatype> + <datatype> + <name name="sctp_rtoinfo"/> + </datatype> + <datatype> + <name name="msghdr_flag"/> + </datatype> + <datatype> + <name name="msghdr_flags"/> + </datatype> + <datatype> + <name name="msghdr"/> + </datatype> + <datatype> + <name name="cmsghdr_level"/> + </datatype> + <datatype> + <name name="cmsghdr_type"/> + </datatype> + <!-- + <datatype> + <name name="cmsghdr_data"/> + </datatype> + --> + <datatype> + <name name="cmsghdr_recv"/> + </datatype> + <datatype> + <name name="cmsghdr_send"/> + </datatype> + <datatype> + <name name="icmp_dest_unreach"/> + </datatype> + <datatype> + <name name="icmpv6_dest_unreach"/> + </datatype> + <datatype> + <name name="ee_origin"/> + </datatype> + <datatype> + <name name="extended_err"/> + </datatype> + <datatype> + <name name="uint8"/> + </datatype> + <datatype> + <name name="uint16"/> + </datatype> + <datatype> + <name name="uint20"/> + </datatype> + <datatype> + <name name="uint32"/> + </datatype> + <datatype> + <name name="int32"/> + </datatype> + <datatype> + <name name="errcode"/> + <desc> + <p> + The POSIX error codes are mostly come from the + OS level socket interface, + but this module may generate some appropriate + POSIX codes. + </p> + <p> + The other values come from this module's lower levels + and are all fairly fatal internal errors: + </p> + <taglist> + <tag><c>exalloc</c></tag> + <item>Memory allocation failed</item> + <tag><c>exmonitor</c></tag> + <item>Failed to set a monitor on a process</item> + <tag><c>exselect</c></tag> + <item>Select operation failed</item> + <tag><c>exself</c></tag> + <item>Failed to get current process</item> + </taglist> + </desc> + </datatype> + </datatypes> + + <funcs> + <func> + <name name="accept" arity="1" since="OTP 22.0"/> + <name name="accept" arity="2" clause_i="2" since="OTP 22.0"/> + <fsummary>Accept a connection on a socket.</fsummary> + <desc> + <p>Accept a connection on a socket.</p> + <p>This call is used with connection-based socket types + (<c>stream</c> or <c>seqpacket</c>). It extracs the first pending + connection request for the listen socket and returns the (newly) + connected socket.</p> + </desc> + </func> + + <func> + <name name="accept" arity="2" clause_i="1" anchor="accept_async" since="OTP 22.1"/> + <fsummary>Accept a connection on a socket.</fsummary> + <desc> + <p>Accept a connection on a socket.</p> + + <p>This call is used with connection-based socket types + (<c>stream</c> or <c>seqpacket</c>). It extracs the first pending + connection request for the listen socket and returns the (newly) + connected socket.</p> + + <p>In the case when there is no connections waiting, the function + will return with the <c>SelectInfo</c>. The caller can then await a + select message, <c>{'$socket', Socket, select, Info}</c> (where + <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the <c>SelectInfo</c>), + when a client connects (a subsequent call to accept will then return + the socket). </p> + </desc> + </func> + + <func> + <name name="bind" arity="2" since="OTP 22.0"/> + <fsummary>Bind a name to a socket.</fsummary> + <desc> + <p>Bind a name to a socket.</p> + <p>When a socket is created + (with <seemfa marker="#open/2"><c>open</c></seemfa>), + it has no address assigned to it. <c>bind</c> assigns the + address specified by the <c>Addr</c> argument.</p> + <p>The rules used for name binding vary between domains.</p> + </desc> + </func> + + <func> + <name name="cancel" arity="2" since="OTP 22.1"/> + <fsummary>Cancel an asynchronous request.</fsummary> + <desc> + <p>Cancel an asynchronous request.</p> + + <p>Call this function in order to cancel a previous + asynchronous call to, e.g. + <seemfa marker="#recv/3"><c>recv/3</c></seemfa>. </p> + </desc> + </func> + + <func> + <name name="close" arity="1" since="OTP 22.0"/> + <fsummary>Close a socket.</fsummary> + <desc> + <p>Closes the socket.</p> + + <note> + <p>Note that for e.g. <c>protocol</c> = <c>tcp</c>, most implementations + doing a close does not guarantee that any data sent is delivered to + the recipient before the close is detected at the remote side. </p> + <p>One way to handle this is to use the + <seemfa marker="#shutdown/2"><c>shutdown</c></seemfa> + function + (<c>socket:shutdown(Socket, write)</c>) to signal that no more data is + to be sent and then wait for the read side of the socket to be closed.</p> + </note> + </desc> + </func> + + <func> + <name name="connect" arity="2" since="OTP 22.0"/> + <name name="connect" arity="3" clause_i="2" since="OTP 22.0"/> + <fsummary>Initiate a connection on a socket.</fsummary> + <desc> + <p>This function connects the socket to the address + specied by the <c>SockAddr</c> argument.</p> + </desc> + </func> + + <func> + <name name="connect" arity="3" clause_i="1" anchor="connect_async" since="OTP 22.1"/> + <fsummary>Initiate a connection on a socket.</fsummary> + <desc> + <p>This function connects the socket to the address + specied by the <c>SockAddr</c> argument.</p> + + <p>In the case when its not possible to immediately establish a + connection, the function will return with the + <seetype marker="#select_info"><c>SelectInfo</c></seetype>. + The caller can then await a + select message, <c>{'$socket', Socket, select, Info}</c> (where + <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the <c>SelectInfo</c>, + a subsequent call to connect will then + establish the connection). </p> + </desc> + </func> + + <func> + <name name="getopt" arity="3" clause_i="1" since="OTP 22.0"/> + <name name="getopt" arity="3" clause_i="2" since="OTP 22.0"/> + <name name="getopt" arity="3" clause_i="3" since="OTP 22.0"/> + <name name="getopt" arity="3" clause_i="4" since="OTP 22.0"/> + <name name="getopt" arity="3" clause_i="5" since="OTP 22.0"/> + <name name="getopt" arity="3" clause_i="6" since="OTP 22.0"/> + <name name="getopt" arity="3" clause_i="7" since="OTP 22.0"/> + <fsummary>Get an option on a socket.</fsummary> + <desc> + <p>Get an option on a socket.</p> + <p>What properties are valid depend both on <c>Level</c> and + on what kind of socket it is (<c>domain</c>, <c>type</c> and + <c>protocol</c>).</p> + + <p>See the + <seeguide marker="socket_usage#socket_options">socket options</seeguide> + chapter of the users guide for more info. </p> + + <note><p>Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.</p></note> + + </desc> + </func> + + <func> + <name name="getopt" arity="3" clause_i="8" since="OTP 22.0"/> + <fsummary>Get an option on a socket.</fsummary> + <desc> + <p>Get an option on a socket.</p> + + <p>When specifying <c>Level</c> as an integer, and therefor + using "native mode", it is *currently* up to the caller to + know how to interpret the result.</p> + + <p>For more info, see + <seemfa marker="#getopt/3">getopt</seemfa> above. </p> + </desc> + </func> + + <func> + <name name="info" arity="1" since="OTP 22.1"/> + <fsummary>Get miscellaneous socket info.</fsummary> + <desc> + <p>Get miscellaneous info about the socket.</p> + <p>The function returns a map with each info item as a key-value + binding. It reflects the "current" state of the socket. </p> + <note> + <p>In order to ensure data integrity, mutex'es are taken when + needed. So, do not call this function often. </p> + </note> + </desc> + </func> + + <func> + <name name="listen" arity="1" since="OTP 22.0"/> + <name name="listen" arity="2" since="OTP 22.0"/> + <fsummary>Listen for connections on a socket.</fsummary> + <desc> + <p>Listen for connections on a socket.</p> + </desc> + </func> + + <func> + <name name="number_of" arity="0" since="OTP 22.3"/> + <fsummary>Get the number of active sockets.</fsummary> + <desc> + <p>Returns the number of active sockets.</p> + </desc> + </func> + + <func> + <name name="open" arity="1" since="OTP 23.0"/> + <name name="open" arity="2" clause_i="1" since="OTP 23.0"/> + <fsummary>Create an endpoint for communication.</fsummary> + <desc> + <p>Create an endpoint (socket) for communication based on an + already existing file descriptor. + The function attempts to retrieve domain, type and protocol from + the system. This is however not possible on all platforms, and + in those cases it expects it in <c>Opts</c>. </p> + + <p>The <c>Opts</c> argument is intended for providing extra + information for the open call:</p> + <taglist> + <tag><c><![CDATA[dup: boolean()]]></c></tag> + <item> + <p>Shall the provided descriptor be duplicated (dup) or not. + <br/>Defaults to <c>true</c>. </p> + </item> + + <tag><c><![CDATA[debug: boolean()]]></c></tag> + <item> + <p>Enable or disable debug during the open call. + <br/>Defaults to <c>false</c>. </p> + </item> + + <tag><c><![CDATA[domain: socket:domain()]]></c></tag> + <item> + <p>Which domain is the descriptor of. </p> + </item> + + <tag><c><![CDATA[type: socket:type()]]></c></tag> + <item> + <p>Which type is the descriptor of. </p> + </item> + + <tag><c><![CDATA[protocol: socket:protocol()]]></c></tag> + <item> + <p>Which protocol is the descriptor of. </p> + </item> + + </taglist> + + <note> + <p>This function should be used with care! </p> + <p>On some platforms its <em>necessary</em> to provide the + <c>protocol</c> as its impossible to retrieve it. </p> + </note> + </desc> + </func> + + <func> + <name name="open" arity="2" clause_i="2" since="OTP 22.0"/> + <name name="open" arity="3" since="OTP 22.0"/> + <name name="open" arity="4" since="OTP 22.0"/> + <fsummary>Create an endpoint for communication.</fsummary> + <desc> + <p>Creates an endpoint (socket) for communication.</p> + + <p>For some <c>types</c> there is a default protocol, + indicated by <c>default</c>, which it <em>may</em> be + possible to specify. + And for <c>Domain = local</c>, if a protocol <em>is</em> pecified, + it <em>must</em> be <c>default</c>. </p> + + <p>The <c>Opts</c> argument is intended for "other" options. + Currently the only supported option(s) are <c>netns</c>, which + is only supported on the linux platform and <c>debug</c> (controls debug + printouts during the open call).</p> + + <note> + <p>It may not be possible to specify the default protocol (except + when <c>Domain = local</c>). We need to be able to retreive + the resulting protocol, which is <em>not</em> possble on all + platforms. </p> + </note> + </desc> + </func> + + <func> + <name name="peername" arity="1" since="OTP 22.0"/> + <fsummary>Get name of connected socket peer.</fsummary> + <desc> + <p>Returns the address of the peer connected to the socket.</p> + </desc> + </func> + + <func> + <name name="recv" arity="1" since="OTP 22.0"/> + <name name="recv" arity="2" since="OTP 22.0"/> + <name name="recv" arity="3" clause_i="1" since="OTP 22.0"/> + <name name="recv" arity="3" clause_i="3" since="OTP 22.0"/> + <name name="recv" arity="4" clause_i="2" since="OTP 22.0"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>There is a special case for the argument <c>Length</c>. + If it is set to zero (0), it means "give me everything you + currently have".</p> + </desc> + </func> + + <func> + <name name="recv" arity="3" clause_i="2" anchor="recv_async" since="OTP 22.1"/> + <name name="recv" arity="4" clause_i="1" since="OTP 22.1"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + + <p>There is a special case for the argument <c>Length</c>. + If it is set to zero (0), it means "give me everything you + currently have".</p> + + <p>In the case when there is no data waiting, the function + will return with the <c>SelectInfo</c>. The caller can then await a + select message, <c>{'$socket', Socket, select, Info}</c> (where + <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the <c>SelectInfo</c>), + when data has arrived (a subsequent call to recv will then return + the data). </p> + <p>Note that if a length (<c>> 0</c>) is specified, and only part + of that amount of data is available, the function will return with + that data <em>and</em> the <c>SelectInfo</c> (if the caller don't + want to wait for the remaining data, it must immediately call + the <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa> function.)</p> + </desc> + </func> + + <func> + <name name="recvfrom" arity="1" since="OTP 22.0"/> + <name name="recvfrom" arity="2" since="OTP 22.0"/> + <name name="recvfrom" arity="3" clause_i="2" since="OTP 22.0"/> + <name name="recvfrom" arity="3" clause_i="3" since="OTP 22.0"/> + <name name="recvfrom" arity="3" clause_i="5" since="OTP 22.0"/> + <name name="recvfrom" arity="4" clause_i="2" since="OTP 22.0"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message + (if the buffer size is too small, the message will be truncated).</p> + <p>The <c>BufSz</c> argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with <c>Level</c> = <c>otp</c> and + <c>Key</c> = <c>rcvbuf</c>) is used.</p> + <p>It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvfrom call + is needed, possibly with a then adjusted buffer size.</p> + </desc> + </func> + + <func> + <name name="recvfrom" arity="3" clause_i="1" anchor="recvfrom_async" since="OTP 22.1"/> + <name name="recvfrom" arity="3" clause_i="4" since="OTP 22.1"/> + <name name="recvfrom" arity="4" clause_i="1" since="OTP 22.1"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message + (if the buffer size is too small, the message will be truncated).</p> + <p>The <c>BufSz</c> argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with <c>Level</c> = <c>otp</c> and + <c>Key</c> = <c>rcvbuf</c>) is used.</p> + <p>It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvfrom call + is needed, possibly with a then adjusted buffer size.</p> + + <p>In the case when there is no data waiting, the function + will return with the <c>SelectInfo</c>. The caller can then await a + select message, <c>{'$socket', Socket, select, Info}</c> (where + <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the <c>SelectInfo</c>), + when data has arrived (a subsequent call to recvfrom will then return + the data). </p> + </desc> + </func> + + <func> + <name name="recvmsg" arity="1" since="OTP 22.0"/> + <name name="recvmsg" arity="2" clause_i="1" since="OTP 22.0"/> + <name name="recvmsg" arity="2" clause_i="3" since="OTP 22.0"/> + <name name="recvmsg" arity="3" clause_i="2" since="OTP 22.0"/> + <name name="recvmsg" arity="3" clause_i="3" since="OTP 22.0"/> + <name name="recvmsg" arity="5" clause_i="2" since="OTP 22.0"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message.</p> + <p>The message will be delivered in the form of a <c>msghdr()</c>, + which may contain the source address (if socket not connected), + a list of <c>cmsghdr_recv()</c> (depends on what socket options have + been set and what the protocol and platform supports) and + also a set of flags, providing further info about the read. </p> + + <p>The <c>BufSz</c> argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with <c>Level</c> = <c>otp</c> and + <c>Key</c> = <c>rcvbuf</c>) is used.</p> + + <p>The <c>CtrlSz</c> argument basically defines the size of the + receive buffer for the control messages. + By setting the value to zero (0), the configured size (setopt + with <c>Level</c> = <c>otp</c>) is used.</p> + + <p>It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvmsg call + is needed, possibly with a then adjusted buffer size.</p> + </desc> + </func> + + <func> + <name name="recvmsg" arity="2" clause_i="2" anchor="recvmsg_async" since="OTP 22.1"/> + <name name="recvmsg" arity="3" clause_i="1" since="OTP 22.1"/> + <name name="recvmsg" arity="5" clause_i="1" since="OTP 22.1"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message.</p> + <p>The message will be delivered in the form of a <c>msghdr()</c>, + which may contain the source address (if socket not connected), + a list of <c>cmsghdr_recv()</c> (depends on what socket options have + been set and what the protocol and platform supports) and + also a set of flags, providing further info about the read. </p> + + <p>The <c>BufSz</c> argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with <c>Level</c> = <c>otp</c> and + <c>Key</c> = <c>rcvbuf</c>) is used.</p> + + <p>The <c>CtrlSz</c> argument basically defines the size of the + receive buffer for the control messages. + By setting the value to zero (0), the configured size (setopt + with <c>Level</c> = <c>otp</c>) is used.</p> + + <p>It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvmsg call + is needed, possibly with a then adjusted buffer size.</p> + + <p>In the case when there is no data waiting, the function + will return with the <c>SelectInfo</c>. The caller can then await a + select message, <c>{'$socket', Socket, select, Info}</c> (where + <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the <c>SelectInfo</c>), + when data has arrived (a subsequent call to recvmsg will then return + the data). </p> + </desc> + </func> + + <func> + <name name="send" arity="2" since="OTP 22.0"/> + <name name="send" arity="3" clause_i="1" since="OTP 22.0"/> + <name name="send" arity="3" clause_i="3" since="OTP 22.0"/> + <name name="send" arity="4" clause_i="2" since="OTP 22.0"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a connected socket.</p> + </desc> + </func> + + <func> + <name name="send" arity="3" clause_i="2" anchor="send_async" since="OTP 22.1"/> + <name name="send" arity="4" clause_i="1" since="OTP 22.1"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a connected socket.</p> + + <p>In the case when there is no room in the (system-) buffers, + the function will return with the <c>SelectInfo</c>. The caller + can then await a select message, + <c>{'$socket', Socket, select, Info}</c> + (where <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the + <c>SelectInfo</c>), when there is room for more data (a subsequent + call to send will then send the data). </p> + <p>Note that if not all the data was sent, the function will return + with the remaining data <em>and</em> the <c>SelectInfo</c> + (if the caller don't + want to wait to be able to send the rest, it should immediately call + the <seemfa marker="#cancel/2"><c>cancel/2</c></seemfa> function.)</p> + </desc> + </func> + + <func> + <name name="sendmsg" arity="2" since="OTP 22.0"/> + <name name="sendmsg" arity="3" clause_i="1" since="OTP 22.0"/> + <name name="sendmsg" arity="3" clause_i="3" since="OTP 22.0"/> + <name name="sendmsg" arity="4" clause_i="2" since="OTP 22.0"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a socket. The destination, if needed + (socket <em>not</em> connected) is provided in the <c>MsgHdr</c>, + which also contains the message to send, + The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c> + (depends on what the protocol and platform supports).</p> + + <p>Unlike the <seemfa marker="#send/2"><c>send</c></seemfa> function, + this one sends <em>one message</em>. + This means that if, for whatever reason, its not possible to send the + message in one go, the function will instead return with the + <em>remaining</em> data (<c>{ok, Remaining}</c>). Thereby leaving it + up to the caller to decide what to do (retry with the remaining data + of give up). </p> + + </desc> + </func> + + <func> + <name name="sendmsg" arity="3" clause_i="2" anchor="sendmsg_async" since="OTP 22.1"/> + <name name="sendmsg" arity="4" clause_i="1" since="OTP 22.1"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a socket. The destination, if needed + (socket <em>not</em> connected) is provided in the <c>MsgHdr</c>, + which also contains the message to send, + The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c> + (depends on what the protocol and platform supports).</p> + + <p>Unlike the <seemfa marker="#send/2"><c>send</c></seemfa> function, + this one sends <em>one message</em>. + This means that if, for whatever reason, its not possible to send the + message in one go, the function will instead return with the + <em>remaining</em> data (<c>{ok, Remaining}</c>). Thereby leaving it + up to the caller to decide what to do (retry with the remaining data + of give up). </p> + + <p>In the case when there is no room in the (system-) buffers, + the function will return with the <c>SelectInfo</c>. The caller + can then await a select message, + <c>{'$socket', Socket, select, Info}</c> + (where <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the + <c>SelectInfo</c>), when there is room for more data (a subsequent + call to sendmsg will then send the data). </p> + </desc> + </func> + + <func> + <name name="sendto" arity="3" since="OTP 22.0"/> + <name name="sendto" arity="4" clause_i="1" since="OTP 22.0"/> + <name name="sendto" arity="4" clause_i="3" since="OTP 22.0"/> + <name name="sendto" arity="5" clause_i="2" since="OTP 22.0"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a socket, to the specified destination.</p> + </desc> + </func> + + <func> + <name name="sendto" arity="4" clause_i="2" anchor="sendto_async" since="OTP 22.1"/> + <name name="sendto" arity="5" clause_i="1" since="OTP 22.1"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a socket, to the specified destination.</p> + + <p>In the case when there is no room in the (system-) buffers, + the function will return with the <c>SelectInfo</c>. The caller + can then await a select message, + <c>{'$socket', Socket, select, Info}</c> + (where <c>Info</c> is the + <seetype marker="socket#select_ref"><c>ref</c></seetype> + field from the + <c>SelectInfo</c>), when there is room for more data (a subsequent + call to sendto will then send the data). </p> + </desc> + </func> + + <func> + <name name="setopt" arity="4" clause_i="1" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="2" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="3" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="4" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="5" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="6" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="7" since="OTP 22.0"/> + <name name="setopt" arity="4" clause_i="8" since="OTP 22.0"/> + <fsummary>Set an option on a socket.</fsummary> + <desc> + <p>Set an option on a socket.</p> + <p>What options are valid depend both on <c>Level</c> and on + what kind of socket it is (<c>domain</c>, <c>type</c> and + <c>protocol</c>).</p> + + <p>See the + <seeguide marker="socket_usage#socket_options">socket options</seeguide> + chapter of the users guide for more info. </p> + + <note><p>Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.</p></note> + + <note><p>Sockets are set 'non-blocking' when created, so this option + is *not* available (as it would adversely effect the Erlang VM + to set a socket 'blocking').</p></note> + </desc> + </func> + + <func> + <name name="setopt" arity="4" clause_i="8" since="OTP 22.0"/> + <fsummary>Set options on a socket.</fsummary> + <desc> + <p>Set options on a socket.</p> + + <p>When specifying <c>Level</c> as an integer, and therefor + using "native mode", it is *currently* up to the caller to + know how to encode the <c>Value</c>.</p> + + <p>For more info, see + <seemfa marker="#setopt/4">setopt</seemfa> above. </p> + </desc> + </func> + + <func> + <name name="shutdown" arity="2" since="OTP 22.0"/> + <fsummary>Shut down part of a full-duplex connection.</fsummary> + <desc> + <p>Shut down all or part of a full-duplex connection.</p> + </desc> + </func> + + <func> + <name name="sockname" arity="1" since="OTP 22.0"/> + <fsummary>Get socket name.</fsummary> + <desc> + <p>Returns the current address to which the socket is bound.</p> + </desc> + </func> + + <func> + <name since="OTP 22.0"> + supports() -> Supports + </name> + <name since="OTP 22.0"> + supports(Key1 :: options) -> SupportsOptions + </name> + <name since="OTP 22.0"> + supports(Key1 :: send_flags) -> SupportsSendFlags + </name> + <name since="OTP 22.0"> + supports(Key1 :: recv_flags) -> SupportsRecvFlags + </name> + <name since="OTP 22.0"> + supports(Key1 :: options, Key2 :: socket) -> SupportsOptionsSocket + </name> + <name since="OTP 22.0"> + supports(Key1 :: options, Key2 :: ip) -> SupportsOptionsIP + </name> + <name since="OTP 22.0"> + supports(Key1 :: options, Key2 :: ipv6) -> SupportsOptionsIPv6 + </name> + <name since="OTP 22.0"> + supports(Key1 :: options, Key2 :: tcp) -> SupportsOptionsTCP + </name> + <name since="OTP 22.0"> + supports(Key1 :: options, Key2 :: udp) -> SupportsOptionsUDP + </name> + <name since="OTP 22.0"> + supports(Key1 :: options, Key2 :: sctp) -> SupportsOptionsSCTP + </name> + <!-- + <name name="supports" arity="0" since="OTP 22.0"/> + <name name="supports" arity="1" since="OTP 22.0"/> + <name name="supports" arity="2" since="OTP 22.0"/> + --> + <fsummary>Report info about what the platform supports.</fsummary> + <type> + <v> + Supports :: [{Feature, boolean()} + | {send_flags, SupportsSendFlags} + | {recv_flags, SupportsRecvFlags} + | {options, SupportsOptions}] + </v> + <v>Feature :: sctp | ipv6 | local | netns</v> + <v> + SupportsSendFlags :: + [{<seetype marker="#send_flag">send_flag()</seetype>, boolean()}] + </v> + <v> + SupportsRecvFlags :: + [{<seetype marker="#recv_flag">recv_flag()</seetype>, boolean()}] + </v> + <v> + SupportsOptions :: + [{socket, SupportsOptionsSocket} + | {ip, SupportsOptionsIP} + | {ipv6, SupportsOptionsIPv6} + | {tcp, SupportsOptionsTCP} + | {udp, SupportsOptionsUDP} + | {sctp, SupportsOptionsSCTP}] + </v> + <v> + SupportsOptionsSocket :: + [{<seetype marker="#socket_option">socket_option()</seetype>, boolean()}] + </v> + <v> + SupportsOptionsIP :: + [{<seetype marker="#ip_socket_option">ip_socket_option()</seetype>, boolean()}] + </v> + <v> + SupportsOptionsIPv6 :: + [{<seetype marker="#ipv6_socket_option">ipv6_socket_option()</seetype>, boolean()}] + </v> + <v> + SupportsOptionsTCP :: + [{<seetype marker="#tcp_socket_option">tcp_socket_option()</seetype>, boolean()}] + </v> + <v> + SupportsOptionsUDP :: + [{<seetype marker="#udp_socket_option">udp_socket_option()</seetype>, boolean()}] + </v> + <v> + SupportsOptionsSCTP :: + [{<seetype marker="#sctp_socket_option">sctp_socket_option()</seetype>, boolean()}] + </v> + </type> + <desc> + <p> + This function retreives information about what the + platform supports, such as if SCTP is supported, + or which socket options are supported. + </p> + <p> + For keys other than the known the empty list is returned, + Note that in a future version or on a different platform + there might be more supported items. + </p> + </desc> + </func> + + <func> + <name since="OTP 23.0"> + is_supported(Key1 :: sctp | ipv6 | local | netns) -> boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: send_flags, Key2 :: SendFlag) -> boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: recv_flags, Key2 :: RecvFlag) -> boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: options, Key2 :: socket, Key3 :: SocketOption) -> + boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: options, Key2 :: ip, Key3 :: IPSocketOption) -> + boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: options, Key2 :: ipv6, Key3 :: IPv6SocketOption) -> + boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: options, Key2 :: tcp, Key3 :: TCPSocketOption) -> + boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: options, Key2 :: udp, Key3 :: UDPSocketOption) -> + boolean() + </name> + <name since="OTP 23.0"> + is_supported(Key1 :: options, Key2 :: sctp, Key3 :: SCTPSocketOption) -> + boolean() + </name> + <fsummary>Report info about what the platform supports.</fsummary> + <type> + <v> + SocketOption :: + <seetype marker="#socket_option">socket_option()</seetype> + </v> + <v> + IPSocketOption :: + <seetype marker="#ip_socket_option">ip_socket_option()</seetype> + </v> + <v> + IPv6SocketOption :: + <seetype marker="#ipv6_socket_option">ipv6_socket_option()</seetype> + </v> + <v> + TCPSocketOption :: + <seetype marker="#tcp_socket_option">tcp_socket_option()</seetype> + </v> + <v> + UDPSocketOption :: + <seetype marker="#udp_socket_option">udp_socket_option()</seetype> + </v> + <v> + SCTPSocketOption :: + <seetype marker="#sctp_socket_option">sctp_socket_option()</seetype> + </v> + </type> + <desc> + <p> + This function retreives information about what the + platform supports, such as if SCTP is supported, + or which socket options are supported. + </p> + <p> + For keys other than the known <c>false</c> is returned. + Note that in a future version or on a different platform + there might be more supported items. + </p> + </desc> + </func> + + <func> + <name name="which_sockets" arity="0" since="OTP 22.3"/> + <name name="which_sockets" arity="1" since="OTP 22.3"/> + <fsummary>Get the current active sockets.</fsummary> + <desc> + <p>Returns a list of all sockets, according to the + filter rule.</p> + <p>There are several pre-made filter rule(s) and one general: </p> + <taglist> + <tag><c><![CDATA[inet | inet6]]></c></tag> + <item> + <p>Selection based on the domain of the socket. + <br/>Only a subset is valid. </p> + </item> + + <tag><c><![CDATA[stream | dgram | seqpacket]]></c></tag> + <item> + <p>Selection based on the type of the socket. + <br/>Only a subset is valid. </p> + </item> + + <tag><c><![CDATA[sctp | tcp | udp]]></c></tag> + <item> + <p>Selection based on the protocol of the socket. + <br/>Only a subset is valid. </p> + </item> + + <tag><c><![CDATA[pid()]]></c></tag> + <item> + <p>Selection base on which sockets has this pid as + Controlling Process. </p> + </item> + + <tag><c><![CDATA[fun((socket_info()) -> boolean())]]></c></tag> + <item> + <p>The general filter rule. + <br/>A fun that takes the socket info and returns a + <c><![CDATA[boolean()]]></c> + (<c><![CDATA[true]]></c> if the socket sould be included and + <c><![CDATA[false]]></c> if should not). </p> + </item> + </taglist> + </desc> + </func> + + </funcs> + <section> + <title>Examples</title> + <marker id="examples"></marker> + <code type="none"> +client(Addr, SAddr, SPort) -> + {ok, Sock} = socket:open(inet, stream, tcp), + {ok, _} = socket:bind(Sock, #{family => inet, + addr => Addr}), + ok = socket:connect(Sock, #{family => inet, + addr => SAddr, + port => SPort}), + Msg = list_to_binary("hello"), + ok = socket:send(Sock, Msg), + ok = socket:shutdown(Sock, write), + {ok, Msg} = socket:recv(Sock), + ok = socket:close(Sock). + +server(Addr, Port) -> + {ok, LSock} = socket:open(inet, stream, tcp), + {ok, _} = socket:bind(LSock, #{family => inet, + port => Port, + addr => Addr}), + ok = socket:listen(LSock), + {ok, Sock} = socket:accept(LSock), + {ok, Msg} = socket:recv(Sock), + ok = socket:send(Sock, Msg), + ok = socket:shutdown(Sock, write), + ok = socket:close(Sock), + ok = socket:close(LSock). + </code> + </section> +</erlref> diff --git a/lib/kernel/doc/src/socket_usage.xml b/lib/kernel/doc/src/socket_usage.xml new file mode 100644 index 0000000000..580ff4cbd0 --- /dev/null +++ b/lib/kernel/doc/src/socket_usage.xml @@ -0,0 +1,913 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2018</year><year>2019</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>Socket Usage</title> + <prepared>Micael Karlberg</prepared> + <docno></docno> + <date>2018-07-17</date> + <rev>PA1</rev> + <file>socket_usage.xml</file> + </header> + + <section> + <title>Introduction</title> + <p>The socket interface (module) is basically an "thin" layer on top of + the OS socket interface. It is assumed that, unless you have special needs, + gen_[tcp|udp|sctp] should be sufficent (when they become available). </p> + <p>Note that just because we have a documented and described option, + it does <em>not</em> mean that the OS supports it. So its recommended + that the user reads the platform specific documentation for the + option used. </p> + <section> + <title>Asynchronous calls</title> + <p>Some functions allow for an <i>asynchronous</i> call + (<seeerl marker="socket#accept_async"><c>accept/2</c></seeerl>, + <seeerl marker="socket#connect_async"><c>connect/3</c></seeerl>, + <seeerl marker="socket#recv_async"><c>recv/3,4</c></seeerl>, + <seeerl marker="socket#recvfrom_async"><c>recvfrom/3,4</c></seeerl>, + <seeerl marker="socket#recvmsg_async"><c>recvmsg/2,3,5</c></seeerl>, + <seeerl marker="socket#send_async"><c>send/3,4</c></seeerl>, + <seeerl marker="socket#sendmsg_async"><c>sendmsg/3,4</c></seeerl> and + <seeerl marker="socket#sendto_async"><c>sendto/4,5</c></seeerl>). + This is achieved by setting the <c>Timeout</c> argument to + <c>nowait</c>. For instance, if calling the + <seeerl marker="socket#recv_async"><c>recv/3</c></seeerl> + function with Timeout set to <c>nowait</c> (i.e. + <c>recv(Sock, 0, nowait)</c>) + when there is actually nothing to read, it will return with + <c>{select, </c> + <seetype marker="socket#select_info"><c>SelectInfo</c></seetype><c>}</c> + (<c>SelectInfo</c> contains the + <seetype marker="socket#select_ref">SelectRef</seetype>). + When data eventually arrives a 'select message' + will be sent to the caller:</p> + <taglist> + <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL --> + <tag></tag> + <item><c>{'$socket', socket(), select, SelectRef}</c></item> + </taglist> + <p>The caller can then make another + call to the recv function and now expect data.</p> + <p>Note that all other users are <em>locked out</em> until the + 'current user' has called the function (recv in this case). So either + immediately call the function or + <seemfa marker="socket#cancel/2"><c>cancel</c></seemfa>. </p> + <p>The user must also be prepared to receive an abort message: </p> + <taglist> + <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL --> + <tag></tag> + <item><c>{'$socket', socket(), abort, Info}</c></item> + </taglist> + <p>If the operation is aborted + for whatever reason (e.g. if the socket is closed "by someone else"). + The <c>Info</c> part contains the abort reason (in this case that + the socket has been closed <c>Info = {SelectRef, closed}</c>). </p> + + <p>The general form of the 'socket' message is: </p> + <taglist> + <!-- NOTE THAT THE EMPTY TAG IS INTENTIONAL --> + <tag></tag> + <item><c>{'$socket', Sock :: socket(), Tag :: atom(), Info :: term()}</c></item> + </taglist> + <p>Where the format of <c>Info</c> is a function of <c>Tag</c>:</p> + <table> + <row> + <cell><em>Tag</em></cell> + <cell><em>Info value type</em></cell> + </row> + <row> + <cell>select</cell> + <cell>select_ref()</cell> + </row> + <row> + <cell>abort</cell> + <cell>{select_ref(), Reason :: term()}</cell> + </row> + <tcaption>socket message info value type</tcaption> + </table> + <p>The <c>select_ref()</c> is the same as was received in the + <seetype marker="socket#select_info"><c>SelectInfo</c></seetype>. </p> + </section> + </section> + + <section> + <marker id="socket_options"></marker> + <title>Socket Options</title> + + <p>Options for level <c>otp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>assoc_id</cell> + <cell>integer()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>type = seqpacket, protocol = sctp, is an association</cell> + </row> + <row> + <cell>debug</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>iow</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>controlling_process</cell> + <cell>pid()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvbuf</cell> + <cell>default | pos_integer() | {pos_integer(), pos_ineteger()}</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>'default' only valid for set. + The tuple form is only valid for type 'stream' and protocol 'tcp'.</cell> + </row> + <row> + <cell>rcvctrlbuf</cell> + <cell>default | pos_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>default only valid for set</cell> + </row> + <row> + <cell>sndctrlbuf</cell> + <cell>default | pos_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>default only valid for set</cell> + </row> + <row> + <cell>fd</cell> + <cell>integer()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <tcaption>option levels</tcaption> + </table> + + <p>Options for level <c>socket</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>acceptconn</cell> + <cell>boolean()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>bindtodevice</cell> + <cell>string()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>Before Linux 3.8, this socket option could be set, but not get. + Only works for some socket types (e.g. <c>inet</c>). + If empty value is set, the binding is removed.</cell> + </row> + <row> + <cell>broadcast</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram</cell> + </row> + <row> + <cell>debug</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>may require admin capability</cell> + </row> + <row> + <cell>domain</cell> + <cell>domain()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell><em>Not</em> on FreeBSD (for instance)</cell> + </row> + <row> + <cell>dontroute</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>keepalive</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>linger</cell> + <cell>abort | linger()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>oobinline</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>peek_off</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>domain = local (unix). + Currently disabled due to a possible infinite loop when + calling recv([peek]) the second time. + </cell> + </row> + <row> + <cell>priority</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>protocol</cell> + <cell>protocol()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell><em>Not</em> on (some) Darwin (for instance)</cell> + </row> + <row> + <cell>rcvbuf</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvlowat</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvtimeo</cell> + <cell>timeval()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell> + This option is not normally supported (see why below). + OTP has to be explicitly built with the + <c>--enable-esock-rcvsndtime</c> configure option for this + to be available. + Since our implementation is <em>nonblocking</em>, + its unknown if and how this option works, or even if + it may cause malfunctions. + Therefor, we do not recommend setting this option. + Instead, use the <c>Timeout</c> argument to, for instance, + the + <seemfa marker="socket#recv/3"><c>recv/3</c></seemfa> + function. + </cell> + </row> + <row> + <cell>reuseaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>reuseport</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>domain = inet | inet6</cell> + </row> + <row> + <cell>sndbuf</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>sndlowat</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>not changeable on Linux</cell> + </row> + <row> + <cell>sndtimeo</cell> + <cell>timeval()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell> + This option is not normally supported (see why below). + OTP has to be explicitly built with the + <c>--enable-esock-rcvsndtime</c> configure option for this + to be available. + Since our implementation is <em>nonblocking</em>, + its unknown if and how this option works, or even if + it may cause malfunctions. + Therefor, we do not recommend setting this option. + Instead, use the <c>Timeout</c> argument to, for instance, + the + <seemfa marker="socket#send/3"><c>send/3</c></seemfa> + function. + </cell> + </row> + <row> + <cell>timestamp</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>type</cell> + <cell>type()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <tcaption>socket options</tcaption> + </table> + + <p>Options for level <c>ip</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>add_membership</cell> + <cell>ip_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>add_source_membership</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>block_source</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>drop_membership</cell> + <cell>ip_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>drop_source_membership</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>freebind</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>hdrincl</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>minttl</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>msfilter</cell> + <cell>null | ip_msfilter()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>mtu</cell> + <cell>integer()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>mtu_discover</cell> + <cell>ip_pmtudisc()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_all</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_if</cell> + <cell>any | ip4_address()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_loop</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_ttl</cell> + <cell>uint8()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>nodefrag</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>pktinfo</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram</cell> + </row> + <row> + <cell>recvdstaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram</cell> + </row> + <row> + <cell>recverr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recvif</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw</cell> + </row> + <row> + <cell>recvopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type =/= stream</cell> + </row> + <row> + <cell>recvorigdstaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recvttl</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type =/= stream</cell> + </row> + <row> + <cell>retopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type =/= stream</cell> + </row> + <row> + <cell>router_alert</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>sendsrcaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>tos</cell> + <cell>ip_tos()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>some high-priority levels may require superuser capability</cell> + </row> + <row> + <cell>transparent</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>requires admin capability</cell> + </row> + <row> + <cell>ttl</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>unblock_source</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <tcaption>ip options</tcaption> + </table> + + <p>Options for level <c>ipv6</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>addrform</cell> + <cell>inet</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>allowed only for IPv6 sockets that are connected and bound to a + v4-mapped-on-v6 address</cell> + </row> + <row> + <cell>add_membership</cell> + <cell>ipv6_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>authhdr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, obsolete?</cell> + </row> + <row> + <cell>drop_membership</cell> + <cell>ipv6_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>dstopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>flowinfo</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>hoplimit</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw. + On some platforms (e.g. FreeBSD) is used to set in order to + get <c>hoplimit</c> as a control message heeader. + On others (e.g. Linux), <c>recvhoplimit</c> is set in order to get + <c>hoplimit</c>. </cell> + </row> + <row> + <cell>hopopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>mtu</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>Get: Only after the socket has been connected</cell> + </row> + <row> + <cell>mtu_discover</cell> + <cell>ipv6_pmtudisc()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_hops</cell> + <cell>default | uint8()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_if</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw</cell> + </row> + <row> + <cell>multicast_loop</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recverr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recvhoplimit</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw. + On some platforms (e.g. Linux), <c>recvhoplimit</c> + is set in order to get <c>hoplimit</c></cell> + </row> + <row> + <cell>recvpktinfo | pktinfo</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw. + On some platforms (e.g. FreeBSD) is used to set in order to + get <c>hoplimit</c> as a control message heeader. + On others (e.g. Linux), <c>recvhoplimit</c> is set in order to get + <c>hoplimit</c>. </cell> + </row> + <row> + <cell>recvtclass</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw. + On some platforms is used to set (=true) in order to + get the <c>tclass</c> control message heeader. + On others, <c>tclass</c> is set in order to get + <c>tclass</c> control message heeader. </cell> + </row> + <row> + <cell>router_alert</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>rthdr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>tclass</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw. + On some platforms is used to set (=true) in order to + get the <c>tclass</c> control message heeader. + On others, <c>recvtclass</c> is set in order to get + <c>tclass</c> control message heeader. </cell> + </row> + <row> + <cell>unicast_hops</cell> + <cell>default | uint8()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>v6only</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <tcaption>ipv6 options</tcaption> + </table> + + <p>Options for level <c>tcp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>congestion</cell> + <cell>string()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>maxseg</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>Set not allowed on all platforms.</cell> + </row> + <row> + <cell>nodelay</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <tcaption>tcp options</tcaption> + </table> + + <p>Options for level <c>udp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>cork</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <tcaption>udp options</tcaption> + </table> + + <p>Options for level <c>sctp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>associnfo</cell> + <cell>sctp_assocparams()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>autoclose</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>disable_fragments</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>events</cell> + <cell>sctp_event_subscribe()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>initmsg</cell> + <cell>sctp_initmsg()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>maxseg</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>nodelay</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rtoinfo</cell> + <cell>sctp_rtoinfo()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <tcaption>sctp options</tcaption> + </table> + + </section> +</chapter> + diff --git a/lib/kernel/doc/src/specs.xml b/lib/kernel/doc/src/specs.xml index 00f6f04218..990bc85782 100644 --- a/lib/kernel/doc/src/specs.xml +++ b/lib/kernel/doc/src/specs.xml @@ -35,6 +35,7 @@ <xi:include href="../specs/specs_pg2.xml"/> <xi:include href="../specs/specs_rpc.xml"/> <xi:include href="../specs/specs_seq_trace.xml"/> + <xi:include href="../specs/specs_socket.xml"/> <xi:include href="../specs/specs_user.xml"/> <xi:include href="../specs/specs_wrap_log_reader.xml"/> <xi:include href="../specs/specs_zlib_stub.xml"/> diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index 07f4f2304c..6c75bcffee 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -135,6 +135,7 @@ MODULES = \ ram_file \ rpc \ seq_trace \ + socket \ standard_error \ user \ user_drv \ diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl index 8db0646dae..869e9be1e8 100644 --- a/lib/kernel/src/application_controller.erl +++ b/lib/kernel/src/application_controller.erl @@ -271,13 +271,12 @@ which_applications(Timeout) -> gen_server:call(?AC, which_applications, Timeout). loaded_applications() -> - ets:filter(ac_tab, - fun([{{loaded, AppName}, #appl{descr = Descr, vsn = Vsn}}]) -> - {true, {AppName, Descr, Vsn}}; - (_) -> - false - end, - []). + ets:select(ac_tab, + [{ + {{loaded, '$1'}, #appl{descr = '$2', vsn = '$3', _ = '_'}}, + [], + [{{'$1', '$2', '$3'}}] + }]). %% Returns some debug info info() -> diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl index 9eb0e80af4..7b0ef8cf38 100644 --- a/lib/kernel/src/code.erl +++ b/lib/kernel/src/code.erl @@ -862,7 +862,7 @@ get_doc_chunk(Filename, Mod) when is_atom(Mod) -> {error,beam_lib,{file_error,_Filename,enoent}} -> get_doc_chunk(Filename, atom_to_list(Mod)); {ok, {Mod, [{"Docs",Bin}]}} -> - binary_to_term(Bin) + {ok,binary_to_term(Bin)} end; get_doc_chunk(Filename, Mod) -> case filename:dirname(Filename) of @@ -901,24 +901,22 @@ get_function_docs_from_ast(AST) -> lists:flatmap(fun(E) -> get_function_docs_from_ast(E, AST) end, AST). get_function_docs_from_ast({function,Anno,Name,Arity,_Code}, AST) -> Signature = io_lib:format("~p/~p",[Name,Arity]), - Specs = lists:filter(fun({attribute,_Ln,spec,{FA,_}}) -> - case FA of - {F,A} -> - F =:= Name andalso A =:= Arity; - {_, F, A} -> - F =:= Name andalso A =:= Arity - end; - (_) -> false - end, AST), + Specs = lists:filter( + fun({attribute,_Ln,spec,{FA,_}}) -> + case FA of + {F,A} -> + F =:= Name andalso A =:= Arity; + {_, F, A} -> + F =:= Name andalso A =:= Arity + end; + (_) -> false + end, AST), SpecMd = case Specs of - [S] -> #{ spec => [S] }; + [S] -> #{ signature => [S] }; [] -> #{} end, - FnDocs = [], - Md = SpecMd#{}, - [{{function, Name, Arity}, Anno, [unicode:characters_to_binary(Signature)], - #{ <<"en">> => FnDocs }, - Md#{}}]; + [{{function, Name, Arity}, Anno, + [unicode:characters_to_binary(Signature)], none, SpecMd}]; get_function_docs_from_ast(_, _) -> []. diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl index 0b43377821..75a48e8ac4 100644 --- a/lib/kernel/src/dist_util.erl +++ b/lib/kernel/src/dist_util.erl @@ -353,7 +353,7 @@ do_mark_pending(Kernel, MyNode, Node, Flags) -> receive {Kernel,{accept_pending,Ret}} -> ?trace("do_mark_pending(~p,~p,~p,~p) -> ~p~n", - [Kernel,Node,Address,Flags,Ret]), + [Kernel, MyNode, Node, Flags, Ret]), Ret end. @@ -674,7 +674,10 @@ send_name(#hs_data{socket = Socket, this_node = Node, ?ERL_DIST_VER_5; is_integer(Version), Version >= ?ERL_DIST_VER_6 -> - Creation = erts_internal:get_creation(), + Creation = case name_type(Flags) of + static -> erts_internal:get_creation(); + dynamic -> 0 + end, NameLen = byte_size(NameBin), ?trace("send_name: 'N' node=~p creation=~w\n", [Node, Creation]), @@ -781,7 +784,7 @@ recv_name_new(HSData, <<Creation:32>> = <<Cr3,Cr2,Cr1,Cr0>>, <<NameLen:16>> = <<NL1,NL0>>, {Name, _Residue} = lists:split(NameLen, Rest), - ?trace("recv_name: 'N' node=~p creation=~w\n", [Node, Creation]), + ?trace("recv_name: 'N' name=~p creation=~w\n", [Name, Creation]), case is_name_ok(Name, Flags) of true -> check_allowed(HSData, Name), @@ -1135,7 +1138,7 @@ send_status(#hs_data{socket = Socket, other_creation = Creation, f_send = FSend}, named) -> - ?debug({dist_util,self(),send_status, Node, Stat}), + ?debug({dist_util, self(), send_status, Node}), NameBin = atom_to_binary(Node, utf8), NameLen = byte_size(NameBin), case FSend(Socket, [$s, "named:", diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl index 0c98f62cdf..b44144d88a 100644 --- a/lib/kernel/src/gen_tcp.erl +++ b/lib/kernel/src/gen_tcp.erl @@ -130,7 +130,7 @@ {netns, file:filename_all()} | {bind_to_device, binary()} | option(). --type socket() :: port(). +-type socket() :: inet:socket(). -export_type([option/0, option_name/0, connect_option/0, listen_option/0, socket/0, pktoptions_value/0]). diff --git a/lib/kernel/src/gen_tcp_socket.erl b/lib/kernel/src/gen_tcp_socket.erl index fe58cc0cba..68733ce8e6 100644 --- a/lib/kernel/src/gen_tcp_socket.erl +++ b/lib/kernel/src/gen_tcp_socket.erl @@ -291,10 +291,10 @@ send(?module_socket(Server, Socket), Data) -> Result = socket_send(Socket, Data, SendTimeout), send_result(Server, Meta, Result) end; - {ok, _Meta} -> + {ok, _BadMeta} -> exit(badarg); {error, _} = Error -> - ?badarg_exit(Error) + Error end. %% send_result(Server, Meta, Result) -> @@ -1227,9 +1227,7 @@ handle_event( {call, From}, {recv, Length, Timeout}, State, {P, D}) -> case State of 'connected' -> - handle_recv( - P, recv_start(D, From, Length), - [{{timeout, recv}, Timeout, recv}]); + handle_recv_start(P, D, From, Length, Timeout); #recv{} -> %% Receive in progress {keep_state_and_data, @@ -1352,6 +1350,32 @@ handle_connected(P, D, ActionsR) -> handle_recv(P, recv_start(D), ActionsR) end. +handle_recv_start( + P, #{packet := Packet, buffer := Buffer} = D, From, Length, Timeout) + when Packet =:= raw, 0 < Length; + Packet =:= 0, 0 < Length -> + Size = iolist_size(Buffer), + if + Length =< Size -> + {Data, NewBuffer} = + split_binary(condense_buffer(Buffer), Length), + handle_recv_deliver( + P, + D#{recv_length => Length, % Redundant + recv_from => From, + buffer := NewBuffer}, + [], Data); + true -> + N = Length - Size, + handle_recv( + P, D#{recv_length => N, recv_from => From}, + [{{timeout, recv}, Timeout, recv}]) + end; +handle_recv_start(P, D, From, _Length, Timeout) -> + handle_recv( + P, D#{recv_length => 0, recv_from => From}, + [{{timeout, recv}, Timeout, recv}]). + handle_recv(P, #{packet := Packet, recv_length := Length} = D, ActionsR) -> if 0 < Length -> @@ -1661,16 +1685,6 @@ cleanup_recv_reply( end}. %% Initialize packet recv state -recv_start(#{packet := Packet} = D, From, Length) -> - %% - D#{recv_length => - case Packet of - raw -> Length; - 0 -> Length; - _ -> 0 - end, - recv_from => From}. - recv_start(D) -> D#{recv_length => 0}. @@ -1699,6 +1713,10 @@ recv_data_deliver( [{reply, From, {ok, DeliverData}}, {{timeout, recv}, cancel} | ActionsR]}; + #{active := false} -> + D_1 = D#{buffer := unrecv_buffer(Data, maps:get(buffer, D))}, + {recv_stop(next_packet(D_1, Packet, Data)), + ActionsR}; #{active := Active} -> ModuleSocket = module_socket(P), Owner ! @@ -1767,6 +1785,16 @@ catbin(Bin, <<>>) when is_binary(Bin) -> Bin; catbin(Bin1, Bin2) when is_binary(Bin1), is_binary(Bin2) -> <<Bin1/binary, Bin2/binary>>. +unrecv_buffer(Data, Buffer) -> + case Buffer of + <<>> -> + Data; + _ when is_binary(Buffer) -> + [Data, Buffer]; + _ -> + [Data | Buffer] + end. + condense_buffer([Bin]) when is_binary(Bin) -> Bin; condense_buffer(Buffer) -> iolist_to_binary(reverse_improper(Buffer, [])). @@ -1993,7 +2021,7 @@ getstat_avg(SumTag, D, C, CntTag) -> socket_info_counters(Socket) -> #{counters := Counters} = socket:info(Socket), - maps:from_list(Counters). + Counters. receive_counter_wrap(Socket, D, Wrapped) -> receive diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 8b944a1049..6ae5351652 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -128,7 +128,8 @@ 'etimedout' | 'ewouldblock' | 'exbadport' | 'exbadseq' | file:posix(). --type socket() :: port(). +-type module_socket() :: {'$inet', Handler :: module(), Handle :: term()}. +-type socket() :: port() | module_socket(). -type socket_setopt() :: gen_sctp:option() | gen_tcp:option() | gen_udp:option(). @@ -1433,7 +1434,7 @@ gethostbyaddr_tm_native(Addr, Timer, Opts) -> Family :: address_family(), Type :: socket_type(), Module :: atom()) -> - {'ok', socket()} | {'error', posix()}. + {'ok', port()} | {'error', posix()}. open(FdO, Addr, Port, Opts, Protocol, Family, Type, Module) when is_integer(FdO), FdO < 0; diff --git a/lib/kernel/src/inet6_udp.erl b/lib/kernel/src/inet6_udp.erl index 7ebdb3d301..b8c33e91ec 100644 --- a/lib/kernel/src/inet6_udp.erl +++ b/lib/kernel/src/inet6_udp.erl @@ -44,10 +44,10 @@ getaddr(Address, Timer) -> inet:getaddr(Address, ?FAMILY, Timer). %% inet_udp special this side addresses translate_ip(IP) -> inet:translate_ip(IP, ?FAMILY). --spec open(_) -> {ok, inet:socket()} | {error, atom()}. +-spec open(_) -> {ok, port()} | {error, atom()}. open(Port) -> open(Port, []). --spec open(_, _) -> {ok, inet:socket()} | {error, atom()}. +-spec open(_, _) -> {ok, port()} | {error, atom()}. open(Port, Opts) -> case inet:udp_options( [{port,Port} | Opts], @@ -91,7 +91,7 @@ recv(S, Len) -> recv(S, Len, Time) -> prim_inet:recvfrom(S, Len, Time). --spec close(inet:socket()) -> ok. +-spec close(port()) -> ok. close(S) -> inet:udp_close(S). diff --git a/lib/kernel/src/inet_udp.erl b/lib/kernel/src/inet_udp.erl index ace095f30c..00ebd4bb1d 100644 --- a/lib/kernel/src/inet_udp.erl +++ b/lib/kernel/src/inet_udp.erl @@ -45,10 +45,10 @@ getaddr(Address, Timer) -> inet:getaddr(Address, ?FAMILY, Timer). %% inet_udp special this side addresses translate_ip(IP) -> inet:translate_ip(IP, ?FAMILY). --spec open(_) -> {ok, inet:socket()} | {error, atom()}. +-spec open(_) -> {ok, port()} | {error, atom()}. open(Port) -> open(Port, []). --spec open(_, _) -> {ok, inet:socket()} | {error, atom()}. +-spec open(_, _) -> {ok, port()} | {error, atom()}. open(Port, Opts) -> case inet:udp_options( [{port,Port}, {recbuf, ?RECBUF} | Opts], @@ -92,7 +92,7 @@ recv(S, Len) -> recv(S, Len, Time) -> prim_inet:recvfrom(S, Len, Time). --spec close(inet:socket()) -> ok. +-spec close(port()) -> ok. close(S) -> inet:udp_close(S). diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index 48c77c49a9..e9f6049d5f 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -115,6 +115,7 @@ raw_file_io_list, raw_file_io_raw, seq_trace, + socket, standard_error, wrap_log_reader]}, {registered, [application_controller, @@ -154,6 +155,6 @@ {shell_docs_ansi,auto} ]}, {mod, {kernel, []}}, - {runtime_dependencies, ["erts-@OTP-15251@", "stdlib-@OTP-15251@", "sasl-3.0"]} + {runtime_dependencies, ["erts-11.0", "stdlib-3.13", "sasl-3.0"]} ] }. diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src index f42dd8ca6e..09a55d6f0a 100644 --- a/lib/kernel/src/kernel.appup.src +++ b/lib/kernel/src/kernel.appup.src @@ -21,6 +21,7 @@ %% versions from the following OTP releases: %% - OTP 21 %% - OTP 22 +%% - OTP 23 %% %% We also allow upgrade from, and downgrade to all %% versions that have branched off from the above @@ -44,7 +45,8 @@ {<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^6\\.5$">>,[restart_new_emulator]}, {<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, - {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], [{<<"^6\\.0$">>,[restart_new_emulator]}, {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, @@ -62,4 +64,5 @@ {<<"^6\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^6\\.5$">>,[restart_new_emulator]}, {<<"^6\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, - {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. + {<<"^6\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.5\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. diff --git a/lib/kernel/src/net.erl b/lib/kernel/src/net.erl index aceb903bb6..905114ab7c 100644 --- a/lib/kernel/src/net.erl +++ b/lib/kernel/src/net.erl @@ -152,7 +152,6 @@ relay(X) -> slave:relay(X). info() -> prim_net:info(). -else. --dialyzer({nowarn_function, info/0}). info() -> erlang:error(notsup). -endif. @@ -164,7 +163,6 @@ info() -> command(Cmd) -> prim_net:command(Cmd). -else. --dialyzer({nowarn_function, command/1}). command(_Cmd) -> erlang:error(notsup). -endif. @@ -191,7 +189,6 @@ command(_Cmd) -> gethostname() -> prim_net:gethostname(). -else. --dialyzer({nowarn_function, gethostname/0}). gethostname() -> erlang:error(notsup). -endif. @@ -208,6 +205,8 @@ gethostname() -> Info :: name_info(), Reason :: term(). +-dialyzer({no_return, getnameinfo/1}). + getnameinfo(SockAddr) -> getnameinfo(SockAddr, undefined). @@ -226,25 +225,18 @@ getnameinfo(SockAddr) -> -endif. -ifdef(USE_ESOCK). -getnameinfo(SockAddr, [] = _Flags) -> - getnameinfo(SockAddr, undefined); -getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags) - when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso - (is_list(Flags) orelse (Flags =:= undefined)) -> - prim_net:getnameinfo(socket:ensure_sockaddr(SockAddr), Flags); -getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags) - when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> +getnameinfo(SockAddr, Flags) + when is_map(SockAddr), is_list(Flags); + is_map(SockAddr), Flags =:= undefined -> prim_net:getnameinfo(SockAddr, Flags). + -else. --dialyzer({nowarn_function, getnameinfo/2}). -getnameinfo(SockAddr, [] = _Flags) -> - getnameinfo(SockAddr, undefined); -getnameinfo(#{family := Fam, addr := _Addr} = _SockAddr, Flags) - when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso - (is_list(Flags) orelse (Flags =:= undefined)) -> - erlang:error(notsup); -getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags) - when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> + +-dialyzer({no_return, getnameinfo/2}). + +getnameinfo(SockAddr, Flags) + when is_map(SockAddr), is_list(Flags); + is_map(SockAddr), Flags =:= undefined -> erlang:error(notsup). -endif. @@ -260,6 +252,8 @@ getnameinfo(#{family := Fam, path := _Path} = _SockAddr, Flags) Info :: [address_info()], Reason :: term(). +-dialyzer({no_return, getaddrinfo/1}). + getaddrinfo(Host) when is_list(Host) -> getaddrinfo(Host, undefined). @@ -285,7 +279,6 @@ getaddrinfo(Host, Service) (not ((Service =:= undefined) andalso (Host =:= undefined))) -> prim_net:getaddrinfo(Host, Service). -else. --dialyzer({nowarn_function, getaddrinfo/2}). getaddrinfo(Host, Service) when (is_list(Host) orelse (Host =:= undefined)) andalso (is_list(Service) orelse (Service =:= undefined)) andalso @@ -332,7 +325,6 @@ getifaddrs(Filter) when is_function(Filter, 1) -> getifaddrs(Namespace) when is_list(Namespace) -> prim_net:getifaddrs(#{netns => Namespace}). -else. --dialyzer({nowarn_function, getifaddrs/1}). getifaddrs(Filter) when is_atom(Filter) orelse is_map(Filter) orelse is_function(Filter) -> @@ -348,6 +340,8 @@ getifaddrs(Namespace) when is_list(Namespace) -> IfAddrs :: [ifaddrs()], Reason :: term(). +-dialyzer({no_return, getifaddrs/2}). + getifaddrs(Filter, Namespace) when (is_atom(Filter) orelse is_map(Filter)) andalso is_list(Namespace) -> do_getifaddrs(getifaddrs_filter_map(Filter), @@ -356,6 +350,8 @@ getifaddrs(Filter, Namespace) when is_function(Filter, 1) andalso is_list(Namespace) -> do_getifaddrs(Filter, fun() -> getifaddrs(Namespace) end). +-dialyzer({no_return, do_getifaddrs/2}). + do_getifaddrs(Filter, GetIfAddrs) -> case GetIfAddrs() of {ok, IfAddrs0} when is_function(Filter) -> @@ -395,6 +391,8 @@ getifaddrs_filter_map_inet6() -> getifaddrs_filter_map_packet() -> #{family => packet, flags => any}. +-compile({nowarn_unused_function, getifaddrs_filter/2}). + getifaddrs_filter(#{family := FFamily, flags := FFlags}, #{addr := #{family := Family}, flags := Flags} = _Entry) when (FFamily =:= default) andalso @@ -419,6 +417,8 @@ getifaddrs_filter(#{family := FFamily, flags := FFlags}, getifaddrs_filter(_Filter, _Entry) -> false. +-compile({nowarn_unused_function, getifaddrs_filter_flags/2}). + getifaddrs_filter_flags(any, _Flags) -> true; getifaddrs_filter_flags(FilterFlags, Flags) -> @@ -442,7 +442,6 @@ getifaddrs_filter_flags(FilterFlags, Flags) -> if_name2index(If) when is_list(If) -> prim_net:if_name2index(If). -else. --dialyzer({nowarn_function, if_name2index/1}). if_name2index(If) when is_list(If) -> erlang:error(notsup). -endif. @@ -465,7 +464,6 @@ if_name2index(If) when is_list(If) -> if_index2name(Idx) when is_integer(Idx) -> prim_net:if_index2name(Idx). -else. --dialyzer({nowarn_function, if_index2name/1}). if_index2name(Idx) when is_integer(Idx) -> erlang:error(notsup). -endif. @@ -488,7 +486,6 @@ if_index2name(Idx) when is_integer(Idx) -> if_names() -> prim_net:if_names(). -else. --dialyzer({nowarn_function, if_names/0}). if_names() -> erlang:error(notsup). -endif. diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index c93862e602..b0e6127520 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -1679,14 +1679,18 @@ epmd_module() -> %% dist_listen() -> whether the erlang distribution should listen for connections %% dist_listen() -> - case init:get_argument(dist_listen) of - {ok,[[DoListen]]} -> - list_to_atom(DoListen) =/= false; - _ -> - true + case persistent_term:get(net_kernel, undefined) of + dynamic_node_name -> + false; + _ -> + case init:get_argument(dist_listen) of + {ok,[[DoListen]]} -> + list_to_atom(DoListen) =/= false; + _ -> + true + end end. - %% %% Start all protocols %% @@ -1761,8 +1765,12 @@ wrap_creation(Cr) -> start_protos_listen(Node, Ps, CleanHalt) -> - {Name, "@"++Host} = split_node(Node), - start_protos_listen(list_to_atom(Name), Host, Node, Ps, [], CleanHalt). + case split_node(Node) of + {"undefined", _} -> + start_protos_no_listen(Node, Ps, [], CleanHalt); + {Name, "@"++Host} -> + start_protos_listen(list_to_atom(Name), Host, Node, Ps, [], CleanHalt) + end. start_protos_listen(Name, Host, Node, [Proto | Ps], Ls, CleanHalt) -> Mod = list_to_atom(Proto ++ "_dist"), try try Mod:listen(Name,Host) diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl new file mode 100644 index 0000000000..9533066c6b --- /dev/null +++ b/lib/kernel/src/socket.erl @@ -0,0 +1,2570 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket). + +-compile({no_auto_import,[error/1]}). + +%% Administrative and "global" utility functions +-export([ + number_of/0, + which_sockets/0, which_sockets/1, + + debug/1, socket_debug/1, + info/0, info/1, + supports/0, supports/1, supports/2, + is_supported/1, is_supported/2, is_supported/3 + ]). + +-export([ + open/1, open/2, open/3, open/4, + bind/2, bind/3, + connect/1, connect/2, connect/3, + listen/1, listen/2, + accept/1, accept/2, + + send/2, send/3, send/4, + sendto/3, sendto/4, sendto/5, + sendmsg/2, sendmsg/3, sendmsg/4, + + recv/1, recv/2, recv/3, recv/4, + recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, + recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5, + + close/1, + shutdown/2, + + setopt/4, + getopt/3, + + sockname/1, + peername/1, + + cancel/2 + ]). + +-export_type([ + socket/0, + + select_tag/0, + select_ref/0, + select_info/0, + + socket_counters/0, + socket_info/0, + + domain/0, + type/0, + protocol/0, + + port_number/0, + ip4_address/0, + ip6_address/0, + sockaddr/0, + sockaddr_in4/0, + sockaddr_in6/0, + sockaddr_un/0, + sockaddr_ll/0, + + send_flags/0, + send_flag/0, + + recv_flags/0, + recv_flag/0, + + shutdown_how/0, + + sockopt_level/0, + otp_socket_option/0, + socket_option/0, + ip_socket_option/0, + ipv6_socket_option/0, + tcp_socket_option/0, + udp_socket_option/0, + sctp_socket_option/0, + raw_socket_option/0, + + timeval/0, + ip_tos/0, + ip_mreq/0, + ip_mreq_source/0, + ip_pmtudisc/0, + ip_msfilter_mode/0, + ip_msfilter/0, + ip_pktinfo/0, + ipv6_mreq/0, + ipv6_pmtudisc/0, + ipv6_pktinfo/0, + in6_flow_info/0, + in6_scope_id/0, + sctp_assoc_id/0, + sctp_sndrcvinfo/0, + sctp_event_subscribe/0, + sctp_assocparams/0, + sctp_initmsg/0, + sctp_rtoinfo/0, + + + msghdr_flag/0, + msghdr_flags/0, + msghdr/0, + cmsghdr_level/0, + cmsghdr_type/0, + %% cmsghdr_data/0, + cmsghdr_recv/0, cmsghdr_send/0, + + ee_origin/0, + icmp_dest_unreach/0, + icmpv6_dest_unreach/0, + extended_err/0, + + uint8/0, + uint16/0, + uint20/0, + uint32/0, + int32/0 + ]). + +%% Also in prim_socket +-define(REGISTRY, socket_registry). + + +-type socket_counters() :: #{read_byte := non_neg_integer(), + read_fails := non_neg_integer(), + read_pkg := non_neg_integer(), + read_pkg_max := non_neg_integer(), + read_tries := non_neg_integer(), + read_waits := non_neg_integer(), + write_byte := non_neg_integer(), + write_fails := non_neg_integer(), + write_pkg := non_neg_integer(), + write_pkg_max := non_neg_integer(), + write_tries := non_neg_integer(), + write_waits := non_neg_integer(), + acc_success := non_neg_integer(), + acc_fails := non_neg_integer(), + acc_tries := non_neg_integer(), + acc_waits := non_neg_integer()}. +-type socket_info() :: #{domain := domain(), + type := type(), + protocol := protocol(), + ctrl := pid(), + ctype := normal | fromfd | {fromfd, integer()}, + counters := socket_counters(), + num_readers := non_neg_integer(), + num_writers := non_neg_integer(), + num_acceptors := non_neg_integer(), + writable := boolean(), + readable := boolean()}. + +-type uint8() :: 0..16#FF. +-type uint16() :: 0..16#FFFF. +-type uint20() :: 0..16#FFFFF. +-type uint32() :: 0..16#FFFFFFFF. +-type int32() :: -2147483648..2147483647. + + +%% We support only a subset of all domains. +-type domain() :: local | inet | inet6. + +%% We support only a subset of all types. +%% RDM - Reliably Delivered Messages +-type type() :: stream | dgram | raw | rdm | seqpacket. + +%% We support only a subset of all protocols: +%% Note that the '{raw, integer()}' construct is intended +%% to be used with type = raw. +%% Note also that only the "superuser" can create a raw socket. +-type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}. + +-type port_number() :: 0..65535. + +-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. + +-type in6_flow_info() :: uint20(). +-type in6_scope_id() :: uint32(). + +-type ip6_address() :: + {0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535}. + +-type timeval() :: #{sec := integer(), + usec := integer()}. + +-type ip_pktinfo() :: #{ + ifindex := non_neg_integer(), % Interface Index + spec_dst := ip4_address(), % Local Address + addr := ip4_address() % Header Destination address + }. + +%% If the integer value is used, its up to the caller to ensure its valid! +-type ip_tos() :: lowdelay | + throughput | + reliability | + mincost | + integer(). + +%% This type is used when requesting to become member of a multicast +%% group with a call to setopt. Example: +%% +%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr, +%% interface => any}). +%% +%% Its also used when removing from a multicast group. Example: +%% +%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr, +%% interface => any}). +%% + +-type ip_mreq() :: #{multiaddr := ip4_address(), + interface := any | ip4_address()}. +%% -type ip_mreqn() :: #{multiaddr := ip4_address(), +%% address := any | ip4_address(), +%% ifindex := integer()}. + +-type ip_mreq_source() :: #{multiaddr := ip4_address(), + interface := ip4_address(), + sourceaddr := ip4_address()}. + +-type ip_pmtudisc() :: want | dont | do | probe. + +%% multiaddr: Multicast group address +%% interface: Address of local interface +%% mode: Filter mode +%% slist: List of source addresses +-type ip_msfilter_mode() :: include | exclude. + +-type ip_msfilter() :: #{multiaddr := ip4_address(), + interface := ip4_address(), + mode := ip_msfilter_mode(), + slist := [ip4_address()]}. + +-type ipv6_mreq() :: #{multiaddr := ip6_address(), + interface := non_neg_integer()}. + +-type ipv6_pmtudisc() :: ip_pmtudisc(). + +-type ipv6_pktinfo() :: #{ + addr := ip6_address(), + ifindex := integer() + }. + + +-type sctp_assoc_id() :: int32(). +-type sctp_sndrcvinfo() :: #{ + stream := uint16(), + ssn := uint16(), + flags := uint16(), + ppid := uint16(), + context := uint16(), + timetolive := uint16(), + tsn := uint16(), + cumtsn := uint16(), + assoc_id := sctp_assoc_id() + }. + +-type sctp_event_subscribe() :: #{data_in := boolean(), + association := boolean(), + address := boolean(), + send_failure := boolean(), + peer_error := boolean(), + shutdown := boolean(), + partial_delivery := boolean(), + adaptation_layer := boolean(), + authentication := boolean(), + sender_dry := boolean()}. + +-type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(), + max_rxt := uint16(), + num_peer_dests := uint16(), + peer_rwnd := uint32(), + local_rwnd := uint32(), + cookie_life := uint32()}. + +-type sctp_initmsg() :: #{num_outstreams := uint16(), + max_instreams := uint16(), + max_attempts := uint16(), + max_init_timeo := uint16() + }. + +-type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(), + initial := uint32(), + max := uint32(), + min := uint32()}. + +-type sockaddr_un() :: #{family := local, + path := binary() | string()}. +-type sockaddr_in4() :: #{family := inet, + port := port_number(), + %% The 'broadcast' here is the "limited broadcast" + addr := any | broadcast | loopback | ip4_address()}. +-type sockaddr_in6() :: #{family := inet6, + port := port_number(), + addr := any | loopback | ip6_address(), + flowinfo := in6_flow_info(), + scope_id := in6_scope_id()}. +-type sockaddr_ll() :: #{family := packet, + protocol := non_neg_integer(), + ifindex := integer(), + pkttype := packet_type(), + hatype := non_neg_integer(), + addr := binary()}. +-type packet_type() :: host | broadcast | multicast | otherhost | + outgoing | loopback | user | kernel | fastroute | + non_neg_integer(). +-type sockaddr() :: sockaddr_in4() | + sockaddr_in6() | + sockaddr_un() | + sockaddr_ll(). + +%% otp - This option is internal to our (OTP) implementation. +%% socket - The socket layer (SOL_SOCKET). +%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?). +%% ipv6 - The IPv6 layer (SOL_IPV6). +%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP). +%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP). +%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP). +%% Int - Raw level, sent down and used "as is". +%% Its up to the caller to make sure this is correct! +-type sockopt_level() :: otp | + socket | + ip | ipv6 | tcp | udp | sctp | + non_neg_integer(). + +%% There are some options that are 'read-only'. +%% Should those be included here or in a special list? +%% Should we just document it and leave it to the user? +%% Or catch it in the encode functions? +%% A setopt for a readonly option leads to einval? +%% Do we really need a sndbuf? + +-type otp_socket_option() :: debug | + iow | + controlling_process | + rcvbuf | % sndbuf | + rcvctrlbuf | + sndctrlbuf | + meta | + fd. +%% Shall we have special treatment of linger?? +%% read-only options: +%% domain | protocol | type. +%% FreeBSD (only?): acceptfilter +-type socket_option() :: acceptconn | + acceptfilter | + bindtodevice | + broadcast | + busy_poll | + debug | + domain | + dontroute | + error | + keepalive | + linger | + mark | + oobinline | + passcred | + peek_off | + peercred | + priority | + protocol | + rcvbuf | + rcvbufforce | + rcvlowat | + rcvtimeo | + reuseaddr | + reuseport | + rxq_ovfl | + setfib | + sndbuf | + sndbufforce | + sndlowat | + sndtimeo | + timestamp | + type. + +%% Read-only options: +%% mtu +%% +%% Options only valid on FreeBSD?: +%% dontfrag +%% Options only valid for RAW sockets: +%% nodefrag (linux only?) +-type ip_socket_option() :: add_membership | + add_source_membership | + block_source | + dontfrag | + drop_membership | + drop_source_membership | + freebind | + hdrincl | + minttl | + msfilter | + mtu | + mtu_discover | + multicast_all | + multicast_if | + multicast_loop | + multicast_ttl | + nodefrag | + options | + pktinfo | + recverr | + recvif | + recvdstaddr | + recvopts | + recvorigdstaddr | + recvtos | + recvttl | + retopts | + router_alert | + sndsrcaddr | + tos | + transparent | + ttl | + unblock_source. +-type ipv6_socket_option() :: + addrform | + add_membership | + authhdr | + auth_level | + checksum | + drop_membership | + dstopts | + esp_trans_level | + esp_network_level | + faith | + flowinfo | + hopopts | + ipcomp_level | + join_group | + leave_group | + mtu | + mtu_discover | + multicast_hops | + multicast_if | + multicast_loop | + portrange | + pktoptions | + recverr | + recvhoplimit | hoplimit | + recvpktinfo | pktinfo | + recvtclass | + router_alert | + rthdr | + tclass | + unicast_hops | + use_min_mtu | + v6only. + +-type tcp_socket_option() :: congestion | + cork | + info | + keepcnt | + keepidle | + keepintvl | + maxseg | + md5sig | + nodelay | + noopt | + nopush | + syncnt | + user_timeout. + +-type udp_socket_option() :: cork. + +-type sctp_socket_option() :: + adaption_layer | + associnfo | + auth_active_key | + auth_asconf | + auth_chunk | + auth_key | + auth_delete_key | + autoclose | + context | + default_send_params | + delayed_ack_time | + disable_fragments | + hmac_ident | + events | + explicit_eor | + fragment_interleave | + get_peer_addr_info | + initmsg | + i_want_mapped_v4_addr | + local_auth_chunks | + maxseg | + maxburst | + nodelay | + partial_delivery_point | + peer_addr_params | + peer_auth_chunks | + primary_addr | + reset_streams | + rtoinfo | + set_peer_primary_addr | + status | + use_ext_recvinfo. + +-type raw_socket_option() :: filter. + +%% -type plain_socket_option() :: integer(). +%% -type sockopt() :: otp_socket_option() | +%% socket_option() | +%% ip_socket_option() | +%% ipv6_socket_option() | +%% tcp_socket_option() | +%% udp_socket_option() | +%% sctp_socket_option() | +%% raw_socket_option() | +%% plain_socket_option(). + +%% The names of these macros match the names of corresponding +%%C functions in the NIF code, so a search will match both +%% +-define(socket_tag, '$socket'). +%% +%% Our socket abstract data type +-define(mk_socket(Ref), {?socket_tag, (Ref)}). +%% +%% Messages sent from the nif-code to erlang processes: +-define(mk_socket_msg(Socket, Tag, Info), {?socket_tag, (Socket), (Tag), (Info)}). + +-opaque socket() :: ?mk_socket(reference()). + +-type send_flags() :: [send_flag()]. +-type send_flag() :: confirm | + dontroute | + eor | + more | + nosignal | + oob. + +%% Note that not all of these flags are useful for every recv function! +%% +-type recv_flags() :: [recv_flag()]. +-type recv_flag() :: cmsg_cloexec | + errqueue | + oob | + peek | + trunc. + +-type shutdown_how() :: read | write | read_write. + +-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc. +-type msghdr_flags() :: [msghdr_flag()]. +-type msghdr() :: #{ + %% *Optional* target address + %% Used on an unconnected socket to specify the + %% target address for a datagram. + addr := sockaddr(), + + iov := [binary()], + + %% The maximum size of the control buffer is platform + %% specific. It is the users responsibility to ensure + %% that its not exceeded. + ctrl := [cmsghdr_recv()] | [cmsghdr_send()], + + %% Only valid with recvmsg + flags := msghdr_flags() + }. +%% We are able to (completely) decode *some* control message headers. +%% Even if we are able to decode both level and type, we may not be +%% able to decode the data, in which case it will be a binary. + +-type cmsghdr_level() :: socket | ip | ipv6 | integer(). +-type cmsghdr_type() :: credentials | + hoplevel | + origdstaddr | + pktinfo | + recvtos | + rights | + timestamp | + tos | + ttl | + integer(). +-type cmsghdr_recv() :: + #{level := socket, type := timestamp, data := timeval()} | + #{level := socket, type := rights, data := binary()} | + #{level := socket, type := credentials, data := binary()} | + #{level := socket, type := integer(), data := binary()} | + #{level := ip, type := tos, data := ip_tos()} | + #{level := ip, type := recvtos, data := ip_tos()} | + #{level := ip, type := ttl, data := integer()} | + #{level := ip, type := recvttl, data := integer()} | + #{level := ip, type := pktinfo, data := ip_pktinfo()} | + #{level := ip, type := origdstaddr, data := sockaddr_in4()} | + #{level := ip, type := recverr, data := extended_err() | binary()} | + #{level := ip, type := integer(), data := binary()} | + #{level := ipv6, type := hoplevel, data := integer()} | + #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} | + #{level := ipv6, type := recverr, data := extended_err() | binary()} | + #{level := ipv6, type := tclass, data := integer()} | + #{level := ipv6, type := integer(), data := binary()} | + #{level := integer(), type := integer(), data := binary()}. +-type cmsghdr_send() :: + #{level := socket, type := timestamp, data := binary()} | + #{level := socket, type := rights, data := binary()} | + #{level := socket, type := credentials, data := binary()} | + #{level := socket, type := integer(), data := binary()} | + #{level := ip, type := tos, data := ip_tos() | binary()} | + #{level := ip, type := ttl, data := integer() | binary()} | + #{level := ip, type := integer(), data := binary()} | + #{level := ipv6, type := tclass, data := integer()} | + #{level := ipv6, type := integer(), data := binary()} | + #{level := udp, type := integer(), data := binary()} | + #{level := integer(), type := integer(), data := binary()}. + +-type ee_origin() :: none | local | icmp | icmp6 | uint8(). +-type icmp_dest_unreach() :: net_unreach | host_unreach | port_unreach | frag_needed | + net_unknown | host_unknown | uint8(). +-type icmpv6_dest_unreach() :: noroute | adm_prohibited | not_neighbour | addr_unreach | + port_unreach | policy_fail | reject_route | uint8(). +-type extended_err() :: + #{error := term(), + origin := icmp, + type := dest_unreach, + code := icmp_dest_unreach(), + info := uint32(), + data := uint32(), + offender := undefined | sockaddr()} | + #{error := term(), + origin := icmp, + type := time_exceeded | uint8(), + code := uint8(), + info := uint32(), + data := uint32(), + offender := undefined | sockaddr()} | + #{error := term(), + origin := icmp6, + type := dest_unreach, + code := icmpv6_dest_unreach(), + info := uint32(), + data := uint32(), + offender := undefined | sockaddr()} | + #{error := term(), + origin := icmp6, + type := pkt_toobig | time_exceeded | uint8(), + code := uint8(), + info := uint32(), + data := uint32(), + offender := undefined | sockaddr()} | + #{error := term(), + origin := ee_origin(), + type := uint8(), + code := uint8(), + info := uint32(), + data := uint32(), + offender := undefined | sockaddr()}. + +-type errcode() :: + inet:posix() | % closed | timeout | not_owner | + exalloc | exmonitor | exselect | exself. + +%% =========================================================================== +%% +%% Interface term formats +%% + +-opaque select_tag() :: atom(). +-opaque select_ref() :: reference(). + +-type select_info() :: {select_info, select_tag(), select_ref()}. + +-define(SELECT_INFO(T, R), {select_info, T, R}). +-define(SELECT(T, R), {select, ?SELECT_INFO(T, R)}). + + +%% =========================================================================== +%% +%% Defaults +%% + +-define(ESOCK_LISTEN_BACKLOG_DEFAULT, 5). + +-define(ESOCK_ACCEPT_TIMEOUT_DEFAULT, infinity). + +-define(ESOCK_SEND_FLAGS_DEFAULT, []). +-define(ESOCK_SEND_TIMEOUT_DEFAULT, infinity). +-define(ESOCK_SENDTO_FLAGS_DEFAULT, []). +-define(ESOCK_SENDTO_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT). +-define(ESOCK_SENDMSG_FLAGS_DEFAULT, []). +-define(ESOCK_SENDMSG_TIMEOUT_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT). + +-define(ESOCK_RECV_FLAGS_DEFAULT, []). +-define(ESOCK_RECV_TIMEOUT_DEFAULT, infinity). + + +%% =========================================================================== +%% +%% Administrative and utility API +%% +%% =========================================================================== + +%% *** number_of *** +%% +%% Interface function to the socket registry +%% returns the number of existing (and "alive") sockets. +%% +-spec number_of() -> non_neg_integer(). + +number_of() -> + ?REGISTRY:number_of(). + + +%% *** which_sockets/0,1 *** +%% +%% Interface function to the socket registry +%% Returns a list of all the sockets, accoring to the filter rule. +%% +-spec which_sockets() -> [socket()]. + +which_sockets() -> + ?REGISTRY:which_sockets(fun(_) -> true end). + +-spec which_sockets(FilterRule) -> [socket()] when + FilterRule :: inet | inet6 | + stream | dgram | seqpacket | + sctp | tcp | udp | + pid() | + fun((socket_info()) -> boolean()). + +which_sockets(Domain) + when ((Domain =:= inet) orelse (Domain =:= inet6)) -> + ?REGISTRY:which_sockets(fun(#{domain := D}) when (D =:= Domain) -> true; + (_) -> false end); +which_sockets(Type) + when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) -> + ?REGISTRY:which_sockets(fun(#{type := T}) when (T =:= Type) -> true; + (_) -> false end); +which_sockets(Proto) + when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) -> + ?REGISTRY:which_sockets(fun(#{protocol := P}) when (P =:= Proto) -> true; + (_) -> false end); +which_sockets(CTRL) + when is_pid(CTRL) -> + ?REGISTRY:which_sockets(fun(#{ctrl := C}) when (C =:= CTRL) -> true; + (_) -> false end); +which_sockets(Filter) when is_function(Filter, 1) -> + ?REGISTRY:which_sockets(Filter). + + +%% =========================================================================== +%% +%% Debug features +%% +%% =========================================================================== + + +-spec info() -> map(). +%% +info() -> + prim_socket:info(). + + +-spec debug(D :: boolean()) -> ok. +%% +debug(D) when is_boolean(D) -> + prim_socket:debug(D). + + +-spec socket_debug(D :: boolean()) -> ok. +%% +socket_debug(D) when is_boolean(D) -> + prim_socket:socket_debug(D). + + +%% =========================================================================== +%% +%% info - Get miscellaneous information about a socket. +%% +%% Generates a list of various info about the socket, such as counter values. +%% +%% Do *not* call this function often. +%% +%% =========================================================================== + +-spec info(Socket) -> socket_info() when + Socket :: socket(). +%% +info(?mk_socket(SockRef)) when is_reference(SockRef) -> + prim_socket:info(SockRef); +info(Socket) -> + erlang:error(badarg, [Socket]). + + +%% =========================================================================== +%% +%% supports - get information about what the platform "supports". +%% +%% Generates a list of various info about what the plaform can support. +%% The most obvious case is 'options'. +%% +%% Each item in a 'supports'-list will appear only *one* time. +%% +%% =========================================================================== + +-spec supports() -> [{Key1 :: term(), + boolean() | [{Key2 :: term(), + boolean() | [{Key3 :: term(), + boolean()}]}]}]. +supports() -> + [{Key1, supports(Key1)} + || Key1 <- [options, send_flags, recv_flags]] + ++ prim_socket:supports(). + +-spec supports(Key1 :: term()) -> + [{Key2 :: term(), + boolean() | [{Key3 :: term(), + boolean()}]}]. +%% +supports(options) -> + [{Level, supports(options, Level)} + || Level <- [socket, ip, ipv6, tcp, udp, sctp]]; +supports(Key) -> + prim_socket:supports(Key). + +-spec supports(Key1 :: term(), Key2 :: term()) -> + [{Key3 :: term(), + boolean()}]. +%% +supports(Key1, Key2) -> + prim_socket:supports(Key1, Key2). + + +-spec is_supported(Key1 :: term()) -> + boolean(). +is_supported(Key1) -> + get_is_supported(Key1, supports()). +%% +-spec is_supported(Key1 :: term(), Key2 :: term()) -> + boolean(). +is_supported(Key1, Key2) -> + get_is_supported(Key2, supports(Key1)). +%% +-spec is_supported(Key1 :: term(), Key2 :: term(), Key3 :: term()) -> + boolean(). +is_supported(Key1, Key2, Key3) -> + get_is_supported(Key3, supports(Key1, Key2)). + + +get_is_supported(Key, Supported) -> + case lists:keyfind(Key, 1, Supported) of + false -> + false; + {_, Value} -> + if + is_boolean(Value) -> + Value; + is_list(Value) -> + false + end + end. + + +%% =========================================================================== +%% +%% The proper socket API +%% +%% =========================================================================== + +%% =========================================================================== +%% +%% <KOLLA> +%% +%% The nif sets up a monitor to this process, and if it dies the socket +%% is closed. It is also used if someone wants to monitor the socket. +%% +%% We may therefor need monitor function(s): +%% +%% socket:monitor(Socket) +%% socket:demonitor(Socket) +%% +%% </KOLLA> +%% + +%% =========================================================================== +%% +%% open - create an endpoint for communication +%% + +-spec open(FD) -> {ok, Socket} | {error, Reason} when + FD :: integer(), + Socket :: socket(), + Reason :: errcode(). + +open(FD) when is_integer(FD) -> + open(FD, #{}); +open(FD) -> + erlang:error(badarg, [FD]). + +-spec open(FD, Opts) -> {ok, Socket} | {error, Reason} when + FD :: integer(), + Opts :: + #{domain => domain(), + type => type(), + protocol => protocol(), + dup => boolean()}, + Socket :: socket(), + Reason :: errcode(); + + (Domain, Type) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Socket :: socket(), + Reason :: errcode(). + +open(FD, Opts) when is_integer(FD), is_map(Opts) -> + case prim_socket:open(FD, Opts) of + {ok, SockRef} -> + Socket = ?mk_socket(SockRef), + {ok, Socket}; + {error, _} = ERROR -> + ERROR + end; +open(Domain, Type) -> + open(Domain, Type, default). + +-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Protocol :: default | protocol(), + Socket :: socket(), + Reason :: errcode(). + +open(Domain, Type, Protocol) -> + open(Domain, Type, Protocol, #{}). + +-spec open(Domain, Type, Protocol, Opts) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Protocol :: default | protocol(), + Opts :: map(), + Socket :: socket(), + Reason :: errcode(). + +open(Domain, Type, Protocol, Opts) when is_map(Opts) -> + case prim_socket:open(Domain, Type, Protocol, Opts) of + {ok, SockRef} -> + Socket = ?mk_socket(SockRef), + {ok, Socket}; + {error, _} = ERROR -> + ERROR + end; +open(Domain, Type, Protocol, Opts) -> + erlang:error(badarg, [Domain, Type, Protocol, Opts]). + + +%% =========================================================================== +%% +%% bind - bind a name (an address) to a socket +%% +%% Note that the short (atom) addresses only work for some domains, +%% and that the nif will reject 'broadcast' for other domains than 'inet' +%% + +-spec bind(Socket, Addr) -> {ok, Port} | {error, Reason} when + Socket :: socket(), + Addr :: sockaddr() | any | broadcast | loopback, + Port :: port_number(), + Reason :: inet:posix() | closed. + +bind(?mk_socket(SockRef) = Socket, Addr) when is_reference(SockRef) -> + if + is_map(Addr) -> + prim_socket:bind(SockRef, Addr); + %% + Addr =:= any; + Addr =:= broadcast; + Addr =:= loopback -> + case prim_socket:getopt(SockRef, otp, domain) of + {ok, Domain} + when Domain =:= inet; + Domain =:= inet6 -> + prim_socket:bind( + SockRef, #{family => Domain, addr => Addr}); + {ok, _Domain} -> + {error, eafnosupport}; + {error, _} = ERROR -> + ERROR + end; + %% + true -> + erlang:error(badarg, [Socket, Addr]) + end; +bind(Socket, Addr) -> + erlang:error(badarg, [Socket, Addr]). + + +%% =========================================================================== +%% +%% bind - Add or remove a bind addresses on a socket +%% +%% Calling this function is only valid if the socket is: +%% type = seqpacket +%% protocol = sctp +%% +%% If the domain is inet, then all addresses *must* be IPv4. +%% If the domain is inet6, the addresses can be aither IPv4 or IPv6. +%% + +-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when + Socket :: socket(), + Addrs :: [sockaddr()], + Action :: add | remove, + Reason :: inet:posix() | closed. + +bind(?mk_socket(SockRef), Addrs, Action) + when is_reference(SockRef) + andalso is_list(Addrs) + andalso (Action =:= add + orelse Action =:= remove) -> + prim_socket:bind(SockRef, Addrs, Action); +bind(Socket, Addrs, Action) -> + erlang:error(badarg, [Socket, Addrs, Action]). + + +%% =========================================================================== +%% +%% connect - initiate a connection on a socket +%% + +-spec connect(Socket) -> ok | {error, Reason} when + Socket :: socket(), + Reason :: errcode() | closed. + +%% Finalize connect after connect(,, nowait) and received +%% select message - see connect_deadline/3 +%% +connect(?mk_socket(SockRef)) + when is_reference(SockRef) -> + prim_socket:connect(SockRef); +connect(Socket) -> + erlang:error(badarg, [Socket]). + + +-spec connect(Socket, SockAddr) -> ok | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: errcode() | closed. + +connect(Socket, SockAddr) -> + connect(Socket, SockAddr, infinity). + + +-spec connect(Socket, SockAddr, nowait) -> + ok | {select, SelectInfo} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, SockAddr, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Timeout :: timeout(), + Reason :: errcode() | closed | timeout. + +%% <KOLLA> +%% Is it possible to connect with family = local for the (dest) sockaddr? +%% </KOLLA> +connect(?mk_socket(SockRef) = Socket, SockAddr, Timeout) + when is_reference(SockRef) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, SockAddr, Timeout]); + nowait -> + connect_nowait(SockRef, SockAddr); + Deadline -> + connect_deadline(SockRef, SockAddr, Deadline) + end; +connect(Socket, SockAddr, Timeout) -> + erlang:error(badarg, [Socket, SockAddr, Timeout]). + +connect_nowait(SockRef, SockAddr) -> + case prim_socket:connect(SockRef, SockAddr) of + {select, Ref} -> + ?SELECT(connect, Ref); + Result -> + Result + end. + +connect_deadline(SockRef, SockAddr, Deadline) -> + case prim_socket:connect(SockRef, SockAddr) of + {select, Ref} -> + %% Connecting... + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(_Socket, select, Ref) -> + prim_socket:connect(SockRef); + ?mk_socket_msg(_Socket, abort, {Ref, Reason}) -> + {error, Reason} + after Timeout -> + cancel(SockRef, connect, Ref), + {error, timeout} + end; + Result -> + Result + end. + + +%% =========================================================================== +%% +%% listen - listen for connections on a socket +%% + +-spec listen(Socket) -> ok | {error, Reason} when + Socket :: socket(), + Reason :: inet:posix() | closed. + +listen(Socket) -> + listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT). + +-spec listen(Socket, Backlog) -> ok | {error, Reason} when + Socket :: socket(), + Backlog :: integer(), + Reason :: inet:posix() | closed. + +listen(?mk_socket(SockRef), Backlog) + when is_reference(SockRef), is_integer(Backlog) -> + prim_socket:listen(SockRef, Backlog); +listen(Socket, Backlog) -> + erlang:error(badarg, [Socket, Backlog]). + + +%% =========================================================================== +%% +%% accept, accept4 - accept a connection on a socket +%% + +-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Socket :: socket(), + Reason :: errcode() | closed. + +accept(Socket) -> + accept(Socket, ?ESOCK_ACCEPT_TIMEOUT_DEFAULT). + +-spec accept(LSocket, nowait) -> + {ok, Socket} | + {select, SelectInfo} | + {error, Reason} when + LSocket :: socket(), + Socket :: socket(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (LSocket, Timeout) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Timeout :: timeout(), + Socket :: socket(), + Reason :: errcode() | closed | timeout. + +accept(?mk_socket(LSockRef) = Socket, Timeout) + when is_reference(LSockRef) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, Timeout]); + nowait -> + accept_nowait(LSockRef); + Deadline -> + accept_deadline(LSockRef, Deadline) + end; +accept(Socket, Timeout) -> + erlang:error(badarg, [Socket, Timeout]). + +accept_nowait(LSockRef) -> + AccRef = make_ref(), + case prim_socket:accept(LSockRef, AccRef) of + select -> + ?SELECT(accept, AccRef); + Result -> + accept_result(LSockRef, AccRef, Result) + end. + +accept_deadline(LSockRef, Deadline) -> + AccRef = make_ref(), + case prim_socket:accept(LSockRef, AccRef) of + select -> + %% Each call is non-blocking, but even then it takes + %% *some* time, so just to be sure, recalculate before + %% the receive. + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(?mk_socket(LSockRef), select, AccRef) -> + accept_deadline(LSockRef, Deadline); + ?mk_socket_msg(_Socket, abort, {AccRef, Reason}) -> + {error, Reason} + after Timeout -> + cancel(LSockRef, accept, AccRef), + {error, timeout} + end; + Result -> + accept_result(LSockRef, AccRef, Result) + end. + +accept_result(LSockRef, AccRef, Result) -> + case Result of + {ok, SockRef} -> + Socket = ?mk_socket(SockRef), + {ok, Socket}; + {error, _} = ERROR -> + cancel(LSockRef, accept, AccRef), % Just to be on the safe side... + ERROR + end. + + +%% =========================================================================== +%% +%% send, sendto, sendmsg - send a message on a socket +%% + +-spec send(Socket, Data) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Reason :: term(). + +send(Socket, Data) -> + send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT). + +-spec send(Socket, Data, Flags) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}; + + (Socket, Data, Timeout :: nowait) -> + ok | + {ok, {binary(), SelectInfo}} | + {select, SelectInfo} | + {ok, {RestData, SelectInfo}} | + {error, Reason} when + Socket :: socket(), + Data :: iodata(), + RestData :: binary(), + SelectInfo :: select_info(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}; + + (Socket, Data, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Timeout :: timeout(), + Reason :: {errcode() | closed | timeout, + Remaining :: pos_integer()}. + +send(Socket, Data, Flags) when is_list(Flags) -> + send(Socket, Data, Flags, ?ESOCK_SEND_TIMEOUT_DEFAULT); +send(Socket, Data, Timeout) -> + send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout). + +-spec send(Socket, Data, Flags, nowait) -> ok | + {select, SelectInfo} | + {ok, {RestData, SelectInfo}} | + {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + RestData :: binary(), + SelectInfo :: select_info(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}; + + (Socket, Data, Flags, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + Timeout :: timeout(), + Reason :: {errcode() | closed | timeout, + Remaining :: pos_integer()}. + +send(Socket, Data, Flags, Timeout) when is_list(Data) -> + Bin = erlang:list_to_binary(Data), + send(Socket, Bin, Flags, Timeout); +send(?mk_socket(SockRef) = Socket, Data, Flags, Timeout) + when is_reference(SockRef), is_binary(Data), is_list(Flags) -> + To = undefined, + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, Data, Flags, Timeout]); + nowait -> + send_common_nowait(SockRef, Data, To, Flags, send); + Deadline -> + send_common_deadline(SockRef, Data, To, Flags, Deadline, send) + end; +send(Socket, Data, Flags, Timeout) -> + erlang:error(badarg, [Socket, Data, Flags, Timeout]). + +send_common_nowait(SockRef, Data, To, Flags, SendName) -> + SendRef = make_ref(), + case + case SendName of + send -> + prim_socket:send(SockRef, SendRef, Data, Flags); + sendto -> + prim_socket:sendto(SockRef, SendRef, Data, To, Flags) + end + of + {ok, Written} -> + %% We are partially done, but the user don't want to wait (here) + %% for completion + <<_:Written/binary, Rest/binary>> = Data, + {ok, {Rest, ?SELECT_INFO(SendName, SendRef)}}; + select -> + ?SELECT(SendName, SendRef); + Result -> + send_common_result(Data, Result) + end. + +send_common_deadline(SockRef, Data, To, Flags, Deadline, SendName) -> + SendRef = make_ref(), + case + case SendName of + send -> + prim_socket:send(SockRef, SendRef, Data, Flags); + sendto -> + prim_socket:sendto(SockRef, SendRef, Data, To, Flags) + end + of + {ok, Written} -> + %% We are partially done, wait for continuation + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(_Socket, select, SendRef) + when (Written > 0) -> + <<_:Written/binary, Rest/binary>> = Data, + send_common_deadline( + SockRef, Rest, To, Flags, Deadline, SendName); + ?mk_socket_msg(_Socket, select, SendRef) -> + send_common_deadline( + SockRef, Data, To, Flags, Deadline, SendName); + ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) -> + {error, {Reason, byte_size(Data)}} + after Timeout -> + _ = cancel(SockRef, SendName, SendRef), + {error, {timeout, byte_size(Data)}} + end; + select -> + %% Wait for continuation + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(_Socket, select, SendRef) -> + send_common_deadline( + SockRef, Data, To, Flags, Deadline, SendName); + ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) -> + {error, {Reason, byte_size(Data)}} + after Timeout -> + _ = cancel(SockRef, SendName, SendRef), + {error, {timeout, byte_size(Data)}} + end; + %% + {error, ealready = Reason} -> + %% Internal error: + %% we called send, got eagain, and called send again + %% - without waiting for select message + erlang:error(Reason); + Result -> + send_common_result(Data, Result) + end. + +send_common_result(Data, Result) -> + case Result of + ok -> + ok; + {error, Reason} -> + {error, {Reason, byte_size(Data)}} + end. + + +%% --------------------------------------------------------------------------- +%% + +-spec sendto(Socket, Data, Dest) -> + ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: sockaddr(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}. + +sendto(Socket, Data, Dest) -> + sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT). + +-spec sendto(Socket, Data, Dest, Flags) -> + ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: sockaddr(), + Flags :: send_flags(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}; + + (Socket, Data, Dest, Timeout :: nowait) -> + ok | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Dest :: sockaddr(), + SelectInfo :: select_info(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}; + + (Socket, Data, Dest, Timeout) -> + ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Dest :: sockaddr(), + Timeout :: timeout(), + Reason :: {errcode() | closed | timeout, + Remaining :: pos_integer()}. + +sendto(Socket, Data, Dest, Flags) when is_list(Flags) -> + sendto(Socket, Data, Dest, Flags, ?ESOCK_SENDTO_TIMEOUT_DEFAULT); +sendto(Socket, Data, Dest, Timeout) -> + sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout). + + +-spec sendto(Socket, Data, Dest, Flags, nowait) -> + ok | + {ok, {binary(), SelectInfo}} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: sockaddr(), + Flags :: send_flags(), + SelectInfo :: select_info(), + Reason :: {errcode() | closed, + Remaining :: pos_integer()}; + + (Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: sockaddr(), + Flags :: send_flags(), + Timeout :: timeout(), + Reason :: {errcode() | closed | timeout, + Remaining :: pos_integer()}. + +sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) -> + Bin = erlang:list_to_binary(Data), + sendto(Socket, Bin, Dest, Flags, Timeout); +sendto(?mk_socket(SockRef) = Socket, Data, Dest, Flags, Timeout) + when is_reference(SockRef), is_binary(Data), is_list(Flags) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, Data, Dest, Flags, Timeout]); + nowait -> + send_common_nowait(SockRef, Data, Dest, Flags, sendto); + Deadline -> + send_common_deadline( + SockRef, Data, Dest, Flags, Deadline, sendto) + end; +sendto(Socket, Data, Dest, Flags, Timeout) -> + erlang:error(badarg, [Socket, Data, Dest, Flags, Timeout]). + + +%% --------------------------------------------------------------------------- +%% +%% The only part of the msghdr() that *must* exist (a connected +%% socket need not specify the addr field) is the iov. +%% The ctrl field is optional, and the addr and flags are not +%% used when sending. +%% + +-spec sendmsg(Socket, MsgHdr) -> + ok | + {ok, Remaining} | + {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Remaining :: erlang:iovec(), + Reason :: term(). + +sendmsg(Socket, MsgHdr) -> + sendmsg(Socket, MsgHdr, + ?ESOCK_SENDMSG_FLAGS_DEFAULT, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT). + + +-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Reason :: errcode() | closed; + + (Socket, MsgHdr, Timeout :: nowait) -> + ok | + {ok, Remaining} | + {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Remaining :: erlang:iovec(), + Reason :: errcode() | closed; + + (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Timeout :: timeout(), + Reason :: errcode() | closed | timeout. + +sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) -> + sendmsg(Socket, MsgHdr, Flags, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT); +sendmsg(Socket, MsgHdr, Timeout) -> + sendmsg(Socket, MsgHdr, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout). + + +-spec sendmsg(Socket, MsgHdr, Flags, nowait) -> + ok | + {ok, Remaining} | + {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Remaining :: erlang:iovec(), + Reason :: errcode() | closed; + + (Socket, MsgHdr, Flags, Timeout) -> + ok | + {ok, Remaining} | + {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Timeout :: timeout(), + Remaining :: erlang:iovec(), + Reason :: errcode() | closed | timeout. + +sendmsg(?mk_socket(SockRef) = Socket, MsgHdr, Flags, Timeout) + when is_reference(SockRef), is_map(MsgHdr), is_list(Flags) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, MsgHdr, Flags, Timeout]); + Deadline -> + sendmsg_loop(SockRef, MsgHdr, Flags, Deadline) + end; +sendmsg(Socket, MsgHdr, Flags, Timeout) -> + erlang:error(badarg, [Socket, MsgHdr, Flags, Timeout]). + +sendmsg_loop(SockRef, MsgHdr, Flags, Deadline) -> + SendRef = make_ref(), + case prim_socket:sendmsg(SockRef, SendRef, MsgHdr, Flags) of + ok -> + %% We are done + ok; + %% + {ok, Written} when is_integer(Written) andalso (Written > 0) -> + %% We should not retry here since the protocol may not + %% be able to handle a message being split. Leave it to + %% the caller to figure out (call again with the rest). + %% + %% We need to cancel this partial write. + %% + _ = cancel(SockRef, sendmsg, SendRef), + {ok, sendmsg_rest(maps:get(iov, MsgHdr), Written)}; + %% + select when (Deadline =:= nowait) -> + ?SELECT(sendmsg, SendRef); + select -> + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(?mk_socket(SockRef), select, SendRef) -> + sendmsg_loop(SockRef, MsgHdr, Flags, Deadline); + ?mk_socket_msg(_Socket, abort, {SendRef, Reason}) -> + {error, Reason} + after Timeout -> + _ = cancel(SockRef, sendmsg, SendRef), + {error, timeout} + end; + %% + {error, ealready = Reason} when Deadline =/= nowait -> + %% Internal error: + %% we called send, got eagain, and called send again + %% - without waiting for select message + erlang:error(Reason); + {error, _} = ERROR -> + ERROR + end. + +sendmsg_rest([B|IOVec], Written) when Written >= byte_size(B) -> + sendmsg_rest(IOVec, Written - byte_size(B)); +sendmsg_rest([B|IOVec], Written) -> + <<_:Written/binary, Rest/binary>> = B, + [Rest|IOVec]. + + +%% =========================================================================== +%% +%% recv, recvfrom, recvmsg - receive a message from a socket +%% +%% Description: +%% There is a special case for the argument Length. If its set to zero (0), +%% it means "give me everything you have". +%% +%% Returns: {ok, Binary} | {error, Reason} +%% Binary - The received data as a binary +%% Reason - The error reason: +%% timeout | {timeout, AccData} | +%% posix() | {posix(), AccData} | +%% atom() | {atom(), AccData} +%% AccData - The data (as a binary) that we did manage to receive +%% before the timeout. +%% +%% Arguments: +%% Socket - The socket to read from. +%% Length - The number of bytes to read. +%% Flags - A list of "options" for the read. +%% Timeout - Time-out in milliseconds. + +-spec recv(Socket) -> + {ok, Data} | + {error, Reason} when + Socket :: socket(), + Data :: binary(), + Reason :: + errcode() | closed | + {errcode() | closed, Data :: binary()}. + +recv(Socket) -> + recv(Socket, 0). + +-spec recv(Socket, Length) -> + {ok, Data} | + {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Data :: binary(), + Reason :: + errcode() | closed | + {errcode() | closed, Data :: binary()}. + +recv(Socket, Length) -> + recv(Socket, Length, + ?ESOCK_RECV_FLAGS_DEFAULT, + ?ESOCK_RECV_TIMEOUT_DEFAULT). + +-spec recv(Socket, Length, Flags) -> + {ok, Data} | + {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Data :: binary(), + Reason :: + errcode() | closed | + {errcode() | closed, Data :: binary()}; + + (Socket, Length, Timeout :: nowait) -> + {ok, Data} | + {ok, {Data, SelectInfo}} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Data :: binary(), + SelectInfo :: select_info(), + Reason :: + errcode() | closed | + {errcode() | closed, Data :: binary()}; + + (Socket, Length, Timeout) -> + {ok, Data} | + {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Timeout :: timeout(), + Data :: binary(), + Reason :: + errcode() | closed | timeout | + {errcode() | closed | timeout, Data :: binary()}. + +recv(Socket, Length, Flags) when is_list(Flags) -> + recv(Socket, Length, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT); +recv(Socket, Length, Timeout) -> + recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout). + +-spec recv(Socket, Length, Flags, nowait) -> + {ok, Data} | + {ok, {Data, SelectInfo}} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Data :: binary(), + SelectInfo :: select_info(), + Reason :: + errcode() | closed | + {errcode() | closed, Data :: binary()}; + + (Socket, Length, Flags, Timeout) -> + {ok, Data} | + {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + Data :: binary(), + Reason :: + errcode() | closed | timeout | + {errcode() | closed | timeout, Data :: binary()}. + +recv(?mk_socket(SockRef) = Socket, Length, Flags, Timeout) + when is_reference(SockRef), + is_integer(Length), Length >= 0, + is_list(Flags) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, Length, Flags, Timeout]); + nowait -> + recv_nowait(SockRef, Length, Flags, <<>>); + Deadline -> + recv_deadline(SockRef, Length, Flags, Deadline, <<>>) + end; +recv(Socket, Length, Flags, Timeout) -> + erlang:error(badarg, [Socket, Length, Flags, Timeout]). + +%% We will only recurse with Length == 0 if Length is 0, +%% so Length == 0 means to return all available data also when recursing + +recv_nowait(SockRef, Length, Flags, Acc) -> + RecvRef = make_ref(), + case prim_socket:recv(SockRef, RecvRef, Length, Flags) of + {more, Bin} -> + %% We got what we requested but will not waste more time + %% although there might be more data available + {ok, bincat(Acc, Bin)}; + {select, Bin} -> + %% We got less than requested so the caller will + %% get a select message when there might be more to read + {ok, {bincat(Acc, Bin), ?SELECT_INFO(recv, RecvRef)}}; + select -> + %% The caller will get a select message when there + %% might me data to read + if + byte_size(Acc) =:= 0 -> + ?SELECT(recv, RecvRef); + true -> + {ok, {Acc, ?SELECT_INFO(recv, RecvRef)}} + end; + Result -> + recv_result(Acc, Result) + end. + +recv_deadline(SockRef, Length, Flags, Deadline, Acc) -> + RecvRef = make_ref(), + case prim_socket:recv(SockRef, RecvRef, Length, Flags) of + {more, Bin} -> + %% There is more data readily available + %% - repeat unless time's up + Timeout = timeout(Deadline), + if + 0 < Timeout -> + %% Recv more + recv_deadline( + SockRef, Length, Flags, Deadline, bincat(Acc, Bin)); + true -> + {ok, bincat(Acc, Bin)} + end; + %% + {select, Bin} -> + %% We got less than requested + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) -> + if + 0 < Timeout -> + %% Recv more + recv_deadline( + SockRef, Length - byte_size(Bin), Flags, + Deadline, bincat(Acc, Bin)); + true -> + {error, {timeout, bincat(Acc, Bin)}} + end; + ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) -> + {error, {Reason, bincat(Acc, Bin)}} + after Timeout -> + cancel(SockRef, recv, RecvRef), + {error, {timeout, bincat(Acc, Bin)}} + end; + %% + select when Length =:= 0, 0 < byte_size(Acc) -> + %% We first got some data and are then asked to wait, + %% but we only want the first that comes + %% - cancel and return what we have + cancel(SockRef, recv, RecvRef), + {ok, Acc}; + select -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) -> + if + 0 < Timeout -> + %% Retry + recv_deadline( + SockRef, Length, Flags, Deadline, Acc); + true -> + recv_error(Acc, timeout) + end; + ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) -> + recv_error(Acc, Reason) + after Timeout -> + cancel(SockRef, recv, RecvRef), + recv_error(Acc, timeout) + end; + %% + {error, ealready = Reason} -> + %% Internal error: + %% we called recv, got eagain, and called recv again + %% - without waiting for select message + erlang:error(Reason); + Result -> + recv_result(Acc, Result) + end. + +recv_result(Acc, Result) -> + case Result of + {ok, Bin} -> + {ok, bincat(Acc, Bin)}; + {error, _} = ERROR when byte_size(Acc) =:= 0 -> + ERROR; + {error, Reason} -> + {error, {Reason, Acc}} + end. + +recv_error(Acc, Reason) -> + if + byte_size(Acc) =:= 0 -> + {error, Reason}; + true -> + {error, {Reason, Acc}} + end. + +%% --------------------------------------------------------------------------- +%% +%% With recvfrom we get messages, which means that regardless of how +%% much we want to read, we return when we get a message. +%% The MaxSize argument basically defines the size of our receive +%% buffer. By setting the size to zero (0), we use the configured +%% size (see setopt). +%% It may be impossible to know what (buffer) size is appropriate +%% "in advance", and in those cases it may be convenient to use the +%% (recv) 'peek' flag. When this flag is provided the message is *not* +%% "consumed" from the underlying (OS) buffers, so another recvfrom call +%% is needed, possibly with a then adjusted buffer size. +%% + +-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: errcode() | closed. + +recvfrom(Socket) -> + recvfrom(Socket, 0). + +-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: errcode() | closed. + +recvfrom(Socket, BufSz) -> + recvfrom(Socket, BufSz, + ?ESOCK_RECV_FLAGS_DEFAULT, + ?ESOCK_RECV_TIMEOUT_DEFAULT). + +-spec recvfrom(Socket, Flags, nowait) -> + {ok, {Source, Data}} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Source :: sockaddr() | undefined, + Data :: binary(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, Flags, Timeout) -> + {ok, {Source, Data}} | + {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: errcode() | closed | timeout; + + (Socket, BufSz, Flags) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: errcode() | closed; + + (Socket, BufSz, nowait) -> + {ok, {Source, Data}} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Source :: sockaddr() | undefined, + Data :: binary(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, BufSz, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: errcode() | closed | timeout. + +recvfrom(Socket, Flags, Timeout) when is_list(Flags) -> + recvfrom(Socket, 0, Flags, Timeout); +recvfrom(Socket, BufSz, Flags) when is_list(Flags) -> + recvfrom(Socket, BufSz, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT); +recvfrom(Socket, BufSz, Timeout) -> + recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout). + +-spec recvfrom(Socket, BufSz, Flags, nowait) -> + {ok, {Source, Data}} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Source :: sockaddr() | undefined, + Data :: binary(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, BufSz, Flags, Timeout) -> + {ok, {Source, Data}} | + {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: errcode() | closed | timeout. + +recvfrom(?mk_socket(SockRef) = Socket, BufSz, Flags, Timeout) + when is_reference(SockRef), + is_integer(BufSz), 0 =< BufSz, + is_list(Flags) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, BufSz, Flags, Timeout]); + nowait -> + recvfrom_nowait(SockRef, BufSz, Flags); + Deadline -> + recvfrom_deadline(SockRef, BufSz, Flags, Deadline) + end; +recvfrom(Socket, BufSz, Flags, Timeout) -> + erlang:error(badarg, [Socket, BufSz, Flags, Timeout]). + +recvfrom_nowait(SockRef, BufSz, Flags) -> + RecvRef = make_ref(), + case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of + select -> + ?SELECT(recvfrom, RecvRef); + Result -> + recvfrom_result(Result) + end. + +recvfrom_deadline(SockRef, BufSz, Flags, Deadline) -> + RecvRef = make_ref(), + case prim_socket:recvfrom(SockRef, RecvRef, BufSz, Flags) of + select -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) -> + recvfrom_deadline(SockRef, BufSz, Flags, Deadline); + ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) -> + {error, Reason} + after Timeout -> + cancel(SockRef, recvfrom, RecvRef), + {error, timeout} + end; + {error, ealready = Reason} -> + %% Internal error: + %% we called recvfrom, got eagain, and called recvfrom again + %% - without waiting for select message + erlang:error(Reason); + Result -> + recvfrom_result(Result) + end. + +recvfrom_result(Result) -> + case Result of + {ok, {_Source, _NewData}} = OK -> + OK; + {error, _Reason} = ERROR -> + ERROR + end. + + +%% --------------------------------------------------------------------------- +%% + +-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Reason :: errcode() | closed. + +recvmsg(Socket) -> + recvmsg(Socket, 0, 0, + ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT). + +-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + MsgHdr :: msghdr(), + Reason :: errcode() | closed; + + (Socket, Timeout :: nowait) -> {ok, MsgHdr} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: errcode() | closed | timeout. + +recvmsg(Socket, Flags) when is_list(Flags) -> + recvmsg(Socket, 0, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT); +recvmsg(Socket, Timeout) -> + recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout). + +-spec recvmsg(Socket, Flags, nowait) -> {ok, MsgHdr} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + MsgHdr :: msghdr(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: errcode() | closed | timeout; + + (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + MsgHdr :: msghdr(), + Reason :: errcode() | closed. + +recvmsg(Socket, Flags, Timeout) when is_list(Flags) -> + recvmsg(Socket, 0, 0, Flags, Timeout); +recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz), is_integer(CtrlSz) -> + recvmsg(Socket, BufSz, CtrlSz, + ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT). + + +-spec recvmsg(Socket, BufSz, CtrlSz, Flags, nowait) -> + {ok, MsgHdr} | + {select, SelectInfo} | + {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + Flags :: recv_flags(), + MsgHdr :: msghdr(), + SelectInfo :: select_info(), + Reason :: errcode() | closed; + + (Socket, BufSz, CtrlSz, Flags, Timeout) -> + {ok, MsgHdr} | + {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: errcode() | closed | timeout. + +recvmsg(?mk_socket(SockRef) = Socket, BufSz, CtrlSz, Flags, Timeout) + when is_reference(SockRef), + is_integer(BufSz), 0 =< BufSz, + is_integer(CtrlSz), 0 =< CtrlSz, + is_list(Flags) -> + case deadline(Timeout) of + badarg = Reason -> + erlang:error(Reason, [Socket, BufSz, CtrlSz, Flags, Timeout]); + nowait -> + recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags); + Deadline -> + recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) + end; +recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout) -> + erlang:error(badarg, [Socket, BufSz, CtrlSz, Flags, Timeout]). + +recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags) -> + RecvRef = make_ref(), + case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of + select -> + ?SELECT(recvmsg, RecvRef); + Result -> + recvmsg_result(Result) + end. + +recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) -> + RecvRef = make_ref(), + case prim_socket:recvmsg(SockRef, RecvRef, BufSz, CtrlSz, Flags) of + select -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + Timeout = timeout(Deadline), + receive + ?mk_socket_msg(?mk_socket(SockRef), select, RecvRef) -> + recvmsg_deadline( + SockRef, BufSz, CtrlSz, Flags, Deadline); + ?mk_socket_msg(_Socket, abort, {RecvRef, Reason}) -> + {error, Reason} + after Timeout -> + cancel(SockRef, recvmsg, RecvRef), + {error, timeout} + end; + %% + {error, ealready = Reason} -> + %% Internal error: + %% we called recvmsg, got eagain, and called recvmsg again + %% - without waiting for select message + erlang:error(Reason); + Result -> + recvmsg_result(Result) + end. + +recvmsg_result(Result) -> + case Result of + {ok, _MsgHdr} = OK -> + OK; + {error, _Reason} = ERROR -> + ERROR + end. + + +%% =========================================================================== +%% +%% close - close a file descriptor +%% +%% Closing a socket is a two stage rocket (because of linger). +%% We need to perform the actual socket close while in BLOCKING mode. +%% But that would hang the entire VM, so what we do is divide the +%% close in two steps: +%% 1) prim_socket:nif_close + the socket_stop (nif) callback function +%% This is for everything that can be done safely NON-BLOCKING. +%% 2) prim_socket:nif_finalize_close which is executed by a *dirty* scheduler +%% Before we call the socket close function, we set the socket +%% BLOCKING. Thereby linger is handled properly. + +-spec close(Socket) -> ok | {error, Reason} when + Socket :: socket(), + Reason :: errcode() | closed | timeout. + +close(?mk_socket(SockRef)) + when is_reference(SockRef) -> + case prim_socket:close(SockRef) of + ok -> + prim_socket:finalize_close(SockRef); + {ok, CloseRef} -> + %% We must wait for the socket_stop callback function to + %% complete its work + receive + ?mk_socket_msg(?mk_socket(SockRef), close, CloseRef) -> + prim_socket:finalize_close(SockRef) + end; + {error, _} = ERROR -> + ERROR + end; +close(Socket) -> + erlang:error(badarg, [Socket]). + + + +%% =========================================================================== +%% +%% shutdown - shut down part of a full-duplex connection +%% + +-spec shutdown(Socket, How) -> ok | {error, Reason} when + Socket :: socket(), + How :: shutdown_how(), + Reason :: inet:posix() | closed. + +shutdown(?mk_socket(SockRef), How) + when is_reference(SockRef) -> + prim_socket:shutdown(SockRef, How); +shutdown(Socket, How) -> + erlang:error(badarg, [Socket, How]). + + +%% =========================================================================== +%% +%% setopt - manipulate individual properties of a socket +%% +%% What properties are valid depend on what kind of socket it is +%% (domain, type and protocol) +%% If its an "invalid" option (or value), we should not crash but return some +%% useful error... +%% +%% <KOLLA> +%% +%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING +%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!! +%% +%% </KOLLA> + +-spec setopt(Socket, otp, otp_socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: errcode() | closed | not_owner; + + (Socket, socket, socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, ip, ip_socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, ipv6, ipv6_socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, tcp, tcp_socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, udp, udp_socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, sctp, sctp_socket_option(), Value) -> + ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, Level, Key, Value) -> + ok | {error, Reason} when + Socket :: socket(), + Level :: non_neg_integer(), + Key :: non_neg_integer(), + Value :: binary(), + Reason :: inet:posix() | closed. + +setopt(?mk_socket(SockRef), Level, Key, Value) + when is_reference(SockRef) -> + prim_socket:setopt(SockRef, Level, Key, Value); +setopt(Socket, Level, Key, Value) -> + erlang:error(badarg, [Socket, Level, Key, Value]). + + +%% =========================================================================== +%% +%% getopt - retrieve individual properties of a socket +%% +%% What properties are valid depend on what kind of socket it is +%% (domain, type and protocol). +%% If its an "invalid" option, we should not crash but return some +%% useful error... +%% +%% When specifying level as an integer, and therefor using "native mode", +%% we should make it possible to specify common types instead of the +%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer() +%% + +-spec getopt(Socket, otp, otp_socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: einval | closed; + + (Socket, socket, socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, ip, ip_socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, ipv6, ipv6_socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, tcp, tcp_socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, udp, udp_socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, sctp, sctp_socket_option()) -> + {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: inet:posix() | closed; + + (Socket, Level, Key) -> + ok | {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: integer(), + Key :: {NativeOpt, ValueSize}, + NativeOpt :: integer(), + ValueSize :: int | bool | non_neg_integer(), + Value :: term(), + Reason :: inet:posix() | closed. + +getopt(?mk_socket(SockRef), Level, Key) + when is_reference(SockRef) -> + prim_socket:getopt(SockRef, Level, Key); +getopt(Socket, Level, Key) -> + erlang:error(badarg, [Socket, Level, Key]). + + +%% =========================================================================== +%% +%% sockname - return the current address of the socket. +%% +%% + +-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: inet:posix() | closed. + +sockname(?mk_socket(SockRef)) + when is_reference(SockRef) -> + prim_socket:sockname(SockRef); +sockname(Socket) -> + erlang:error(badarg, [Socket]). + + +%% =========================================================================== +%% +%% peername - return the address of the peer *connected* to the socket. +%% +%% + +-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: inet:posix() | closed. + +peername(?mk_socket(SockRef)) + when is_reference(SockRef) -> + prim_socket:peername(SockRef); +peername(Socket) -> + erlang:error(badarg, [Socket]). + + +%% =========================================================================== +%% +%% cancel - cancel an operation resulting in a select +%% +%% A call to accept, recv/recvfrom/recvmsg and send/sendto/sendmsg +%% can result in a select if they are called with the Timeout argument +%% set to nowait. This is indicated by the return of the select-info. +%% Such a operation can be cancelled by calling this function. +%% + +-spec cancel(Socket, SelectInfo) -> ok | {error, Reason} when + Socket :: socket(), + SelectInfo :: select_info(), + Reason :: einval | closed | exself. + +cancel(?mk_socket(SockRef), ?SELECT_INFO(Tag, Ref)) + when is_reference(SockRef) -> + cancel(SockRef, Tag, Ref); +cancel(Socket, SelectInfo) -> + erlang:error(badarg, [Socket, SelectInfo]). + + +cancel(SockRef, Op, OpRef) -> + case prim_socket:cancel(SockRef, Op, OpRef) of + %% The select has already completed + {error, select_sent} -> + flush_select_msg(SockRef, OpRef), + _ = flush_abort_msg(SockRef, OpRef), + ok; + {error, not_found} -> + _ = flush_abort_msg(SockRef, OpRef), + {error, einval}; + Other -> + _ = flush_abort_msg(SockRef, OpRef), + Other + end. + +flush_select_msg(SockRef, Ref) -> + receive + ?mk_socket_msg(?mk_socket(SockRef), select, Ref) -> + ok + after 0 -> + ok + end. + +flush_abort_msg(SockRef, Ref) -> + receive + ?mk_socket_msg(?mk_socket(SockRef), abort, {Ref, Reason}) -> + Reason + after 0 -> + ok + end. + + +%% =========================================================================== +%% +%% Misc utility functions +%% +%% =========================================================================== + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). + +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). + + +deadline(Timeout) -> + case Timeout of + nowait -> + Timeout; + infinity -> + Timeout; + 0 -> + zero; + _ when is_integer(Timeout), 0 < Timeout -> + timestamp() + Timeout; + _ -> + badarg + end. + +timeout(Deadline) -> + case Deadline of + infinity -> + Deadline; + zero -> + 0; + _ -> + Now = timestamp(), + if + Deadline > Now -> + Deadline - Now; + true -> + 0 + end + end. + +timestamp() -> + erlang:monotonic_time(milli_seconds). + + +-compile({inline, [bincat/2]}). +bincat(<<>>, <<_/binary>> = B) -> B; +bincat(<<_/binary>> = A, <<>>) -> A; +bincat(<<_/binary>> = A, <<_/binary>> = B) -> + <<A/binary, B/binary>>. + + +%% p(F) -> +%% p(F, []). + +%% p(F, A) -> +%% p(get(sname), F, A). + +%% p(undefined, F, A) -> +%% p("***", F, A); +%% p(SName, F, A) -> +%% TS = formated_timestamp(), +%% io:format(user,"[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]), +%% io:format("[~s][~s,~p] " ++ F ++ "~n", [TS, SName, self()|A]). diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index 3ef6bc5533..f420b4faea 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -121,6 +121,13 @@ server1(Iport, Oport, Shell) -> case init:get_argument(remsh) of {ok,[[Node]]} -> ANode = list_to_atom(append_hostname(Node)), + %% We try to connect to the node if the current node is not + %% a distributed node yet. If this succeeds it means that we + %% are running using "-sname undefined". + [begin + _ = net_kernel:start([undefined, shortnames]), + net_kernel:connect_node(ANode) + end || node() =:= nonode@nohost], RShell = {ANode,shell,start,[]}, RGr = group:start(self(), RShell, rem_sh_opts(ANode)), {RGr,RShell}; diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index dcc892ec50..6a3696f92e 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -24,6 +24,21 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # Target Specs # ---------------------------------------------------- +SOCKET_MODULES = \ + socket_test_lib \ + socket_test_logger \ + socket_test_evaluator \ + socket_test_ttest_lib \ + socket_test_ttest_tcp_gen \ + socket_test_ttest_tcp_socket \ + socket_test_ttest_tcp_client \ + socket_test_ttest_tcp_client_gen \ + socket_test_ttest_tcp_client_socket \ + socket_test_ttest_tcp_server \ + socket_test_ttest_tcp_server_gen \ + socket_test_ttest_tcp_server_socket \ + socket_SUITE + MODULES= \ erpc_SUITE \ rpc_SUITE \ @@ -89,6 +104,7 @@ MODULES= \ pg_SUITE \ pg2_SUITE \ seq_trace_SUITE \ + $(SOCKET_MODULES) \ wrap_log_reader_SUITE \ cleanup \ ignore_cores \ @@ -113,6 +129,10 @@ APP_FILES = \ topApp3.app ERL_FILES= $(MODULES:%=%.erl) code_a_test.erl +HRL_FILES= \ + socket_test_evaluator.hrl \ + socket_test_ttest.hrl \ + socket_test_ttest_client.hrl TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) INSTALL_PROGS= $(TARGET_FILES) @@ -135,6 +155,7 @@ ERL_COMPILE_FLAGS += EBIN = . TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) # ---------------------------------------------------- @@ -161,6 +182,7 @@ clean: docs: targets: $(TARGETS) +socket_targets: $(SOCKET_TARGETS) # ---------------------------------------------------- @@ -172,7 +194,7 @@ release_spec: opt release_tests_spec: make_emakefile $(INSTALL_DIR) "$(RELSYSDIR)" - $(INSTALL_DATA) $(ERL_FILES) "$(RELSYSDIR)" + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(APP_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) \ kernel.spec kernel_smoke.spec kernel_bench.spec logger.spec \ diff --git a/lib/kernel/test/erl_boot_server_SUITE.erl b/lib/kernel/test/erl_boot_server_SUITE.erl index 1eaa2cf500..491176678a 100644 --- a/lib/kernel/test/erl_boot_server_SUITE.erl +++ b/lib/kernel/test/erl_boot_server_SUITE.erl @@ -238,7 +238,7 @@ responses(Config) when is_list(Config) -> {ok,BootPid} = erl_boot_server:start_link([Host]), %% Send junk - S1 = open_udp(), + S1 = open_udp(Ip), prim_inet:sendto(S1, Ip, EBOOT_PORT, ["0"]), receive What -> @@ -249,10 +249,10 @@ responses(Config) when is_list(Config) -> end, %% Req from a slave with same erlang vsn. - S2 = open_udp(), + S2 = open_udp(Ip), prim_inet:sendto(S2, Ip, EBOOT_PORT, [EBOOT_REQUEST,ThisVer]), receive - {udp,S2,Ip,_Port1,Resp1} -> + {udp,S2,_Ip,_Port1,Resp1} -> close_udp(S2), EBOOT_REPLY = string:substr(Resp1, 1, length(EBOOT_REPLY)), Rest1 = string:substr(Resp1, length(EBOOT_REPLY)+1, length(Resp1)), @@ -263,7 +263,7 @@ responses(Config) when is_list(Config) -> end, %% Req from a slave with other erlang vsn. - S3 = open_udp(), + S3 = open_udp(Ip), prim_inet:sendto(S3, Ip, EBOOT_PORT, [EBOOT_REQUEST,"1.0"]), receive Anything -> @@ -284,7 +284,7 @@ responses(Config) when is_list(Config) -> {ok,BootPid2} = erl_boot_server:start_link(["127.0.0.1"]), %% Req from slave with invalid ip address. - S4 = open_udp(), + S4 = open_udp(Ip), Ret = case Ip of {127,0,0,1} -> @@ -336,11 +336,11 @@ good_hosts(_Config) -> GoodHost3 = "sauron", [GoodHost1, GoodHost2, GoodHost3]. -open_udp() -> +open_udp(Ip) -> {ok, S} = prim_inet:open(udp, inet, dgram), ok = prim_inet:setopts(S, [{mode,list},{active,true}, {deliver,term},{broadcast,true}]), - {ok,_} = prim_inet:bind(S, {0,0,0,0}, 0), + {ok,_} = prim_inet:bind(S, Ip, 0), S. close_udp(S) -> diff --git a/lib/kernel/test/file_name_SUITE.erl b/lib/kernel/test/file_name_SUITE.erl index 91dce39d12..7166064558 100644 --- a/lib/kernel/test/file_name_SUITE.erl +++ b/lib/kernel/test/file_name_SUITE.erl @@ -634,9 +634,9 @@ hopeless_darwin() -> case {os:type(),os:version()} of {{unix,darwin},{Major,_,_}} -> %% icky file names worked between 10 and 17, but started returning - %% EILSEQ in 18. The check against 18 is exact in case newer + %% EILSEQ in 18. The check against 18..19 is exact in case newer %% versions of Darwin support them again. - Major < 9 orelse Major =:= 18; + Major < 9 orelse (Major >= 18 andalso Major =< 19); _ -> false end. diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl index 65183d83cc..55109a5178 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -41,7 +41,8 @@ peeloff_active_once/1, peeloff_active_true/1, peeloff_active_n/1, buffers/1, names_unihoming_ipv4/1, names_unihoming_ipv6/1, - names_multihoming_ipv4/1, names_multihoming_ipv6/1]). + names_multihoming_ipv4/1, names_multihoming_ipv6/1, + recv_close/1]). suite() -> [{ct_hooks,[ts_install_cth]}, @@ -56,10 +57,25 @@ all() -> {group,G}]. groups() -> - [{smoke,[],[basic,basic_stream]}, - {old_solaris,[],[skip_old_solaris]}, - {extensive,[], - [api_open_close, api_listen, api_connect_init, + [ + {smoke, [], smoke_cases()}, + {old_solaris, [], old_solaris_cases()}, + {extensive, [], extensive_cases()} + ]. + +smoke_cases() -> + [ + basic, + basic_stream + ]. + +old_solaris_cases() -> + [ + skip_old_solaris + ]. + +extensive_cases() -> + [api_open_close, api_listen, api_connect_init, api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6, open_multihoming_ipv4_socket, open_unihoming_ipv6_socket, @@ -68,7 +84,8 @@ groups() -> xfer_stream_min, peeloff_active_once, peeloff_active_true, peeloff_active_n, buffers, names_unihoming_ipv4, names_unihoming_ipv6, - names_multihoming_ipv4, names_multihoming_ipv6]}]. + names_multihoming_ipv4, names_multihoming_ipv6, + recv_close]. init_per_suite(_Config) -> case gen_sctp:open() of @@ -1511,6 +1528,111 @@ recv_comm_up_eventually(S) -> recv_comm_up_eventually(S) end. + +%% +recv_close(Config) when is_list(Config) -> + p("create server socket (and listen)"), + {ok, S} = gen_sctp:open(), + gen_sctp:listen(S, true), + {ok, SPort} = inet:port(S), + + p("create client socket (and connect)"), + {ok, C} = gen_sctp:open(), + {ok, _} = gen_sctp:connect(C, localhost, SPort, []), + + TC = self(), + RECV = fun() -> + p("try setup recv(s)"), + ok = recv_close_setup_recv(S), + p("announce ready"), + TC ! {self(), ready}, + p("try data recv"), + Res = gen_sctp:recv(S), + p("recv res: " + "~n ~p", [Res]), + exit(Res) + end, + p("spawn reader - then await reader ready"), + {Pid, MRef} = spawn_monitor(RECV), + receive + {'DOWN', MRef, process, Pid, PreReason} -> + %% Make sure it does not die for some other reason... + p("unexpected reader termination:" + "~n ~p", [PreReason]), + (catch gen_sctp:close(S)), + (catch gen_sctp:close(C)), + ?line ct:fail("Unexpected pre close from reader (~p): ~p", + [Pid, PreReason]); + {Pid, ready} -> + p("reader ready"), + ok + after 30000 -> % Just in case... + %% This is **extreme**, but there is no way to know + %% how long it will take to iterate through all the + %% addresses of a host... + p("reader ready timeout"), + (catch gen_sctp:close(S)), + (catch gen_sctp:close(C)), + ?line ct:fail("Unexpected pre close timeout (~p)", [Pid]) + end, + + p("\"ensure\" reader reading..."), + receive + Any -> + p("Received unexpected message: " + "~n ~p", [Any]), + (catch gen_sctp:close(S)), + (catch gen_sctp:close(C)), + ?line ct:fail("Unexpected message: ~p", [Any]) + after 5000 -> + ok + end, + + p("close server socket"), + ok = gen_sctp:close(S), + p("await reader termination"), + receive + {'DOWN', MRef, process, Pid, {error, closed}} -> + p("expected reader termination result"), + (catch gen_sctp:close(C)), + ok; + {'DOWN', MRef, process, Pid, PostReason} -> + p("unexpected reader termination: " + "~n ~p", [PostReason]), + (catch gen_sctp:close(C)), + ?line ct:fail("Unexpected post close from reader (~p): ~p", + [Pid, PostReason]) + after 5000 -> + p("unexpected reader termination timeout"), + demonitor(MRef, [flush]), + (catch gen_sctp:close(C)), + exit(Pid, kill), + ?line ct:fail("Reader (~p) termination timeout", [Pid]) + end, + p("close client socket"), + (catch gen_sctp:close(C)), + p("done"), + ok. + + +recv_close_setup_recv(S) -> + recv_close_setup_recv(S, 1). + +recv_close_setup_recv(S, N) -> + p("try setup recv ~w", [N]), + case gen_sctp:recv(S, 5000) of + {ok, {Addr, + Port, + _AncData, + Data}} when is_tuple(Addr) andalso is_integer(Port) -> + p("setup recv ~w: " + "~n ~p", [N, Data]), + recv_close_setup_recv(S, N+1); + {error, timeout} -> + ok + end. + + %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% socket gen_server ultra light @@ -1745,3 +1867,21 @@ match_unless_solaris(A, B) -> timestamp() -> erlang:monotonic_time(). + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour, Min, Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w", + [Hour, Min, Sec, N3 div 1000]), + lists:flatten(FormatTS). + +p(F) -> + p(F, []). + +p(F, A) -> + io:format("~s ~p " ++ F ++ "~n", [formated_timestamp(), self() | A]). + + diff --git a/lib/kernel/test/gen_tcp_echo_SUITE.erl b/lib/kernel/test/gen_tcp_echo_SUITE.erl index 1cf67a374a..e2b17d374b 100644 --- a/lib/kernel/test/gen_tcp_echo_SUITE.erl +++ b/lib/kernel/test/gen_tcp_echo_SUITE.erl @@ -246,6 +246,16 @@ echo_packet0(Echo, Type, EchoFun, SlowEcho, Opts) -> echo_packet1(Echo, Type, EchoFun, infinite); true -> ok end, + PacketSize =:= 0 andalso + begin + %% Switch to raw mode and echo one byte + ok = inet:setopts(Echo, [{packet, raw}, {active, false}]), + ok = gen_tcp:send(Echo, <<"$">>), + case gen_tcp:recv(Echo, 1) of + {ok, <<"$">>} -> ok; + {ok, "$"} -> ok + end + end, _CloseResult = gen_tcp:close(Echo), ct:log("echo_packet0[~w] close: ~p", [self(), _CloseResult]), ok. diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index 2720d3cc77..7ec553ec51 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -40,25 +40,45 @@ recvtos/1, recvtosttl/1, recvttl/1, recvtclass/1, sendtos/1, sendtosttl/1, sendttl/1, sendtclass/1, local_basic/1, local_unbound/1, - local_fdopen/1, local_fdopen_unbound/1, local_abstract/1]). + local_fdopen/1, local_fdopen_unbound/1, local_abstract/1, + recv_close/1]). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,1}}]. all() -> - [send_to_closed, buffer_size, binary_passive_recv, max_buffer_size, - bad_address, read_packets, recv_poll_after_active_once, - open_fd, connect, - implicit_inet6, active_n, + [ + send_to_closed, + buffer_size, + binary_passive_recv, + max_buffer_size, + bad_address, + read_packets, + recv_poll_after_active_once, + open_fd, + connect, + implicit_inet6, + active_n, recvtos, recvtosttl, recvttl, recvtclass, sendtos, sendtosttl, sendttl, sendtclass, - {group, local}]. + {group, local}, + recv_close + ]. groups() -> - [{local, [], - [local_basic, local_unbound, - local_fdopen, local_fdopen_unbound, local_abstract]}]. + [ + {local, [], local_cases()} + ]. + +local_cases() -> + [ + local_basic, + local_unbound, + local_fdopen, + local_fdopen_unbound, + local_abstract + ]. init_per_suite(Config) -> Config. @@ -969,6 +989,52 @@ local_handshake(S, SAddr, C, CAddr) -> end. + + +%%------------------------------------------------------------- +%% Open a passive socket. Create a socket that reads from it. +%% Then close the socket. +recv_close(Config) when is_list(Config) -> + {ok, Sock} = gen_udp:open(0, [{active, false}]), + RECV = fun() -> + io:format("~p try recv~n", [self()]), + Res = gen_udp:recv(Sock, 0), + io:format("~p recv res: ~p~n", [self(), Res]), + exit(Res) + end, + io:format("~p spawn reader", [self()]), + {Pid, MRef} = spawn_monitor(RECV), + receive + {'DOWN', MRef, process, Pid, PreReason} -> + %% Make sure id does not die for some other reason... + ?line ct:fail("Unexpected pre close from reader (~p): ~p", + [Pid, PreReason]) + after 5000 -> % Just in case... + ok + end, + io:format("~p close socket", [self()]), + ok = gen_udp:close(Sock), + io:format("~p await reader termination", [self()]), + receive + {'DOWN', MRef, process, Pid, {error, closed}} -> + io:format("~p expected reader termination result", [self()]), + ok; + {'DOWN', MRef, process, Pid, PostReason} -> + io:format("~p unexpected reader termination: ~p", + [self(), PostReason]), + ?line ct:fail("Unexpected post close from reader (~p): ~p", + [Pid, PostReason]) + after 5000 -> + io:format("~p unexpected reader termination timeout", [self()]), + demonitor(MRef, [flush]), + exit(Pid, kill), + ?line ct:fail("Reader (~p) termination timeout", [Pid]) + end, + ok. + + + + %% %% Utils %% diff --git a/lib/kernel/test/global_SUITE.erl b/lib/kernel/test/global_SUITE.erl index 3c4654b44c..22db756d97 100644 --- a/lib/kernel/test/global_SUITE.erl +++ b/lib/kernel/test/global_SUITE.erl @@ -3738,13 +3738,17 @@ start_node(Name, How, Config) -> start_node(Name0, How, Args, Config) -> Name = node_name(Name0, Config), Pa = filename:dirname(code:which(?MODULE)), - R = test_server:start_node(Name, How, [{args, - Args ++ " " ++ - "-kernel net_setuptime 100 " - %% "-noshell " - "-pa " ++ Pa}, - {linked, false} - ]), + R = test_server:start_node( + Name, How, [{args, + Args ++ + " -kernel net_setuptime 100 " ++ + %% Limit the amount of threads so that we + %% don't run into the maximum allowed + " +S 1 +SDio 1 " ++ + %% "-noshell " + "-pa " ++ Pa}, + {linked, false} + ]), %% {linked,false} only seems to work for slave nodes. %% ct:sleep(1000), record_started_node(R). @@ -3761,12 +3765,16 @@ start_node_rel(Name0, Rel, Config) -> end, Env = [], Pa = filename:dirname(code:which(?MODULE)), - Res = test_server:start_node(Name, peer, - [{args, - Compat ++ - " -kernel net_setuptime 100 " - " -pa " ++ Pa}, - {erl, Release}] ++ Env), + Res = test_server:start_node( + Name, peer, + [{args, + Compat ++ + " -kernel net_setuptime 100 " ++ + %% Limit the amount of threads so that we + %% don't run into the maximum allowed + " +S 1 +SDio 1 " ++ + "-pa " ++ Pa}, + {erl, Release}] ++ Env), record_started_node(Res). record_started_node({ok, Node}) -> diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl index ceee387289..47e44200bf 100644 --- a/lib/kernel/test/init_SUITE.erl +++ b/lib/kernel/test/init_SUITE.erl @@ -354,11 +354,18 @@ restart_with_mode(Config) when is_list(Config) -> {ok,[[Erl]]} = init:get_argument(progname), ModPath = filename:dirname(code:which(?MODULE)), - Eval1 = "'Mode=code:get_mode(), io:fwrite(Mode), case Mode of interactive -> init:restart([{mode,embedded}]); embedded -> erlang:halt() end'", + Quote = case os:type() of + {win32,_} -> + [$"]; + {unix,_} -> + [$'] + end, + + Eval1 = Quote ++ "Mode=code:get_mode(), io:fwrite(Mode), case Mode of interactive -> init:restart([{mode,embedded}]); embedded -> erlang:halt() end" ++ Quote, Cmd1 = Erl ++ " -mode interactive -noshell -eval " ++ Eval1, "interactiveembedded" = os:cmd(Cmd1), - Eval2 = "'Mode=code:get_mode(), io:fwrite(Mode), case Mode of embedded -> init:restart([{mode,interactive}]); interactive -> erlang:halt() end'", + Eval2 = Quote ++ "Mode=code:get_mode(), io:fwrite(Mode), case Mode of embedded -> init:restart([{mode,interactive}]); interactive -> erlang:halt() end" ++ Quote, Cmd2 = Erl ++ " -mode embedded -noshell -eval " ++ Eval2, "embeddedinteractive" = os:cmd(Cmd2), diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl index d65305384f..8ba2f946e7 100644 --- a/lib/kernel/test/logger_std_h_SUITE.erl +++ b/lib/kernel/test/logger_std_h_SUITE.erl @@ -291,9 +291,16 @@ errors(Config) -> _ -> NoDir = lists:concat(["/",?MODULE,"_dir"]), {error, - {handler_not_added,{open_failed,NoDir,eacces}}} = + {handler_not_added,{open_failed,NoDir,Error}}} = logger:add_handler(myh2,logger_std_h, - #{config=>#{type=>{file,NoDir}}}) + #{config=>#{type=>{file,NoDir}}}), + case Error of + erofs -> + %% Happens on OS X + ok; + eacces -> + ok + end end, {error, @@ -1768,7 +1775,7 @@ rotation_opts_restart_handler(Config) -> HConfig3#{config=>StdHConfig3#{max_no_bytes=>75, max_no_files=>1, compress_on_rotate=>true}}), - timer:sleep(100), + timer:sleep(500), {ok,#file_info{size=0}} = file:read_file_info(Log), {ok,#file_info{size=29}} = file:read_file_info(Log++".0.gz"), [_] = filelib:wildcard(Log++".*"), diff --git a/lib/kernel/test/net_SUITE.erl b/lib/kernel/test/net_SUITE.erl index b3226f7e8c..5e6ee054f7 100644 --- a/lib/kernel/test/net_SUITE.erl +++ b/lib/kernel/test/net_SUITE.erl @@ -132,18 +132,13 @@ api_basic_cases() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% init_per_suite(Config) -> - %% We test on the socket module for simplicity - case lists:member(socket, erlang:loaded()) of - true -> - case os:type() of - {win32, _} -> - not_yet_implemented(); - _ -> - %% ?LOGGER:start(), - Config - end; - false -> - {skip, "esock disabled"} + try net:info() of + #{} -> + %% ?LOGGER:start(), + Config + catch + error : notsup -> + {skip, "esock not supported"} end. end_per_suite(_) -> diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl new file mode 100644 index 0000000000..7dd9b04662 --- /dev/null +++ b/lib/kernel/test/socket_SUITE.erl @@ -0,0 +1,44297 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% There are some environment variables that can be used to "manipulate" +%% the test suite: +%% +%% Variable that controls which 'groups' are to run (with default values) +%% +%% ESOCK_TEST_API: include +%% ESOCK_TEST_SOCK_CLOSE: include +%% ESOCK_TEST_TRAFFIC: include +%% ESOCK_TEST_TTEST: exclude +%% +%% Variable that controls "verbosity" of the test case(s): +%% +%% ESOCK_TEST_QUIET: true (default) | false +%% +%% Defines the runtime of the ttest cases +%% (This is the time during which "measurement" is performed. +%% the actual time it takes for the test case to complete +%% will be longer; setup, completion, ...) +%% +%% ESOCK_TEST_TTEST_RUNTIME: 10 seconds +%% Format of values: <integer>[<unit>] +%% Where unit is: ms | s | m +%% ms - milli seconds +%% s - seconds (default) +%% m - minutes +%% + +%% Run the entire test suite: +%% ts:run(emulator, socket_SUITE, [batch]). +%% +%% Run a specific group: +%% ts:run(emulator, socket_SUITE, {group, foo}, [batch]). +%% +%% Run a specific test case: +%% ts:run(emulator, socket_SUITE, foo, [batch]). + +-module(socket_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). +-include("socket_test_evaluator.hrl"). + +%% Suite exports +-export([suite/0, all/0, groups/0]). +-export([init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). + +%% Test cases +-export([ + %% *** API Misc *** + api_m_info/1, + api_m_debug/1, + + %% *** API Basic *** + api_b_open_and_info_udp4/1, + api_b_open_and_info_udp6/1, + api_b_open_and_info_tcp4/1, + api_b_open_and_info_tcp6/1, + api_b_open_and_close_udp4/1, + api_b_open_and_close_udp6/1, + api_b_open_and_close_tcp4/1, + api_b_open_and_close_tcp6/1, + api_b_open_and_close_udpL/1, + api_b_open_and_close_tcpL/1, + api_b_open_and_close_seqpL/1, + api_b_open_and_close_sctp4/1, + api_b_open_and_maybe_close_raw/1, + api_b_sendto_and_recvfrom_udp4/1, + api_b_sendto_and_recvfrom_udpL/1, + api_b_sendmsg_and_recvmsg_udp4/1, + api_b_sendmsg_and_recvmsg_udpL/1, + api_b_send_and_recv_tcp4/1, + api_b_send_and_recv_tcpL/1, + api_b_send_and_recv_seqpL/1, + api_b_sendmsg_and_recvmsg_tcp4/1, + api_b_sendmsg_and_recvmsg_tcpL/1, + api_b_sendmsg_and_recvmsg_seqpL/1, + api_b_sendmsg_and_recvmsg_sctp4/1, + + %% *** API socket from FD *** + api_ffd_open_wod_and_info_udp4/1, + api_ffd_open_wod_and_info_udp6/1, + api_ffd_open_wod_and_info_tcp4/1, + api_ffd_open_wod_and_info_tcp6/1, + api_ffd_open_wd_and_info_udp4/1, + api_ffd_open_wd_and_info_udp6/1, + api_ffd_open_wd_and_info_tcp4/1, + api_ffd_open_wd_and_info_tcp6/1, + api_ffd_open_and_open_wod_and_send_udp4/1, + api_ffd_open_and_open_wod_and_send_udp6/1, + api_ffd_open_and_open_wd_and_send_udp4/1, + api_ffd_open_and_open_wd_and_send_udp6/1, + api_ffd_open_connect_and_open_wod_and_send_tcp4/1, + api_ffd_open_connect_and_open_wod_and_send_tcp6/1, + api_ffd_open_connect_and_open_wd_and_send_tcp4/1, + api_ffd_open_connect_and_open_wd_and_send_tcp6/1, + + + %% *** API async *** + api_a_connect_tcp4/1, + api_a_connect_tcp6/1, + api_a_sendto_and_recvfrom_udp4/1, + api_a_sendto_and_recvfrom_udp6/1, + api_a_sendmsg_and_recvmsg_udp4/1, + api_a_sendmsg_and_recvmsg_udp6/1, + api_a_send_and_recv_tcp4/1, + api_a_send_and_recv_tcp6/1, + api_a_sendmsg_and_recvmsg_tcp4/1, + api_a_sendmsg_and_recvmsg_tcp6/1, + api_a_recvfrom_cancel_udp4/1, + api_a_recvfrom_cancel_udp6/1, + api_a_recvmsg_cancel_udp4/1, + api_a_recvmsg_cancel_udp6/1, + api_a_accept_cancel_tcp4/1, + api_a_accept_cancel_tcp6/1, + api_a_recv_cancel_tcp4/1, + api_a_recv_cancel_tcp6/1, + api_a_recvmsg_cancel_tcp4/1, + api_a_recvmsg_cancel_tcp6/1, + api_a_mrecvfrom_cancel_udp4/1, + api_a_mrecvfrom_cancel_udp6/1, + api_a_mrecvmsg_cancel_udp4/1, + api_a_mrecvmsg_cancel_udp6/1, + api_a_maccept_cancel_tcp4/1, + api_a_maccept_cancel_tcp6/1, + api_a_mrecv_cancel_tcp4/1, + api_a_mrecv_cancel_tcp6/1, + api_a_mrecvmsg_cancel_tcp4/1, + api_a_mrecvmsg_cancel_tcp6/1, + + + %% *** API Options *** + api_opt_simple_otp_options/1, + api_opt_simple_otp_meta_option/1, + api_opt_simple_otp_rcvbuf_option/1, + api_opt_simple_otp_controlling_process/1, + api_opt_sock_acceptconn_udp/1, + api_opt_sock_acceptconn_tcp/1, + api_opt_sock_acceptfilter/1, + api_opt_sock_bindtodevice/1, + api_opt_sock_broadcast/1, + api_opt_sock_debug/1, + api_opt_sock_domain/1, + api_opt_sock_dontroute/1, + api_opt_sock_error/1, + api_opt_sock_keepalive/1, + api_opt_sock_linger/1, + api_opt_sock_mark/1, + api_opt_sock_oobinline/1, + api_opt_sock_passcred_tcp4/1, + api_opt_sock_peek_off_tcpL/1, + api_opt_sock_peercred_tcpL/1, + api_opt_sock_priority_udp4/1, + api_opt_sock_priority_tcp4/1, + api_opt_sock_rcvbuf_udp4/1, + api_opt_sock_rcvlowat_udp4/1, + api_opt_sock_rcvtimeo_udp4/1, + api_opt_sock_sndbuf_udp4/1, + api_opt_sock_sndlowat_udp4/1, + api_opt_sock_sndtimeo_udp4/1, + api_opt_sock_timestamp_udp4/1, + api_opt_sock_timestamp_tcp4/1, + api_opt_ip_add_drop_membership/1, + api_opt_ip_pktinfo_udp4/1, + api_opt_ip_recvopts_udp4/1, + api_opt_ip_recvorigdstaddr_udp4/1, + api_opt_ip_recvtos_udp4/1, + api_opt_ip_recvttl_udp4/1, + api_opt_ip_tos_udp4/1, + api_opt_ip_recverr_udp4/1, + api_opt_ip_mopts_udp4/1, + api_opt_ipv6_recvpktinfo_udp6/1, + api_opt_ipv6_flowinfo_udp6/1, + api_opt_ipv6_hoplimit_udp6/1, + api_opt_ipv6_tclass_udp6/1, + api_opt_ipv6_recverr_udp6/1, + api_opt_ipv6_mopts_udp6/1, + api_opt_tcp_congestion_tcp4/1, + api_opt_tcp_cork_tcp4/1, + api_opt_tcp_maxseg_tcp4/1, + api_opt_tcp_nodelay_tcp4/1, + api_opt_udp_cork_udp4/1, + + %% *** API Operation Timeout *** + api_to_connect_tcp4/1, + api_to_connect_tcp6/1, + api_to_accept_tcp4/1, + api_to_accept_tcp6/1, + api_to_maccept_tcp4/1, + api_to_maccept_tcp6/1, + api_to_send_tcp4/1, + api_to_send_tcp6/1, + api_to_sendto_udp4/1, + api_to_sendto_udp6/1, + api_to_sendmsg_tcp4/1, + api_to_sendmsg_tcp6/1, + api_to_recv_udp4/1, + api_to_recv_udp6/1, + api_to_recv_tcp4/1, + api_to_recv_tcp6/1, + api_to_recvfrom_udp4/1, + api_to_recvfrom_udp6/1, + api_to_recvmsg_udp4/1, + api_to_recvmsg_udp6/1, + api_to_recvmsg_tcp4/1, + api_to_recvmsg_tcp6/1, + + %% Socket Registry + reg_s_single_open_and_close_and_count/1, + + %% *** Socket Closure *** + sc_cpe_socket_cleanup_tcp4/1, + sc_cpe_socket_cleanup_tcp6/1, + sc_cpe_socket_cleanup_tcpL/1, + sc_cpe_socket_cleanup_udp4/1, + sc_cpe_socket_cleanup_udp6/1, + sc_cpe_socket_cleanup_udpL/1, + + sc_lc_recv_response_tcp4/1, + sc_lc_recv_response_tcp6/1, + sc_lc_recv_response_tcpL/1, + sc_lc_recvfrom_response_udp4/1, + sc_lc_recvfrom_response_udp6/1, + sc_lc_recvfrom_response_udpL/1, + sc_lc_recvmsg_response_tcp4/1, + sc_lc_recvmsg_response_tcp6/1, + sc_lc_recvmsg_response_tcpL/1, + sc_lc_recvmsg_response_udp4/1, + sc_lc_recvmsg_response_udp6/1, + sc_lc_recvmsg_response_udpL/1, + sc_lc_acceptor_response_tcp4/1, + sc_lc_acceptor_response_tcp6/1, + sc_lc_acceptor_response_tcpL/1, + + sc_rc_recv_response_tcp4/1, + sc_rc_recv_response_tcp6/1, + sc_rc_recv_response_tcpL/1, + sc_rc_recvmsg_response_tcp4/1, + sc_rc_recvmsg_response_tcp6/1, + sc_rc_recvmsg_response_tcpL/1, + + sc_rs_recv_send_shutdown_receive_tcp4/1, + sc_rs_recv_send_shutdown_receive_tcp6/1, + sc_rs_recv_send_shutdown_receive_tcpL/1, + sc_rs_recvmsg_send_shutdown_receive_tcp4/1, + sc_rs_recvmsg_send_shutdown_receive_tcp6/1, + sc_rs_recvmsg_send_shutdown_receive_tcpL/1, + + %% *** Traffic *** + traffic_send_and_recv_counters_tcp4/1, + traffic_send_and_recv_counters_tcp6/1, + traffic_send_and_recv_counters_tcpL/1, + traffic_sendmsg_and_recvmsg_counters_tcp4/1, + traffic_sendmsg_and_recvmsg_counters_tcp6/1, + traffic_sendmsg_and_recvmsg_counters_tcpL/1, + traffic_sendto_and_recvfrom_counters_udp4/1, + traffic_sendto_and_recvfrom_counters_udp6/1, + traffic_sendto_and_recvfrom_counters_udpL/1, + traffic_sendmsg_and_recvmsg_counters_udp4/1, + traffic_sendmsg_and_recvmsg_counters_udp6/1, + traffic_sendmsg_and_recvmsg_counters_udpL/1, + + traffic_send_and_recv_chunks_tcp4/1, + traffic_send_and_recv_chunks_tcp6/1, + traffic_send_and_recv_chunks_tcpL/1, + + traffic_ping_pong_small_send_and_recv_tcp4/1, + traffic_ping_pong_small_send_and_recv_tcp6/1, + traffic_ping_pong_small_send_and_recv_tcpL/1, + traffic_ping_pong_medium_send_and_recv_tcp4/1, + traffic_ping_pong_medium_send_and_recv_tcp6/1, + traffic_ping_pong_medium_send_and_recv_tcpL/1, + traffic_ping_pong_large_send_and_recv_tcp4/1, + traffic_ping_pong_large_send_and_recv_tcp6/1, + traffic_ping_pong_large_send_and_recv_tcpL/1, + + traffic_ping_pong_small_sendto_and_recvfrom_udp4/1, + traffic_ping_pong_small_sendto_and_recvfrom_udp6/1, + traffic_ping_pong_small_sendto_and_recvfrom_udpL/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udp4/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udp6/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udpL/1, + + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL/1, + + traffic_ping_pong_small_sendmsg_and_recvmsg_udp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp6/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_udpL/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL/1, + + %% *** Time Test *** + %% Server: transport = gen_tcp, active = false + %% Client: transport = gen_tcp + ttest_sgenf_cgenf_small_tcp4/1, + ttest_sgenf_cgenf_small_tcp6/1, + ttest_sgenf_cgenf_medium_tcp4/1, + ttest_sgenf_cgenf_medium_tcp6/1, + ttest_sgenf_cgenf_large_tcp4/1, + ttest_sgenf_cgenf_large_tcp6/1, + + ttest_sgenf_cgeno_small_tcp4/1, + ttest_sgenf_cgeno_small_tcp6/1, + ttest_sgenf_cgeno_medium_tcp4/1, + ttest_sgenf_cgeno_medium_tcp6/1, + ttest_sgenf_cgeno_large_tcp4/1, + ttest_sgenf_cgeno_large_tcp6/1, + + ttest_sgenf_cgent_small_tcp4/1, + ttest_sgenf_cgent_small_tcp6/1, + ttest_sgenf_cgent_medium_tcp4/1, + ttest_sgenf_cgent_medium_tcp6/1, + ttest_sgenf_cgent_large_tcp4/1, + ttest_sgenf_cgent_large_tcp6/1, + + %% Server: transport = gen_tcp, active = false + %% Client: transport = socket(tcp) + ttest_sgenf_csockf_small_tcp4/1, + ttest_sgenf_csockf_small_tcp6/1, + ttest_sgenf_csockf_medium_tcp4/1, + ttest_sgenf_csockf_medium_tcp6/1, + ttest_sgenf_csockf_large_tcp4/1, + ttest_sgenf_csockf_large_tcp6/1, + + ttest_sgenf_csocko_small_tcp4/1, + ttest_sgenf_csocko_small_tcp6/1, + ttest_sgenf_csocko_medium_tcp4/1, + ttest_sgenf_csocko_medium_tcp6/1, + ttest_sgenf_csocko_large_tcp4/1, + ttest_sgenf_csocko_large_tcp6/1, + + ttest_sgenf_csockt_small_tcp4/1, + ttest_sgenf_csockt_small_tcp6/1, + ttest_sgenf_csockt_medium_tcp4/1, + ttest_sgenf_csockt_medium_tcp6/1, + ttest_sgenf_csockt_large_tcp4/1, + ttest_sgenf_csockt_large_tcp6/1, + + %% Server: transport = gen_tcp, active = once + %% Client: transport = gen_tcp + ttest_sgeno_cgenf_small_tcp4/1, + ttest_sgeno_cgenf_small_tcp6/1, + ttest_sgeno_cgenf_medium_tcp4/1, + ttest_sgeno_cgenf_medium_tcp6/1, + ttest_sgeno_cgenf_large_tcp4/1, + ttest_sgeno_cgenf_large_tcp6/1, + + ttest_sgeno_cgeno_small_tcp4/1, + ttest_sgeno_cgeno_small_tcp6/1, + ttest_sgeno_cgeno_medium_tcp4/1, + ttest_sgeno_cgeno_medium_tcp6/1, + ttest_sgeno_cgeno_large_tcp4/1, + ttest_sgeno_cgeno_large_tcp6/1, + + ttest_sgeno_cgent_small_tcp4/1, + ttest_sgeno_cgent_small_tcp6/1, + ttest_sgeno_cgent_medium_tcp4/1, + ttest_sgeno_cgent_medium_tcp6/1, + ttest_sgeno_cgent_large_tcp4/1, + ttest_sgeno_cgent_large_tcp6/1, + + %% Server: transport = gen_tcp, active = once + %% Client: transport = socket(tcp) + ttest_sgeno_csockf_small_tcp4/1, + ttest_sgeno_csockf_small_tcp6/1, + ttest_sgeno_csockf_medium_tcp4/1, + ttest_sgeno_csockf_medium_tcp6/1, + ttest_sgeno_csockf_large_tcp4/1, + ttest_sgeno_csockf_large_tcp6/1, + + ttest_sgeno_csocko_small_tcp4/1, + ttest_sgeno_csocko_small_tcp6/1, + ttest_sgeno_csocko_medium_tcp4/1, + ttest_sgeno_csocko_medium_tcp6/1, + ttest_sgeno_csocko_large_tcp4/1, + ttest_sgeno_csocko_large_tcp6/1, + + ttest_sgeno_csockt_small_tcp4/1, + ttest_sgeno_csockt_small_tcp6/1, + ttest_sgeno_csockt_medium_tcp4/1, + ttest_sgeno_csockt_medium_tcp6/1, + ttest_sgeno_csockt_large_tcp4/1, + ttest_sgeno_csockt_large_tcp6/1, + + %% Server: transport = gen_tcp, active = true + %% Client: transport = gen_tcp + ttest_sgent_cgenf_small_tcp4/1, + ttest_sgent_cgenf_small_tcp6/1, + ttest_sgent_cgenf_medium_tcp4/1, + ttest_sgent_cgenf_medium_tcp6/1, + ttest_sgent_cgenf_large_tcp4/1, + ttest_sgent_cgenf_large_tcp6/1, + + ttest_sgent_cgeno_small_tcp4/1, + ttest_sgent_cgeno_small_tcp6/1, + ttest_sgent_cgeno_medium_tcp4/1, + ttest_sgent_cgeno_medium_tcp6/1, + ttest_sgent_cgeno_large_tcp4/1, + ttest_sgent_cgeno_large_tcp6/1, + + ttest_sgent_cgent_small_tcp4/1, + ttest_sgent_cgent_small_tcp6/1, + ttest_sgent_cgent_medium_tcp4/1, + ttest_sgent_cgent_medium_tcp6/1, + ttest_sgent_cgent_large_tcp4/1, + ttest_sgent_cgent_large_tcp6/1, + + %% Server: transport = gen_tcp, active = true + %% Client: transport = socket(tcp) + ttest_sgent_csockf_small_tcp4/1, + ttest_sgent_csockf_small_tcp6/1, + ttest_sgent_csockf_medium_tcp4/1, + ttest_sgent_csockf_medium_tcp6/1, + ttest_sgent_csockf_large_tcp4/1, + ttest_sgent_csockf_large_tcp6/1, + + ttest_sgent_csocko_small_tcp4/1, + ttest_sgent_csocko_small_tcp6/1, + ttest_sgent_csocko_medium_tcp4/1, + ttest_sgent_csocko_medium_tcp6/1, + ttest_sgent_csocko_large_tcp4/1, + ttest_sgent_csocko_large_tcp6/1, + + ttest_sgent_csockt_small_tcp4/1, + ttest_sgent_csockt_small_tcp6/1, + ttest_sgent_csockt_medium_tcp4/1, + ttest_sgent_csockt_medium_tcp6/1, + ttest_sgent_csockt_large_tcp4/1, + ttest_sgent_csockt_large_tcp6/1, + + %% Server: transport = socket(tcp), active = false + %% Client: transport = gen_tcp + ttest_ssockf_cgenf_small_tcp4/1, + ttest_ssockf_cgenf_small_tcp6/1, + ttest_ssockf_cgenf_medium_tcp4/1, + ttest_ssockf_cgenf_medium_tcp6/1, + ttest_ssockf_cgenf_large_tcp4/1, + ttest_ssockf_cgenf_large_tcp6/1, + + ttest_ssockf_cgeno_small_tcp4/1, + ttest_ssockf_cgeno_small_tcp6/1, + ttest_ssockf_cgeno_medium_tcp4/1, + ttest_ssockf_cgeno_medium_tcp6/1, + ttest_ssockf_cgeno_large_tcp4/1, + ttest_ssockf_cgeno_large_tcp6/1, + + ttest_ssockf_cgent_small_tcp4/1, + ttest_ssockf_cgent_small_tcp6/1, + ttest_ssockf_cgent_medium_tcp4/1, + ttest_ssockf_cgent_medium_tcp6/1, + ttest_ssockf_cgent_large_tcp4/1, + ttest_ssockf_cgent_large_tcp6/1, + + %% Server: transport = socket(tcp), active = false + %% Client: transport = socket(tcp) + ttest_ssockf_csockf_small_tcp4/1, + ttest_ssockf_csockf_small_tcp6/1, + ttest_ssockf_csockf_small_tcpL/1, + ttest_ssockf_csockf_medium_tcp4/1, + ttest_ssockf_csockf_medium_tcp6/1, + ttest_ssockf_csockf_medium_tcpL/1, + ttest_ssockf_csockf_large_tcp4/1, + ttest_ssockf_csockf_large_tcp6/1, + ttest_ssockf_csockf_large_tcpL/1, + + ttest_ssockf_csocko_small_tcp4/1, + ttest_ssockf_csocko_small_tcp6/1, + ttest_ssockf_csocko_small_tcpL/1, + ttest_ssockf_csocko_medium_tcp4/1, + ttest_ssockf_csocko_medium_tcp6/1, + ttest_ssockf_csocko_medium_tcpL/1, + ttest_ssockf_csocko_large_tcp4/1, + ttest_ssockf_csocko_large_tcp6/1, + ttest_ssockf_csocko_large_tcpL/1, + + ttest_ssockf_csockt_small_tcp4/1, + ttest_ssockf_csockt_small_tcp6/1, + ttest_ssockf_csockt_small_tcpL/1, + ttest_ssockf_csockt_medium_tcp4/1, + ttest_ssockf_csockt_medium_tcp6/1, + ttest_ssockf_csockt_medium_tcpL/1, + ttest_ssockf_csockt_large_tcp4/1, + ttest_ssockf_csockt_large_tcp6/1, + ttest_ssockf_csockt_large_tcpL/1, + + %% Server: transport = socket(tcp), active = once + %% Client: transport = gen_tcp + ttest_ssocko_cgenf_small_tcp4/1, + ttest_ssocko_cgenf_small_tcp6/1, + ttest_ssocko_cgenf_medium_tcp4/1, + ttest_ssocko_cgenf_medium_tcp6/1, + ttest_ssocko_cgenf_large_tcp4/1, + ttest_ssocko_cgenf_large_tcp6/1, + + ttest_ssocko_cgeno_small_tcp4/1, + ttest_ssocko_cgeno_small_tcp6/1, + ttest_ssocko_cgeno_medium_tcp4/1, + ttest_ssocko_cgeno_medium_tcp6/1, + ttest_ssocko_cgeno_large_tcp4/1, + ttest_ssocko_cgeno_large_tcp6/1, + + ttest_ssocko_cgent_small_tcp4/1, + ttest_ssocko_cgent_small_tcp6/1, + ttest_ssocko_cgent_medium_tcp4/1, + ttest_ssocko_cgent_medium_tcp6/1, + ttest_ssocko_cgent_large_tcp4/1, + ttest_ssocko_cgent_large_tcp6/1, + + %% Server: transport = socket(tcp), active = once + %% Client: transport = socket(tcp) + ttest_ssocko_csockf_small_tcp4/1, + ttest_ssocko_csockf_small_tcp6/1, + ttest_ssocko_csockf_small_tcpL/1, + ttest_ssocko_csockf_medium_tcp4/1, + ttest_ssocko_csockf_medium_tcpL/1, + ttest_ssocko_csockf_medium_tcp6/1, + ttest_ssocko_csockf_large_tcp4/1, + ttest_ssocko_csockf_large_tcp6/1, + ttest_ssocko_csockf_large_tcpL/1, + + ttest_ssocko_csocko_small_tcp4/1, + ttest_ssocko_csocko_small_tcp6/1, + ttest_ssocko_csocko_small_tcpL/1, + ttest_ssocko_csocko_medium_tcp4/1, + ttest_ssocko_csocko_medium_tcp6/1, + ttest_ssocko_csocko_medium_tcpL/1, + ttest_ssocko_csocko_large_tcp4/1, + ttest_ssocko_csocko_large_tcp6/1, + ttest_ssocko_csocko_large_tcpL/1, + + ttest_ssocko_csockt_small_tcp4/1, + ttest_ssocko_csockt_small_tcp6/1, + ttest_ssocko_csockt_small_tcpL/1, + ttest_ssocko_csockt_medium_tcp4/1, + ttest_ssocko_csockt_medium_tcp6/1, + ttest_ssocko_csockt_medium_tcpL/1, + ttest_ssocko_csockt_large_tcp4/1, + ttest_ssocko_csockt_large_tcp6/1, + ttest_ssocko_csockt_large_tcpL/1, + + %% Server: transport = socket(tcp), active = true + %% Client: transport = gen_tcp + ttest_ssockt_cgenf_small_tcp4/1, + ttest_ssockt_cgenf_small_tcp6/1, + ttest_ssockt_cgenf_medium_tcp4/1, + ttest_ssockt_cgenf_medium_tcp6/1, + ttest_ssockt_cgenf_large_tcp4/1, + ttest_ssockt_cgenf_large_tcp6/1, + + ttest_ssockt_cgeno_small_tcp4/1, + ttest_ssockt_cgeno_small_tcp6/1, + ttest_ssockt_cgeno_medium_tcp4/1, + ttest_ssockt_cgeno_medium_tcp6/1, + ttest_ssockt_cgeno_large_tcp4/1, + ttest_ssockt_cgeno_large_tcp6/1, + + ttest_ssockt_cgent_small_tcp4/1, + ttest_ssockt_cgent_small_tcp6/1, + ttest_ssockt_cgent_medium_tcp4/1, + ttest_ssockt_cgent_medium_tcp6/1, + ttest_ssockt_cgent_large_tcp4/1, + ttest_ssockt_cgent_large_tcp6/1, + + %% Server: transport = socket(tcp), active = true + %% Client: transport = socket(tcp) + ttest_ssockt_csockf_small_tcp4/1, + ttest_ssockt_csockf_small_tcp6/1, + ttest_ssockt_csockf_small_tcpL/1, + ttest_ssockt_csockf_medium_tcp4/1, + ttest_ssockt_csockf_medium_tcp6/1, + ttest_ssockt_csockf_medium_tcpL/1, + ttest_ssockt_csockf_large_tcp4/1, + ttest_ssockt_csockf_large_tcp6/1, + ttest_ssockt_csockf_large_tcpL/1, + + ttest_ssockt_csocko_small_tcp4/1, + ttest_ssockt_csocko_small_tcp6/1, + ttest_ssockt_csocko_small_tcpL/1, + ttest_ssockt_csocko_medium_tcp4/1, + ttest_ssockt_csocko_medium_tcp6/1, + ttest_ssockt_csocko_medium_tcpL/1, + ttest_ssockt_csocko_large_tcp4/1, + ttest_ssockt_csocko_large_tcp6/1, + ttest_ssockt_csocko_large_tcpL/1, + + ttest_ssockt_csockt_small_tcp4/1, + ttest_ssockt_csockt_small_tcp6/1, + ttest_ssockt_csockt_small_tcpL/1, + ttest_ssockt_csockt_medium_tcp4/1, + ttest_ssockt_csockt_medium_tcp6/1, + ttest_ssockt_csockt_medium_tcpL/1, + ttest_ssockt_csockt_large_tcp4/1, + ttest_ssockt_csockt_large_tcp6/1, + ttest_ssockt_csockt_large_tcpL/1, + + %% Tickets + otp16359_maccept_tcp4/1, + otp16359_maccept_tcp6/1, + otp16359_maccept_tcpL/1 + ]). + + +%% Internal exports +%% -export([]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(LIB, socket_test_lib). +-define(TTEST_LIB, socket_test_ttest_lib). +-define(LOGGER, socket_test_logger). + +-define(BASIC_REQ, <<"hejsan">>). +-define(BASIC_REP, <<"hoppsan">>). + +-define(DATA, <<"HOPPSAN">>). % Temporary +-define(FAIL(R), exit(R)). + +-define(SLEEP(T), receive after T -> ok end). + +-define(MINS(M), timer:minutes(M)). +-define(SECS(S), timer:seconds(S)). + +-define(TT(T), ct:timetrap(T)). + +-define(F(F, A), ?LIB:f(F, A)). + + +-define(TPP_SMALL, lists:seq(1, 8)). +-define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))). +-define(TPP_LARGE, lists:flatten(lists:duplicate(1024, ?TPP_MEDIUM))). + +-define(TPP_SMALL_NUM, 5000). +-define(TPP_MEDIUM_NUM, 500). +-define(TPP_LARGE_NUM, 50). +-define(TPP_NUM(Config, Base), Base div lookup(esock_factor, 1, Config)). + +-define(TTEST_RUNTIME, ?SECS(10)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + Groups = [{api, "ESOCK_TEST_API", include}, + {socket_close, "ESOCK_TEST_SOCK_CLOSE", include}, + {traffic, "ESOCK_TEST_TRAFFIC", include}, + {ttest, "ESOCK_TEST_TTEST", exclude}, + {tickets, "ESOCK_TEST_TICKETS", include}], + [use_group(Group, Env, Default) || {Group, Env, Default} <- Groups]. + +use_group(Group, Env, Default) -> + case os:getenv(Env) of + false when (Default =:= include) -> + [{group, Group}]; + false -> + []; + Val -> + case list_to_atom(string:to_lower(Val)) of + Use when (Use =:= include) orelse + (Use =:= enable) orelse + (Use =:= true) -> + [{group, Group}]; + _ -> + [] + end + end. + + +groups() -> + [{api, [], api_cases()}, + {api_misc, [], api_misc_cases()}, + {api_basic, [], api_basic_cases()}, + {api_from_fd, [], api_from_fd_cases()}, + {api_async, [], api_async_cases()}, + {api_options, [], api_options_cases()}, + {api_options_otp, [], api_options_otp_cases()}, + {api_options_socket, [], api_options_socket_cases()}, + {api_option_sock_acceptconn, [], api_option_sock_acceptconn_cases()}, + {api_option_sock_passcred, [], api_option_sock_passcred_cases()}, + {api_option_sock_priority, [], api_option_sock_priority_cases()}, + {api_option_sock_buf, [], api_option_sock_buf_cases()}, + {api_option_sock_lowat, [], api_option_sock_lowat_cases()}, + {api_option_sock_timeo, [], api_option_sock_timeo_cases()}, + {api_option_sock_timestamp, [], api_option_sock_timestamp_cases()}, + {api_options_ip, [], api_options_ip_cases()}, + {api_options_ipv6, [], api_options_ipv6_cases()}, + {api_options_tcp, [], api_options_tcp_cases()}, + {api_options_udp, [], api_options_udp_cases()}, + %% {api_options_sctp, [], api_options_sctp_cases()}, + {api_op_with_timeout, [], api_op_with_timeout_cases()}, + {reg, [], reg_simple_cases()}, + {socket_close, [], socket_close_cases()}, + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, + {sc_local_close, [], sc_lc_cases()}, + {sc_remote_close, [], sc_rc_cases()}, + {sc_remote_shutdown, [], sc_rs_cases()}, + {traffic, [], traffic_cases()}, + {traffic_counters, [], traffic_counters_cases()}, + {traffic_chunks, [], traffic_chunks_cases()}, + {traffic_ping_pong, [], traffic_ping_pong_cases()}, + {traffic_pp_send_recv, [], traffic_pp_send_recv_cases()}, + {traffic_pp_sendto_recvfrom, [], traffic_pp_sendto_recvfrom_cases()}, + {traffic_pp_sendmsg_recvmsg, [], traffic_pp_sendmsg_recvmsg_cases()}, + {ttest, [], ttest_cases()}, + {ttest_sgenf, [], ttest_sgenf_cases()}, + {ttest_sgenf_cgen, [], ttest_sgenf_cgen_cases()}, + {ttest_sgenf_cgenf, [], ttest_sgenf_cgenf_cases()}, + {ttest_sgenf_cgeno, [], ttest_sgenf_cgeno_cases()}, + {ttest_sgenf_cgent, [], ttest_sgenf_cgent_cases()}, + {ttest_sgenf_csock, [], ttest_sgenf_csock_cases()}, + {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()}, + {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()}, + {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()}, + {ttest_sgeno, [], ttest_sgeno_cases()}, + {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()}, + {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()}, + {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()}, + {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()}, + {ttest_sgeno_csock, [], ttest_sgeno_csock_cases()}, + {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()}, + {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()}, + {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()}, + {ttest_sgent, [], ttest_sgent_cases()}, + {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()}, + {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()}, + {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()}, + {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()}, + {ttest_sgent_csock, [], ttest_sgent_csock_cases()}, + {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()}, + {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()}, + {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()}, + {ttest_ssockf, [], ttest_ssockf_cases()}, + {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()}, + {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()}, + {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()}, + {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()}, + {ttest_ssockf_csock, [], ttest_ssockf_csock_cases()}, + {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()}, + {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()}, + {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()}, + {ttest_ssocko, [], ttest_ssocko_cases()}, + {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()}, + {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()}, + {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()}, + {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()}, + {ttest_ssocko_csock, [], ttest_ssocko_csock_cases()}, + {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()}, + {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()}, + {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()}, + {ttest_ssockt, [], ttest_ssockt_cases()}, + {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()}, + {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()}, + {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()}, + {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()}, + {ttest_ssockt_csock, [], ttest_ssockt_csock_cases()}, + {ttest_ssockt_csockf, [], ttest_ssockt_csockf_cases()}, + {ttest_ssockt_csocko, [], ttest_ssockt_csocko_cases()}, + {ttest_ssockt_csockt, [], ttest_ssockt_csockt_cases()}, + + %% Ticket groups + {tickets, [], tickets_cases()}, + {otp16359, [], otp16359_cases()} + ]. + +api_cases() -> + [ + {group, api_misc}, + {group, api_basic}, + {group, api_async}, + {group, api_options}, + {group, api_op_with_timeout} + ]. + +api_misc_cases() -> + [ + api_m_info, + api_m_debug + ]. + +api_basic_cases() -> + [ + api_b_open_and_info_udp4, + api_b_open_and_info_udp6, + api_b_open_and_info_tcp4, + api_b_open_and_info_tcp6, + api_b_open_and_close_udp4, + api_b_open_and_close_udp6, + api_b_open_and_close_tcp4, + api_b_open_and_close_tcp6, + api_b_open_and_close_udpL, + api_b_open_and_close_tcpL, + api_b_open_and_close_seqpL, + api_b_open_and_close_sctp4, + api_b_open_and_maybe_close_raw, + api_b_sendto_and_recvfrom_udp4, + api_b_sendto_and_recvfrom_udpL, + api_b_sendmsg_and_recvmsg_udp4, + api_b_sendmsg_and_recvmsg_udpL, + api_b_send_and_recv_tcp4, + api_b_send_and_recv_tcpL, + api_b_send_and_recv_seqpL, + api_b_sendmsg_and_recvmsg_tcp4, + api_b_sendmsg_and_recvmsg_tcpL, + api_b_sendmsg_and_recvmsg_seqpL, + api_b_sendmsg_and_recvmsg_sctp4 + ]. + +api_from_fd_cases() -> + [ + api_ffd_open_wod_and_info_udp4, + api_ffd_open_wod_and_info_udp6, + api_ffd_open_wod_and_info_tcp4, + api_ffd_open_wod_and_info_tcp6, + api_ffd_open_wd_and_info_udp4, + api_ffd_open_wd_and_info_udp6, + api_ffd_open_wd_and_info_tcp4, + api_ffd_open_wd_and_info_tcp6, + api_ffd_open_and_open_wod_and_send_udp4, + api_ffd_open_and_open_wod_and_send_udp6, + api_ffd_open_and_open_wd_and_send_udp4, + api_ffd_open_and_open_wd_and_send_udp6, + api_ffd_open_connect_and_open_wod_and_send_tcp4, + api_ffd_open_connect_and_open_wod_and_send_tcp6, + api_ffd_open_connect_and_open_wd_and_send_tcp4, + api_ffd_open_connect_and_open_wd_and_send_tcp6 + ]. + +api_async_cases() -> + [ + api_a_connect_tcp4, + api_a_connect_tcp6, + api_a_sendto_and_recvfrom_udp4, + api_a_sendto_and_recvfrom_udp6, + api_a_sendmsg_and_recvmsg_udp4, + api_a_sendmsg_and_recvmsg_udp6, + api_a_send_and_recv_tcp4, + api_a_send_and_recv_tcp6, + api_a_sendmsg_and_recvmsg_tcp4, + api_a_sendmsg_and_recvmsg_tcp6, + api_a_recvfrom_cancel_udp4, + api_a_recvfrom_cancel_udp6, + api_a_recvmsg_cancel_udp4, + api_a_recvmsg_cancel_udp6, + api_a_accept_cancel_tcp4, + api_a_accept_cancel_tcp6, + api_a_recv_cancel_tcp4, + api_a_recv_cancel_tcp6, + api_a_recvmsg_cancel_tcp4, + api_a_recvmsg_cancel_tcp6, + api_a_mrecvfrom_cancel_udp4, + api_a_mrecvfrom_cancel_udp6, + api_a_mrecvmsg_cancel_udp4, + api_a_mrecvmsg_cancel_udp6, + api_a_maccept_cancel_tcp4, + api_a_maccept_cancel_tcp6, + api_a_mrecv_cancel_tcp4, + api_a_mrecv_cancel_tcp6, + api_a_mrecvmsg_cancel_tcp4, + api_a_mrecvmsg_cancel_tcp6 + ]. + +api_options_cases() -> + [ + {group, api_options_otp}, + {group, api_options_socket}, + {group, api_options_ip}, + {group, api_options_ipv6}, + {group, api_options_tcp}, + {group, api_options_udp} + %% {group, api_options_sctp} + ]. + +api_options_otp_cases() -> + [ + api_opt_simple_otp_options, + api_opt_simple_otp_meta_option, + api_opt_simple_otp_rcvbuf_option, + api_opt_simple_otp_controlling_process + ]. + +api_options_socket_cases() -> + [ + {group, api_option_sock_acceptconn}, + api_opt_sock_acceptfilter, + api_opt_sock_bindtodevice, + api_opt_sock_broadcast, + api_opt_sock_debug, + api_opt_sock_domain, + api_opt_sock_dontroute, + api_opt_sock_error, + api_opt_sock_keepalive, + api_opt_sock_linger, + api_opt_sock_mark, + api_opt_sock_oobinline, + {group, api_option_sock_passcred}, + api_opt_sock_peek_off_tcpL, + api_opt_sock_peercred_tcpL, + {group, api_option_sock_priority}, + {group, api_option_sock_buf}, + {group, api_option_sock_lowat}, + {group, api_option_sock_timeo}, + {group, api_option_sock_timestamp} + ]. + +api_option_sock_acceptconn_cases() -> + [ + api_opt_sock_acceptconn_udp, + api_opt_sock_acceptconn_tcp + ]. + +api_option_sock_passcred_cases() -> + [ + %% api_opt_sock_passcred_udp4, + api_opt_sock_passcred_tcp4 + ]. + +api_option_sock_priority_cases() -> + [ + api_opt_sock_priority_udp4, + api_opt_sock_priority_tcp4%, + %% api_opt_sock_priority_udp6, + %% api_opt_sock_priority_tcp6 + ]. + +api_option_sock_buf_cases() -> + [ + api_opt_sock_rcvbuf_udp4, + api_opt_sock_sndbuf_udp4 + ]. + +api_option_sock_lowat_cases() -> + [ + api_opt_sock_rcvlowat_udp4, + api_opt_sock_sndlowat_udp4 + ]. + +api_option_sock_timeo_cases() -> + [ + api_opt_sock_rcvtimeo_udp4, + api_opt_sock_sndtimeo_udp4 + ]. + +api_option_sock_timestamp_cases() -> + [ + api_opt_sock_timestamp_udp4, + api_opt_sock_timestamp_tcp4 + ]. + +api_options_ip_cases() -> + [ + api_opt_ip_add_drop_membership, + api_opt_ip_pktinfo_udp4, + api_opt_ip_recvopts_udp4, + api_opt_ip_recvorigdstaddr_udp4, + api_opt_ip_recvtos_udp4, + api_opt_ip_recvttl_udp4, + api_opt_ip_tos_udp4, + api_opt_ip_recverr_udp4, + + %% Should be last! + api_opt_ip_mopts_udp4 + ]. + +api_options_ipv6_cases() -> + [ + api_opt_ipv6_recvpktinfo_udp6, + api_opt_ipv6_flowinfo_udp6, + api_opt_ipv6_hoplimit_udp6, + api_opt_ipv6_tclass_udp6, + api_opt_ipv6_recverr_udp6, + + %% Should be last! + api_opt_ipv6_mopts_udp6 + ]. + +api_options_tcp_cases() -> + [ + api_opt_tcp_congestion_tcp4, + %% api_opt_tcp_congestion_tcp6, + api_opt_tcp_cork_tcp4, + %% api_opt_tcp_cork_tcp6, + api_opt_tcp_maxseg_tcp4, + %% api_opt_tcp_maxseg_tcp6, + api_opt_tcp_nodelay_tcp4%, + %% api_opt_tcp_nodelay_tcp6 + ]. + +api_options_udp_cases() -> + [ + api_opt_udp_cork_udp4%, + %% api_opt_udp_cork_udp6 + ]. + +api_op_with_timeout_cases() -> + [ + api_to_connect_tcp4, + api_to_connect_tcp6, + api_to_accept_tcp4, + api_to_accept_tcp6, + api_to_maccept_tcp4, + api_to_maccept_tcp6, + api_to_send_tcp4, + api_to_send_tcp6, + api_to_sendto_udp4, + api_to_sendto_udp6, + api_to_sendmsg_tcp4, + api_to_sendmsg_tcp6, + api_to_recv_udp4, + api_to_recv_udp6, + api_to_recv_tcp4, + api_to_recv_tcp6, + api_to_recvfrom_udp4, + api_to_recvfrom_udp6, + api_to_recvmsg_udp4, + api_to_recvmsg_udp6, + api_to_recvmsg_tcp4, + api_to_recvmsg_tcp6 + ]. + +%% Socket Registry "simple" test cases +reg_simple_cases() -> + [ + reg_s_single_open_and_close_and_count + ]. + + +%% These cases tests what happens when the socket is closed/shutdown, +%% locally or remotely. +socket_close_cases() -> + [ + {group, sc_ctrl_proc_exit}, + {group, sc_local_close}, + {group, sc_remote_close}, + {group, sc_remote_shutdown} + ]. + +%% These cases are all about socket cleanup after the controlling process +%% exits *without* explicitly calling socket:close/1. +sc_cp_exit_cases() -> + [ + sc_cpe_socket_cleanup_tcp4, + sc_cpe_socket_cleanup_tcp6, + sc_cpe_socket_cleanup_tcpL, + sc_cpe_socket_cleanup_udp4, + sc_cpe_socket_cleanup_udp6, + sc_cpe_socket_cleanup_udpL + ]. + +%% These cases tests what happens when the socket is closed locally. +sc_lc_cases() -> + [ + sc_lc_recv_response_tcp4, + sc_lc_recv_response_tcp6, + sc_lc_recv_response_tcpL, + + sc_lc_recvfrom_response_udp4, + sc_lc_recvfrom_response_udp6, + sc_lc_recvfrom_response_udpL, + + sc_lc_recvmsg_response_tcp4, + sc_lc_recvmsg_response_tcp6, + sc_lc_recvmsg_response_tcpL, + sc_lc_recvmsg_response_udp4, + sc_lc_recvmsg_response_udp6, + sc_lc_recvmsg_response_udpL, + + sc_lc_acceptor_response_tcp4, + sc_lc_acceptor_response_tcp6, + sc_lc_acceptor_response_tcpL + ]. + +%% These cases tests what happens when the socket is closed remotely. +sc_rc_cases() -> + [ + sc_rc_recv_response_tcp4, + sc_rc_recv_response_tcp6, + sc_rc_recv_response_tcpL, + + sc_rc_recvmsg_response_tcp4, + sc_rc_recvmsg_response_tcp6, + sc_rc_recvmsg_response_tcpL + ]. + +%% These cases tests what happens when the socket is shutdown/closed remotely +%% after writing and reading is ongoing. +sc_rs_cases() -> + [ + sc_rs_recv_send_shutdown_receive_tcp4, + sc_rs_recv_send_shutdown_receive_tcp6, + sc_rs_recv_send_shutdown_receive_tcpL, + + sc_rs_recvmsg_send_shutdown_receive_tcp4, + sc_rs_recvmsg_send_shutdown_receive_tcp6, + sc_rs_recvmsg_send_shutdown_receive_tcpL + ]. + + +traffic_cases() -> + [ + {group, traffic_counters}, + {group, traffic_chunks}, + {group, traffic_ping_pong} + ]. + +traffic_counters_cases() -> + [ + traffic_send_and_recv_counters_tcp4, + traffic_send_and_recv_counters_tcp6, + traffic_send_and_recv_counters_tcpL, + traffic_sendmsg_and_recvmsg_counters_tcp4, + traffic_sendmsg_and_recvmsg_counters_tcp6, + traffic_sendmsg_and_recvmsg_counters_tcpL, + traffic_sendto_and_recvfrom_counters_udp4, + traffic_sendto_and_recvfrom_counters_udp6, + traffic_sendto_and_recvfrom_counters_udpL, + traffic_sendmsg_and_recvmsg_counters_udp4, + traffic_sendmsg_and_recvmsg_counters_udp6, + traffic_sendmsg_and_recvmsg_counters_udpL + ]. + +traffic_chunks_cases() -> + [ + traffic_send_and_recv_chunks_tcp4, + traffic_send_and_recv_chunks_tcp6, + traffic_send_and_recv_chunks_tcpL + ]. + +traffic_ping_pong_cases() -> + [ + {group, traffic_pp_send_recv}, + {group, traffic_pp_sendto_recvfrom}, + {group, traffic_pp_sendmsg_recvmsg} + ]. + +traffic_pp_send_recv_cases() -> + [ + traffic_ping_pong_small_send_and_recv_tcp4, + traffic_ping_pong_small_send_and_recv_tcp6, + traffic_ping_pong_small_send_and_recv_tcpL, + traffic_ping_pong_medium_send_and_recv_tcp4, + traffic_ping_pong_medium_send_and_recv_tcp6, + traffic_ping_pong_medium_send_and_recv_tcpL, + traffic_ping_pong_large_send_and_recv_tcp4, + traffic_ping_pong_large_send_and_recv_tcp6, + traffic_ping_pong_large_send_and_recv_tcpL + ]. + +traffic_pp_sendto_recvfrom_cases() -> + [ + traffic_ping_pong_small_sendto_and_recvfrom_udp4, + traffic_ping_pong_small_sendto_and_recvfrom_udp6, + traffic_ping_pong_small_sendto_and_recvfrom_udpL, + traffic_ping_pong_medium_sendto_and_recvfrom_udp4, + traffic_ping_pong_medium_sendto_and_recvfrom_udp6, + traffic_ping_pong_medium_sendto_and_recvfrom_udpL + ]. + +traffic_pp_sendmsg_recvmsg_cases() -> + [ + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL, + + traffic_ping_pong_small_sendmsg_and_recvmsg_udp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp6, + traffic_ping_pong_small_sendmsg_and_recvmsg_udpL, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL + ]. + +ttest_cases() -> + [ + %% Server: transport = gen_tcp, active = false + {group, ttest_sgenf}, + + %% Server: transport = gen_tcp, active = once + {group, ttest_sgeno}, + + %% Server: transport = gen_tcp, active = true + {group, ttest_sgent}, + + %% Server: transport = socket(tcp), active = false + {group, ttest_ssockf}, + + %% Server: transport = socket(tcp), active = once + {group, ttest_ssocko}, + + %% Server: transport = socket(tcp), active = true + {group, ttest_ssockt} + + ]. + + +%% Server: transport = gen_tcp, active = false +ttest_sgenf_cases() -> + [ + {group, ttest_sgenf_cgen}, + {group, ttest_sgenf_csock} + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp +ttest_sgenf_cgen_cases() -> + [ + {group, ttest_sgenf_cgenf}, + {group, ttest_sgenf_cgeno}, + {group, ttest_sgenf_cgent} + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = false +ttest_sgenf_cgenf_cases() -> + [ + ttest_sgenf_cgenf_small_tcp4, + ttest_sgenf_cgenf_small_tcp6, + + ttest_sgenf_cgenf_medium_tcp4, + ttest_sgenf_cgenf_medium_tcp6, + + ttest_sgenf_cgenf_large_tcp4, + ttest_sgenf_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = once +ttest_sgenf_cgeno_cases() -> + [ + ttest_sgenf_cgeno_small_tcp4, + ttest_sgenf_cgeno_small_tcp6, + + ttest_sgenf_cgeno_medium_tcp4, + ttest_sgenf_cgeno_medium_tcp6, + + ttest_sgenf_cgeno_large_tcp4, + ttest_sgenf_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = true +ttest_sgenf_cgent_cases() -> + [ + ttest_sgenf_cgent_small_tcp4, + ttest_sgenf_cgent_small_tcp6, + + ttest_sgenf_cgent_medium_tcp4, + ttest_sgenf_cgent_medium_tcp6, + + ttest_sgenf_cgent_large_tcp4, + ttest_sgenf_cgent_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = socket(tcp) +ttest_sgenf_csock_cases() -> + [ + {group, ttest_sgenf_csockf}, + {group, ttest_sgenf_csocko}, + {group, ttest_sgenf_csockt} + ]. + +ttest_sgenf_csockf_cases() -> + [ + ttest_sgenf_csockf_small_tcp4, + ttest_sgenf_csockf_small_tcp6, + + ttest_sgenf_csockf_medium_tcp4, + ttest_sgenf_csockf_medium_tcp6, + + ttest_sgenf_csockf_large_tcp4, + ttest_sgenf_csockf_large_tcp6 + ]. + +ttest_sgenf_csocko_cases() -> + [ + ttest_sgenf_csocko_small_tcp4, + ttest_sgenf_csocko_small_tcp6, + + ttest_sgenf_csocko_medium_tcp4, + ttest_sgenf_csocko_medium_tcp6, + + ttest_sgenf_csocko_large_tcp4, + ttest_sgenf_csocko_large_tcp6 + ]. + +ttest_sgenf_csockt_cases() -> + [ + ttest_sgenf_csockt_small_tcp4, + ttest_sgenf_csockt_small_tcp6, + + ttest_sgenf_csockt_medium_tcp4, + ttest_sgenf_csockt_medium_tcp6, + + ttest_sgenf_csockt_large_tcp4, + ttest_sgenf_csockt_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +ttest_sgeno_cases() -> + [ + {group, ttest_sgeno_cgen}, + {group, ttest_sgeno_csock} + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp +ttest_sgeno_cgen_cases() -> + [ + {group, ttest_sgeno_cgenf}, + {group, ttest_sgeno_cgeno}, + {group, ttest_sgeno_cgent} + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = false +ttest_sgeno_cgenf_cases() -> + [ + ttest_sgeno_cgenf_small_tcp4, + ttest_sgeno_cgenf_small_tcp6, + + ttest_sgeno_cgenf_medium_tcp4, + ttest_sgeno_cgenf_medium_tcp6, + + ttest_sgeno_cgenf_large_tcp4, + ttest_sgeno_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = once +ttest_sgeno_cgeno_cases() -> + [ + ttest_sgeno_cgeno_small_tcp4, + ttest_sgeno_cgeno_small_tcp6, + + ttest_sgeno_cgeno_medium_tcp4, + ttest_sgeno_cgeno_medium_tcp6, + + ttest_sgeno_cgeno_large_tcp4, + ttest_sgeno_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = true +ttest_sgeno_cgent_cases() -> + [ + ttest_sgeno_cgent_small_tcp4, + ttest_sgeno_cgent_small_tcp6, + + ttest_sgeno_cgent_medium_tcp4, + ttest_sgeno_cgent_medium_tcp6, + + ttest_sgeno_cgent_large_tcp4, + ttest_sgeno_cgent_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = socket(tcp) +ttest_sgeno_csock_cases() -> + [ + {group, ttest_sgeno_csockf}, + {group, ttest_sgeno_csocko}, + {group, ttest_sgeno_csockt} + ]. + +ttest_sgeno_csockf_cases() -> + [ + ttest_sgeno_csockf_small_tcp4, + ttest_sgeno_csockf_small_tcp6, + + ttest_sgeno_csockf_medium_tcp4, + ttest_sgeno_csockf_medium_tcp6, + + ttest_sgeno_csockf_large_tcp4, + ttest_sgeno_csockf_large_tcp6 + ]. + +ttest_sgeno_csocko_cases() -> + [ + ttest_sgeno_csocko_small_tcp4, + ttest_sgeno_csocko_small_tcp6, + + ttest_sgeno_csocko_medium_tcp4, + ttest_sgeno_csocko_medium_tcp6, + + ttest_sgeno_csocko_large_tcp4, + ttest_sgeno_csocko_large_tcp6 + ]. + +ttest_sgeno_csockt_cases() -> + [ + ttest_sgeno_csockt_small_tcp4, + ttest_sgeno_csockt_small_tcp6, + + ttest_sgeno_csockt_medium_tcp4, + ttest_sgeno_csockt_medium_tcp6, + + ttest_sgeno_csockt_large_tcp4, + ttest_sgeno_csockt_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +ttest_sgent_cases() -> + [ + {group, ttest_sgent_cgen}, + {group, ttest_sgent_csock} + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp +ttest_sgent_cgen_cases() -> + [ + {group, ttest_sgent_cgenf}, + {group, ttest_sgent_cgeno}, + {group, ttest_sgent_cgent} + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = false +ttest_sgent_cgenf_cases() -> + [ + ttest_sgent_cgenf_small_tcp4, + ttest_sgent_cgenf_small_tcp6, + + ttest_sgent_cgenf_medium_tcp4, + ttest_sgent_cgenf_medium_tcp6, + + ttest_sgent_cgenf_large_tcp4, + ttest_sgent_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = once +ttest_sgent_cgeno_cases() -> + [ + ttest_sgent_cgeno_small_tcp4, + ttest_sgent_cgeno_small_tcp6, + + ttest_sgent_cgeno_medium_tcp4, + ttest_sgent_cgeno_medium_tcp6, + + ttest_sgent_cgeno_large_tcp4, + ttest_sgent_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = true +ttest_sgent_cgent_cases() -> + [ + ttest_sgent_cgent_small_tcp4, + ttest_sgent_cgent_small_tcp6, + + ttest_sgent_cgent_medium_tcp4, + ttest_sgent_cgent_medium_tcp6, + + ttest_sgent_cgent_large_tcp4, + ttest_sgent_cgent_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = socket(tcp) +ttest_sgent_csock_cases() -> + [ + {group, ttest_sgent_csockf}, + {group, ttest_sgent_csocko}, + {group, ttest_sgent_csockt} + ]. + +ttest_sgent_csockf_cases() -> + [ + ttest_sgent_csockf_small_tcp4, + ttest_sgent_csockf_small_tcp6, + + ttest_sgent_csockf_medium_tcp4, + ttest_sgent_csockf_medium_tcp6, + + ttest_sgent_csockf_large_tcp4, + ttest_sgent_csockf_large_tcp6 + ]. + +ttest_sgent_csocko_cases() -> + [ + ttest_sgent_csocko_small_tcp4, + ttest_sgent_csocko_small_tcp6, + + ttest_sgent_csocko_medium_tcp4, + ttest_sgent_csocko_medium_tcp6, + + ttest_sgent_csocko_large_tcp4, + ttest_sgent_csocko_large_tcp6 + ]. + +ttest_sgent_csockt_cases() -> + [ + ttest_sgent_csockt_small_tcp4, + ttest_sgent_csockt_small_tcp6, + + ttest_sgent_csockt_medium_tcp4, + ttest_sgent_csockt_medium_tcp6, + + ttest_sgent_csockt_large_tcp4, + ttest_sgent_csockt_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +ttest_ssockf_cases() -> + [ + {group, ttest_ssockf_cgen}, + {group, ttest_ssockf_csock} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp +ttest_ssockf_cgen_cases() -> + [ + {group, ttest_ssockf_cgenf}, + {group, ttest_ssockf_cgeno}, + {group, ttest_ssockf_cgent} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = false +ttest_ssockf_cgenf_cases() -> + [ + ttest_ssockf_cgenf_small_tcp4, + ttest_ssockf_cgenf_small_tcp6, + + ttest_ssockf_cgenf_medium_tcp4, + ttest_ssockf_cgenf_medium_tcp6, + + ttest_ssockf_cgenf_large_tcp4, + ttest_ssockf_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = once +ttest_ssockf_cgeno_cases() -> + [ + ttest_ssockf_cgeno_small_tcp4, + ttest_ssockf_cgeno_small_tcp6, + + ttest_ssockf_cgeno_medium_tcp4, + ttest_ssockf_cgeno_medium_tcp6, + + ttest_ssockf_cgeno_large_tcp4, + ttest_ssockf_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = true +ttest_ssockf_cgent_cases() -> + [ + ttest_ssockf_cgent_small_tcp4, + ttest_ssockf_cgent_small_tcp6, + + ttest_ssockf_cgent_medium_tcp4, + ttest_ssockf_cgent_medium_tcp6, + + ttest_ssockf_cgent_large_tcp4, + ttest_ssockf_cgent_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp) +ttest_ssockf_csock_cases() -> + [ + {group, ttest_ssockf_csockf}, + {group, ttest_ssockf_csocko}, + {group, ttest_ssockf_csockt} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = false +ttest_ssockf_csockf_cases() -> + [ + ttest_ssockf_csockf_small_tcp4, + ttest_ssockf_csockf_small_tcp6, + ttest_ssockf_csockf_small_tcpL, + + ttest_ssockf_csockf_medium_tcp4, + ttest_ssockf_csockf_medium_tcp6, + ttest_ssockf_csockf_medium_tcpL, + + ttest_ssockf_csockf_large_tcp4, + ttest_ssockf_csockf_large_tcp6, + ttest_ssockf_csockf_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = once +ttest_ssockf_csocko_cases() -> + [ + ttest_ssockf_csocko_small_tcp4, + ttest_ssockf_csocko_small_tcp6, + ttest_ssockf_csocko_small_tcpL, + + ttest_ssockf_csocko_medium_tcp4, + ttest_ssockf_csocko_medium_tcp6, + ttest_ssockf_csocko_medium_tcpL, + + ttest_ssockf_csocko_large_tcp4, + ttest_ssockf_csocko_large_tcp6, + ttest_ssockf_csocko_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = true +ttest_ssockf_csockt_cases() -> + [ + ttest_ssockf_csockt_small_tcp4, + ttest_ssockf_csockt_small_tcp6, + ttest_ssockf_csockt_small_tcpL, + + ttest_ssockf_csockt_medium_tcp4, + ttest_ssockf_csockt_medium_tcp6, + ttest_ssockf_csockt_medium_tcpL, + + ttest_ssockf_csockt_large_tcp4, + ttest_ssockf_csockt_large_tcp6, + ttest_ssockf_csockt_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = once +ttest_ssocko_cases() -> + [ + {group, ttest_ssocko_cgen}, + {group, ttest_ssocko_csock} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp +ttest_ssocko_cgen_cases() -> + [ + {group, ttest_ssocko_cgenf}, + {group, ttest_ssocko_cgeno}, + {group, ttest_ssocko_cgent} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = false +ttest_ssocko_cgenf_cases() -> + [ + ttest_ssocko_cgenf_small_tcp4, + ttest_ssocko_cgenf_small_tcp6, + + ttest_ssocko_cgenf_medium_tcp4, + ttest_ssocko_cgenf_medium_tcp6, + + ttest_ssocko_cgenf_large_tcp4, + ttest_ssocko_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = once +ttest_ssocko_cgeno_cases() -> + [ + ttest_ssocko_cgeno_small_tcp4, + ttest_ssocko_cgeno_small_tcp6, + + ttest_ssocko_cgeno_medium_tcp4, + ttest_ssocko_cgeno_medium_tcp6, + + ttest_ssocko_cgeno_large_tcp4, + ttest_ssocko_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = true +ttest_ssocko_cgent_cases() -> + [ + ttest_ssocko_cgent_small_tcp4, + ttest_ssocko_cgent_small_tcp6, + + ttest_ssocko_cgent_medium_tcp4, + ttest_ssocko_cgent_medium_tcp6, + + ttest_ssocko_cgent_large_tcp4, + ttest_ssocko_cgent_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp) +ttest_ssocko_csock_cases() -> + [ + {group, ttest_ssocko_csockf}, + {group, ttest_ssocko_csocko}, + {group, ttest_ssocko_csockt} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = false +ttest_ssocko_csockf_cases() -> + [ + ttest_ssocko_csockf_small_tcp4, + ttest_ssocko_csockf_small_tcp6, + ttest_ssocko_csockf_small_tcpL, + + ttest_ssocko_csockf_medium_tcp4, + ttest_ssocko_csockf_medium_tcp6, + ttest_ssocko_csockf_medium_tcpL, + + ttest_ssocko_csockf_large_tcp4, + ttest_ssocko_csockf_large_tcp6, + ttest_ssocko_csockf_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = once +ttest_ssocko_csocko_cases() -> + [ + ttest_ssocko_csocko_small_tcp4, + ttest_ssocko_csocko_small_tcp6, + ttest_ssocko_csocko_small_tcpL, + + ttest_ssocko_csocko_medium_tcp4, + ttest_ssocko_csocko_medium_tcp6, + ttest_ssocko_csocko_medium_tcpL, + + ttest_ssocko_csocko_large_tcp4, + ttest_ssocko_csocko_large_tcp6, + ttest_ssocko_csocko_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = true +ttest_ssocko_csockt_cases() -> + [ + ttest_ssocko_csockt_small_tcp4, + ttest_ssocko_csockt_small_tcp6, + ttest_ssocko_csockt_small_tcpL, + + ttest_ssocko_csockt_medium_tcp4, + ttest_ssocko_csockt_medium_tcp6, + ttest_ssocko_csockt_medium_tcpL, + + ttest_ssocko_csockt_large_tcp4, + ttest_ssocko_csockt_large_tcp6, + ttest_ssocko_csockt_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = true +ttest_ssockt_cases() -> + [ + {group, ttest_ssockt_cgen}, + {group, ttest_ssockt_csock} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp +ttest_ssockt_cgen_cases() -> + [ + {group, ttest_ssockt_cgenf}, + {group, ttest_ssockt_cgeno}, + {group, ttest_ssockt_cgent} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = false +ttest_ssockt_cgenf_cases() -> + [ + ttest_ssockt_cgenf_small_tcp4, + ttest_ssockt_cgenf_small_tcp6, + + ttest_ssockt_cgenf_medium_tcp4, + ttest_ssockt_cgenf_medium_tcp6, + + ttest_ssockt_cgenf_large_tcp4, + ttest_ssockt_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = once +ttest_ssockt_cgeno_cases() -> + [ + ttest_ssockt_cgeno_small_tcp4, + ttest_ssockt_cgeno_small_tcp6, + + ttest_ssockt_cgeno_medium_tcp4, + ttest_ssockt_cgeno_medium_tcp6, + + ttest_ssockt_cgeno_large_tcp4, + ttest_ssockt_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = true +ttest_ssockt_cgent_cases() -> + [ + ttest_ssockt_cgent_small_tcp4, + ttest_ssockt_cgent_small_tcp6, + + ttest_ssockt_cgent_medium_tcp4, + ttest_ssockt_cgent_medium_tcp6, + + ttest_ssockt_cgent_large_tcp4, + ttest_ssockt_cgent_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp) +ttest_ssockt_csock_cases() -> + [ + {group, ttest_ssockt_csockf}, + {group, ttest_ssockt_csocko}, + {group, ttest_ssockt_csockt} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = false +ttest_ssockt_csockf_cases() -> + [ + ttest_ssockt_csockf_small_tcp4, + ttest_ssockt_csockf_small_tcp6, + ttest_ssockt_csockf_small_tcpL, + + ttest_ssockt_csockf_medium_tcp4, + ttest_ssockt_csockf_medium_tcp6, + ttest_ssockt_csockf_medium_tcpL, + + ttest_ssockt_csockf_large_tcp4, + ttest_ssockt_csockf_large_tcp6, + ttest_ssockt_csockf_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = once +ttest_ssockt_csocko_cases() -> + [ + ttest_ssockt_csocko_small_tcp4, + ttest_ssockt_csocko_small_tcp6, + ttest_ssockt_csocko_small_tcpL, + + ttest_ssockt_csocko_medium_tcp4, + ttest_ssockt_csocko_medium_tcp6, + ttest_ssockt_csocko_medium_tcpL, + + ttest_ssockt_csocko_large_tcp4, + ttest_ssockt_csocko_large_tcp6, + ttest_ssockt_csocko_large_tcpL + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = true +ttest_ssockt_csockt_cases() -> + [ + ttest_ssockt_csockt_small_tcp4, + ttest_ssockt_csockt_small_tcp6, + ttest_ssockt_csockt_small_tcpL, + + ttest_ssockt_csockt_medium_tcp4, + ttest_ssockt_csockt_medium_tcp6, + ttest_ssockt_csockt_medium_tcpL, + + ttest_ssockt_csockt_large_tcp4, + ttest_ssockt_csockt_large_tcp6, + ttest_ssockt_csockt_large_tcpL + ]. + +tickets_cases() -> + [ + {group, otp16359} + ]. + +otp16359_cases() -> + [ + otp16359_maccept_tcp4, + otp16359_maccept_tcp6, + otp16359_maccept_tcpL + ]. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_suite(Config) -> + ct:timetrap(?MINS(2)), + Factor = analyze_and_print_host_info(), + try socket:info() of + #{} -> + case quiet_mode(Config) of + default -> + ?LOGGER:start(), + [{esock_factor, Factor} | Config]; + Quiet -> + ?LOGGER:start(Quiet), + [{esock_factor, Factor}, + {esock_test_quiet, Quiet} | Config] + end + catch + error : notsup -> + {skip, "esock not supported"} + end. + +end_per_suite(_) -> + (catch ?LOGGER:stop()), + ok. + + +init_per_group(GroupName, Config) + when (GroupName =:= sc_remote_close) orelse + (GroupName =:= sc_remote_shutdown) orelse + (GroupName =:= traffic) -> + io:format("init_per_group(~w) -> entry with" + "~n Config: ~p" + "~n", [GroupName, Config]), + %% Maybe we should skip the entire suite for this platform, + %% but for now we just skip these groups, which seem to + %% have problems (slave node start). + %% As stated elsewhere, its not really Fedora 16, but + %% the *really* slow VM that is the issue. + try is_old_fedora16() of + ok -> + Config + catch + throw:{skip, _} = SKIP -> + SKIP + end; +init_per_group(ttest = _GroupName, Config) -> + io:format("init_per_group(~w) -> entry with" + "~n Config: ~p" + "~n", [_GroupName, Config]), + ttest_manager_start(), + case lists:keysearch(esock_test_ttest_runtime, 1, Config) of + {value, _} -> + Config; + false -> + [{esock_test_ttest_runtime, which_ttest_runtime_env()} | Config] + end; +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(ttest = _GroupName, Config) -> + io:format("init_per_group(~w) -> entry with" + "~n Config: ~p" + "~n", [_GroupName, Config]), + ttest_manager_stop(), + lists:keydelete(esock_test_ttest_runtime, 1, Config); +end_per_group(_GroupName, Config) -> + Config. + + +init_per_testcase(_TC, Config) -> + io:format("init_per_testcase(~w) -> entry with" + "~n Config: ~p" + "~n", [_TC, Config]), + %% case quiet_mode(Config) of + %% default -> + %% ?LOGGER:start(); + %% Quiet -> + %% ?LOGGER:start(Quiet) + %% end, + Config. + +end_per_testcase(_TC, Config) -> + %% ?LOGGER:stop(), + Config. + + +quiet_mode(Config) -> + case lists:keysearch(esock_test_quiet, 1, Config) of + {value, {esock_test_quiet, Quiet}} -> + Quiet; + false -> + case os:getenv("ESOCK_TEST_QUIET") of + "true" -> true; + "false" -> false; + _ -> default + end + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API MISC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This is an extremely rudimentary test case, that just tests +%% that we can call the "global" info function and that it returns +%% a non-empty map... + +api_m_info(suite) -> + []; +api_m_info(doc) -> + []; +api_m_info(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_m_info, + fun() -> + ok = api_m_info() + end). + +api_m_info() -> + case socket:info() of + Info when is_map(Info) -> + Sz = maps:size(Info), + if + (Sz > 0) -> + ok; + true -> + ?FAIL(no_info) + end; + Info -> + ?FAIL({invalid_info, Info}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% A simple test case that tests that the global debug can be changed. +%% At the same time, it will test the info function (since it uses it +%% for verification). + +api_m_debug(suite) -> + []; +api_m_debug(doc) -> + []; +api_m_debug(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_m_debug, + fun() -> has_bugfree_gcc() end, + fun() -> + ok = api_m_debug() + end). + +%% For some reason this test case triggers a gcc bug, which causes +%% a segfault, on an ancient Fedora 16 VM. So, check the version of gcc... +%% Not pretty, but the simplest way to skip (without actually testing +%% for the host). +has_bugfree_gcc() -> + has_bugfree_gcc(os:type()). + +%% Make sure we are on linux +has_bugfree_gcc({unix, linux}) -> + has_bugfree_gcc2(string:trim(os:cmd("cat /etc/issue"))); +has_bugfree_gcc(_) -> + ok. + +%% Make sure we are on Fedora 16 +has_bugfree_gcc2("Fedora release 16 " ++ _) -> + has_bugfree_gcc3(os:cmd("gcc --version")); +has_bugfree_gcc2("Welcome to SUSE Linux " ++ _) -> + has_bugfree_gcc4(os:cmd("gcc --version")); +has_bugfree_gcc2(_) -> + ok. + +has_bugfree_gcc3("gcc (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2" ++ _) -> + skip("Buggy GCC"); +has_bugfree_gcc3(_) -> + ok. + +has_bugfree_gcc4("gcc (SUSE Linux) 4.3.2" ++ _) -> + skip("Buggy GCC"); +has_bugfree_gcc4(_) -> + ok. + +api_m_debug() -> + i("get initial info"), + #{debug := D0} = socket:info(), + D1 = not D0, + i("set new debug (~w => ~w)", [D0, D1]), + ok = socket:debug(D1), + i("get updated info (~w)", [D1]), + #{debug := D1} = socket:info(), + D2 = not D1, + i("set new debug (~w => ~w)", [D1, D2]), + ok = socket:debug(D2), + i("get updated info (~w)", [D2]), + #{debug := D2} = socket:info(), + i("ok"), + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API BASIC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and info of an IPv4 UDP (dgram) socket. +%% With some extra checks... +api_b_open_and_info_udp4(suite) -> + []; +api_b_open_and_info_udp4(doc) -> + []; +api_b_open_and_info_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_info_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_info(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and info of an IPv6 UDP (dgram) socket. +%% With some extra checks... +api_b_open_and_info_udp6(suite) -> + []; +api_b_open_and_info_udp6(doc) -> + []; +api_b_open_and_info_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_info_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = api_b_open_and_info(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and info of an IPv4 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_info_tcp4(suite) -> + []; +api_b_open_and_info_tcp4(doc) -> + []; +api_b_open_and_info_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_info_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_info(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and info of an IPv6 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_info_tcp6(suite) -> + []; +api_b_open_and_info_tcp6(doc) -> + []; +api_b_open_and_info_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_info_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => stream, + protocol => tcp}, + ok = api_b_open_and_info(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_open_and_info(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get socket info", + cmd => fun(#{sock := Sock} = State) -> + Info = socket:info(Sock), + ?SEV_IPRINT("Got (some) Info: " + "~n ~p", [Info]), + {ok, State#{info => Info}} + end}, + #{desc => "validate socket info", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol, + info := #{domain := Domain, + type := Type, + protocol := Protocol, + counters := _, + num_readers := 0, + num_writers := 0, + num_acceptors := 0}}) -> + ok; + (#{domain := Domain, + type := Type, + protocol := Protocol, + info := Info}) -> + ?SEV_EPRINT("Unexpected Info: " + "~n (expected) Domain: ~p" + "~n (expected) Type: ~p" + "~n (expected) Protocol: ~p" + "~n ~p", + [Domain, Type, Protocol, Info]), + {error, unexpected_infio} + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 UDP (dgram) socket. +%% With some extra checks... +api_b_open_and_close_udp4(suite) -> + []; +api_b_open_and_close_udp4(doc) -> + []; +api_b_open_and_close_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv6 UDP (dgram) socket. +%% With some extra checks... +api_b_open_and_close_udp6(suite) -> + []; +api_b_open_and_close_udp6(doc) -> + []; +api_b_open_and_close_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_close_tcp4(suite) -> + []; +api_b_open_and_close_tcp4(doc) -> + []; +api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv6 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_close_tcp6(suite) -> + []; +api_b_open_and_close_tcp6(doc) -> + []; +api_b_open_and_close_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an Unix Domain dgram (UDP) socket. +%% With some extra checks... +api_b_open_and_close_udpL(suite) -> + []; +api_b_open_and_close_udpL(doc) -> + []; +api_b_open_and_close_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + type => dgram, + protocol => default}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an Unix Domain stream (TCP) socket. +%% With some extra checks... +api_b_open_and_close_tcpL(suite) -> + []; +api_b_open_and_close_tcpL(doc) -> + []; +api_b_open_and_close_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + type => stream, + protocol => default}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an Unix Domain dgram (UDP) socket. +%% With some extra checks... +api_b_open_and_close_seqpL(suite) -> + []; +api_b_open_and_close_seqpL(doc) -> + []; +api_b_open_and_close_seqpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + type => seqpacket, + protocol => default}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 SCTP (seqpacket) socket. +%% With some extra checks... +api_b_open_and_close_sctp4(suite) -> + []; +api_b_open_and_close_sctp4(doc) -> + []; +api_b_open_and_close_sctp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_sctp4, + fun() -> has_support_sctp() end, + fun() -> + InitState = #{domain => inet, + type => seqpacket, + protocol => sctp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_open_and_close(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = S) -> + Res = socket:open(Domain, Type, Protocol), + {ok, {S, Res}} + end}, + #{desc => "validate open", + cmd => fun({S, {ok, Sock}}) -> + NewS = S#{socket => Sock}, + {ok, NewS}; + ({_, {error, epfnosupport = Reason}}) -> + {skip, Reason}; + ({_, {error, eprotonosupport = Reason}}) -> + {skip, Reason}; + ({_, {error, esocktnosupport = Reason}}) -> + {skip, Reason}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get domain (maybe)", + cmd => fun(#{socket := Sock} = S) -> + Res = socket:getopt(Sock, socket, domain), + {ok, {S, Res}} + end}, + #{desc => "validate domain (maybe)", + cmd => fun({#{domain := Domain} = S, {ok, Domain}}) -> + {ok, S}; + ({#{domain := ExpDomain}, {ok, Domain}}) -> + {error, {unexpected_domain, ExpDomain, Domain}}; + %% Some platforms do not support this option + ({S, {error, einval}}) -> + {ok, S}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get type", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, socket, type), + {ok, {State, Res}} + end}, + #{desc => "validate type", + cmd => fun({#{type := Type} = State, {ok, Type}}) -> + {ok, State}; + ({#{type := ExpType}, {ok, Type}}) -> + {error, {unexpected_type, ExpType, Type}}; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get protocol", + cmd => fun(#{socket := Sock} = State) -> + case socket:is_supported(options, socket, protocol) of + true -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}}; + false -> + {ok, {State, not_supported}} + end + end}, + #{desc => "validate protocol", + cmd => fun({State, not_supported}) -> + ?SEV_IPRINT("socket option 'protocol' " + "not supported"), + {ok, State}; + ({#{protocol := Protocol} = State, {ok, Protocol}}) -> + {ok, State}; + ({#{domain := Domain, + protocol := ExpProtocol}, {ok, Protocol}}) -> + %% On OpenBSD (at least 6.6) something screwy happens + %% when domain = local. + %% It will report a completly different protocol (icmp) + %% but everything still works. So we skip if this happens + %% on OpenBSD... + case os:type() of + {unix, openbsd} when (Domain =:= local) -> + {skip, ?F("Unexpected protocol: ~p instead of ~p", + [Protocol, ExpProtocol])}; + _ -> + {error, {unexpected_protocol, + ExpProtocol, Protocol}} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "get controlling-process", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:getopt(Sock, otp, controlling_process), + {ok, {State, Res}} + end}, + #{desc => "validate controlling-process", + cmd => fun({State, {ok, Pid}}) -> + case self() of + Pid -> + {ok, State}; + _ -> + {error, {unexpected_owner, Pid}} + end; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + #{desc => "close socket", + cmd => fun(#{socket := Sock} = State) -> + Res = socket:close(Sock), + {ok, {State, Res}} + end}, + #{desc => "validate socket close", + cmd => fun({_, ok}) -> + ok; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and (maybe) close an RAW socket. + +api_b_open_and_maybe_close_raw(suite) -> + []; +api_b_open_and_maybe_close_raw(doc) -> + []; +api_b_open_and_maybe_close_raw(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_maybe_close_raw, + fun() -> + InitState = #{domain => inet, + type => raw, + protocol => {raw, 255}}, + ok = do_api_b_open_and_maybe_close_raw(InitState) + end). + +do_api_b_open_and_maybe_close_raw(InitState) -> + Tester = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + ?SEV_IPRINT("try open with:" + "~n Domain: ~p" + "~n Type: ~p" + "~n Protocol: ~p", + [Domain, Type, Protocol]), + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, Reason} when (Reason =:= eperm) orelse + (Reason =:= eacces) -> + ?SEV_IPRINT("not allowed (~w) => SKIP", + [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("open failed:" + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{socket := Sock} = State) -> + ?SEV_IPRINT("try socket close"), + case socket:close(Sock) of + ok -> + ?SEV_IPRINT("socket closed"), + {ok, maps:remote(sock, State)}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("close failed:" + "~n Reason: ~p", [Reason]), + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Tester, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom.. +api_b_sendto_and_recvfrom_udp4(suite) -> + []; +api_b_sendto_and_recvfrom_udp4(doc) -> + []; +api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendto_and_recvfrom_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom. +api_b_sendto_and_recvfrom_udpL(suite) -> + []; +api_b_sendto_and_recvfrom_udpL(doc) -> + []; +api_b_sendto_and_recvfrom_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendto_and_recvfrom_udpL, + fun() -> + has_support_unix_domain_socket(), + unix_domain_socket_host_cond() + end, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + InitState = #{domain => local, + proto => default, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket +%% using sendmsg and recvmsg. +api_b_sendmsg_and_recvmsg_udp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_udp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% We need tests for this, + %% but this is not the place it. + %% CMsgHdr = #{level => ip, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + %% We have some issues on old darwing... + %% socket:setopt(Sock, otp, debug, true), + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + %% socket:setopt(Sock, otp, debug, false), + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket +%% using sendmsg and recvmsg. +api_b_sendmsg_and_recvmsg_udpL(suite) -> + []; +api_b_sendmsg_and_recvmsg_udpL(doc) -> + []; +api_b_sendmsg_and_recvmsg_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendmsg_and_recvmsg_udpL, + fun() -> + has_support_unix_domain_socket(), + unix_domain_socket_host_cond() + end, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% We need tests for this, + %% but this is not the place it. + %% CMsgHdr = #{level => ip, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + %% We have some issues on old darwing... + %% socket:setopt(Sock, otp, debug, true), + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + %% socket:setopt(Sock, otp, debug, false), + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => local, + proto => default, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src socket", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("src bound (to ~p)", [Port]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst socket", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("src bound (to ~p)", [Port]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, ?BASIC_REQ}} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + case Recv(Sock) of + {ok, {Dst, ?BASIC_REP}} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "close src socket", + cmd => fun(#{domain := local, + sock_src := Sock, + lsa_src := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> maps:remove(lsa_src, State) end, + fun() -> State end), + {ok, maps:remove(sock_src, State1)}; + (#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{domain := local, + sock_dst := Sock, + lsa_dst := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> maps:remove(lsa_dst, State) end, + fun() -> State end), + {ok, maps:remove(sock_dst, State1)}; + (#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an IPv4 TCP (stream) socket. +api_b_send_and_recv_tcp4(suite) -> + []; +api_b_send_and_recv_tcp4(doc) -> + []; +api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_b_send_and_recv_tcp4, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => inet, + type => stream, + proto => tcp, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_conn(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an Unix Domain (stream) socket (TCP). +api_b_send_and_recv_tcpL(suite) -> + []; +api_b_send_and_recv_tcpL(doc) -> + []; +api_b_send_and_recv_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_b_send_and_recv_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => local, + type => stream, + proto => default, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_conn(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an Unix Domain seqpacket socket. +api_b_send_and_recv_seqpL(suite) -> + []; +api_b_send_and_recv_seqpL(doc) -> + []; +api_b_send_and_recv_seqpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => local, + type => seqpacket, + proto => default, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_conn(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an IPv4 TCP (stream) socket. +api_b_sendmsg_and_recvmsg_tcp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_tcp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_b_sendmsg_and_recvmsg_tcp4, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + type => stream, + proto => tcp, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_conn(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an Unix Domain (stream) socket (TCP). +api_b_sendmsg_and_recvmsg_tcpL(suite) -> + []; +api_b_sendmsg_and_recvmsg_tcpL(doc) -> + []; +api_b_sendmsg_and_recvmsg_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_b_sendmsg_and_recvmsg_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + %% On some platforms, the address + %% is *not* provided (e.g. FreeBSD) + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + %% On some platforms, the address + %% *is* provided (e.g. linux) + {ok, #{addr := #{family := local}, + iov := [Data]}} -> + socket:setopt(Sock, + otp, + debug, + false), + {ok, Data}; + {error, _} = ERROR -> + socket:setopt(Sock, + otp, + debug, + false), + ERROR + end + end, + InitState = #{domain => local, + type => stream, + proto => default, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_conn(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an Unix Domain (stream) socket (TCP). +api_b_sendmsg_and_recvmsg_seqpL(suite) -> + []; +api_b_sendmsg_and_recvmsg_seqpL(doc) -> + []; +api_b_sendmsg_and_recvmsg_seqpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Send = + fun(Sock, Data) -> + MsgHdr = + #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + %% On some platforms, the address + %% is *not* provided (e.g. FreeBSD) + {ok, + #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + %% On some platforms, the address + %% *is* provided (e.g. linux) + {ok, + #{addr := #{family := local}, + iov := [Data]}} -> + socket:setopt( + Sock, otp, debug, false), + {ok, Data}; + {error, _} = ERROR -> + socket:setopt( + Sock, otp, debug, false), + ERROR + end + end, + InitState = + #{domain => local, + type => seqpacket, + proto => default, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_conn(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_conn(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, + type := Type, + proto := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, eprotonosupport = Reason} -> + {skip, Reason}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, lport := Port}) -> + %% This is actually not used for unix domain socket + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await (recv) request", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + proto := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + #{desc => "order client to continue (with send request)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + i("await evaluator(s)"), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 SCTP (seqpacket) socket +%% using sendmsg and recvmsg. +api_b_sendmsg_and_recvmsg_sctp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_sctp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_sctp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendmsg_and_recvmsg_sctp4, + fun() -> + has_support_sctp(), + not_yet_implemented() + end, + fun() -> + Send = fun(Sock, Data) -> + %% CMsgHdr = #{level => sctp, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], + MsgHdr = #{%% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + %% We have some issues on old darwing... + %% socket:setopt(Sock, otp, debug, true), + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + %% socket:setopt(Sock, otp, debug, false), + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => sctp, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_sctp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_sctp(_InitState) -> +%% Seq = +%% [ +%% #{desc => "local address", +%% cmd => fun(#{domain := Domain} = State) -> +%% LSA = which_local_socket_addr(Domain), +%% {ok, State#{lsa_src => LSA, +%% lsa_dst => LSA}} +%% end}, + +%% #{desc => "open src socket", +%% cmd => fun(#{domain := Domain, +%% proto := Proto} = State) -> +%% Sock = sock_open(Domain, seqpacket, Proto), +%% {ok, State#{sock_src => Sock}} +%% end}, +%% #{desc => "bind src", +%% cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> +%% case socket:bind(Sock, LSA) of +%% {ok, _Port} -> +%% ?SEV_IPRINT("src bound"), +%% ok; +%% {error, Reason} = ERROR -> +%% ?SEV_EPRINT("src bind failed: ~p", [Reason]), +%% ERROR +%% end +%% end}, +%% #{desc => "sockname src socket", +%% cmd => fun(#{sock_src := Sock} = State) -> +%% SASrc = sock_sockname(Sock), +%% ?SEV_IPRINT("src sockaddr: " +%% "~n ~p", [SASrc]), +%% {ok, State#{sa_src => SASrc}} +%% end}, + +%% #{desc => "open dst socket", +%% cmd => fun(#{domain := Domain, +%% proto := Proto} = State) -> +%% Sock = sock_open(Domain, seqpacket, Proto), +%% {ok, State#{sock_dst => Sock}} +%% end}, +%% #{desc => "bind dst", +%% cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> +%% case socket:bind(Sock, LSA) of +%% {ok, _Port} -> +%% ?SEV_IPRINT("src bound"), +%% ok; +%% {error, Reason} = ERROR -> +%% ?SEV_EPRINT("src bind failed: ~p", [Reason]), +%% ERROR +%% end +%% end}, +%% #{desc => "sockname dst socket", +%% cmd => fun(#{sock_dst := Sock} = State) -> +%% SADst = sock_sockname(Sock), +%% ?SEV_IPRINT("dst sockaddr: " +%% "~n ~p", [SADst]), +%% {ok, State#{sa_dst => SADst}} +%% end}, +%% #{desc => "send req (to dst)", +%% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> +%% Send(Sock, ?BASIC_REQ, Dst) +%% end}, +%% #{desc => "recv req (from src)", +%% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> +%% case Recv(Sock) of +%% {ok, {Src, ?BASIC_REQ}} -> +%% ok; +%% {ok, UnexpData} -> +%% {error, {unexpected_data, UnexpData}}; +%% {error, _} = ERROR -> +%% %% At the moment there is no way to get +%% %% status or state for the socket... +%% ERROR +%% end +%% end}, +%% #{desc => "send rep (to src)", +%% cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> +%% Send(Sock, ?BASIC_REP, Src) +%% end}, +%% #{desc => "recv rep (from dst)", +%% cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> +%% case Recv(Sock) of +%% {ok, {Dst, ?BASIC_REP}} -> +%% ok; +%% {ok, UnexpData} -> +%% {error, {unexpected_data, UnexpData}}; +%% {error, _} = ERROR -> +%% %% At the moment there is no way to get +%% %% status or state for the socket... +%% ERROR +%% end +%% end}, +%% #{desc => "close src socket", +%% cmd => fun(#{sock_src := Sock} = State) -> +%% ok = socket:close(Sock), +%% {ok, maps:remove(sock_src, State)} +%% end}, +%% #{desc => "close dst socket", +%% cmd => fun(#{sock_dst := Sock} = State) -> +%% ok = socket:close(Sock), +%% {ok, maps:remove(sock_dst, State)} +%% end}, + +%% %% *** We are done *** +%% ?SEV_FINISH_NORMAL +%% ], +%% Evaluator = ?SEV_START("tester", Seq, InitState), +%% ok = ?SEV_AWAIT_FINISH([Evaluator]). + +%% process_flag(trap_exit, true), +%% ServerSeq = +%% [ +%% %% *** Wait for start order *** +%% #{desc => "await start (from tester)", +%% cmd => fun(State) -> +%% Tester = ?SEV_AWAIT_START(), +%% {ok, State#{tester => Tester}} +%% end}, +%% #{desc => "monitor tester", +%% cmd => fun(#{tester := Tester}) -> +%% _MRef = erlang:monitor(process, Tester), +%% ok +%% end}, + +%% %% *** Init part *** +%% #{desc => "which local address", +%% cmd => fun(#{domain := Domain} = State) -> +%% LSA = which_local_socket_addr(Domain), +%% {ok, State#{lsa => LSA}} +%% end}, +%% #{desc => "create (listen) socket", +%% cmd => fun(#{domain := Domain, +%% proto := Proto} = State) -> +%% case socket:open(Domain, seqpacket, Proto) of +%% {ok, Sock} -> +%% {ok, State#{lsock => Sock}}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "bind to local address", +%% cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> +%% case socket:bind(LSock, LSA) of +%% {ok, Port} -> +%% ?SEV_IPRINT("bound to port: ~w", [Port]), +%% {ok, State#{lport => Port}}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "make listen socket", +%% cmd => fun(#{lsock := LSock}) -> +%% socket:listen(LSock) +%% end}, +%% #{desc => "announce ready (init)", +%% cmd => fun(#{tester := Tester, lport := Port}) -> +%% ?SEV_ANNOUNCE_READY(Tester, init, Port), +%% ok +%% end}, + +%% %% The actual test +%% #{desc => "await continue (accept)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_AWAIT_CONTINUE(Tester, tester, accept) +%% end}, +%% #{desc => "accepting (await connection) FAKE", +%% cmd => fun(#{lsock := LSock} = State) -> +%% {ok, State#{csock => LSock}} +%% end}, +%% %% #{desc => "accepting (await connection)", +%% %% cmd => fun(#{lsock := LSock} = State) -> +%% %% socket:setopt(LSock, otp, debug, true), +%% %% case socket:accept(LSock) of +%% %% {ok, Sock} -> +%% %% socket:setopt(LSock, otp, debug, false), +%% %% ?SEV_IPRINT("accepted: ~n ~p", [Sock]), +%% %% {ok, State#{csock => Sock}}; +%% %% {error, Reason} = ERROR -> +%% %% socket:setopt(LSock, otp, debug, false), +%% %% ?SEV_EPRINT("Failed accepting: " +%% %% "~n ~p", [Reason]), +%% %% ERROR +%% %% end +%% %% end}, +%% #{desc => "announce ready (accept)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, accept), +%% ok +%% end}, +%% #{desc => "await (recv) request", +%% cmd => fun(#{csock := Sock, recv := Recv}) -> +%% case Recv(Sock) of +%% {ok, ?BASIC_REQ} -> +%% ok; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "announce ready (recv request)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, recv_req), +%% ok +%% end}, +%% #{desc => "await continue (with send reply)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) +%% end}, +%% #{desc => "send reply", +%% cmd => fun(#{csock := Sock, send := Send}) -> +%% Send(Sock, ?BASIC_REP) +%% end}, +%% #{desc => "announce ready (send reply)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, send_reply), +%% ok +%% end}, + +%% %% *** Termination *** +%% #{desc => "await terminate", +%% cmd => fun(#{tester := Tester} = State) -> +%% case ?SEV_AWAIT_TERMINATE(Tester, tester) of +%% ok -> +%% {ok, maps:remove(tester, State)}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "close connection socket", +%% cmd => fun(#{csock := Sock} = State) -> +%% ok = socket:close(Sock), +%% {ok, maps:remove(csock, State)} +%% end}, +%% #{desc => "close listen socket", +%% cmd => fun(#{lsock := LSock} = State) -> +%% case socket:close(LSock) of +%% ok -> +%% {ok, maps:remove(lsock, State)}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, + +%% %% *** We are done *** +%% ?SEV_FINISH_NORMAL +%% ], + +%% ClientSeq = +%% [ +%% %% *** Wait for start order *** +%% #{desc => "await start (from tester)", +%% cmd => fun(State) -> +%% {Tester, Port} = ?SEV_AWAIT_START(), +%% {ok, State#{tester => Tester, server_port => Port}} +%% end}, +%% #{desc => "monitor tester", +%% cmd => fun(#{tester := Tester}) -> +%% _MRef = erlang:monitor(process, Tester), +%% ok +%% end}, + +%% %% *** The init part *** +%% #{desc => "which server (local) address", +%% cmd => fun(#{domain := Domain, server_port := Port} = State) -> +%% LSA = which_local_socket_addr(Domain), +%% SSA = LSA#{port => Port}, +%% {ok, State#{local_sa => LSA, server_sa => SSA}} +%% end}, +%% #{desc => "create socket", +%% cmd => fun(#{domain := Domain, +%% proto := Proto} = State) -> +%% case socket:open(Domain, seqpacket, Proto) of +%% {ok, Sock} -> +%% {ok, State#{sock => Sock}}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "bind to local address", +%% cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> +%% case socket:bind(Sock, LSA) of +%% {ok, _Port} -> +%% ok; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "announce ready (init)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, init), +%% ok +%% end}, + +%% %% *** The actual test *** +%% #{desc => "await continue (connect)", +%% cmd => fun(#{tester := Tester} = _State) -> +%% ?SEV_AWAIT_CONTINUE(Tester, tester, connect) +%% end}, +%% #{desc => "connect to server", +%% cmd => fun(#{sock := Sock, server_sa := SSA}) -> +%% case socket:connect(Sock, SSA) of +%% ok -> +%% ?SEV_IPRINT("connected"), +%% ok; +%% {error, Reason} = ERROR -> +%% ?SEV_EPRINT("Failed connect: " +%% "~n ~p", [Reason]), +%% ERROR +%% end +%% end}, +%% #{desc => "announce ready (connect)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, connect), +%% ok +%% end}, +%% #{desc => "await continue (send request)", +%% cmd => fun(#{tester := Tester} = _State) -> +%% ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) +%% end}, +%% #{desc => "send request (to server)", +%% cmd => fun(#{sock := Sock, send := Send}) -> +%% Send(Sock, ?BASIC_REQ) +%% end}, +%% #{desc => "announce ready (send request)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, send_req), +%% ok +%% end}, +%% #{desc => "await recv reply (from server)", +%% cmd => fun(#{sock := Sock, recv := Recv}) -> +%% {ok, ?BASIC_REP} = Recv(Sock), +%% ok +%% end}, +%% #{desc => "announce ready (recv reply)", +%% cmd => fun(#{tester := Tester}) -> +%% ?SEV_ANNOUNCE_READY(Tester, recv_reply), +%% ok +%% end}, + +%% %% *** Termination *** +%% #{desc => "await terminate", +%% cmd => fun(#{tester := Tester} = State) -> +%% case ?SEV_AWAIT_TERMINATE(Tester, tester) of +%% ok -> +%% {ok, maps:remove(tester, State)}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end}, +%% #{desc => "close socket", +%% cmd => fun(#{sock := Sock} = State) -> +%% ok = socket:close(Sock), +%% {ok, maps:remove(sock, State)} +%% end}, + +%% %% *** We are done *** +%% ?SEV_FINISH_NORMAL +%% ], + +%% TesterSeq = +%% [ +%% %% *** Init part *** +%% #{desc => "monitor server", +%% cmd => fun(#{server := Pid} = _State) -> +%% _MRef = erlang:monitor(process, Pid), +%% ok +%% end}, +%% #{desc => "monitor client", +%% cmd => fun(#{client := Pid} = _State) -> +%% _MRef = erlang:monitor(process, Pid), +%% ok +%% end}, + +%% %% Start the server +%% #{desc => "order server start", +%% cmd => fun(#{server := Pid} = _State) -> +%% ?SEV_ANNOUNCE_START(Pid), +%% ok +%% end}, +%% #{desc => "await server ready (init)", +%% cmd => fun(#{server := Pid} = State) -> +%% {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), +%% {ok, State#{server_port => Port}} +%% end}, + +%% %% Start the client +%% #{desc => "order client start", +%% cmd => fun(#{client := Pid, server_port := Port} = _State) -> +%% ?SEV_ANNOUNCE_START(Pid, Port), +%% ok +%% end}, +%% #{desc => "await client ready (init)", +%% cmd => fun(#{client := Pid} = _State) -> +%% ok = ?SEV_AWAIT_READY(Pid, client, init) +%% end}, + +%% %% *** The actual test *** +%% #{desc => "order server to continue (with accept)", +%% cmd => fun(#{server := Server} = _State) -> +%% ?SEV_ANNOUNCE_CONTINUE(Server, accept), +%% ok +%% end}, +%% ?SEV_SLEEP(?SECS(1)), +%% #{desc => "order client to continue (with connect)", +%% cmd => fun(#{client := Client} = _State) -> +%% ?SEV_ANNOUNCE_CONTINUE(Client, connect), +%% ok +%% end}, +%% #{desc => "await client ready (connect)", +%% cmd => fun(#{client := Client} = _State) -> +%% ?SEV_AWAIT_READY(Client, client, connect) +%% end}, +%% #{desc => "await server ready (accept)", +%% cmd => fun(#{server := Server} = _State) -> +%% ?SEV_AWAIT_READY(Server, server, accept) +%% end}, +%% #{desc => "order client to continue (with send request)", +%% cmd => fun(#{client := Client} = _State) -> +%% ?SEV_ANNOUNCE_CONTINUE(Client, send_req), +%% ok +%% end}, +%% #{desc => "await client ready (with send request)", +%% cmd => fun(#{client := Client} = _State) -> +%% ?SEV_AWAIT_READY(Client, client, send_req) +%% end}, +%% #{desc => "await server ready (request recv)", +%% cmd => fun(#{server := Server} = _State) -> +%% ?SEV_AWAIT_READY(Server, server, recv_req) +%% end}, +%% #{desc => "order server to continue (with send reply)", +%% cmd => fun(#{server := Server} = _State) -> +%% ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), +%% ok +%% end}, +%% #{desc => "await server ready (with reply sent)", +%% cmd => fun(#{server := Server} = _State) -> +%% ?SEV_AWAIT_READY(Server, server, send_reply) +%% end}, +%% #{desc => "await client ready (reply recv)", +%% cmd => fun(#{client := Client} = _State) -> +%% ?SEV_AWAIT_READY(Client, client, recv_reply) +%% end}, + + +%% %% *** Termination *** +%% #{desc => "order client to terminate", +%% cmd => fun(#{client := Client} = _State) -> +%% ?SEV_ANNOUNCE_TERMINATE(Client), +%% ok +%% end}, +%% #{desc => "await client termination", +%% cmd => fun(#{client := Client} = State) -> +%% ?SEV_AWAIT_TERMINATION(Client), +%% State1 = maps:remove(client, State), +%% {ok, State1} +%% end}, +%% #{desc => "order server to terminate", +%% cmd => fun(#{server := Server} = _State) -> +%% ?SEV_ANNOUNCE_TERMINATE(Server), +%% ok +%% end}, +%% #{desc => "await server termination", +%% cmd => fun(#{server := Server} = State) -> +%% ?SEV_AWAIT_TERMINATION(Server), +%% State1 = maps:remove(server, State), +%% {ok, State1} +%% end}, + +%% %% *** We are done *** +%% ?SEV_FINISH_NORMAL +%% ], + +%% i("start server evaluator"), +%% Server = ?SEV_START("server", ServerSeq, InitState), + +%% i("start client evaluator"), +%% Client = ?SEV_START("client", ClientSeq, InitState), +%% i("await evaluator(s)"), + +%% i("start tester evaluator"), +%% TesterInitState = #{server => Server#ev.pid, +%% client => Client#ev.pid}, +%% Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + +%% ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API FROM FD %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket. +%% With some extra checks... +%% IPv4 +%% Without dup +api_ffd_open_wod_and_info_udp4(suite) -> + []; +api_ffd_open_wod_and_info_udp4(doc) -> + []; +api_ffd_open_wod_and_info_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_wod_and_info_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp, + dup => false}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv6 UDP (dgram) socket. +%% With some extra checks... +%% IPv6 +%% Without dup +api_ffd_open_wod_and_info_udp6(suite) -> + []; +api_ffd_open_wod_and_info_udp6(doc) -> + []; +api_ffd_open_wod_and_info_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_wod_and_info_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + protocol => udp, + dup => false}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket. +%% With some extra checks... +%% IPv4 +%% With dup +api_ffd_open_wd_and_info_udp4(suite) -> + []; +api_ffd_open_wd_and_info_udp4(doc) -> + []; +api_ffd_open_wd_and_info_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_wd_open_and_info_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp, + dup => true}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv4 UDP (dgram) socket. +%% With some extra checks... +%% IPv6 +%% With dup +api_ffd_open_wd_and_info_udp6(suite) -> + []; +api_ffd_open_wd_and_info_udp6(doc) -> + []; +api_ffd_open_wd_and_info_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_wd_open_and_info_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp, + dup => true}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv4 TCP (stream) socket. +%% With some extra checks... +%% IPv6 +%% Without dup +api_ffd_open_wod_and_info_tcp4(suite) -> + []; +api_ffd_open_wod_and_info_tcp4(doc) -> + []; +api_ffd_open_wod_and_info_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_wod_and_info_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp, + dup => false}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv6 TCP (stream) socket. +%% With some extra checks... +%% IPv6 +%% Without dup +api_ffd_open_wod_and_info_tcp6(suite) -> + []; +api_ffd_open_wod_and_info_tcp6(doc) -> + []; +api_ffd_open_wod_and_info_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_wod_and_info_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + dup => false}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv4 TCP (stream) socket. +%% With some extra checks... +%% IPv6 +%% With dup +api_ffd_open_wd_and_info_tcp4(suite) -> + []; +api_ffd_open_wd_and_info_tcp4(doc) -> + []; +api_ffd_open_wd_and_info_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_wd_and_info_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp, + dup => true}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) a socket from an already existing +%% file descriptor (FD) and info of an IPv6 TCP (stream) socket. +%% With some extra checks... +%% IPv6 +%% With dup +api_ffd_open_wd_and_info_tcp6(suite) -> + []; +api_ffd_open_wd_and_info_tcp6(doc) -> + []; +api_ffd_open_wd_and_info_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_wd_and_info_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + dup => true}, + ok = api_ffd_open_and_info(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_ffd_open_and_info(InitState) -> + Seq = + [ + #{desc => "open", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock1} -> + {ok, State#{sock1 => Sock1}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get socket (1) FD", + cmd => fun(#{sock1 := Sock1} = State) -> + case socket:getopt(Sock1, otp, fd) of + {ok, FD} -> + ?SEV_IPRINT("FD: ~w", [FD]), + {ok, State#{fd => FD}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed get FD: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "check if we need to provide protocol or not", + cmd => fun(#{sock1 := Sock1} = State) -> + case socket:getopt(Sock1, socket, protocol) of + {ok, _} -> + ?SEV_IPRINT("protocol accessible"), + {ok, State#{provide_protocol => false}}; + {error, Reason} -> + ?SEV_IPRINT("failed get protocol: " + "~n ~p", [Reason]), + {ok, State#{provide_protocol => true}} + end + end}, + #{desc => "open with FD", + cmd => fun(#{fd := FD, + dup := DUP, + provide_protocol := true, + protocol := Protocol} = State) -> + case socket:open(FD, #{dup => DUP, + protocol => Protocol}) of + {ok, Sock2} -> + ?SEV_IPRINT("socket 2 open"), + {ok, State#{sock2 => Sock2}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed open socket with FD (~w): " + "~n ~p", [FD, Reason]), + ERROR + end; + (#{fd := FD, + dup := DUP, + provide_protocol := false} = State) -> + case socket:open(FD, #{dup => DUP}) of + {ok, Sock2} -> + ?SEV_IPRINT("socket 2 open"), + {ok, State#{sock2 => Sock2}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed open socket with FD (~w): " + "~n ~p", [FD, Reason]), + ERROR + end + end}, + #{desc => "get socket (1) info", + cmd => fun(#{sock1 := Sock} = State) -> + %% socket:setopt(Sock, otp, debug, true), + Info = socket:info(Sock), + %% socket:setopt(Sock, otp, debug, false), + ?SEV_IPRINT("Got Info: " + "~n ~p", [Info]), + {ok, State#{info1 => Info}} + end}, + #{desc => "get socket (2) info", + cmd => fun(#{sock2 := Sock} = State) -> + %% socket:setopt(Sock, otp, debug, true), + Info = socket:info(Sock), + %% socket:setopt(Sock, otp, debug, false), + ?SEV_IPRINT("Got Info: " + "~n ~p", [Info]), + {ok, State#{info2 => Info}} + end}, + #{desc => "validate socket (1) info", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol, + info1 := #{domain := Domain, + type := Type, + protocol := Protocol, + ctype := normal, + counters := _, + num_readers := 0, + num_writers := 0, + num_acceptors := 0}}) -> + ok; + (#{domain := Domain, + type := Type, + protocol := Protocol, + info := Info}) -> + ?SEV_EPRINT("Unexpected Info for socket 1: " + "~n (expected) Domain: ~p" + "~n (expected) Type: ~p" + "~n (expected) Protocol: ~p" + "~n (expected) Create Type: ~p" + "~n ~p", + [Domain, Type, Protocol, normal, Info]), + {error, unexpected_infio} + end}, + #{desc => "validate socket (2) info", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol, + fd := _FD, + dup := false, + info2 := #{domain := Domain, + type := Type, + protocol := Protocol, + ctype := fromfd, + counters := _, + num_readers := 0, + num_writers := 0, + num_acceptors := 0}}) -> + ok; + (#{domain := Domain, + type := Type, + protocol := Protocol, + fd := _FD, + dup := false, + info := Info}) -> + ?SEV_EPRINT("Unexpected Info for socket 2: " + "~n (expected) Domain: ~p" + "~n (expected) Type: ~p" + "~n (expected) Protocol: ~p" + "~n (expected) Create Type: ~p" + "~n ~p", + [Domain, Type, Protocol, + fromfd, Info]), + {error, unexpected_info}; + (#{domain := Domain, + type := Type, + protocol := Protocol, + fd := FD, + dup := true, + info2 := #{domain := Domain, + type := Type, + protocol := Protocol, + ctype := {fromfd, FD}, + counters := _, + num_readers := 0, + num_writers := 0, + num_acceptors := 0}}) -> + ok; + (#{domain := Domain, + type := Type, + protocol := Protocol, + fd := FD, + dup := true, + info := Info}) -> + ?SEV_EPRINT("Unexpected Info for socket 2: " + "~n (expected) Domain: ~p" + "~n (expected) Type: ~p" + "~n (expected) Protocol: ~p" + "~n (expected) Create Type: ~p" + "~n ~p", + [Domain, Type, Protocol, + {fromfd, FD}, Info]), + {error, unexpected_info} + end}, + #{desc => "close socket (1)", + cmd => fun(#{sock1 := Sock} = _State) -> + socket:close(Sock) + end}, + #{desc => "close socket (2)", + cmd => fun(#{sock2 := Sock} = _State) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1) and then create another socket (2) from +%% its file descriptor *without* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv4 UDP (dgram) socket. +%% +%% <WARNING> +%% +%% This is *not* how its intended to be used. +%% That an erlang process creating a socket and then handing over the +%% file descriptor to another erlang process. *But* its a convient way +%% to test it! +%% +%% </WARNING> +%% +api_ffd_open_and_open_wod_and_send_udp4(suite) -> + []; +api_ffd_open_and_open_wod_and_send_udp4(doc) -> + []; +api_ffd_open_and_open_wod_and_send_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_and_open_wod_and_send_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp, + dup => false}, + ok = api_ffd_open_and_open_and_send_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1) and then create another socket (2) from +%% its file descriptor *without* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv6 UDP (dgram) socket. +%% +%% <WARNING> +%% +%% This is *not* how its intended to be used. +%% That an erlang process creating a socket and then handing over the +%% file descriptor to another erlang process. *But* its a convient way +%% to test it! +%% +%% </WARNING> +%% +api_ffd_open_and_open_wod_and_send_udp6(suite) -> + []; +api_ffd_open_and_open_wod_and_send_udp6(doc) -> + []; +api_ffd_open_and_open_wod_and_send_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_and_open_wod_and_send_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + protocol => udp, + dup => false}, + ok = api_ffd_open_and_open_and_send_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1) and then create another socket (2) from +%% its file descriptor *with* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv4 UDP (dgram) socket. +%% +api_ffd_open_and_open_wd_and_send_udp4(suite) -> + []; +api_ffd_open_and_open_wd_and_send_udp4(doc) -> + []; +api_ffd_open_and_open_wd_and_send_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_and_open_wd_and_send_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp, + dup => true}, + ok = api_ffd_open_and_open_and_send_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1) and then create another socket (2) from +%% its file descriptor *with* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv6 UDP (dgram) socket. +%% +api_ffd_open_and_open_wd_and_send_udp6(suite) -> + []; +api_ffd_open_and_open_wd_and_send_udp6(doc) -> + []; +api_ffd_open_and_open_wd_and_send_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_and_open_wd_and_send_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + protocol => udp, + dup => true}, + ok = api_ffd_open_and_open_and_send_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_ffd_open_and_open_and_send_udp(InitState) -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + api_ffd_open_and_open_and_send_udp2(InitState#{send => Send, + recv => Recv}). + +api_ffd_open_and_open_and_send_udp2(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + #{desc => "await request 1 (recv)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, {Source, ?BASIC_REQ}} -> + ?SEV_IPRINT("received request (1) from: " + "~n ~p", [Source]), + {ok, State#{source => Source}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 1 (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue 1 (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply 1", + cmd => fun(#{sock := Sock, send := Send, source := Source}) -> + Send(Sock, ?BASIC_REP, Source) + end}, + #{desc => "announce ready 1 (send reply)", + cmd => fun(#{tester := Tester} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + {ok, maps:remove(source, State)} + end}, + + #{desc => "await request 2 (recv)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, {Source, ?BASIC_REQ}} -> + ?SEV_IPRINT("received request (2) from: " + "~n ~p", [Source]), + {ok, State#{source => Source}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 2 (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue 2 (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply 2", + cmd => fun(#{sock := Sock, send := Send, source := Source}) -> + Send(Sock, ?BASIC_REP, Source) + end}, + #{desc => "announce ready 2 (send reply)", + cmd => fun(#{tester := Tester} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + {ok, maps:remove(source, State)} + end}, + + #{desc => "await request 3 (recv)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, {Source, ?BASIC_REQ}} -> + ?SEV_IPRINT("received request (2) from: " + "~n ~p", [Source]), + {ok, State#{source => Source}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 3 (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue 3 (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply 3", + cmd => fun(#{sock := Sock, send := Send, source := Source}) -> + Send(Sock, ?BASIC_REP, Source) + end}, + #{desc => "announce ready 3 (send reply)", + cmd => fun(#{tester := Tester} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + {ok, maps:remove(source, State)} + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Client1Seq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get socket FD", + cmd => fun(#{sock := Sock} = State) -> + case socket:getopt(Sock, otp, fd) of + {ok, FD} -> + ?SEV_IPRINT("FD: ~w", [FD]), + {ok, State#{fd => FD}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed get FD: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, + fd := FD}) -> + ?SEV_ANNOUNCE_READY(Tester, init, FD), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (send request 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 1 (to server)", + cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) -> + Send(Sock, ?BASIC_REQ, SSA) + end}, + #{desc => "announce ready (send request 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 1 (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, {_, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + #{desc => "await continue (send request 3)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 3 (to server)", + cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) -> + Send(Sock, ?BASIC_REQ, SSA) + end}, + #{desc => "announce ready (send request 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 3 (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, {_, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Client2Seq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, {Port, FD}} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_port => Port, + fd => FD}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{fd := FD, + dup := DUP} = State) -> + case socket:open(FD, #{dup => DUP}) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (send request 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 2 (to server)", + cmd => fun(#{sock := Sock, send := Send, server_sa := SSA}) -> + Send(Sock, ?BASIC_REQ, SSA) + end}, + #{desc => "announce ready (send request 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 2 (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, {_, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client 1 + #{desc => "order client 1 start", + cmd => fun(#{client1 := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = State) -> + case ?SEV_AWAIT_READY(Pid, client1, init) of + {ok, FD} -> + {ok, State#{fd => FD}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Client 1 init error: " + "~n ~p", [Reason]), + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + %% Start the client 2 + #{desc => "order client 2 start", + cmd => fun(#{client2 := Pid, + server_port := Port, + fd := FD} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {Port, FD}), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client2, init) + end}, + + %% *** The actual test *** + + #{desc => "order client 1 to continue (with send request 1)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client 1 ready (with send request 1)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, send_req) + end}, + #{desc => "await server ready (request recv 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply 1 sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client 1 ready (reply recv 1)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, recv_reply) + end}, + + + #{desc => "order client 2 to continue (with send request 2)", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client 2 ready (with send request 2)", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client2, send_req) + end}, + #{desc => "await server ready (request recv 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply 2 sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client 2 ready (reply recv 2)", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client2, recv_reply) + end}, + + + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client2, State), + {ok, State1} + end}, + + + #{desc => "order client 1 to continue (with send request 3)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client 1 ready (with send request 3)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, send_req) + end}, + #{desc => "await server ready (request recv 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply 3 sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client 1 ready (reply recv 3)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client1, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, maps:remove(dup, InitState)), + + i("start (socket origin) client 1 evaluator"), + Client1 = ?SEV_START("client-1", Client1Seq, maps:remove(dup, InitState)), + i("await evaluator(s)"), + + i("start client 2 evaluator"), + Client2 = ?SEV_START("client-2", Client2Seq, InitState), + i("await evaluator(s)"), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client1, Client2, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1), connect to a server and then create +%% another socket (2) from its file descriptor *without* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv4 TCP (stream) socket. +%% +%% <WARNING> +%% +%% This is *not* how its intended to be used. +%% That an erlang process creating a socket and then handing over the +%% file descriptor to another erlang process. *But* its a convient way +%% to test it! +%% +%% </WARNING> +%% +api_ffd_open_connect_and_open_wod_and_send_tcp4(suite) -> + []; +api_ffd_open_connect_and_open_wod_and_send_tcp4(doc) -> + []; +api_ffd_open_connect_and_open_wod_and_send_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_connect_and_open_wod_and_send_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp, + dup => false}, + ok = api_ffd_open_connect_and_open_and_send_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1), connect to a server and then create +%% another socket (2) from its file descriptor *without* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv6 TCP (stream) socket. +%% +%% <WARNING> +%% +%% This is *not* how its intended to be used. +%% That an erlang process creating a socket and then handing over the +%% file descriptor to another erlang process. *But* its a convient way +%% to test it! +%% +%% </WARNING> +%% +api_ffd_open_connect_and_open_wod_and_send_tcp6(suite) -> + []; +api_ffd_open_connect_and_open_wod_and_send_tcp6(doc) -> + []; +api_ffd_open_connect_and_open_wod_and_send_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_connect_and_open_wod_and_send_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + dup => false}, + ok = api_ffd_open_connect_and_open_and_send_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1), connect to a server and then create +%% another socket (2) from its file descriptor *with* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv4 TCP (stream) socket. +api_ffd_open_connect_and_open_wd_and_send_tcp4(suite) -> + []; +api_ffd_open_connect_and_open_wd_and_send_tcp4(doc) -> + []; +api_ffd_open_connect_and_open_wd_and_send_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_connect_and_open_wd_and_send_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp, + dup => true}, + ok = api_ffd_open_connect_and_open_and_send_tcp(InitState) + end). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open a socket (1), connect to a server and then create +%% another socket (2) from its file descriptor *with* dup. +%% Exchange som data from via both "client" sockets. +%% Finally close the second socket. Ensure that the original socket +%% has not been closed (test by sending some data). +%% IPv6 TCP (stream) socket. +api_ffd_open_connect_and_open_wd_and_send_tcp6(suite) -> + []; +api_ffd_open_connect_and_open_wd_and_send_tcp6(doc) -> + []; +api_ffd_open_connect_and_open_wd_and_send_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_ffd_open_connect_and_open_wd_and_send_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + dup => true}, + ok = api_ffd_open_connect_and_open_and_send_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_ffd_open_connect_and_open_and_send_tcp(InitState) -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + api_ffd_open_connect_and_open_and_send_tcp2(InitState#{send => Send, + recv => Recv}). + +api_ffd_open_connect_and_open_and_send_tcp2(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + %% This is actually not used for unix domain socket + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await request 1 (recv)", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 1 (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue 1 (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply 1", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready 1 (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + #{desc => "await request 2 (recv)", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 2 (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue 2 (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply 2", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready 2 (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + #{desc => "await request 3 (recv)", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 3 (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue 3 (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply 3", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready 3 (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Client1Seq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "get socket FD", + cmd => fun(#{sock := Sock} = State) -> + case socket:getopt(Sock, otp, fd) of + {ok, FD} -> + ?SEV_IPRINT("FD: ~w", [FD]), + {ok, State#{fd => FD}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed get FD: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester, + fd := FD}) -> + ?SEV_ANNOUNCE_READY(Tester, connect, FD), + ok + end}, + + + %% *** The actual test *** + #{desc => "await continue (send request 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 1 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 1 (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + #{desc => "await continue (send request 3)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 3 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 3 (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Client2Seq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, FD} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, fd => FD}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "create socket", + cmd => fun(#{fd := FD, + dup := DUP} = State) -> + case socket:open(FD, #{dup => DUP}) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (send request 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 2 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 2 (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client 1 + #{desc => "order client 1 start", + cmd => fun(#{client1 := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client1, init) + end}, + + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 to continue (with connect)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client 1 ready (connect)", + cmd => fun(#{client1 := Pid} = State) -> + {ok, FD} = ?SEV_AWAIT_READY(Pid, client1, connect), + {ok, State#{fd => FD}} + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + %% Start the client 2 + #{desc => "order client 2 start", + cmd => fun(#{client2 := Pid, fd := FD} = _State) -> + ?SEV_ANNOUNCE_START(Pid, FD), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client2, init) + end}, + + %% *** The actual test *** + + #{desc => "order client 1 to continue (with send request 1)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client 1 ready (with send request 1)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, send_req) + end}, + #{desc => "await server ready (request recv 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply 1 sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client 1 ready (reply recv 1)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, recv_reply) + end}, + + + #{desc => "order client 2 to continue (with send request 2)", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client 2 ready (with send request 2)", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client2, send_req) + end}, + #{desc => "await server ready (request recv 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply 2 sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client 2 ready (reply recv 2)", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client2, recv_reply) + end}, + + + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client2, State), + {ok, State1} + end}, + + + #{desc => "order client 1 to continue (with send request 3)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client 1 ready (with send request 3)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, send_req) + end}, + #{desc => "await server ready (request recv 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply 3 sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client 1 ready (reply recv 3)", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client1, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client1, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, maps:remove(dup, InitState)), + + i("start (socket origin) client 1 evaluator"), + Client1 = ?SEV_START("client-1", Client1Seq, maps:remove(dup, InitState)), + i("await evaluator(s)"), + + i("start client 2 evaluator"), + Client2 = ?SEV_START("client-2", Client2Seq, InitState), + i("await evaluator(s)"), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client1, Client2, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API ASYNC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically establish a TCP connection via an async connect. IPv4. + +api_a_connect_tcp4(suite) -> + []; +api_a_connect_tcp4(doc) -> + []; +api_a_connect_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_connect_tcp4, + fun() -> + ok = api_a_connect_tcpD(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically establish a TCP connection via an async connect. IPv6. + +api_a_connect_tcp6(suite) -> + []; +api_a_connect_tcp6(doc) -> + []; +api_a_connect_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_connect_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + ok = api_a_connect_tcpD(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_connect_tcpD(Domain) -> + Connect = fun(Sock, SockAddr) -> + socket:connect(Sock, SockAddr, nowait) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => Domain, + connect => Connect, + send => Send, + recv => Recv}, + api_a_connect_tcp(InitState). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_connect_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv_req)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_req) + end}, + #{desc => "recv req", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "announce ready (recv_req)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (send_rep)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_rep) + end}, + #{desc => "send rep", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send_rep)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_rep), + ok + end}, + + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (async connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, async_connect) + end}, + #{desc => "connect (async) to server", + cmd => fun(#{sock := Sock, + server_sa := SSA, + connect := Connect} = State) -> + case Connect(Sock, SSA) of + ok -> + ?SEV_IPRINT("ok -> " + "unexpected success => SKIP", + []), + {skip, unexpected_success}; + {select, {select_info, ST, SR}} -> + ?SEV_IPRINT("select ->" + "~n tag: ~p" + "~n ref: ~p", [ST, SR]), + {ok, State#{connect_stag => ST, + connect_sref => SR}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (connect select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{sock := Sock, connect_sref := Ref}) -> + receive + {'$socket', Sock, select, Ref} -> + ?SEV_IPRINT("select message ->" + "~n ref: ~p", [Ref]), + ok + after 5000 -> + ?SEV_EPRINT("timeout: " + "~n message queue: ~p", + [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, select), + ok + end}, + #{desc => "connect (async) to server", + cmd => fun(#{sock := Sock, server_sa := SSA, connect := Connect}) -> + case Connect(Sock, SSA) of + ok -> + ok; + {select, SelectInfo} -> + {error, {unexpected_select, SelectInfo}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "get peername", + cmd => fun(#{sock := Sock} = _State) -> + case socket:peername(Sock) of + {ok, SockAddr} -> + ?SEV_IPRINT("Peer Name: ~p", [SockAddr]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "await continue (send_req)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send req", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send_req)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await continue (recv_rep)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_rep) + end}, + #{desc => "recv rep", + cmd => fun(#{sock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REP} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "announce ready (recv_rep)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_rep), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + State2 = maps:remove(sock, State), + State3 = maps:remove(connect_stag, State2), + State4 = maps:remove(connect_sref, State3), + {ok, State4} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + + %% *** The actual test *** + #{desc => "order client to continue (async connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, async_connect), + ok + end}, + #{desc => "await client ready (connect select)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect_select) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "await client ready (select)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, select) + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order server to recv test req (recv req)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv_req), + ok + end}, + #{desc => "order client to send test req (send req)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (send_req)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (recv_req)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order client to recv test rep (send rep)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv_rep), + ok + end}, + #{desc => "order server to send test rep (send rep)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_rep), + ok + end}, + #{desc => "await server ready (send_rep)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_rep) + end}, + #{desc => "await client ready (recv_rep)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_rep) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + i("await evaluator(s)"), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recvfrom, +%% since its much more difficult to "arrange" for sendto. +%% +api_a_sendto_and_recvfrom_udp4(suite) -> + []; +api_a_sendto_and_recvfrom_udp4(doc) -> + []; +api_a_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_a_sendto_and_recvfrom_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock, 0, nowait) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv6 UDP (dgram) socket using +%% sendto and recvfrom. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recvfrom, +%% since its much more difficult to "arrange" for sendto. +%% +api_a_sendto_and_recvfrom_udp6(suite) -> + []; +api_a_sendto_and_recvfrom_udp6(doc) -> + []; +api_a_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_a_sendto_and_recvfrom_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock, 0, nowait) + end, + InitState = #{domain => inet6, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recvmsg, +%% since its much more difficult to "arrange" for sendmsg. +%% +api_a_sendmsg_and_recvmsg_udp4(suite) -> + []; +api_a_sendmsg_and_recvmsg_udp4(doc) -> + []; +api_a_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_a_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv6 UDP (dgram) socket using +%% sendto and recvfrom. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recvmsg, +%% since its much more difficult to "arrange" for sendmsg. +%% +api_a_sendmsg_and_recvmsg_udp6(suite) -> + []; +api_a_sendmsg_and_recvmsg_udp6(doc) -> + []; +api_a_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_a_sendmsg_and_recvmsg_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Send = fun(Sock, Data, Dest) -> + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_send_and_recv_udp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket (to local address)", + cmd => fun(#{sock := Sock, local_sa := LSA} = State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, {select_info, Tag, RecvRef}} -> + ?SEV_IPRINT("expected select: " + "~n Tag: ~p" + "~n Ref: ~p", [Tag, RecvRef]), + {ok, State#{recv_stag => Tag, + recv_sref => RecvRef}}; + {ok, X} -> + {error, {unexpected_succes, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{sock := Sock, recv_sref := RecvRef}) -> + receive + {'$socket', Sock, select, RecvRef} -> + ok + after 5000 -> + ?SEV_EPRINT("message queue: ~p", [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, select), + ok + end}, + #{desc => "now read the data (request)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, {Src, ?BASIC_REQ}} -> + {ok, State#{req_src => Src}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + + #{desc => "await continue (send reply)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{sock := Sock, req_src := Src, send := Send}) -> + Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + State2 = maps:remove(tester, State), + State3 = maps:remove(recv_stag, State2), + State4 = maps:remove(recv_sref, State3), + State5 = maps:remove(req_src, State4), + {ok, State5}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "open socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SA = sock_sockname(Sock), + {ok, State#{sock => Sock, sa => SA}} + end}, + #{desc => "bind socket (to local address)", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request", + cmd => fun(#{sock := Sock, server_sa := Server, send := Send}) -> + Send(Sock, ?BASIC_REQ, Server) + end}, + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv reply (with nowait)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, {select_info, Tag, RecvRef}} -> + {ok, State#{recv_stag => Tag, + recv_sref => RecvRef}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{sock := Sock, recv_sref := RecvRef}) -> + receive + {'$socket', Sock, select, RecvRef} -> + ok + end + end}, + #{desc => "announce ready (select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, select), + ok + end}, + #{desc => "now read the data (reply)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {_Src, ?BASIC_REP}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_rep), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + State2 = maps:remove(tester, State), + State3 = maps:remove(recv_stag, State2), + State4 = maps:remove(recv_sref, State3), + {ok, State4}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (recv_select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, recv_select) + end}, + + #{desc => "order client continue (send request)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_req), + ok + end}, + #{desc => "await client ready (send request)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, send_req) + end}, + #{desc => "await server ready (select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, select) + end}, + #{desc => "await server ready (recv request)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, recv_req) + end}, + + #{desc => "order client continue (recv)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await client ready (recv_select)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, recv_select) + end}, + #{desc => "order server continue (send reply)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_reply), + ok + end}, + #{desc => "await server ready (send)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, send) + end}, + #{desc => "await client ready (select)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, select) + end}, + #{desc => "await client ready (recv reply)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, recv_rep) + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an IPv4 TCP (stream) socket. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recv, +%% since its much more difficult to "arrange" for send. +%% We *also* test async for accept. +api_a_send_and_recv_tcp4(suite) -> + []; +api_a_send_and_recv_tcp4(doc) -> + []; +api_a_send_and_recv_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_send_and_recv_tcp4, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock, 0, nowait) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an IPv6 TCP (stream) socket. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recv, +%% since its much more difficult to "arrange" for send. +%% We *also* test async for accept. +api_a_send_and_recv_tcp6(suite) -> + []; +api_a_send_and_recv_tcp6(doc) -> + []; +api_a_send_and_recv_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_send_and_recv_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock, 0, nowait) + end, + InitState = #{domain => inet6, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an IPv4 TCP (stream) socket. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recvmsg, +%% since its much more difficult to "arrange" for sendmsg. +%% We *also* test async for accept. +api_a_sendmsg_and_recvmsg_tcp4(suite) -> + []; +api_a_sendmsg_and_recvmsg_tcp4(doc) -> + []; +api_a_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_sendmsg_and_recvmsg_tcp4, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the msg functions (sendmsg and recvmsg) +%% on an IPv6 TCP (stream) socket. But we try to be async. That is, we use +%% the 'nowait' value for the Timeout argument (and await the eventual +%% select message). Note that we only do this for the recvmsg, +%% since its much more difficult to "arrange" for sendmsg. +%% We *also* test async for accept. +api_a_sendmsg_and_recvmsg_tcp6(suite) -> + []; +api_a_sendmsg_and_recvmsg_tcp6(doc) -> + []; +api_a_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_sendmsg_and_recvmsg_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + send => Send, + recv => Recv}, + ok = api_a_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_send_and_recv_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection (nowait)", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock, nowait) of + {select, {select_info, Tag, Ref}} -> + ?SEV_IPRINT("accept select: " + "~n Tag: ~p" + "~n Ref: ~p", [Tag, Ref]), + {ok, State#{accept_stag => Tag, + accept_sref => Ref}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{lsock := Sock, accept_sref := Ref}) -> + receive + {'$socket', Sock, select, Ref} -> + ok + end + end}, + #{desc => "announce ready (select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, select), + ok + end}, + #{desc => "await connection (again)", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock, nowait) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: " + "~n Sock: ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_req) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{csock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, {select_info, Tag, Ref}} -> + ?SEV_IPRINT("recv select: " + "~n Tag: ~p" + "~n Ref: ~p", [Tag, Ref]), + {ok, State#{recv_stag => Tag, + recv_sref => Ref}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{csock := Sock, recv_sref := RecvRef}) -> + receive + {'$socket', Sock, select, RecvRef} -> + ok + end + end}, + #{desc => "announce ready (select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, select), + ok + end}, + #{desc => "now read the data (request)", + cmd => fun(#{csock := Sock, recv := Recv} = _State) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + + #{desc => "await continue (send reply)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_rep) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_rep), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + ok = Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + + #{desc => "try recv reply (with nowait, expect select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, {select_info, Tag, Ref}} -> + ?SEV_IPRINT("recv select: " + "~n Tag: ~p" + "~n Ref: ~p", [Tag, Ref]), + {ok, State#{recv_stag => Tag, + recv_sref => Ref}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{sock := Sock, recv_sref := RecvRef}) -> + receive + {'$socket', Sock, select, RecvRef} -> + ok + end + end}, + #{desc => "announce ready (select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, select), + ok + end}, + #{desc => "now read the data (reply)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_rep), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "await server ready (accept select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, accept_select) + end}, + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await server ready (select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, select) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, accept) + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, connect) + end}, + + #{desc => "order server to continue (recv request)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_req), + ok + end}, + #{desc => "await server ready (recv select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, recv_select) + end}, + #{desc => "order client to continue (send request)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_req), + ok + end}, + #{desc => "await client ready (send request)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, select) + end}, + #{desc => "await server ready (recv request)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, recv_req) + end}, + + #{desc => "order client to continue (recv reply)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_rep), + ok + end}, + #{desc => "await client ready (recv select)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, recv_select) + end}, + #{desc => "order server to continue (send reply)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_rep), + ok + end}, + #{desc => "await server ready (send reply)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, send_rep) + end}, + #{desc => "await client ready (select)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, select) + end}, + #{desc => "await client ready (reply recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_rep) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recvfrom, +%% wait some time and then cancel. IPv4 +%% +api_a_recvfrom_cancel_udp4(suite) -> + []; +api_a_recvfrom_cancel_udp4(doc) -> + []; +api_a_recvfrom_cancel_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recvfrom_cancel_udp4, + fun() -> + Recv = fun(Sock) -> + case socket:recvfrom(Sock, 0, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_recv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recvfrom, +%% wait some time and then cancel. IPv6 +%% +api_a_recvfrom_cancel_udp6(suite) -> + []; +api_a_recvfrom_cancel_udp6(doc) -> + []; +api_a_recvfrom_cancel_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recvfrom_cancel_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + case socket:recvfrom(Sock, 0, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_recv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recvmsg, +%% wait some time and then cancel. IPv4 +%% +api_a_recvmsg_cancel_udp4(suite) -> + []; +api_a_recvmsg_cancel_udp4(doc) -> + []; +api_a_recvmsg_cancel_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recvmsg_cancel_udp4, + fun() -> + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_recv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recvmsg, +%% wait some time and then cancel. IPv6 +%% +api_a_recvmsg_cancel_udp6(suite) -> + []; +api_a_recvmsg_cancel_udp6(doc) -> + []; +api_a_recvmsg_cancel_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recvmsg_cancel_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_recv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_recv_cancel_udp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket (to local address)", + cmd => fun(#{sock := Sock, local_sa := LSA} = State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, SelectInfo} -> + {ok, State#{recv_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message (without success)", + cmd => fun(#{sock := Sock}) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}} + after 5000 -> + ok + end + end}, + #{desc => "announce ready (no select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, no_select), + ok + end}, + #{desc => "await continue (cancel)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, cancel) + end}, + #{desc => "cancel", + cmd => fun(#{sock := Sock, recv_select_info := SelectInfo}) -> + ok = socket:cancel(Sock, SelectInfo) + end}, + #{desc => "announce ready (cancel)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, cancel), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + State2 = maps:remove(tester, State), + State3 = maps:remove(recv_stag, State2), + State4 = maps:remove(recv_sref, State3), + State5 = maps:remove(req_src, State4), + {ok, State5}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% The actual test + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (recv select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, recv_select) + end}, + #{desc => "await server ready (no select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, no_select) + end}, + #{desc => "order server continue (cancel)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, cancel), + ok + end}, + #{desc => "await server ready (cancel)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, cancel) + end}, + + %% Terminations + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Tester]). + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to accept, +%% wait some time and then cancel. IPv4 +%% +api_a_accept_cancel_tcp4(suite) -> + []; +api_a_accept_cancel_tcp4(doc) -> + []; +api_a_accept_cancel_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_accept_cancel_tcp4, + fun() -> + Accept = fun(Sock) -> + case socket:accept(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + accept => Accept}, + ok = api_a_accept_cancel_tcp(InitState) + end). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to accept, +%% wait some time and then cancel. IPv6 +%% +api_a_accept_cancel_tcp6(suite) -> + []; +api_a_accept_cancel_tcp6(doc) -> + []; +api_a_accept_cancel_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_accept_cancel_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Accept = fun(Sock) -> + case socket:accept(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + accept => Accept}, + ok = api_a_accept_cancel_tcp(InitState) + end). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_accept_cancel_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection (nowait)", + cmd => fun(#{lsock := LSock, accept := Accept} = State) -> + case Accept(LSock) of + {select, {select_info, T, R} = SelectInfo} -> + ?SEV_IPRINT("accept select: " + "~n T: ~p" + "~n R: ~p", [T, R]), + {ok, State#{accept_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_select), + ok + end}, + #{desc => "await select message (without success)", + cmd => fun(#{lsock := Sock}) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}} + after 5000 -> + ok + end + end}, + #{desc => "announce ready (no select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, no_select), + ok + end}, + #{desc => "await continue (cancel)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, cancel) + end}, + #{desc => "cancel", + cmd => fun(#{lsock := Sock, accept_select_info := SelectInfo}) -> + ok = socket:cancel(Sock, SelectInfo) + end}, + #{desc => "announce ready (cancel)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, cancel), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "await server ready (accept select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, accept_select) + end}, + #{desc => "await server ready (no select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, no_select) + end}, + #{desc => "order server to continue (cancel)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, cancel), + ok + end}, + #{desc => "await server ready (cancel)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, cancel) + end}, + + %% *** Termination *** + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recv, +%% wait some time and then cancel. IPv4 +%% +api_a_recv_cancel_tcp4(suite) -> + []; +api_a_recv_cancel_tcp4(doc) -> + []; +api_a_recv_cancel_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recv_cancel_tcp4, + fun() -> + Recv = fun(Sock) -> + socket:recv(Sock, 0, nowait) + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_recv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recv, +%% wait some time and then cancel. IPv6 +%% +api_a_recv_cancel_tcp6(suite) -> + []; +api_a_recv_cancel_tcp6(doc) -> + []; +api_a_recv_cancel_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recv_cancel_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + socket:recv(Sock, 0, nowait) + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_recv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recvmsg, +%% wait some time and then cancel. IPv4 +%% +api_a_recvmsg_cancel_tcp4(suite) -> + []; +api_a_recvmsg_cancel_tcp4(doc) -> + []; +api_a_recvmsg_cancel_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recvmsg_cancel_tcp4, + fun() -> + Recv = fun(Sock) -> + socket:recvmsg(Sock, nowait) + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_recv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make an async (Timeout = nowait) call to recvmsg, +%% wait some time and then cancel. IPv6 +%% +api_a_recvmsg_cancel_tcp6(suite) -> + []; +api_a_recvmsg_cancel_tcp6(doc) -> + []; +api_a_recvmsg_cancel_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_a_recvmsg_cancel_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + socket:recvmsg(Sock, nowait) + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_recv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_recv_cancel_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection (nowait)", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, CSock} -> + {ok, State#{csock => CSock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (nowait recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{csock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, {select_info, T, R} = SelectInfo} -> + ?SEV_IPRINT("recv select: " + "~n Tag: ~p" + "~n Ref: ~p", [T, R]), + {ok, State#{recv_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{csock := Sock}) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}} + after 5000 -> + ok + end + end}, + #{desc => "announce ready (no select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, no_select), + ok + end}, + #{desc => "await continue (cancel)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, cancel) + end}, + #{desc => "cancel", + cmd => fun(#{csock := Sock, recv_select_info := SelectInfo}) -> + ok = socket:cancel(Sock, SelectInfo) + end}, + #{desc => "announce ready (cancel)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, cancel), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "order client to continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, accept) + end}, + + #{desc => "order server to continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (recv select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, recv_select) + end}, + #{desc => "await server ready (no select)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, no_select) + end}, + #{desc => "order server to continue (send request)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, cancel), + ok + end}, + #{desc => "await server ready (cancel)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, server, cancel) + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recvfrom +%% (from *several* processes), wait some time and then cancel. +%% This should result in abort messages to the 'other' processes. IPv4 +%% +api_a_mrecvfrom_cancel_udp4(suite) -> + []; +api_a_mrecvfrom_cancel_udp4(doc) -> + []; +api_a_mrecvfrom_cancel_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecvfrom_cancel_udp4, + fun() -> + Recv = fun(Sock) -> + case socket:recvfrom(Sock, 0, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_mrecv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recvfrom +%% (from *several* processes), wait some time and then cancel. +%% This should result in abort messages to the 'other' processes. IPv6 +%% +api_a_mrecvfrom_cancel_udp6(suite) -> + []; +api_a_mrecvfrom_cancel_udp6(doc) -> + []; +api_a_mrecvfrom_cancel_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecvfrom_cancel_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + case socket:recvfrom(Sock, 0, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_mrecv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg +%% (from *several* processes), wait some time and then cancel. +%% This should result in abort messages to the 'other' processes. IPv4 +%% +api_a_mrecvmsg_cancel_udp4(suite) -> + []; +api_a_mrecvmsg_cancel_udp4(doc) -> + []; +api_a_mrecvmsg_cancel_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecvmsg_cancel_udp4, + fun() -> + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_mrecv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg +%% (from *several* processes), wait some time and then cancel. +%% This should result in abort messages to the 'other' processes. IPv6 +%% +api_a_mrecvmsg_cancel_udp6(suite) -> + []; +api_a_mrecvmsg_cancel_udp6(doc) -> + []; +api_a_mrecvmsg_cancel_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecvmsg_cancel_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + case socket:recvmsg(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_mrecv_cancel_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_mrecv_cancel_udp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket (to local address)", + cmd => fun(#{sock := Sock, local_sa := LSA} = State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, SelectInfo} -> + {ok, State#{recv_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await abort message", + cmd => fun(#{sock := Sock, + recv_select_info := {select_info, _, Ref}} = State) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}}; + {'$socket', Sock, abort, {Ref, closed}} -> + {ok, maps:remove(sock, State)} + after 5000 -> + ?SEV_EPRINT("message queue: ~p", [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (abort)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, abort), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + State2 = maps:remove(tester, State), + State3 = maps:remove(recv_stag, State2), + State4 = maps:remove(recv_sref, State3), + State5 = maps:remove(req_src, State4), + {ok, State5}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + AltServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, SelectInfo} -> + {ok, State#{recv_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await abort message", + cmd => fun(#{sock := Sock, + recv_select_info := {select_info, _, Ref}} = State) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}}; + {'$socket', Sock, abort, {Ref, closed}} -> + {ok, maps:remove(sock, State)} + after 5000 -> + ?SEV_EPRINT("message queue: ~p", [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (abort)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, abort), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + ?SEV_IPRINT("terminating"), + State1 = maps:remove(recv_select_info, State), + State2 = maps:remove(tester, State1), + State3 = maps:remove(sock, State2), + {ok, State3}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor alt-server 1", + cmd => fun(#{alt_server1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor alt-server 2", + cmd => fun(#{alt_server2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{sock => Sock}} + end}, + + %% Start the alt-server 1 + #{desc => "order alt-server 1 start", + cmd => fun(#{alt_server1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await alt-server 1 ready (init)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, alt_server1, init) + end}, + + %% Start the alt-server 2 + #{desc => "order alt-server 2 start", + cmd => fun(#{alt_server2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await alt-server 2 ready (init)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, alt_server2, init) + end}, + + + %% The actual test + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (recv select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, recv_select) + end}, + + #{desc => "order alt-server 1 continue (recv)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await alt-server 1 ready (recv select)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, recv_select) + end}, + + #{desc => "order alt-server 2 continue (recv)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await alt-server 2 ready (recv select)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, recv_select) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close the socket", + cmd => fun(#{sock := Sock} = _State) -> + socket:close(Sock) + end}, + + #{desc => "await server ready (abort)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, abort) + end}, + #{desc => "await alt-server 1 ready (abort)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, abort) + end}, + #{desc => "await alt-server 2 ready (abort)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, abort) + end}, + + %% Terminations + #{desc => "order alt-server 2 to terminate", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await alt-server 2 termination", + cmd => fun(#{alt_server2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(alt_server2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order alt-server 1 to terminate", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await alt-server 1 termination", + cmd => fun(#{alt_server1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(alt_server1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start alt-server 1 evaluator"), + AltServer1 = ?SEV_START("alt_server1", AltServerSeq, InitState), + + i("start alt-server 2 evaluator"), + AltServer2 = ?SEV_START("alt_server2", AltServerSeq, InitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + alt_server1 => AltServer1#ev.pid, + alt_server2 => AltServer2#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, AltServer1, AltServer2, Tester]). + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to accept +%% (from *several* processes), wait some time and then cancel, +%% This should result in abort messages to the 'other' processes. IPv4 +%% +api_a_maccept_cancel_tcp4(suite) -> + []; +api_a_maccept_cancel_tcp4(doc) -> + []; +api_a_maccept_cancel_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_maccept_cancel_tcp4, + fun() -> + Accept = fun(Sock) -> + case socket:accept(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + accept => Accept}, + ok = api_a_maccept_cancel_tcp(InitState) + end). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to accept +%% (from *several* processes), wait some time and then cancel, +%% This should result in abort messages to the 'other' processes. IPv6 +%% +api_a_maccept_cancel_tcp6(suite) -> + []; +api_a_maccept_cancel_tcp6(doc) -> + []; +api_a_maccept_cancel_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_maccept_cancel_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Accept = fun(Sock) -> + case socket:accept(Sock, nowait) of + {ok, _} = OK -> + OK; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + accept => Accept}, + ok = api_a_maccept_cancel_tcp(InitState) + end). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_maccept_cancel_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lsock := Sock}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection (nowait)", + cmd => fun(#{lsock := LSock, accept := Accept} = State) -> + case Accept(LSock) of + {select, {select_info, T, R} = SelectInfo} -> + ?SEV_IPRINT("accept select: " + "~n T: ~p" + "~n R: ~p", [T, R]), + {ok, State#{accept_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_select), + ok + end}, + #{desc => "await select message (without success)", + cmd => fun(#{lsock := Sock, + accept_select_info := {select_info, _, Ref}} = State) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}}; + {'$socket', Sock, abort, {Ref, closed}} -> + {ok, maps:remove(lsock, State)} + after 5000 -> + ?SEV_EPRINT("message queue: ~p", [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (abort)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, abort), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + AltServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, lsock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "try accept request (with nowait, expect select)", + cmd => fun(#{lsock := Sock, accept := Accept} = State) -> + case Accept(Sock) of + {select, SelectInfo} -> + {ok, State#{accept_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_select), + ok + end}, + #{desc => "await abort message", + cmd => fun(#{lsock := Sock, + accept_select_info := {select_info, _, Ref}} = State) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}}; + {'$socket', Sock, abort, {Ref, closed}} -> + {ok, maps:remove(sock, State)} + after 5000 -> + ?SEV_EPRINT("message queue: ~p", [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (abort)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, abort), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + ?SEV_IPRINT("terminating"), + State1 = maps:remove(tester, State), + State2 = maps:remove(accept_select_info, State1), + State3 = maps:remove(lsock, State2), + {ok, State3}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor alt-server 1", + cmd => fun(#{alt_server1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor alt-server 2", + cmd => fun(#{alt_server2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{sock => Sock}} + end}, + + %% Start the alt-server 1 + #{desc => "order alt-server 1 start", + cmd => fun(#{alt_server1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await alt-server 1 ready (init)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, alt_server1, init) + end}, + + %% Start the alt-server 2 + #{desc => "order alt-server 2 start", + cmd => fun(#{alt_server2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await alt-server 2 ready (init)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, alt_server2, init) + end}, + + + %% *** The actual test *** + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "await server ready (accept select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, accept_select) + end}, + + #{desc => "order alt-server 1 continue (accept)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "await alt-server 1 ready (accept select)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, accept_select) + end}, + + #{desc => "order alt-server 2 continue (accept)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "await alt-server 2 ready (accept select)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, accept_select) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close the socket", + cmd => fun(#{sock := Sock} = _State) -> + socket:close(Sock) + end}, + + #{desc => "await server ready (abort)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, abort) + end}, + #{desc => "await alt-server 1 ready (abort)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, abort) + end}, + #{desc => "await alt-server 2 ready (abort)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, abort) + end}, + + + %% *** Termination *** + #{desc => "order alt-server 2 to terminate", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await alt-server 2 termination", + cmd => fun(#{alt_server2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(alt_server2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order alt-server 1 to terminate", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await alt-server 1 termination", + cmd => fun(#{alt_server1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(alt_server1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start alt-server 1 evaluator"), + AltServer1 = ?SEV_START("alt_server1", AltServerSeq, InitState), + + i("start alt-server 2 evaluator"), + AltServer2 = ?SEV_START("alt_server2", AltServerSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + alt_server1 => AltServer1#ev.pid, + alt_server2 => AltServer2#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, AltServer1, AltServer2, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recv +%% (from *several* processes), wait some time and then cancel, +%% This should result in abort messages to the 'other' processes. IPv4 +%% +api_a_mrecv_cancel_tcp4(suite) -> + []; +api_a_mrecv_cancel_tcp4(doc) -> + []; +api_a_mrecv_cancel_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecv_cancel_tcp4, + fun() -> + Recv = fun(Sock) -> + socket:recv(Sock, 0, nowait) + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_mrecv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recv +%% (from *several* processes), wait some time and then cancel, +%% This should result in abort messages to the 'other' processes. IPv6 +%% +api_a_mrecv_cancel_tcp6(suite) -> + []; +api_a_mrecv_cancel_tcp6(doc) -> + []; +api_a_mrecv_cancel_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecv_cancel_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + socket:recv(Sock, 0, nowait) + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_mrecv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg +%% (from *several* processes), wait some time and then cancel, +%% This should result in abort messages to the 'other' processes. IPv4 +%% +api_a_mrecvmsg_cancel_tcp4(suite) -> + []; +api_a_mrecvmsg_cancel_tcp4(doc) -> + []; +api_a_mrecvmsg_cancel_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecvmsg_cancel_tcp4, + fun() -> + Recv = fun(Sock) -> + socket:recvmsg(Sock, nowait) + end, + InitState = #{domain => inet, + recv => Recv}, + ok = api_a_mrecv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically we make multiple async (Timeout = nowait) call(s) to recvmsg +%% (from *several* processes), wait some time and then cancel, +%% This should result in abort messages to the 'other' processes. IPv6 +%% +api_a_mrecvmsg_cancel_tcp6(suite) -> + []; +api_a_mrecvmsg_cancel_tcp6(doc) -> + []; +api_a_mrecvmsg_cancel_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_a_mrecvmsg_cancel_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> + socket:recvmsg(Sock, nowait) + end, + InitState = #{domain => inet6, + recv => Recv}, + ok = api_a_mrecv_cancel_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_a_mrecv_cancel_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection (nowait)", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, CSock} -> + {ok, State#{csock => CSock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester, csock := Sock}) -> + ?SEV_ANNOUNCE_READY(Tester, accept, Sock), + ok + end}, + + #{desc => "await continue (nowait recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{csock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, {select_info, T, R} = SelectInfo} -> + ?SEV_IPRINT("recv select: " + "~n Tag: ~p" + "~n Ref: ~p", [T, R]), + {ok, State#{recv_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await select message", + cmd => fun(#{csock := Sock, + recv_select_info := {select_info, _, Ref}} = State) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}}; + {'$socket', Sock, abort, {Ref, closed}} -> + {ok, maps:remove(sock, State)} + after 5000 -> + ok + end + end}, + #{desc => "announce ready (abort)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, abort), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + AltServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "try recv request (with nowait, expect select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, SelectInfo} -> + {ok, State#{recv_select_info => SelectInfo}}; + {ok, X} -> + {error, {unexpected_select_info, X}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv_select)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_select), + ok + end}, + #{desc => "await abort message", + cmd => fun(#{sock := Sock, + recv_select_info := {select_info, _, Ref}} = State) -> + receive + {'$socket', Sock, select, Ref} -> + {error, {unexpected_select, Ref}}; + {'$socket', Sock, abort, {Ref, closed}} -> + {ok, maps:remove(sock, State)} + after 5000 -> + ?SEV_EPRINT("message queue: ~p", [mq()]), + {error, timeout} + end + end}, + #{desc => "announce ready (abort)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, abort), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + ?SEV_IPRINT("terminating"), + State1 = maps:remove(recv_select_info, State), + State2 = maps:remove(tester, State1), + State3 = maps:remove(sock, State2), + {ok, State3}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor alt-server 1", + cmd => fun(#{alt_server1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor alt-server 2", + cmd => fun(#{alt_server2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "order client to continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, server, accept), + {ok, State#{sock => Sock}} + end}, + + %% Start the alt server 1 + #{desc => "order alt-server 1 start", + cmd => fun(#{alt_server1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await alt-server 1 ready (init)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, init) + end}, + + %% Start the alt server 2 + #{desc => "order alt-server 2 start", + cmd => fun(#{alt_server2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await alt-server 2 ready (init)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, init) + end}, + + + %% *** The actual test *** + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (recv select)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, recv_select) + end}, + + #{desc => "order alt-server 1 continue (recv)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await alt-server 1 ready (recv select)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, recv_select) + end}, + + #{desc => "order alt-server 2 continue (recv)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await alt-server 2 ready (recv select)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, recv_select) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close the socket", + cmd => fun(#{sock := Sock} = _State) -> + socket:close(Sock) + end}, + + #{desc => "await server ready (abort)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, abort) + end}, + #{desc => "await alt-server 1 ready (abort)", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server1, abort) + end}, + #{desc => "await alt-server 2 ready (abort)", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, alt_server2, abort) + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + + #{desc => "order alt-server 2 to terminate", + cmd => fun(#{alt_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await alt-server 2 termination", + cmd => fun(#{alt_server2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(alt_server2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order alt-server 1 to terminate", + cmd => fun(#{alt_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await alt-server 1 termination", + cmd => fun(#{alt_server1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(alt_server1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start alt-server 1 evaluator"), + AltServer1 = ?SEV_START("alt_server1", AltServerSeq, InitState), + + i("start alt-server 2 evaluator"), + AltServer2 = ?SEV_START("alt_server2", AltServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + alt_server1 => AltServer1#ev.pid, + alt_server2 => AltServer2#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, AltServer1, AltServer2, Client, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPTIONS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_options(suite) -> + []; +api_opt_simple_otp_options(doc) -> + []; +api_opt_simple_otp_options(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_simple_otp_options, + fun() -> api_opt_simple_otp_options() end). + +api_opt_simple_otp_options() -> + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + Seq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "create dummy process", + cmd => fun(State) -> + Pid = spawn_link(fun() -> + put(sname, "dummy"), + receive + die -> + exit(normal) + end + end), + {ok, State#{dummy => Pid}} + end}, + + %% *** Check iow part *** + #{desc => "get iow", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, iow) of + {ok, IOW} when is_boolean(IOW) -> + {ok, State#{iow => IOW}}; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% ok = socket:setopt(Sock, otp, debug, true) + %% end}, + + #{desc => "set (new) iow", + cmd => fun(#{sock := Sock, iow := OldIOW} = State) -> + NewIOW = not OldIOW, + case Set(Sock, iow, NewIOW) of + ok -> + {ok, State#{iow => NewIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) iow", + cmd => fun(#{sock := Sock, iow := IOW}) -> + case Get(Sock, iow) of + {ok, IOW} -> + ok; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvbuf part *** + #{desc => "get rcvbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} when is_integer(RcvBuf) -> + {ok, State#{rcvbuf => RcvBuf}}; + {ok, {N, RcvBuf} = V} when is_integer(N) andalso + is_integer(RcvBuf) -> + {ok, State#{rcvbuf => V}}; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := {OldN, OldRcvBuf}} = State) -> + NewRcvBuf = {OldN+2, OldRcvBuf + 1024}, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, rcvbuf := OldRcvBuf} = State) when is_integer(OldRcvBuf) -> + NewRcvBuf = 2 * OldRcvBuf, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, rcvbuf := OldRcvBuf, + type := stream, + protocol := tcp} = State) when is_integer(OldRcvBuf) -> + NewRcvBuf = {2, OldRcvBuf}, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} -> + ok; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Check sndctrlbuf part *** + #{desc => "get sndctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) -> + {ok, State#{sndctrlbuf => SndCtrlBuf}}; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) -> + NewSndCtrlBuf = 2 * OldSndCtrlBuf, + case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of + ok -> + {ok, State#{sndctrlbuf => NewSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} -> + ok; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check controlling-process part *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock}) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + Set(Sock, controlling_process, Dummy) + end}, + #{desc => "verify dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + case Get(Sock, controlling_process) of + {ok, Dummy} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + i("start tcp (stream) evaluator"), + InitState1 = #{domain => inet, type => stream, protocol => tcp}, + Tester1 = ?SEV_START("tcp-tester", Seq, InitState1), + i("await tcp evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester1]), + + i("start udp (dgram) socket"), + InitState2 = #{domain => inet, type => dgram, protocol => udp}, + Tester2 = ?SEV_START("udp-tester", Seq, InitState2), + i("await udp evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester2]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt otp meta option +api_opt_simple_otp_meta_option(suite) -> + []; +api_opt_simple_otp_meta_option(doc) -> + []; +api_opt_simple_otp_meta_option(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_simple_otp_meta_option, + fun() -> api_opt_simple_otp_meta_option() end). + +api_opt_simple_otp_meta_option() -> + Get = fun(S) -> + socket:getopt(S, otp, meta) + end, + Set = fun(S, Val) -> + socket:setopt(S, otp, meta, Val) + end, + + MainSeq = + [ + #{desc => "monitor helper", + cmd => fun(#{helper := Pid}) -> + _ = erlang:monitor(process, Pid), + ok + end}, + + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + + #{desc => "get default", + cmd => fun(#{sock := Sock}) -> + case Get(Sock) of + {ok, undefined} -> + ok; + {ok, Invalid} -> + {error, {invalid, Invalid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "set value", + cmd => fun(#{sock := Sock} = State) -> + Value = make_ref(), + case Set(Sock, Value) of + ok -> + {ok, State#{value => Value}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "get value", + cmd => fun(#{sock := Sock, value := Value}) -> + case Get(Sock) of + {ok, Value} -> + ok; + {ok, Invalid} -> + {error, {invalid, Invalid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "set complex value", + cmd => fun(#{sock := Sock} = State) -> + Value = + #{a => 1, + b => {2, 3}, + c => make_ref(), + d => self(), + e => State}, + case Set(Sock, Value) of + ok -> + {ok, State#{value := Value}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "get complex value", + cmd => fun(#{sock := Sock, value := Value}) -> + case Get(Sock) of + {ok, Value} -> + ok; + {ok, Invalid} -> + {error, {invalid, Invalid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "start helper", + cmd => fun(#{helper := Pid, sock := Sock, value := Value}) -> + ?SEV_ANNOUNCE_START(Pid, {Sock, Value}), + ok + end}, + + #{desc => "wait for helper ready", + cmd => fun(#{helper := Pid}) -> + ?SEV_AWAIT_READY(Pid, helper, test) + end}, + + #{desc => "socket close", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + ?SEV_FINISH_NORMAL], + + HelperSeq = + [#{desc => "await start", + cmd => fun (State) -> + {Main, {Sock, Value}} = ?SEV_AWAIT_START(), + {ok, State#{main => Main, + sock => Sock, + value => Value}} + end}, + #{desc => "monitor main", + cmd => fun(#{main := Main}) -> + _ = erlang:monitor(process, Main), + ok + end} + + #{desc => "get value", + cmd => fun(#{sock := Sock, value := Value}) -> + case Get(Sock) of + {ok, Value} -> + ok; + {ok, Invalid} -> + {error, {invalid, Invalid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "set and fail", + cmd => fun(#{sock := Sock}) -> + Value = self(), + case Set(Sock, Value) of + ok -> + {error, only_owner_may_set}; + {error, not_owner} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "announce ready (test)", + cmd => fun(#{main := Main}) -> + ?SEV_ANNOUNCE_READY(Main, test), + ok + end}, + + ?SEV_FINISH_NORMAL], + + + i("start tcp helper evaluator"), + Helper = ?SEV_START("tcp-helper", HelperSeq, #{}), + + i("start tcp main evaluator"), + MainState = #{domain => inet, type => stream, protocol => tcp, + helper => Helper#ev.pid}, + Main = ?SEV_START("tcp-main", MainSeq, MainState), + + i("await tcp evaluators"), + ok = ?SEV_AWAIT_FINISH([Helper, Main]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple operations with the rcvbuf otp option +%% The operations we test here are only for type = stream and +%% protocol = tcp. +api_opt_simple_otp_rcvbuf_option(suite) -> + []; +api_opt_simple_otp_rcvbuf_option(doc) -> + []; +api_opt_simple_otp_rcvbuf_option(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(api_opt_simple_otp_rcvbuf_option, + fun() -> api_opt_simple_otp_rcvbuf_option() end). + +api_opt_simple_otp_rcvbuf_option() -> + Get = fun(S) -> + socket:getopt(S, otp, rcvbuf) + end, + Set = fun(S, Val) -> + socket:setopt(S, otp, rcvbuf, Val) + end, + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, + local_sa := LocalSA, + lport := Port}) -> + ServerSA = LocalSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + + %% *** The actual test part *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% Recv with default size for (otp) rcvbuf + #{desc => "await continue (recv initial)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, MsgSz} -> + ?SEV_IPRINT("MsgSz: ~p", [MsgSz]), + {ok, State#{msg_sz => MsgSz}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + ?SEV_IPRINT("try recv ~w bytes when rcvbuf is ~s", + [MsgSz, + case Get(Sock) of + {ok, RcvBuf} -> f("~w", [RcvBuf]); + {error, _} -> "-" + end]), + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv initial)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Recv with new size (1) for (otp) rcvbuf + #{desc => "await continue (recv 1)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, NewRcvBuf} -> + ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]), + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to setopt rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) -> + case Set(Sock, NewRcvBuf) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Recv with new size (2) for (otp) rcvbuf + #{desc => "await continue (recv 2)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, NewRcvBuf} -> + ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]), + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to setopt rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) -> + case Set(Sock, NewRcvBuf) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Recv with new size (3) for (otp) rcvbuf + #{desc => "await continue (recv 3, truncated)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, {ExpSz, NewRcvBuf}} -> + {ok, State#{msg_sz => ExpSz, + rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to setopt rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) -> + case Set(Sock, NewRcvBuf) of + ok -> + ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + ?SEV_IPRINT("try recv ~w bytes of data", [MsgSz]), + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + + %% Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket(s)", + cmd => fun(#{lsock := LSock, sock := Sock} = State) -> + sock_close(Sock), + sock_close(LSock), + State1 = maps:remove(sock, State), + State2 = maps:remove(lport, State1), + State3 = maps:remove(lsock, State2), + {ok, State3} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send initial)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, send) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "send (initial) data to server", + cmd => fun(#{sock := Sock, data := Data} = _State) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send initial)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (send 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (1) data to server", + cmd => fun(#{sock := Sock, data := Data}) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (send 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (2) data to server", + cmd => fun(#{sock := Sock, data := Data}) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (send 3)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (3) data to server", + cmd => fun(#{sock := Sock, data := Data}) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + + %% Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "order server start", + cmd => fun(#{server := Server}) -> + ?SEV_ANNOUNCE_START(Server) + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Server, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + #{desc => "order client start", + cmd => fun(#{client := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init) + end}, + + + %% The actual test (connecting) + #{desc => "order server accept (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + %% The actual test (initial part) + #{desc => "order client continue (send initial)", + cmd => fun(#{client := Client, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv initial)", + cmd => fun(#{server := Server, data := Data} = _State) -> + ExpMsgSz = size(Data), + ?SEV_ANNOUNCE_CONTINUE(Server, recv, ExpMsgSz), + ok + end}, + #{desc => "await client ready (send initial)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv initial)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% The actual test (part 1) + #{desc => "order client continue (send 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv 1)", + cmd => fun(#{server := Server, data := Data} = _State) -> + MsgSz = size(Data), + NewRcvBuf = {2 + (MsgSz div 1024), 1024}, + ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf), + ok + end}, + #{desc => "await client ready (send 1)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv 1)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% The actual test (part 2) + #{desc => "order client continue (send 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv 2)", + cmd => fun(#{server := Server, data := Data} = _State) -> + MsgSz = size(Data), + NewRcvBuf = {2 + (MsgSz div 2048), 2048}, + ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf), + ok + end}, + #{desc => "await client ready (send 2)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv 2)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% The actual test (part 3) + #{desc => "order client continue (send 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv 3)", + cmd => fun(#{server := Server, data := Data} = _State) -> + MsgSz = size(Data), + BufSz = 2048, + N = MsgSz div BufSz - 1, + NewRcvBuf = {N, BufSz}, + ?SEV_ANNOUNCE_CONTINUE(Server, recv, + {N*BufSz, NewRcvBuf}) + end}, + #{desc => "await client ready (send 3)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv 3)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + ?SEV_SLEEP(?SECS(1)), + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + State2 = maps:remove(server_sa, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% Create a data binary of 6*1024 bytes + Data = list_to_binary(lists:duplicate(6*4, lists:seq(0, 255))), + InitState = #{domain => inet, + data => Data}, + + i("create server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("create client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("create tester evaluator"), + TesterInitState = InitState#{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_controlling_process(suite) -> + []; +api_opt_simple_otp_controlling_process(doc) -> + []; +api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_simple_otp_controlling_process, + fun() -> api_opt_simple_otp_controlling_process() end). + +api_opt_simple_otp_controlling_process() -> + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + ClientSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The actual test *** + #{desc => "verify tester as controlling-process", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Tester} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (not owner)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, not_owner), + ok + end}, + #{desc => "await continue (owner)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, owner) + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt controlling-process transfer to tester", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Set(Sock, controlling_process, Tester) + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (owner)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, owner), + ok + + end}, + + %% *** Termination *** + #{desc => "await termination", + cmd => fun(#{tester := Tester} = State) -> + ?SEV_AWAIT_TERMINATE(Tester, tester), + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + + %% *** The actual test *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) start", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Client, Sock), + ok + end}, + #{desc => "await (client) ready (not owner)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, not_owner) + end}, + #{desc => "attempt controlling-process transfer to client", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Set(Sock, controlling_process, Client) + end}, + #{desc => "verify client as controlling-process", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Client} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) continue (owner)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, owner), + ok + end}, + #{desc => "await (client) ready (2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, owner), + ok + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Termination *** + #{desc => "order (client) terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + {ok, maps:remove(client, State)} + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tcp (stream) client evaluator"), + ClientInitState1 = #{}, + Client1 = ?SEV_START("tcp-client", ClientSeq, ClientInitState1), + + i("start tcp (stream) tester evaluator"), + TesterInitState1 = #{domain => inet, + type => stream, + protocol => tcp, + client => Client1#ev.pid}, + Tester1 = ?SEV_START("tcp-tester", TesterSeq, TesterInitState1), + + i("await tcp evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester1, Client1]), + + i("start udp (dgram) client evaluator"), + ClientInitState2 = #{}, + Client2 = ?SEV_START("udp-client", ClientSeq, ClientInitState2), + + i("start udp (dgram) tester evaluator"), + TesterInitState2 = #{domain => inet, + type => dgram, + protocol => udp, + client => Client2#ev.pid}, + Tester2 = ?SEV_START("udp-tester", TesterSeq, TesterInitState2), + + i("await udp evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester2, Client2]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option acceptconn for UDP. +%% This should be possible to get but not set. + +api_opt_sock_acceptconn_udp(suite) -> + []; +api_opt_sock_acceptconn_udp(doc) -> + []; +api_opt_sock_acceptconn_udp(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_sock_acceptconn_udp, + fun() -> + has_support_sock_acceptconn() + end, + fun() -> api_opt_sock_acceptconn_udp() end). + + + +api_opt_sock_acceptconn_udp() -> + Opt = acceptconn, + Set = fun(S, Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[get] verify socket (before bind)", + cmd => fun(#{sock := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, enoprotoopt = Reason} -> + %% On some platforms this is not accepted + %% for UDP, so skip this part (UDP). + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(Sock)), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[set] verify socket (before bind)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", + [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + #{desc => "bind socket to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[get] verify socket (after bind)", + cmd => fun(#{sock := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[set] verify socket (after bind)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", + [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option acceptconn for TCP. +%% This should be possible to get but not set. + +api_opt_sock_acceptconn_tcp(suite) -> + []; +api_opt_sock_acceptconn_tcp(doc) -> + []; +api_opt_sock_acceptconn_tcp(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_sock_acceptconn_tcp, + fun() -> + has_support_sock_acceptconn() + end, + fun() -> api_opt_sock_acceptconn_tcp() end). + + + +api_opt_sock_acceptconn_tcp() -> + Opt = acceptconn, + Set = fun(S, Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[get] verify listen socket (before bind)", + cmd => fun(#{lsock := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, enoprotoopt = Reason} -> + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(Sock)), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[set] verify listen socket (before bind)", + cmd => fun(#{lsock := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "bind listen socket to local address", + cmd => fun(#{lsock := Sock, local_sa := LSA} = State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{server_sa => LSA#{port => Port}}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[get] verify listen socket (after bind)", + cmd => fun(#{lsock := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[set] verify listen socket (after bind)", + cmd => fun(#{lsock := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "make listen socket accept connections", + cmd => fun(#{lsock := Sock} = _State) -> + case socket:listen(Sock) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[get] verify listen socket (after listen)", + cmd => fun(#{lsock := Sock} = _State) -> + case Get(Sock) of + {ok, true} -> + ?SEV_IPRINT("Expected Success: " + "Accepting connections"), + ok; + {ok, false} -> + ?SEV_EPRINT("Unexpected Success: " + "Not accepting connections"), + {error, {unexpected_success, {Opt, false}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[set] verify listen socket (after listen)", + cmd => fun(#{lsock := Sock} = _State) -> + case Set(Sock, false) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=false)"), + {error, unexpected_success} + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "create (connecting) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{csockc => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "bind connecting socket to local address", + cmd => fun(#{csockc := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[get] verify connecting socket (before connect)", + cmd => fun(#{csockc := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[set] verify connecting socket (before connect)", + cmd => fun(#{csockc := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "connect to server", + cmd => fun(#{csockc := Sock, server_sa := SSA} = _State) -> + case socket:connect(Sock, SSA) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "accept connection", + cmd => fun(#{lsock := Sock} = State) -> + case socket:accept(Sock) of + {ok, CSock} -> + {ok, State#{csocks => CSock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[get] verify connecting socket (after connect)", + cmd => fun(#{csockc := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[set] verify connecting socket (after connect)", + cmd => fun(#{csockc := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + #{desc => "[get] verify connected socket", + cmd => fun(#{csocks := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "Not accepting connections"), + ok; + {ok, true} -> + ?SEV_EPRINT("Unexpected Success: " + "Accepting connections"), + {error, {unexpected_success, {Opt, true}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[set] verify connected socket", + cmd => fun(#{csocks := Sock} = _State) -> + case Set(Sock, true) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=true)"), + {error, unexpected_success} + end + end}, + + #{desc => "[get] verify listen socket (after connect)", + cmd => fun(#{lsock := Sock} = _State) -> + case Get(Sock) of + {ok, true} -> + ?SEV_IPRINT("Expected Success: " + "Accepting connections"), + ok; + {ok, false} -> + ?SEV_EPRINT("Unexpected Success: " + "Not accepting connections"), + {error, {unexpected_success, {Opt, false}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[set] verify listen socket (after connect)", + cmd => fun(#{lsock := Sock} = _State) -> + case Set(Sock, false) of + {error, Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + ok; + ok -> + ?SEV_EPRINT("Unexpected Success: " + "Set acceptconn (=false)"), + {error, unexpected_success} + end + end}, + + %% *** Termination *** + #{desc => "close connecting socket(s)", + cmd => fun(#{csockc := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(csockc, State0), + State2 = maps:remove(csocks, State1), %% Auto-close + {ok, maps:remove(csockc, State2)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option acceptfilter. PLACEHOLDER! + +api_opt_sock_acceptfilter(suite) -> + []; +api_opt_sock_acceptfilter(doc) -> + []; +api_opt_sock_acceptfilter(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_sock_acceptfilter, + fun() -> not_yet_implemented() end, + fun() -> ok end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option bindtodevice. +%% It has not always been possible to 'get' this option +%% (atleast on linux). + +api_opt_sock_bindtodevice(suite) -> + []; +api_opt_sock_bindtodevice(doc) -> + []; +api_opt_sock_bindtodevice(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_sock_bindtodevice, + fun() -> has_support_sock_bindtodevice() end, + fun() -> api_opt_sock_bindtodevice() end). + + +api_opt_sock_bindtodevice() -> + Opt = bindtodevice, + Set = fun(S, Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name, addr := Addr}} -> + ?SEV_IPRINT("local host info (~p): " + "~n Name: ~p" + "~n Addr: ~p", + [Domain, Name, Addr]), + LSA = #{family => Domain, + addr => Addr}, + {ok, State#{dev => Name, + local_sa => LSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create UDP socket 1", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{usock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create UDP socket 2", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{usock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create TCP socket 1", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{tsock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create TCP socket 2", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{tsock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[get] verify UDP socket 1 (before bindtodevice)", + cmd => fun(#{usock1 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, enoprotoopt = Reason} -> + ?SEV_EPRINT("Unexpected Failure: ~p => SKIP", + [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[get] verify UDP socket 2 (before bind)", + cmd => fun(#{usock2 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[get] verify TCP socket 1 (before bindtodevice)", + cmd => fun(#{tsock1 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[get] verify TCP socket 2 (before bind)", + cmd => fun(#{tsock2 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "Bind UDP socket 1 to device", + cmd => fun(#{usock1 := Sock, dev := Dev} = State) -> + case Set(Sock, Dev) of + ok -> + ?SEV_IPRINT("Expected Success"), + ok; + {error, eperm = Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + (catch socket:close(Sock)), + {ok, State#{usock1 => skip}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "Bind UDP socket 2 to local address", + cmd => fun(#{usock2 := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("Expected Success"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "Bind TCP socket 1 to device", + cmd => fun(#{usock1 := USock1, + tsock1 := Sock, dev := Dev} = State) -> + case Set(Sock, Dev) of + ok -> + ?SEV_IPRINT("Expected Success"), + ok; + {error, eperm = Reason} when (USock1 =:= skip) -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + {skip, Reason}; + {error, eperm = Reason} -> + ?SEV_IPRINT("Expected Failure: ~p", [Reason]), + (catch socket:close(Sock)), + {ok, State#{tsock1 => skip}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "Bind TCP socket 2 to local address", + cmd => fun(#{tsock2 := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("Expected Success"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[get] verify UDP socket 1 (after bindtodevice)", + cmd => fun(#{usock1 := skip} = _State) -> + ?SEV_IPRINT("SKIP'ed (previous eperm)"), + ok; + (#{usock1 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[get] verify UDP socket 2 (after bind)", + cmd => fun(#{usock2 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[get] verify TCP socket 1 (after bindtodevice)", + cmd => fun(#{tsock1 := skip} = _State) -> + ?SEV_IPRINT("SKIP'ed (previous eperm)"), + ok; + (#{tsock1 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + #{desc => "[get] verify TCP socket 2 (after bind)", + cmd => fun(#{tsock2 := Sock} = _State) -> + case Get(Sock) of + {ok, Dev} -> + ?SEV_IPRINT("Expected Success: ~p", [Dev]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", [Reason]), + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + %% *** Termination *** + #{desc => "close UDP socket 1", + cmd => fun(#{usock1 := skip} = State) -> + ?SEV_IPRINT("SKIP'ed (already closed)"), + {ok, maps:remove(usock1, State)}; + (#{usock1 := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(usock1, State)} + end}, + #{desc => "close UDP socket 2", + cmd => fun(#{usock2 := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(usock2, State)} + end}, + #{desc => "close TCP socket 1", + cmd => fun(#{tsock1 := skip} = State) -> + ?SEV_IPRINT("SKIP'ed (already closed)"), + {ok, maps:remove(tsock1, State)}; + (#{tsock1 := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(tsock1, State)} + end}, + #{desc => "close TCP socket 2", + cmd => fun(#{tsock2 := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(tsock2, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option broadcast. +%% Make it possible for datagram sockets to send packets to a broadcast +%% address (IPv4 only). + +api_opt_sock_broadcast(suite) -> + []; +api_opt_sock_broadcast(doc) -> + []; +api_opt_sock_broadcast(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_sock_broadcast, + fun() -> has_support_sock_broadcast() end, + fun() -> api_opt_sock_broadcast() end). + + +api_opt_sock_broadcast() -> + Opt = broadcast, + Set = fun(S, Val) when is_boolean(Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name, + addr := Addr, + broadaddr := BAddr}} -> + ?SEV_IPRINT("local host info: " + "~n Name: ~p" + "~n Addr: ~p" + "~n Broadcast Addr: ~p", + [Name, Addr, BAddr]), + LSA = #{family => Domain, + addr => Addr}, + BSA = #{family => Domain, + addr => BAddr}, + {ok, State#{lsa => LSA, + bsa => BSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "[socket 1] create UDP socket (listening 1)", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "[socket 1] Bind UDP socket (to limited broadcast address)", + cmd => fun(#{sock1 := Sock} = State) -> + BSA = #{family => inet, + addr => broadcast}, + ?SEV_IPRINT("Try bind (socket 1) to: " + "~n ~p", [BSA]), + case socket:bind(Sock, BSA) of + {ok, Port} -> + ?SEV_IPRINT("Expected Success (bound): ~p", + [Port]), + {ok, State#{sa1 => BSA#{port => Port}}}; + {error, eaddrnotavail = Reason} -> + ?SEV_IPRINT("~p => " + "SKIP limited broadcast test", + [Reason]), + {ok, State#{sa1 => skip}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 1] UDP socket sockname", + cmd => fun(#{sa1 := skip} = _State) -> + ?SEV_IPRINT("SKIP limited broadcast test"), + ok; + (#{sock1 := Sock} = _State) -> + case socket:sockname(Sock) of + {ok, SA} -> + ?SEV_IPRINT("SA: ~p", [SA]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[socket 2] create UDP socket (listening 2)", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "[socket 2] Bind UDP socket (to subnet-directed broadcast address)", + cmd => fun(#{sock2 := Sock, + bsa := BSA} = State) -> + ?SEV_IPRINT("Try bind (socket 1) to: " + "~n ~p", [BSA]), + case socket:bind(Sock, BSA) of + {ok, Port} -> + ?SEV_IPRINT("Expected Success (bound): ~p", + [Port]), + {ok, State#{sa2 => BSA#{port => Port}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 2] UDP socket sockname", + cmd => fun(#{sock2 := Sock} = _State) -> + case socket:sockname(Sock) of + {ok, SA} -> + ?SEV_IPRINT("SA: ~p", [SA]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[socket 3] create UDP socket (sender)", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock3 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "[socket 3][get] verify UDP socket (before bind and set)", + cmd => fun(#{sock3 := Sock} = _State) -> + case Get(Sock) of + {ok, false} -> + ?SEV_IPRINT("Expected Success: " + "broadcast not allowed"), + ok; + {ok, true} -> + ?SEV_IPRINT("Unexpected Success result: " + "broadcast already allowed"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 3] Try make broadcast allowed", + cmd => fun(#{sock3 := Sock} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("Expected Success: " + "broadcast now allowed"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 3] verify UDP socket broadcast allowed", + cmd => fun(#{sock3 := Sock} = _State) -> + case Get(Sock) of + {ok, true} -> + ?SEV_IPRINT("Expected Success: " + "broadcast allowed"), + ok; + {ok, false} -> + ?SEV_IPRINT("Unexpected Success result: " + "broadcast *not* allowed"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 3] Bind UDP socket (to local address)", + cmd => fun(#{sock3 := Sock, lsa := LSA} = State) -> + ?SEV_IPRINT("Try bind (socket 2) to: " + "~n ~p", [LSA]), + case socket:bind(Sock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("Expected Success (bound): ~p", + [Port]), + {ok, State#{sa3 => LSA#{port => Port}}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 3] verify UDP socket (after set)", + cmd => fun(#{sock3 := Sock} = _State) -> + case Get(Sock) of + {ok, true} -> + ?SEV_IPRINT("Expected Success: " + "broadcast allowed"), + ok; + {ok, false} -> + ?SEV_IPRINT("Unexpected Success result: " + "broadcast not allowed"), + {error, not_allowed}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[socket 3] try send to limited broadcast address", + cmd => fun(#{sa1 := skip} = _State) -> + ?SEV_IPRINT("SKIP limited broadcast test"), + ok; + (#{sock3 := Sock, + sa1 := Dest} = _State) -> + Data = list_to_binary("hejsan"), + ?SEV_IPRINT("try send to bradcast address: " + "~n ~p", [Dest]), + case socket:sendto(Sock, Data, Dest) of + ok -> + ?SEV_IPRINT("Expected Success: " + "broadcast message sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 1] try recv", + cmd => fun(#{sa1 := skip} = _State) -> + ?SEV_IPRINT("SKIP limited broadcast test"), + ok; + (#{sock1 := Sock} = State) -> + case socket:recvfrom(Sock, 0, 5000) of + {ok, _} -> + ?SEV_IPRINT("Expected Success: " + "received message"), + ok; + {error, timeout = Reason} -> + %% Some platforms seem to balk at this. + %% It spossible to bind to this, and + %% send to it, but no data is received. + %% At some point we should investigate... + %% For now, we just skip this part of + %% the test... + ?SEV_IPRINT("Unexpected Failure: ~p", + [Reason]), + {ok, State#{sa1 => skip}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "[socket 3] try send to subnet-directed broadcast address", + cmd => fun(#{sock3 := Sock, + sa2 := Dest} = _State) -> + Data = list_to_binary("hejsan"), + ?SEV_IPRINT("try send to bradcast address: " + "~n ~p", [Dest]), + case socket:sendto(Sock, Data, Dest) of + ok -> + ?SEV_IPRINT("Expected Success: " + "broadcast message sent"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "[socket 2] try recv", + cmd => fun(#{sock2 := Sock, sa1 := SA1} = _State) -> + case socket:recvfrom(Sock, 0, 5000) of + {ok, _} -> + ?SEV_IPRINT("Expected Success: " + "received message"), + ok; + {error, timeout = Reason} when (SA1 =:= skip) -> + ?SEV_IPRINT("Unexpected Failure: ~p", + [Reason]), + {skip, "receive timeout"}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + + %% *** Termination *** + #{desc => "[socket 3] close UDP socket (sender)", + cmd => fun(#{sock3 := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(sock3, State0), + State2 = maps:remove(sa3, State1), + {ok, State2} + end}, + #{desc => "[socket 2] close UDP socket (listener 2)", + cmd => fun(#{sock2 := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(sock2, State0), + State2 = maps:remove(sa2, State1), + {ok, State2} + end}, + #{desc => "[socket 1] close UDP socket (listener 1)", + cmd => fun(#{sock1 := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(sock1, State0), + State2 = maps:remove(sa1, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option debug. +%% On linux, this test requires that the user running the test to have +%% CAP_NET_ADMIN capabilities or be root (effective user ID of 0), +%% therefor we explicitly test for the result eacces when attempting to +%% set, and skip if we get it. + +api_opt_sock_debug(suite) -> + []; +api_opt_sock_debug(doc) -> + []; +api_opt_sock_debug(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_debug, + fun() -> has_support_sock_debug() end, + fun() -> api_opt_sock_debug() end). + + +api_opt_sock_debug() -> + Opt = debug, + Set = fun(S, Val) when is_integer(Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name, + addr := Addr, + broadaddr := BAddr}} -> + ?SEV_IPRINT("local host info: " + "~n Name: ~p" + "~n Addr: ~p" + "~n Broadcast Addr: ~p", + [Name, Addr, BAddr]), + LSA = #{family => Domain, + addr => Addr}, + BSA = #{family => Domain, + addr => BAddr}, + {ok, State#{lsa => LSA, + bsa => BSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "create UDP socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "Get current debug value", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock) of + {ok, Debug} when is_integer(Debug) -> + ?SEV_IPRINT("Success: ~p", [Debug]), + {ok, State#{debug => Debug}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "Try enable socket debug", + cmd => fun(#{sock := Sock, debug := Debug} = State) -> + NewDebug = Debug + 1, + case Set(Sock, NewDebug) of + ok -> + ?SEV_IPRINT("Expected Success"), + {ok, State#{debug => NewDebug}}; + {error, eacces = Reason} -> + ?SEV_EPRINT("NO ACCESS => SKIP"), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "Get current (new) debug value", + cmd => fun(#{sock := Sock, debug := Debug} = _State) -> + case Get(Sock) of + {ok, Debug} when is_integer(Debug) -> + ?SEV_IPRINT("Success: ~p", [Debug]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + + %% *** Termination *** + #{desc => "close UDP socket", + cmd => fun(#{sock := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(sock, State0), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option domain. +%% This is a read only option. Also not available on all platforms. + +api_opt_sock_domain(suite) -> + []; +api_opt_sock_domain(doc) -> + []; +api_opt_sock_domain(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_domain, + fun() -> has_support_sock_domain() end, + fun() -> api_opt_sock_domain() end). + + +api_opt_sock_domain() -> + Opt = domain, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name, + addr := Addr, + broadaddr := BAddr}} -> + ?SEV_IPRINT("local host info: " + "~n Name: ~p" + "~n Addr: ~p" + "~n Broadcast Addr: ~p", + [Name, Addr, BAddr]), + LSA = #{family => Domain, + addr => Addr}, + BSA = #{family => Domain, + addr => BAddr}, + {ok, State#{lsa => LSA, + bsa => BSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "create IPv4 UDP socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{usock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "Get domain for the UDP socket", + cmd => fun(#{domain := Domain, usock := Sock} = _State) -> + case Get(Sock) of + {ok, Domain} -> + ?SEV_IPRINT("Success: ~p", [Domain]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "create TCP socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{tsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "Get domain for the TCP socket", + cmd => fun(#{domain := Domain, tsock := Sock} = _State) -> + case Get(Sock) of + {ok, Domain} -> + ?SEV_IPRINT("Success: ~p", [Domain]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + + %% *** Termination *** + #{desc => "close UDP socket", + cmd => fun(#{usock := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(usock, State0), + {ok, State1} + end}, + #{desc => "close TCP socket", + cmd => fun(#{tsock := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(tsock, State0), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option dontroute. +%% The man page has the following to say: +%% "Don't send via a gateway, send only to directly connected hosts. +%% The same effect can be achieved by setting the MSG_DONTROUTE +%% flag on a socket send(2) operation." +%% Since its "kind of" difficult to check if it actually takes an +%% effect (you would need a gateway for that and a machine "on the +%% other side"), we only test if we can set and get the value. +%% Better then nothing. + +api_opt_sock_dontroute(suite) -> + []; +api_opt_sock_dontroute(doc) -> + []; +api_opt_sock_dontroute(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_dontroute, + fun() -> has_support_sock_dontroute() end, + fun() -> api_opt_sock_dontroute() end). + + +api_opt_sock_dontroute() -> + Opt = dontroute, + Set = fun(S, Val) when is_boolean(Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name, + addr := Addr, + broadaddr := BAddr}} -> + ?SEV_IPRINT("local host info: " + "~n Name: ~p" + "~n Addr: ~p" + "~n Broadcast Addr: ~p", + [Name, Addr, BAddr]), + LSA = #{family => Domain, + addr => Addr}, + BSA = #{family => Domain, + addr => BAddr}, + {ok, State#{lsa => LSA, + bsa => BSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "create UDP socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "Get current value", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock) of + {ok, Val} when is_boolean(Val) -> + ?SEV_IPRINT("Success: ~p", [Val]), + {ok, State#{dontroute => Val}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "Try change value", + cmd => fun(#{sock := Sock, dontroute := Current} = State) -> + New = not Current, + ?SEV_IPRINT("Change from ~p to ~p", [Current, New]), + case Set(Sock, New) of + ok -> + ?SEV_IPRINT("Expected Success"), + {ok, State#{dontroute => New}}; + {error, eopnotsupp = Reason} -> + ?SEV_EPRINT("Expected Failure: ~p", + [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "Verify changed value", + cmd => fun(#{sock := Sock, dontroute := Val} = _State) -> + case Get(Sock) of + {ok, Val} -> + ?SEV_IPRINT("Expected Success"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + + %% *** Termination *** + #{desc => "close UDP socket", + cmd => fun(#{sock := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(sock, State0), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option error. PLACEHOLDER! + +api_opt_sock_error(suite) -> + []; +api_opt_sock_error(doc) -> + []; +api_opt_sock_error(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_error, + fun() -> not_yet_implemented() end, + fun() -> ok end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option keepalive. +%% This is bit tricky to test, partly because we have no control over +%% the underlying TCP timeouts. So, for now, we just test that we can +%% change the value. + +api_opt_sock_keepalive(suite) -> + []; +api_opt_sock_keepalive(doc) -> + []; +api_opt_sock_keepalive(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_keepalive, + fun() -> has_support_sock_keepalive() end, + fun() -> api_opt_sock_keepalive() end). + + +api_opt_sock_keepalive() -> + Opt = keepalive, + Set = fun(S, Val) when is_boolean(Val) -> + socket:setopt(S, socket, Opt, Val) + end, + Get = fun(S) -> + socket:getopt(S, socket, Opt) + end, + + TesterSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name, + addr := Addr, + broadaddr := BAddr}} -> + ?SEV_IPRINT("local host info: " + "~n Name: ~p" + "~n Addr: ~p" + "~n Broadcast Addr: ~p", + [Name, Addr, BAddr]), + LSA = #{family => Domain, + addr => Addr}, + BSA = #{family => Domain, + addr => BAddr}, + {ok, State#{lsa => LSA, + bsa => BSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "create TCP socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "Get current value", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock) of + {ok, Val} when is_boolean(Val) -> + ?SEV_IPRINT("Success: ~p", [Val]), + {ok, State#{keepalive => Val}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "Try change the value", + cmd => fun(#{sock := Sock, keepalive := Current} = State) -> + New = not Current, + ?SEV_IPRINT("Try change value from ~p to ~p", + [Current, New]), + case Set(Sock, New) of + ok -> + ?SEV_IPRINT("Expected Success"), + {ok, State#{keepalive => New}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected Failure: ~p", + [Reason]), + ERROR + end + end}, + #{desc => "Verify (new) current value", + cmd => fun(#{sock := Sock, keepalive := Val} = _State) -> + case Get(Sock) of + {ok, Val} -> + ?SEV_IPRINT("Expected Success (~p)", [Val]), + ok; + {ok, OtherVal} -> + ?SEV_IPRINT("Unexpected Success: ~p", + [OtherVal]), + {error, {unexpected_success_value, + Val, OtherVal}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected failure: ~p", + [Reason]), + ERROR + end + end}, + + %% *** Termination *** + #{desc => "close UDP socket", + cmd => fun(#{sock := Sock} = State0) -> + socket:close(Sock), + State1 = maps:remove(sock, State0), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = inet, + + i("start tester evaluator"), + InitState = #{domain => Domain}, + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option linger. PLACEHOLDER! + +api_opt_sock_linger(suite) -> + []; +api_opt_sock_linger(doc) -> + []; +api_opt_sock_linger(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_linger, + fun() -> not_yet_implemented() end, + fun() -> ok end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the socket option mark. PLACEHOLDER! + +api_opt_sock_mark(suite) -> + []; +api_opt_sock_mark(doc) -> + []; +api_opt_sock_mark(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_opt_sock_mark, + fun() -> not_yet_implemented() end, + fun() -> ok end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case tries to test that the oobinline socket 'socket' option +%% works. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, socket, oobinline, boolean()). +%% +%% This works on linux of some version (atleast linux kernel 4.15.0), +%% but not on FreeBSD (12) for some reason. Until we have figured out +%% exctly why, we skip a bunch of OSs... +%% +%% Do we need to make sure the two entities does not run in the same +%% process? This test case does not currently do that (which works in' +%% linux but maybe not in, say, FreeBSD). +%% + +api_opt_sock_oobinline(suite) -> + []; +api_opt_sock_oobinline(doc) -> + []; +api_opt_sock_oobinline(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_ooinline, + fun() -> + has_support_sock_oobinline(), + has_support_send_flag_oob(), + has_support_recv_flag_oob(), + is_valid_oobinline_platform() + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, oobinline, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, oobinline) + end, + Send = fun(Sock, Data, true) -> + socket:send(Sock, Data, [oob]); + (Sock, Data, false) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock, true) -> + socket:recv(Sock, 0, [oob]); + (Sock, false) -> + socket:recv(Sock) + end, + InitState = #{domain => inet, + proto => tcp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = do_api_opt_sock_oobinline(InitState) + end). + +%% Hopefully this is a temporary solution... +is_valid_oobinline_platform() -> + case os:type() of + {unix, linux} -> + ok; + + Type -> + %% Actually, all we know is that the + %% test case only work for linux, but + %% it *should* for FreeBSD and Solaris + %% also... + not_supported(Type) + end. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +do_api_opt_sock_oobinline(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, lport := Port}) -> + %% This is actually not used for unix domain socket + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** no oobinline *** + + #{desc => "await continue (verify no oobinline)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_oobinline) + end}, + #{desc => "verify no oobinline", + cmd => fun(#{csock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = _Value} -> + ?SEV_IPRINT("oobinline: ~p", [_Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected oobinline: ~p", + [Unexpected]), + {error, {unexpected_oobinline, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting oobinline:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (no oobinline)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, no_oobinline), + ok + end}, + + #{desc => "await continue (recv no oobinline)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "await plain data", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock, false) of + {ok, <<"a">>} -> + ?SEV_IPRINT("received plain data"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (plain data)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_plain), + ok + end}, + #{desc => "await oob data", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock, true) of + {ok, <<"b">>} -> + ?SEV_IPRINT("received oob data"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (oob data)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_oob), + ok + end}, + + %% *** oobinline *** + + #{desc => "await continue (enable oobinline)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, enable_oobinline) + end}, + #{desc => "enable oobinline", + cmd => fun(#{csock := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("oobinline enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed enable oobinline:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (oobinline)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, oobinline), + ok + end}, + + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "await (recv) data", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock, false) of + {ok, <<"ba">>} -> + ?SEV_IPRINT("received expected message: " + "both plain and oob data"), + ok; + {ok, BadMsg} -> + ?SEV_EPRINT("received unexpected message: ~p", + [BadMsg]), + {error, {unexpected_msg, BadMsg}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** First batch of data (no oobinline) *** + + #{desc => "await continue (send data)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send plain data", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, <<"a">>, false) + end}, + %% #{desc => "enable (socket & global) debug", + %% cmd => fun(#{sock := Sock}) -> + %% ok = socket:setopt(Sock, otp, debug, true), + %% ok = socket:debug(true) + %% end}, + #{desc => "send oob data", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, <<"b">>, true) + end}, + %% #{desc => "disable (socket) debug", + %% cmd => fun(#{sock := Sock}) -> + %% ok = socket:debug(true), + %% ok = socket:setopt(Sock, otp, debug, false) + %% end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + %% *** Second batch of data (oobinline) *** + + #{desc => "await continue (send data)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send plain data", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, <<"a">>, false) + end}, + #{desc => "send oob data", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, <<"b">>, true) + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + %% *** First batch of data (no oobinline) *** + + #{desc => "order server to continue (with verify no oobinline)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, verify_oobinline), + ok + end}, + #{desc => "await server ready (no oobinline)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, no_oobinline) + end}, + + #{desc => "order client to continue (with send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + #{desc => "await client ready (with send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send) + end}, + #{desc => "order server to continue (with recv plain)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv), + ok + end}, + #{desc => "await server ready (recv plain)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_plain) + end}, + #{desc => "await server ready (recv oob)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_oob) + end}, + + %% Second message (w timestamp) + + #{desc => "order server to continue (with enable oobinline)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, enable_oobinline), + ok + end}, + #{desc => "await server ready (oobinline)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, oobinline) + end}, + + #{desc => "order client to continue (with send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + #{desc => "await client ready (with send)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send) + end}, + #{desc => "order server to continue (with recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv), + ok + end}, + #{desc => "await server ready (recv plain)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the credentials control message header is received when +%% setting the socket 'socket' option true when using sendmsg/recvmsg +%% on an IPv4 TCP (stream) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, socket, passcred, boolean()). +%% +%% We *may* need to run the different entities (server and client) in +%% separate VM (os processes) for this to actually work. +%% As it is now, the client does *not* get any credentials! +%% Until this has been done, this case is skipped!. + +api_opt_sock_passcred_tcp4(suite) -> + []; +api_opt_sock_passcred_tcp4(doc) -> + []; +api_opt_sock_passcred_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_passcred_tcp4, + fun() -> has_support_sock_passcred(), + not_yet_implemented() + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, passcred, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, passcred) + end, + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => tcp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_sock_passcred_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_passcred_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, lport := Port}) -> + %% This is actually not used for unix domain socket + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** First message *** + + #{desc => "await (recv) request 1", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REQ}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Second message *** + + #{desc => "await (recv) request 2", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REQ}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Third message *** + + #{desc => "await (recv) request 3", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REQ}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** First message (default=wo passcred) *** + + #{desc => "await continue (verify timestamp off)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_passcred) + end}, + #{desc => "verify passcred off", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = _Value} -> + ?SEV_IPRINT("passcred: ~p", [_Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected passcred: ~p", + [Unexpected]), + {error, {unexpected_passcred, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting passcred:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (passcred off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, passcred_off), + ok + end}, + + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 1 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 1 (from server, wo passcred)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REP}} -> + ok; + {ok, {[], UnexpData}} -> + {error, {unexpected_reply_data, UnexpData}}; + {ok, {BadCMsgHdrs, ?BASIC_REP}} -> + {error, {unexpected_reply_cmsghdrs, + BadCMsgHdrs}}; + {ok, BadReply} -> + {error, {unexpected_reply, BadReply}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 1 (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Second message (w passcred) *** + + #{desc => "await continue (enable passcred)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, enable_passcred) + end}, + #{desc => "enable passcred", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("passcred enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed enable passcred:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (passcred on)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, passcred_on), + ok + end}, + + #{desc => "await continue (send request 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 2 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 2 (from server, w passcred)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + %% socket:setopt(Sock, otp, debug, true), + case Recv(Sock) of + {ok, {[#{level := socket, + type := passcred, + data := Cred}], ?BASIC_REP}} -> + %% socket:setopt(Sock, otp, debug, false), + ?SEV_IPRINT("received reply *with* " + "expected passcred: " + "~n ~p", [Cred]), + ok; + {ok, {BadCMsgHdrs, ?BASIC_REP}} -> + %% socket:setopt(Sock, otp, debug, false), + {error, {unexpected_reply_cmsghdrs, + BadCMsgHdrs}}; + {ok, {[#{level := socket, + type := passcred, + data := _Cred}], BadData}} -> + %% socket:setopt(Sock, otp, debug, false), + {error, {unexpected_reply_data, + BadData}}; + {ok, BadReply} -> + %% socket:setopt(Sock, otp, debug, false), + {error, {unexpected_reply, BadReply}}; + {error, _} = ERROR -> + %% socket:setopt(Sock, otp, debug, false), + ERROR + end + end}, + #{desc => "announce ready 2 (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Third message (wo passcred) *** + + #{desc => "await continue (disable passcred)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, disable_passcred) + end}, + #{desc => "disable passcred", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, false) of + ok -> + ?SEV_IPRINT("passcred disabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed disable timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (passcred off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, passcred_off), + ok + end}, + + #{desc => "await continue (send request 3)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 3 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 3 (from server, wo passcred)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REP}} -> + ?SEV_IPRINT("received reply *without* " + "passcred"), + ok; + {ok, {BadCMsgHdrs, ?BASIC_REP}} -> + {error, {unexpected_reply_cmsghdrs, + BadCMsgHdrs}}; + {ok, {[], BadData}} -> + {error, {unexpected_reply_data, + BadData}}; + {ok, BadReply} -> + {error, {unexpected_reply, BadReply}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 3 (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + %% *** First message (default=wo passcred) *** + + #{desc => "order client to continue (with verify timestamp off)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, verify_passcred), + ok + end}, + #{desc => "await client ready (passcred off)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, passcred_off) + end}, + + #{desc => "order client to continue (with send request 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + %% Second message (w passcred) + + #{desc => "order client to continue (with enable passcred)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, enable_passcred), + ok + end}, + #{desc => "await client ready (passcred on)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, passcred_on) + end}, + + #{desc => "order client to continue (with send request 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + %% Third message (wo passcred) + + #{desc => "order client to continue (with disable passcred)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, disable_passcred), + ok + end}, + #{desc => "await client ready (passcred off)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, passcred_off) + end}, + + #{desc => "order client to continue (with send request 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the the peek-off socket option for a unix domain socket +%% (stream TCP in this case). +%% +%% THIS IS A PLACEHOLDER!! +%% +%% + +api_opt_sock_peek_off_tcpL(suite) -> + []; +api_opt_sock_peek_off_tcpL(doc) -> + []; +api_opt_sock_peek_off_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_peek_off_tcpL, + fun() -> + has_support_unix_domain_socket(), + has_support_sock_peek_off(), + has_support_recv_flag_peek() + end, + fun() -> + Set = fun(Sock, Val) when is_integer(Val) -> + socket:setopt(Sock, socket, peek_off, Val) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, peek_off) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock, L, false) -> + socket:recv(Sock, L); + (Sock, L, true) -> + socket:recv(Sock, L, [peek]) + end, + InitState = #{domain => local, + proto => default, % Type = stream => tcp + set => Set, + get => Get, + send => Send, + recv => Recv}, + ok = api_opt_sock_peek_off(InitState) + end). + +api_opt_sock_peek_off(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, + lsa := LSA} = _State) -> + case sock_bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok + end}, + + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + + %% The actual test + + %% 1) peek (0 = everything: 1,2,3,4,5,6,7,8) + #{desc => "1a: await continue (peek)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, peek) + end}, + #{desc => "1a: peek read", + cmd => fun(#{csock := Sock, + recv := Recv} = _State) -> + case Recv(Sock, 0, true) of + {ok, <<1,2,3,4,5,6,7,8>>} -> + ?SEV_IPRINT("peek'ed expected data"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "1a: announce ready (peek)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, peek), + ok + end}, + + #{desc => "1b: await continue (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off) + end}, + #{desc => "1b: verify peek-off", + cmd => fun(#{csock := Sock, + get := Get} = _State) -> + case Get(Sock) of + {ok, DefaultPeekOff} -> + ?SEV_IPRINT("verify peek-off: ~w", + [DefaultPeekOff]), + ok; + {error, {not_supported, {socket, peek_off}}} -> + {skip, "Not supported"}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "1b: announce ready (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, verify_peek_off), + ok + end}, + + + %% 2) set peek-off to 4 + #{desc => "2a: await continue (set peek-off: 4)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, set_peek_off) + end}, + #{desc => "2a: set peek-off: 4", + cmd => fun(#{csock := Sock, + set := Set} = _State) -> + case Set(Sock, 4) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "2a: announce ready (set peek-off: 4)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, set_peek_off), + ok + end}, + + #{desc => "2b: await continue (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off) + end}, + #{desc => "2b: verify peek-off", + cmd => fun(#{csock := Sock, + get := Get} = _State) -> + case Get(Sock) of + {ok, 4 = PeekOff} -> + ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "2b: announce ready (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, verify_peek_off), + ok + end}, + + + %% 3) peek (0 = everything: 5,6,7,8) + %% NOTE THAT THIS WILL MOVE THE PEEK-OFF "POINTER" TO THE END OF + %% THE *CURRENT* DATA POSITION IN THE BUFFER (READY FOR NEXT BATCH + %% OF DATA). + #{desc => "3a: await continue (peek)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, peek) + end}, + #{desc => "3a: peek read", + cmd => fun(#{csock := Sock, + recv := Recv} = _State) -> + case Recv(Sock, 0, true) of + {ok, <<5,6,7,8>>} -> + ?SEV_IPRINT("peek'ed expected data"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "3a: announce ready (peek)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, peek), + ok + end}, + + #{desc => "3b: await continue (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off) + end}, + #{desc => "3b: verify peek-off", + cmd => fun(#{csock := Sock, + get := Get} = _State) -> + case Get(Sock) of + {ok, 8 = PeekOff} -> + ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "3b: announce ready (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, verify_peek_off), + ok + end}, + + + %% 4) read two byte(s): 1,2 + #{desc => "4a: await continue (read 2 byte)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, read) + end}, + #{desc => "4a: read (2 bytes)", + cmd => fun(#{csock := Sock, + recv := Recv} = _State) -> + case Recv(Sock, 2, false) of + {ok, <<1,2>>} -> + ?SEV_IPRINT("read expected data"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "4a: announce ready (read)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, read), + ok + end}, + + #{desc => "4b: await continue (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off) + end}, + #{desc => "4b: verify peek-off", + cmd => fun(#{csock := Sock, + get := Get} = _State) -> + case Get(Sock) of + {ok, 6 = PeekOff} -> + ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "4b: announce ready (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, verify_peek_off), + ok + end}, + + + %% 5) read the rest: 3,4,5,6,7,8) + #{desc => "5a: await continue (read the rest)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, read) + end}, + #{desc => "5a: read (the rest)", + cmd => fun(#{csock := Sock, + recv := Recv} = _State) -> + case Recv(Sock, 0, false) of + {ok, <<3,4,5,6,7,8>>} -> + ?SEV_IPRINT("read expected data"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "5a: announce ready (read)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, read), + ok + end}, + + #{desc => "5b: await continue (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_peek_off) + end}, + #{desc => "5b: verify peek-off", + cmd => fun(#{csock := Sock, + get := Get} = _State) -> + case Get(Sock) of + {ok, PeekOff} -> + ?SEV_IPRINT("verify peek-off: ~w", [PeekOff]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "5b: announce ready (verify peek-off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, verify_peek_off), + ok + end}, + + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock, + lsa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + + %% *** The actual test *** + #{desc => "await continue (send data)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_data) + end}, + #{desc => "send data (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, <<1:8/integer, + 2:8/integer, + 3:8/integer, + 4:8/integer, + 5:8/integer, + 6:8/integer, + 7:8/integer, + 8:8/integer>>) + end}, + #{desc => "announce ready (send data)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_data), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% Establish the connection + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + + %% *** The actual test *** + #{desc => "order client to continue (with send data)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_data), + ok + end}, + #{desc => "await client ready (with send data)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_data) + end}, + + %% There is no way to be sure that the data has actually arrived, + %% and with no data on the server side, the peek will fail. + %% Hopfully a sleep will take care of this... + ?SEV_SLEEP(?SECS(1)), + + %% 1) peek + #{desc => "1a: order server to continue (peek)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, peek), + ok + end}, + #{desc => "1a: await server ready (peek)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, peek) + end}, + + #{desc => "1b: order server to continue (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off), + ok + end}, + #{desc => "1b: await server ready (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, verify_peek_off) + end}, + + + %% 2) set peek-off + #{desc => "2a: order server to continue (set peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, set_peek_off), + ok + end}, + #{desc => "2a: await server ready (set peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, set_peek_off) + end}, + + #{desc => "2b: order server to continue (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off), + ok + end}, + #{desc => "2b: await server ready (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, verify_peek_off) + end}, + + + + %% 3) peek + #{desc => "3a: order server to continue (peek)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, peek), + ok + end}, + #{desc => "3a: await server ready (peek)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, peek) + end}, + + #{desc => "3b: order server to continue (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off), + ok + end}, + #{desc => "3b: await server ready (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, verify_peek_off) + end}, + + + + %% 4) read part + #{desc => "4a: order server to continue (read part)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, read), + ok + end}, + #{desc => "4a: await server ready (peek)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, read) + end}, + + #{desc => "4b: order server to continue (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off), + ok + end}, + #{desc => "4b: await server ready (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, verify_peek_off) + end}, + + + %% 5) read (the rest) + #{desc => "5a: order server to continue (read the rest)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, read), + ok + end}, + #{desc => "5a: await server ready (peek)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, read) + end}, + + #{desc => "5b: order server to continue (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, verify_peek_off), + ok + end}, + #{desc => "5b: await server ready (verify peek-off)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, verify_peek_off) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + i("await evaluator(s)"), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that we get the peer credentials for a connected unix domain +%% TCP (stream) socket. +%% That is, all we need to do is to create a slave node, and have +%% process connect from that to a local (unix domain socket) socket. +%% +%% THIS IS A PLACEHOLDER!! +%% +%% We need to figure out what the ucred structure looks like, +%% and decode it... +%% + +api_opt_sock_peercred_tcpL(suite) -> + []; +api_opt_sock_peercred_tcpL(doc) -> + []; +api_opt_sock_peercred_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_peercred_tcpL, + fun() -> + has_support_unix_domain_socket(), + has_support_sock_peercred(), + not_yet_implemented() + end, + fun() -> + Get = fun(Sock) -> + socket:getopt(Sock, socket, peercred) + end, + InitState = #{domain => local, + proto => default, % Type = stream => tcp + get => Get}, + ok = api_opt_sock_peercred_tcp(InitState) + end). + + +api_opt_sock_peercred_tcp(_InitState) -> + %% ServerSeq = + %% [ + %% %% *** Wait for start order part *** + %% #{desc => "await start (from tester)", + %% cmd => fun(State) -> + %% {Tester, Backlog} = ?SEV_AWAIT_START(), + %% {ok, State#{tester => Tester, + %% backlog => Backlog}} + %% end}, + %% #{desc => "monitor tester", + %% cmd => fun(#{tester := Tester} = _State) -> + %% _MRef = erlang:monitor(process, Tester), + %% ok + %% end}, + + %% %% *** Init part *** + %% #{desc => "which local address", + %% cmd => fun(#{domain := Domain} = State) -> + %% LSA = which_local_socket_addr(Domain), + %% {ok, State#{lsa => LSA}} + %% end}, + %% #{desc => "create listen socket", + %% cmd => fun(#{domain := Domain, proto := Proto} = State) -> + %% case socket:open(Domain, stream, Proto) of + %% {ok, Sock} -> + %% {ok, State#{lsock => Sock}}; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "bind to local address", + %% cmd => fun(#{domain := local, + %% lsock := LSock, + %% lsa := LSA} = _State) -> + %% case socket:bind(LSock, LSA) of + %% {ok, _Port} -> + %% ok; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "make listen socket", + %% cmd => fun(#{lsock := LSock}) -> + %% socket:listen(LSock) + %% end}, + %% #{desc => "announce ready (init)", + %% cmd => fun(#{domain := local, + %% tester := Tester, lsa := #{path := Path}}) -> + %% ?SEV_ANNOUNCE_READY(Tester, init, Path), + %% ok + %% end}, + + + %% %% The actual test + %% #{desc => "await continue (accept)", + %% cmd => fun(#{tester := Tester}) -> + %% ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + %% end}, + %% #{desc => "await connection", + %% cmd => fun(#{lsock := LSock} = State) -> + %% case socket:accept(LSock) of + %% {ok, Sock} -> + %% ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + %% {ok, State#{csock => Sock}}; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "announce ready (accept)", + %% cmd => fun(#{tester := Tester}) -> + %% ?SEV_ANNOUNCE_READY(Tester, accept), + %% ok + %% end}, + + %% #{desc => "await continue (peercred)", + %% cmd => fun(#{tester := Tester}) -> + %% ?SEV_AWAIT_CONTINUE(Tester, tester, peercred) + %% end}, + %% #{desc => "get peercred", + %% cmd => fun(#{csock := Sock, get := Get} = _State) -> + %% case Get(Sock) of + %% {ok, PeerCred} -> + %% ?SEV_IPRINT("PeerCred: ~n ~p", [PeerCred]), + %% ok; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "announce ready (peercred)", + %% cmd => fun(#{tester := Tester}) -> + %% ?SEV_ANNOUNCE_READY(Tester, peercred), + %% ok + %% end}, + + + %% %% Termination + %% #{desc => "await terminate", + %% cmd => fun(#{tester := Tester} = State) -> + %% case ?SEV_AWAIT_TERMINATE(Tester, tester) of + %% ok -> + %% {ok, maps:remove(tester, State)}; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "close connection socket", + %% cmd => fun(#{csock := Sock} = State) -> + %% ok = socket:close(Sock), + %% {ok, maps:remove(csock, State)} + %% end}, + %% #{desc => "close listen socket", + %% cmd => fun(#{domain := local, + %% lsock := Sock, + %% lsa := #{path := Path}} = State) -> + %% ok = socket:close(Sock), + %% State1 = + %% unlink_path(Path, + %% fun() -> + %% maps:remove(lsa, State) + %% end, + %% fun() -> State end), + %% {ok, maps:remove(lsock, State1)} + %% end}, + + %% %% *** We are done *** + %% ?SEV_FINISH_NORMAL + %% ], + + + %% ClientSeq = + %% [ + %% %% *** Wait for start order part *** + %% #{desc => "await start", + %% cmd => fun(#{domain := local} = State) -> + %% {Tester, Path} = ?SEV_AWAIT_START(), + %% {ok, State#{tester => Tester, + %% server_path => Path}} + %% end}, + %% #{desc => "monitor tester", + %% cmd => fun(#{tester := Tester} = _State) -> + %% _MRef = erlang:monitor(process, Tester), + %% ok + %% end}, + + + %% %% *** Init part *** + %% #{desc => "which local address", + %% cmd => fun(#{domain := local = Domain, + %% server_path := Path} = State) -> + %% LSA = which_local_socket_addr(Domain), + %% SSA = #{family => Domain, path => Path}, + %% {ok, State#{local_sa => LSA, server_sa => SSA}} + %% end}, + %% #{desc => "create node", + %% cmd => fun(#{host := Host} = State) -> + %% ?SEV_IPRINT("try create node on ~p", [Host]), + %% case start_node(Host, client) of + %% {ok, Node} -> + %% ?SEV_IPRINT("client node ~p started", + %% [Node]), + %% {ok, State#{node => Node}}; + %% {error, Reason} -> + %% {skip, Reason} + %% end + %% end}, + %% #{desc => "monitor client node", + %% cmd => fun(#{node := Node} = _State) -> + %% true = erlang:monitor_node(Node, true), + %% ok + %% end}, + %% #{desc => "start remote client on client node", + %% cmd => fun(#{node := Node} = State) -> + %% Pid = api_opt_sock_peercred_tcp_client_start(Node), + %% ?SEV_IPRINT("remote client ~p started", [Pid]), + %% {ok, State#{rclient => Pid}} + %% end}, + %% #{desc => "monitor remote client", + %% cmd => fun(#{rclient := Pid}) -> + %% _MRef = erlang:monitor(process, Pid), + %% ok + %% end}, + %% #{desc => "order remote client to start", + %% cmd => fun(#{rclient := Client, + %% proto := Proto, + %% server_sa := ServerSA}) -> + %% ?SEV_ANNOUNCE_START(Client, {Proto, ServerSA}), + %% ok + %% end}, + %% #{desc => "await remote client ready", + %% cmd => fun(#{tester := Tester, + %% rclient := Client} = _State) -> + %% ?SEV_AWAIT_READY(Client, rclient, init, + %% [{tester, Tester}]) + %% end}, + %% #{desc => "announce ready (init)", + %% cmd => fun(#{tester := Tester}) -> + %% ?SEV_ANNOUNCE_READY(Tester, init), + %% ok + %% end}, + + + %% %% The actual test + %% #{desc => "await continue (connect)", + %% cmd => fun(#{tester := Tester, + %% rclient := Client} = State) -> + %% case ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + %% [{rclient, Client}]) of + %% {ok, {ConTimeout, ConLimit}} -> + %% {ok, State#{connect_timeout => ConTimeout, + %% connect_limit => ConLimit}}; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "order remote client to continue (connect)", + %% cmd => fun(#{rclient := RClient, + %% connect_timeout := ConTimeout, + %% connect_limit := ConLimit}) -> + %% ?SEV_ANNOUNCE_CONTINUE(RClient, connect, + %% {ConTimeout, ConLimit}), + %% ok + %% end}, + %% #{desc => "await remote client ready (connect)", + %% cmd => fun(#{tester := Tester, + %% rclient := RClient} = State) -> + %% case ?SEV_AWAIT_READY(RClient, rclient, connect, + %% [{tester, Tester}]) of + %% {ok, ok = _Result} -> + %% {ok, maps:remove(connect_limit, State)}; + %% {ok, {error, {connect_limit_reached,R,L}}} -> + %% {skip, + %% ?LIB:f("Connect limit reached ~w: ~w", + %% [L, R])}; + %% {ok, Result} -> + %% Result; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "announce ready (connect)", + %% cmd => fun(#{tester := Tester}) -> + %% ?SEV_ANNOUNCE_READY(Tester, connect), + %% ok + %% end}, + + %% %% Termination + %% #{desc => "await terminate (from tester)", + %% cmd => fun(#{tester := Tester, + %% rclient := RClient} = State) -> + %% case ?SEV_AWAIT_TERMINATE(Tester, tester, + %% [{rclient, RClient}]) of + %% ok -> + %% {ok, maps:remove(tester, State)}; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "kill remote client", + %% cmd => fun(#{rclient := Client}) -> + %% ?SEV_ANNOUNCE_TERMINATE(Client), + %% ok + %% end}, + %% #{desc => "await remote client termination", + %% cmd => fun(#{rclient := Client} = State) -> + %% ?SEV_AWAIT_TERMINATION(Client), + %% State1 = maps:remove(rclient, State), + %% {ok, State1} + %% end}, + %% #{desc => "stop client node", + %% cmd => fun(#{node := Node} = _State) -> + %% stop_node(Node) + %% end}, + %% #{desc => "await client node termination", + %% cmd => fun(#{node := Node} = State) -> + %% receive + %% {nodedown, Node} -> + %% State1 = maps:remove(node_id, State), + %% State2 = maps:remove(node, State1), + %% {ok, State2} + %% end + %% end}, + + %% %% *** We are done *** + %% ?SEV_FINISH_NORMAL + %% ], + + %% TesterSeq = + %% [ + %% %% *** Init part *** + %% #{desc => "monitor server", + %% cmd => fun(#{server := Server} = _State) -> + %% _MRef = erlang:monitor(process, Server), + %% ok + %% end}, + %% #{desc => "monitor client", + %% cmd => fun(#{client := Client} = _State) -> + %% _MRef = erlang:monitor(process, Client), + %% ok + %% end}, + %% #{desc => "which local address", + %% cmd => fun(#{domain := Domain} = State) -> + %% LSA = which_local_socket_addr(Domain), + %% {ok, State#{local_sa => LSA}} + %% end}, + %% #{desc => "order server start", + %% cmd => fun(#{server := Server, + %% backlog := Backlog}) -> + %% ?SEV_ANNOUNCE_START(Server, Backlog), + %% ok + %% end}, + %% #{desc => "await server ready (init)", + %% cmd => fun(#{server := Server, local_sa := LSA} = State) -> + %% {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), + %% ServerSA = LSA#{port => Port}, + %% {ok, State#{server_sa => ServerSA}} + %% end}, + %% #{desc => "order client start", + %% cmd => fun(#{client := Client, + %% server_sa := ServerSA}) -> + %% ?SEV_ANNOUNCE_START(Client, ServerSA), + %% ok + %% end}, + %% #{desc => "await client ready (init)", + %% cmd => fun(#{client := Client} = _State) -> + %% ?SEV_AWAIT_READY(Client, client, init), + %% ok + %% end}, + + + %% %% The actual test + %% %% The server accepts the connect from the client, announces + %% %% this to us (accept) and then attempts to get peercred. + %% #{desc => "order client continue (connect)", + %% cmd => fun(#{client := Client, + %% timeout := Timeout, + %% connect_limit := ConLimit} = _State) -> + %% ?SEV_ANNOUNCE_CONTINUE(Client, connect, + %% {Timeout, ConLimit}), + %% ok + %% end}, + %% #{desc => "await client ready (connect)", + %% cmd => fun(#{server := Server, + %% client := Client} = _State) -> + %% case ?SEV_AWAIT_READY(Client, client, connect, + %% [{server, Server}]) of + %% ok -> + %% ok; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "await server ready (accept)", + %% cmd => fun(#{server := Server, + %% client := Client} = _State) -> + %% case ?SEV_AWAIT_READY(Server, server, accept, + %% [{client, Client}]) of + %% ok -> + %% ok; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + %% #{desc => "await server ready (peercred)", + %% cmd => fun(#{server := Server, + %% client := Client} = _State) -> + %% case ?SEV_AWAIT_READY(Server, server, peercred, + %% [{client, Client}]) of + %% ok -> + %% ok; + %% {error, _} = ERROR -> + %% ERROR + %% end + %% end}, + + + %% %% *** Terminate server *** + %% #{desc => "order client terminate", + %% cmd => fun(#{client := Client} = _State) -> + %% ?SEV_ANNOUNCE_TERMINATE(Client), + %% ok + %% end}, + %% #{desc => "await client down", + %% cmd => fun(#{client := Client} = State) -> + %% ?SEV_AWAIT_TERMINATION(Client), + %% State1 = maps:remove(client, State), + %% {ok, State1} + %% end}, + %% #{desc => "order server terminate", + %% cmd => fun(#{server := Server} = _State) -> + %% ?SEV_ANNOUNCE_TERMINATE(Server), + %% ok + %% end}, + %% #{desc => "await server down", + %% cmd => fun(#{server := Server} = State) -> + %% ?SEV_AWAIT_TERMINATION(Server), + %% State1 = maps:remove(server, State), + %% State2 = maps:remove(server_sa, State1), + %% {ok, State2} + %% end}, + + %% %% *** We are done *** + %% ?SEV_FINISH_NORMAL + %% ], + + %% i("create server evaluator"), + %% ServerInitState = #{domain => maps:get(domain, InitState)}, + %% Server = ?SEV_START("server", ServerSeq, ServerInitState), + + %% i("create client evaluator"), + %% ClientInitState = #{host => local_host(), + %% domain => maps:get(domain, InitState)}, + %% Client = ?SEV_START("client", ClientSeq, ClientInitState), + + %% i("create tester evaluator"), + %% TesterInitState = InitState#{server => Server#ev.pid, + %% client => Client#ev.pid}, + %% Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + %% i("await evaluator(s)"), + %% ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + %% This should actually never be called (the conditions should cause a skip), + %% but just to be on the safe side... + skip. + + +%% api_opt_sock_peercred_tcp_client_start(Node) -> +%% Self = self(), +%% Fun = fun() -> api_opt_sock_peercred_tcp_client(Self) end, +%% erlang:spawn(Node, Fun). + +%% api_opt_sock_peercred_tcp_client(Parent) -> +%% api_opt_sock_peercred_tcp_client_init(Parent), +%% {Proto, ServerSA} = api_opt_sock_peercred_tcp_client_await_start(Parent), +%% Domain = maps:get(family, ServerSA), +%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, init), +%% api_opt_sock_peercred_tcp_client_await_continue(Parent, connect), +%% Result = api_opt_sock_peercred_tcp_client_connect(Domain, Proto, ServerSA), +%% ?SEV_IPRINT("result: ~p", [Result]), +%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, connect, Result), +%% Reason = api_opt_sock_peercred_tcp_client_await_terminate(Parent), +%% api_opt_sock_peercred_tcp_client_close(Result), +%% exit(Reason). + +%% api_opt_sock_peercred_tcp_client_init(Parent) -> +%% put(sname, "rclient"), +%% _MRef = erlang:monitor(process, Parent), +%% ok. + +%% api_opt_sock_peercred_tcp_client_await_start(Parent) -> +%% ?SEV_AWAIT_START(Parent). + +%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, Slogan) -> +%% ?SEV_ANNOUNCE_READY(Parent, Slogan). +%% api_opt_sock_peercred_tcp_client_announce_ready(Parent, Slogan, Result) -> +%% ?SEV_ANNOUNCE_READY(Parent, Slogan, Result). + +%% api_opt_sock_peercred_tcp_client_await_continue(Parent, Slogan) -> +%% case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of +%% ok -> +%% ok; +%% {ok, Extra} -> +%% Extra; +%% {error, Reason} -> +%% exit({await_continue, Slogan, Reason}) +%% end. + +%% api_opt_sock_peercred_tcp_client_await_terminate(Parent) -> +%% case ?SEV_AWAIT_TERMINATE(Parent, parent) of +%% ok -> +%% ok; +%% {error, Reason} -> +%% Reason +%% end. + +%% api_opt_sock_peercred_tcp_client_connect(Domain, Proto, ServerSA) -> +%% LSA = which_local_socket_addr(Domain), +%% Sock = case socket:open(Domain, stream, Proto) of +%% {ok, S} -> +%% S; +%% {error, OReason} -> +%% ?FAIL({open, OReason}) +%% end, +%% case socket:bind(Sock, LSA) of +%% {ok, _} -> +%% ok; +%% {error, BReason} -> +%% (catch socket:close(Sock)), +%% ?FAIL({bind, BReason}) +%% end, +%% case socket:connect(Sock, ServerSA) of +%% ok -> +%% {ok, Sock}; +%% {error, Reason} -> +%% (catch socket:close(Sock)), +%% ?FAIL({connect, Reason}) +%% end. + +%% api_opt_sock_peercred_tcp_client_close({ok, Sock}) -> +%% (catch socket:close(Sock)); +%% api_opt_sock_peercred_tcp_client_close(_) -> +%% ok. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'PRIORITY' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, priority, integer()). +%% +%% + +api_opt_sock_priority_udp4(suite) -> + []; +api_opt_sock_priority_udp4(doc) -> + []; +api_opt_sock_priority_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_priority_udp4, + fun() -> has_support_sock_priority() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, priority, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, priority) + end, + InitState = #{domain => inet, + type => dgram, + proto => udp, + set => Set, + get => Get}, + ok = api_opt_sock_priority(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'PRIORITY' socket 'socket' option with IPv4 TCP: +%% +%% socket:setopt(Sock, socket, priority, integer()). +%% +%% + +api_opt_sock_priority_tcp4(suite) -> + []; +api_opt_sock_priority_tcp4(doc) -> + []; +api_opt_sock_priority_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_priority_tcp4, + fun() -> has_support_sock_priority() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, priority, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, priority) + end, + InitState = #{domain => inet, + type => stream, + proto => tcp, + set => Set, + get => Get}, + ok = api_opt_sock_priority(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_priority(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + + #{desc => "open socket", + cmd => fun(#{domain := Domain, + type := Type, + proto := Proto} = State) -> + Sock = sock_open(Domain, Type, Proto), + {ok, State#{sock => Sock}} + end}, + #{desc => "bind", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "get current (default) priority", + cmd => fun(#{sock := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, Prio} -> + ?SEV_IPRINT("(default) priority: ~p", + [Prio]), + {ok, State#{default => Prio}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) " + "priority:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "change priority (to within non-root range)", + cmd => fun(#{sock := Sock, + default := DefaultPrio, + set := Set} = _State) -> + NewPrio = + if + (DefaultPrio =< 0) andalso + (DefaultPrio < 6) -> + DefaultPrio+1; + (DefaultPrio =:= 6) -> + DefaultPrio-1; + true -> + 3 % ... + end, + ?SEV_IPRINT("try set new priority (to ~p)", + [NewPrio]), + case Set(Sock, NewPrio) of + ok -> + ?SEV_IPRINT("priority changed (to ~p)", + [NewPrio]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "change priority (to outside root-range)", + cmd => fun(#{sock := Sock, + set := Set} = _State) -> + NewPrio = 42, + ?SEV_IPRINT("try set new priority (to ~p)", + [NewPrio]), + case Set(Sock, NewPrio) of + ok -> + ?SEV_IPRINT("priority changed (to ~p)", + [NewPrio]), + ok; + {error, eperm} -> + ?SEV_IPRINT("priority change not allowed"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'SNDBUF' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, sndbuf, integer()). +%% +%% + +api_opt_sock_rcvbuf_udp4(suite) -> + []; +api_opt_sock_rcvbuf_udp4(doc) -> + []; +api_opt_sock_rcvbuf_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_rcvbuf_udp4, + fun() -> has_support_sock_rcvbuf() end, + fun() -> + ok = api_opt_sock_buf_udp4(rcvbuf) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'RCVBUF' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, rcvbuf, integer()). +%% +%% + +api_opt_sock_sndbuf_udp4(suite) -> + []; +api_opt_sock_sndbuf_udp4(doc) -> + []; +api_opt_sock_sndbuf_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_sndbuf_udp4, + fun() -> has_support_sock_sndbuf() end, + fun() -> + ok = api_opt_sock_buf_udp4(sndbuf) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_buf_udp4(Opt) -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, Opt, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, Opt) + end, + InitState = #{domain => inet, + type => dgram, + proto => udp, + set => Set, + get => Get}, + ok = api_opt_sock_buf(InitState). + + +api_opt_sock_buf(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + + #{desc => "open socket", + cmd => fun(#{domain := Domain, + type := Type, + proto := Proto} = State) -> + Sock = sock_open(Domain, Type, Proto), + {ok, State#{sock => Sock}} + end}, + #{desc => "bind", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "get current (default) buffer size", + cmd => fun(#{sock := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, Sz} -> + ?SEV_IPRINT("(default) buffer: ~p", + [Sz]), + {ok, State#{default_sz => Sz}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) " + "buffer size:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "change buffer size (default + 1024)", + cmd => fun(#{sock := Sock, + default_sz := DefaultSz, + set := Set} = State) -> + NewSz = DefaultSz + 1024, + ?SEV_IPRINT("try set new buffer size to ~w", [NewSz]), + case Set(Sock, NewSz) of + ok -> + ?SEV_IPRINT("Buffer size change success", []), + {ok, State#{new_sz => NewSz}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed changing buffer size:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "validate buffer change", + cmd => fun(#{sock := Sock, + get := Get, + new_sz := ExpSz} = _State) -> + ?SEV_IPRINT("try validate buffer size (~w)", [ExpSz]), + case Get(Sock) of + {ok, Sz} when (Sz >= ExpSz) -> + ?SEV_IPRINT("buffer size validated:" + "~n Sz: ~w (~w)", [Sz, ExpSz]), + ok; + {ok, Sz} -> + ?SEV_EPRINT("buffer size invalid:" + "~n Sz: ~w" + "~n Expected Sz: ~w", [Sz, ExpSz]), + {error, {invalid_size, Sz, ExpSz}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed get buffer size:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'RCVTIMEO' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, rcvtimeo, #{sec => integer(), +%% usec => integer()}). +%% +%% We should really test that the receive behaves as expected, +%% but we don't (we just set the value and read it back...) +%% + +api_opt_sock_rcvtimeo_udp4(suite) -> + []; +api_opt_sock_rcvtimeo_udp4(doc) -> + []; +api_opt_sock_rcvtimeo_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_rcvtimeo_udp4, + fun() -> has_support_sock_rcvtimeo() end, + fun() -> + ok = api_opt_sock_timeo_udp4(rcvtimeo) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'SNDTIMEO' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, sndtimeo, integer()). +%% +%% + +api_opt_sock_sndtimeo_udp4(suite) -> + []; +api_opt_sock_sndtimeo_udp4(doc) -> + []; +api_opt_sock_sndtimeo_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_sndtimeo_udp4, + fun() -> has_support_sock_sndtimeo() end, + fun() -> + ok = api_opt_sock_timeo_udp4(sndtimeo) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_timeo_udp4(Opt) -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, Opt, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, Opt) + end, + InitState = #{domain => inet, + type => dgram, + proto => udp, + opt => Opt, + set => Set, + get => Get}, + ok = api_opt_sock_timeo(InitState). + + +api_opt_sock_timeo(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + + #{desc => "open socket", + cmd => fun(#{domain := Domain, + type := Type, + proto := Proto} = State) -> + Sock = sock_open(Domain, Type, Proto), + {ok, State#{sock => Sock}} + end}, + #{desc => "bind", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "get current (default) timeout", + cmd => fun(#{sock := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, #{sec := _, usec := _} = TO} -> + ?SEV_IPRINT("(default) timeout: ~p", [TO]), + {ok, State#{default_timeo => TO}}; + {error, enoprotoopt = Reason} -> + ?SEV_IPRINT("Failed getting (default) timeout:" + " ~p", [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) timeout:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "change timeout", + cmd => fun(#{sock := Sock, + default_timeo := #{sec := DefaultSec} = DefaultTO, + set := Set} = State) -> + NewTO = DefaultTO#{sec => DefaultSec + 100}, + ?SEV_IPRINT("try set new timeout to ~w", [NewTO]), + case Set(Sock, NewTO) of + ok -> + ?SEV_IPRINT("Timeout change success", []), + {ok, State#{new_timeo => NewTO}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed changing timeout:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "validate timeout change", + cmd => fun(#{sock := Sock, + get := Get, + new_timeo := #{sec := ExpSec} = ExpTO} = _State) -> + ?SEV_IPRINT("try validate timeout (~w)", [ExpTO]), + case Get(Sock) of + {ok, ExpTO} -> + ?SEV_IPRINT("timeout (exactly) validated"), + ok; + {ok, #{sec := Sec}} when (ExpSec =:= Sec) -> + %% For some reason OpenBSD "adjusts" the timeout, + %% so that usec does not (allways match) + ?SEV_IPRINT("timeout (approx) validated"), + ok; + {ok, TO} -> + ?SEV_EPRINT("timeout invalid:" + "~n Timeout: ~w" + "~n Expected Timeout: ~w", + [TO, ExpTO]), + {error, {invalid_timeo, TO, ExpTO}}; + {error, edom = Reason} -> + %% On OpenBSD (at least) its possible that if the value + %% is too far "out of bounds", this will be the result: + %% + %% "Numerical argument out of domain" + %% + ?SEV_IPRINT("Failed get timeout:" + " ~p", [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed get timeout:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'RCVLOWAT' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, rcvlowat, integer()). +%% +%% + +api_opt_sock_rcvlowat_udp4(suite) -> + []; +api_opt_sock_rcvlowat_udp4(doc) -> + []; +api_opt_sock_rcvlowat_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_rcvlowat_udp4, + fun() -> has_support_sock_rcvlowat() end, + fun() -> + ok = api_opt_sock_lowat_udp4(rcvlowat) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the 'SNDLOWAT' socket 'socket' option with IPv4 UDP: +%% +%% socket:setopt(Sock, socket, sndlowat, integer()). +%% +%% This is (currently) not changeable on linux (among others), +%% so we skip if we get ENOPROTOOPT when attempting a change. +%% + +api_opt_sock_sndlowat_udp4(suite) -> + []; +api_opt_sock_sndlowat_udp4(doc) -> + []; +api_opt_sock_sndlowat_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_sndlowat_udp4, + fun() -> has_support_sock_sndlowat() end, + fun() -> + ok = api_opt_sock_lowat_udp4(sndlowat) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_lowat_udp4(Opt) -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, Opt, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, Opt) + end, + InitState = #{domain => inet, + type => dgram, + proto => udp, + set => Set, + get => Get}, + ok = api_opt_sock_lowat(InitState). + + +api_opt_sock_lowat(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + + #{desc => "open socket", + cmd => fun(#{domain := Domain, + type := Type, + proto := Proto} = State) -> + Sock = sock_open(Domain, Type, Proto), + {ok, State#{sock => Sock}} + end}, + #{desc => "bind", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "get current (default) lowat", + cmd => fun(#{sock := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, LOWAT} -> + ?SEV_IPRINT("(default) lowat: ~p", + [LOWAT]), + {ok, State#{default_lowat => LOWAT}}; + {error, enoprotoopt = Reason} -> + ?SEV_IPRINT("Failed getting (default) lowat:" + " ~p", [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) lowat:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "change lowat ( + 1 )", + cmd => fun(#{sock := Sock, + default_lowat := DefaultLOWAT, + set := Set} = State) -> + NewLOWAT = DefaultLOWAT + 1, + ?SEV_IPRINT("try set new lowat to ~w", [NewLOWAT]), + case Set(Sock, NewLOWAT) of + ok -> + ?SEV_IPRINT("LOWAT change success", []), + {ok, State#{new_lowat => NewLOWAT}}; + {error, enoprotoopt} -> + ?SEV_IPRINT("LOWAT not changeable", []), + {skip, "Not changeable"}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed changing buffer size:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "validate lowat", + cmd => fun(#{sock := Sock, + get := Get, + new_lowat := ExpLOWAT} = _State) -> + ?SEV_IPRINT("try validate lowat (~w)", [ExpLOWAT]), + case Get(Sock) of + {ok, ExpLOWAT} -> + ?SEV_IPRINT("lowat validated:" + "~n LOWAT: ~w", [ExpLOWAT]), + ok; + {ok, LOWAT} -> + ?SEV_EPRINT("lowat invalid:" + "~n LOWAT: ~w" + "~n Expected LOWAT: ~w", + [LOWAT, ExpLOWAT]), + {error, {invalid_lowat, LOWAT, ExpLOWAT}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed get lowat:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the timestamp control message header is received when +%% setting the socket 'socket' option true when using sendmsg/recvmsg +%% on an IPv4 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, socket, timestamp, boolean()). +%% +%% All subsequent *received* messages will be timestamped. +%% + +api_opt_sock_timestamp_udp4(suite) -> + []; +api_opt_sock_timestamp_udp4(doc) -> + []; +api_opt_sock_timestamp_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_timestamp_udp4, + fun() -> has_support_sock_timestamp() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, timestamp, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, timestamp) + end, + Send = fun(Sock, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_sock_timestamp_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_timestamp_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + #{desc => "get current (default) timestamp for src socket", + cmd => fun(#{sock_src := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("src timestamp: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src timestamp: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + + #{desc => "send req (to dst) (WO TIMESTAMP)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + case Recv(Sock) of + {ok, {Dst, [], ?BASIC_REP}} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + #{desc => "enable timestamp on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst timestamp enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + + #{desc => "send req 1 (to dst) (W TIMESTAMP)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := socket, + type := timestamp, + data := TS}], ?BASIC_REQ}} -> + ?SEV_IPRINT("received req *with* " + "expected timestamp: " + "~n ~p", [TS]), + ok; + {ok, {Src, [#{level := Level, + type := Type, + data := Data} = CMsgHdr], ?BASIC_REQ}} -> + ?SEV_EPRINT("Unexpected control message header:" + "~n Level: ~p" + "~n Type: ~p" + "~n Data: ~p", + [Level, Type, Data]), + {error, {unexpected_cmsghdr, CMsgHdr}}; + {ok, {Src, CMsgHdrs, ?BASIC_REQ}} -> + ?SEV_EPRINT("Unexpected control message header(s):" + "~n CMsgHdrs: ~p", + [CMsgHdrs]), + {error, {unexpected_cmsghdrs, CMsgHdrs}}; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + case Recv(Sock) of + {ok, {Dst, [], ?BASIC_REP}} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + #{desc => "send req 2 (to dst) (W TIMESTAMP)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := socket, + type := timestamp, + data := TS}], ?BASIC_REQ}} -> + ?SEV_IPRINT("received req *with* " + "expected timestamp: " + "~n ~p", [TS]), + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + case Recv(Sock) of + {ok, {Dst, [], ?BASIC_REP}} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + #{desc => "disable timestamps on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, false) of + ok -> + ?SEV_IPRINT("dst timestamp disabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + + #{desc => "send req (to dst) (WO TIMESTAMP)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ?SEV_IPRINT("received req *without* timestamp"), + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + case Recv(Sock) of + {ok, {Dst, [], ?BASIC_REP}} -> + ok; + {ok, UnexpData} -> + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + #{desc => "close src socket", + cmd => fun(#{domain := local, + sock_src := Sock, + lsa_src := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> maps:remove(lsa_src, State) end, + fun() -> State end), + {ok, maps:remove(sock_src, State1)}; + (#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{domain := local, + sock_dst := Sock, + lsa_dst := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> maps:remove(lsa_dst, State) end, + fun() -> State end), + {ok, maps:remove(sock_dst, State1)}; + (#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the timestamp control message header is received when +%% setting the socket 'socket' option true when using sendmsg/recvmsg +%% on an IPv4 TCP (stream) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, socket, timestamp, boolean()). +%% +%% All subsequent *received* messages will be timestamped. +%% +%% There is no mention of this not working for TCP in the man page +%% on a SLES 11 SP4 machine (=> 3.0.101-108.87), but it does not +%% (we don't get a timestamp control message header when its enabled). +%% It also does not work on SLES 12 SP2 (=> 4.4.120-92.70), +%% so we start by skipping from that version (4.4.120) or older! +%% Don't actually know if its the distro or the (kernel) version... +%% + +api_opt_sock_timestamp_tcp4(suite) -> + []; +api_opt_sock_timestamp_tcp4(doc) -> + []; +api_opt_sock_timestamp_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_sock_timestamp_tcp4, + fun() -> + has_support_sock_timestamp(), + is_good_enough_linux({4,4,120}), + is_not_freebsd(), + is_not_openbsd(), + is_not_netbsd(), + is_not_darwin() + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, socket, timestamp, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, socket, timestamp) + end, + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => tcp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_sock_timestamp_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_sock_timestamp_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, lport := Port}) -> + %% This is actually not used for unix domain socket + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** First message *** + + #{desc => "await (recv) request 1", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REQ}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Second message *** + + #{desc => "await (recv) request 2", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REQ}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Third message *** + + #{desc => "await (recv) request 3", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REQ}} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** First message (default=wo timestamp) *** + + #{desc => "await continue (verify timestamp off)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, verify_timestamp) + end}, + #{desc => "verify timestamp off", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = _Value} -> + ?SEV_IPRINT("timestamp: ~p", [_Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected timestamp: ~p", + [Unexpected]), + {error, {unexpected_timestamp, Unexpected}}; + {error, enoprotoopt = Reason} -> + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (timestamp off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, timestamp_off), + ok + end}, + + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 1 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 1 (from server, wo timestamp)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REP}} -> + ok; + {ok, {[], UnexpData}} -> + {error, {unexpected_reply_data, UnexpData}}; + {ok, {BadCMsgHdrs, ?BASIC_REP}} -> + {error, {unexpected_reply_cmsghdrs, + BadCMsgHdrs}}; + {ok, BadReply} -> + {error, {unexpected_reply, BadReply}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 1 (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Second message (w timestamp) *** + + #{desc => "await continue (enable timestamp)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, enable_timestamp) + end}, + #{desc => "enable timestamp", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("timestamp enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed enable timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + %% Linux pecularity observed here... + %% Detected on Kernel 4.15.0-72 x96_64. + %% The option set to enable receiving timestamps just above + %% has failed to be effective down in "await recv reply 2 + %% (from server, w timestamp)" below, unless we put the + %% sleep between setting the option and informing + %% the writer that it shall write to the other socket end. + %% A sleep 1 ms improves a lot but does not remove + %% problem completely. Believe it or not. + ?SEV_SLEEP(100), + #{desc => "announce ready (timestamp on)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, timestamp_on), + ok + end}, + + #{desc => "await continue (send request 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 2 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 2 (from server, w timestamp)", + cmd => fun(#{sock := Sock, recv := Recv, get := Get}) -> + case Recv(Sock) of + {ok, {[#{level := socket, + type := timestamp, + data := TS}], ?BASIC_REP}} -> + ?SEV_IPRINT("received reply *with* " + "expected timestamp: " + "~n ~p", [TS]), + ok; + {ok, {[#{level := socket, + type := timestamp, + data := UTS}], BadData}} -> + ?SEV_EPRINT("received reply *with* " + "unexpected timestamp:" + "~n ~p" + "Current timestamp value:" + "~n ~p", + [UTS, Get(Sock)]), + {error, {unexpected_reply_data, BadData}}; + {ok, {BadCMsgHdrs, ?BASIC_REP}} -> + ?SEV_EPRINT("received reply *with* " + "unexpected cmsg headers:" + "~n ~p" + "Current timestamp value: " + "~n ~p", + [BadCMsgHdrs, Get(Sock)]), + {error, {unexpected_reply_cmsghdrs, + BadCMsgHdrs}}; + {ok, BadReply} -> + {error, {unexpected_reply, BadReply}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 2 (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Third message (wo timestamp) *** + + #{desc => "await continue (disable timestamp)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, disable_timestamp) + end}, + #{desc => "disable timestamp", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, false) of + ok -> + ?SEV_IPRINT("timestamp disabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed disable timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (timestamp off)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, timestamp_off), + ok + end}, + + #{desc => "await continue (send request 3)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request 3 (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply 3 (from server, wo timestamp)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, {[], ?BASIC_REP}} -> + ?SEV_IPRINT("received reply *without* " + "timestamp"), + ok; + {ok, {BadCMsgHdrs, ?BASIC_REP}} -> + {error, {unexpected_reply_cmsghdrs, + BadCMsgHdrs}}; + {ok, {[], BadData}} -> + {error, {unexpected_reply_data, + BadData}}; + {ok, BadReply} -> + {error, {unexpected_reply, BadReply}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready 3 (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, +%%% ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + %% *** First message (default=wo timestamp) *** + + #{desc => "order client to continue (with verify timestamp off)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, verify_timestamp), + ok + end}, + #{desc => "await client ready (timestamp off)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, timestamp_off) + end}, + + #{desc => "order client to continue (with send request 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + %% Second message (w timestamp) + + #{desc => "order client to continue (with enable timestamp)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, enable_timestamp), + ok + end}, + #{desc => "await client ready (timestamp on)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, timestamp_on) + end}, + + #{desc => "order client to continue (with send request 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + %% Third message (wo timestamp) + + #{desc => "order client to continue (with disable timestamp)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, disable_timestamp), + ok + end}, + #{desc => "await client ready (timestamp off)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, timestamp_off) + end}, + + #{desc => "order client to continue (with send request 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent 3)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the add_mambership and drop_membership ip options work. +%% We create one server and two clients. The server only send messages, +%% the clients only receives messages. +%% An UDP datagram is forbidden (RFC 1122) from having a source address +%% that is a multicast address (or a broadcast address). +%% So, the server create a socket "for sending" and the clients sockets +%% "for receiving". +%% Sending socket: Bound to the local address (and any port). +%% When sending, the dest will be the multicast address +%% and port of the receiving socket. +%% Receiving socket: Bound to the multicast address and port. +api_opt_ip_add_drop_membership(suite) -> + []; +api_opt_ip_add_drop_membership(doc) -> + ["OTP-15908 (ERL-980)"]; +api_opt_ip_add_drop_membership(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_ip_add_drop_membership, + fun() -> + has_support_ip_add_membership(), + has_support_ip_drop_membership(), + has_support_ip_multicast() + end, + fun() -> api_opt_ip_add_drop_membership() end). + + +api_opt_ip_add_drop_membership() -> + Set = fun(S, Key, Val) -> + socket:setopt(S, ip, Key, Val) + end, + AddMembership = fun(S, Val) -> Set(S, add_membership, Val) end, + DropMembership = fun(S, Val) -> Set(S, drop_membership, Val) end, + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, MSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, msa => MSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make recv socket reuse addr", + cmd => fun(#{sock := Sock} = _State) -> + case socket:setopt(Sock, socket, reuseaddr, true) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed set reuseaddr: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "bind recv socket to multicast address", + cmd => fun(#{sock := Sock, msa := MSA} = State) -> + case sock_bind(Sock, MSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to:" + "~n ~p", [Port]), + {ok, State#{msa => MSA#{port => Port}}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (add_membership)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, add_membership) + end}, + #{desc => "add membership", + cmd => fun(#{sock := Sock, + msa := #{addr := MAddr}, + local_sa := #{addr := Addr}} = State) -> + MReq = #{multiaddr => MAddr, + interface => Addr}, + ?SEV_IPRINT("try add membership to:" + "~n ~p", [MReq]), + case AddMembership(Sock, MReq) of + ok -> + ?SEV_IPRINT("membership added"), + {ok, State#{mreq => MReq}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed adding membership to: " + "~n ~p" + "~n Reason: ~p", + [MReq, Reason]), + ERROR + end + end}, + #{desc => "announce ready (add-membership)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, add_membership), + ok + end}, + + #{desc => "await continue (drop_membership)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, drop_membership) + end}, + #{desc => "drop membership", + cmd => fun(#{sock := Sock, + mreq := MReq} = State) -> + ?SEV_IPRINT("try drop membership from:" + "~n ~p", [MReq]), + case DropMembership(Sock, MReq) of + ok -> + ?SEV_IPRINT("membership dropped"), + {ok, maps:remove(mreq, State)}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed drop membership from: " + "~n ~p" + "~n Reason: ~p", + [MReq, Reason]), + ERROR + end + end}, + #{desc => "announce ready (drop-membership)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, drop_membership), + ok + end}, + + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid, msa := MSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, MSA), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, server, init) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Start of server failed: " + "~n ~p", [Reason]), + ERROR + end + end}, + + + %% *** The actual test *** + #{desc => "order server to continue (add-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, add_membership), + ok + end}, + #{desc => "await server ready (add-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, add_membership) + end}, + + #{desc => "order server to continue (drop-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, drop_membership), + ok + end}, + #{desc => "await server ready (drop-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, drop_membership) + end}, + + ?SEV_SLEEP(?SECS(1)), + + %% *** Termination *** + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + {ok, maps:remove(server, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Domain = inet, + i("get multicast address"), + MAddr = which_ip_multicast_address(), + MSA = #{family => Domain, addr => MAddr}, + + i("start server evaluator"), + ServerInitState = #{domain => Domain}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start tester evaluator"), + TesterInitState = #{domain => Domain, + msa => MSA, + server => Server#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester, Server]). + + + +which_ip_multicast_address() -> + which_multicast_address(inet). + +which_multicast_address(Domain) -> + case os:type() of + {unix, linux} -> + WhichMAddr = fun([_, _, MAddr]) -> MAddr end, + which_multicast_address2(Domain, WhichMAddr); + + {unix, sunos} -> + WhichMAddr = fun([_, MAddr, _]) -> MAddr end, + which_multicast_address2(Domain, WhichMAddr); + + Type -> + %% Actually, what is "not supported". is netstat! + not_supported({multicast, Type}) + end. + +%% Note that the 'netstat -g' table looks different on linux and SunOS +%% Linux: IfName - RefCnt - Group +%% SunOS: IfName - Group - RefCnt + +which_multicast_address2(Domain, WhichMAddr) -> + IfName = which_local_host_ifname(Domain), + %% On some platforms the netstat barfs out some crap on stderr + %% before the actual info... + case os:cmd("netstat -g 2>/dev/null | grep " ++ IfName) of + [] -> + %% Can't figure out if we support multicast or not... + not_supported(no_netstat); + NetstatGroupsStr -> + try + begin + NetstatGroups0 = string:tokens(NetstatGroupsStr, [$\n]), + NetstatGroups = [string:tokens(G, [$ ]) || + G <- NetstatGroups0], + MAddrs = [WhichMAddr(NetstatGroup) || + NetstatGroup <- NetstatGroups], + which_multicast_address3(Domain, MAddrs) + end + catch + throw:E:_ -> + throw(E); + C:E:S -> + not_supported({multicast, {C,E,S}}) + end + end. + +which_multicast_address3(_Domain, []) -> + not_supported({multicast, no_valid_addrs}); +which_multicast_address3(Domain, [MAddrStr|MAddrs]) -> + %% Even on linux some of these are not actually addresses, but + %% "host names", such as all-systems.mcast.net. But both + %% address strings, such as "224.0.0.251" and host name strings + %% gets translated into an address by the inet:inet:getaddr/2. + case inet:getaddr(MAddrStr, Domain) of + {ok, MAddr} -> + MAddr; + {error, _} -> + which_multicast_address3(Domain, MAddrs) + end. + +which_local_host_ifname(Domain) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{name := Name}} -> + Name; + {error, Reason} -> + not_supported({multicast, Reason}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the pktinfo control message header is received when +%% setting the socket 'ip' option pktinfo is set to true when using +%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ip, pktinfo, boolean()). +%% +%% For all subsequent *received* messages, the pktinfo control message +%% header will be with the message. +%% +%% Note that it *should* be possible to explicitly send pktinfo also, +%% but this have not yet been implemented (in socket), so that part +%% we do not test!! +%% + +api_opt_ip_pktinfo_udp4(suite) -> + []; +api_opt_ip_pktinfo_udp4(doc) -> + []; +api_opt_ip_pktinfo_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_pktinfo_udp4, + fun() -> has_support_ip_pktinfo() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ip, pktinfo, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ip, pktinfo) + end, + Send = fun(Sock, Data, Dest, default) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, Info) -> + %% We do not support this at the moment!!! + CMsgHdr = #{level => ip, + type => pktinfo, + data => Info}, + MsgHdr = #{addr => Dest, + ctrl => [CMsgHdr], + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ip_pktinfo_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_pktinfo_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default pktinfo for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst recvttl: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src recvtos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo (explicit) pktinfo)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + %% *** We do not *yet* support sending pktinfo *** + + %% #{desc => "send req (to dst) (w explicit pktinfo)", + %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + %% Send(Sock, ?BASIC_REQ, Dst, PktInfo) + %% end}, + %% #{desc => "recv req (from src) - wo pktinfo", + %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + %% case Recv(Sock) of + %% {ok, {Src, [], ?BASIC_REQ}} -> + %% ok; + %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Recv Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Recv CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Recv Msg: ~p", + %% [Src, BadSrc, + %% [], BadCHdrs, + %% ?BASIC_REQ, BadReq]), + %% {error, {unexpected_data, UnexpData}}; + %% {ok, UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Unexp Data: ~p", + %% [Src, [], ?BASIC_REQ, UnexpData]), + %% {error, {unexpected_data, UnexpData}}; + %% {error, _} = ERROR -> + %% %% At the moment there is no way to get + %% %% status or state for the socket... + %% ERROR + %% end + %% end}, + + #{desc => "enable pktinfo on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst pktinfo enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting pktinfo:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit pktinfo)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src) - w default pktinfo", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ip, + type := pktinfo, + data := #{addr := Addr, + ifindex := IfIdx, + spec_dst := SpecDst}}], + ?BASIC_REQ}} -> + ?SEV_IPRINT("Got (default) Pkt Info: " + "~n Addr: ~p" + "~n If Index: ~p" + "~n Spec Dst: ~p", + [Addr, IfIdx, SpecDst]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + %% *** We do not *yet* support sending pktinfo *** + + %% #{desc => "send req (to dst) (w explicit pktinfo)", + %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + %% Send(Sock, ?BASIC_REQ, Dst, PktInfo) + %% end}, + %% #{desc => "recv req (from src) - w ttl = 100", + %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + %% case Recv(Sock) of + %% {ok, {Src, [#{level := ip, + %% type := ttl, + %% data := PktInfo}], ?BASIC_REQ}} -> + %% ?SEV_IPRINT("Got Pkt Info: " + %% "~n ~p", [Info]), + %% ok; + %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Recv Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Recv CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Recv Msg: ~p", + %% [Src, BadSrc, + %% [], BadCHdrs, + %% ?BASIC_REQ, BadReq]), + %% {error, {unexpected_data, UnexpData}}; + %% {ok, UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Unexp Data: ~p", + %% [Src, [], ?BASIC_REQ, UnexpData]), + %% {error, {unexpected_data, UnexpData}}; + %% {error, _} = ERROR -> + %% %% At the moment there is no way to get + %% %% status or state for the socket... + %% ERROR + %% end + %% end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the options control message header is received when +%% setting the socket 'ip' option recvopts is set to true when using +%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ip, recvopts, boolean()). +%% +%% For all subsequent *received* messages, the options control message +%% header will be with the message. +%% +%% Note that it *should* be possible to explicitly send options also, +%% but this have not yet been implemented (in socket), so that part +%% we do not test!! +%% +%% +%% <NOTE> +%% +%% This test does not currently work. The recvopts is supposed to +%% result in a IP_OPTIONS control message header but does not! +%% So, exactly how we are suppose to use this option is unknown. +%% So, let the test code remain, but skip until we have figured out +%% how to test this. +%% +%% </NOTE> +%% + +api_opt_ip_recvopts_udp4(suite) -> + []; +api_opt_ip_recvopts_udp4(doc) -> + []; +api_opt_ip_recvopts_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_recvopts_udp4, + fun() -> + has_support_ip_recvopts(), + %% We also use the recvtos and timestamp options + %% in this test, so at least one of them must + %% be supported + has_support_ip_recvtos_and_or_sock_timestamp(), + not_yet_implemented() + + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ip, recvopts, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ip, recvopts) + end, + Send = fun(Sock, Data, Dest, default) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, Info) -> + %% We do not support this at the moment!!! + CMsgHdr = #{level => ip, + type => options, + data => Info}, + MsgHdr = #{addr => Dest, + ctrl => [CMsgHdr], + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ip_recvopts_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_recvopts_udp(InitState) -> + Seq = + [ + %% Start by figure out which of the ip:recvtos and/or socket:timestamp + %% options we can use. + #{desc => "test for ip:recvtos", + cmd => fun(State) -> + ?SEV_IPRINT("test for ip:recvtos"), + case socket:is_supported(options, ip, recvtos) of + true -> + ?SEV_IPRINT("use ip:recvtos"), + {ok, State#{recvtos => true}}; + false -> + ?SEV_IPRINT("do *not* use ip:recvtos"), + {ok, State#{recvtos => false}} + end + end}, + #{desc => "test for socket:timestamp", + cmd => fun(State) -> + ?SEV_IPRINT("test for socket:timestamp"), + case socket:is_supported(options, socket, timestamp) of + true -> + ?SEV_IPRINT("use socket:timestamp"), + {ok, State#{timestamp => true}}; + false -> + ?SEV_IPRINT("do *not* use socket:timestamp"), + {ok, State#{timestamp => false}} + end + end}, + + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default recvopts for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst recvopts: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src recvtos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo (explicit) options)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + %% *** We do not *yet* support sending options *** + + %% #{desc => "send req (to dst) (w explicit options)", + %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + %% Send(Sock, ?BASIC_REQ, Dst, Opts) + %% end}, + %% #{desc => "recv req (from src) - wo options", + %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + %% case Recv(Sock) of + %% {ok, {Src, [], ?BASIC_REQ}} -> + %% ok; + %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Recv Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Recv CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Recv Msg: ~p", + %% [Src, BadSrc, + %% [], BadCHdrs, + %% ?BASIC_REQ, BadReq]), + %% {error, {unexpected_data, UnexpData}}; + %% {ok, UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Unexp Data: ~p", + %% [Src, [], ?BASIC_REQ, UnexpData]), + %% {error, {unexpected_data, UnexpData}}; + %% {error, _} = ERROR -> + %% %% At the moment there is no way to get + %% %% status or state for the socket... + %% ERROR + %% end + %% end}, + + #{desc => "enable recvopts on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst recvopts enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting recvopts:" + " ~p", [Reason]), + ERROR + end + end}, + + %% This specific option, recvtos, is tested in another test case + %% Note that this may not actually be supported here!! + #{desc => "maybe enable ip:recvtos on dst socket", + cmd => fun(#{recvtos := true, sock_dst := Sock} = _State) -> + ?SEV_IPRINT("enable ip:recvtos"), + ok = socket:setopt(Sock, ip, recvtos, true); + (#{recvtos := false} = _State) -> + ok + end}, + %% This specific option, timestamp, is tested in another test case + #{desc => "maybe enable socket:timestamp on dst socket", + cmd => fun(#{timestamp := true, sock_dst := Sock} = _State) -> + ?SEV_IPRINT("enable socket:timestamp"), + ok = socket:setopt(Sock, socket, timestamp, true); + (#{timestamp := false} = _State) -> + ok + end}, + + #{desc => "send req (to dst) (wo explicit options)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src) - w default options", + cmd => fun(#{recvtos := true, timestamp := true, + sock_dst := Sock, + sa_src := Src, + recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, Opts, ?BASIC_REQ}} + when (length(Opts) =:= 2) -> + ?SEV_IPRINT("Got (default) Options: " + "~n Opts: ~p", [Opts]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end; + (#{timestamp := true, + sock_dst := Sock, + sa_src := Src, + recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, Opts, ?BASIC_REQ}} + when (length(Opts) =:= 1) -> + ?SEV_IPRINT("Got (default) Options: " + "~n Opts: ~p", [Opts]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end; + (#{recvtos := true, + sock_dst := Sock, + sa_src := Src, + recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, Opts, ?BASIC_REQ}} + when (length(Opts) =:= 1) -> + ?SEV_IPRINT("Got (default) Options: " + "~n Opts: ~p", [Opts]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + %% *** We do not *yet* support sending options *** + + %% #{desc => "send req (to dst) (w explicit options)", + %% cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + %% Send(Sock, ?BASIC_REQ, Dst, Opts) + %% end}, + %% #{desc => "recv req (from src) - w options", + %% cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + %% case Recv(Sock) of + %% {ok, {Src, Opts, ?BASIC_REQ}} -> + %% ?SEV_IPRINT("Got Options: " + %% "~n ~p", [Opts]), + %% ok; + %% {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Recv Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Recv CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Recv Msg: ~p", + %% [Src, BadSrc, + %% [], BadCHdrs, + %% ?BASIC_REQ, BadReq]), + %% {error, {unexpected_data, UnexpData}}; + %% {ok, UnexpData} -> + %% ?SEV_EPRINT("Unexpected msg: " + %% "~n Expect Source: ~p" + %% "~n Expect CHdrs: ~p" + %% "~n Expect Msg: ~p" + %% "~n Unexp Data: ~p", + %% [Src, [], ?BASIC_REQ, UnexpData]), + %% {error, {unexpected_data, UnexpData}}; + %% {error, _} = ERROR -> + %% %% At the moment there is no way to get + %% %% status or state for the socket... + %% ERROR + %% end + %% end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the origdstaddr control message header is received when +%% setting the socket 'ip' option recvorigdstaddr is set to true when +%% using sendmsg/recvmsg on an IPv4 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ip, recvorigdstaddr, boolean()). +%% +%% For all subsequent *received* messages, the origdstaddr control +%% message header will be with the message. +%% +%% + +api_opt_ip_recvorigdstaddr_udp4(suite) -> + []; +api_opt_ip_recvorigdstaddr_udp4(doc) -> + []; +api_opt_ip_recvorigdstaddr_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_recvorigdstaddr_udp4, + fun() -> has_support_ip_recvorigdstaddr() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ip, recvorigdstaddr, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ip, recvorigdstaddr) + end, + Send = fun(Sock, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ip_recvorigdstaddr_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_recvorigdstaddr_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "get default recvorigdstaddr for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst recvorigdstaddr: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src recvorigdstaddr: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) " + "recvorigdstaddr:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (when recvorigdstaddr disabled)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src) - wo origdstaddr", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "enable recvorigdstaddr on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst recvorigdstaddr enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed enable recvorigdstaddr:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (when recvorigdstaddr enabled)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src) - w origdstaddr", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ip, + type := origdstaddr, + data := Addr}], ?BASIC_REQ}} -> + ?SEV_IPRINT("got origdstaddr " + "control message header: " + "~n ~p", [Addr]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [#{level => ip, + type => origdstaddr, + data => "something"}], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, + [#{level => ip, + type => origdstaddr, + data => "something"}], + ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the tos control message header is received when +%% setting the socket 'ip' option recvtos is set to true when using +%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ip, recvtos, boolean()). +%% +%% For all subsequent *received* messages, the tos control message +%% header will be with the message. +%% +%% On some platforms it works sending TOS with the message (sendmsg with +%% a control message header), but since its not universal, we can't use +%% that method. Instead, set tos (true) on the sending socket. +%% + +api_opt_ip_recvtos_udp4(suite) -> + []; +api_opt_ip_recvtos_udp4(doc) -> + []; +api_opt_ip_recvtos_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_recvtos_udp4, + fun() -> has_support_ip_recvtos() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ip, recvtos, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ip, recvtos) + end, + Send = fun(Sock, Data, Dest, default) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, TOS) -> + CMsgHdr = #{level => ip, + type => tos, + data => TOS}, + MsgHdr = #{addr => Dest, + ctrl => [CMsgHdr], + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ip_recvtos_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_recvtos_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default recvtos for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst recvtos: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src recvtos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit tos)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "set tos = mincost on src sock", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:setopt(Sock, ip, tos, mincost) + end}, + #{desc => "send req (to dst) (w tos = mincost)", + cmd => fun(#{sock_src := Sock, + sa_dst := Dst, + send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + + %% #{desc => "send req (to dst) (w explicit tos = mincost)", + %% cmd => fun(#{sock_src := Sock, + %% sa_dst := Dst, + %% send := Send}) -> + %% socket:setopt(Sock, otp, debug, true), + %% case Send(Sock, ?BASIC_REQ, Dst, mincost) of + %% ok -> + %% socket:setopt(Sock, otp, debug, false), + %% ok; + %% {error, Reason} -> + %% ?SEV_EPRINT("Failed sending message with tos: " + %% "~n Reason: ~p", [Reason]), + %% socket:setopt(Sock, otp, debug, false), + %% {skip, "Failed sending message with TOS"} + %% end + %% end}, + + #{desc => "recv req (from src) - wo explicit tos", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "set tos = 0 on src sock (\"disabled\")", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:setopt(Sock, ip, tos, 0) + end}, + + #{desc => "enable recvtos on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst recvtos enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting recvtos:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit tos)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src) - w default tos", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ip, + type := TOS, + data := 0}], ?BASIC_REQ}} + when ((TOS =:= tos) orelse (TOS =:= recvtos)) -> + ?SEV_IPRINT("got default TOS (~w) " + "control message header", [TOS]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "set tos = mincost on src sock", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:setopt(Sock, ip, tos, mincost) + end}, + + #{desc => "send req (to dst) (w tos = mincost)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src) - w tos = mincost", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ip, + type := TOS, + data := mincost = TOSData}], + ?BASIC_REQ}} + when ((TOS =:= tos) orelse (TOS =:= recvtos)) -> + ?SEV_IPRINT("got expected TOS (~w) = ~w " + "control message header", + [TOS, TOSData]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the ttl control message header is received when +%% setting the socket 'ip' option recvttl is set to true when using +%% sendmsg/recvmsg on an IPv4 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ip, recvttl, boolean()). +%% +%% For all subsequent *received* messages, the ttl control message +%% header will be with the message. +%% +%% On darwin we don't actually get the TTL we send even after we have +%% enabled TTL. Instead we get the default value (which was 64). +%% Possibly this is because we run the test in the same OS process and +%% even the same erlang process.... +%% The same issue on OpenBSD (6.6). +%% Maybe we should send and receive from different VMs, until then +%% skip darwin and OpenBSD. +%% + +api_opt_ip_recvttl_udp4(suite) -> + []; +api_opt_ip_recvttl_udp4(doc) -> + []; +api_opt_ip_recvttl_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_recvttl_udp4, + fun() -> + has_support_ip_recvttl(), + is_not_openbsd(), + is_not_darwin() + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ip, recvttl, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ip, recvttl) + end, + Send = fun(Sock, Data, Dest, default) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, TTL) -> + CMsgHdr = #{level => ip, + type => ttl, + data => TTL}, + MsgHdr = #{addr => Dest, + ctrl => [CMsgHdr], + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ip_recvttl_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_recvttl_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := local = Domain} = State) -> + LSASrc = which_local_socket_addr(Domain), + LSADst = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSASrc, + lsa_dst => LSADst}}; + (#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default recvttl for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst recvttl: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src recvttl: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) timestamp:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit ttl)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "send req (to dst) (w explicit ttl = 100)", + cmd => fun(#{sock_src := SSock, + sock_dst := DSock, sa_dst := Dst, + send := Send}) -> + case Send(SSock, ?BASIC_REQ, Dst, 100) of + ok -> + ok; + {error, einval = Reason} -> + %% IF we can't send it the test will not work + ?SEV_EPRINT("Cannot send TTL: " + "~p => SKIP", [Reason]), + (catch socket:close(SSock)), + (catch socket:close(DSock)), + {skip, + ?F("Cannot send with TTL: ~p", [Reason])}; + {error, enoprotoopt = Reason} -> + %% On some platforms this is not + %% accepted (FreeBSD), so skip. + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(SSock)), + (catch socket:close(DSock)), + {skip, Reason}; + {error, _Reason} = ERROR -> + ERROR + end + end}, + #{desc => "recv req (from src) - wo ttl", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {Src, [#{level := ip, + type := TTLType, + data := TTL}], ?BASIC_REQ}} + when ((TTLType =:= recvttl) andalso + (TTL =:= 255)) -> + %% This is the behaviopur on Solaris (11) + %% and maybe on other platforms... + ?SEV_IPRINT("Got (default) TTL (~w): ~p", + [TTLType, TTL]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "enable recvttl on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst recvttl enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed enabling recvttl:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit ttl)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src) - w default ttl", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ip, + type := TTLType, + data := TTL}], ?BASIC_REQ}} + when ((TTLType =:= ttl) orelse + (TTLType =:= recvttl)) -> + ?SEV_IPRINT("Got (default) TTL (~w): ~p", + [TTLType, TTL]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [#{level => ip, + type => ttl, + data => "something"}], + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [#{level => ip, + type => ttl, + data => "something"}], + ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "send req (to dst) (w explicit ttl = 100)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, 100) + end}, + #{desc => "recv req (from src) - w ttl = 100", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ip, + type := TTLType, + data := 100 = TTL}], ?BASIC_REQ}} + when ((TTLType =:= ttl) orelse + (TTLType =:= recvttl)) -> + ?SEV_IPRINT("Got TTL (~w): ~p", + [TTLType, TTL]), + ok; + {ok, {Src, [#{level := ip, + type := TTLType, + data := BadTTL}], ?BASIC_REQ}} + when ((TTLType =:= ttl) orelse + (TTLType =:= recvttl)) -> + ?SEV_EPRINT("Unexpected TTL: " + "~n Expect TTL: ~p" + "~n Recv TTL: ~p", + [100, BadTTL]), + {error, {unexpected_ttl, {100, BadTTL}}}; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [#{level => ip, + type => ttl, + data => 100}], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [#{level => ip, + type => ttl, + data => 100}], + ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the ip socket option 'tos' can be set and retrieved from a +%% the socket its set on. It sets the type-of-server field in the IP +%% header for a TCP or UDP socket. +%% There is no way to fetch the value a received IP datagram. +%% Default value is supposed to be '0'. +%% + +api_opt_ip_tos_udp4(suite) -> + []; +api_opt_ip_tos_udp4(doc) -> + []; +api_opt_ip_tos_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_tos_udp4, + fun() -> has_support_ip_tos() end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ip, tos, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ip, tos) + end, + InitState = #{set => Set, + get => Get}, + ok = api_opt_ip_tos_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_tos_udp(InitState) -> + process_flag(trap_exit, true), + %% mincost is not supported on all platforms. + %% For instance, Solaris 10, does not have that constant. + %% Instead it has two others with, what appers to be, + %% completely different meanings... + %% So, avoid the complication by not using this value... + %% TOS1 = mincost, TOS1Str = atom_to_list(TOS1), + TOS2 = throughput, TOS2Str = atom_to_list(TOS2), + TOS3 = reliability, TOS3Str = atom_to_list(TOS3), + TOS4 = lowdelay, TOS4Str = atom_to_list(TOS4), + TOS5 = 42, TOS5Str = integer_to_list(TOS5), + Seq = + [ + #{desc => "local address", + cmd => fun(State) -> + LSA = which_local_socket_addr(inet), + {ok, State#{lsa => LSA}} + end}, + + #{desc => "open socket", + cmd => fun(State) -> + Sock = sock_open(inet, dgram, udp), + {ok, State#{sock => Sock}} + end}, + #{desc => "bind", + cmd => fun(#{sock := Sock, lsa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("socket bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("bind failed: ~p", [Reason]), + ERROR + end + end}, + + #{desc => "get default tos", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, 0 = Value} -> + ?SEV_IPRINT("expected default tos: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected default tos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) tos:" + " ~p", [Reason]), + ERROR + end + end}, + + %% #{desc => "set tos " ++ TOS1Str, + %% cmd => fun(#{sock := Sock, set := Set} = _State) -> + %% socket:setopt(Sock, otp, debug, true), + %% case Set(Sock, TOS1) of + %% ok -> + %% socket:setopt(Sock, otp, debug, false), + %% ?SEV_IPRINT("tos set to ~p", [TOS1]), + %% ok; + %% {error, Reason} = ERROR -> + %% socket:setopt(Sock, otp, debug, false), + %% ?SEV_EPRINT("Failed setting tos:" + %% " ~p", [Reason]), + %% ERROR + %% end + %% end}, + %% #{desc => "get tos (expect " ++ TOS1Str ++ ")", + %% cmd => fun(#{sock := Sock, get := Get} = _State) -> + %% case Get(Sock) of + %% {ok, TOS1 = Value} -> + %% ?SEV_IPRINT("expected tos (~p)", [Value]), + %% ok; + %% {ok, Unexpected} -> + %% ?SEV_EPRINT("Unexpected tos: ~p", + %% [Unexpected]), + %% {error, {unexpected, Unexpected}}; + %% {error, Reason} = ERROR -> + %% ?SEV_EPRINT("Failed getting (default) tos:" + %% " ~p", [Reason]), + %% ERROR + %% end + %% end}, + + #{desc => "set tos " ++ TOS2Str, + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, TOS2) of + ok -> + ?SEV_IPRINT("tos set to ~p", [TOS2]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting tos:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "get tos (expect " ++ TOS2Str ++ ")", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, TOS2 = Value} -> + ?SEV_IPRINT("expected tos (~p)", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected tos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) tos:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "set tos " ++ TOS3Str, + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, TOS3) of + ok -> + ?SEV_IPRINT("tos set to ~p", [TOS3]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting tos:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "get tos (expect " ++ TOS3Str ++ ")", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, TOS3 = Value} -> + ?SEV_IPRINT("expected tos (~p)", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected tos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) tos:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "set tos " ++ TOS4Str, + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, TOS4) of + ok -> + ?SEV_IPRINT("tos set to ~p", [TOS4]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting tos:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "get tos (expect " ++ TOS4Str ++ ")", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, TOS4 = Value} -> + ?SEV_IPRINT("expected tos (~p)", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected tos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) tos:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "set tos " ++ TOS5Str, + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, TOS5) of + ok -> + ?SEV_IPRINT("tos set to ~p", [TOS5]), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting tos:" + " ~p", [Reason]), + ERROR + end + end}, + #{desc => "get tos (expect " ++ TOS5Str ++ ")", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, TOS5 = Value} -> + ?SEV_IPRINT("expected tos (~p)", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected tos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) tos:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the ip socket option 'recverr' can be set and that the error +%% queue can be read. +%% + +api_opt_ip_recverr_udp4(suite) -> + []; +api_opt_ip_recverr_udp4(doc) -> + []; +api_opt_ip_recverr_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_recverr_udp4, + fun() -> + has_support_ip_recverr() + end, + fun() -> + Set = fun(Sock, Key, Value) -> + socket:setopt(Sock, ip, Key, Value) + end, + Get = fun(Sock, Key) -> + socket:getopt(Sock, ip, Key) + end, + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest, [], nowait) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock, 0, [], nowait) + end, + InitState = #{domain => inet, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_recverr_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests the ipv6 socket option 'recverr' can be set and that the error +%% queue can be read. +%% + +api_opt_ipv6_recverr_udp6(suite) -> + []; +api_opt_ipv6_recverr_udp6(doc) -> + []; +api_opt_ipv6_recverr_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ipv6_recverr_udp6, + fun() -> + has_support_ipv6(), + has_support_ipv6_recverr() + end, + fun() -> + Set = fun(Sock, Key, Value) -> + socket:setopt(Sock, ipv6, Key, Value) + end, + Get = fun(Sock, Key) -> + socket:getopt(Sock, ipv6, Key) + end, + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest, [], nowait) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock, 0, [], nowait) + end, + InitState = #{domain => inet6, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_recverr_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_recverr_udp(InitState) -> + Seq = + [ + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + ?SEV_IPRINT("test for ip:recvtos"), + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind (to loopback)", + cmd => fun(#{sock := Sock} = _State) -> + case socket:bind(Sock, loopback) of + {ok, _} -> + ?SEV_IPRINT("bound"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "enable recverr", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + Set(Sock, recverr, true) + end}, + + #{desc => "disable mtu_discover", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + Set(Sock, mtu_discover, dont) + end}, + + #{desc => "try (async) read (=> select)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {select, SelectInfo} -> + ?SEV_IPRINT("expected select: " + "~n ~p", [SelectInfo]), + {ok, State#{rselect => SelectInfo}}; + {ok, _} -> + ?SEV_EPRINT("unexpected successs"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("unexpected error: ~p", [Reason]), + ERROR + end + end}, + + #{desc => "try (dummy) send", + cmd => fun(#{domain := Domain, sock := Sock, send := Send} = State) -> + Dest = #{family => Domain, + addr => if + (Domain =:= inet) -> + {127,0,0,1}; + (Domain =:= inet6) -> + {0,0,0,0,0,0,0,1} + end, + port => 1234}, + case Send(Sock, <<"ping">>, Dest) of + ok -> + ?SEV_IPRINT("sent"), + ok; + {select, SelectInfo} -> + ?SEV_IPRINT("expected select: ~p", + [SelectInfo]), + {ok, State#{sselect => SelectInfo}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("unexpected error: ~p", + [Reason]), + ERROR + end + end}, + + #{desc => "await select message", + cmd => fun(#{sock := Sock, + rselect := {select_info, _, Ref}} = _State) -> + receive + {'$socket', Sock, select, Ref} -> + ?SEV_IPRINT("received expected (read) select message: " + "~n ~p", [Ref]), + ok + end + end}, + + #{desc => "try recv - expect econnrefused", + cmd => fun(#{sock := Sock, recv := Recv} = _State) -> + case Recv(Sock) of + {error, econnrefused = Reason} -> + ?SEV_IPRINT("expected failure: ~p", [Reason]), + ok; + {ok, _} -> + ?SEV_EPRINT("unexpected successs"), + {error, unexpected_success}; + {select, SelectInfo} -> + ?SEV_EPRINT("unexpected select: ~p", + [SelectInfo]), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("unexpected error: ~p", + [Reason]), + ERROR + end + end}, + + #{desc => "try recv error queue", + cmd => fun(#{domain := Domain, sock := Sock}) -> + %% Note that not all platforms that support + %% recverr, actually supports "encoding" the data + %% part, so we need to adjust for that. + Origin = + if (Domain =:= inet) -> icmp; + (Domain =:= inet6) -> icmp6 + end, + Level = + if (Domain =:= inet) -> ip; + (Domain =:= inet6) -> ipv6 + end, + case socket:recvmsg(Sock, [errqueue]) of + {ok, #{addr := #{family := Domain, + addr := Addr}, + flags := [errqueue], + iov := [<<"ping">>], + ctrl := [#{level := Level, + type := recverr, + data := + #{code := port_unreach, + data := 0, + error := econnrefused, + info := 0, + offender := #{family := Domain, + addr := Addr}, + origin := Origin, + type := dest_unreach} + }]} = MsgHdr} -> + ?SEV_IPRINT("expected error queue (decoded): " + "~n ~p", [MsgHdr]), + ok; + {ok, #{addr := #{family := Domain, + addr := _Addr}, + flags := [errqueue], + iov := [<<"ping">>], + ctrl := [#{level := Level, + type := recverr, + data := _Data}]} = _MsgHdr} -> + ?SEV_IPRINT("expected error queue"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed reading error queue: " + "~n ~p", [Reason]), + ERROR + end + end}, + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This intended to test "all" of the (currently) supported IPv4 +%% options that results in control message header(s). +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ip, Flag, boolean()). +%% +%% For all subsequent *received* messages, a control message header +%% for each of the enabled options will be received with the message. +%% +%% Only allowed for dgram and raw, +%% although we only test this with dgram. +%% +%% Currently we *try* to use the following opts: +%% +%% pktinfo => pktinfo +%% recvorigdstaddr => origdstaddr +%% recvtos => tos +%% recvttl => ttl +%% +%% +%% Every time we add a test case for a new option (that results in +%% a control message hedare), we should also add it here. +%% +%% Even though this is a IPv4 test case, we add the 'socket' timestamp +%% option (just to fill up), but in the test to see if we should run +%% the test (since its a IPv4 test case). +%% + +api_opt_ip_mopts_udp4(suite) -> + []; +api_opt_ip_mopts_udp4(doc) -> + []; +api_opt_ip_mopts_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ip_mopts_udp4, + fun() -> + case is_any_options_supported( + [{ip, pktinfo}, + {ip, recvorigdstaddr}, + {ip, recvtos}, + {ip, recvttl}]) of + true -> + ok; + false -> + skip("None of the needed options are supported") + end + end, + fun() -> + %% If we get this far, we *know* that at least one of the + %% options are available. + + %% This is list of all the options and there resulting + %% control message header type(s): + %% [{level, + %% 'ipv6 socket option', + %% 'control message header type', + %% default | value()}] + Opts = + case socket:is_supported(options, socket, timestamp) of + true -> + [{socket, timestamp, timestamp, default}]; + false -> + [] + end ++ + case socket:is_supported(options, ip, pktinfo) of + true -> + [{ip, pktinfo, pktinfo, default}]; + false -> + [] + end ++ + case socket:is_supported(options, ip, recvorigdstaddr) of + true -> + [{ip, recvorigdstaddr, origdstaddr, default}]; + false -> + [] + end ++ + case socket:is_supported(options, ip, recvtos) of + true -> + %% It seems that sending any of the + %% TOS or TTL values will fail on: + %% FreeBSD + %% Linux when + %% version =< 3.12.60 (at least) + %% Don't know when this starts working, + %% but it works on: + %% Ubunto 16.04.6 => 4.15.0-65 + %% SLES 12 SP2 => 4.4.120-92.70 + %% so don't! + %% + %% The latest we know it not to work was a + %% SLES 12 (plain) at 3.12.50-52.54 + %% + [{ip, recvtos, tos, + case os:type() of + {unix, freebsd} -> + default; + {unix, linux} -> + case os:version() of + Vsn when Vsn > {3,12,60} -> + 42; + _ -> + default + end; + _ -> + 42 + end}]; + false -> + [] + end ++ + case os:type() of + {unix, darwin} -> + []; + _ -> + case socket:is_supported(options, ip, recvttl) of + true -> + %% It seems that sending any of the + %% TOS or TTL values will fail on: + %% FreeBSD and NetBSD + %% Linux when + %% version =< 3.12.60 (at least) + %% so don't! + %% See recvtos above for more info. + [{ip, recvttl, ttl, + case os:type() of + {unix, BSD} + when (BSD =:= freebsd) orelse + (BSD =:= netbsd) -> + default; + {unix, netbsd} -> + default; + {unix, linux} -> + case os:version() of + Vsn when Vsn > {3,12,60} -> + 42; + _ -> + default + end; + _ -> + 42 + end}]; + false -> + [] + end + end, + + Enable = fun(Sock, Level, Opt) -> + ?SEV_IPRINT("try enable [~w] ~p", [Level, Opt]), + socket:setopt(Sock, Level, Opt, true) + end, + Send = fun(Sock, Data, Dest, []) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, Hdrs) when is_list(Hdrs) -> + CMsgHdrs = [#{level => Level, + type => Type, + data => Val} || + {Level, Type, Val} <- Hdrs], + MsgHdr = #{addr => Dest, + ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + proto => udp, + opts => Opts, + send => Send, + recv => Recv, + enable => Enable}, + ok = api_opt_ip_mopts_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ip_mopts_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + + #{desc => "enable options on dst socket", + cmd => fun(#{sock_dst := DSock, + sock_src := SSock, + opts := Opts, + enable := Enable} = _State) -> + %% If we fail to enable *any* of the options, + %% we give up. + E = fun({Level, Opt, _, _}) -> + case Enable(DSock, Level, Opt) of + ok -> + ?SEV_IPRINT("dst [~w] ~w enabled", + [Level, Opt]), + ok; + {error, enoprotoopt = Reason} -> + ?SEV_EPRINT("Expected " + "Failure: " + "~p => SKIP", + [Reason]), + (catch socket:close(DSock)), + (catch socket:close(SSock)), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed " + "setting ~w:" + " ~p", + [Opt, Reason]), + throw(ERROR) + end + end, + lists:foreach(E, Opts), + ok + end}, + + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, + sa_dst := Dst, + opts := Opts, + send := Send}) -> + Hdrs = [{Level, Type, Data} || + {Level, _, Type, Data} <- + Opts, (Data =/= default)], + Send(Sock, ?BASIC_REQ, Dst, Hdrs) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, + sa_src := Src, + recv := Recv, + opts := Opts}) -> + case Recv(Sock) of + {ok, {Src, CMsgHdrs, ?BASIC_REQ}} + when length(CMsgHdrs) =:= length(Opts) -> + ?SEV_IPRINT("Got (expected) cmsg headers: " + "~n ~p", [CMsgHdrs]), + %% We should really verify the headers: + %% values, types and so on... + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [{Level, Type} || + {Level, _, Type, _} <- Opts], + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, + [{Level, Type} || + {Level, _, Type, _} <- Opts], + ?BASIC_REQ, + UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the IPv6 pktinfo control message header is received on +%% incoming datagrams (UDP and RAW) when setting the socket 'ipv6' +%% option recvpktinfo is set to true when using. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ipv6, recvpktinfo, boolean()). +%% +%% For all subsequent *received* messages, the pktinfo control message +%% header will be with the message. +%% +%% Only allowed for dgram and raw, +%% although we only test this with dgram. +%% + +api_opt_ipv6_recvpktinfo_udp6(suite) -> + []; +api_opt_ipv6_recvpktinfo_udp6(doc) -> + []; +api_opt_ipv6_recvpktinfo_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ipv6_recvpktinfo_udp6, + fun() -> + has_support_ipv6(), + has_support_ipv6_recvpktinfo() + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ipv6, recvpktinfo, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ipv6, recvpktinfo) + end, + Send = fun(Sock, Data, Dest, default) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, Info) -> + %% We do not support this at the moment!!! + CMsgHdr = #{level => ipv6, + type => pktinfo, + data => Info}, + MsgHdr = #{addr => Dest, + ctrl => [CMsgHdr], + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ipv6_recvpktinfo_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ipv6_recvpktinfo_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default pktinfo for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst recvttl: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src recvtos: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) recvtos:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo (explicit) pktinfo)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "enable pktinfo on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst pktinfo enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting pktinfo:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit pktinfo)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src) - w default pktinfo", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ipv6, + type := pktinfo, + data := #{addr := Addr, + ifindex := IfIdx}}], + ?BASIC_REQ}} -> + ?SEV_IPRINT("Got (default) Pkt Info: " + "~n Addr: ~p" + "~n If Index: ~p", + [Addr, IfIdx]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + #{level => ipv6, + type => pktinfo, + data => "something"} , BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the 'flow info' control message header is received when +%% setting the socket 'ipv6' option flowinfo is set to true when using +%% sendmsg/recvmsg on an IPv6 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ipv6, flowinfo, boolean()). +%% +%% For all subsequent *received* messages, the 'flow info' control message +%% header will be with the message. +%% +%% Only allowed for dgram and raw, +%% although we only test this with dgram. +%% +%% There seem to be some weirdness with the definition of this +%% option, so its defined in an include file we don't include +%% (directly or indirectly). And since some of the defines +%% are occure in a file we *do* include (via netinet/in.h), we +%% leave it as is for now... +%% + +api_opt_ipv6_flowinfo_udp6(suite) -> + []; +api_opt_ipv6_flowinfo_udp6(doc) -> + []; +api_opt_ipv6_flowinfo_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ipv6_flowinfo_udp6, + fun() -> + has_support_ipv6(), + has_support_ipv6_flowinfo() + end, + fun() -> + Set = fun(Sock, Value) -> + socket:setopt(Sock, ipv6, flowinfo, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, ipv6, flowinfo) + end, + Send = fun(Sock, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ipv6_flowinfo_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ipv6_flowinfo_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default flowinfo for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst flowinfo: ~p", [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src flowinfo: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) flowinfo:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "enable flowinfo on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst flowinfo enabled"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting flowinfo:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ipv6, + type := flowinfo, + data := FlowID}], ?BASIC_REQ}} -> + ?SEV_IPRINT("Got flow info: " + "~n Flow ID: ~p", [FlowID]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + #{level => ipv6, + type => flowinfo, + data => "something"}, + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the 'hop limit' control message header is received when +%% setting the socket 'ipv6' hoplimit or recvhoplimit option is set to +%% true when using sendmsg/recvmsg on an IPv6 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ipv6, recvhoplimit | hoplimit, boolean()). +%% +%% For all subsequent *received* messages, the 'hop limit' control message +%% header will be with the message. +%% We make the assumption, that if 'recvhoplimit' is supported, then +%% that option is used to order the hoplimit control message, otherwise +%% hoplimit is used. +%% +%% Only allowed for dgram and raw, +%% although we only test this with dgram. +%% +%% <Note> +%% +%% There is also an IPV6_RECVHOPLIMIT option defined in the header +%% file (bits/in.h) with a different value. This is not mentioned +%% in the man page. Deprecated? More testing needed... +%% +%% </Note> +%% + +api_opt_ipv6_hoplimit_udp6(suite) -> + []; +api_opt_ipv6_hoplimit_udp6(doc) -> + []; +api_opt_ipv6_hoplimit_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ipv6_hoplimit_udp6, + fun() -> + has_support_ipv6(), + has_support_ipv6_hoplimit_or_recvhoplimit(), + is_good_enough_darwin({9,8,0}) + end, + fun() -> + %% Begin by choosing which of the options we shall use + Opt = case socket:is_supported(options, ipv6, recvhoplimit) of + true -> recvhoplimit; + false -> hoplimit + end, + Set = fun(Sock, Value) -> + ?SEV_IPRINT("try set ~p: ~p", [Opt, Value]), + socket:setopt(Sock, ipv6, Opt, Value) + end, + Get = fun(Sock) -> + ?SEV_IPRINT("try get ~p", [Opt]), + socket:getopt(Sock, ipv6, Opt) + end, + Send = fun(Sock, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ipv6_hoplimit_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ipv6_hoplimit_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default [recv]hoplimit for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst [recv]hoplimit: ~p", + [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src [recv]hoplimit: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, enoprotoopt = Reason} -> + %% On some platforms this is not accepted + %% for UDP, so skip this part (UDP). + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(Sock)), + (catch socket:close(maps:get_value(sock_src, + State))), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) hoplimit:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "enable [recv]hoplimit on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst [recv]hoplimit enabled"), + ok; + {error, enoprotoopt = Reason} -> + %% On some platforms this is not accepted + %% for UDP, so skip this part (UDP). + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(Sock)), + (catch socket:close(maps:get_value(sock_src, + State))), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting hoplimit:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit ttl)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ipv6, + type := hoplimit, + data := HL}], ?BASIC_REQ}} + when is_integer(HL) -> + ?SEV_IPRINT("Got hop limit: " + "~n Hop Limit: ~p", [HL]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + #{level => ipv6, + type => hoplimit, + data => "something"}, + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, #{level => ipv6, + type => hoplimit, + data => "something"}, + ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the 'tclass' control message header is received when +%% setting the socket 'ipv6' tclass or recvtclass option is set to +%% true when using sendmsg/recvmsg on an IPv6 UDP (dgram) socket. +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ipv6, recvtclass | tclass, boolean()). +%% +%% For all subsequent *received* messages, the 'tclass' control message +%% header will be with the message. +%% We make the assumption, that if 'recvtclass' is supported, then +%% that option is used to order the tclass control message, otherwise +%% tclass is used. +%% +%% Only allowed for dgram and raw, +%% although we only test this with dgram. +%% +%% <Note> +%% +%% There is also an IPV6_RECVTCLASS option defined in the header +%% file (bits/in.h) with a different value. This is not mentioned +%% in the man page. Deprecated? More testing needed... +%% +%% </Note> +%% + +api_opt_ipv6_tclass_udp6(suite) -> + []; +api_opt_ipv6_tclass_udp6(doc) -> + []; +api_opt_ipv6_tclass_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ipv6_tclass_udp6, + fun() -> + has_support_ipv6(), + has_support_ipv6_tclass_or_recvtclass() + end, + fun() -> + %% Begin by choosing which of the options we shall use + Opt = case socket:is_supported(options, ipv6, recvtclass) of + true -> recvtclass; + false -> tclass + end, + Set = fun(Sock, Value) -> + ?SEV_IPRINT("try set ~p: ~p", [Opt, Value]), + socket:setopt(Sock, ipv6, Opt, Value) + end, + Get = fun(Sock) -> + ?SEV_IPRINT("try get ~p", [Opt]), + socket:getopt(Sock, ipv6, Opt) + end, + Send = fun(Sock, Data, Dest, default) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, TC) -> + TCHdr = #{level => ipv6, + type => tclass, + data => TC}, + CMsgHdrs = [TCHdr], + MsgHdr = #{addr => Dest, + ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + proto => udp, + send => Send, + recv => Recv, + set => Set, + get => Get}, + ok = api_opt_ipv6_tclass_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ipv6_tclass_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "default [recv]tclass for dst socket", + cmd => fun(#{sock_dst := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("dst [recv]tclass: ~p", + [Value]), + ok; + {ok, Unexpected} -> + ?SEV_EPRINT("Unexpected src [recv]tclass: ~p", + [Unexpected]), + {error, {unexpected, Unexpected}}; + {error, enoprotoopt = Reason} -> + %% On some platforms this is not accepted + %% for UDP, so skip this part (UDP). + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(Sock)), + (catch socket:close(maps:get_value(sock_src, + State))), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed getting (default) tclass:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [], ?BASIC_REQ}} -> + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [], BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, [], ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "enable [recv]tclass on dst socket", + cmd => fun(#{sock_dst := Sock, set := Set} = State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("dst [recv]tclass enabled"), + ok; + {error, enoprotoopt = Reason} -> + %% On some platforms this is not accepted + %% for UDP, so skip this part (UDP). + ?SEV_EPRINT("Expected Failure: " + "~p => SKIP", [Reason]), + (catch socket:close(Sock)), + (catch socket:close(maps:get_value(sock_src, + State))), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed setting tclass:" + " ~p", [Reason]), + ERROR + end + end}, + + #{desc => "send req (to dst) (wo explicit tc)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, default) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ipv6, + type := tclass, + data := TClass}], ?BASIC_REQ}} + when is_integer(TClass) -> + ?SEV_IPRINT("Got tclass: " + "~n TClass: ~p", [TClass]), + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + #{level => ipv6, + type => tclass, + data => "something"}, + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, #{level => ipv6, + type => tclass, + data => "something"}, + ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "send req (to dst) (w explicit tc = 1)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + Send(Sock, ?BASIC_REQ, Dst, 1) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + case Recv(Sock) of + {ok, {Src, [#{level := ipv6, + type := tclass, + data := 1 = TClass}], ?BASIC_REQ}} + when is_integer(TClass) -> + ?SEV_IPRINT("Got (expected) tclass: " + "~n TClass: ~p", [TClass]), + ok; + {ok, {_Src, [#{level := ipv6, + type := tclass, + data := TClass}], ?BASIC_REQ}} + when is_integer(TClass) -> + ?SEV_EPRINT("Unexpected tclass: " + "~n Expect TClass: ~p" + "~n Recv TClass: ~p", + [1, TClass]), + {error, {unexpected_tclass, TClass}}; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + #{level => ipv6, + type => tclass, + data => "something"}, + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, #{level => ipv6, + type => tclass, + data => "something"}, + ?BASIC_REQ, UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This intended to test "all" of the (currently) supported IPv6 +%% options that results in control message header(s). +%% So, this is done on the receiving side: +%% +%% socket:setopt(Sock, ipv6, Flag, boolean()). +%% +%% For all subsequent *received* messages, a control message header +%% for each of the enabled options will be received with the message. +%% +%% Only allowed for dgram and raw, +%% although we only test this with dgram. +%% +%% Currently we *try* to use the following opts: +%% +%% recvpktinfo | pktinfo => pktinfo +%% flowinfo => flowinfo +%% recvhoplimit | hoplimit => hoplimit +%% recvtclass | tclass => tclass +%% +%% +%% Every time we add a test case for a new option (that results in +%% a control message hedare), we should also add it here. +%% +%% Even though this is a IPv6 test case, we add the 'socket' timestamp +%% option (just to fill up), but in the test to see if we should run +%% the test (since its a IPv6 test case). +%% + +api_opt_ipv6_mopts_udp6(suite) -> + []; +api_opt_ipv6_mopts_udp6(doc) -> + []; +api_opt_ipv6_mopts_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_ipv6_mopts_udp6, + fun() -> + has_support_ipv6(), + case is_any_options_supported( + [{ipv6, recvpktinfo}, + {ipv6, flowinfo}, + {ipv6, recvhoplimit}, + {ipv6, hoplimit}, + {ipv6, recvtclass}, + {ipv6, tclass}]) of + true -> + ok; + false -> + skip("None of the needed options are supported") + end, + %% The problem here is hoplimit on darwin 9.8.0, + %% but I can't be bothered to adjust the test case, + %% just skip on that machine (there is only one)... + is_good_enough_darwin({9,8,0}) + end, + fun() -> + %% If we get this far, we *know* that at least one of the + %% options are available. + + %% This is list of all the options and there resulting + %% control message header type(s): + %% [{'ipv6 socket option', 'control message header type'}] + Opts = + case socket:is_supported(options, socket, timestamp) of + true -> + [{socket, timestamp, timestamp, default}]; + false -> + [] + end ++ + case socket:is_supported(options, ipv6, recvpktinfo) of + true -> + [{ipv6, recvpktinfo, pktinfo, default}]; + false -> + [] + end ++ + case socket:is_supported(options, ipv6, flowinfo) of + true -> + [{ipv6, flowinfo, flowinfo, default}]; + false -> + [] + end ++ + case socket:is_supported(options, ipv6, recvhoplimit) of + true -> + [{ipv6, recvhoplimit, hoplimit, default}]; + false -> + case socket:is_supported(options, ipv6, hoplimit) of + true -> + [{ipv6, hoplimit, hoplimit, default}]; + false -> + [] + end + end ++ + case socket:is_supported(options, ipv6, recvtclass) of + true -> + [{ipv6, recvtclass, tclass, 42}]; + false -> + case socket:is_supported(options, ipv6, tclass) of + true -> + [{ipv6, tclass, tclass, 42}]; + false -> + [] + end + end, + + Enable = fun(Sock, Level, Opt) -> + ?SEV_IPRINT("try enable [~w] ~p", [Level, Opt]), + socket:setopt(Sock, Level, Opt, true) + end, + Send = fun(Sock, Data, Dest, []) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest, Hdrs) when is_list(Hdrs) -> + CMsgHdrs = [#{level => Level, + type => Type, + data => Val} || + {Level, Type, Val} <- Hdrs], + MsgHdr = #{addr => Dest, + ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + ctrl := CMsgHdrs, + iov := [Data]}} -> + {ok, {Source, CMsgHdrs, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet6, + proto => udp, + opts => Opts, + send => Send, + recv => Recv, + enable => Enable}, + ok = api_opt_ipv6_mopts_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_ipv6_mopts_udp(InitState) -> + Seq = + [ + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa_src => LSA, + lsa_dst => LSA}} + end}, + + #{desc => "open src socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_src => Sock}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa_src := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + ?SEV_IPRINT("src sockaddr: " + "~n ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + + #{desc => "open dst socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa_dst := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + ?SEV_IPRINT("dst sockaddr: " + "~n ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + + #{desc => "enable options on dst socket", + cmd => fun(#{sock_dst := DSock, + sock_src := SSock, + opts := Opts, + enable := Enable} = _State) -> + %% If we fail to enable *any* of the options, + %% we give up. + E = fun({Level, Opt, _, _}) -> + case Enable(DSock, Level, Opt) of + ok -> + ?SEV_IPRINT("dst [~w] ~w enabled", + [Level, Opt]), + ok; + {error, enoprotoopt = Reason} -> + ?SEV_EPRINT("Expected " + "Failure: " + "~p => SKIP", + [Reason]), + (catch socket:close(DSock)), + (catch socket:close(SSock)), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed " + "setting ~w:" + " ~p", + [Opt, Reason]), + throw(ERROR) + end + end, + lists:foreach(E, Opts), + ok + end}, + + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, + sa_dst := Dst, + opts := Opts, + send := Send}) -> + Hdrs = [{Level, Type, Data} || + {Level, _, Type, Data} <- + Opts, (Data =/= default)], + Send(Sock, ?BASIC_REQ, Dst, Hdrs) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, + sa_src := Src, + recv := Recv, + opts := Opts}) -> + case Recv(Sock) of + {ok, {Src, CMsgHdrs, ?BASIC_REQ}} + when length(CMsgHdrs) =:= length(Opts) -> + ?SEV_IPRINT("Got (expected) cmsg headers: " + "~n ~p", [CMsgHdrs]), + %% We should really verify the headers: + %% values, types and so on... + ok; + {ok, {BadSrc, BadCHdrs, BadReq} = UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Recv Source: ~p" + "~n Expect CHdrs: ~p" + "~n Recv CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Recv Msg: ~p", + [Src, BadSrc, + [{Level, Type} || + {Level, _, Type, _} <- Opts], + BadCHdrs, + ?BASIC_REQ, BadReq]), + {error, {unexpected_data, UnexpData}}; + {ok, UnexpData} -> + ?SEV_EPRINT("Unexpected msg: " + "~n Expect Source: ~p" + "~n Expect CHdrs: ~p" + "~n Expect Msg: ~p" + "~n Unexp Data: ~p", + [Src, + [{Level, Type} || + {Level, _, Type, _} <- Opts], + ?BASIC_REQ, + UnexpData]), + {error, {unexpected_data, UnexpData}}; + {error, _} = ERROR -> + %% At the moment there is no way to get + %% status or state for the socket... + ERROR + end + end}, + + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_src, State)} + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock_dst, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the congestion tcp socket option. +%% +%% According to the man page (on linux) for this option it *should* be +%% possible to both get and set *allowed* algorithms. But when we attempt +%% to set, we get 'enoent'. +%% Accoring to /proc/sys/net/ipv4/tcp_allowed_congestion_control that +%% allgorithm was allowed, so... +%% For now, we only test that we can get (it could be a bug in our code) + +api_opt_tcp_congestion_tcp4(suite) -> + []; +api_opt_tcp_congestion_tcp4(doc) -> + []; +api_opt_tcp_congestion_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_tcp_congestion_tcp4, + fun() -> has_support_tcp_congestion() end, + fun() -> + Set = fun(Sock, Value) when is_list(Value) -> + socket:setopt(Sock, tcp, congestion, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, tcp, congestion) + end, + InitState = #{domain => inet, + proto => tcp, + set => Set, + get => Get}, + ok = api_opt_tcp_congestion_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_tcp_congestion_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := LSock, lsa := LSA} = _State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (get congestion)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, get_congestion) + end}, + #{desc => "get congestion", + cmd => fun(#{sock := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, Algorithm} -> + ?SEV_IPRINT("algorithm: ~s", [Algorithm]), + {ok, State#{alg => Algorithm}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (get congestion)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, get_congestion), + ok + end}, + + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, init), + ok + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with get-congestion)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, get_congestion), + ok + end}, + #{desc => "await server ready (get-congestion)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, get_congestion) + end}, + + + %% *** Termination *** + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the cork tcp socket option. +%% +%% This is a very simple test. We simple set and get the value. +%% To test that it has an effect is just "to much work"... +%% +%% Reading the man page it seems like (on linux) that the +%% value resets itself after some (short) time... + +api_opt_tcp_cork_tcp4(suite) -> + []; +api_opt_tcp_cork_tcp4(doc) -> + []; +api_opt_tcp_cork_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_tcp_cork_tcp4, + fun() -> has_support_tcp_cork() end, + fun() -> + Set = fun(Sock, Value) when is_boolean(Value) -> + socket:setopt(Sock, tcp, cork, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, tcp, cork) + end, + InitState = #{domain => inet, + proto => tcp, + set => Set, + get => Get}, + ok = api_opt_tcp_cork_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_tcp_cork_tcp(InitState) -> + process_flag(trap_exit, true), + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := LSock, lsa := LSA} = _State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% The actual test + #{desc => "get (default) cork (= false)", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("cork default: ~p", [Value]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "enable cork (=> true)", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("cork enabled"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get cork (= true)", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, true = Value} -> + ?SEV_IPRINT("cork: ~p", [Value]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Termination *** + #{desc => "close connection socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the maxseg tcp socket option. +%% +%% This is a very simple test. We simple set and get the value. +%% To test that it has an effect is just "to much work"... +%% +%% Note that there is no point in reading this value back, +%% since the kernel imposes its own rules with regard +%% to what is an acceptible value. +%% + +api_opt_tcp_maxseg_tcp4(suite) -> + []; +api_opt_tcp_maxseg_tcp4(doc) -> + []; +api_opt_tcp_maxseg_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_tcp_maxseg_tcp4, + fun() -> has_support_tcp_maxseg() end, + fun() -> + Set = fun(Sock, Value) when is_integer(Value) -> + socket:setopt(Sock, tcp, maxseg, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, tcp, maxseg) + end, + InitState = #{domain => inet, + proto => tcp, + set => Set, + get => Get}, + ok = api_opt_tcp_maxseg_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_tcp_maxseg_tcp(InitState) -> + process_flag(trap_exit, true), + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := LSock, lsa := LSA} = _State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% The actual test + #{desc => "get (default) maxseg", + cmd => fun(#{sock := Sock, get := Get} = State) -> + case Get(Sock) of + {ok, DefMaxSeg} -> + ?SEV_IPRINT("maxseg default: ~p", [DefMaxSeg]), + {ok, State#{def_maxseg => DefMaxSeg}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% Note that there is no point in reading this value back, + %% since the kernel imposes its own rules with regard + %% to what is an acceptible value. + %% So, even if the set operation is a success, the value + %% still might not have changed. + %% + %% Note that not all platforms allow this to be set! + %% Since this is the *last* operation in the test sequence + %% (before termination) we also accept error reason = einval + %% as success (rather then skip). + %% The same goes for the error reason = enoprotoopt (Solaris). + #{desc => "(maybe) change maxseg (default + 16)", + cmd => fun(#{sock := Sock, + set := Set, + def_maxseg := DefMaxSeg} = _State) -> + NewMaxSeg = DefMaxSeg + 16, + case Set(Sock, NewMaxSeg) of + ok -> + ?SEV_IPRINT("maxseg (maybe) changed (to ~w)", + [NewMaxSeg]), + ok; + {error, Reason} when (Reason =:= einval) orelse + (Reason =:= enoprotoopt) -> + ?SEV_IPRINT("change not allowed (~w)", + [Reason]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Termination *** + #{desc => "close connection socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the nodelay tcp socket option. +%% +%% This is a very simple test. We simple set and get the value. +%% To test that it has an effect is just "to much work"... + +api_opt_tcp_nodelay_tcp4(suite) -> + []; +api_opt_tcp_nodelay_tcp4(doc) -> + []; +api_opt_tcp_nodelay_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_tcp_nodelay_tcp4, + fun() -> has_support_tcp_nodelay() end, + fun() -> + Set = fun(Sock, Value) when is_boolean(Value) -> + socket:setopt(Sock, tcp, nodelay, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, tcp, nodelay) + end, + InitState = #{domain => inet, + proto => tcp, + set => Set, + get => Get}, + ok = api_opt_tcp_nodelay_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_tcp_nodelay_tcp(InitState) -> + process_flag(trap_exit, true), + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% The actual test + #{desc => "get (default) nodelay (= false)", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("nodelay default: ~p", [Value]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "enable nodelay (=> true)", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("nodelay enabled"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get nodelay (= true)", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, true = Value} -> + ?SEV_IPRINT("nodelay: ~p", [Value]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the cork udp socket option. +%% +%% This is a very simple test. We simple set and get the value. +%% To test that it has an effect is just "to much work"... +%% + +api_opt_udp_cork_udp4(suite) -> + []; +api_opt_udp_cork_udp4(doc) -> + []; +api_opt_udp_cork_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_udp_cork_udp4, + fun() -> has_support_udp_cork() end, + fun() -> + Set = fun(Sock, Value) when is_boolean(Value) -> + socket:setopt(Sock, udp, cork, Value) + end, + Get = fun(Sock) -> + socket:getopt(Sock, udp, cork) + end, + InitState = #{domain => inet, + proto => udp, + set => Set, + get => Get}, + ok = api_opt_udp_cork_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_opt_udp_cork_udp(InitState) -> + process_flag(trap_exit, true), + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% The actual test + #{desc => "get (default) cork (= false)", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, false = Value} -> + ?SEV_IPRINT("cork default: ~p", [Value]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "enable cork (=> true)", + cmd => fun(#{sock := Sock, set := Set} = _State) -> + case Set(Sock, true) of + ok -> + ?SEV_IPRINT("cork enabled"), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get cork (= true)", + cmd => fun(#{sock := Sock, get := Get} = _State) -> + case Get(Sock) of + {ok, true = Value} -> + ?SEV_IPRINT("cork: ~p", [Value]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + ok = ?SEV_AWAIT_FINISH([Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPERATIONS WITH TIMEOUT %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the connect timeout option +%% on an IPv4 TCP (stream) socket. +api_to_connect_tcp4(suite) -> + []; +api_to_connect_tcp4(doc) -> + []; +api_to_connect_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + Cond = fun() -> api_to_connect_cond() end, + tc_try(api_to_connect_tcp4, + Cond, + fun() -> + InitState = #{domain => inet, + backlog => 1, + timeout => 5000, + connect_limit => 3}, + ok = api_to_connect_tcp(InitState) + end). + +api_to_connect_cond() -> + api_to_connect_cond(os:type(), os:version()). + +%% I don't know exactly at which version this starts to work. +%% I know it does not work for 4.4.*, but is does for 4.15. +%% So, just to simplify, we require atleast 4.15 +api_to_connect_cond({unix, linux}, {Maj, Min, _Rev}) -> + if + (Maj > 4) -> + ok; + ((Maj =:= 4) andalso (Min >= 15)) -> + ok; + true -> + skip("TC does not work") + end; +%% Only test on one machine, which has version 6.3, and there it does +%% not work, so disable for all. +api_to_connect_cond({unix, openbsd}, _) -> + skip("TC does not work"); +api_to_connect_cond({unix, freebsd}, {Maj, Min, _Rev}) -> + if + ((Maj >= 10) andalso (Min >= 4)) -> + ok; + true -> + skip("TC may not work") + end; +api_to_connect_cond({unix, sunos}, {Maj, Min, _Rev}) -> + if + ((Maj >= 5) andalso (Min >= 10)) -> + ok; + true -> + skip("TC may not work") + end; +api_to_connect_cond(_, _) -> + skip("TC may not work"). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the connect timeout option +%% on an IPv6 TCP (stream) socket. +api_to_connect_tcp6(suite) -> + []; +api_to_connect_tcp6(doc) -> + []; +api_to_connect_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_connect_tcp6, + fun() -> has_support_ipv6(), api_to_connect_cond() end, + fun() -> + InitState = #{domain => inet6, + backlog => 1, + timeout => 5000, + connect_limit => 3}, + ok = api_to_connect_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% We use the backlog (listen) argument to test this. +%% Note that the behaviour of the TCP "server side" can vary when +%% a client connect to a "busy" server (full backlog). +%% For instance, on FreeBSD (11.2) the reponse when the backlog is full +%% is a econreset. + +api_to_connect_tcp(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Backlog} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + backlog => Backlog}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock, backlog := Backlog}) -> + socket:listen(LSock, Backlog) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{lsock := Sock} = State) -> + sock_close(Sock), + State1 = maps:remove(lport, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + ?SEV_IPRINT("try create node on ~p", [Host]), + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node} = State) -> + Pid = api_toc_tcp_client_start(Node), + ?SEV_IPRINT("remote client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]) of + {ok, {ConTimeout, ConLimit}} -> + {ok, State#{connect_timeout => ConTimeout, + connect_limit => ConLimit}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := RClient, + connect_timeout := ConTimeout, + connect_limit := ConLimit}) -> + ?SEV_ANNOUNCE_CONTINUE(RClient, connect, + {ConTimeout, ConLimit}), + ok + end}, + #{desc => "await remote client ready (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, connect, + [{tester, Tester}]) of + {ok, ok = _Result} -> + {ok, maps:remove(connect_limit, State)}; + {ok, {error, {connect_limit_reached,R,L}}} -> + {skip, + ?LIB:f("Connect limit reached ~w: ~w", + [L, R])}; + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, RClient}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "order server start", + cmd => fun(#{server := Server, + backlog := Backlog}) -> + ?SEV_ANNOUNCE_START(Server, Backlog), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server, local_sa := LSA} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}} + end}, + #{desc => "order client start", + cmd => fun(#{client := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init), + ok + end}, + + %% The actual test + %% The server does nothing (this is the point), no accept, + %% the client tries to connect. + #{desc => "order client continue (connect)", + cmd => fun(#{client := Client, + timeout := Timeout, + connect_limit := ConLimit} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect, + {Timeout, ConLimit}), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + State2 = maps:remove(server_sa, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("create server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("create client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("create tester evaluator"), + TesterInitState = InitState#{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +api_toc_tcp_client_start(Node) -> + Self = self(), + Fun = fun() -> api_toc_tcp_client(Self) end, + erlang:spawn(Node, Fun). + +api_toc_tcp_client(Parent) -> + api_toc_tcp_client_init(Parent), + ServerSA = api_toc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + api_toc_tcp_client_announce_ready(Parent, init), + {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect), + Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit), + ?SEV_IPRINT("result: ~p", [Result]), + api_toc_tcp_client_announce_ready(Parent, connect, Result), + Reason = api_toc_tcp_client_await_terminate(Parent), + exit(Reason). + +api_toc_tcp_client_init(Parent) -> + put(sname, "rclient"), + %% i("api_toc_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + ok. + +api_toc_tcp_client_await_start(Parent) -> + %% i("api_toc_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +api_toc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). +api_toc_tcp_client_announce_ready(Parent, Slogan, Result) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result). + +api_toc_tcp_client_await_continue(Parent, Slogan) -> + %% i("api_toc_tcp_client_await_continue -> entry"), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ok; + {ok, Extra} -> + Extra; + {error, Reason} -> + exit({await_continue, Slogan, Reason}) + end. + +api_toc_tcp_client_await_terminate(Parent) -> + %% i("api_toc_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit) -> + LSA = which_local_socket_addr(Domain), + NewSock = fun() -> + S = case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, OReason} -> + ?FAIL({open, OReason}) + end, + case socket:bind(S, LSA) of + {ok, _} -> + S; + {error, BReason} -> + ?FAIL({bind, BReason}) + end + end, + api_to_connect_tcp_await_timeout(1, ConLimit, To, ServerSA, NewSock, []). + +api_to_connect_tcp_await_timeout(ID, ConLimit, _To, _ServerSA, _NewSock, Acc) + when (ID > ConLimit) -> + api_to_connect_tcp_await_timeout3(Acc), + {error, {connect_limit_reached, ID, ConLimit}}; +api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) -> + case api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) of + ok -> + %% ?SEV_IPRINT("success when number of socks: ~w", [length(Acc)]), + api_to_connect_tcp_await_timeout3(Acc), + ok; + {ok, Sock} -> + %% ?SEV_IPRINT("~w: unexpected success (connect)", [ID]), + api_to_connect_tcp_await_timeout(ID+1, ConLimit, + To, ServerSA, NewSock, + [Sock|Acc]); + {error, _} = ERROR -> + ERROR + end. + +api_to_connect_tcp_await_timeout2(_ID, To, ServerSA, NewSock) -> + Sock = NewSock(), + %% ?SEV_IPRINT("~w: try connect", [ID]), + Start = t(), + case socket:connect(Sock, ServerSA, To) of + {error, timeout} -> + Stop = t(), + TDiff = Stop - Start, + if + (TDiff >= To) -> + (catch socket:close(Sock)), + ok; + true -> + (catch socket:close(Sock)), + ?FAIL({unexpected_timeout, TDiff, To}) + end; + {error, econnreset = _Reason} -> + (catch socket:close(Sock)), + ok; + {error, Reason} -> + (catch socket:close(Sock)), + ?FAIL({connect, Reason}); + ok -> + {ok, Sock} + end. + +api_to_connect_tcp_await_timeout3([]) -> + ok; +api_to_connect_tcp_await_timeout3([Sock|Socka]) -> + (catch socket:close(Sock)), + api_to_connect_tcp_await_timeout3(Socka). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv4 TCP (stream) socket. +api_to_accept_tcp4(suite) -> + []; +api_to_accept_tcp4(doc) -> + []; +api_to_accept_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_accept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_accept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_accept_tcp6(suite) -> + []; +api_to_accept_tcp6(doc) -> + []; +api_to_accept_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_accept_tcp4, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_accept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_accept_tcp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case sock_bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + + %% *** The actual test part *** + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = Stop - Start, + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(sock3, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("create tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the multi accept timeout option +%% on an IPv4 TCP (stream) socket with multiple acceptor processes +%% (three in this case). +api_to_maccept_tcp4(suite) -> + []; +api_to_maccept_tcp4(doc) -> + []; +api_to_maccept_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_to_maccept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_maccept_tcp6(suite) -> + []; +api_to_maccept_tcp6(doc) -> + []; +api_to_maccept_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_to_maccept_tcp4, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_maccept_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, lsa := LSA} = _State) -> + case sock_bind(LSock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{lsock := LSock, tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init, LSock), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + ?SEV_EPRINT("Unexpected accept success: " + "~n ~p", [Sock]), + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = Stop - Start, + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_TERMINATE(Tester, tester), + ok + end}, + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, LSock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + lsock => LSock}} + + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test part *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) -> + TDiff = Stop - Start, + if + (TDiff >= To) -> + State1 = maps:remove(start, State), + State2 = maps:remove(stop, State1), + {ok, State2}; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% Init part + #{desc => "monitor prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + + %% Start the prim-acceptor + #{desc => "start prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await prim-acceptor ready (init)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acceptor, init), + {ok, State#{lsock => Sock}} + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LSock), + ok + end}, + #{desc => "await sec-acceptor 1 ready (init)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor1, init) + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LSock), + ok + end}, + #{desc => "await sec-acceptor 2 ready (init)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor2, init) + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (accept)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_acceptor, accept) + end}, + #{desc => "await sec-acceptor 1 ready (accept)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept) + end}, + #{desc => "await sec-acceptor 2 ready (accept)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept) + end}, + + %% Terminate + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState), + + i("create sec-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1), + + i("create sec-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2), + + i("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid, + sec_acceptor1 => SecAcceptor1#ev.pid, + sec_acceptor2 => SecAcceptor2#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv4 TCP (stream) socket. +api_to_send_tcp4(suite) -> + []; +api_to_send_tcp4(doc) -> + []; +api_to_send_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_send_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv6 TCP (stream) socket. +api_to_send_tcp6(suite) -> + []; +api_to_send_tcp6(doc) -> + []; +api_to_send_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_send_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendto timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_sendto_udp4(suite) -> + []; +api_to_sendto_udp4(doc) -> + []; +api_to_sendto_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendto timeout option +%% on an IPv6 UDP (dgram) socket. +api_to_sendto_udp6(suite) -> + []; +api_to_sendto_udp6(doc) -> + []; +api_to_sendto_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_sendto_udp6, + fun() -> has_support_ipv6() end, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendto_to_udp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendmsg timeout option +%% on an IPv4 TCP (stream) socket. +api_to_sendmsg_tcp4(suite) -> + []; +api_to_sendmsg_tcp4(doc) -> + []; +api_to_sendmsg_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_sendmsg_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the sendmsg timeout option +%% on an IPv6 TCP (stream) socket. +api_to_sendmsg_tcp6(suite) -> + []; +api_to_sendmsg_tcp6(doc) -> + []; +api_to_sendmsg_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_sendmsg_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_sendmsg_tcp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv4 UDP (dgram) socket. To test this we must connect +%% the socket. +api_to_recv_udp4(suite) -> + []; +api_to_recv_udp4(doc) -> + []; +api_to_recv_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_recv_udp4, + fun() -> + not_yet_implemented()%%, + %%ok = api_to_recv_udp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv6 UDP (dgram) socket. To test this we must connect +%% the socket. +api_to_recv_udp6(suite) -> + []; +api_to_recv_udp6(doc) -> + []; +api_to_recv_udp6(_Config) when is_list(_Config) -> + tc_try(api_to_recv_udp6, + fun() -> has_support_ipv6() end, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_recv_udp(inet6) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv4 TCP (stream) socket. +api_to_recv_tcp4(suite) -> + []; +api_to_recv_tcp4(doc) -> + []; +api_to_recv_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recv_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recv timeout option +%% on an IPv6 TCP (stream) socket. +api_to_recv_tcp6(suite) -> + []; +api_to_recv_tcp6(doc) -> + []; +api_to_recv_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recv_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + case socket:is_supported(ipv6) of + true -> + Recv = fun(Sock, To) -> + socket:recv(Sock, 0, To) + end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_tcp(InitState); + false -> + skip("ipv6 not supported") + end + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_receive_tcp(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket (with backlog = 1)", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock, 1) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (accept and recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept_recv) + end}, + #{desc => "attempt accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, _Data} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) -> + TDiff = Stop - Start, + if + (TDiff >= To) -> + State1 = maps:remove(start, State), + State2 = maps:remove(stop, State1), + {ok, State2}; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (recv timeout success)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, accept_recv), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close (traffic) socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (with connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + sock_connect(Sock, SSA), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + + %% *** Activate server *** + #{desc => "start server", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_START(Server), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), + {ok, State#{server_port => Port}} + end}, + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept_recv), + ok + end}, + + %% *** Activate client *** + #{desc => "start client", + cmd => fun(#{client := Client, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Client, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init) + end}, + + %% *** The actual test *** + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await server ready (accept/recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept_recv) + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + case ?SEV_AWAIT_TERMINATION(Client) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + case ?SEV_AWAIT_TERMINATION(Server) of + ok -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_port, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = InitState, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_recvfrom_udp4(suite) -> + []; +api_to_recvfrom_udp4(doc) -> + []; +api_to_recvfrom_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recvfrom_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv6 UDP (dgram) socket. +api_to_recvfrom_udp6(suite) -> + []; +api_to_recvfrom_udp6(doc) -> + []; +api_to_recvfrom_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recvfrom_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_receive_udp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** The actual test *** + #{desc => "attempt to read (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, + stop => t()}}; + {ok, _} -> + {error, unexpected_sucsess}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = Stop - Start, + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + %% socket:setopt(Sock, otp, debug, true), + sock_close(Sock), + ok + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_recvmsg_udp4(suite) -> + []; +api_to_recvmsg_udp4(doc) -> + []; +api_to_recvmsg_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recvmsg_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv6 UDP (dgram) socket. +api_to_recvmsg_udp6(suite) -> + []; +api_to_recvmsg_udp6(doc) -> + []; +api_to_recvmsg_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recvmsg_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 TCP (stream) socket. +api_to_recvmsg_tcp4(suite) -> + []; +api_to_recvmsg_tcp4(doc) -> + []; +api_to_recvmsg_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recvmsg_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv6 TCP (stream) socket. +api_to_recvmsg_tcp6(suite) -> + []; +api_to_recvmsg_tcp6(doc) -> + []; +api_to_recvmsg_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_to_recvmsg_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 2000}, + ok = api_to_receive_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% REGISTRY %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% We create a bunch of different sockets and ensure that the registry +%% has the correct info. + +reg_s_single_open_and_close_and_count(suite) -> + []; +reg_s_single_open_and_close_and_count(doc) -> + []; +reg_s_single_open_and_close_and_count(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(reg_s_single_open_and_close_and_count, + fun() -> + ok = reg_s_single_open_and_close_and_count() + end). + + +reg_s_single_open_and_close_and_count() -> + SupportsIPV6 = + case (catch has_support_ipv6()) of + ok -> + true; + _ -> + false + end, + SupportsLOCAL = + case (catch has_support_unix_domain_socket()) of + ok -> + true; + _ -> + false + end, + SupportsSCTP = + case (catch has_support_sctp()) of + ok -> + true; + _ -> + false + end, + InitSockInfos = + [ + {inet, stream, tcp}, + {inet, dgram, udp} + ] ++ + case SupportsIPV6 of + true -> + [ + {inet6, stream, tcp}, + {inet6, dgram, udp} + ]; + false -> + [] + end ++ + case SupportsLOCAL of + true -> + [ + {local, stream, default}, + {local, dgram, default} + ]; + false -> + [] + end ++ + [ + {inet, stream, tcp}, + {inet, dgram, udp} + ] ++ + case SupportsSCTP of + true -> + [ + {inet, seqpacket, sctp}, + {inet, seqpacket, sctp} + ]; + false -> + [] + end ++ + [ + {inet, stream, tcp}, + {inet, dgram, udp} + ] ++ + case SupportsSCTP andalso SupportsIPV6 of + true -> + [ + {inet6, seqpacket, sctp}, + {inet6, seqpacket, sctp} + ]; + false -> + [] + end, + + i("open sockets"), + Socks = + [fun({Domain, Type, Proto}) -> + i("open socket: ~w, ~w, ~w", [Domain, Type, Proto]), + {ok, Sock} = socket:open(Domain, Type, Proto), + Sock + end(InitSockInfo) || InitSockInfo <- InitSockInfos], + + ?SLEEP(1000), + + + %% **** Total Number Of Sockets **** + + NumSocks1 = length(Socks), + NumberOf1 = socket:number_of(), + + i("verify (total) number of sockets(1): ~w, ~w", [NumSocks1, NumberOf1]), + case (NumSocks1 =:= NumberOf1) of + true -> + ok; + false -> + exit({wrong_number_of_sockets1, NumSocks1, NumberOf1}) + end, + + + %% **** Number Of IPv4 TCP Sockets **** + + %% inet, stream, tcp + SiNumTCP = reg_si_num(InitSockInfos, inet, stream, tcp), + SrNumTCP = reg_sr_num(inet, stream, tcp), + + i("verify number of IPv4 TCP sockets: ~w, ~w", [SiNumTCP, SrNumTCP]), + case (SiNumTCP =:= SrNumTCP) of + true -> + ok; + false -> + exit({wrong_number_of_ipv4_tcp_sockets, SiNumTCP, SrNumTCP}) + end, + + + %% **** Number Of IPv4 UDP Sockets **** + + %% inet, dgram, udp + SiNumUDP = reg_si_num(InitSockInfos, inet, dgram, udp), + SrNumUDP = reg_sr_num(inet, dgram, udp), + + i("verify number of IPv4 UDP sockets: ~w, ~w", [SiNumUDP, SrNumUDP]), + case (SiNumUDP =:= SrNumUDP) of + true -> + ok; + false -> + exit({wrong_number_of_ipv4_udp_sockets, SiNumUDP, SrNumUDP}) + end, + + + %% **** Number Of IPv4 SCTP Sockets **** + + %% inet, seqpacket, sctp + SiNumSCTP = reg_si_num(InitSockInfos, inet, seqpacket, sctp), + SrNumSCTP = reg_sr_num(inet, seqpacket, sctp), + + i("verify number of IPv4 SCTP sockets: ~w, ~w", [SiNumSCTP, SrNumSCTP]), + case (SiNumSCTP =:= SrNumSCTP) of + true -> + ok; + false -> + exit({wrong_number_of_sctp_sockets, SiNumSCTP, SrNumSCTP}) + end, + + + %% **** Number Of IPv4 Sockets **** + + %% inet + SiNumINET = reg_si_num(InitSockInfos, inet), + SrNumINET = reg_sr_num(inet), + + i("verify number of IPv4 sockets: ~w, ~w", [SiNumINET, SrNumINET]), + case (SiNumINET =:= SrNumINET) of + true -> + ok; + false -> + exit({wrong_number_of_ipv4_sockets, SiNumINET, SrNumINET}) + end, + + + %% **** Number Of IPv6 Sockets **** + + %% inet6 + SiNumINET6 = reg_si_num(InitSockInfos, inet6), + SrNumINET6 = reg_sr_num(inet6), + + i("verify number of IPv6 sockets: ~w, ~w", [SiNumINET6, SrNumINET6]), + case (SiNumINET6 =:= SrNumINET6) of + true -> + ok; + false -> + exit({wrong_number_of_ipv6_sockets, SiNumINET6, SrNumINET6}) + end, + + + %% **** Number Of Unix Domain Sockets Sockets **** + + %% local + SiNumLOCAL = reg_si_num(InitSockInfos, local), + SrNumLOCAL = reg_sr_num(local), + + i("verify number of Unix Domain Sockets sockets: ~w, ~w", + [SiNumLOCAL, SrNumLOCAL]), + case (SiNumLOCAL =:= SrNumLOCAL) of + true -> + ok; + false -> + exit({wrong_number_of_local_sockets, SiNumLOCAL, SrNumLOCAL}) + end, + + + %% **** Close *all* Sockets then verify Number Of Sockets **** + + i("close sockets"), + lists:foreach(fun(S) -> + i("close socket"), + ok = socket:close(S) + end, Socks), + + ?SLEEP(1000), + + NumSocks2 = 0, + NumberOf2 = socket:number_of(), + + i("verify number of sockets(2): ~w, ~w", [NumSocks2, NumberOf2]), + case (NumSocks2 =:= NumberOf2) of + true -> + ok; + false -> + exit({wrong_number_of_sockets2, NumSocks2, NumberOf2}) + end, + + ok. + + +reg_si_num(SocksInfo, Domain) + when ((Domain =:= inet) orelse (Domain =:= inet6) orelse (Domain =:= local)) -> + reg_si_num(SocksInfo, Domain, undefined, undefined); +reg_si_num(SocksInfo, Type) + when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) -> + reg_si_num(SocksInfo, undefined, Type, undefined); +reg_si_num(SocksInfo, Proto) + when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) -> + reg_si_num(SocksInfo, undefined, undefined, Proto). + +reg_si_num(SocksInfo, Domain, undefined, undefined) -> + F = fun({D, _T, _P}) when (D =:= Domain) -> true; + (_) -> false + end, + reg_si_num2(F, SocksInfo); +reg_si_num(SocksInfo, undefined, Type, undefined) -> + F = fun({_D, T, _P}) when (T =:= Type) -> true; + (_) -> false + end, + reg_si_num2(F, SocksInfo); +reg_si_num(SocksInfo, undefined, undefined, Proto) -> + F = fun({_D, _T, P}) when (P =:= Proto) -> true; + (_) -> false + end, + reg_si_num2(F, SocksInfo); +reg_si_num(SocksInfo, Domain, Type, Proto) -> + F = fun({D, T, P}) when (D =:= Domain) andalso + (T =:= Type) andalso + (P =:= Proto) -> + true; + (_) -> + false + end, + reg_si_num2(F, SocksInfo). + +reg_si_num2(F, SocksInfo) -> + length(lists:filter(F, SocksInfo)). + + +reg_sr_num(Domain) + when ((Domain =:= inet) orelse (Domain =:= inet6)) -> + length(socket:which_sockets(Domain)); +reg_sr_num(Domain) + when (Domain =:= local) -> + reg_sr_num(Domain, undefined, undefined); +reg_sr_num(Type) + when ((Type =:= stream) orelse (Type =:= dgram) orelse (Type =:= seqpacket)) -> + length(socket:which_sockets(Type)); +reg_sr_num(Proto) + when ((Proto =:= sctp) orelse (Proto =:= tcp) orelse (Proto =:= udp)) -> + length(socket:which_sockets(Proto)). + +reg_sr_num(Domain, undefined, undefined) -> + F = fun(#{domain := D}) when (D =:= Domain) -> + true; + (_X) -> + false + end, + reg_sr_num2(F); +reg_sr_num(Domain, Type, Proto) -> + F = fun(#{domain := D, + type := T, + protocol := P}) when (D =:= Domain) andalso + (T =:= Type) andalso + (P =:= Proto) -> + true; + (_X) -> + %% i("reg_sr_num -> not counting: " + %% "~n ~p", [_X]), + false + end, + reg_sr_num2(F). + +reg_sr_num2(F) -> + length(socket:which_sockets(F)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% SOCKET CLOSURE %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 TCP (stream) socket. + +sc_cpe_socket_cleanup_tcp4(suite) -> + []; +sc_cpe_socket_cleanup_tcp4(doc) -> + []; +sc_cpe_socket_cleanup_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_cpe_socket_cleanup_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 TCP (stream) socket. + +sc_cpe_socket_cleanup_tcp6(suite) -> + []; +sc_cpe_socket_cleanup_tcp6(doc) -> + []; +sc_cpe_socket_cleanup_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_cpe_socket_cleanup_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => stream, + protocol => tcp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a Unix Domain (stream) socket (TCP). + +sc_cpe_socket_cleanup_tcpL(suite) -> + []; +sc_cpe_socket_cleanup_tcpL(doc) -> + []; +sc_cpe_socket_cleanup_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_cpe_socket_cleanup_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + type => stream, + protocol => default}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a IPv4 UDP (dgram) socket. + +sc_cpe_socket_cleanup_udp4(suite) -> + []; +sc_cpe_socket_cleanup_udp4(doc) -> + []; +sc_cpe_socket_cleanup_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_cpe_socket_cleanup_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% (removed) when the controlling process terminates (without explicitly +%% calling the close function). For a IPv6 UDP (dgram) socket. + +sc_cpe_socket_cleanup_udp6(suite) -> + []; +sc_cpe_socket_cleanup_udp6(doc) -> + []; +sc_cpe_socket_cleanup_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_cpe_socket_cleanup_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sockets are cleaned up +%% ("removed") when the controlling process terminates (without explicitly +%% calling the close function). For a Unix Domain (dgram) socket (UDP). + +sc_cpe_socket_cleanup_udpL(suite) -> + []; +sc_cpe_socket_cleanup_udpL(doc) -> + []; +sc_cpe_socket_cleanup_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_cpe_socket_cleanup_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + type => dgram, + protocol => default}, + ok = sc_cpe_socket_cleanup(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_cpe_socket_cleanup(InitState) -> + OwnerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% *** The actual test *** + %% We *intentially* leave the socket "as is", no explicit close + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor owner", + cmd => fun(#{owner := Owner} = _State) -> + _MRef = erlang:monitor(process, Owner), + ok + end}, + #{desc => "order (owner) start", + cmd => fun(#{owner := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await (owner) ready", + cmd => fun(#{owner := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init), + {ok, State#{sock => Sock}} + end}, + #{desc => "verify owner as controlling-process", + cmd => fun(#{owner := Pid, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Pid} -> + ok; + {ok, Other} -> + {error, {unexpected_owner, Other}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (owner) terminate", + cmd => fun(#{owner := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await (owner) termination", + cmd => fun(#{owner := Pid} = _State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + ?SEV_SLEEP(?SECS(5)), + + %% The reason we get closed, is that as long as there is a ref to + %% the resource (socket), then it will not be garbage collected. + %% Note that its still a race that the nif has processed that the + %% "controlling process" has terminated. There really is no + %% proper timeout for this, but the 5 seconds "should" be enough... + %% We should really have some way to subscribe to socket events... + #{desc => "verify no socket (closed)", + cmd => fun(#{owner := Pid, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, OtherPid} -> + {error, {unexpected_success, Pid, OtherPid}}; + {error, closed} -> + ok; + {error, Reason} -> + ?SEV_IPRINT("expected failure: ~p", [Reason]), + {error, {unexpected_failure, Reason}} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start (socket) owner evaluator"), + Owner = ?SEV_START("owner", OwnerSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{owner => Owner#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Owner, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recv function. +%% Socket is IPv4. +%% +%% <KOLLA> +%% +%% We should really have a similar test cases for when the controlling +%% process exits and there are other processes in recv, accept, and +%% all the other functions. +%% +%% </KOLLA> + +sc_lc_recv_response_tcp4(suite) -> + []; +sc_lc_recv_response_tcp4(doc) -> + []; +sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_recv_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_lc_recv_response_tcp6(suite) -> + []; +sc_lc_recv_response_tcp6(doc) -> + []; +sc_lc_recv_response_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_recv_response_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is Unix Domain (stream) socket. + +sc_lc_recv_response_tcpL(suite) -> + []; +sc_lc_recv_response_tcpL(doc) -> + []; +sc_lc_recv_response_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_recv_response_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => local, + protocol => default, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_tcp(InitState) -> + %% This (acceptor) is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure (=closed) for the handler process. + AcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + ?SEV_IPRINT("bind to LSA: " + "~n ~p", [LSA]), + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, + lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, {H1, H2, H3}} -> + {ok, State#{handler1 => H1, + handler2 => H2, + handler3 => H3}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("connection accepted: " + "~n ~p", [socket:sockname(Sock)]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "transfer connection to handler 1", + cmd => fun(#{handler1 := Handler, csock := Sock}) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), + ok + end}, + #{desc => "transfer connection to handler 2", + cmd => fun(#{handler2 := Handler, csock := Sock}) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), + ok + end}, + #{desc => "transfer connection to handler 3", + cmd => fun(#{handler3 := Handler, csock := Sock}) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close), + ok + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + lsa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() ->maps:remove(lsa, State) end, + fun() -> State end), + State2 = maps:remove(lsock, State1), + State3 = maps:remove(lport, State2), + {ok, State3}; + (#{lsock := Sock} = State) -> + case socket:close(Sock) of + ok -> + State1 = maps:remove(lsock, State), + State2 = maps:remove(lport, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% The point of this is to perform the recv for which + %% we are testing the reponse. + HandlerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Acceptor} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + acceptor => Acceptor}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Acceptor} = _State) -> + _MRef = erlang:monitor(process, Acceptor), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (transfer)", + cmd => fun(#{acceptor := Pid} = State) -> + case ?SEV_AWAIT_CONTINUE(Pid, acceptor, transfer) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (transfer)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, transfer), + ok + end}, + #{desc => "attempt recv (=> closed)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + case Recv(Sock) of + {ok, _Data} -> + ?SEV_EPRINT("Unexpected data received"), + {error, unexpected_success}; + {error, closed} -> + ?SEV_IPRINT("received expected 'closed' " + "result"), + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected read failure: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (recv closed)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% The point of this is basically just to create the connection. + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + ?SEV_IPRINT("bind to LSA: " + "~n ~p", [LSA]), + case sock_bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{domain := local = Domain, + tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of + {ok, ServerPath} -> + ?SEV_IPRINT("Server Path: " + "~n ~s", [ServerPath]), + ServerSA = #{family => Domain, + path => ServerPath}, + {ok, State#{server_sa => ServerSA}}; + {error, _} = ERROR -> + ERROR + end; + (#{tester := Tester, local_sa := LSA} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of + {ok, Port} -> + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := ServerSA}) -> + socket:connect(Sock, ServerSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + sock_close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 1", + cmd => fun(#{handler1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 2", + cmd => fun(#{handler2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 3", + cmd => fun(#{handler3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the acceptor + #{desc => "order acceptor start", + cmd => fun(#{acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + case ?SEV_AWAIT_READY(Pid, acceptor, init) of + {ok, PortOrPath} -> + {ok, State#{server_info => PortOrPath}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% Start the handler(s) + #{desc => "order handler 1 start", + cmd => fun(#{acceptor := Acceptor, handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Acceptor), + ok + end}, + #{desc => "await handler 1 ready (init)", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, handler1, init) + end}, + #{desc => "order handler 2 start", + cmd => fun(#{acceptor := Acceptor, handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Acceptor), + ok + end}, + #{desc => "await handler 2 ready (init)", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, handler2, init) + end}, + #{desc => "order handler 3 start", + cmd => fun(#{acceptor := Acceptor, handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Acceptor), + ok + end}, + #{desc => "await handler 3 ready (init)", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, handler3, init) + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order acceptor to continue (accept)", + cmd => fun(#{acceptor := Pid, + handler1 := H1, + handler2 := H2, + handler3 := H3} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, {H1, H2, H3}), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (connect)", + cmd => fun(#{client := Pid, server_info := Info} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect, Info), + ok + end}, + #{desc => "await acceptor ready (accept)", + cmd => fun(#{acceptor := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, acceptor, accept) + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, connect) + end}, + #{desc => "await handler 1 ready (transfer)", + cmd => fun(#{handler1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler1, transfer) + end}, + #{desc => "await handler 2 ready (transfer)", + cmd => fun(#{handler2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler2, transfer) + end}, + #{desc => "await handler 3 ready (transfer)", + cmd => fun(#{handler3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler3, transfer) + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order acceptor to continue (close connection socket)", + cmd => fun(#{acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await acceptor ready (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, acceptor, close) + end}, + #{desc => "await handler 1 ready (recv closed)", + cmd => fun(#{handler1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler1, recv_closed) + end}, + #{desc => "await handler 2 ready (recv closed)", + cmd => fun(#{handler2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler2, recv_closed) + end}, + #{desc => "await handler 3 ready (recv closed)", + cmd => fun(#{handler3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler3, recv_closed) + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(client, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler1, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler2, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler3, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(acceptor, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = ?SEV_START("acceptor", AcceptorSeq, AccInitState), + + i("start handler 1 evaluator"), + HandlerInitState = #{recv => maps:get(recv, InitState)}, + Handler1 = ?SEV_START("handler-1", HandlerSeq, HandlerInitState), + + i("start handler 2 evaluator"), + Handler2 = ?SEV_START("handler-2", HandlerSeq, HandlerInitState), + + i("start handler 3 evaluator"), + Handler3 = ?SEV_START("handler-3", HandlerSeq, HandlerInitState), + + i("start client evaluator"), + ClientInitState = InitState, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor#ev.pid, + handler1 => Handler1#ev.pid, + handler2 => Handler2#ev.pid, + handler3 => Handler3#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Acceptor, + Handler1, Handler2, Handler3, + Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recvfrom function. +%% Socket is IPv4. +%% + +sc_lc_recvfrom_response_udp4(suite) -> + []; +sc_lc_recvfrom_response_udp4(doc) -> + []; +sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_lc_recvfrom_response_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end, + InitState = #{domain => inet, + protocol => udp, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_lc_recvfrom_response_udp6(suite) -> + []; +sc_lc_recvfrom_response_udp6(doc) -> + []; +sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_lc_recvfrom_response_udp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end, + InitState = #{domain => inet6, + protocol => udp, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is Unix Domainm (dgram) socket. + +sc_lc_recvfrom_response_udpL(suite) -> + []; +sc_lc_recvfrom_response_udpL(doc) -> + []; +sc_lc_recvfrom_response_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_lc_recvfrom_response_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Recv = fun(Sock, To) -> + socket:recvfrom(Sock, [], To) + end, + InitState = #{domain => local, + protocol => default, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_udp(InitState) -> + PrimServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "open socket", + cmd => fun(#{domain := Domain, protocol := Proto} = State) -> + Sock = sock_open(Domain, dgram, Proto), + %% SA = sock_sockname(Sock), + case socket:sockname(Sock) of + {ok, SA} -> + {ok, State#{sock => Sock, sa => SA}}; + {error, eafnosupport = Reason} -> + ?SEV_IPRINT("Failed get socket name: " + "~n ~p", [Reason]), + (catch socket:close(Sock)), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed get socket name: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "bind socket", + cmd => fun(#{sock := Sock, local_sa := LSA}) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ?SEV_IPRINT("src bound"), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("src bind failed: ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% The actual test + #{desc => "await continue (recv, with timeout)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "receive, with timeout", + cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) -> + case Recv(Sock, Timeout) of + {error, timeout} -> + ok; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv, with timeout)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, terminate) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SecServerSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + + end}, + #{desc => "receive", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock, infinity) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv closed)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor primary server", + cmd => fun(#{prim_server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 1", + cmd => fun(#{sec_server1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 2", + cmd => fun(#{sec_server2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 3", + cmd => fun(#{sec_server3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the primary server + #{desc => "order 'primary server' start", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await 'primary server' ready (init)", + cmd => fun(#{prim_server := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_server, init), + {ok, State#{sock => Sock}} + end}, + + %% Start the secondary server 1 + #{desc => "order 'secondary server 1' start", + cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary server 1' ready (init)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_server1, init) + end}, + + %% Start the secondary server 2 + #{desc => "order 'secondary server 2' start", + cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary server 2' ready (init)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_server2, init) + end}, + + %% Start the secondary server 3 + #{desc => "order 'secondary server 3' start", + cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary server 3' ready (init)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_server3, init) + end}, + + + %% The actual test + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary server 1' to continue (recv)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary server 2' to continue (recv)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary server 3' to continue (recv)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'primary server' to continue (recv, with timeout)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv, ?SECS(5)), + ok + end}, + #{desc => "await 'primary server' ready (recv, with timeout)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_server, recv) + end}, + #{desc => "order 'primary server' to continue (close)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await 'primary server' ready (close)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_server, close) + end}, + #{desc => "await 'secondary server 1' ready (closed)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_server1, recv_closed) + end}, + #{desc => "await 'secondary server 2' ready (closed)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_server2, recv_closed) + end}, + #{desc => "await 'secondary server 3' ready (closed)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_server3, recv_closed) + end}, + + %% Terminations + #{desc => "order 'secondary server 3' to terminate", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary server 3' termination", + cmd => fun(#{sec_server3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server3, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary server 2' to terminate", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary server 2' termination", + cmd => fun(#{sec_server2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server2, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary server 1' to terminate", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary server 1' termination", + cmd => fun(#{sec_server1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server1, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'primary server' to terminate", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'primary server' termination", + cmd => fun(#{prim_server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(prim_server, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start 'primary server' evaluator"), + PrimSrvInitState = InitState, + PrimServer = ?SEV_START("prim-server", PrimServerSeq, PrimSrvInitState), + + i("start 'secondary server 1' evaluator"), + SecSrvInitState = #{recv => maps:get(recv, InitState)}, + SecServer1 = ?SEV_START("sec-server-1", SecServerSeq, SecSrvInitState), + + i("start 'secondary server 2' evaluator"), + SecServer2 = ?SEV_START("sec-server-2", SecServerSeq, SecSrvInitState), + + i("start 'secondary server 3' evaluator"), + SecServer3 = ?SEV_START("sec-server-3", SecServerSeq, SecSrvInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{prim_server => PrimServer#ev.pid, + sec_server1 => SecServer1#ev.pid, + sec_server2 => SecServer2#ev.pid, + sec_server3 => SecServer3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([PrimServer, + SecServer1, SecServer2, SecServer3, + Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_tcp4(suite) -> + []; +sc_lc_recvmsg_response_tcp4(doc) -> + []; +sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_recvmsg_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_lc_recvmsg_response_tcp6(suite) -> + []; +sc_lc_recvmsg_response_tcp6(doc) -> + []; +sc_lc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_recvmsg_response_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is Unix Domain (stream) socket. + +sc_lc_recvmsg_response_tcpL(suite) -> + []; +sc_lc_recvmsg_response_tcpL(doc) -> + []; +sc_lc_recvmsg_response_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_recvmsg_response_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => local, + protocol => default, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_udp4(suite) -> + []; +sc_lc_recvmsg_response_udp4(doc) -> + []; +sc_lc_recvmsg_response_udp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_udp4, + fun() -> + ?TT(?SECS(10)), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + protocol => udp, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_lc_recvmsg_response_udp6(suite) -> + []; +sc_lc_recvmsg_response_udp6(doc) -> + []; +sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_udp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(10)), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + protocol => udp, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is Unix Domain (dgram) socket. + +sc_lc_recvmsg_response_udpL(suite) -> + []; +sc_lc_recvmsg_response_udpL(doc) -> + []; +sc_lc_recvmsg_response_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_recvmsg_response_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => local, + protocol => default, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +sc_lc_acceptor_response_tcp4(suite) -> + []; +sc_lc_acceptor_response_tcp4(doc) -> + []; +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_acceptor_response_tcp4, + fun() -> + InitState = #{domain => inet, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv6. + +sc_lc_acceptor_response_tcp6(suite) -> + []; +sc_lc_acceptor_response_tcp6(doc) -> + []; +sc_lc_acceptor_response_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_acceptor_response_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is Unix Domain (stream) socket. + +sc_lc_acceptor_response_tcpL(suite) -> + []; +sc_lc_acceptor_response_tcpL(doc) -> + []; +sc_lc_acceptor_response_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_lc_acceptor_response_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + protocol => default}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_acceptor_response_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await connection", + cmd => fun(#{sock := LSock, timeout := Timeout} = _State) -> + case socket:accept(LSock, Timeout) of + {error, timeout} -> + ok; + {ok, Sock} -> + ?SEV_EPRINT("unexpected success"), + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept timeout)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_timeout), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + lsa := #{path := Path}} = State) -> + case socket:close(Sock) of + ok -> + State1 = + unlink_path(Path, + fun() -> + maps:remove(lsa, State) + end, + fun() -> + State + end), + {ok, maps:remove(sock, State1)}; + {error, _} = ERROR -> + unlink_path(Path), + ERROR + end; + (#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + % Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init) + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{sock := Sock} = State) -> + case socket:accept(Sock) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept closed)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_closed) + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor 'primary acceptor'", + cmd => fun(#{prim_acc := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor 'secondary acceptor 1'", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary acceptor 2", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary acceptor 3", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the primary server + #{desc => "order 'primary acceptor' start", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await 'primary acceptor' ready (init)", + cmd => fun(#{prim_acc := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init), + {ok, State#{sock => Sock}} + end}, + + %% Start the secondary acceptor 1 + #{desc => "order 'secondary acceptor 1' start", + cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary acceptor 1' ready (init)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init) + end}, + + %% Start the secondary acceptor 2 + #{desc => "order 'secondary acceptor 2' start", + cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary acceptor 2' ready (init)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init) + end}, + + %% Start the secondary acceptor 3 + #{desc => "order 'secondary acceptor 3' start", + cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary acceptor 3' ready (init)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init) + end}, + + + %% The actual test + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary acceptor 1' to continue (accept)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary acceptor 2' to continue (accept)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary acceptor 3' to continue (accept)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'primary acceptor' to continue", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)), + ok + end}, + #{desc => "await 'primary acceptor' ready (accept timeout)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout) + end}, + #{desc => "order 'primary acceptor' to continue (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await 'primary acceptor' ready (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, prim_acc, close) + end}, + #{desc => "await 'secondary acceptor 1' ready (accept closed)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed) + end}, + #{desc => "await 'secondary acceptor 2' ready (accept closed)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed) + end}, + #{desc => "await 'secondary acceptor 3' ready (accept closed)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed) + end}, + + + %% Terminations + #{desc => "order 'secondary acceptor 3' to terminate", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary acceptor 3' termination", + cmd => fun(#{sec_acc3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc3, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary acceptor 2' to terminate", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary acceptor 2' termination", + cmd => fun(#{sec_acc2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc2, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary acceptor 1' to terminate", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary acceptor 1' termination", + cmd => fun(#{sec_acc1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc1, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'primary acceptor' to terminate", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'primary acceptor' termination", + cmd => fun(#{prim_acc := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(prim_acc, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start 'primary acceptor' evaluator"), + PrimAccInitState = InitState, + PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), + + i("start 'secondary acceptor 1' evaluator"), + SecAccInitState = #{}, + SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 2' evaluator"), + SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 3' evaluator"), + SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{prim_acc => PrimAcc#ev.pid, + sec_acc1 => SecAcc1#ev.pid, + sec_acc2 => SecAcc2#ev.pid, + sec_acc3 => SecAcc3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% + +sc_rc_recv_response_tcp4(suite) -> + []; +sc_rc_recv_response_tcp4(doc) -> + []; +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rc_recv_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_rc_recv_response_tcp6(suite) -> + []; +sc_rc_recv_response_tcp6(doc) -> + []; +sc_rc_recv_response_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rc_recv_response_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is Unix Domain (stream) socket. + +sc_rc_recv_response_tcpL(suite) -> + []; +sc_rc_recv_response_tcpL(doc) -> + []; +sc_rc_recv_response_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rc_recv_response_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => local, + protocol => default, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rc_receive_response_tcp(InitState) -> + %% Each connection are handled by handler processes. + %% These are created (on the fly) and handled internally + %% by the server! + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, + local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + local_sa := LSA}) -> + %% Actually we only need to send the path, + %% but to keep it simple, we send the "same" + %% as for non-local. + ?SEV_ANNOUNCE_READY(Tester, init, LSA), + ok; + (#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept all three connections)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept 1", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(1, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock1 => Sock, + handler1 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler 1 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1} = _State) -> + ?SEV_AWAIT_READY(Handler1, handler1, init, + [{tester, Tester}]) + end}, + #{desc => "accept 2", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(2, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock2 => Sock, + handler2 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler 2 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2} = _State) -> + ?SEV_AWAIT_READY(Handler2, handler2, init, + [{tester, Tester}, + {handler1, Handler1}]) + end}, + #{desc => "accept 3", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(3, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock3 => Sock, + handler3 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler 3 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2, + handler3 := Handler3} = _State) -> + ?SEV_AWAIT_READY(Handler3, handler3, init, + [{tester, Tester}, + {handler1, Handler1}, + {handler2, Handler2}]) + end}, + #{desc => "announce ready (accept all three connections)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler 1 to receive", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "order handler 2 to receive", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "order handler 3 to receive", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler 1 (recv)", + cmd => fun(#{tester := Tester, handler1 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler1, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ready from handler 2 (recv)", + cmd => fun(#{tester := Tester, handler2 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler2, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ready from handler 3 (recv)", + cmd => fun(#{tester := Tester, handler3 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler3, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv closed from all handlers)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + %% Pid ! {terminate, self(), ok}, + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock1, State), + State2 = maps:remove(handler1, State1), + {ok, State2} + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock2, State), + State2 = maps:remove(handler2, State1), + {ok, State2} + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock3, State), + State2 = maps:remove(handler3, State1), + {ok, State2} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := #{path := Path}} = State) -> + case socket:close(LSock) of + ok -> + State1 = + unlink_path(Path, + fun() -> + maps:remove(lsa, State) + end, + fun() -> + State + end), + {ok, maps:remove(lsock, State1)}; + {error, _} = ERROR -> + unlink_path(Path), + ERROR + end; + (#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + node_id => NodeID, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host, node_id := NodeID} = State) -> + case start_node(Host, l2a(f("client_~w", [NodeID]))) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", [Node]), + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node 1", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node} = State) -> + Pid = sc_rc_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, + server_sa := ServerSA, + protocol := Proto}) -> + ?SEV_ANNOUNCE_START(Client, {ServerSA, Proto}), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connected)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to close", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 3", + cmd => fun(#{client3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client(s) + #{desc => "order client 1 start", + cmd => fun(#{client1 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}), + ok + end}, + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client1, init) + end}, + #{desc => "order client 2 start", + cmd => fun(#{client2 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client2, init) + end}, + #{desc => "order client 3 start", + cmd => fun(#{client3 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}), + ok + end}, + #{desc => "await client 3 ready (init)", + cmd => fun(#{client3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client3, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 continue (connect)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 1 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client1, client1, connect, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 2 continue (connect)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 2 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client2, client2, connect, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (connect)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 3 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client3, client3, connect, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (accept from all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order server continue (recv for all connections)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 continue (close)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 1 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client1, client1, close, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 2 continue (close)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 2 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client2, client2, close, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (close)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 3 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client3, client1, close, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (close for all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_closed, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + + %% Terminations + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 3 to terminate", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 3 termination", + cmd => fun(#{client3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client3, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState), + Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState), + Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid, + client3 => Client3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, + Client1, Client2, Client3, + Tester]). + + +sc_rc_tcp_client_start(Node) -> + Self = self(), + Fun = fun() -> sc_rc_tcp_client(Self) end, + erlang:spawn(Node, Fun). + + +sc_rc_tcp_client(Parent) -> + sc_rc_tcp_client_init(Parent), + {ServerSA, Proto} = sc_rc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rc_tcp_client_create(Domain, Proto), + Path = sc_rc_tcp_client_bind(Sock, Domain), + sc_rc_tcp_client_announce_ready(Parent, init), + sc_rc_tcp_client_await_continue(Parent, connect), + sc_rc_tcp_client_connect(Sock, ServerSA), + sc_rc_tcp_client_announce_ready(Parent, connect), + sc_rc_tcp_client_await_continue(Parent, close), + sc_rc_tcp_client_close(Sock, Path), + sc_rc_tcp_client_announce_ready(Parent, close), + Reason = sc_rc_tcp_client_await_terminate(Parent), + ?SEV_IPRINT("terminate"), + exit(Reason). + +sc_rc_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +sc_rc_tcp_client_await_start(Parent) -> + i("sc_rc_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +sc_rc_tcp_client_create(Domain, Proto) -> + i("sc_rc_tcp_client_create -> entry"), + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + case socket:getopt(Sock, otp, fd) of + {ok, FD} -> + put(sname, f("rclient-~w", [FD])); % Update SName + _ -> + ok + end, + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rc_tcp_client_bind(Sock, Domain) -> + i("sc_rc_tcp_client_bind -> entry"), + LSA = which_local_socket_addr(Domain), + case socket:bind(Sock, LSA) of + {ok, _} -> + case socket:sockname(Sock) of + {ok, #{family := local, path := Path}} -> + Path; + {ok, _} -> + undefined; + {error, Reason1} -> + exit({sockname, Reason1}) + end; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("ready ~w", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +sc_rc_tcp_client_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await ~w continue", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +sc_rc_tcp_client_connect(Sock, ServerSA) -> + i("sc_rc_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rc_tcp_client_close(Sock, Path) -> + i("sc_rc_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + unlink_path(Path), + ok; + {error, Reason} -> + ?SEV_EPRINT("failed closing: " + "~n Reason: ~p", [Reason]), + unlink_path(Path), + {error, {close, Reason}} + end. + +sc_rc_tcp_client_await_terminate(Parent) -> + i("sc_rc_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +%% The handlers run on the same node as the server (the local node). + +sc_rc_tcp_handler_start(ID, Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + Pid. + +sc_rc_tcp_handler(ID, Parent, Recv, Sock) -> + sc_rc_tcp_handler_init(ID, socket:getopt(Sock, otp, fd), Parent), + sc_rc_tcp_handler_await(Parent, recv), + RecvRes = sc_rc_tcp_handler_recv(Recv, Sock), + sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes), + Reason = sc_rc_tcp_handler_await(Parent, terminate), + exit(Reason). + +sc_rc_tcp_handler_init(ID, {ok, FD}, Parent) -> + put(sname, f("handler-~w:~w", [ID, FD])), + _MRef = erlang:monitor(process, Parent), + ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), + ok. + +sc_rc_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rc_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +sc_rc_tcp_handler_recv(Recv, Sock) -> + ?SEV_IPRINT("recv"), + try Recv(Sock) of + {error, closed} -> + ok; + {ok, _} -> + ?SEV_IPRINT("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_IPRINT("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ?SEV_IPRINT("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) -> + ?SEV_IPRINT("announce ready"), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_rc_recvmsg_response_tcp4(suite) -> + []; +sc_rc_recvmsg_response_tcp4(doc) -> + []; +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_rc_recvmsg_response_tcp6(suite) -> + []; +sc_rc_recvmsg_response_tcp6(doc) -> + []; +sc_rc_recvmsg_response_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rc_recvmsg_response_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is Unix Domain (stream) socket. + +sc_rc_recvmsg_response_tcpL(suite) -> + []; +sc_rc_recvmsg_response_tcpL(doc) -> + []; +sc_rc_recvmsg_response_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rc_recvmsg_response_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => local, + protocol => default, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% This would of course not work for Unix Domain sockets. +%% + +sc_rs_recv_send_shutdown_receive_tcp4(suite) -> + []; +sc_rs_recv_send_shutdown_receive_tcp4(doc) -> + []; +sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(sc_rs_recv_send_shutdown_receive_tcp4, + fun() -> + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => inet, + proto => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv6. + +sc_rs_recv_send_shutdown_receive_tcp6(suite) -> + []; +sc_rs_recv_send_shutdown_receive_tcp6(doc) -> + []; +sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rs_recv_send_shutdown_receive_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(10)), + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => inet6, + proto => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is Unix Domain (stream) socket. + +sc_rs_recv_send_shutdown_receive_tcpL(suite) -> + []; +sc_rs_recv_send_shutdown_receive_tcpL(doc) -> + []; +sc_rs_recv_send_shutdown_receive_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_rs_recv_send_shutdown_receive_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => local, + proto => default, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rs_send_shutdown_receive_tcp(InitState) -> + %% The connection is handled by a handler processes. + %% This are created (on the fly) and handled internally + %% by the server! + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + i("get local address for ~p", [Domain]), + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + local_sa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, local_sa := LSA}) -> + ?SEV_ANNOUNCE_READY(Tester, init, LSA), + ok; + (#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = + sc_rs_tcp_handler_start(Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock => Sock, + handler => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (first recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler to receive (first)", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler (first recv)", + cmd => fun(#{tester := Tester, handler := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + ?SEV_IPRINT("first recv: ~p", [Result]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (first recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + #{desc => "await continue (second recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler to receive (second)", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler (second recv)", + cmd => fun(#{tester := Tester, handler := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + ?SEV_IPRINT("second recv: ~p", [Result]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (second recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock, State), + State2 = maps:remove(handler, State1), + {ok, State2} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node, + send := Send} = State) -> + Pid = sc_rs_tcp_client_start(Node, Send), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, + proto := Proto, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, {ServerSA, Proto}), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, send, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{rclient_data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to send", + cmd => fun(#{rclient := Client, + rclient_data := Data}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + + #{desc => "await continue (shutdown)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, shutdown, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to shutdown", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, shutdown), + ok + end}, + #{desc => "await remote client ready (shiutdown)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, shutdown, + [{tester, Tester}]) + end}, + #{desc => "announce ready (shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, shutdown), + ok + end}, + + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to close", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client(s) + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid, + data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send, Data), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]), + ok + end}, + + #{desc => "order client continue (shutdown)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, shutdown), + ok + end}, + #{desc => "await client ready (shutdown)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, shutdown, + [{server, Server}]), + ok + end}, + + #{desc => "order server continue (first recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (first recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]), + ok + end}, + + #{desc => "order server continue (second recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (second recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]), + ok + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client continue (close)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client ready (close)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, close, + [{server, Server}]), + ok + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + proto => maps:get(proto, InitState), + recv => maps:get(recv, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + proto => maps:get(proto, InitState), + send => maps:get(send, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + data => maps:get(data, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +sc_rs_tcp_client_start(Node, Send) -> + Self = self(), + Fun = fun() -> sc_rs_tcp_client(Self, Send) end, + erlang:spawn(Node, Fun). + + +sc_rs_tcp_client(Parent, Send) -> + sc_rs_tcp_client_init(Parent), + {ServerSA, Proto} = sc_rs_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rs_tcp_client_create(Domain, Proto), + Path = sc_rs_tcp_client_bind(Sock, Domain), + sc_rs_tcp_client_announce_ready(Parent, init), + sc_rs_tcp_client_await_continue(Parent, connect), + sc_rs_tcp_client_connect(Sock, ServerSA), + sc_rs_tcp_client_announce_ready(Parent, connect), + Data = sc_rs_tcp_client_await_continue(Parent, send), + sc_rs_tcp_client_send(Sock, Send, Data), + sc_rs_tcp_client_announce_ready(Parent, send), + sc_rs_tcp_client_await_continue(Parent, shutdown), + sc_rs_tcp_client_shutdown(Sock), + sc_rs_tcp_client_announce_ready(Parent, shutdown), + sc_rs_tcp_client_await_continue(Parent, close), + sc_rs_tcp_client_close(Sock, Path), + sc_rs_tcp_client_announce_ready(Parent, close), + Reason = sc_rs_tcp_client_await_terminate(Parent), + ?SEV_IPRINT("terminate"), + exit(Reason). + +sc_rs_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +sc_rs_tcp_client_await_start(Parent) -> + i("sc_rs_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +sc_rs_tcp_client_create(Domain, Proto) -> + i("sc_rs_tcp_client_create -> entry"), + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rs_tcp_client_bind(Sock, Domain) -> + i("sc_rs_tcp_client_bind -> entry"), + LSA = which_local_socket_addr(Domain), + case socket:bind(Sock, LSA) of + {ok, _} -> + case socket:sockname(Sock) of + {ok, #{family := local, path := Path}} -> + Path; + {ok, _} -> + undefined; + {error, Reason1} -> + exit({sockname, Reason1}) + end; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rs_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +sc_rs_tcp_client_await_continue(Parent, Slogan) -> + i("sc_rs_tcp_client_await_continue -> entry"), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ok; + {ok, Extra} -> + Extra; + {error, Reason} -> + exit({await_continue, Slogan, Reason}) + end. + + +sc_rs_tcp_client_connect(Sock, ServerSA) -> + i("sc_rs_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rs_tcp_client_send(Sock, Send, Data) -> + i("sc_rs_tcp_client_send -> entry"), + case Send(Sock, Data) of + ok -> + ok; + {error, Reason} -> + exit({send, Reason}) + end. + +sc_rs_tcp_client_shutdown(Sock) -> + i("sc_rs_tcp_client_shutdown -> entry"), + case socket:shutdown(Sock, write) of + ok -> + ok; + {error, Reason} -> + exit({shutdown, Reason}) + end. + +sc_rs_tcp_client_close(Sock, Path) -> + i("sc_rs_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + unlink_path(Path), + ok; + {error, Reason} -> + ?SEV_EPRINT("failed closing: " + "~n Reason: ~p", [Reason]), + unlink_path(Path), + {error, {close, Reason}} + end. + +sc_rs_tcp_client_await_terminate(Parent) -> + i("sc_rs_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +%% The handlers run on the same node as the server (the local node). + +sc_rs_tcp_handler_start(Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rs_tcp_handler(Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + Pid. + +sc_rs_tcp_handler(Parent, Recv, Sock) -> + sc_rs_tcp_handler_init(Parent), + sc_rs_tcp_handler_await(Parent, recv), + ok = sc_rs_tcp_handler_recv(Recv, Sock, true), + sc_rs_tcp_handler_announce_ready(Parent, recv, received), + sc_rs_tcp_handler_await(Parent, recv), + ok = sc_rs_tcp_handler_recv(Recv, Sock, false), + sc_rs_tcp_handler_announce_ready(Parent, recv, closed), + Reason = sc_rs_tcp_handler_await(Parent, terminate), + exit(Reason). + +sc_rs_tcp_handler_init(Parent) -> + put(sname, "handler"), + _MRef = erlang:monitor(process, Parent), + ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), + ok. + +sc_rs_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rs_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +%% This hould actually work - we leave it for now +sc_rs_tcp_handler_recv(Recv, Sock, First) -> + ?SEV_IPRINT("recv"), + try Recv(Sock) of + {ok, _} when (First =:= true) -> + ok; + {error, closed} when (First =:= false) -> + ok; + {ok, _} -> + ?SEV_IPRINT("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_IPRINT("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ?SEV_IPRINT("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) -> + ?SEV_IPRINT("announce ready"), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv4. + +sc_rs_recvmsg_send_shutdown_receive_tcp4(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp4(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4, + fun() -> + ?TT(?SECS(30)), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => inet, + proto => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv6. + +sc_rs_recvmsg_send_shutdown_receive_tcp6(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp6(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(10)), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => inet6, + proto => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is UNix Domain (stream) socket. + +sc_rs_recvmsg_send_shutdown_receive_tcpL(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcpL(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + {ok, CWD} = file:get_cwd(), + ?SEV_IPRINT("CWD: ~s", [CWD]), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + %% On some platforms, the address + %% is *not* provided (e.g. FreeBSD) + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + %% On some platforms, the address + %% *is* provided (e.g. linux) + {ok, #{addr := #{family := local}, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => local, + proto => default, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test that the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use TCP on IPv4. + +traffic_send_and_recv_counters_tcp4(suite) -> + []; +traffic_send_and_recv_counters_tcp4(doc) -> + []; +traffic_send_and_recv_counters_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_send_and_recv_counters_tcp4, + fun() -> + InitState = #{domain => inet, + proto => tcp, + recv => fun(S) -> socket:recv(S) end, + send => fun(S, D) -> socket:send(S, D) end}, + ok = traffic_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test that the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use TCP on IPv6. + +traffic_send_and_recv_counters_tcp6(suite) -> + []; +traffic_send_and_recv_counters_tcp6(doc) -> + []; +traffic_send_and_recv_counters_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_send_and_recv_counters_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => tcp, + recv => fun(S) -> socket:recv(S) end, + send => fun(S, D) -> socket:send(S, D) end}, + ok = traffic_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test that the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use default (TCP) on local. + +traffic_send_and_recv_counters_tcpL(suite) -> + []; +traffic_send_and_recv_counters_tcpL(doc) -> + []; +traffic_send_and_recv_counters_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_send_and_recv_counters_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default, + recv => fun(S) -> socket:recv(S) end, + send => fun(S, D) -> socket:send(S, D) end}, + ok = traffic_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test that the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use TCP on IPv4. + +traffic_sendmsg_and_recvmsg_counters_tcp4(suite) -> + []; +traffic_sendmsg_and_recvmsg_counters_tcp4(doc) -> + []; +traffic_sendmsg_and_recvmsg_counters_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendmsg_and_recvmsg_counters_tcp4, + fun() -> + InitState = #{domain => inet, + proto => tcp, + recv => fun(S) -> + case socket:recvmsg(S) of + {ok, #{addr := _Source, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + send => fun(S, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(S, MsgHdr) + end}, + ok = traffic_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test that the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use TCP on IPv6. + +traffic_sendmsg_and_recvmsg_counters_tcp6(suite) -> + []; +traffic_sendmsg_and_recvmsg_counters_tcp6(doc) -> + []; +traffic_sendmsg_and_recvmsg_counters_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendmsg_and_recvmsg_counters_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => tcp, + recv => fun(S) -> + case socket:recvmsg(S) of + {ok, #{addr := _Source, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + send => fun(S, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(S, MsgHdr) + end}, + ok = traffic_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test that the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use default (TCP) on local. + +traffic_sendmsg_and_recvmsg_counters_tcpL(suite) -> + []; +traffic_sendmsg_and_recvmsg_counters_tcpL(doc) -> + []; +traffic_sendmsg_and_recvmsg_counters_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendmsg_and_recvmsg_counters_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default, + recv => fun(S) -> + case socket:recvmsg(S) of + {ok, #{addr := _Source, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + send => fun(S, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(S, MsgHdr) + end}, + ok = traffic_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +traffic_send_and_recv_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, eafnosupport = Reason} -> + {skip, Reason}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + local_sa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, + local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "initial (listen socket) counter validation (=zero)", + cmd => fun(#{lsock := LSock} = _State) -> + try socket:info(LSock) of + #{counters := Counters} -> + ?SEV_IPRINT("Validate initial listen socket counters: " + "~s", [format_counters(listen, + Counters)]), + traffic_sar_counters_validation(Counters) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + local_sa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, + lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, CSock} -> + #{counters := LCnts} = socket:info(LSock), + ?SEV_IPRINT("Validate listen counters: " + "~s", [format_counters(listen, + LCnts)]), + traffic_sar_counters_validation( + LCnts, + [{acc_success, 1}, + {acc_fails, 0}, + {acc_tries, 1}, + {acc_waits, 0}]), + #{counters := CCnts} = socket:info(CSock), + ?SEV_IPRINT("Validate initial accept counters: " + "~s", [format_counters(CCnts)]), + traffic_sar_counters_validation(CCnts), + {ok, State#{csock => CSock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "initial counter validation (=zero)", + cmd => fun(#{csock := Sock} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("Validate initial counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation(Counters) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (1)", + cmd => fun(#{csock := Sock, + recv := Recv} = State) -> + case Recv(Sock) of + {ok, Data} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => 1, + read_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 1)", + cmd => fun(#{csock := Sock, + read_pkg := Pkg, + read_byte := Byte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, Pkg}, + {read_byte, Byte}, + {read_tries, any}, + {read_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + #{desc => "await continue (send_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (1)", + cmd => fun(#{csock := Sock, + send := Send} = State) -> + Data = ?DATA, + case Send(Sock, Data) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => 1, + write_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 1)", + cmd => fun(#{csock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + #{desc => "await continue (recv_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (2)", + cmd => fun(#{csock := Sock, + recv := Recv, + read_pkg := Pkg, + read_byte := Byte} = State) -> + case Recv(Sock) of + {ok, Data} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => Pkg + 1, + read_byte => Byte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 2)", + cmd => fun(#{csock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + #{desc => "await continue (send_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (2)", + cmd => fun(#{csock := Sock, + send := Send, + write_pkg := Pkg, + write_byte := Byte} = State) -> + Data = ?DATA, + case Send(Sock, Data) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => Pkg + 1, + write_byte => Byte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 2)", + cmd => fun(#{csock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket (just in case)", + cmd => fun(#{csock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, eafnosupport = Reason} -> + {skip, Reason}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect), + ok + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (1)", + cmd => fun(#{sock := Sock, + send := Send} = State) -> + Data = ?DATA, + case Send(Sock, Data) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => 1, + write_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 1)", + cmd => fun(#{sock := Sock, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{write_pkg, SPkg}, + {write_byte, SByte}, + {write_tries, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + #{desc => "await continue (recv_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (1)", + cmd => fun(#{sock := Sock, + recv := Recv} = State) -> + case Recv(Sock) of + {ok, Data} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => 1, + read_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 1)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + #{desc => "await continue (send_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (2)", + cmd => fun(#{sock := Sock, + send := Send, + write_pkg := SPkg, + write_byte := SByte} = State) -> + Data = ?DATA, + case Send(Sock, Data) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => SPkg + 1, + write_byte => SByte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 2)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + #{desc => "await continue (recv_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (2)", + cmd => fun(#{sock := Sock, + recv := Recv, + read_pkg := RPkg, + read_byte := RByte} = State) -> + case Recv(Sock) of + {ok, Data} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => RPkg + 1, + read_byte => RByte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 2)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{domain := local, + server := Pid} = State) -> + {ok, Path} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{path => Path}}; + (#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{domain := local, + client := Pid, + path := Path} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Path), + ok; + (#{client := Pid, + port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order server to continue (recv_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate), + ok + end}, + #{desc => "order client to continue (send_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate), + ok + end}, + #{desc => "await client ready (send_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_and_validate) + end}, + #{desc => "await server ready (recv_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_and_validate) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client to continue (recv_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate), + ok + end}, + #{desc => "order server to continue (send_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate), + ok + end}, + #{desc => "await server ready (send_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_and_validate) + end}, + #{desc => "await client ready (recv_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_and_validate) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order server to continue (recv_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate), + ok + end}, + #{desc => "order client to continue (send_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate), + ok + end}, + #{desc => "await client ready (send_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_and_validate) + end}, + #{desc => "await server ready (recv_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_and_validate) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client to continue (recv_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate), + ok + end}, + #{desc => "order server to continue (send_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate), + ok + end}, + #{desc => "await server ready (send_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_and_validate) + end}, + #{desc => "await client ready (recv_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_and_validate) + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState#{host => local_host()}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +format_counters(Counters) -> + format_counters(traffic, Counters). + +format_counters(Type, Counters) when (Type =:= listen) orelse (Type =:= traffic) -> + format_counters(" ", Type, Counters). + +format_counters(Prefix, traffic, Counters) -> + ReadByte = maps:get(read_byte, Counters, -1), + ReadFails = maps:get(read_fails, Counters, -1), + ReadPkg = maps:get(read_pkg, Counters, -1), + ReadPkgMax = maps:get(read_pkg_max, Counters, -1), + ReadTries = maps:get(read_tries, Counters, -1), + ReadWaits = maps:get(read_waits, Counters, -1), + WriteByte = maps:get(write_byte, Counters, -1), + WriteFails = maps:get(write_fails, Counters, -1), + WritePkg = maps:get(write_pkg, Counters, -1), + WritePkgMax = maps:get(write_pkg_max, Counters, -1), + WriteTries = maps:get(write_tries, Counters, -1), + WriteWaits = maps:get(write_waits, Counters, -1), + f("~n~sNumber Of Read Bytes: ~p" + "~n~sNumber Of Read Fails: ~p" + "~n~sNumber Of Read Packages: ~p" + "~n~sNumber Of Read Tries: ~p" + "~n~sNumber Of Read Waits: ~p" + "~n~sMax Read Package Size: ~p" + "~n~sNumber Of Write Bytes: ~p" + "~n~sNumber Of Write Fails: ~p" + "~n~sNumber Of Write Packages: ~p" + "~n~sNumber Of Write Tries: ~p" + "~n~sNumber Of Write Waits: ~p" + "~n~sMax Write Package Size: ~p", + [Prefix, ReadByte, + Prefix, ReadFails, + Prefix, ReadPkg, + Prefix, ReadTries, + Prefix, ReadWaits, + Prefix, ReadPkgMax, + Prefix, WriteByte, + Prefix, WriteFails, + Prefix, WritePkg, + Prefix, WriteTries, + Prefix, WriteWaits, + Prefix, WritePkgMax]); + +format_counters(Prefix, listen, Counters) -> + AccSuccess = maps:get(acc_success, Counters, -1), + AccFails = maps:get(acc_fails, Counters, -1), + AccTries = maps:get(acc_tries, Counters, -1), + AccWaits = maps:get(acc_waits, Counters, -1), + f("~n~sNumber Of Successful Accepts: ~p" + "~n~sNumber Of Failed Accepts: ~p" + "~n~sNumber Of Accept Attempts: ~p" + "~n~sNumber Of Accept Waits: ~p", + [Prefix, AccSuccess, + Prefix, AccFails, + Prefix, AccTries, + Prefix, AccWaits]). + +all_counters() -> + [ + read_byte, + read_fails, + read_pkg, + read_pkg_max, + read_tries, + read_waits, + write_byte, + write_fails, + write_pkg, + write_pkg_max, + write_tries, + write_waits, + acc_success, + acc_fails, + acc_tries, + acc_waits + ]. + +zero_counters() -> + [{Cnt, 0} || Cnt <- all_counters()]. + +any_counters() -> + [{Cnt, any} || Cnt <- all_counters()]. + + +%% This function ensures that we have a list of "validate counters" +%% that have an entry for each existing counter. + +ensure_counters(Counters) -> + ensure_counters(any_counters(), Counters, []). + +ensure_counters([], [], Acc) -> + lists:reverse(Acc); +ensure_counters([{Cnt, Val}|DefCounters], Counters, Acc) -> + case lists:keysearch(Cnt, 1, Counters) of + {value, {Cnt, _} = T} -> + Counters2 = lists:keydelete(Cnt, 1, Counters), + ensure_counters(DefCounters, Counters2, [T|Acc]); + false -> + ensure_counters(DefCounters, Counters, [{Cnt, Val}|Acc]) + end. + +traffic_sar_counters_validation(Counters) -> + %% ?SEV_IPRINT("traffic_sar_counters_validation -> entry with" + %% "~n Counters: ~p", [Counters]), + traffic_sar_counters_validation2(maps:to_list(Counters), + zero_counters()). + +traffic_sar_counters_validation(Counters, ValidateCounters) -> + %% ?SEV_IPRINT("traffic_sar_counters_validation -> entry with" + %% "~n Counters: ~p" + %% "~n Validate Counters: ~p", [Counters, ValidateCounters]), + traffic_sar_counters_validation2(maps:to_list(Counters), + ensure_counters(ValidateCounters)). + +traffic_sar_counters_validation2(Counters, []) -> + %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> Remaining Counters: " + %% "~n ~p", [Counters]), + (catch lists:foreach( + fun({_Cnt, 0}) -> ok; + ({Cnt, Val}) -> + throw({error, {invalid_counter, Cnt, Val}}) + end, + Counters)); +traffic_sar_counters_validation2(Counters, [{Cnt, Val}|ValidateCounters]) -> + %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> try validate ~w when" + %% "~n Counters: ~p" + %% "~n ValidateCounters: ~p", [Cnt, Counters, ValidateCounters]), + case lists:keysearch(Cnt, 1, Counters) of + {value, {Cnt, Val}} -> + %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> ~w validated", [Cnt]), + Counters2 = lists:keydelete(Cnt, 1, Counters), + traffic_sar_counters_validation2(Counters2, ValidateCounters); + {value, {Cnt, _Val}} when (Val =:= any) -> + %% ?SEV_IPRINT("traffic_sar_counters_validation2 -> " + %% "~w validated (any) when" + %% "~n Counters: ~p", [Cnt, Counters]), + Counters2 = lists:keydelete(Cnt, 1, Counters), + traffic_sar_counters_validation2(Counters2, ValidateCounters); + {value, {Cnt, InvVal}} -> + ?SEV_EPRINT("traffic_sar_counters_validation2 -> " + "~w validation failed: " + "~n Expected Value: ~p" + "~n Actual Value: ~p", [Cnt, Val, InvVal]), + {error, {invalid_counter, Cnt, InvVal, Val}}; + false -> + ?SEV_EPRINT("traffic_sar_counters_validation2 -> " + "~w validation failed: Unknown", [Cnt]), + {error, {unknown_counter, Cnt, Counters}} + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use UDP on IPv4. + +traffic_sendto_and_recvfrom_counters_udp4(suite) -> + []; +traffic_sendto_and_recvfrom_counters_udp4(doc) -> + []; +traffic_sendto_and_recvfrom_counters_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendto_and_recvfrom_counters_udp4, + fun() -> + InitState = #{domain => inet, + proto => udp, + recv => fun(S) -> + socket:recvfrom(S) + end, + send => fun(S, Data, Dest) -> + socket:sendto(S, Data, Dest) + end}, + ok = traffic_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use UDP on IPv6. + +traffic_sendto_and_recvfrom_counters_udp6(suite) -> + []; +traffic_sendto_and_recvfrom_counters_udp6(doc) -> + []; +traffic_sendto_and_recvfrom_counters_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendto_and_recvfrom_counters_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => udp, + recv => fun(S) -> + socket:recvfrom(S) + end, + send => fun(S, Data, Dest) -> + socket:sendto(S, Data, Dest) + end}, + ok = traffic_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use default (UDP) on local. + +traffic_sendto_and_recvfrom_counters_udpL(suite) -> + []; +traffic_sendto_and_recvfrom_counters_udpL(doc) -> + []; +traffic_sendto_and_recvfrom_counters_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendto_and_recvfrom_counters_udp4, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default, + recv => fun(S) -> + socket:recvfrom(S) + end, + send => fun(S, Data, Dest) -> + socket:sendto(S, Data, Dest) + end}, + ok = traffic_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use UDP on IPv4. + +traffic_sendmsg_and_recvmsg_counters_udp4(suite) -> + []; +traffic_sendmsg_and_recvmsg_counters_udp4(doc) -> + []; +traffic_sendmsg_and_recvmsg_counters_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendmsg_and_recvmsg_counters_udp4, + fun() -> + InitState = #{domain => inet, + proto => udp, + recv => fun(S) -> + case socket:recvmsg(S) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + send => fun(S, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(S, MsgHdr) + end}, + ok = traffic_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use UDP on IPv6. + +traffic_sendmsg_and_recvmsg_counters_udp6(suite) -> + []; +traffic_sendmsg_and_recvmsg_counters_udp6(doc) -> + []; +traffic_sendmsg_and_recvmsg_counters_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendmsg_and_recvmsg_counters_udp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => udp, + recv => fun(S) -> + case socket:recvmsg(S) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + send => fun(S, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(S, MsgHdr) + end}, + ok = traffic_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to (simply) test the counters +%% for both read and write. +%% So that its easy to extend, we use fun's for read and write. +%% We use default (UDP) on local. + +traffic_sendmsg_and_recvmsg_counters_udpL(suite) -> + []; +traffic_sendmsg_and_recvmsg_counters_udpL(doc) -> + []; +traffic_sendmsg_and_recvmsg_counters_udpL(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(traffic_sendmsg_and_recvmsg_counters_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default, + recv => fun(S) -> + case socket:recvmsg(S) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + send => fun(S, Data, Dest) -> + MsgHdr = #{addr => Dest, + iov => [Data]}, + socket:sendmsg(S, MsgHdr) + end}, + ok = traffic_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +traffic_send_and_recv_udp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, proto := Proto} = State) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{sock := LSock, + local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "initial counter validation (=zero)", + cmd => fun(#{sock := Sock} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("Validate initial counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation(Counters) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + local_sa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, + lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (recv_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (1)", + cmd => fun(#{sock := Sock, + recv := Recv} = State) -> + case Recv(Sock) of + {ok, {ClientSA, Data}} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{client_sa => ClientSA, + read_pkg => 1, + read_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 1)", + cmd => fun(#{sock := Sock, + read_pkg := Pkg, + read_byte := Byte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, Pkg}, + {read_byte, Byte}, + {read_tries, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + #{desc => "await continue (send_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (1)", + cmd => fun(#{sock := Sock, + send := Send, + client_sa := ClientSA} = State) -> + Data = ?DATA, + case Send(Sock, Data, ClientSA) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => 1, + write_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 1)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + #{desc => "await continue (recv_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (2)", + cmd => fun(#{sock := Sock, + recv := Recv, + read_pkg := Pkg, + read_byte := Byte} = State) -> + case Recv(Sock) of + {ok, {Source, Data}} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{client_sa => Source, + read_pkg => Pkg + 1, + read_byte => Byte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 2)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + #{desc => "await continue (send_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (2)", + cmd => fun(#{sock := Sock, + client_sa := ClientSA, + send := Send, + write_pkg := Pkg, + write_byte := Byte} = State) -> + Data = ?DATA, + case Send(Sock, Data, ClientSA) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => Pkg + 1, + write_byte => Byte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 2)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket (just in case)", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{sock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{local_sa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + proto := Proto} = State) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "initial counter validation (=zero)", + cmd => fun(#{sock := Sock} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("Validate initial counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation(Counters) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (send_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (1)", + cmd => fun(#{sock := Sock, + send := Send, + server_sa := ServerSA} = State) -> + Data = ?DATA, + case Send(Sock, Data, ServerSA) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => 1, + write_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 1)", + cmd => fun(#{sock := Sock, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{write_pkg, SPkg}, + {write_byte, SByte}, + {write_tries, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + #{desc => "await continue (recv_and_validate 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (1)", + cmd => fun(#{sock := Sock, + recv := Recv, + server_sa := #{family := local} = ServerSA} = State) -> + case Recv(Sock) of + {ok, {ServerSA, Data}} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => 1, + read_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, + recv := Recv, + server_sa := #{addr := Addr, port := Port}} = State) -> + case Recv(Sock) of + {ok, {#{addr := Addr, port := Port}, Data}} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => 1, + read_byte => size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 1)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + #{desc => "await continue (send_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_and_validate) + end}, + #{desc => "send (2)", + cmd => fun(#{sock := Sock, + send := Send, + server_sa := ServerSA, + write_pkg := SPkg, + write_byte := SByte} = State) -> + Data = ?DATA, + case Send(Sock, Data, ServerSA) of + ok -> + ?SEV_IPRINT("sent ~p bytes", [size(Data)]), + {ok, State#{write_pkg => SPkg + 1, + write_byte => SByte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (send 2)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (send_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_validate), + ok + end}, + + #{desc => "await continue (recv_and_validate 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_and_validate) + end}, + #{desc => "recv (2)", + cmd => fun(#{sock := Sock, + server_sa := #{family := local} = ServerSA, + recv := Recv, + read_pkg := RPkg, + read_byte := RByte} = State) -> + case Recv(Sock) of + {ok, {ServerSA, Data}} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => RPkg + 1, + read_byte => RByte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, + server_sa := #{addr := Addr, port := Port}, + recv := Recv, + read_pkg := RPkg, + read_byte := RByte} = State) -> + case Recv(Sock) of + {ok, {#{addr := Addr, port := Port}, Data}} -> + ?SEV_IPRINT("recv ~p bytes", [size(Data)]), + {ok, State#{read_pkg => RPkg + 1, + read_byte => RByte + size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate (recv 2)", + cmd => fun(#{sock := Sock, + read_pkg := RPkg, + read_byte := RByte, + write_pkg := SPkg, + write_byte := SByte} = _State) -> + try socket:info(Sock) of + #{counters := Counters} -> + ?SEV_IPRINT("validate counters: " + "~s", [format_counters(Counters)]), + traffic_sar_counters_validation( + Counters, + [{read_pkg, RPkg}, + {read_byte, RByte}, + {write_pkg, SPkg}, + {write_byte, SByte}, + {read_tries, any}, + {write_tries, any}, + {read_pkg_max, any}, + {write_pkg_max, any}]) + catch + C:E:S -> + ?SEV_EPRINT("Failed get socket info: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {socket_info_failed, {C, E, S}}} + end + end}, + #{desc => "announce ready (recv_and_validate 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_and_validate), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{domain := local, + sock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{domain := local, + server := Pid} = State) -> + {ok, Path} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{path => Path}}; + (#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{domain := local, + client := Pid, + path := Path} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Path), + ok; + (#{client := Pid, + port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + + #{desc => "order server to continue (recv_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate), + ok + end}, + #{desc => "order client to continue (send_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate), + ok + end}, + #{desc => "await client ready (send_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_and_validate) + end}, + #{desc => "await server ready (recv_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_and_validate) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client to continue (recv_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate), + ok + end}, + #{desc => "order server to continue (send_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate), + ok + end}, + #{desc => "await server ready (send_and_validate 1)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_and_validate) + end}, + #{desc => "await client ready (recv_and_validate 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_and_validate) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order server to continue (recv_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, recv_and_validate), + ok + end}, + #{desc => "order client to continue (send_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_and_validate), + ok + end}, + #{desc => "await client ready (send_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_and_validate) + end}, + #{desc => "await server ready (recv_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_and_validate) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client to continue (recv_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, recv_and_validate), + ok + end}, + #{desc => "order server to continue (send_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_and_validate), + ok + end}, + #{desc => "await server ready (send_and_validate 2)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_and_validate) + end}, + #{desc => "await client ready (recv_and_validate 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_and_validate) + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState#{host => local_host()}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv4. + +traffic_send_and_recv_chunks_tcp4(suite) -> + []; +traffic_send_and_recv_chunks_tcp4(doc) -> + []; +traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(traffic_send_and_recv_chunks_tcp4, + fun() -> + InitState = #{domain => inet, + proto => tcp}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv6. + +traffic_send_and_recv_chunks_tcp6(suite) -> + []; +traffic_send_and_recv_chunks_tcp6(doc) -> + []; +traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(traffic_send_and_recv_chunks_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => tcp}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is UNix Domain (Stream) socket. + +traffic_send_and_recv_chunks_tcpL(suite) -> + []; +traffic_send_and_recv_chunks_tcpL(doc) -> + []; +traffic_send_and_recv_chunks_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(traffic_send_and_recv_chunks_tcp6, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +traffic_send_and_recv_chunks_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + local_sa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, + local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + local_sa := LSA}) -> + ?SEV_ANNOUNCE_READY(Tester, init, LSA), + ok; + (#{tester := Tester, + local_sa := LSA, + lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv-many-small)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small) + end}, + #{desc => "recv chunk 1", + cmd => fun(#{csock := Sock} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 1 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 2", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 2 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 3", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 3 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 4", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 4 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 5", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 5 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 6", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 6 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 7", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 7 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 8", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 8 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 9", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 9 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 10", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 10 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv-many-small)", + cmd => fun(#{tester := Tester, + chunks := Chunks} = State) -> + Data = lists:flatten(lists:reverse(Chunks)), + ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data), + {ok, maps:remove(chunks, State)} + end}, + + #{desc => "await continue (recv-one-big)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of + {ok, Size} -> + {ok, State#{size => Size}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv (one big)", + cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) -> + %% socket:setopt(Sock, otp, debug, true), + case socket:recv(Sock, Size) of + {ok, Data} -> + ?SEV_ANNOUNCE_READY(Tester, + recv_one_big, + b2l(Data)), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket (just in case)", + cmd => fun(#{csock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client", + cmd => fun(#{node := Node} = State) -> + Pid = traffic_snr_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, + server_sa := ServerSA, + proto := Proto}) -> + ?SEV_ANNOUNCE_START(Client, {ServerSA, Proto}), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send-one-big)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_one_big, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := Client, data := Data}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await client process ready (send)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-one-big)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_one_big), + ok + end}, + + #{desc => "await continue (send-many-small)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_many_small, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 1)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 1: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 1)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 2)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 2: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 2)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 3)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 3: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 3)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 4)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 4: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 4)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 5)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 5: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 5)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 6)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 6: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 6)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 7)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 7: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 7)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 8)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 8: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 8)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 9)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 9: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 9)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 10)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, []} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 10: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, maps:remove(data, State)} + end}, + #{desc => "await client process ready (send chunk 10)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send stop)", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, stop), + {ok, maps:remove(data, State)} + end}, + #{desc => "await client process ready (send stop)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-many-small)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_many_small), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) + end}, + + #{desc => "generate data", + cmd => fun(State) -> + D1 = lists:seq(1,250), + D2 = lists:duplicate(4, D1), + D3 = lists:flatten(D2), + {ok, State#{data => D3}} + end}, + + %% (client) Send one big and (server) recv may small + #{desc => "order server continue (recv-many-small)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_many_small), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send-one-big)", + cmd => fun(#{client := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_one_big, Data), + ok + end}, + #{desc => "await client ready (send-one-big)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, send_one_big, + [{server, Server}]) + end}, + #{desc => "await server ready (recv-many-small)", + cmd => fun(#{server := Server, + client := Client, + data := Data} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv_many_small, + [{client, Client}]) of + {ok, Data} -> + ok; + {ok, OtherData} -> + {error, {mismatched_data, Data, OtherData}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order server continue (recv-one-big)", + cmd => fun(#{server := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_one_big, length(Data)), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send-many-small)", + cmd => fun(#{client := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_many_small, Data), + ok + end}, + #{desc => "await client ready (send-many-small)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, send_many_small, + [{server, Server}]) + end}, + #{desc => "await server ready (recv-one-big)", + cmd => fun(#{server := Server, + client := Client, + data := Data} = State) -> + case ?SEV_AWAIT_READY(Server, server, recv_one_big, + [{client, Client}]) of + {ok, Data} -> + {ok, maps:remove(data, State)}; + {ok, OtherData} -> + {error, {mismatched_data, Data, OtherData}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +traffic_snr_tcp_client_start(Node) -> + Self = self(), + Fun = fun() -> traffic_snr_tcp_client(Self) end, + erlang:spawn(Node, Fun). + +traffic_snr_tcp_client(Parent) -> + {Sock, ServerSA, Path} = traffic_snr_tcp_client_init(Parent), + traffic_snr_tcp_client_announce_ready(Parent, init), + traffic_snr_tcp_client_await_continue(Parent, connect), + traffic_snr_tcp_client_connect(Sock, ServerSA), + traffic_snr_tcp_client_announce_ready(Parent, connect), + traffic_snr_tcp_client_send_loop(Parent, Sock), + Reason = traffic_snr_tcp_client_await_terminate(Parent), + traffic_snr_tcp_client_close(Sock, Path), + exit(Reason). + + +traffic_snr_tcp_client_send_loop(Parent, Sock) -> + case ?SEV_AWAIT_CONTINUE(Parent, parent, send) of + {ok, stop} -> % Breakes the loop + ?SEV_ANNOUNCE_READY(Parent, send, ok), + ok; + {ok, Data} -> + case socket:send(Sock, Data) of + ok -> + ?SEV_ANNOUNCE_READY(Parent, send, ok), + traffic_snr_tcp_client_send_loop(Parent, Sock); + {error, Reason} = ERROR -> + ?SEV_ANNOUNCE_READY(Parent, send, ERROR), + exit({send, Reason}) + end; + {error, Reason} -> + exit({await_continue, Reason}) + end. + +traffic_snr_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + {ServerSA, Proto} = traffic_snr_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = traffic_snr_tcp_client_create(Domain, Proto), + Path = traffic_snr_tcp_client_bind(Sock, Domain), + {Sock, ServerSA, Path}. + +traffic_snr_tcp_client_await_start(Parent) -> + i("traffic_snr_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +traffic_snr_tcp_client_create(Domain, Proto) -> + i("traffic_snr_tcp_client_create -> entry"), + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +traffic_snr_tcp_client_bind(Sock, Domain) -> + i("traffic_snr_tcp_client_bind -> entry"), + LSA = which_local_socket_addr(Domain), + case socket:bind(Sock, LSA) of + {ok, _} -> + case socket:sockname(Sock) of + {ok, #{family := local, path := Path}} -> + Path; + {ok, _} -> + undefined; + {error, Reason1} -> + exit({sockname, Reason1}) + end; + {error, Reason} -> + exit({bind, Reason}) + end. + +traffic_snr_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +traffic_snr_tcp_client_await_continue(Parent, Slogan) -> + i("traffic_snr_tcp_client_await_continue -> entry"), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +traffic_snr_tcp_client_connect(Sock, ServerSA) -> + i("traffic_snr_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +traffic_snr_tcp_client_close(Sock, Path) -> + i("traffic_snr_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + unlink_path(Path), + ok; + {error, Reason} -> + ?SEV_EPRINT("failed closing: " + "~n Reason: ~p", [Reason]), + unlink_path(Path), + {error, {close, Reason}} + end. + +traffic_snr_tcp_client_await_terminate(Parent) -> + i("traffic_snr_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcp4(Config) when is_list(Config) -> + ?TT(?SECS(15)), + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_send_and_recv_tcp4, + fun() -> + InitState = #{domain => inet, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcp6(Config) when is_list(Config) -> + ?TT(?SECS(15)), + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_send_and_recv_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for Unix Domain (stream) socket. + +traffic_ping_pong_small_send_and_recv_tcpL(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcpL(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcpL(Config) when is_list(Config) -> + ?TT(?SECS(15)), + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_send_and_recv_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_send_and_recv_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet6, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for Unix Domain (stream) socket. + +traffic_ping_pong_medium_send_and_recv_tcpL(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcpL(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcpL(Config) when is_list(Config) -> + ?TT(?SECS(30)), + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_send_and_recv_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv4. + +traffic_ping_pong_large_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcp4(Config) when is_list(Config) -> + ?TT(?SECS(60)), + Msg = l2b(?TPP_LARGE), + Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM), + tc_try(traffic_ping_pong_large_send_and_recv_tcp4, + fun() -> is_old_fedora16() end, + fun() -> + InitState = #{domain => inet, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv6. + +traffic_ping_pong_large_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcp6(Config) when is_list(Config) -> + ?TT(?SECS(60)), + Msg = l2b(?TPP_LARGE), + Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM), + tc_try(traffic_ping_pong_large_send_and_recv_tcp6, + fun() -> is_old_fedora16(), + has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for UNix Domain (stream) socket. + +traffic_ping_pong_large_send_and_recv_tcpL(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcpL(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcpL(Config) when is_list(Config) -> + ?TT(?SECS(60)), + Msg = l2b(?TPP_LARGE), + Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM), + tc_try(traffic_ping_pong_large_send_and_recv_tcpL, + fun() -> + has_support_unix_domain_socket(), + traffic_ping_pong_large_host_cond() + end, + fun() -> + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + +%% This test case is a bit extreme and fails on some hosts +%% (e.g. OpenIndiana Hipster), so exclude them. +traffic_ping_pong_large_host_cond() -> + traffic_ping_pong_large_host_cond(os:type(), os:version()). + +traffic_ping_pong_large_host_cond({unix, sunos}, _) -> + skip("TC does not work on platform"); +traffic_ping_pong_large_host_cond({unix, linux}, _) -> + traffic_ping_pong_large_host_cond2(string:trim(os:cmd("cat /etc/issue"))); +traffic_ping_pong_large_host_cond(_, _) -> + ok. + +traffic_ping_pong_large_host_cond2("Welcome to SUSE Linux Enterprise Server 10 SP1 (i586)" ++ _) -> + skip("TC does not work on platform"); +traffic_ping_pong_large_host_cond2("Fedora release 16 " ++ _) -> + skip("Very slow VM"); +traffic_ping_pong_large_host_cond2(_) -> + ok. + + +is_old_fedora16() -> + is_old_fedora16(string:trim(os:cmd("cat /etc/issue"))). + +%% We actually only have one host running this, a slow VM. +is_old_fedora16("Fedora release 16 " ++ _) -> + skip("Very slow VM"); +is_old_fedora16(_) -> + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendto_and_recvfrom_udp4(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp4(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendto_and_recvfrom_udp6(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp6(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet6, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for Unix Domain (dgram) socket. + +traffic_ping_pong_small_sendto_and_recvfrom_udpL(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udpL(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet6, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for Unix Domain (dgram) socket. + +traffic_ping_pong_medium_sendto_and_recvfrom_udpL(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udpL(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(20)), + InitState = #{domain => inet, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(20)), + InitState = #{domain => inet6, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for Unix Domain (stream) socket. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(20)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet6, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for Unix Domain (stream) socket. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv4. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM), + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + fun() -> traffic_ping_pong_large_sendmsg_and_recvmsg_cond() end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => inet, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +traffic_ping_pong_large_sendmsg_and_recvmsg_cond() -> + traffic_ping_pong_large_sendmsg_and_recvmsg_cond(os:type(), os:version()). + +traffic_ping_pong_large_sendmsg_and_recvmsg_cond({unix, linux}, {M, _, _}) + when (M < 3) -> + skip("TC may not work on this version"); +traffic_ping_pong_large_sendmsg_and_recvmsg_cond(_, _) -> + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv6. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM), + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, + fun() -> + has_support_ipv6(), + traffic_ping_pong_large_sendmsg_and_recvmsg_cond() + end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => inet6, + proto => tcp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for Unix Domain (stream) socket. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_NUM(Config, ?TPP_LARGE_NUM), + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp4, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => inet, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => inet6, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for Unix Domain (dgram) socket. + +traffic_ping_pong_small_sendmsg_and_recvmsg_udpL(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udpL(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_NUM(Config, ?TPP_SMALL_NUM), + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => inet, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6, + fun() -> has_support_ipv6() end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => inet6, + proto => udp, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for Unix Domain (dgram) socket. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL(Config) when is_list(Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_NUM(Config, ?TPP_MEDIUM_NUM), + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + ?TT(?SECS(60)), + InitState = #{domain => local, + proto => default, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Ping-Pong for TCP + +traffic_ping_pong_send_and_recv_tcp(InitState) -> + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_tcp(#{domain := local} = InitState) -> + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + %% On some platforms, the address + %% is *not* provided (e.g. FreeBSD) + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + %% On some platforms, the address + %% *is* provided (e.g. linux) + {ok, #{addr := #{family := local}, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{recv => Recv}, % Receive function + traffic_ping_pong_sendmsg_and_recvmsg_tcp2(InitState2); +traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) -> + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{recv => Recv}, % Receive function + traffic_ping_pong_sendmsg_and_recvmsg_tcp2(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_tcp2(InitState) -> + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState2 = InitState#{send => Send}, % Send function + traffic_ping_pong_send_and_receive_tcp(InitState2). + + +traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> + Fun = fun(Sock) -> + {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + ?SEV_IPRINT("RcvBuf is ~p (needs atleast ~p)", + [RcvSz, 16+size(Msg)]), + if (RcvSz < size(Msg)) -> + NewRcvSz = 1024+size(Msg), + case socket:setopt(Sock, socket, rcvbuf, NewRcvSz) of + ok -> + ok; + {error, enobufs} -> + skip(?F("Change ~w buffer size (to ~w) " + "not allowed", + [rcvbuf, NewRcvSz])); + {error, Reason1} -> + ?FAIL({rcvbuf, Reason1}) + end; + true -> + ok + end, + {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + ?SEV_IPRINT("SndBuf is ~p (needs atleast ~p)", + [SndSz, 16+size(Msg)]), + if (SndSz < size(Msg)) -> + NewSndSz = 1024+size(Msg), + case socket:setopt(Sock, socket, sndbuf, NewSndSz) of + ok -> + ok; + {error, enobufs} -> + skip(?F("Change ~w buffer size (to ~w) " + "not allowed", + [sndbuf, NewSndSz])); + {error, Reason2} -> + ?FAIL({sndbuf, Reason2}) + end; + true -> + ok + end, + ok = socket:setopt(Sock, otp, rcvbuf, {12, 1024}) + end, + traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}). + +traffic_ping_pong_send_and_receive_tcp2(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain, proto := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _Port} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, local_sa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to port: ~w", [Port]), + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "maybe init buffers", + cmd => fun(#{lsock := LSock, buf_init := BufInit} = _State) -> + BufInit(LSock) + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, local_sa := LSA}) -> + ?SEV_ANNOUNCE_READY(Tester, init, LSA), + ok; + (#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create handler", + cmd => fun(State) -> + Handler = tpp_tcp_handler_create(), + ?SEV_IPRINT("handler created: ~p", [Handler]), + {ok, State#{handler => Handler}} + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Handler} = _State) -> + _MRef = erlang:monitor(process, Handler), + ok + end}, + #{desc => "transfer connection socket ownership to handler", + cmd => fun(#{handler := Handler, csock := Sock} = _State) -> + socket:setopt(Sock, otp, controlling_process, Handler) + end}, + #{desc => "start handler", + cmd => fun(#{handler := Handler, + csock := Sock, + send := Send, + recv := Recv} = _State) -> + ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}), + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv, + [{handler, Handler}]) + end}, + #{desc => "order handler to recv", + cmd => fun(#{handler := Handler} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, recv), + ok + end}, + #{desc => "await handler ready (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("Result: ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, recv, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + #{desc => "close listen socket", + cmd => fun(#{domain := local, + lsock := Sock, + local_sa := #{path := Path}} = State) -> + (catch socket:close(Sock)), + State1 = + unlink_path(Path, + fun() -> + maps:remove(local_sa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create remote client", + cmd => fun(#{node := Node} = State) -> + Pid = tpp_tcp_client_create(Node), + ?SEV_IPRINT("remote client created: ~p", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := RClient, + proto := Proto, + server_sa := ServerSA, + buf_init := BufInit, + send := Send, + recv := Recv}) -> + ?SEV_ANNOUNCE_START(RClient, + {ServerSA, Proto, BufInit, + Send, Recv}), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + case ?SEV_AWAIT_READY(RClient, rclient, init, + [{tester, Tester}]) of + ok -> + ?SEV_IPRINT("remote client started"), + ok; + {error, {unexpected_exit, _, {bind, eaddrnotavail = Reason}}} -> + ?SEV_IPRINT("remote client bind failure:" + "~n ~p", [Reason]), + {skip, Reason}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("remote client failure:" + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, RClient}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := RClient}) -> + ?SEV_ANNOUNCE_CONTINUE(RClient, connect), + ok + end}, + #{desc => "await remote client ready (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_READY(RClient, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, + send, + [{rclient, RClient}]) + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := RClient, + msg := Msg, + num := Num} = State) -> + Data = {Msg, Num}, + ?SEV_ANNOUNCE_CONTINUE(RClient, send, Data), + {ok, maps:remove(data, State)} + end}, + #{desc => "await remote client ready (send)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester, result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, RClient}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop remote client", + cmd => fun(#{rclient := RClient}) -> + ?SEV_ANNOUNCE_TERMINATE(RClient), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := RClient} = State) -> + ?SEV_AWAIT_TERMINATION(RClient), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) + end}, + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + {ok, {_, _, _, _, _} = Result} -> + ?SEV_IPRINT("client result: " + "~n ~p", [Result]), + {ok, State#{client_result => Result}}; + {ok, BadResult} -> + ?SEV_EPRINT("client result: " + "~n ~p", [BadResult]), + {error, {invalid_client_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + client := Client, + num := Num} = State) -> + case ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]) of + {ok, {Num, _, _, _, _} = Result} -> + ?SEV_IPRINT("server result: " + "~n ~p", [Result]), + Result2 = erlang:delete_element(1, Result), + {ok, State#{server_result => Result2}}; + {ok, BadResult} -> + ?SEV_EPRINT("bad server result: " + "~n ~p", [BadResult]), + {error, {invalid_server_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "present result", + cmd => fun(#{server_result := SRes, + client_result := CRes, + num := Num} = State) -> + {SSent, SReceived, SStart, SStop} = SRes, + {CSent, CReceived, _, CStart, CStop} = CRes, + STime = tdiff(SStart, SStop), + CTime = tdiff(CStart, CStop), + %% Note that the sizes we are counting is only + %% the "data" part of the messages. There is also + %% fixed header for each message, which of cource + %% is small for the large messages, but comparatively + %% big for the small messages! + ?SEV_IPRINT("Results: ~w messages exchanged" + "~n Server: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received" + "~n Client: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received", + [Num, + STime, + STime / Num, + Num / STime, + SSent div STime, + SReceived div STime, + CTime, + CTime / Num, + Num / CTime, + CSent div CTime, + CReceived div CTime]), + State1 = maps:remove(server_result, State), + State2 = maps:remove(client_result, State1), + {ok, State2} + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + proto => maps:get(proto, InitState), + recv => maps:get(recv, InitState), + send => maps:get(send, InitState), + buf_init => maps:get(buf_init, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + num => maps:get(num, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +tpp_tcp_handler_create() -> + Self = self(), + erlang:spawn(fun() -> tpp_tcp_handler(Self) end). + +tpp_tcp_handler(Parent) -> + tpp_tcp_handler_init(Parent), + {Sock, Send, Recv} = tpp_tcp_handler_await_start(Parent), + tpp_tcp_handler_announce_ready(Parent, init), + tpp_tcp_handler_await_continue(Parent, recv), + Result = tpp_tcp_handler_msg_exchange(Sock, Send, Recv), + tpp_tcp_handler_announce_ready(Parent, recv, Result), + Reason = tpp_tcp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_tcp_handler_init(Parent) -> + put(sname, "handler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_tcp_handler_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_tcp_handler_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_tcp_handler_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_tcp_handler_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + %% ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_tcp_handler_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +tpp_tcp_handler_msg_exchange(Sock, Send, Recv) -> + tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). + +tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + case tpp_tcp_recv_req(Sock, Recv) of + {ok, Msg, RecvSz} -> + NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); + true -> Start end, + %% ?SEV_IPRINT("[~w] received - now try send", [N]), + case tpp_tcp_send_rep(Sock, Send, Msg) of + {ok, SendSz} -> + tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, + N+1, + Sent+SendSz, + Received+RecvSz, + NewStart); + {error, SReason} -> + ?SEV_EPRINT("send (~w): ~p", [N, SReason]), + exit({send, SReason, N}) + end; + {error, closed} -> + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]), + Stop = ?LIB:timestamp(), + {N, Sent, Received, Start, Stop}; + {error, RReason} -> + ?SEV_EPRINT("recv (~w): ~p", [N, RReason]), + exit({recv, RReason, N}) + end. + +%% The (remote) client process + +tpp_tcp_client_create(Node) -> + Self = self(), + Fun = fun() -> tpp_tcp_client(Self) end, + erlang:spawn(Node, Fun). + +tpp_tcp_client(Parent) -> + tpp_tcp_client_init(Parent), + {ServerSA, Proto, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = tpp_tcp_client_sock_open(Domain, Proto, BufInit), + Path = tpp_tcp_client_sock_bind(Sock, Domain), + tpp_tcp_client_announce_ready(Parent, init), + tpp_tcp_client_await_continue(Parent, connect), + tpp_tcp_client_sock_connect(Sock, ServerSA), + tpp_tcp_client_announce_ready(Parent, connect), + {InitMsg, Num} = tpp_tcp_client_await_continue(Parent, send), + Result = tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num), + tpp_tcp_client_announce_ready(Parent, send, Result), + Reason = tpp_tcp_client_await_terminate(Parent), + tpp_tcp_client_sock_close(Sock, Path), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_tcp_client_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_tcp_client_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p): ~p", [Slogan, Extra]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_tcp_client_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {ok, Data} -> + ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + Data; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_tcp_client_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ?SEV_IPRINT("termination received: normal"), + normal; + {error, Reason} -> + ?SEV_IPRINT("termination received: ~w", [Reason]), + Reason + end. + +tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num) -> + Start = ?LIB:timestamp(), + tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, InitMsg, + Num, 0, 0, 0, Start). + +tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg, + Num, Num, Sent, Received, + Start) -> + Stop = ?LIB:timestamp(), + Info = socket:info(Sock), + case socket:close(Sock) of + ok -> + {Sent, Received, Info, Start, Stop}; + {error, Reason} -> + exit({failed_closing, Reason}) + end; +tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Data, + Num, N, Sent, Received, Start) -> + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) try send ~w", [Num,N,size(Data)]), + case tpp_tcp_send_req(Sock, Send, Data) of + {ok, SendSz} -> + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - " + %% "now try recv", [Num,N]), + case tpp_tcp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz} -> + tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p: " + "~n ~p", [N, Num, RReason, mq()]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p" + "~n ~p", [N, Num, SReason, mq()]), + exit({send, SReason, N}) + end. + +tpp_tcp_client_sock_open(Domain, Proto, BufInit) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + ok = BufInit(Sock), + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +tpp_tcp_client_sock_bind(Sock, Domain) -> + LSA = which_local_socket_addr(Domain), + case socket:bind(Sock, LSA) of + {ok, _} -> + case socket:sockname(Sock) of + {ok, #{family := local, path := Path}} -> + Path; + {ok, _} -> + undefined; + {error, Reason1} -> + exit({sockname, Reason1}) + end; + {error, Reason2} -> + exit({bind, Reason2}) + end. + +tpp_tcp_client_sock_connect(Sock, ServerSA) -> + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +tpp_tcp_client_sock_close(Sock, Path) -> + case socket:close(Sock) of + ok -> + unlink_path(Path), + ok; + {error, closed} -> + ok; + {error, Reason} -> + ?SEV_EPRINT("failed closing: " + "~n Reason: ~p", [Reason]), + unlink_path(Path), + {error, {close, Reason}} + end. + + + +-define(TPP_REQUEST, 1). +-define(TPP_REPLY, 2). + +tpp_tcp_recv_req(Sock, Recv) -> + tpp_tcp_recv(Sock, Recv, ?TPP_REQUEST). + +tpp_tcp_recv_rep(Sock, Recv) -> + tpp_tcp_recv(Sock, Recv, ?TPP_REPLY). + +tpp_tcp_recv(Sock, Recv, Tag) -> + case Recv(Sock, 0) of + {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg} + when (Sz =:= size(Data)) -> + %% We got it all + {ok, Data, size(Msg)}; + {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg} -> + Remains = Sz - size(Data), + tpp_tcp_recv(Sock, Recv, Tag, Remains, size(Msg), [Data]); + {ok, <<Tag:32/integer, _/binary>>} -> + {error, {invalid_msg_tag, Tag}}; + {error, _R} = ERROR -> + ERROR + end. + +tpp_tcp_recv(Sock, Recv, Tag, Remaining, AccSz, Acc) -> + case Recv(Sock, Remaining) of + {ok, Data} when (Remaining =:= size(Data)) -> + %% We got the rest + TotSz = AccSz + size(Data), + {ok, erlang:iolist_to_binary(lists:reverse([Data | Acc])), TotSz}; + {ok, Data} when (Remaining > size(Data)) -> + tpp_tcp_recv(Sock, Recv, Tag, + Remaining - size(Data), AccSz + size(Data), + [Data | Acc]); + {error, _R} = ERROR -> + ERROR + end. + + +tpp_tcp_send_req(Sock, Send, Data) -> + tpp_tcp_send(Sock, Send, ?TPP_REQUEST, Data). + +tpp_tcp_send_rep(Sock, Send, Data) -> + tpp_tcp_send(Sock, Send, ?TPP_REPLY, Data). + +tpp_tcp_send(Sock, Send, Tag, Data) -> + DataSz = size(Data), + Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>, + tpp_tcp_send_msg(Sock, Send, Msg, 0). + +tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) -> + case Send(Sock, Msg) of + ok -> + {ok, AccSz+size(Msg)}; + {ok, Rest} -> % This is an IOVec + RestBin = list_to_binary(Rest), + tpp_tcp_send_msg(Sock, Send, RestBin, AccSz+(size(Msg)-size(RestBin))); + {error, _} = ERROR -> + ERROR + end. + + +%% size_of_data(Data) when is_binary(Data) -> +%% size(Data); +%% size_of_data(Data) when is_list(Data) -> +%% size_of_iovec(Data, 0). + +%% size_of_iovec([], Sz) -> +%% Sz; +%% size_of_iovec([B|IOVec], Sz) -> +%% size_of_iovec(IOVec, Sz+size(B)). + +mq() -> + mq(self()). + +mq(Pid) when is_pid(Pid) -> + Tag = messages, + {Tag, Msgs} = process_info(Pid, Tag), + Msgs. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Ping-Pong for UDP + +traffic_ping_pong_sendto_and_recvfrom_udp(InitState) -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock, Sz) -> + socket:recvfrom(Sock, Sz) + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_udp(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) -> + Send = fun(Sock, Data, Dest) when is_binary(Data) -> + MsgHdr = #{addr => Dest, iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{addr => Dest, iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_udp(InitState2). + + +traffic_ping_pong_send_and_receive_udp(#{msg := Msg} = InitState) -> + Fun = fun(Sock) -> + {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + if (RcvSz =< (8+size(Msg))) -> + i("adjust socket rcvbuf buffer size"), + ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + if (SndSz =< (8+size(Msg))) -> + i("adjust socket sndbuf buffer size"), + ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, OtpRcvBuf} = socket:getopt(Sock, otp, rcvbuf), + if + (OtpRcvBuf =< (8+size(Msg))) -> + i("adjust otp rcvbuf buffer size"), + ok = socket:setopt(Sock, otp, rcvbuf, 1024+size(Msg)); + true -> + ok + end + end, + traffic_ping_pong_send_and_receive_udp2(InitState#{buf_init => Fun}). + +traffic_ping_pong_send_and_receive_udp2(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, proto := Proto} = State) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + sock := Sock, local_sa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, local_sa := LSA} = State) -> + case sock_bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "maybe init buffers", + cmd => fun(#{sock := Sock, buf_init := BufInit} = _State) -> + BufInit(Sock) + end}, + #{desc => "create handler", + cmd => fun(State) -> + Handler = tpp_udp_server_handler_create(), + ?SEV_IPRINT("handler created: ~p", [Handler]), + {ok, State#{handler => Handler}} + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Handler} = _State) -> + _MRef = erlang:monitor(process, Handler), + ok + end}, + #{desc => "start handler", + cmd => fun(#{handler := Handler, + sock := Sock, + send := Send, + recv := Recv} = _State) -> + ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}), + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, local_sa := LSA}) -> + ?SEV_ANNOUNCE_READY(Tester, init, LSA), + ok; + (#{tester := Tester, local_sa := LSA, port := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv, + [{handler, Handler}]) + end}, + #{desc => "order handler to recv", + cmd => fun(#{handler := Handler, + sock := _Sock} = _State) -> + %% socket:setopt(Sock, otp, debug, true), + ?SEV_ANNOUNCE_CONTINUE(Handler, recv), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{handler, Handler}]) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + %% socket:setopt(Sock, otp, debug, true), + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "(maybe) unlink socket", + cmd => fun(#{domain := local, + local_sa := #{path := Path}} = State) -> + unlink_path(Path, + fun() -> + {ok, maps:remove(local_sa, State)} + end, + fun() -> + ok + end); + (_) -> + ok + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + #{desc => "await handler ready (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("Result: ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, recv, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create (remote) handler", + cmd => fun(#{node := Node} = State) -> + Pid = tpp_udp_client_handler_create(Node), + ?SEV_IPRINT("handler created: ~p", [Pid]), + {ok, State#{handler => Pid}} + end}, + #{desc => "monitor remote handler", + cmd => fun(#{handler := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote handler to start", + cmd => fun(#{handler := Handler, + server_sa := ServerSA, + proto := Proto, + buf_init := BufInit, + send := Send, + recv := Recv}) -> + ?SEV_ANNOUNCE_START(Handler, + {ServerSA, Proto, BufInit, + Send, Recv}), + ok + end}, + #{desc => "await (remote) handler ready", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, + send, + [{handler, Handler}]) + end}, + #{desc => "order handler to continue (send)", + cmd => fun(#{handler := Handler, + msg := Msg, + num := Num} = State) -> + Data = {Msg, Num}, + ?SEV_ANNOUNCE_CONTINUE(Handler, send, Data), + {ok, maps:remove(data, State)} + end}, + #{desc => "await remote handler ready (send)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, send, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("remote client result: " + %% "~n ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester, result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{handler, Handler}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop (remote) handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await (remote) handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + {ok, {_, _, _, _} = Result} -> + ?SEV_IPRINT("client result: " + "~n ~p", [Result]), + {ok, State#{client_result => Result}}; + {ok, BadResult} -> + ?SEV_EPRINT("client result: " + "~n ~p", [BadResult]), + {error, {invalid_client_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server continue (close)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await server ready (close)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, close) + end}, + %% Because of the way we control the server, there is no real + %% point in collecting statistics from it (the time will include + %% our communication with it). + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]) of + {ok, _Result} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "present result", + cmd => fun(#{client_result := CRes, + num := Num} = State) -> + {CSent, CReceived, CStart, CStop} = CRes, + CTime = tdiff(CStart, CStop), + %% Note that the sizes we are counting is only + %% the "data" part of the messages. There is also + %% fixed header for each message, which of cource + %% is small for the large messages, but comparatively + %% big for the small messages! + ?SEV_IPRINT("Results: ~w messages exchanged" + "~n Client: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received", + [Num, + CTime, + CTime / Num, + Num / CTime, + CSent div CTime, + CReceived div CTime]), + State1 = maps:remove(client_result, State), + {ok, State1} + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + proto => maps:get(proto, InitState), + recv => maps:get(recv, InitState), + send => maps:get(send, InitState), + buf_init => maps:get(buf_init, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + num => maps:get(num, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%% Server side handler process +%% We don't actually need a separate process for this socket, +%% but we do it anyway to simplify the sequence. +tpp_udp_server_handler_create() -> + Self = self(), + erlang:spawn(fun() -> tpp_udp_server_handler(Self) end). + +tpp_udp_server_handler(Parent) -> + tpp_udp_server_handler_init(Parent), + {Sock, Send, Recv} = tpp_udp_handler_await_start(Parent), + tpp_udp_handler_announce_ready(Parent, init), + tpp_udp_handler_await_continue(Parent, recv), + Result = tpp_udp_server_handler_msg_exchange(Sock, Send, Recv), + tpp_udp_handler_announce_ready(Parent, recv, Result), + Reason = tpp_udp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_udp_server_handler_init(Parent) -> + put(sname, "shandler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_udp_server_handler_msg_exchange(Sock, Send, Recv) -> + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + 0, 0, 0, undefined). + +tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + N, Sent, Received, Start) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + %% if + %% (N =:= (?TPP_SMALL_NUM-2)) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + %% socket:setopt(Sock, otp, debug, true); + %% true -> ok + %% end, + try tpp_udp_recv_req(Sock, Recv) of + {ok, Msg, RecvSz, From} -> + NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); + true -> Start end, + %% ?SEV_IPRINT("[~w] received - now try send", [N]), + try tpp_udp_send_rep(Sock, Send, Msg, From) of + {ok, SendSz} -> + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + N+1, + Sent+SendSz, + Received+RecvSz, + NewStart); + {error, SReason} -> + ?SEV_EPRINT("send (~w): ~p", [N, SReason]), + exit({send, SReason, N}) + catch + SC:SE:SS -> + exit({send, {SC, SE, SS}, N}) + end; + {error, closed} -> + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", + [N, Sent, Received]), + Stop = ?LIB:timestamp(), + {N, Sent, Received, Start, Stop}; + {error, RReason} -> + ?SEV_EPRINT("recv (~w): ~p", [N, RReason]), + exit({recv, RReason, N}) + catch + RC:RE:RS -> + exit({recv, {RC, RE, RS}, N}) + end. + + +%% The (remote) client side handler process + +tpp_udp_client_handler_create(Node) -> + Self = self(), + Fun = fun() -> put(sname, "chandler"), tpp_udp_client_handler(Self) end, + erlang:spawn(Node, Fun). + +tpp_udp_client_handler(Parent) -> + tpp_udp_client_handler_init(Parent), + ?SEV_IPRINT("await start command"), + {ServerSA, Proto, BufInit, Send, Recv} = tpp_udp_handler_await_start(Parent), + ?SEV_IPRINT("start command with" + "~n ServerSA: ~p", [ServerSA]), + Domain = maps:get(family, ServerSA), + Sock = tpp_udp_sock_open(Domain, Proto, BufInit), + Path = tpp_udp_sock_bind(Sock, Domain), + ?SEV_IPRINT("announce ready", []), + tpp_udp_handler_announce_ready(Parent, init), + {InitMsg, Num} = tpp_udp_handler_await_continue(Parent, send), + ?SEV_IPRINT("received continue with" + "~n Num: ~p", [Num]), + Result = tpp_udp_client_handler_msg_exchange(Sock, ServerSA, + Send, Recv, InitMsg, Num), + ?SEV_IPRINT("ready"), + tpp_udp_handler_announce_ready(Parent, send, Result), + ?SEV_IPRINT("await terminate"), + Reason = tpp_udp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminate with ~p", [Reason]), + tpp_udp_sock_close(Sock, Path), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_udp_client_handler_init(Parent) -> + put(sname, "chandler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_udp_client_handler_msg_exchange(Sock, ServerSA, + Send, Recv, InitMsg, Num) -> + Start = ?LIB:timestamp(), + tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA, + Send, Recv, InitMsg, + Num, 0, 0, 0, Start). + +tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg, + Num, Num, Sent, Received, + Start) -> + Stop = ?LIB:timestamp(), + {Sent, Received, Start, Stop}; +tpp_udp_client_handler_msg_exchange_loop(Sock, + #{family := local} = Dest, + Send, Recv, Data, + Num, N, Sent, Received, Start) -> + case tpp_udp_send_req(Sock, Send, Data, Dest) of + {ok, SendSz} -> + case tpp_udp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz, Dest} -> + tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, + Send, Recv, + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]), + exit({send, SReason, N}) + end; +tpp_udp_client_handler_msg_exchange_loop(Sock, + #{addr := Addr, port := Port} = Dest0, + Send, Recv, Data, + Num, N, Sent, Received, Start) -> + case tpp_udp_send_req(Sock, Send, Data, Dest0) of + {ok, SendSz} -> + case tpp_udp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz, #{addr := Addr, port := Port} = Dest1} -> + tpp_udp_client_handler_msg_exchange_loop(Sock, Dest1, + Send, Recv, + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]), + exit({send, SReason, N}) + end. + + +tpp_udp_recv_req(Sock, Recv) -> + tpp_udp_recv(Sock, Recv, ?TPP_REQUEST). + +tpp_udp_recv_rep(Sock, Recv) -> + tpp_udp_recv(Sock, Recv, ?TPP_REPLY). + +tpp_udp_recv(Sock, Recv, Tag) -> + %% ok = socket:setopt(Sock, otp, debug, true), + try Recv(Sock, 0) of + {ok, {Source, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg}} + when (Sz =:= size(Data)) -> + %% ok = socket:setopt(Sock, otp, debug, false), + %% We got it all + %% ?SEV_IPRINT("tpp_udp_recv -> got all: " + %% "~n Source: ~p" + %% "~n Tag: ~p" + %% "~n Sz: ~p" + %% "~n size(Data): ~p", + %% [Source, Tag, Sz, size(Data)]), + {ok, Data, size(Msg), Source}; + {ok, {_Source, <<Tag:32/integer, Sz:32/integer, Data/binary>>}} -> + %% ok = socket:setopt(Sock, otp, debug, false), + {error, {invalid_msg, Sz, size(Data)}}; + {ok, {_, <<Tag:32/integer, _/binary>>}} -> + %% ok = socket:setopt(Sock, otp, debug, false), + {error, {invalid_msg_tag, Tag}}; + {error, _} = ERROR -> + %% ok = socket:setopt(Sock, otp, debug, false), + ERROR + catch + C:E:S -> + {error, {catched, C, E, S}} + end. + +tpp_udp_send_req(Sock, Send, Data, Dest) -> + tpp_udp_send(Sock, Send, ?TPP_REQUEST, Data, Dest). + +tpp_udp_send_rep(Sock, Send, Data, Dest) -> + tpp_udp_send(Sock, Send, ?TPP_REPLY, Data, Dest). + +tpp_udp_send(Sock, Send, Tag, Data, Dest) -> + DataSz = size(Data), + Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>, + tpp_udp_send_msg(Sock, Send, Msg, Dest, 0). + +tpp_udp_send_msg(Sock, Send, Msg, Dest, AccSz) when is_binary(Msg) -> + case Send(Sock, Msg, Dest) of + ok -> + {ok, AccSz+size(Msg)}; + {ok, Rest} -> % This is an IOVec + RestBin = list_to_binary(Rest), + tpp_udp_send_msg(Sock, Send, RestBin, Dest, + AccSz+(size(Msg)-size(RestBin))); + {error, _} = ERROR -> + ERROR + end. + + +tpp_udp_handler_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_udp_handler_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_udp_handler_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_udp_handler_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {ok, Data} -> + ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + Data; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_udp_handler_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +tpp_udp_sock_open(Domain, Proto, BufInit) -> + case socket:open(Domain, dgram, Proto) of + {ok, Sock} -> + ok = BufInit(Sock), + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +tpp_udp_sock_bind(Sock, Domain) -> + LSA = which_local_socket_addr(Domain), + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +tpp_udp_sock_close(Sock, Path) -> + case socket:close(Sock) of + ok -> + unlink_path(Path), + ok; + {error, Reason} -> + ?SEV_EPRINT("Failed closing socket: " + "~n ~p", [Reason]), + unlink_path(Path), + {error, {close, Reason}} + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% TIME TEST %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgenf_small_tcp4(suite) -> + []; +ttest_sgenf_cgenf_small_tcp4(doc) -> + []; +ttest_sgenf_cgenf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgenf_small_tcp4, + Runtime, + inet, + gen, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_small_tcp6(suite) -> + []; +ttest_sgenf_cgenf_small_tcp6(doc) -> + []; +ttest_sgenf_cgenf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgenf_small_tcp6, + Runtime, + inet6, + gen, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgenf_medium_tcp4(suite) -> + []; +ttest_sgenf_cgenf_medium_tcp4(doc) -> + []; +ttest_sgenf_cgenf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgenf_medium_tcp4, + Runtime, + inet, + gen, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_medium_tcp6(suite) -> + []; +ttest_sgenf_cgenf_medium_tcp6(doc) -> + []; +ttest_sgenf_cgenf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgenf_medium_tcp6, + Runtime, + inet6, + gen, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgenf_large_tcp4(suite) -> + []; +ttest_sgenf_cgenf_large_tcp4(doc) -> + []; +ttest_sgenf_cgenf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgenf_large_tcp4, + Runtime, + inet, + gen, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_large_tcp6(suite) -> + []; +ttest_sgenf_cgenf_large_tcp6(doc) -> + []; +ttest_sgenf_cgenf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgenf_large_tcp6, + Runtime, + inet6, + gen, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgeno_small_tcp4(suite) -> + []; +ttest_sgenf_cgeno_small_tcp4(doc) -> + []; +ttest_sgenf_cgeno_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_small_tcp4, + Runtime, + inet, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_small_tcp6(suite) -> + []; +ttest_sgenf_cgeno_small_tcp6(doc) -> + []; +ttest_sgenf_cgeno_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_small_tcp6, + Runtime, + inet6, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgeno_medium_tcp4(suite) -> + []; +ttest_sgenf_cgeno_medium_tcp4(doc) -> + []; +ttest_sgenf_cgeno_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_medium_tcp4, + Runtime, + inet, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_medium_tcp6(suite) -> + []; +ttest_sgenf_cgeno_medium_tcp6(doc) -> + []; +ttest_sgenf_cgeno_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_medium_tcp6, + Runtime, + inet6, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgeno_large_tcp4(suite) -> + []; +ttest_sgenf_cgeno_large_tcp4(doc) -> + []; +ttest_sgenf_cgeno_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_large_tcp4, + Runtime, + inet, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_large_tcp6(suite) -> + []; +ttest_sgenf_cgeno_large_tcp6(doc) -> + []; +ttest_sgenf_cgeno_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_large_tcp6, + Runtime, + inet6, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgent_small_tcp4(suite) -> + []; +ttest_sgenf_cgent_small_tcp4(doc) -> + []; +ttest_sgenf_cgent_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgent_small_tcp4, + Runtime, + inet, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_small_tcp6(suite) -> + []; +ttest_sgenf_cgent_small_tcp6(doc) -> + []; +ttest_sgenf_cgent_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgeno_small_tcp6, + Runtime, + inet6, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgent_medium_tcp4(suite) -> + []; +ttest_sgenf_cgent_medium_tcp4(doc) -> + []; +ttest_sgenf_cgent_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgent_medium_tcp4, + Runtime, + inet, + gen, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_medium_tcp6(suite) -> + []; +ttest_sgenf_cgent_medium_tcp6(doc) -> + []; +ttest_sgenf_cgent_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgent_medium_tcp6, + Runtime, + inet6, + gen, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgent_large_tcp4(suite) -> + []; +ttest_sgenf_cgent_large_tcp4(doc) -> + []; +ttest_sgenf_cgent_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgent_large_tcp4, + Runtime, + inet, + gen, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_large_tcp6(suite) -> + []; +ttest_sgenf_cgent_large_tcp6(doc) -> + []; +ttest_sgenf_cgent_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_cgent_large_tcp6, + Runtime, + inet6, + gen, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csockf_small_tcp4(suite) -> + []; +ttest_sgenf_csockf_small_tcp4(doc) -> + []; +ttest_sgenf_csockf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockf_small_tcp4, + Runtime, + inet, + gen, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_small_tcp6(suite) -> + []; +ttest_sgenf_csockf_small_tcp6(doc) -> + []; +ttest_sgenf_csockf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockf_small_tcp6, + Runtime, + inet6, + gen, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csockf_medium_tcp4(suite) -> + []; +ttest_sgenf_csockf_medium_tcp4(doc) -> + []; +ttest_sgenf_csockf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockf_medium_tcp4, + Runtime, + inet, + gen, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_medium_tcp6(suite) -> + []; +ttest_sgenf_csockf_medium_tcp6(doc) -> + []; +ttest_sgenf_csockf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockf_medium_tcp6, + Runtime, + inet6, + gen, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csockf_large_tcp4(suite) -> + []; +ttest_sgenf_csockf_large_tcp4(doc) -> + []; +ttest_sgenf_csockf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockf_large_tcp4, + Runtime, + inet, + gen, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_large_tcp6(suite) -> + []; +ttest_sgenf_csockf_large_tcp6(doc) -> + []; +ttest_sgenf_csockf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockf_large_tcp6, + Runtime, + inet6, + gen, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csocko_small_tcp4(suite) -> + []; +ttest_sgenf_csocko_small_tcp4(doc) -> + []; +ttest_sgenf_csocko_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_small_tcp4, + Runtime, + inet, + gen, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_small_tcp6(suite) -> + []; +ttest_sgenf_csocko_small_tcp6(doc) -> + []; +ttest_sgenf_csocko_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_small_tcp6, + Runtime, + inet6, + gen, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csocko_medium_tcp4(suite) -> + []; +ttest_sgenf_csocko_medium_tcp4(doc) -> + []; +ttest_sgenf_csocko_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_medium_tcp4, + Runtime, + inet, + gen, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_medium_tcp6(suite) -> + []; +ttest_sgenf_csocko_medium_tcp6(doc) -> + []; +ttest_sgenf_csocko_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_medium_tcp6, + Runtime, + inet6, + gen, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csocko_large_tcp4(suite) -> + []; +ttest_sgenf_csocko_large_tcp4(doc) -> + []; +ttest_sgenf_csocko_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_large_tcp4, + Runtime, + inet, + gen, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_large_tcp6(suite) -> + []; +ttest_sgenf_csocko_large_tcp6(doc) -> + []; +ttest_sgenf_csocko_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_large_tcp6, + Runtime, + inet6, + gen, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csockt_small_tcp4(suite) -> + []; +ttest_sgenf_csockt_small_tcp4(doc) -> + []; +ttest_sgenf_csockt_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockt_small_tcp4, + Runtime, + inet, + gen, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_small_tcp6(suite) -> + []; +ttest_sgenf_csockt_small_tcp6(doc) -> + []; +ttest_sgenf_csockt_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csocko_small_tcp6, + Runtime, + inet6, + gen, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csockt_medium_tcp4(suite) -> + []; +ttest_sgenf_csockt_medium_tcp4(doc) -> + []; +ttest_sgenf_csockt_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockt_medium_tcp4, + Runtime, + inet, + gen, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_medium_tcp6(suite) -> + []; +ttest_sgenf_csockt_medium_tcp6(doc) -> + []; +ttest_sgenf_csockt_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockt_medium_tcp6, + Runtime, + inet6, + gen, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csockt_large_tcp4(suite) -> + []; +ttest_sgenf_csockt_large_tcp4(doc) -> + []; +ttest_sgenf_csockt_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockt_large_tcp4, + Runtime, + inet, + gen, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_large_tcp6(suite) -> + []; +ttest_sgenf_csockt_large_tcp6(doc) -> + []; +ttest_sgenf_csockt_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgenf_csockt_large_tcp6, + Runtime, + inet6, + gen, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgenf_small_tcp4(suite) -> + []; +ttest_sgeno_cgenf_small_tcp4(doc) -> + []; +ttest_sgeno_cgenf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgenf_small_tcp4, + Runtime, + inet, + gen, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_small_tcp6(suite) -> + []; +ttest_sgeno_cgenf_small_tcp6(doc) -> + []; +ttest_sgeno_cgenf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgenf_small_tcp6, + Runtime, + inet6, + gen, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgenf_medium_tcp4(suite) -> + []; +ttest_sgeno_cgenf_medium_tcp4(doc) -> + []; +ttest_sgeno_cgenf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgenf_medium_tcp4, + Runtime, + inet, + gen, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_medium_tcp6(suite) -> + []; +ttest_sgeno_cgenf_medium_tcp6(doc) -> + []; +ttest_sgeno_cgenf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgenf_medium_tcp6, + Runtime, + inet6, + gen, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgenf_large_tcp4(suite) -> + []; +ttest_sgeno_cgenf_large_tcp4(doc) -> + []; +ttest_sgeno_cgenf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgenf_large_tcp4, + Runtime, + inet, + gen, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_large_tcp6(suite) -> + []; +ttest_sgeno_cgenf_large_tcp6(doc) -> + []; +ttest_sgeno_cgenf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgenf_large_tcp6, + Runtime, + inet6, + gen, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgeno_small_tcp4(suite) -> + []; +ttest_sgeno_cgeno_small_tcp4(doc) -> + []; +ttest_sgeno_cgeno_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_small_tcp4, + Runtime, + inet, + gen, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_small_tcp6(suite) -> + []; +ttest_sgeno_cgeno_small_tcp6(doc) -> + []; +ttest_sgeno_cgeno_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_small_tcp6, + Runtime, + inet6, + gen, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgeno_medium_tcp4(suite) -> + []; +ttest_sgeno_cgeno_medium_tcp4(doc) -> + []; +ttest_sgeno_cgeno_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_medium_tcp4, + Runtime, + inet, + gen, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_medium_tcp6(suite) -> + []; +ttest_sgeno_cgeno_medium_tcp6(doc) -> + []; +ttest_sgeno_cgeno_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_medium_tcp6, + Runtime, + inet6, + gen, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgeno_large_tcp4(suite) -> + []; +ttest_sgeno_cgeno_large_tcp4(doc) -> + []; +ttest_sgeno_cgeno_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_large_tcp4, + Runtime, + inet, + gen, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_large_tcp6(suite) -> + []; +ttest_sgeno_cgeno_large_tcp6(doc) -> + []; +ttest_sgeno_cgeno_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_large_tcp6, + Runtime, + inet6, + gen, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgent_small_tcp4(suite) -> + []; +ttest_sgeno_cgent_small_tcp4(doc) -> + []; +ttest_sgeno_cgent_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgent_small_tcp4, + Runtime, + inet, + gen, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_small_tcp6(suite) -> + []; +ttest_sgeno_cgent_small_tcp6(doc) -> + []; +ttest_sgeno_cgent_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgeno_small_tcp6, + Runtime, + inet6, + gen, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgent_medium_tcp4(suite) -> + []; +ttest_sgeno_cgent_medium_tcp4(doc) -> + []; +ttest_sgeno_cgent_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgent_medium_tcp4, + Runtime, + inet, + gen, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_medium_tcp6(suite) -> + []; +ttest_sgeno_cgent_medium_tcp6(doc) -> + []; +ttest_sgeno_cgent_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgent_medium_tcp6, + Runtime, + inet6, + gen, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgent_large_tcp4(suite) -> + []; +ttest_sgeno_cgent_large_tcp4(doc) -> + []; +ttest_sgeno_cgent_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgent_large_tcp4, + Runtime, + inet, + gen, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_large_tcp6(suite) -> + []; +ttest_sgeno_cgent_large_tcp6(doc) -> + []; +ttest_sgeno_cgent_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_cgent_large_tcp6, + Runtime, + inet6, + gen, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csockf_small_tcp4(suite) -> + []; +ttest_sgeno_csockf_small_tcp4(doc) -> + []; +ttest_sgeno_csockf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockf_small_tcp4, + Runtime, + inet, + gen, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_small_tcp6(suite) -> + []; +ttest_sgeno_csockf_small_tcp6(doc) -> + []; +ttest_sgeno_csockf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockf_small_tcp6, + Runtime, + inet6, + gen, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csockf_medium_tcp4(suite) -> + []; +ttest_sgeno_csockf_medium_tcp4(doc) -> + []; +ttest_sgeno_csockf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockf_medium_tcp4, + Runtime, + inet, + gen, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_medium_tcp6(suite) -> + []; +ttest_sgeno_csockf_medium_tcp6(doc) -> + []; +ttest_sgeno_csockf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockf_medium_tcp6, + Runtime, + inet6, + gen, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csockf_large_tcp4(suite) -> + []; +ttest_sgeno_csockf_large_tcp4(doc) -> + []; +ttest_sgeno_csockf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockf_large_tcp4, + Runtime, + inet, + gen, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_large_tcp6(suite) -> + []; +ttest_sgeno_csockf_large_tcp6(doc) -> + []; +ttest_sgeno_csockf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockf_large_tcp6, + Runtime, + inet6, + gen, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csocko_small_tcp4(suite) -> + []; +ttest_sgeno_csocko_small_tcp4(doc) -> + []; +ttest_sgeno_csocko_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_small_tcp4, + Runtime, + inet, + gen, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_small_tcp6(suite) -> + []; +ttest_sgeno_csocko_small_tcp6(doc) -> + []; +ttest_sgeno_csocko_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_small_tcp6, + Runtime, + inet6, + gen, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csocko_medium_tcp4(suite) -> + []; +ttest_sgeno_csocko_medium_tcp4(doc) -> + []; +ttest_sgeno_csocko_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_medium_tcp4, + Runtime, + inet, + gen, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_medium_tcp6(suite) -> + []; +ttest_sgeno_csocko_medium_tcp6(doc) -> + []; +ttest_sgeno_csocko_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_medium_tcp6, + Runtime, + inet6, + gen, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csocko_large_tcp4(suite) -> + []; +ttest_sgeno_csocko_large_tcp4(doc) -> + []; +ttest_sgeno_csocko_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_large_tcp4, + Runtime, + inet, + gen, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_large_tcp6(suite) -> + []; +ttest_sgeno_csocko_large_tcp6(doc) -> + []; +ttest_sgeno_csocko_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_large_tcp6, + Runtime, + inet6, + gen, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csockt_small_tcp4(suite) -> + []; +ttest_sgeno_csockt_small_tcp4(doc) -> + []; +ttest_sgeno_csockt_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockt_small_tcp4, + Runtime, + inet, + gen, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_small_tcp6(suite) -> + []; +ttest_sgeno_csockt_small_tcp6(doc) -> + []; +ttest_sgeno_csockt_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csocko_small_tcp6, + Runtime, + inet6, + gen, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csockt_medium_tcp4(suite) -> + []; +ttest_sgeno_csockt_medium_tcp4(doc) -> + []; +ttest_sgeno_csockt_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockt_medium_tcp4, + Runtime, + inet, + gen, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_medium_tcp6(suite) -> + []; +ttest_sgeno_csockt_medium_tcp6(doc) -> + []; +ttest_sgeno_csockt_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockt_medium_tcp6, + Runtime, + inet6, + gen, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csockt_large_tcp4(suite) -> + []; +ttest_sgeno_csockt_large_tcp4(doc) -> + []; +ttest_sgeno_csockt_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockt_large_tcp4, + Runtime, + inet, + gen, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_large_tcp6(suite) -> + []; +ttest_sgeno_csockt_large_tcp6(doc) -> + []; +ttest_sgeno_csockt_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgeno_csockt_large_tcp6, + Runtime, + inet6, + gen, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgenf_small_tcp4(suite) -> + []; +ttest_sgent_cgenf_small_tcp4(doc) -> + []; +ttest_sgent_cgenf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgenf_small_tcp4, + Runtime, + inet, + gen, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_small_tcp6(suite) -> + []; +ttest_sgent_cgenf_small_tcp6(doc) -> + []; +ttest_sgent_cgenf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgenf_small_tcp6, + Runtime, + inet6, + gen, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgenf_medium_tcp4(suite) -> + []; +ttest_sgent_cgenf_medium_tcp4(doc) -> + []; +ttest_sgent_cgenf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgenf_medium_tcp4, + Runtime, + inet, + gen, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_medium_tcp6(suite) -> + []; +ttest_sgent_cgenf_medium_tcp6(doc) -> + []; +ttest_sgent_cgenf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgenf_medium_tcp6, + Runtime, + inet6, + gen, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgenf_large_tcp4(suite) -> + []; +ttest_sgent_cgenf_large_tcp4(doc) -> + []; +ttest_sgent_cgenf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgenf_large_tcp4, + Runtime, + inet, + gen, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_large_tcp6(suite) -> + []; +ttest_sgent_cgenf_large_tcp6(doc) -> + []; +ttest_sgent_cgenf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgenf_large_tcp6, + Runtime, + inet6, + gen, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgeno_small_tcp4(suite) -> + []; +ttest_sgent_cgeno_small_tcp4(doc) -> + []; +ttest_sgent_cgeno_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_small_tcp4, + Runtime, + inet, + gen, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_small_tcp6(suite) -> + []; +ttest_sgent_cgeno_small_tcp6(doc) -> + []; +ttest_sgent_cgeno_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_small_tcp6, + Runtime, + inet6, + gen, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgeno_medium_tcp4(suite) -> + []; +ttest_sgent_cgeno_medium_tcp4(doc) -> + []; +ttest_sgent_cgeno_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_medium_tcp4, + Runtime, + inet, + gen, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_medium_tcp6(suite) -> + []; +ttest_sgent_cgeno_medium_tcp6(doc) -> + []; +ttest_sgent_cgeno_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_medium_tcp6, + Runtime, + inet6, + gen, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgeno_large_tcp4(suite) -> + []; +ttest_sgent_cgeno_large_tcp4(doc) -> + []; +ttest_sgent_cgeno_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_large_tcp4, + Runtime, + inet, + gen, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_large_tcp6(suite) -> + []; +ttest_sgent_cgeno_large_tcp6(doc) -> + []; +ttest_sgent_cgeno_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_large_tcp6, + Runtime, + inet6, + gen, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgent_small_tcp4(suite) -> + []; +ttest_sgent_cgent_small_tcp4(doc) -> + []; +ttest_sgent_cgent_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgent_small_tcp4, + Runtime, + inet, + gen, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgent_small_tcp6(suite) -> + []; +ttest_sgent_cgent_small_tcp6(doc) -> + []; +ttest_sgent_cgent_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgeno_small_tcp6, + Runtime, + inet6, + gen, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgent_medium_tcp4(suite) -> + []; +ttest_sgent_cgent_medium_tcp4(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet, msg=medium"]; +ttest_sgent_cgent_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgent_medium_tcp4, + Runtime, + inet, + gen, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgent_medium_tcp6(suite) -> + []; +ttest_sgent_cgent_medium_tcp6(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet6, msg=medium"]; +ttest_sgent_cgent_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgent_medium_tcp6, + Runtime, + inet6, + gen, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgent_large_tcp4(suite) -> + []; +ttest_sgent_cgent_large_tcp4(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet, msg=large"]; +ttest_sgent_cgent_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgent_large_tcp4, + Runtime, + inet, + gen, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgent_large_tcp6(suite) -> + []; +ttest_sgent_cgent_large_tcp6(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet6, msg=large"]; +ttest_sgent_cgent_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_cgent_large_tcp6, + Runtime, + inet6, + gen, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csockf_small_tcp4(suite) -> + []; +ttest_sgent_csockf_small_tcp4(doc) -> + []; +ttest_sgent_csockf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockf_small_tcp4, + Runtime, + inet, + gen, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csockf_small_tcp6(suite) -> + []; +ttest_sgent_csockf_small_tcp6(doc) -> + []; +ttest_sgent_csockf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockf_small_tcp6, + Runtime, + inet6, + gen, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csockf_medium_tcp4(suite) -> + []; +ttest_sgent_csockf_medium_tcp4(doc) -> + []; +ttest_sgent_csockf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockf_medium_tcp4, + Runtime, + inet, + gen, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csockf_medium_tcp6(suite) -> + []; +ttest_sgent_csockf_medium_tcp6(doc) -> + []; +ttest_sgent_csockf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockf_medium_tcp6, + Runtime, + inet6, + gen, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csockf_large_tcp4(suite) -> + []; +ttest_sgent_csockf_large_tcp4(doc) -> + []; +ttest_sgent_csockf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockf_large_tcp4, + Runtime, + inet, + gen, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csockf_large_tcp6(suite) -> + []; +ttest_sgent_csockf_large_tcp6(doc) -> + []; +ttest_sgent_csockf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockf_large_tcp6, + Runtime, + inet6, + gen, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csocko_small_tcp4(suite) -> + []; +ttest_sgent_csocko_small_tcp4(doc) -> + []; +ttest_sgent_csocko_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_small_tcp4, + Runtime, + inet, + gen, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csocko_small_tcp6(suite) -> + []; +ttest_sgent_csocko_small_tcp6(doc) -> + []; +ttest_sgent_csocko_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_small_tcp6, + Runtime, + inet6, + gen, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csocko_medium_tcp4(suite) -> + []; +ttest_sgent_csocko_medium_tcp4(doc) -> + []; +ttest_sgent_csocko_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_medium_tcp4, + Runtime, + inet, + gen, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csocko_medium_tcp6(suite) -> + []; +ttest_sgent_csocko_medium_tcp6(doc) -> + []; +ttest_sgent_csocko_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_medium_tcp6, + Runtime, + inet6, + gen, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csocko_large_tcp4(suite) -> + []; +ttest_sgent_csocko_large_tcp4(doc) -> + []; +ttest_sgent_csocko_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_large_tcp4, + Runtime, + inet, + gen, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csocko_large_tcp6(suite) -> + []; +ttest_sgent_csocko_large_tcp6(doc) -> + []; +ttest_sgent_csocko_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_large_tcp6, + Runtime, + inet6, + gen, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csockt_small_tcp4(suite) -> + []; +ttest_sgent_csockt_small_tcp4(doc) -> + []; +ttest_sgent_csockt_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockt_small_tcp4, + Runtime, + inet, + gen, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csockt_small_tcp6(suite) -> + []; +ttest_sgent_csockt_small_tcp6(doc) -> + []; +ttest_sgent_csockt_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csocko_small_tcp6, + Runtime, + inet6, + gen, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csockt_medium_tcp4(suite) -> + []; +ttest_sgent_csockt_medium_tcp4(doc) -> + []; +ttest_sgent_csockt_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockt_medium_tcp4, + Runtime, + inet, + gen, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csockt_medium_tcp6(suite) -> + []; +ttest_sgent_csockt_medium_tcp6(doc) -> + []; +ttest_sgent_csockt_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockt_medium_tcp6, + Runtime, + inet6, + gen, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csockt_large_tcp4(suite) -> + []; +ttest_sgent_csockt_large_tcp4(doc) -> + []; +ttest_sgent_csockt_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockt_large_tcp4, + Runtime, + inet, + gen, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csockt_large_tcp6(suite) -> + []; +ttest_sgent_csockt_large_tcp6(doc) -> + []; +ttest_sgent_csockt_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_sgent_csockt_large_tcp6, + Runtime, + inet6, + gen, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgenf_small_tcp4(suite) -> + []; +ttest_ssockf_cgenf_small_tcp4(doc) -> + []; +ttest_ssockf_cgenf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgenf_small_tcp4, + Runtime, + inet, + sock, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_small_tcp6(suite) -> + []; +ttest_ssockf_cgenf_small_tcp6(doc) -> + []; +ttest_ssockf_cgenf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgenf_small_tcp6, + Runtime, + inet6, + sock, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgenf_medium_tcp4(suite) -> + []; +ttest_ssockf_cgenf_medium_tcp4(doc) -> + []; +ttest_ssockf_cgenf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgenf_medium_tcp4, + Runtime, + inet, + sock, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_medium_tcp6(suite) -> + []; +ttest_ssockf_cgenf_medium_tcp6(doc) -> + []; +ttest_ssockf_cgenf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgenf_medium_tcp6, + Runtime, + inet6, + sock, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgenf_large_tcp4(suite) -> + []; +ttest_ssockf_cgenf_large_tcp4(doc) -> + []; +ttest_ssockf_cgenf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgenf_large_tcp4, + Runtime, + inet, + sock, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_large_tcp6(suite) -> + []; +ttest_ssockf_cgenf_large_tcp6(doc) -> + []; +ttest_ssockf_cgenf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgenf_large_tcp6, + Runtime, + inet6, + sock, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgeno_small_tcp4(suite) -> + []; +ttest_ssockf_cgeno_small_tcp4(doc) -> + []; +ttest_ssockf_cgeno_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_small_tcp4, + Runtime, + inet, + sock, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_small_tcp6(suite) -> + []; +ttest_ssockf_cgeno_small_tcp6(doc) -> + []; +ttest_ssockf_cgeno_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_small_tcp6, + Runtime, + inet6, + sock, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgeno_medium_tcp4(suite) -> + []; +ttest_ssockf_cgeno_medium_tcp4(doc) -> + []; +ttest_ssockf_cgeno_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_medium_tcp4, + Runtime, + inet, + sock, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_medium_tcp6(suite) -> + []; +ttest_ssockf_cgeno_medium_tcp6(doc) -> + []; +ttest_ssockf_cgeno_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_medium_tcp6, + Runtime, + inet6, + sock, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgeno_large_tcp4(suite) -> + []; +ttest_ssockf_cgeno_large_tcp4(doc) -> + []; +ttest_ssockf_cgeno_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_large_tcp4, + Runtime, + inet, + sock, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_large_tcp6(suite) -> + []; +ttest_ssockf_cgeno_large_tcp6(doc) -> + []; +ttest_ssockf_cgeno_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_large_tcp6, + Runtime, + inet6, + sock, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgent_small_tcp4(suite) -> + []; +ttest_ssockf_cgent_small_tcp4(doc) -> + []; +ttest_ssockf_cgent_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgent_small_tcp4, + Runtime, + inet, + sock, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_small_tcp6(suite) -> + []; +ttest_ssockf_cgent_small_tcp6(doc) -> + []; +ttest_ssockf_cgent_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgeno_small_tcp6, + Runtime, + inet6, + sock, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgent_medium_tcp4(suite) -> + []; +ttest_ssockf_cgent_medium_tcp4(doc) -> + []; +ttest_ssockf_cgent_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgent_medium_tcp4, + Runtime, + inet, + sock, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_medium_tcp6(suite) -> + []; +ttest_ssockf_cgent_medium_tcp6(doc) -> + []; +ttest_ssockf_cgent_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgent_medium_tcp6, + Runtime, + inet6, + sock, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgent_large_tcp4(suite) -> + []; +ttest_ssockf_cgent_large_tcp4(doc) -> + []; +ttest_ssockf_cgent_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgent_large_tcp4, + Runtime, + inet, + sock, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_large_tcp6(suite) -> + []; +ttest_ssockf_cgent_large_tcp6(doc) -> + []; +ttest_ssockf_cgent_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_cgent_large_tcp6, + Runtime, + inet6, + sock, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csockf_small_tcp4(suite) -> + []; +ttest_ssockf_csockf_small_tcp4(doc) -> + []; +ttest_ssockf_csockf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_small_tcp4, + Runtime, + inet, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_small_tcp6(suite) -> + []; +ttest_ssockf_csockf_small_tcp6(doc) -> + []; +ttest_ssockf_csockf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_small_tcp6, + Runtime, + inet6, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssockf_csockf_small_tcpL(suite) -> + []; +ttest_ssockf_csockf_small_tcpL(doc) -> + []; +ttest_ssockf_csockf_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_small_tcpL, + Runtime, + local, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csockf_medium_tcp4(suite) -> + []; +ttest_ssockf_csockf_medium_tcp4(doc) -> + []; +ttest_ssockf_csockf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_medium_tcp4, + Runtime, + inet, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_medium_tcp6(suite) -> + []; +ttest_ssockf_csockf_medium_tcp6(doc) -> + []; +ttest_ssockf_csockf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_medium_tcp6, + Runtime, + inet6, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssockf_csockf_medium_tcpL(suite) -> + []; +ttest_ssockf_csockf_medium_tcpL(doc) -> + []; +ttest_ssockf_csockf_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_medium_tcpL, + Runtime, + local, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csockf_large_tcp4(suite) -> + []; +ttest_ssockf_csockf_large_tcp4(doc) -> + []; +ttest_ssockf_csockf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_large_tcp4, + Runtime, + inet, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_large_tcp6(suite) -> + []; +ttest_ssockf_csockf_large_tcp6(doc) -> + []; +ttest_ssockf_csockf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_large_tcp6, + Runtime, + inet6, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssockf_csockf_large_tcpL(suite) -> + []; +ttest_ssockf_csockf_large_tcpL(doc) -> + []; +ttest_ssockf_csockf_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockf_large_tcpL, + Runtime, + local, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csocko_small_tcp4(suite) -> + []; +ttest_ssockf_csocko_small_tcp4(doc) -> + []; +ttest_ssockf_csocko_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_small_tcp4, + Runtime, + inet, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_small_tcp6(suite) -> + []; +ttest_ssockf_csocko_small_tcp6(doc) -> + []; +ttest_ssockf_csocko_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_small_tcp6, + Runtime, + inet6, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssockf_csocko_small_tcpL(suite) -> + []; +ttest_ssockf_csocko_small_tcpL(doc) -> + []; +ttest_ssockf_csocko_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_small_tcpL, + Runtime, + local, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csocko_medium_tcp4(suite) -> + []; +ttest_ssockf_csocko_medium_tcp4(doc) -> + []; +ttest_ssockf_csocko_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_medium_tcp4, + Runtime, + inet, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_medium_tcp6(suite) -> + []; +ttest_ssockf_csocko_medium_tcp6(doc) -> + []; +ttest_ssockf_csocko_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_medium_tcp6, + Runtime, + inet6, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssockf_csocko_medium_tcpL(suite) -> + []; +ttest_ssockf_csocko_medium_tcpL(doc) -> + []; +ttest_ssockf_csocko_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_medium_tcpL, + Runtime, + local, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csocko_large_tcp4(suite) -> + []; +ttest_ssockf_csocko_large_tcp4(doc) -> + []; +ttest_ssockf_csocko_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_large_tcp4, + Runtime, + inet, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_large_tcp6(suite) -> + []; +ttest_ssockf_csocko_large_tcp6(doc) -> + []; +ttest_ssockf_csocko_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_large_tcp6, + Runtime, + inet6, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssockf_csocko_large_tcpL(suite) -> + []; +ttest_ssockf_csocko_large_tcpL(doc) -> + []; +ttest_ssockf_csocko_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_large_tcpL, + Runtime, + local, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csockt_small_tcp4(suite) -> + []; +ttest_ssockf_csockt_small_tcp4(doc) -> + []; +ttest_ssockf_csockt_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_small_tcp4, + Runtime, + inet, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_small_tcp6(suite) -> + []; +ttest_ssockf_csockt_small_tcp6(doc) -> + []; +ttest_ssockf_csockt_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_small_tcp6, + Runtime, + inet6, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssockf_csockt_small_tcpL(suite) -> + []; +ttest_ssockf_csockt_small_tcpL(doc) -> + []; +ttest_ssockf_csockt_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csocko_small_tcpL, + Runtime, + local, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csockt_medium_tcp4(suite) -> + []; +ttest_ssockf_csockt_medium_tcp4(doc) -> + []; +ttest_ssockf_csockt_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_medium_tcp4, + Runtime, + inet, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_medium_tcp6(suite) -> + []; +ttest_ssockf_csockt_medium_tcp6(doc) -> + []; +ttest_ssockf_csockt_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_medium_tcp6, + Runtime, + inet6, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssockf_csockt_medium_tcpL(suite) -> + []; +ttest_ssockf_csockt_medium_tcpL(doc) -> + []; +ttest_ssockf_csockt_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_medium_tcpL, + Runtime, + local, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csockt_large_tcp4(suite) -> + []; +ttest_ssockf_csockt_large_tcp4(doc) -> + []; +ttest_ssockf_csockt_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_large_tcp4, + Runtime, + inet, + sock, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_large_tcp6(suite) -> + []; +ttest_ssockf_csockt_large_tcp6(doc) -> + []; +ttest_ssockf_csockt_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_large_tcp6, + Runtime, + inet6, + sock, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssockf_csockt_large_tcpL(suite) -> + []; +ttest_ssockf_csockt_large_tcpL(doc) -> + []; +ttest_ssockf_csockt_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockf_csockt_large_tcpL, + Runtime, + local, + sock, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgenf_small_tcp4(suite) -> + []; +ttest_ssocko_cgenf_small_tcp4(doc) -> + []; +ttest_ssocko_cgenf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgenf_small_tcp4, + Runtime, + inet, + sock, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_small_tcp6(suite) -> + []; +ttest_ssocko_cgenf_small_tcp6(doc) -> + []; +ttest_ssocko_cgenf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgenf_small_tcp6, + Runtime, + inet6, + sock, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgenf_medium_tcp4(suite) -> + []; +ttest_ssocko_cgenf_medium_tcp4(doc) -> + []; +ttest_ssocko_cgenf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgenf_medium_tcp4, + Runtime, + inet, + sock, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_medium_tcp6(suite) -> + []; +ttest_ssocko_cgenf_medium_tcp6(doc) -> + []; +ttest_ssocko_cgenf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgenf_medium_tcp6, + Runtime, + inet6, + sock, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgenf_large_tcp4(suite) -> + []; +ttest_ssocko_cgenf_large_tcp4(doc) -> + []; +ttest_ssocko_cgenf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgenf_large_tcp4, + Runtime, + inet, + sock, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_large_tcp6(suite) -> + []; +ttest_ssocko_cgenf_large_tcp6(doc) -> + []; +ttest_ssocko_cgenf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgenf_large_tcp6, + Runtime, + inet6, + sock, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgeno_small_tcp4(suite) -> + []; +ttest_ssocko_cgeno_small_tcp4(doc) -> + []; +ttest_ssocko_cgeno_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgeno_small_tcp4, + Runtime, + inet, + sock, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_small_tcp6(suite) -> + []; +ttest_ssocko_cgeno_small_tcp6(doc) -> + []; +ttest_ssocko_cgeno_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgeno_small_tcp6, + Runtime, + inet6, + sock, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgeno_medium_tcp4(suite) -> + []; +ttest_ssocko_cgeno_medium_tcp4(doc) -> + []; +ttest_ssocko_cgeno_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgeno_medium_tcp4, + Runtime, + inet, + sock, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_medium_tcp6(suite) -> + []; +ttest_ssocko_cgeno_medium_tcp6(doc) -> + []; +ttest_ssocko_cgeno_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgeno_medium_tcp6, + Runtime, + inet6, + sock, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgeno_large_tcp4(suite) -> + []; +ttest_ssocko_cgeno_large_tcp4(doc) -> + []; +ttest_ssocko_cgeno_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgeno_large_tcp4, + Runtime, + inet, + sock, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_large_tcp6(suite) -> + []; +ttest_ssocko_cgeno_large_tcp6(doc) -> + []; +ttest_ssocko_cgeno_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgeno_large_tcp6, + Runtime, + inet6, + sock, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgent_small_tcp4(suite) -> + []; +ttest_ssocko_cgent_small_tcp4(doc) -> + []; +ttest_ssocko_cgent_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgent_small_tcp4, + Runtime, + inet, + sock, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_small_tcp6(suite) -> + []; +ttest_ssocko_cgent_small_tcp6(doc) -> + []; +ttest_ssocko_cgent_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgent_small_tcp6, + Runtime, + inet6, + sock, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgent_medium_tcp4(suite) -> + []; +ttest_ssocko_cgent_medium_tcp4(doc) -> + []; +ttest_ssocko_cgent_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgent_medium_tcp4, + Runtime, + inet, + sock, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_medium_tcp6(suite) -> + []; +ttest_ssocko_cgent_medium_tcp6(doc) -> + []; +ttest_ssocko_cgent_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgent_medium_tcp6, + Runtime, + inet6, + sock, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgent_large_tcp4(suite) -> + []; +ttest_ssocko_cgent_large_tcp4(doc) -> + []; +ttest_ssocko_cgent_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgent_large_tcp4, + Runtime, + inet, + sock, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_large_tcp6(suite) -> + []; +ttest_ssocko_cgent_large_tcp6(doc) -> + []; +ttest_ssocko_cgent_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_cgent_large_tcp6, + Runtime, + inet6, + sock, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csockf_small_tcp4(suite) -> + []; +ttest_ssocko_csockf_small_tcp4(doc) -> + []; +ttest_ssocko_csockf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_small_tcp4, + Runtime, + inet, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_small_tcp6(suite) -> + []; +ttest_ssocko_csockf_small_tcp6(doc) -> + []; +ttest_ssocko_csockf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_small_tcp6, + Runtime, + inet6, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssocko_csockf_small_tcpL(suite) -> + []; +ttest_ssocko_csockf_small_tcpL(doc) -> + []; +ttest_ssocko_csockf_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_small_tcpL, + Runtime, + local, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csockf_medium_tcp4(suite) -> + []; +ttest_ssocko_csockf_medium_tcp4(doc) -> + []; +ttest_ssocko_csockf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_medium_tcp4, + Runtime, + inet, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_medium_tcp6(suite) -> + []; +ttest_ssocko_csockf_medium_tcp6(doc) -> + []; +ttest_ssocko_csockf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_medium_tcp6, + Runtime, + inet6, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssocko_csockf_medium_tcpL(suite) -> + []; +ttest_ssocko_csockf_medium_tcpL(doc) -> + []; +ttest_ssocko_csockf_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_medium_tcpL, + Runtime, + local, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csockf_large_tcp4(suite) -> + []; +ttest_ssocko_csockf_large_tcp4(doc) -> + []; +ttest_ssocko_csockf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_large_tcp4, + Runtime, + inet, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_large_tcp6(suite) -> + []; +ttest_ssocko_csockf_large_tcp6(doc) -> + []; +ttest_ssocko_csockf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_large_tcp6, + Runtime, + inet6, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssocko_csockf_large_tcpL(suite) -> + []; +ttest_ssocko_csockf_large_tcpL(doc) -> + []; +ttest_ssocko_csockf_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockf_large_tcpL, + Runtime, + local, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csocko_small_tcp4(suite) -> + []; +ttest_ssocko_csocko_small_tcp4(doc) -> + []; +ttest_ssocko_csocko_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_small_tcp4, + Runtime, + inet, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_small_tcp6(suite) -> + []; +ttest_ssocko_csocko_small_tcp6(doc) -> + []; +ttest_ssocko_csocko_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_small_tcp6, + Runtime, + inet6, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssocko_csocko_small_tcpL(suite) -> + []; +ttest_ssocko_csocko_small_tcpL(doc) -> + []; +ttest_ssocko_csocko_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_small_tcpL, + Runtime, + local, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csocko_medium_tcp4(suite) -> + []; +ttest_ssocko_csocko_medium_tcp4(doc) -> + []; +ttest_ssocko_csocko_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_medium_tcp4, + Runtime, + inet, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_medium_tcp6(suite) -> + []; +ttest_ssocko_csocko_medium_tcp6(doc) -> + []; +ttest_ssocko_csocko_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_medium_tcp6, + Runtime, + inet6, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssocko_csocko_medium_tcpL(suite) -> + []; +ttest_ssocko_csocko_medium_tcpL(doc) -> + []; +ttest_ssocko_csocko_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_medium_tcpL, + Runtime, + local, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csocko_large_tcp4(suite) -> + []; +ttest_ssocko_csocko_large_tcp4(doc) -> + []; +ttest_ssocko_csocko_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_large_tcp4, + Runtime, + inet, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_large_tcp6(suite) -> + []; +ttest_ssocko_csocko_large_tcp6(doc) -> + []; +ttest_ssocko_csocko_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_large_tcp6, + Runtime, + inet6, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssocko_csocko_large_tcpL(suite) -> + []; +ttest_ssocko_csocko_large_tcpL(doc) -> + []; +ttest_ssocko_csocko_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_large_tcpL, + Runtime, + local, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csockt_small_tcp4(suite) -> + []; +ttest_ssocko_csockt_small_tcp4(doc) -> + []; +ttest_ssocko_csockt_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_small_tcp4, + Runtime, + inet, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_small_tcp6(suite) -> + []; +ttest_ssocko_csockt_small_tcp6(doc) -> + []; +ttest_ssocko_csockt_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_small_tcp6, + Runtime, + inet6, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssocko_csockt_small_tcpL(suite) -> + []; +ttest_ssocko_csockt_small_tcpL(doc) -> + []; +ttest_ssocko_csockt_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csocko_small_tcpL, + Runtime, + local, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csockt_medium_tcp4(suite) -> + []; +ttest_ssocko_csockt_medium_tcp4(doc) -> + []; +ttest_ssocko_csockt_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_medium_tcp4, + Runtime, + inet, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_medium_tcp6(suite) -> + []; +ttest_ssocko_csockt_medium_tcp6(doc) -> + []; +ttest_ssocko_csockt_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_medium_tcp6, + Runtime, + inet6, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssocko_csockt_medium_tcpL(suite) -> + []; +ttest_ssocko_csockt_medium_tcpL(doc) -> + []; +ttest_ssocko_csockt_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_medium_tcpL, + Runtime, + local, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csockt_large_tcp4(suite) -> + []; +ttest_ssocko_csockt_large_tcp4(doc) -> + []; +ttest_ssocko_csockt_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_large_tcp4, + Runtime, + inet, + sock, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_large_tcp6(suite) -> + []; +ttest_ssocko_csockt_large_tcp6(doc) -> + []; +ttest_ssocko_csockt_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_large_tcp6, + Runtime, + inet6, + sock, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssocko_csockt_large_tcpL(suite) -> + []; +ttest_ssocko_csockt_large_tcpL(doc) -> + []; +ttest_ssocko_csockt_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssocko_csockt_large_tcpL, + Runtime, + local, + sock, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgenf_small_tcp4(suite) -> + []; +ttest_ssockt_cgenf_small_tcp4(doc) -> + []; +ttest_ssockt_cgenf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgenf_small_tcp4, + Runtime, + inet, + sock, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_small_tcp6(suite) -> + []; +ttest_ssockt_cgenf_small_tcp6(doc) -> + []; +ttest_ssockt_cgenf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgenf_small_tcp6, + Runtime, + inet6, + sock, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgenf_medium_tcp4(suite) -> + []; +ttest_ssockt_cgenf_medium_tcp4(doc) -> + []; +ttest_ssockt_cgenf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgenf_medium_tcp4, + Runtime, + inet, + sock, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_medium_tcp6(suite) -> + []; +ttest_ssockt_cgenf_medium_tcp6(doc) -> + []; +ttest_ssockt_cgenf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgenf_medium_tcp6, + Runtime, + inet6, + sock, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgenf_large_tcp4(suite) -> + []; +ttest_ssockt_cgenf_large_tcp4(doc) -> + []; +ttest_ssockt_cgenf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgenf_large_tcp4, + Runtime, + inet, + sock, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_large_tcp6(suite) -> + []; +ttest_ssockt_cgenf_large_tcp6(doc) -> + []; +ttest_ssockt_cgenf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgenf_large_tcp6, + Runtime, + inet6, + sock, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgeno_small_tcp4(suite) -> + []; +ttest_ssockt_cgeno_small_tcp4(doc) -> + []; +ttest_ssockt_cgeno_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgeno_small_tcp4, + Runtime, + inet, + sock, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_small_tcp6(suite) -> + []; +ttest_ssockt_cgeno_small_tcp6(doc) -> + []; +ttest_ssockt_cgeno_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgeno_small_tcp6, + Runtime, + inet6, + sock, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgeno_medium_tcp4(suite) -> + []; +ttest_ssockt_cgeno_medium_tcp4(doc) -> + []; +ttest_ssockt_cgeno_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgeno_medium_tcp4, + Runtime, + inet, + sock, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_medium_tcp6(suite) -> + []; +ttest_ssockt_cgeno_medium_tcp6(doc) -> + []; +ttest_ssockt_cgeno_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgeno_medium_tcp6, + Runtime, + inet6, + sock, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgeno_large_tcp4(suite) -> + []; +ttest_ssockt_cgeno_large_tcp4(doc) -> + []; +ttest_ssockt_cgeno_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgeno_large_tcp4, + Runtime, + inet, + sock, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_large_tcp6(suite) -> + []; +ttest_ssockt_cgeno_large_tcp6(doc) -> + []; +ttest_ssockt_cgeno_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgeno_large_tcp6, + Runtime, + inet6, + sock, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgent_small_tcp4(suite) -> + []; +ttest_ssockt_cgent_small_tcp4(doc) -> + []; +ttest_ssockt_cgent_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgent_small_tcp4, + Runtime, + inet, + sock, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_small_tcp6(suite) -> + []; +ttest_ssockt_cgent_small_tcp6(doc) -> + []; +ttest_ssockt_cgent_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgent_small_tcp6, + Runtime, + inet6, + sock, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgent_medium_tcp4(suite) -> + []; +ttest_ssockt_cgent_medium_tcp4(doc) -> + []; +ttest_ssockt_cgent_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgent_medium_tcp4, + Runtime, + inet, + sock, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_medium_tcp6(suite) -> + []; +ttest_ssockt_cgent_medium_tcp6(doc) -> + []; +ttest_ssockt_cgent_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgent_medium_tcp6, + Runtime, + inet6, + sock, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgent_large_tcp4(suite) -> + []; +ttest_ssockt_cgent_large_tcp4(doc) -> + []; +ttest_ssockt_cgent_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgent_large_tcp4, + Runtime, + inet, + sock, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_large_tcp6(suite) -> + []; +ttest_ssockt_cgent_large_tcp6(doc) -> + []; +ttest_ssockt_cgent_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_cgent_large_tcp6, + Runtime, + inet6, + sock, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csockf_small_tcp4(suite) -> + []; +ttest_ssockt_csockf_small_tcp4(doc) -> + []; +ttest_ssockt_csockf_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_small_tcp4, + Runtime, + inet, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_small_tcp6(suite) -> + []; +ttest_ssockt_csockf_small_tcp6(doc) -> + []; +ttest_ssockt_csockf_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_small_tcp6, + Runtime, + inet6, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssockt_csockf_small_tcpL(suite) -> + []; +ttest_ssockt_csockf_small_tcpL(doc) -> + []; +ttest_ssockt_csockf_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_small_tcpL, + Runtime, + local, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csockf_medium_tcp4(suite) -> + []; +ttest_ssockt_csockf_medium_tcp4(doc) -> + []; +ttest_ssockt_csockf_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_medium_tcp4, + Runtime, + inet, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_medium_tcp6(suite) -> + []; +ttest_ssockt_csockf_medium_tcp6(doc) -> + []; +ttest_ssockt_csockf_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_medium_tcp6, + Runtime, + inet6, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssockt_csockf_medium_tcpL(suite) -> + []; +ttest_ssockt_csockf_medium_tcpL(doc) -> + []; +ttest_ssockt_csockf_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_medium_tcpL, + Runtime, + local, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csockf_large_tcp4(suite) -> + []; +ttest_ssockt_csockf_large_tcp4(doc) -> + []; +ttest_ssockt_csockf_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_large_tcp4, + Runtime, + inet, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_large_tcp6(suite) -> + []; +ttest_ssockt_csockf_large_tcp6(doc) -> + []; +ttest_ssockt_csockf_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_large_tcp6, + Runtime, + inet6, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssockt_csockf_large_tcpL(suite) -> + []; +ttest_ssockt_csockf_large_tcpL(doc) -> + []; +ttest_ssockt_csockf_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockf_large_tcpL, + Runtime, + local, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csocko_small_tcp4(suite) -> + []; +ttest_ssockt_csocko_small_tcp4(doc) -> + []; +ttest_ssockt_csocko_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_small_tcp4, + Runtime, + inet, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_small_tcp6(suite) -> + []; +ttest_ssockt_csocko_small_tcp6(doc) -> + []; +ttest_ssockt_csocko_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_small_tcp6, + Runtime, + inet6, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssockt_csocko_small_tcpL(suite) -> + []; +ttest_ssockt_csocko_small_tcpL(doc) -> + []; +ttest_ssockt_csocko_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_small_tcpL, + Runtime, + local, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csocko_medium_tcp4(suite) -> + []; +ttest_ssockt_csocko_medium_tcp4(doc) -> + []; +ttest_ssockt_csocko_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_medium_tcp4, + Runtime, + inet, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_medium_tcp6(suite) -> + []; +ttest_ssockt_csocko_medium_tcp6(doc) -> + []; +ttest_ssockt_csocko_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_medium_tcp6, + Runtime, + inet6, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssockt_csocko_medium_tcpL(suite) -> + []; +ttest_ssockt_csocko_medium_tcpL(doc) -> + []; +ttest_ssockt_csocko_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_medium_tcpL, + Runtime, + local, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csocko_large_tcp4(suite) -> + []; +ttest_ssockt_csocko_large_tcp4(doc) -> + []; +ttest_ssockt_csocko_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_large_tcp4, + Runtime, + inet, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_large_tcp6(suite) -> + []; +ttest_ssockt_csocko_large_tcp6(doc) -> + []; +ttest_ssockt_csocko_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_large_tcp6, + Runtime, + inet6, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssockt_csocko_large_tcpL(suite) -> + []; +ttest_ssockt_csocko_large_tcpL(doc) -> + []; +ttest_ssockt_csocko_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_large_tcpL, + Runtime, + local, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csockt_small_tcp4(suite) -> + []; +ttest_ssockt_csockt_small_tcp4(doc) -> + []; +ttest_ssockt_csockt_small_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_small_tcp4, + Runtime, + inet, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_small_tcp6(suite) -> + []; +ttest_ssockt_csockt_small_tcp6(doc) -> + []; +ttest_ssockt_csockt_small_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_small_tcp6, + Runtime, + inet6, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: local +%% + +ttest_ssockt_csockt_small_tcpL(suite) -> + []; +ttest_ssockt_csockt_small_tcpL(doc) -> + []; +ttest_ssockt_csockt_small_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csocko_small_tcpL, + Runtime, + local, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csockt_medium_tcp4(suite) -> + []; +ttest_ssockt_csockt_medium_tcp4(doc) -> + []; +ttest_ssockt_csockt_medium_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_medium_tcp4, + Runtime, + inet, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_medium_tcp6(suite) -> + []; +ttest_ssockt_csockt_medium_tcp6(doc) -> + []; +ttest_ssockt_csockt_medium_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_medium_tcp6, + Runtime, + inet6, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: local +%% + +ttest_ssockt_csockt_medium_tcpL(suite) -> + []; +ttest_ssockt_csockt_medium_tcpL(doc) -> + []; +ttest_ssockt_csockt_medium_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_medium_tcpL, + Runtime, + local, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csockt_large_tcp4(suite) -> + []; +ttest_ssockt_csockt_large_tcp4(doc) -> + []; +ttest_ssockt_csockt_large_tcp4(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_large_tcp4, + Runtime, + inet, + sock, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_large_tcp6(suite) -> + []; +ttest_ssockt_csockt_large_tcp6(doc) -> + []; +ttest_ssockt_csockt_large_tcp6(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_large_tcp6, + Runtime, + inet6, + sock, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: local +%% + +ttest_ssockt_csockt_large_tcpL(suite) -> + []; +ttest_ssockt_csockt_large_tcpL(doc) -> + []; +ttest_ssockt_csockt_large_tcpL(Config) when is_list(Config) -> + Runtime = which_ttest_runtime(Config), + ttest_tcp(ttest_ssockt_csockt_large_tcpL, + Runtime, + local, + sock, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +which_ttest_runtime(Config) when is_list(Config) -> + case lists:keysearch(esock_test_ttest_runtime, 1, Config) of + {value, {esock_test_ttest_runtime, Runtime}} -> + Runtime; + false -> + which_ttest_runtime_env() + end. + +which_ttest_runtime_env() -> + which_ttest_runtime_env(os:getenv("ESOCK_TEST_TTEST_RUNTIME")). + +which_ttest_runtime_env(TStr) when is_list(TStr) -> + which_ttest_runtime_env2(lists:reverse(TStr)); +which_ttest_runtime_env(false) -> + ?TTEST_RUNTIME. + + +%% The format is: <int>[unit] +%% where the optional unit can be: +%% ms: milliseconds +%% s: seconds (default) +%% m: minutes +which_ttest_runtime_env2([$s, $m | MS]) when (length(MS) > 0) -> + convert_time(MS, fun(X) -> X end); +which_ttest_runtime_env2([$m | M]) when (length(M) > 0) -> + convert_time(M, fun(X) -> ?MINS(X) end); +which_ttest_runtime_env2([$s | S]) when (length(S) > 0) -> + convert_time(S, fun(X) -> ?SECS(X) end); +which_ttest_runtime_env2(S) -> + convert_time(S, fun(X) -> ?SECS(X) end). + +convert_time(TStrRev, Convert) -> + try list_to_integer(lists:reverse(TStrRev)) of + I -> Convert(I) + catch + _:_ -> + ?TTEST_RUNTIME + end. + +%% ttest_tcp(TC, +%% Domain, +%% ServerMod, ServerActive, +%% ClientMod, ClientActive, +%% MsgID, MaxOutstanding) -> +%% ttest_tcp(TC, +%% ?TTEST_RUNTIME, +%% Domain, +%% ServerMod, ServerActive, +%% ClientMod, ClientActive, +%% MsgID, MaxOutstanding). +ttest_tcp(TC, + Runtime, + Domain, + ServerMod, ServerActive, + ClientMod, ClientActive, + MsgID, MaxOutstanding) -> + tc_try(TC, + fun() -> + if + + (Domain =:= local) -> + %% On darwin we seem to hit the system limit(s) + %% much earlier. + %% The tests "mostly" work, but random cases fail + %% (even on reasonably powerfull machines), + %% so its much simpler to just skip on darwin... + has_support_unix_domain_socket(), + is_not_darwin(); + (Domain =:= inet6) -> + has_support_ipv6(); + true -> ok + end + end, + fun() -> + %% This may be overkill, depending on the runtime, + %% but better safe then sorry... + ?TT(Runtime + ?SECS(60)), + InitState = #{domain => Domain, + msg_id => MsgID, + max_outstanding => MaxOutstanding, + runtime => Runtime, + server_mod => ServerMod, + server_active => ServerActive, + client_mod => ClientMod, + client_active => ClientActive}, + ok = ttest_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ttest_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, server) of + {ok, Node} -> + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor server node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start ttest (remote) server", + cmd => fun(#{domain := local = Domain, + mod := Mod, + active := Active, + node := Node} = State) -> + case ttest_tcp_server_start(Node, + Domain, Mod, Active) of + {ok, {{Pid, _}, Path}} -> + {ok, State#{rserver => Pid, + path => Path}}; + {error, _} = ERROR -> + ERROR + end; + (#{domain := Domain, + mod := Mod, + active := Active, + node := Node} = State) -> + case ttest_tcp_server_start(Node, + Domain, Mod, Active) of + {ok, {{Pid, _}, {Addr, Port}}} -> + {ok, State#{rserver => Pid, + addr => Addr, + port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + path := Path}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Path), + ok; + (#{tester := Tester, + addr := Addr, + port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, {Addr, Port}), + ok + end}, + + + %% *** Termination *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rserver := RServer} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rserver, RServer}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + %% The remote server is in a accept, with a timeout of 5 seconds, + %% so may have to wait a bit... + #{desc => "order (remote) ttest server terminate", + cmd => fun(#{node := _Node, + rserver := RServer}) -> + ttest_tcp_server_stop(RServer), + ok + end}, + #{desc => "await ttest (remote) server termination", + cmd => fun(#{rserver := RServer} = State) -> + ?SEV_AWAIT_TERMINATION(RServer), + State1 = maps:remove(rserver, State), + {ok, State1} + end}, + #{desc => "stop (server) node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await (server) node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(#{domain := local} = State) -> + {Tester, ServerPath} = + ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_path => ServerPath}}; + (State) -> + {Tester, {ServerAddr, ServerPort}} = + ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_addr => ServerAddr, + server_port => ServerPort}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + {ok, State#{node => Node}}; + {error, Reason} -> + {skip, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (ttest)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, ttest), + ok + end}, + #{desc => "start ttest (remote) client", + cmd => fun(#{domain := local = Domain, + node := Node, + mod := Mod, + active := Active, + msg_id := MsgID, + max_outstanding := MaxOutstanding, + runtime := RunTime, + server_path := Path} = State) -> + Self = self(), + Notify = + fun(Result) -> + ?SEV_ANNOUNCE_READY(Self, ttest, Result) + end, + case ttest_tcp_client_start(Node, Notify, + Domain, Mod, + Path, + Active, + MsgID, MaxOutstanding, + RunTime) of + {ok, {Pid, _MRef}} -> + {ok, State#{rclient => Pid}}; + {error, _} = ERROR -> + ERROR + end; + (#{domain := Domain, + node := Node, + mod := Mod, + active := Active, + msg_id := MsgID, + max_outstanding := MaxOutstanding, + runtime := RunTime, + server_addr := Addr, + server_port := Port} = State) -> + Self = self(), + Notify = + fun(Result) -> + ?SEV_ANNOUNCE_READY(Self, ttest, Result) + end, + case ttest_tcp_client_start(Node, Notify, + Domain, Mod, + {Addr, Port}, + Active, + MsgID, MaxOutstanding, + RunTime) of + {ok, {Pid, _MRef}} -> + {ok, State#{rclient => Pid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ttest ready", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, ttest, + [{tester, Tester}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ttest (remote) client termination", + cmd => fun(#{rclient := RClient} = State) -> + ?SEV_AWAIT_TERMINATION(RClient), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "announce ready (ttest)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, ttest, Result), + {ok, maps:remove(result, State)} + end}, + + + %% *** Termination *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop (client) node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await (client) node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{domain := local, + server := Pid} = State) -> + {ok, Path} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_path => Path}}; + (#{server := Pid} = State) -> + {ok, {Addr, Port}} = + ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_addr => Addr, + server_port => Port}} + end}, + + + %% Start the client + #{desc => "order client start", + cmd => fun(#{domain := local, + client := Pid, + server_path := Path} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Path), + ok; + (#{client := Pid, + server_addr := Addr, + server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {Addr, Port}), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, init) + end}, + + %% The actual test + #{desc => "order client continue (ttest)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, ttest), + ok + end}, + #{desc => "await client ready (ttest)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, ttest, + [{server, Server}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_TERMINATION(Server), + ok + end}, + + + %% Present the results + #{desc => "present the results", + cmd => fun(#{result := Result, + domain := Domain, + server_mod := ServerTrans, + server_active := ServerActive, + client_mod := ClientTrans, + client_active := ClientActive, + msg_id := MsgID} = State) -> + case Result of + #{status := ok, + runtime := RunTime, + cnt := Cnt, + bcnt := BCnt} -> + ttest_report(Domain, + ServerTrans, ServerActive, + ClientTrans, ClientActive, + MsgID, + RunTime, BCnt, Cnt), + ?SEV_IPRINT( + "TTest results: " + "~n Run Time: ~s" + "~n Byte Count: ~s" + "~n Number of message exchanges: ~s" + "~n~n", + [ + ?TTEST_LIB:format_time(RunTime), + if ((BCnt =:= 0) orelse (RunTime =:= 0)) -> + ?TTEST_LIB:format("~w, ~w", + [BCnt, RunTime]); + true -> + ?TTEST_LIB:format("~p => ~p byte / ms", + [BCnt, BCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + ?TTEST_LIB:format("~p => ~p iterations / ms", + [Cnt, Cnt div RunTime]) + end + ]), + {ok, maps:remove(result, State)}; + + #{status := Failure, + runtime := RunTime, + sid := SID, + rid := RID, + scnt := SCnt, + rcnt := RCnt, + bcnt := BCnt, + num := Num} -> + ?SEV_EPRINT("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + ?TTEST_LIB:format_time(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]), + {error, Failure} + end + end}, + + %% This is just so that the printout above shall have time to come + %% out before then end of the test case. + ?SEV_SLEEP(?SECS(1)), + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + Domain = maps:get(domain, InitState), + LHost = local_host(), + LAddr = which_local_addr(Domain), + + i("start server evaluator"), + ServerInitState = #{host => LHost, + addr => LAddr, + domain => Domain, + mod => maps:get(server_mod, InitState), + active => maps:get(server_active, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = #{host => LHost, + addr => LAddr, + domain => Domain, + mod => maps:get(client_mod, InitState), + active => maps:get(client_active, InitState), + msg_id => maps:get(msg_id, InitState), + max_outstanding => maps:get(max_outstanding, InitState), + runtime => maps:get(runtime, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{domain => Domain, + msg_id => maps:get(msg_id, InitState), + client => Client#ev.pid, + client_mod => maps:get(client_mod, InitState), + client_active => maps:get(client_active, InitState), + server => Server#ev.pid, + server_mod => maps:get(server_mod, InitState), + server_active => maps:get(server_active, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +ttest_tcp_server_start(Node, Domain, gen, Active) -> + TransportMod = socket_test_ttest_tcp_gen, + Transport = {TransportMod, #{domain => Domain}}, + socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active); +ttest_tcp_server_start(Node, Domain, sock, Active) -> + TransportMod = socket_test_ttest_tcp_socket, + Transport = {TransportMod, #{domain => Domain, + async => true, + method => plain}}, + socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active). + +ttest_tcp_server_stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). + +ttest_tcp_client_start(Node, + Notify, + Domain, gen, + ServerInfo, Active, MsgID, MaxOutstanding, RunTime) -> + TransportMod = socket_test_ttest_tcp_gen, + Transport = {TransportMod, #{domain => Domain}}, + socket_test_ttest_tcp_client:start_monitor(Node, + Notify, + Transport, + ServerInfo, + Active, + MsgID, MaxOutstanding, RunTime); +ttest_tcp_client_start(Node, + Notify, + Domain, sock, + ServerInfo, Active, MsgID, MaxOutstanding, RunTime) -> + TransportMod = socket_test_ttest_tcp_socket, + Transport = {TransportMod, #{domain => Domain, + async => true, + method => plain}}, + socket_test_ttest_tcp_client:start_monitor(Node, + Notify, + Transport, + ServerInfo, + Active, + MsgID, MaxOutstanding, RunTime). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(TTEST_MANAGER, esock_ttest_manager). + +-record(ttest_report_id, + {domain :: socket:domain(), + serv_trans :: gen | sock, + serv_active :: once | boolean(), + client_trans :: gen | sock, + client_active :: once | boolean(), + msg_id :: small | medium | large}). + +-record(ttest_report, {id :: #ttest_report_id{}, + time :: non_neg_integer(), + bytes :: non_neg_integer(), + msgs :: non_neg_integer()}). + +-spec ttest_report(Domain :: socket:domain(), + ServTrans :: gen | sock, ServActive :: once | boolean(), + ClientTrans :: gen | sock, ClientActive :: once | boolean(), + MsgID :: 1 | 2 | 3, + RunTime :: non_neg_integer(), + NumBytes :: non_neg_integer(), + NumMsgs :: non_neg_integer()) -> ok. + +ttest_report(Domain, + ServTrans, ServActive, + ClientTrans, ClientActive, + MsgID, + RunTime, + NumBytes, + NumMsgs) -> + ID = #ttest_report_id{domain = Domain, + serv_trans = ServTrans, + serv_active = ServActive, + client_trans = ClientTrans, + client_active = ClientActive, + msg_id = ttest_msg_id_num_to_name(MsgID)}, + Report = #ttest_report{id = ID, + time = RunTime, + bytes = NumBytes, + msgs = NumMsgs}, + %% If we run just one test case, the group init has never been run + %% and therefor the ttest manager is not running (we also don't actually + %% care about collecting reports in that case). + (catch global:send(?TTEST_MANAGER, Report)), + ok. + +ttest_msg_id_num_to_name(1) -> + small; +ttest_msg_id_num_to_name(2) -> + medium; +ttest_msg_id_num_to_name(3) -> + large. + +ttest_manager_start() -> + Self = self(), + {Pid, MRef} = spawn_monitor(fun() -> ttest_manager_init(Self) end), + receive + {ttest_manager_started, Pid} -> + erlang:demonitor(MRef, [flush]), + ok; + {'DOWN', MRef, process, Pid, Reason} -> + exit({failed_starting, ttest_manager, Reason}) + after 5000 -> + exit(Pid, kill), + exit({failed_starting, ttest_manager, timeout}) + end. + +ttest_manager_stop() -> + case global:whereis_name(?TTEST_MANAGER) of + Pid when is_pid(Pid) -> + erlang:monitor(process, Pid), + global:send(?TTEST_MANAGER, stop), + receive + {'DOWN', _MRef, process, Pid, _} -> + ok + after 10000 -> + exit(Pid, kill), + ok + end; + _ -> + ok + end. + +ttest_manager_init(Parent) -> + yes = global:register_name(?TTEST_MANAGER, self()), + ets:new(?TTEST_MANAGER, + [{keypos, #ttest_report.id}, named_table, protected, ordered_set]), + Parent ! {ttest_manager_started, self()}, + ttest_manager_loop(). + +ttest_manager_loop() -> + receive + stop -> + ?LOGGER:format("manager stopping~n", []), + ttest_manager_done(); + + #ttest_report{id = _ID, + time = _RunTime, + bytes = _NumBytes, + msgs = _NumMsgs} = Report -> + true = ets:insert_new(?TTEST_MANAGER, Report), + ttest_manager_loop() + end. + +%% We are supposed to pretty print the result here... +ttest_manager_done() -> + format_reports(inet), + %% format_reports(inet6), + ets:delete(?TTEST_MANAGER), + exit(normal). + +format_reports(Domain) -> + ?LOGGER:format("Domain ~w reports:~n~n", [Domain]), + format_reports(Domain, small), + format_reports(Domain, medium), + format_reports(Domain, large). + +format_reports(Domain, MsgID) when is_atom(MsgID) -> + case which_ttest_reports(Domain, MsgID) of + [] -> + ?LOGGER:format(" No ~w reports~n~n", [MsgID]); + Reports -> + ?LOGGER:format(" ~w reports: ~n", [MsgID]), + lists:foreach(fun(R) -> format_report(R) end, Reports) + end. + +%% This should really be a table like this: +%% +%% client +%% server gen(false) gen(once) gen(true) sock(false) sock(once) sock(true) +%% gen(false) nnn +%% gen(once) nnn +%% gen(true) nnn +%% sock(false) nnn +%% sock(once) nnn +%% sock(true) nnn +%% +format_report(#ttest_report{id = #ttest_report_id{serv_trans = STrans, + serv_active = SActive, + client_trans = CTrans, + client_active = CActive}, + time = RunTime, + bytes = BCnt, + msgs = MCnt}) -> + ?LOGGER:format(" server ~w[~w] - client ~w[~w] => " + "~n Run Time: ~s" + "~n Bytes: ~s" + "~n Messages: ~s" + "~n", [STrans, SActive, CTrans, CActive, + ?TTEST_LIB:format_time(RunTime), + if ((BCnt =:= 0) orelse (RunTime =:= 0)) -> + ?TTEST_LIB:format("~w, ~w", + [BCnt, RunTime]); + true -> + ?TTEST_LIB:format("~p => ~p byte / ms", + [BCnt, BCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + ?TTEST_LIB:format("~p => ~p iterations / ms", + [MCnt, MCnt div RunTime]) + end]), + ok. + + +which_ttest_reports(Domain, all) -> + [R || R = #ttest_report{id = #ttest_report_id{domain = D}} <- + ets:tab2list(?TTEST_MANAGER), Domain =:= D]; +which_ttest_reports(Domain, MsgID) -> + [R || R = #ttest_report{id = #ttest_report_id{domain = D, msg_id = MID}} <- + ets:tab2list(?TTEST_MANAGER), (Domain =:= D) andalso (MsgID =:= MID)]. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% TICKETS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Create several acceptor processes (processes that calls socket:accept/1) +%% and then a couple of clients connects to them without any problems. +%% TCP, IPv4. +otp16359_maccept_tcp4(suite) -> + []; +otp16359_maccept_tcp4(doc) -> + []; +otp16359_maccept_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(otp16359_maccept_tcp4, + fun() -> + InitState = #{domain => inet, + protocol => tcp}, + ok = otp16359_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Create several acceptor processes (processes that calls socket:accept/1) +%% and then a couple of clients connects to them without any problems. +%% TCP, IPv6. +otp16359_maccept_tcp6(suite) -> + []; +otp16359_maccept_tcp6(doc) -> + []; +otp16359_maccept_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(otp16359_maccept_tcp6, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + protocol => tcp}, + ok = otp16359_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Create several acceptor processes (processes that calls socket:accept/1) +%% and then a couple of clients connects to them without any problems. +%% TCP, UNix Domain Sockets. +otp16359_maccept_tcpL(suite) -> + []; +otp16359_maccept_tcpL(doc) -> + []; +otp16359_maccept_tcpL(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(otp16359_maccept_tcpL, + fun() -> has_support_unix_domain_socket() end, + fun() -> + InitState = #{domain => local, + protocol => default}, + ok = otp16359_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +otp16359_maccept_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LSA = which_local_socket_addr(Domain), + ?SEV_IPRINT("LSA: ~p", [LSA]), + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{domain := local, + lsock := LSock, + lsa := LSA} = _State) -> + case socket:bind(LSock, LSA) of + {ok, _} -> + ok; % We do not care about the port for local + {error, _} = ERROR -> + ERROR + end; + (#{lsock := LSock, lsa := LSA} = State) -> + case sock_bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{domain := local, + tester := Tester, + lsock := LSock, + lsa := #{path := Path}}) -> + ?SEV_ANNOUNCE_READY(Tester, init, {LSock, Path}), + ok; + (#{lsock := LSock, lport := LPort, tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init, {LSock, LPort}), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept (with success)", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("Expected (accept) success: " + "~n ~p", [Sock]), + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_TERMINATE(Tester, tester), + ok + end}, + %% *** Close (connect) socket *** + #{desc => "close (connect) socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{domain := local, + lsock := Sock, + lsa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(lsa, State) + end, + fun() -> State end), + {ok, maps:remove(lsock, State1)}; + (#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, LSock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + lsock => LSock}} + + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test part *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("Expected (accept) success: " + "~n ~p", [Sock]), + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Close (connect) socket *** + #{desc => "close (connect) socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(#{domain := local} = State) -> + {Tester, Path} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_path => Path}}; + (State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := local = Domain, + server_path := Path} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = #{family => Domain, path => Path}, + {ok, State#{lsa => LSA, server_sa => SSA}}; + (#{domain := Domain, server_port := Port} = State) -> + LSA = which_local_socket_addr(Domain), + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain, + protocol := Proto} = State) -> + case socket:open(Domain, stream, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case sock_bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + case socket:connect(Sock, SSA) of + ok -> + ?SEV_IPRINT("connected", []), + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed connect: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{domain := local, + sock := Sock, + lsa := #{path := Path}} = State) -> + ok = socket:close(Sock), + State1 = + unlink_path(Path, + fun() -> + maps:remove(lsa, State) + end, + fun() -> State end), + {ok, maps:remove(sock, State1)}; + (#{sock := Sock} = State) -> + ok = socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% Init part + #{desc => "monitor prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 3", + cmd => fun(#{client3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + + %% Start the prim-acceptor + #{desc => "start prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await prim-acceptor ready (init)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + {ok, {Sock, Port}} = + ?SEV_AWAIT_READY(Pid, prim_acceptor, init), + {ok, State#{lsock => Sock, lport => Port}} + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LSock), + ok + end}, + #{desc => "await sec-acceptor 1 ready (init)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor1, init) + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LSock), + ok + end}, + #{desc => "await sec-acceptor 2 ready (init)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor2, init) + end}, + + %% Start client-1 + #{desc => "start client 1", + cmd => fun(#{client1 := Pid, lport := LPort} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LPort), + ok + end}, + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client1, init) + end}, + + %% Start client-2 + #{desc => "start client 2", + cmd => fun(#{client2 := Pid, lport := LPort} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LPort), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client2, init) + end}, + + %% Start client-3 + #{desc => "start client 3", + cmd => fun(#{client3 := Pid, lport := LPort} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LPort), + ok + end}, + #{desc => "await client 3 ready (init)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client3, init) + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + + ?SEV_SLEEP(?SECS(1)), + + %% Activate the client(s) + #{desc => "active client 1", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + + ?SEV_SLEEP(100), + + #{desc => "active client 2", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + + ?SEV_SLEEP(100), + + #{desc => "active client 3", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (accept)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_acceptor, accept) + end}, + #{desc => "await sec-acceptor 1 ready (accept)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept) + end}, + #{desc => "await sec-acceptor 2 ready (accept)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept) + end}, + #{desc => "await client 1 ready (connect)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client1, connect) + end}, + #{desc => "await client 2 ready (connect)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client2, connect) + end}, + #{desc => "await client 3 ready (connect)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client3, connect) + end}, + + %% Terminate + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 3 to terminate", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 3 termination", + cmd => fun(#{client3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client3, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + + i("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState), + + i("create sec-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1), + + i("create sec-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2), + + i("create client 1 evaluator"), + ClientInitState1 = InitState, + Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState1), + + i("create client 2 evaluator"), + ClientInitState2 = InitState, + Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState2), + + i("create client 3 evaluator"), + ClientInitState3 = InitState, + Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState3), + + i("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid, + sec_acceptor1 => SecAcceptor1#ev.pid, + sec_acceptor2 => SecAcceptor2#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid, + client3 => Client3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2, + Client1, Client2, Client3, + Tester]). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This mechanism has only one purpose: So that we are able to kill +%% the node-starter process if it takes to long. The node-starter +%% runs on the local node. +%% This crapola is hopefully temporary, but we have seen that on +%% some platforms the ct_slave:start simply hangs. +-define(NODE_START_TIMEOUT, 10000). +start_node(Host, NodeName) -> + start_node(Host, NodeName, ?NODE_START_TIMEOUT). + +start_node(Host, NodeName, Timeout) -> + {NodeStarter, _} = + spawn_monitor(fun() -> exit(start_unique_node(Host, NodeName)) end), + receive + {'DOWN', _, process, NodeStarter, Result} -> + %% i("Node Starter (~p) reported: ~p", [NodeStarter, Result]), + Result + after Timeout -> + exit(NodeStarter, kill), + {error, {failed_starting_node, NodeName, timeout}} + end. + +start_unique_node(Host, NodeName) -> + UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]), + case do_start_node(Host, UniqueNodeName) of + {ok, _} = OK -> + global:sync(), + %% i("Node ~p started: " + %% "~n Nodes: ~p" + %% "~n Logger: ~p" + %% "~n Global Names: ~p", + %% [NodeName, nodes(), + %% global:whereis_name(socket_test_logger), + %% global:registered_names()]), + OK; + {error, Reason, _} -> + {error, Reason} + end. + +do_start_node(Host, NodeName) when is_list(NodeName) -> + do_start_node(Host, list_to_atom(NodeName)); +do_start_node(Host, NodeName) when is_atom(NodeName) -> + Dir = filename:dirname(code:which(?MODULE)), + Flags = "-pa " ++ Dir, + Opts = [{monitor_master, true}, {erl_flags, Flags}], + ct_slave:start(Host, NodeName, Opts). + + +stop_node(Node) -> + case ct_slave:stop(Node) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sock_open(Domain, Type, Proto) -> + try socket:open(Domain, Type, Proto) of + {ok, Socket} -> + Socket; + {error, Reason} -> + ?FAIL({open, Reason}) + catch + C:E:S -> + ?FAIL({open, C, E, S}) + end. + + +sock_bind(Sock, LSA) -> + try socket:bind(Sock, LSA) of + {ok, _} = OK -> + OK; + {error, eaddrnotavail = Reason} -> + ?SEV_IPRINT("Address not available"), + throw({skip, Reason}); + {error, _} = ERROR -> + ERROR + catch + C:E:S -> + ?FAIL({bind, C, E, S}) + end. + +sock_connect(Sock, SockAddr) -> + try socket:connect(Sock, SockAddr) of + ok -> + ok; + {error, Reason} -> + ?FAIL({connect, Reason}) + catch + C:E:S -> + ?FAIL({connect, C, E, S}) + end. + +sock_sockname(Sock) -> + try socket:sockname(Sock) of + {ok, SockAddr} -> + SockAddr; + {error, Reason} -> + ?FAIL({sockname, Reason}) + catch + C:E:S -> + ?FAIL({sockname, C, E, S}) + end. + +sock_close(Sock) -> + try socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + i("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +local_host() -> + try net_adm:localhost() of + Host when is_list(Host) -> + %% Convert to shortname if long + case string:tokens(Host, [$.]) of + [H|_] -> + list_to_atom(H) + end + catch + C:E:S -> + erlang:raise(C, E, S) + end. + + +%% The point of this is to "ensure" that paths from different test runs +%% don't clash. +mk_unique_path() -> + [NodeName | _] = string:tokens(atom_to_list(node()), [$@]), + Path = ?LIB:f("/tmp/esock_~s_~w", [NodeName, erlang:system_time(nanosecond)]), + ensure_unique_path(Path). + +ensure_unique_path(Path) -> + case file:read_file_info(Path) of + {ok, _} -> % Ouch, append a unique ID and try again + ensure_unique_path(Path, 1); + {error, _} -> + %% We assume this means it does not exist yet... + %% If we have several process in paralell trying to create + %% (unique) path's, then we are in trouble. To *really* be + %% on the safe side we should have a (central) path registry... + Path + end. + +ensure_unique_path(Path, ID) when (ID < 100) -> % If this is not enough... + NewPath = ?LIB:f("~s_~w", [Path, ID]), + case file:read_file_info(NewPath) of + {ok, _} -> % Ouch, this also existed, increment and try again + ensure_unique_path(Path, ID + 1); + {error, _} -> % We assume this means it does not exist yet... + NewPath + end; +ensure_unique_path(_, _) -> + skip("Could not create unique path"). + + +which_local_socket_addr(local = Domain) -> + #{family => Domain, + path => mk_unique_path()}; + +%% This gets the local socket address (not 127.0...) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_socket_addr(Domain) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{addr := Addr}} -> + #{family => Domain, + addr => Addr}; + {error, Reason} -> + ?FAIL(Reason) + end. + + + +which_local_addr(local = _Domain) -> + mk_unique_path(); + +%% This gets the local address (not 127.0...) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_addr(Domain) -> + ?LIB:which_local_addr(Domain). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Here are all the *general* test case condition functions. + +%% We also need to (be able to) figure out the multicast address, +%% which we only support for some platforms (linux and sunos). +%% We don't do that here, but since we can only do that (find a +%% multicast address) for specific platforms, we check that we are +%% on of those platforms here. +has_support_ip_multicast() -> + case os:type() of + {unix, OsName} when (OsName =:= linux) orelse + (OsName =:= sunos) -> + case ?LIB:which_local_host_info(inet) of + {ok, #{flags := Flags}} -> + case lists:member(multicast, Flags) of + true -> + ok; + false -> + not_supported(multicast) + end; + {error, Reason} -> + not_supported({multicast, Reason}) + end; + {unix, OsName} -> + skip(?F("Not Supported: platform ~w", [OsName])); + Type -> + skip(?F("Not Supported: platform ~p", [Type])) + end. + + +%% --- SOCK socket option test functions --- + +has_support_sock_acceptconn() -> + has_support_socket_option_sock(acceptconn). + +has_support_sock_bindtodevice() -> + has_support_socket_option_sock(bindtodevice). + +has_support_sock_broadcast() -> + has_support_socket_option_sock(broadcast), + case ?LIB:which_local_host_info(inet) of + {ok, #{flags := Flags}} -> + case lists:member(broadcast, Flags) of + true -> + ok; + false -> + not_supported({broadcast, Flags}) + end; + {error, Reason} -> + not_supported({broadcast, Reason}) + end. + +has_support_sock_debug() -> + has_support_socket_option_sock(debug). + +has_support_sock_domain() -> + has_support_socket_option_sock(domain). + +has_support_sock_dontroute() -> + has_support_socket_option_sock(dontroute). + +has_support_sock_keepalive() -> + has_support_socket_option_sock(keepalive). + +has_support_sock_oobinline() -> + has_support_socket_option_sock(oobinline). + +has_support_sock_passcred() -> + has_support_socket_option_sock(passcred). + +has_support_sock_peek_off() -> + has_support_socket_option_sock(peek_off). + +has_support_sock_peercred() -> + has_support_socket_option_sock(peercred). + +has_support_sock_priority() -> + has_support_socket_option_sock(priority). + +has_support_sock_rcvbuf() -> + has_support_socket_option_sock(rcvbuf). + +has_support_sock_rcvlowat() -> + has_support_socket_option_sock(rcvlowat). + +has_support_sock_rcvtimeo() -> + has_support_socket_option_sock(rcvtimeo). + +has_support_sock_sndbuf() -> + has_support_socket_option_sock(sndbuf). + +has_support_sock_sndlowat() -> + has_support_socket_option_sock(sndlowat). + +has_support_sock_sndtimeo() -> + has_support_socket_option_sock(sndtimeo). + +has_support_sock_timestamp() -> + has_support_socket_option_sock(timestamp). + + +%% --- IP socket option test functions --- + +has_support_ip_add_membership() -> + has_support_socket_option_ip(add_membership). + +has_support_ip_drop_membership() -> + has_support_socket_option_ip(drop_membership). + +has_support_ip_pktinfo() -> + has_support_socket_option_ip(pktinfo). + +has_support_ip_recvopts() -> + has_support_socket_option_ip(recvopts). + +has_support_ip_recvorigdstaddr() -> + has_support_socket_option_ip(recvorigdstaddr). + +has_support_ip_recvtos() -> + has_support_socket_option_ip(recvtos). + +has_support_ip_recvtos_and_or_sock_timestamp() -> + case (socket:is_supported(options, ip, recvtos) orelse + socket:is_supported(options, socket, timestamp)) of + true -> + ok; + false -> + skip(?F("Neither needed opts " + "ip:recvtos or socket:timestamp supported", [])) + end. + +has_support_ip_recvttl() -> + has_support_socket_option_ip(recvttl). + +has_support_ip_tos() -> + has_support_socket_option_ip(tos). + +has_support_ip_recverr() -> + has_support_socket_option_ip(recverr). + + +%% --- IPv6 socket option test functions --- + +has_support_ipv6_flowinfo() -> + has_support_socket_option_ipv6(flowinfo). + +has_support_ipv6_hoplimit_or_recvhoplimit() -> + %% case (socket:is_supported(options, ipv6, recvhoplimit) orelse + %% socket:is_supported(options, ipv6, hoplimit)) of + case is_any_options_supported([{ipv6, recvhoplimit}, {ipv6, hoplimit}]) of + true -> + ok; + false -> + skip(?F("Neither recvhoplimit or hoplimit supported", [])) + end. + +has_support_ipv6_recvpktinfo() -> + has_support_socket_option_ipv6(recvpktinfo). + + +has_support_ipv6_tclass_or_recvtclass() -> + case is_any_options_supported([{ipv6, recvtclass}, {ipv6, tclass}]) of + true -> + ok; + false -> + skip(?F("Neither recvtclass or tclass supported", [])) + end. + + +has_support_ipv6_recverr() -> + has_support_socket_option_ipv6(recverr). + + +%% --- TCP socket option test functions --- + +has_support_tcp_congestion() -> + has_support_socket_option_tcp(congestion). + +has_support_tcp_cork() -> + has_support_socket_option_tcp(cork). + +has_support_tcp_maxseg() -> + has_support_socket_option_tcp(maxseg). + +has_support_tcp_nodelay() -> + has_support_socket_option_tcp(nodelay). + + +%% --- UDP socket option test functions --- + +has_support_udp_cork() -> + has_support_socket_option_udp(cork). + + +%% --- General purpose socket option test functions --- + +has_support_socket_option_sock(Opt) -> + has_support_socket_option(socket, Opt). + +has_support_socket_option_ip(Opt) -> + has_support_socket_option(ip, Opt). + +has_support_socket_option_ipv6(Opt) -> + has_support_socket_option(ipv6, Opt). + +has_support_socket_option_tcp(Opt) -> + has_support_socket_option(tcp, Opt). + +has_support_socket_option_udp(Opt) -> + has_support_socket_option(udp, Opt). + + +has_support_socket_option(Level, Option) -> + case socket:is_supported(options, Level, Option) of + true -> + ok; + false -> + skip(?F("Not Supported: ~w option ~w", [Level, Option])) + end. + +is_any_options_supported(Options) -> + Pred = fun({Level, Option}) -> socket:is_supported(options, Level, Option) end, + lists:any(Pred, Options). + + +%% --- Send flag test functions --- + +has_support_send_flag_oob() -> + has_support_send_flag(oob). + +has_support_send_flag(Flag) -> + has_support_send_or_recv_flag("Send", send_flags, Flag). + +has_support_recv_flag_oob() -> + has_support_recv_flag(oob). + +has_support_recv_flag_peek() -> + has_support_recv_flag(peek). + +has_support_recv_flag(Flag) -> + has_support_send_or_recv_flag("Recv", recv_flags, Flag). + +has_support_send_or_recv_flag(Pre, Key, Flag) -> + case socket:is_supported(Key, Flag) of + true -> + ok; + false -> + skip(?F("~s Flag ~w *Not* Supported", [Pre, Flag])) + end. + + +%% Checks that the version is "good enough" (of the specified platform). + +is_good_enough_linux(CondVsn) -> + is_good_enough_platform(unix, linux, CondVsn). + +is_good_enough_darwin(CondVsn) -> + is_good_enough_platform(unix, darwin, CondVsn). + +is_good_enough_platform(Family, Name, CondVsn) -> + case os:type() of + {Family, Name} -> + ID = fun() -> ?F("~w:~w", [Family, Name]) end, + is_good_enough_platform2(os:version(), CondVsn, ID); + _ -> + ok + end. + +is_good_enough_platform2(Vsn, CondVsn, _) when (Vsn > CondVsn) -> + ok; +is_good_enough_platform2(Vsn, CondVsn, ID) -> + skip(?F("Not 'good enough' ~s (~p <= ~p)", [ID(), Vsn, CondVsn])). + +is_not_freebsd() -> + is_not_platform(freebsd, "FreeBSD"). + +is_not_openbsd() -> + is_not_platform(openbsd, "OpenBSD"). + +is_not_netbsd() -> + is_not_platform(netbsd, "NetBSD"). + +is_not_darwin() -> + is_not_platform(darwin, "Darwin"). + +is_not_platform(Platform, PlatformStr) + when is_atom(Platform) andalso is_list(PlatformStr) -> + case os:type() of + {unix, Platform} -> + skip("This does not work on " ++ PlatformStr); + _ -> + ok + end. + + +unix_domain_socket_host_cond() -> + unix_domain_socket_host_cond(os:type(), os:version()). + +unix_domain_socket_host_cond({unix, linux}, {M, _, _}) when (M < 3) -> + skip("TC may not work on this version"); +unix_domain_socket_host_cond(_, _) -> + ok. + +has_support_unix_domain_socket() -> + case os:type() of + {win32, _} -> + skip("Not supported"); + _ -> + case socket:is_supported(local) of + true -> + ok; + false -> + skip("Not supported") + end + end. + +has_support_sctp() -> + case os:type() of + {win32, _} -> + skip("Not supported"); + _ -> + case socket:is_supported(sctp) of + true -> + ok; + false -> + skip("Not supported") + end + end. + + +%% The idea is that this function shall test if the test host has +%% support for IPv6. If not, there is no point in running IPv6 tests. +%% Currently we just skip. +has_support_ipv6() -> + ?LIB:has_support_ipv6(). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +unlink_path(Path) -> + unlink_path(Path, fun() -> ok end, fun() -> ok end). + +unlink_path(Path, Success, Failure) when is_list(Path) andalso + is_function(Success, 0) andalso + is_function(Failure, 0) -> + ?SEV_IPRINT("try unlink path: " + "~n ~s", [Path]), + case os:cmd("unlink " ++ Path) of + "" -> + ?SEV_IPRINT("path unlinked: " + "~n Path: ~s", [Path]), + Success(); + Result -> + ?SEV_EPRINT("unlink maybe failed: " + "~n Path: ~s" + "~n Res: ~s", [Path, Result]), + Failure() + end; +unlink_path(_, _, _) -> + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_supported(What) -> + skip({not_supported, What}). + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +lookup(Key, Default, Config) -> + case lists:keysearch(Key, 1, Config) of + {value, {Key, Value}} -> + Value; + _ -> + Default + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t() -> + ts(ms). + +ts(ms) -> + erlang:monotonic_time(milli_seconds). + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +set_tc_name(N) when is_atom(N) -> + set_tc_name(atom_to_list(N)); +set_tc_name(N) when is_list(N) -> + put(tc_name, N). + +%% get_tc_name() -> +%% get(tc_name). + +tc_begin(TC) -> + OldVal = process_flag(trap_exit, true), + put(old_trap_exit, OldVal), + set_tc_name(TC), + tc_print("begin ***", + "~n----------------------------------------------------~n", ""). + +tc_end(Result) when is_list(Result) -> + OldVal = erase(old_trap_exit), + process_flag(trap_exit, OldVal), + tc_print("done: ~s", [Result], + "", "----------------------------------------------------~n~n"), + ok. + +%% *** tc_try/2,3 *** +%% Case: Basically the test case name +%% TCCondFun: A fun that is evaluated before the actual test case +%% The point of this is that it can performs checks to +%% see if we shall run the test case at all. +%% For instance, the test case may only work in specific +%% conditions. +%% FCFun: The test case fun +tc_try(Case, TCFun) -> + TCCondFun = fun() -> ok end, + tc_try(Case, TCCondFun, TCFun). + +tc_try(Case, TCCondFun, TCFun) + when is_atom(Case) andalso + is_function(TCCondFun, 0) andalso + is_function(TCFun, 0) -> + tc_begin(Case), + try TCCondFun() of + ok -> + try + begin + TCFun(), + ?SLEEP(?SECS(1)), + tc_end("ok") + end + catch + C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) -> + %% i("catched[tc] (skip): " + %% "~n C: ~p" + %% "~n SKIP: ~p" + %% "~n", [C, SKIP]), + tc_end( f("skipping(catched,~w,tc)", [C]) ), + SKIP; + C:E:S -> + %% i("catched[tc]: " + %% "~n C: ~p" + %% "~n E: ~p" + %% "~n S: ~p" + %% "~n", [C, E, S]), + tc_end( f("failed(catched,~w,tc)", [C]) ), + erlang:raise(C, E, S) + end; + {skip, _} = SKIP -> + tc_end("skipping(tc)"), + SKIP; + {error, Reason} -> + tc_end("failed(tc)"), + exit({tc_cond_failed, Reason}) + catch + C:{skip, _} = SKIP when ((C =:= throw) orelse (C =:= exit)) -> + %% i("catched[cond] (skip): " + %% "~n C: ~p" + %% "~n SKIP: ~p" + %% "~n", [C, SKIP]), + tc_end( f("skipping(catched,~w,cond)", [C]) ), + SKIP; + C:E:S -> + %% i("catched[cond]: " + %% "~n C: ~p" + %% "~n E: ~p" + %% "~n S: ~p" + %% "~n", [C, E, S]), + tc_end( f("failed(catched,~w,cond)", [C]) ), + erlang:raise(C, E, S) + end. + + +tc_print(F, Before, After) -> + tc_print(F, [], Before, After). + +tc_print(F, A, Before, After) -> + Name = tc_which_name(), + FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", + [formated_timestamp(),Name,self()|A]), + io:format(user, Before ++ FStr ++ After, []). + +tc_which_name() -> + case get(tc_name) of + undefined -> + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; + Name when is_list(Name) -> + Name + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This function prints various host info, which might be usefull +%% when analyzing the test suite (results). +%% It also returns a "factor" that can be used when deciding +%% the load for some test cases (traffic). Such as run time or +%% number of iterations. This only works for some OSes (linux). +%% Also, mostly it just returns the factor 1. +%% At this time we just look at BogoMIPS! +analyze_and_print_host_info() -> + {OsFam, OsName} = os:type(), + Version = + case os:version() of + {Maj, Min, Rel} -> + f("~w.~w.~w", [Maj, Min, Rel]); + VStr -> + VStr + end, + case {OsFam, OsName} of + {unix, linux} -> + analyze_and_print_linux_host_info(Version); + {unix, openbsd} -> + analyze_and_print_openbsd_host_info(Version); + {unix, freebsd} -> + analyze_and_print_freebsd_host_info(Version); + {unix, netbsd} -> + analyze_and_print_netbsd_host_info(Version); + {unix, sunos} -> + analyze_and_print_solaris_host_info(Version); + {win32, nt} -> + analyze_and_print_win_host_info(Version); + _ -> + io:format("OS Family: ~p" + "~n OS Type: ~p" + "~n Version: ~p" + "~n Num Schedulers: ~s" + "~n", [OsFam, OsName, Version, str_num_schedulers()]), + num_schedulers_to_factor() + end. + +str_num_schedulers() -> + try erlang:system_info(schedulers) of + N -> f("~w", [N]) + catch + _:_:_ -> "-" + end. + +num_schedulers_to_factor() -> + try erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + N when (N =< 6) -> + 2; + _ -> + 1 + catch + _:_:_ -> + 10 + end. + + + +%% --- Linux --- + +linux_which_distro(Version) -> + case file:read_file_info("/etc/issue") of + {ok, _} -> + case [string:trim(S) || + S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of + [DistroStr|_] -> + io:format("Linux: ~s" + "~n ~s" + "~n", + [Version, DistroStr]), + case DistroStr of + "Wind River Linux" ++ _ -> + wind_river; + "MontaVista" ++ _ -> + montavista; + "Yellow Dog" ++ _ -> + yellow_dog; + _ -> + other + end; + X -> + io:format("Linux: ~s" + "~n ~p" + "~n", + [Version, X]), + other + end; + _ -> + io:format("Linux: ~s" + "~n", [Version]), + other + end. + + +analyze_and_print_linux_host_info(Version) -> + Distro = linux_which_distro(Version), + Factor = + case (catch linux_which_cpuinfo(Distro)) of + {ok, {CPU, BogoMIPS}} -> + io:format("CPU: " + "~n Model: ~s" + "~n BogoMIPS: ~w" + "~n Num Schedulers: ~s" + "~n", [CPU, BogoMIPS, str_num_schedulers()]), + if + (BogoMIPS > 20000) -> + 1; + (BogoMIPS > 10000) -> + 2; + (BogoMIPS > 5000) -> + 3; + (BogoMIPS > 2000) -> + 5; + (BogoMIPS > 1000) -> + 8; + true -> + 10 + end; + {ok, CPU} -> + io:format("CPU: " + "~n Model: ~s" + "~n Num Schedulers: ~s" + "~n", [CPU, str_num_schedulers()]), + num_schedulers_to_factor(); + _ -> + 5 + end, + %% Check if we need to adjust the factor because of the memory + try linux_which_meminfo() of + AddFactor -> + Factor + AddFactor + catch + _:_:_ -> + Factor + end. + + +linux_cpuinfo_lookup(Key) when is_list(Key) -> + linux_info_lookup(Key, "/proc/cpuinfo"). + +linux_cpuinfo_cpu() -> + case linux_cpuinfo_lookup("cpu") of + [Model] -> + Model; + _ -> + "-" + end. + +linux_cpuinfo_motherboard() -> + case linux_cpuinfo_lookup("motherboard") of + [MB] -> + MB; + _ -> + "-" + end. + +linux_cpuinfo_bogomips() -> + case linux_cpuinfo_lookup("bogomips") of + BMips when is_list(BMips) -> + try lists:sum([bogomips_to_int(BM) || BM <- BMips]) + catch + _:_:_ -> + "-" + end; + _ -> + "-" + end. + +linux_cpuinfo_total_bogomips() -> + case linux_cpuinfo_lookup("total bogomips") of + [TBM] -> + try bogomips_to_int(TBM) + catch + _:_:_ -> + "-" + end; + _ -> + "-" + end. + +bogomips_to_int(BM) -> + try list_to_float(BM) of + F -> + floor(F) + catch + _:_:_ -> + try list_to_integer(BM) of + I -> + I + catch + _:_:_ -> + throw(noinfo) + end + end. + +linux_cpuinfo_model() -> + case linux_cpuinfo_lookup("model") of + [M] -> + M; + _ -> + "-" + end. + +linux_cpuinfo_platform() -> + case linux_cpuinfo_lookup("platform") of + [P] -> + P; + _ -> + "-" + end. + +linux_cpuinfo_model_name() -> + case linux_cpuinfo_lookup("model name") of + [P|_] -> + P; + _X -> + "-" + end. + +linux_cpuinfo_processor() -> + case linux_cpuinfo_lookup("Processor") of + [P] -> + P; + _ -> + "-" + end. + +linux_which_cpuinfo(montavista) -> + CPU = + case linux_cpuinfo_cpu() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_motherboard() of + "-" -> + Model; + MB -> + Model ++ " (" ++ MB ++ ")" + end + end, + case linux_cpuinfo_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end; + +linux_which_cpuinfo(yellow_dog) -> + CPU = + case linux_cpuinfo_cpu() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_motherboard() of + "-" -> + Model; + MB -> + Model ++ " (" ++ MB ++ ")" + end + end, + {ok, CPU}; + +linux_which_cpuinfo(wind_river) -> + CPU = + case linux_cpuinfo_model() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_platform() of + "-" -> + Model; + Platform -> + Model ++ " (" ++ Platform ++ ")" + end + end, + case linux_cpuinfo_total_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end; + +linux_which_cpuinfo(other) -> + %% Check for x86 (Intel or AMD) + CPU = + case linux_cpuinfo_model_name() of + "-" -> + %% ARM (at least some distros...) + case linux_cpuinfo_processor() of + "-" -> + %% Ok, we give up + throw(noinfo); + Proc -> + Proc + end; + ModelName -> + ModelName + end, + case linux_cpuinfo_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end. + +linux_meminfo_lookup(Key) when is_list(Key) -> + linux_info_lookup(Key, "/proc/meminfo"). + +linux_meminfo_memtotal() -> + case linux_meminfo_lookup("MemTotal") of + [X] -> + X; + _ -> + "-" + end. + +%% We *add* the value this return to the Factor. +linux_which_meminfo() -> + case linux_meminfo_memtotal() of + "-" -> + 0; + MemTotal -> + io:format("Memory:" + "~n ~s" + "~n", [MemTotal]), + case string:tokens(MemTotal, [$ ]) of + [MemSzStr, MemUnit] -> + MemSz2 = list_to_integer(MemSzStr), + MemSz3 = + case string:to_lower(MemUnit) of + "kb" -> + MemSz2; + "mb" -> + MemSz2*1024; + "gb" -> + MemSz2*1024*1024; + _ -> + throw(noinfo) + end, + if + (MemSz3 >= 8388608) -> + 0; + (MemSz3 >= 4194304) -> + 1; + (MemSz3 >= 2097152) -> + 3; + true -> + 5 + end; + _X -> + 0 + end + end. + + +linux_info_lookup(Key, File) -> + try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of + Info -> + linux_info_lookup_collect(Key, Info, []) + catch + _:_:_ -> + "-" + end. + +linux_info_lookup_collect(_Key, [], Values) -> + lists:reverse(Values); +linux_info_lookup_collect(Key, [Key, Value|Rest], Values) -> + linux_info_lookup_collect(Key, Rest, [Value|Values]); +linux_info_lookup_collect(_, _, Values) -> + lists:reverse(Values). + + +%% --- OpenBSD --- + +%% Just to be clear: This is ***not*** scientific... +analyze_and_print_openbsd_host_info(Version) -> + io:format("OpenBSD:" + "~n Version: ~p" + "~n", [Version]), + Extract = + fun(Key) -> + string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=]) + end, + try + begin + CPU = + case Extract("hw.model") of + ["hw.model", Model] -> + string:trim(Model); + _ -> + "-" + end, + CPUSpeed = + case Extract("hw.cpuspeed") of + ["hw.cpuspeed", Speed] -> + list_to_integer(Speed); + _ -> + -1 + end, + NCPU = + case Extract("hw.ncpufound") of + ["hw.ncpufound", N] -> + list_to_integer(N); + _ -> + -1 + end, + Memory = + case Extract("hw.physmem") of + ["hw.physmem", PhysMem] -> + list_to_integer(PhysMem) div 1024; + _ -> + -1 + end, + io:format("CPU:" + "~n Model: ~s" + "~n Speed: ~w" + "~n N: ~w" + "~nMemory:" + "~n ~w KB" + "~n", [CPU, CPUSpeed, NCPU, Memory]), + CPUFactor = + if + (CPUSpeed =:= -1) -> + 1; + (CPUSpeed >= 2000) -> + if + (NCPU >= 4) -> + 1; + (NCPU >= 2) -> + 2; + true -> + 3 + end; + true -> + if + (NCPU >= 4) -> + 2; + (NCPU >= 2) -> + 3; + true -> + 4 + end + end, + MemAddFactor = + if + (Memory =:= -1) -> + 0; + (Memory >= 8388608) -> + 0; + (Memory >= 4194304) -> + 1; + (Memory >= 2097152) -> + 2; + true -> + 3 + end, + CPUFactor + MemAddFactor + end + catch + _:_:_ -> + 1 + end. + + +%% --- FreeBSD --- + +analyze_and_print_freebsd_host_info(Version) -> + io:format("FreeBSD:" + "~n Version: ~p" + "~n", [Version]), + %% This test require that the program 'sysctl' is in the path. + %% First test with 'which sysctl', if that does not work + %% try with 'which /sbin/sysctl'. If that does not work either, + %% we skip the test... + try + begin + SysCtl = + case string:trim(os:cmd("which sysctl")) of + [] -> + case string:trim(os:cmd("which /sbin/sysctl")) of + [] -> + throw(sysctl); + SC2 -> + SC2 + end; + SC1 -> + SC1 + end, + Extract = + fun(Key) -> + string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)), + [$:]) + end, + CPU = analyze_freebsd_cpu(Extract), + CPUSpeed = analyze_freebsd_cpu_speed(Extract), + NCPU = analyze_freebsd_ncpu(Extract), + Memory = analyze_freebsd_memory(Extract), + io:format("CPU:" + "~n Model: ~s" + "~n Speed: ~w" + "~n N: ~w" + "~n Num Schedulers: ~w" + "~nMemory:" + "~n ~w KB" + "~n", + [CPU, CPUSpeed, NCPU, + erlang:system_info(schedulers), Memory]), + CPUFactor = + if + (CPUSpeed =:= -1) -> + 1; + (CPUSpeed >= 2000) -> + if + (NCPU >= 4) -> + 1; + (NCPU >= 2) -> + 2; + true -> + 3 + end; + true -> + if + (NCPU =:= -1) -> + 1; + (NCPU >= 4) -> + 2; + (NCPU >= 2) -> + 3; + true -> + 4 + end + end, + MemAddFactor = + if + (Memory =:= -1) -> + 0; + (Memory >= 8388608) -> + 0; + (Memory >= 4194304) -> + 1; + (Memory >= 2097152) -> + 2; + true -> + 3 + end, + CPUFactor + MemAddFactor + end + catch + _:_:_ -> + io:format("CPU:" + "~n Num Schedulers: ~w" + "~n", [erlang:system_info(schedulers)]), + case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end + end. + +analyze_freebsd_cpu(Extract) -> + analyze_freebsd_item(Extract, "hw.model", fun(X) -> X end, "-"). + +analyze_freebsd_cpu_speed(Extract) -> + analyze_freebsd_item(Extract, + "hw.clockrate", + fun(X) -> list_to_integer(X) end, + -1). + +analyze_freebsd_ncpu(Extract) -> + analyze_freebsd_item(Extract, + "hw.ncpu", + fun(X) -> list_to_integer(X) end, + -1). + +analyze_freebsd_memory(Extract) -> + analyze_freebsd_item(Extract, + "hw.physmem", + fun(X) -> list_to_integer(X) div 1024 end, + -1). + +analyze_freebsd_item(Extract, Key, Process, Default) -> + try + begin + case Extract(Key) of + [Key, Model] -> + Process(string:trim(Model)); + _ -> + Default + end + end + catch + _:_:_ -> + Default + end. + + +%% --- NetBSD --- + +analyze_and_print_netbsd_host_info(Version) -> + io:format("NetBSD:" + "~n Version: ~p" + "~n", [Version]), + %% This test require that the program 'sysctl' is in the path. + %% First test with 'which sysctl', if that does not work + %% try with 'which /sbin/sysctl'. If that does not work either, + %% we skip the test... + try + begin + SysCtl = + case string:trim(os:cmd("which sysctl")) of + [] -> + case string:trim(os:cmd("which /sbin/sysctl")) of + [] -> + throw(sysctl); + SC2 -> + SC2 + end; + SC1 -> + SC1 + end, + Extract = + fun(Key) -> + [string:trim(S) || + S <- + string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)), + [$=])] + end, + CPU = analyze_netbsd_cpu(Extract), + Machine = analyze_netbsd_machine(Extract), + Arch = analyze_netbsd_machine_arch(Extract), + CPUSpeed = analyze_netbsd_cpu_speed(Extract), + NCPU = analyze_netbsd_ncpu(Extract), + Memory = analyze_netbsd_memory(Extract), + io:format("CPU:" + "~n Model: ~s (~s, ~s)" + "~n Speed: ~w MHz" + "~n N: ~w" + "~n Num Schedulers: ~w" + "~nMemory:" + "~n ~w KB" + "~n", + [CPU, Machine, Arch, CPUSpeed, NCPU, + erlang:system_info(schedulers), Memory]), + CPUFactor = + if + (CPUSpeed =:= -1) -> + 1; + (CPUSpeed >= 2000) -> + if + (NCPU >= 4) -> + 1; + (NCPU >= 2) -> + 2; + true -> + 3 + end; + true -> + if + (NCPU =:= -1) -> + 1; + (NCPU >= 4) -> + 2; + (NCPU >= 2) -> + 3; + true -> + 4 + end + end, + MemAddFactor = + if + (Memory =:= -1) -> + 0; + (Memory >= 8388608) -> + 0; + (Memory >= 4194304) -> + 1; + (Memory >= 2097152) -> + 2; + true -> + 3 + end, + CPUFactor + MemAddFactor + end + catch + _:_:_ -> + io:format("CPU:" + "~n Num Schedulers: ~w" + "~n", [erlang:system_info(schedulers)]), + case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end + end. + +analyze_netbsd_cpu(Extract) -> + analyze_netbsd_item(Extract, "hw.model", fun(X) -> X end, "-"). + +analyze_netbsd_machine(Extract) -> + analyze_netbsd_item(Extract, "hw.machine", fun(X) -> X end, "-"). + +analyze_netbsd_machine_arch(Extract) -> + analyze_netbsd_item(Extract, "hw.machine_arch", fun(X) -> X end, "-"). + +analyze_netbsd_cpu_speed(Extract) -> + analyze_netbsd_item(Extract, "machdep.dmi.processor-frequency", + fun(X) -> case string:tokens(X, [$\ ]) of + [MHz, "MHz"] -> + list_to_integer(MHz); + _ -> + -1 + end + end, "-"). + +analyze_netbsd_ncpu(Extract) -> + analyze_netbsd_item(Extract, + "hw.ncpu", + fun(X) -> list_to_integer(X) end, + -1). + +analyze_netbsd_memory(Extract) -> + analyze_netbsd_item(Extract, + "hw.physmem64", + fun(X) -> list_to_integer(X) div 1024 end, + -1). + +analyze_netbsd_item(Extract, Key, Process, Default) -> + analyze_freebsd_item(Extract, Key, Process, Default). + + + +%% --- Solaris --- + +analyze_and_print_solaris_host_info(Version) -> + Release = + case file:read_file_info("/etc/release") of + {ok, _} -> + case [string:trim(S) || S <- string:tokens(os:cmd("cat /etc/release"), [$\n])] of + [Rel | _] -> + Rel; + _ -> + "-" + end; + _ -> + "-" + end, + %% Display the firmware device tree root properties (prtconf -b) + Props = [list_to_tuple([string:trim(PS) || PS <- Prop]) || + Prop <- [string:tokens(S, [$:]) || + S <- string:tokens(os:cmd("prtconf -b"), [$\n])]], + BannerName = case lists:keysearch("banner-name", 1, Props) of + {value, {_, BN}} -> + string:trim(BN); + _ -> + "-" + end, + InstructionSet = + case string:trim(os:cmd("isainfo -k")) of + "Pseudo-terminal will not" ++ _ -> + "-"; + IS -> + IS + end, + PtrConf = [list_to_tuple([string:trim(S) || S <- Items]) || Items <- [string:tokens(S, [$:]) || S <- string:tokens(os:cmd("prtconf"), [$\n])], length(Items) > 1], + SysConf = + case lists:keysearch("System Configuration", 1, PtrConf) of + {value, {_, SC}} -> + SC; + _ -> + "-" + end, + NumPhysProc = + begin + NPPStr = string:trim(os:cmd("psrinfo -p")), + try list_to_integer(NPPStr) of + _ -> + NPPStr + catch + _:_:_ -> + "-" + end + end, + NumProc = try integer_to_list(length(string:tokens(os:cmd("psrinfo"), [$\n]))) of + NPStr -> + NPStr + catch + _:_:_ -> + "-" + end, + MemSz = + case lists:keysearch("Memory size", 1, PtrConf) of + {value, {_, MS}} -> + MS; + _ -> + "-" + end, + io:format("Solaris: ~s" + "~n Release: ~s" + "~n Banner Name: ~s" + "~n Instruction Set: ~s" + "~n CPUs: ~s (~s)" + "~n System Config: ~s" + "~n Memory Size: ~s" + "~n Num Schedulers: ~s" + "~n~n", [Version, Release, BannerName, InstructionSet, + NumPhysProc, NumProc, + SysConf, MemSz, + str_num_schedulers()]), + MemFactor = + try string:tokens(MemSz, [$ ]) of + [SzStr, "Mega" ++ _] -> + try list_to_integer(SzStr) of + Sz when Sz > 8192 -> + 0; + Sz when Sz > 4096 -> + 1; + Sz when Sz > 2048 -> + 2; + _ -> + 5 + catch + _:_:_ -> + 10 + end; + [SzStr, "Giga" ++ _] -> + try list_to_integer(SzStr) of + Sz when Sz > 8 -> + 0; + Sz when Sz > 4 -> + 1; + Sz when Sz > 2 -> + 2; + _ -> + 5 + catch + _:_:_ -> + 10 + end; + _ -> + 10 + catch + _:_:_ -> + 10 + end, + try erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + N when (N =< 6) -> + 2; + _ -> + 1 + catch + _:_:_ -> + 10 + end + MemFactor. + + +%% --- Windows --- + +analyze_and_print_win_host_info(Version) -> + SysInfo = which_win_system_info(), + OsName = win_sys_info_lookup(os_name, SysInfo), + OsVersion = win_sys_info_lookup(os_version, SysInfo), + SysMan = win_sys_info_lookup(system_manufacturer, SysInfo), + SysMod = win_sys_info_lookup(system_model, SysInfo), + NumProcs = win_sys_info_lookup(num_processors, SysInfo), + TotPhysMem = win_sys_info_lookup(total_phys_memory, SysInfo), + io:format("Windows: ~s" + "~n OS Version: ~s (~p)" + "~n System Manufacturer: ~s" + "~n System Model: ~s" + "~n Number of Processor(s): ~s" + "~n Total Physical Memory: ~s" + "~n Num Schedulers: ~s" + "~n", [OsName, OsVersion, Version, + SysMan, SysMod, NumProcs, TotPhysMem, + str_num_schedulers()]), + MemFactor = + try + begin + [MStr, MUnit|_] = + string:tokens(lists:delete($,, TotPhysMem), [$\ ]), + case string:to_lower(MUnit) of + "gb" -> + try list_to_integer(MStr) of + M when M > 8 -> + 0; + M when M > 4 -> + 1; + M when M > 2 -> + 2; + _ -> + 5 + catch + _:_:_ -> + 10 + end; + "mb" -> + try list_to_integer(MStr) of + M when M > 8192 -> + 0; + M when M > 4096 -> + 1; + M when M > 2048 -> + 2; + _ -> + 5 + catch + _:_:_ -> + 10 + end; + _ -> + 10 + end + end + catch + _:_:_ -> + 10 + end, + CPUFactor = + case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end, + CPUFactor + MemFactor. + +win_sys_info_lookup(Key, SysInfo) -> + win_sys_info_lookup(Key, SysInfo, "-"). + +win_sys_info_lookup(Key, SysInfo, Def) -> + case lists:keysearch(Key, 1, SysInfo) of + {value, {Key, Value}} -> + Value; + false -> + Def + end. + +%% This function only extracts the prop(s) we actually care about! +%% On some hosts this (systeminfo) takes a *long time* (several minutes). +%% And since there is no way to provide a timeout to the os command call, +%% we have to wrap it in a process. +which_win_system_info() -> + F = fun() -> + try + begin + SysInfo = os:cmd("systeminfo"), + process_win_system_info( + string:tokens(SysInfo, [$\r, $\n]), []) + end + catch + C:E:S -> + io:format("Failed get or process System info: " + " Error Class: ~p" + " Error: ~p" + " Stack: ~p" + "~n", [C, E, S]), + [] + end + end, + ?LIB:pcall(F, ?MINS(1), []). + +process_win_system_info([], Acc) -> + Acc; +process_win_system_info([H|T], Acc) -> + case string:tokens(H, [$:]) of + [Key, Value] -> + case string:to_lower(Key) of + "os name" -> + process_win_system_info(T, + [{os_name, string:trim(Value)}|Acc]); + "os version" -> + process_win_system_info(T, + [{os_version, string:trim(Value)}|Acc]); + "system manufacturer" -> + process_win_system_info(T, + [{system_manufacturer, string:trim(Value)}|Acc]); + "system model" -> + process_win_system_info(T, + [{system_model, string:trim(Value)}|Acc]); + "processor(s)" -> + [NumProcStr|_] = string:tokens(Value, [$\ ]), + T2 = lists:nthtail(list_to_integer(NumProcStr), T), + process_win_system_info(T2, + [{num_processors, NumProcStr}|Acc]); + "total physical memory" -> + process_win_system_info(T, + [{total_phys_memory, string:trim(Value)}|Acc]); + _ -> + process_win_system_info(T, Acc) + end; + _ -> + process_win_system_info(T, Acc) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +l2a(S) when is_list(S) -> + list_to_atom(S). + +l2b(L) when is_list(L) -> + list_to_binary(L). + +b2l(B) when is_binary(B) -> + binary_to_list(B). + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + +i(F) -> + i(F, []). + +i(F, A) -> + FStr = f("[~s] " ++ F, [formated_timestamp()|A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). + diff --git a/lib/kernel/test/socket_test_evaluator.erl b/lib/kernel/test/socket_test_evaluator.erl new file mode 100644 index 0000000000..694f0d5f1e --- /dev/null +++ b/lib/kernel/test/socket_test_evaluator.erl @@ -0,0 +1,664 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_evaluator). + +%% Evaluator control functions +-export([ + start/3, + await_finish/1 + ]). + +%% Functions used by evaluators to interact with eachother +-export([ + %% Announce functions + %% (Send an announcement from one evaluator to another) + announce_start/1, announce_start/2, + announce_continue/2, announce_continue/3, + announce_ready/2, announce_ready/3, + announce_terminate/1, + + %% Await functions + %% (Wait for an announcement from another evaluator) + await_start/0, await_start/1, + await_continue/3, await_continue/4, + await_ready/3, await_ready/4, + await_terminate/2, await_terminate/3, + await_termination/1, await_termination/2 + ]). + +%% Utility functions +-export([ + iprint/2, % Info printouts + eprint/2 % Error printouts + ]). + +-export_type([ + ev/0, + initial_evaluator_state/0, + evaluator_state/0, + command_fun/0, + command/0 + ]). + + +-include("socket_test_evaluator.hrl"). + +-type ev() :: #ev{}. +-type initial_evaluator_state() :: map(). +-type evaluator_state() :: term(). +-type command_fun() :: + fun((State :: evaluator_state()) -> ok) | + fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | + fun((State :: evaluator_state()) -> {error, term()}). + +-type command() :: #{desc := string(), + cmd := command_fun()}. + + +%% ============================================================================ + +-define(LIB, socket_test_lib). +-define(LOGGER, socket_test_logger). + +-define(EXTRA_NOTHING, '$nothing'). +-define(ANNOUNCEMENT_START, '$start'). +-define(ANNOUNCEMENT_READY, '$ready'). +-define(ANNOUNCEMENT_CONTINUE, '$continue'). +-define(ANNOUNCEMENT_TERMINATE, '$terminate'). + +-define(START_NAME_NONE, '$no-name'). +-define(START_SLOGAN, ?ANNOUNCEMENT_START). +-define(TERMINATE_SLOGAN, ?ANNOUNCEMENT_TERMINATE). + + +%% ============================================================================ + +-spec start(Name, Seq, Init) -> ev() when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(). + +start(Name, Seq, InitState) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + %% Make sure 'parent' is not already used + case maps:find(parent, InitState) of + {ok, _} -> + erlang:error({already_used, parent}); + error -> + InitState2 = InitState#{parent => self()}, + Pid = erlang:spawn_link( + fun() -> init(Name, Seq, InitState2) end), + %% MRef = erlang:monitor(process, Pid), + #ev{name = Name, pid = Pid}%, mref = MRef} + end. + +init(Name, Seq, Init) -> + put(sname, Name), + process_flag(trap_exit, true), + loop(1, Seq, Init). + +loop(_ID, [], FinalState) -> + exit(FinalState); +loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + iprint("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + loop(ID + 1, Cmds, State); + {ok, NewState} -> + loop(ID + 1, Cmds, NewState); + {skip, Reason} -> + ?SEV_IPRINT("command ~w skip: " + "~n ~p", [ID, Reason]), + exit({skip, Reason}); + {error, Reason} -> + ?SEV_EPRINT("command ~w failed: " + "~n ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:{skip, command} = E:_ when ((C =:= throw) orelse (C =:= exit)) -> + %% Secondary skip + exit(E); + C:{skip, R} = E:_ when ((C =:= throw) orelse (C =:= exit)) -> + ?SEV_IPRINT("command ~w skip catched(~w): " + "~n Reason: ~p", [ID, C, R]), + exit(E); + C:E:S -> + ?SEV_EPRINT("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), + exit({command_crashed, ID, {C,E,S}, State}) + end. + + +%% ============================================================================ + +-spec await_finish(Evs) -> term() when + Evs :: [ev()]. + +await_finish(Evs) -> + await_finish(Evs, [], []). + +await_finish([], _, []) -> + ok; +await_finish([], _OK, Fails) -> + ?SEV_EPRINT("Fails: " + "~n ~p", [Fails]), + Fails; +await_finish(Evs, OK, Fails) -> + receive + %% Successfull termination of evaluator + {'DOWN', _MRef, process, Pid, normal} -> + {Evs2, OK2, Fails2} = await_finish_normal(Pid, Evs, OK, Fails), + await_finish(Evs2, OK2, Fails2); + {'EXIT', Pid, normal} -> + {Evs2, OK2, Fails2} = await_finish_normal(Pid, Evs, OK, Fails), + await_finish(Evs2, OK2, Fails2); + + %% The evaluator can skip the test case: + {'DOWN', _MRef, process, Pid, {skip, Reason}} -> + %% ?SEV_IPRINT("await_finish -> skip (down) received: " + %% "~n Pid: ~p" + %% "~n Reason: ~p", [Pid, Reason]), + await_finish_skip(Pid, Reason, Evs, OK); + {'EXIT', Pid, {skip, Reason}} -> + %% ?SEV_IPRINT("await_finish -> skip (exit) received: " + %% "~n Pid: ~p" + %% "~n Reason: ~p", [Pid, Reason]), + await_finish_skip(Pid, Reason, Evs, OK); + + %% Evaluator failed + {'DOWN', _MRef, process, Pid, Reason} -> + %% ?SEV_IPRINT("await_finish -> fail (down) received: " + %% "~n Pid: ~p" + %% "~n Reason: ~p", [Pid, Reason]), + {Evs2, OK2, Fails2} = + await_finish_fail(Pid, Reason, Evs, OK, Fails), + await_finish(Evs2, OK2, Fails2); + {'EXIT', Pid, Reason} -> + %% ?SEV_IPRINT("await_finish -> fail (exit) received: " + %% "~n Pid: ~p" + %% "~n Reason: ~p", [Pid, Reason]), + {Evs2, OK2, Fails2} = + await_finish_fail(Pid, Reason, Evs, OK, Fails), + await_finish(Evs2, OK2, Fails2) + end. + + +await_finish_normal(Pid, Evs, OK, Fails) -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + iprint("evaluator '~s' (~p) success", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + {NewEvs, [Pid|OK], Fails}; + false -> + case lists:member(Pid, OK) of + true -> + ok; + false -> + iprint("unknown process ~p died (normal)", [Pid]), + ok + end, + {Evs, OK, Fails} + end. + +await_finish_skip(Pid, Reason, Evs, OK) -> + Evs2 = + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + ?SEV_IPRINT("evaluator '~s' (~p) issued SKIP: " + "~n ~p", [Name, Pid, Reason]), + lists:keydelete(Pid, #ev.pid, Evs); + false -> + case lists:member(Pid, OK) of + true -> + ?SEV_IPRINT("already terminated (ok) process ~p skip" + "~n ~p", [Pid]), + ok; + false -> + ?SEV_IPRINT("unknown process ~p issued SKIP: " + "~n ~p", [Pid, Reason]), + iprint("unknown process ~p issued SKIP: " + "~n ~p", [Pid, Reason]) + end, + Evs + end, + await_evs_terminated(Evs2), + ?LIB:skip(Reason). + +await_evs_terminated(Evs) -> + Instructions = + [ + %% Just wait for the evaluators to die on their own + {fun() -> ?SEV_IPRINT("await (no action) evs termination") end, + fun(_) -> ok end}, + + %% Send them a skip message, causing the evaluators to + %% die with a skip reason. + {fun() -> ?SEV_IPRINT("await (send skip message) evs termination") end, + fun(#ev{pid = Pid}) -> Pid ! skip end}, + + %% And if nothing else works, try to kill the remaining evaluators + {fun() -> ?SEV_IPRINT("await (issue exit kill) evs termination") end, + fun(#ev{pid = Pid}) -> exit(Pid, kill) end}], + + await_evs_terminated(Evs, Instructions). + +await_evs_terminated([], _) -> + ok; +await_evs_terminated(Evs, []) -> + {error, {failed_terminated, [P||#ev{pid=P} <- Evs]}}; +await_evs_terminated(Evs, [{Inform, Command}|Instructions]) -> + Inform(), + lists:foreach(Command, Evs), + RemEvs = await_evs_termination(Evs), + await_evs_terminated(RemEvs, Instructions). + +await_evs_termination(Evs) -> + await_evs_termination(Evs, 2000). + +await_evs_termination([], _Timeout) -> + []; +await_evs_termination(Evs, Timeout) -> + T = t(), + receive + {'DOWN', _MRef, process, Pid, _Reason} -> + %% ?SEV_IPRINT("await_evs_termination -> DOWN: " + %% "~n Pid: ~p" + %% "~n Reason: ~p", [Pid, Reason]), + Evs2 = lists:keydelete(Pid, #ev.pid, Evs), + await_evs_termination(Evs2, tdiff(T, t())); + {'EXIT', Pid, _Reason} -> + %% ?SEV_IPRINT("await_evs_termination -> EXIT: " + %% "~n Pid: ~p" + %% "~n Reason: ~p", [Pid, Reason]), + Evs2 = lists:keydelete(Pid, #ev.pid, Evs), + await_evs_termination(Evs2, tdiff(T, t())) + + after Timeout -> + Evs + end. + + +await_finish_fail(Pid, Reason, Evs, OK, Fails) -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + iprint("evaluator '~s' (~p) failed", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + {NewEvs, OK, [{Pid, Reason}|Fails]}; + false -> + case lists:member(Pid, OK) of + true -> + ok; + false -> + iprint("unknown process ~p died: " + "~n ~p", [Pid, Reason]) + end, + {Evs, OK, Fails} + end. + + + +%% ============================================================================ + +-spec announce_start(To) -> ok when + To :: pid(). + +announce_start(To) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN). + +-spec announce_start(To, Extra) -> ok when + To :: pid(), + Extra :: term(). + +announce_start(To, Extra) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN, Extra). + + +%% ============================================================================ + +-spec announce_continue(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_continue(To, Slogan) -> + announce_continue(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_continue(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_continue(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_CONTINUE, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_ready(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_ready(To, Slogan) -> + announce_ready(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_ready(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_ready(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_READY, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_terminate(To) -> ok when + To :: pid(). + +announce_terminate(To) -> + announce(To, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN). + + +%% ============================================================================ + +-spec announce(To, Announcement, Slogan) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(). + +announce(To, Announcement, Slogan) -> + announce(To, Announcement, Slogan, ?EXTRA_NOTHING). + +-spec announce(To, Announcement, Slogan, Extra) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(), + Extra :: term(). + +announce(To, Announcement, Slogan, Extra) + when is_pid(To) andalso + is_atom(Announcement) andalso + is_atom(Slogan) -> + %% iprint("announce -> entry with: " + %% "~n To: ~p" + %% "~n Announcement: ~p" + %% "~n Slogan: ~p" + %% "~n Extra: ~p", + %% [To, Announcement, Slogan, Extra]), + To ! {Announcement, self(), Slogan, Extra}, + ok. + + + +%% ============================================================================ + +-spec await_start() -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start() -> + await_start(any). + +-spec await_start(Pid) -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start(P) when is_pid(P) orelse (P =:= any) -> + case await(P, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of + {ok, Any} when is_pid(P) -> + Any; + {ok, Pid} when is_pid(Pid) andalso (P =:= any) -> + Pid; + {ok, {Pid, _} = OK} when is_pid(Pid) andalso (P =:= any) -> + OK + end. + + +%% ============================================================================ + +-spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan) -> + await_continue(From, Name, Slogan, []). + +-spec await_continue(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_CONTINUE, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_ready(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan) -> + await_ready(From, Name, Slogan, []). + +-spec await_ready(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_READY, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_terminate(Pid, Name) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + Reason :: term(). + +await_terminate(Pid, Name) when is_pid(Pid) andalso is_atom(Name) -> + await_terminate(Pid, Name, []). + +-spec await_terminate(Pid, Name, OtherPids) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + OtherPids :: [{pid(), atom()}], + Reason :: term(). + +await_terminate(Pid, Name, OtherPids) -> + await(Pid, Name, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN, OtherPids). + + +%% ============================================================================ + +-spec await_termination(Pid) -> ok | {error, Reason} when + Pid :: pid(), + Reason :: term(). + +await_termination(Pid) when is_pid(Pid) -> + await_termination(Pid, any). + +-spec await_termination(Pid, ExpReason) -> ok | {error, Reason} when + Pid :: pid(), + ExpReason :: term(), + Reason :: term(). + +await_termination(Pid, ExpReason) -> + receive + {'DOWN', _, process, Pid, _} when (ExpReason =:= any) -> + ok; + {'DOWN', _, process, Pid, Reason} when (ExpReason =:= Reason) -> + ok; + {'DOWN', _, process, Pid, Reason} -> + {error, {unexpected_reason, ExpReason, Reason}} + end. + + +%% ============================================================================ + +%% We expect a message (announcement) from Pid, but we also watch for DOWN from +%% both Pid and OtherPids, in which case the test has failed! + +-spec await(ExpPid, Name, Announcement, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + ExpPid :: any | pid(), + Name :: atom(), + Announcement :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await(ExpPid, Name, Announcement, Slogan, OtherPids) + when (is_pid(ExpPid) orelse (ExpPid =:= any)) andalso + is_atom(Name) andalso + is_atom(Announcement) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + receive + skip -> + %% This means that another evaluator has issued a skip, + %% and we have been instructed to terminate as a result. + ?LIB:skip(command); + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (ExpPid =:= any) -> + {ok, Pid}; + {Announcement, Pid, Slogan, Extra} when (ExpPid =:= any) -> + {ok, {Pid, Extra}}; + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (Pid =:= ExpPid) -> + ok; + {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) -> + {ok, Extra}; + {'DOWN', _, process, Pid, {skip, SkipReason}} when (Pid =:= ExpPid) -> + iprint("Unexpected SKIP from ~w (~p): " + "~n ~p", [Name, Pid, SkipReason]), + ?LIB:skip({Name, SkipReason}); + {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) -> + eprint("Unexpected DOWN from ~w (~p): " + "~n ~p", [Name, Pid, Reason]), + {error, {unexpected_exit, Name, Reason}}; + {'DOWN', _, process, OtherPid, Reason} -> + case check_down(OtherPid, Reason, OtherPids) of + ok -> + iprint("DOWN from unknown process ~p: " + "~n ~p" + "~n when" + "~n OtherPids: " + "~n ~p", [OtherPid, Reason, OtherPids]), + await(ExpPid, Name, Announcement, Slogan, OtherPids); + {error, _} = ERROR -> + ERROR + end + after infinity -> % For easy debugging, just change to some valid time (5000) + iprint("await -> timeout for msg from ~p (~w): " + "~n Announcement: ~p" + "~n Slogan: ~p" + "~nwhen" + "~n Messages: ~p", + [ExpPid, Name, Announcement, Slogan, pi(messages)]), + await(ExpPid, Name, Announcement, Slogan, OtherPids) + end. + +pi(Item) -> + pi(self(), Item). + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + +check_down(Pid, DownReason, Pids) -> + case lists:keymember(Pid, 1, Pids) of + {value, {_, Name}} -> + eprint("Unexpected DOWN from ~w (~p): " + "~n ~p", [Name, Pid, DownReason]), + {error, {unexpected_exit, Name, DownReason}}; + false -> + ok + end. + + +%% ============================================================================ + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +iprint(F, A) -> + print("", F, A). + +eprint(F, A) -> + print("<ERROR> ", F, A). + +print(Prefix, F, A) -> + %% The two prints is to get the output both in the shell (for when + %% "personal" testing is going on) and in the logs. + IDStr = + case get(sname) of + undefined -> + %% This means its not an evaluator, + %% or a named process. Instead its + %% most likely the test case itself, + %% so skip the name and the pid. + ""; + SName -> + f("[~s][~p]", [SName, self()]) + end, + ?LOGGER:format("[~s]~s ~s" ++ F, + [?LIB:formated_timestamp(), IDStr, Prefix | A]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +t() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + diff --git a/lib/kernel/test/socket_test_evaluator.hrl b/lib/kernel/test/socket_test_evaluator.hrl new file mode 100644 index 0000000000..5be49dc022 --- /dev/null +++ b/lib/kernel/test/socket_test_evaluator.hrl @@ -0,0 +1,68 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_evaluator). +-define(socket_test_evaluator, true). + +-record(ev, {name :: string(), + pid :: pid(), + mref :: reference()}). + +-define(SEV, socket_test_evaluator). + +-define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)). +-define(SEV_AWAIT_FINISH(Evs), ?SEV:await_finish(Evs)). + +-define(SEV_ANNOUNCE_START(To), ?SEV:announce_start(To)). +-define(SEV_ANNOUNCE_START(To, Ex), ?SEV:announce_start(To, Ex)). +-define(SEV_ANNOUNCE_CONTINUE(To, S), ?SEV:announce_continue(To, S)). +-define(SEV_ANNOUNCE_CONTINUE(To, S, Ex), ?SEV:announce_continue(To, S, Ex)). +-define(SEV_ANNOUNCE_READY(To, S), ?SEV:announce_ready(To, S)). +-define(SEV_ANNOUNCE_READY(To, S, Ex), ?SEV:announce_ready(To, S, Ex)). +-define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)). + +-define(SEV_AWAIT_START(), ?SEV:await_start()). +-define(SEV_AWAIT_START(P), ?SEV:await_start(P)). +-define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)). +-define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)). +-define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)). +-define(SEV_AWAIT_READY(F, N, S, Ps), ?SEV:await_ready(F, N, S, Ps)). +-define(SEV_AWAIT_TERMINATE(F, N), ?SEV:await_terminate(F, N)). +-define(SEV_AWAIT_TERMINATE(F, N, Ps), ?SEV:await_terminate(F, N, Ps)). +-define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)). +-define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)). + +-define(SEV_IPRINT(F, A), ?SEV:iprint(F, A)). +-define(SEV_IPRINT(F), ?SEV_IPRINT(F, [])). +-define(SEV_EPRINT(F, A), ?SEV:eprint(F, A)). +-define(SEV_EPRINT(F), ?SEV_EPRINT(F, [])). + +-define(SEV_SLEEP(T), #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(T), + ok + end}). +-define(SEV_FINISH_NORMAL, #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end}). + +-endif. % -ifdef(socket_test_evaluator). + diff --git a/lib/kernel/test/socket_test_lib.erl b/lib/kernel/test/socket_test_lib.erl new file mode 100644 index 0000000000..13d4a4ba8e --- /dev/null +++ b/lib/kernel/test/socket_test_lib.erl @@ -0,0 +1,303 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_lib). + +-export([ + %% Process info + pi/1, pi/2, pi/3, + + %% Proxy call + pcall/3, + + %% Time stuff + timestamp/0, + tdiff/2, + formated_timestamp/0, + format_timestamp/1, + + %% String and format + f/2, + + %% Generic 'has support' test function(s) + has_support_ipv6/0, + + which_local_host_info/1, + which_local_addr/1, + + %% Skipping + not_yet_implemented/0, + skip/1 + ]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(FAIL(R), exit(R)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +pi(Item) when is_atom(Item) -> + pi(self(), Item). + +pi(Pid, Item) when is_pid(Pid) andalso is_atom(Item) -> + {Item, Info} = process_info(Pid, Item), + Info; +pi(Node, Pid) when is_pid(Pid) -> + rpc:call(Node, erlang, process_info, [Pid]). + +pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) -> + rpc:call(Node, erlang, process_info, [Pid, Item]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +pcall(F, Timeout, Default) + when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) -> + {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end), + receive + {'DOWN', M, process, P, Reply} -> + Reply + after Timeout -> + erlang:demonitor(M, [flush]), + exit(P, kill), + Default + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +timestamp() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +has_support_ipv6() -> + case socket:is_supported(ipv6) of + true -> + ok; + false -> + skip("IPv6 Not Supported") + end, + Domain = inet6, + LocalAddr = + case which_local_addr(Domain) of + {ok, Addr} -> + Addr; + {error, R1} -> + skip(f("Local Address eval failed: ~p", [R1])) + end, + ServerSock = + case socket:open(Domain, dgram, udp) of + {ok, SS} -> + SS; + {error, R2} -> + skip(f("(server) socket open failed: ~p", [R2])) + end, + LocalSA = #{family => Domain, addr => LocalAddr}, + ServerPort = + case socket:bind(ServerSock, LocalSA) of + {ok, P1} -> + P1; + {error, R3} -> + socket:close(ServerSock), + skip(f("(server) socket bind failed: ~p", [R3])) + end, + ServerSA = LocalSA#{port => ServerPort}, + ClientSock = + case socket:open(Domain, dgram, udp) of + {ok, CS} -> + CS; + {error, R4} -> + skip(f("(client) socket open failed: ~p", [R4])) + end, + case socket:bind(ClientSock, LocalSA) of + {ok, _} -> + ok; + {error, R5} -> + socket:close(ServerSock), + socket:close(ClientSock), + skip(f("(client) socket bind failed: ~p", [R5])) + end, + case socket:sendto(ClientSock, <<"hejsan">>, ServerSA) of + ok -> + ok; + {error, R6} -> + socket:close(ServerSock), + socket:close(ClientSock), + skip(f("failed socket sendto test: ~p", [R6])) + end, + case socket:recvfrom(ServerSock) of + {ok, {_, <<"hejsan">>}} -> + socket:close(ServerSock), + socket:close(ClientSock), + ok; + {error, R7} -> + socket:close(ServerSock), + socket:close(ClientSock), + skip(f("failed socket recvfrom test: ~p", [R7])) + end. + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This gets the local address (not {127, _} or {0, ...} or {16#fe80, ...}) +%% We should really implement this using the (new) net module, +%% but until that gets the necessary functionality... +which_local_addr(Domain) -> + case which_local_host_info(Domain) of + {ok, #{addr := Addr}} -> + {ok, Addr}; + {error, _Reason} = ERROR -> + ERROR + end. + + +%% Returns the interface (name), flags and address (not 127...) +%% of the local host. +which_local_host_info(Domain) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_local_host_info(Domain, IFL); + {error, _} = ERROR -> + ERROR + end. + +which_local_host_info(_Domain, []) -> + {error, no_address}; +which_local_host_info(Domain, [{"docker" ++ _, _}|IFL]) -> + which_local_host_info(Domain, IFL); +which_local_host_info(Domain, [{"br-" ++ _, _}|IFL]) -> + which_local_host_info(Domain, IFL); +which_local_host_info(Domain, [{Name, IFO}|IFL]) -> + case if_is_running_and_not_loopback(IFO) of + true -> + try which_local_host_info2(Domain, IFO) of + Info -> + {ok, Info#{name => Name}} + catch + throw:_:_ -> + which_local_host_info(Domain, IFL) + end; + false -> + which_local_host_info(Domain, IFL) + end; +which_local_host_info(Domain, [_|IFL]) -> + which_local_host_info(Domain, IFL). + +if_is_running_and_not_loopback(If) -> + lists:keymember(flags, 1, If) andalso + begin + {value, {flags, Flags}} = lists:keysearch(flags, 1, If), + (not lists:member(loopback, Flags)) andalso + lists:member(running, Flags) + end. + + +which_local_host_info2(inet = _Domain, IFO) -> + Addr = which_local_host_info3(addr, IFO, + fun({A, _, _, _}) when (A =/= 127) -> true; + (_) -> false + end), + NetMask = which_local_host_info3(netmask, IFO, + fun({_, _, _, _}) -> true; + (_) -> false + end), + BroadAddr = which_local_host_info3(broadaddr, IFO, + fun({_, _, _, _}) -> true; + (_) -> false + end), + Flags = which_local_host_info3(flags, IFO, fun(_) -> true end), + #{flags => Flags, + addr => Addr, + broadaddr => BroadAddr, + netmask => NetMask}; +which_local_host_info2(inet6 = _Domain, IFO) -> + Addr = which_local_host_info3(addr, IFO, + fun({A, _, _, _, _, _, _, _}) + when (A =/= 0) andalso + (A =/= 16#fe80) -> true; + (_) -> false + end), + NetMask = which_local_host_info3(netmask, IFO, + fun({_, _, _, _, _, _, _, _}) -> true; + (_) -> false + end), + Flags = which_local_host_info3(flags, IFO, fun(_) -> true end), + #{flags => Flags, + addr => Addr, + netmask => NetMask}. + +which_local_host_info3(_Key, [], _) -> + throw({error, no_address}); +which_local_host_info3(Key, [{Key, Val}|IFO], Check) -> + case Check(Val) of + true -> + Val; + false -> + which_local_host_info3(Key, IFO, Check) + end; +which_local_host_info3(Key, [_|IFO], Check) -> + which_local_host_info3(Key, IFO, Check). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/kernel/test/socket_test_logger.erl b/lib/kernel/test/socket_test_logger.erl new file mode 100644 index 0000000000..f5d4c8c7b2 --- /dev/null +++ b/lib/kernel/test/socket_test_logger.erl @@ -0,0 +1,118 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_logger). + +-export([ + start/0, start/1, + stop/0, + format/2 + ]). + + +-define(QUIET, true). +-define(LIB, socket_test_lib). +-define(LOGGER, ?MODULE). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start() -> + start(?QUIET). + +start(Quiet) -> + case global:whereis_name(?LOGGER) of + Pid when is_pid(Pid) -> + ok; + undefined -> + Self = self(), + Pid = spawn(fun() -> init(Self, Quiet) end), + yes = global:register_name(?LOGGER, Pid), + ok + end. + + +stop() -> + case global:whereis_name(?LOGGER) of + undefined -> + ok; + Pid when is_pid(Pid) -> + global:unregister_name(?LOGGER), + Pid ! {?LOGGER, '$logger', stop}, + ok + end. + + +format(F, []) -> + do_format(F); +format(F, A) -> + do_format(?LIB:f(F, A)). + +do_format(Msg) -> + case global:whereis_name(?LOGGER) of + undefined -> + ok; + Pid when is_pid(Pid) -> + Pid ! {?MODULE, '$logger', {msg, Msg}}, + ok + end. + +init(Parent, Quiet) -> + put(sname, "logger"), + print("[~s][logger] starting~n", [?LIB:formated_timestamp()]), + loop(#{parent => Parent, quiet => Quiet}). + +loop(#{parent := Parent, + quiet := Quiet} = State) -> + receive + {'EXIT', Parent, _} -> + print("[~s][logger] parent exit~n", [?LIB:formated_timestamp()]), + exit(normal); + + {?MODULE, '$logger', stop} -> + print("[~s][logger] stopping~n", [?LIB:formated_timestamp()]), + exit(normal); + + {?MODULE, '$logger', {msg, Msg}} -> + print_str(Quiet, Msg), + loop(State) + end. + + +print(F, A) -> + print_str(false, ?LIB:f(F, A)). + +print_str(Quiet, Str) -> + try + begin + if (Quiet =/= true) -> io:format(user, Str ++ "~n", []); + true -> ok + end, + io:format(Str, []) + end + catch + _:_:_ -> + io:format(user, + "~nFailed Format message:" + "~n~p~n", [Str]), + io:format("~nFailed Format message:" + "~n~p~n", [Str]) + end. + diff --git a/lib/kernel/test/socket_test_ttest.hrl b/lib/kernel/test/socket_test_ttest.hrl new file mode 100644 index 0000000000..1a004a9a7a --- /dev/null +++ b/lib/kernel/test/socket_test_ttest.hrl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_ttest). +-define(socket_test_ttest, true). + +-define(TTEST_TAG, 42). +-define(TTEST_TYPE_REQUEST, 101). +-define(TTEST_TYPE_REPLY, 102). + +-define(SECS(I), timer:seconds(I)). + +-define(SLEEP(T), receive after T -> ok end). + +-endif. % -ifdef(socket_test_ttest). diff --git a/lib/kernel/test/socket_test_ttest_client.hrl b/lib/kernel/test/socket_test_ttest_client.hrl new file mode 100644 index 0000000000..84e736cc34 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_client.hrl @@ -0,0 +1,141 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_ttest_client). +-define(socket_test_ttest_client, true). + +-define(MSG_ID_DEFAULT, 2). +-define(RUNTIME_DEFAULT, ?SECS(10)). +-define(MAX_ID, 16#FFFFFFFF). + +-define(MSG_DATA1, <<"This is test data 0123456789 0123456789 0123456789">>). +-define(MSG_DATA2, <<"This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789">>). +-define(MSG_DATA3, <<"This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789">>). + + +-endif. % -ifdef(socket_test_ttest_client). diff --git a/lib/kernel/test/socket_test_ttest_lib.erl b/lib/kernel/test/socket_test_ttest_lib.erl new file mode 100644 index 0000000000..ebce16dcfa --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_lib.erl @@ -0,0 +1,127 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_lib). + +-compile({no_auto_import, [error/2]}). + +-export([ + t/0, tdiff/2, + formated_timestamp/0, format_timestamp/1, + format_time/1, + + formated_process_stats/1, formated_process_stats/2, + + format/2, + error/1, error/2, + info/1, info/2 + ]). + +%% ========================================================================== + +t() -> + os:timestamp(). + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour,Min,Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", + [Hour, Min, Sec, round(N3/1000)]), + lists:flatten(FormatTS). + +%% Time is always in number os ms (milli seconds) +%% At some point, we should convert this to a more readable format... +format_time(T) when (T < 1000) -> + format("~w ms", [T]); +format_time(T) -> + format("~w sec (~w ms)", [T div 1000, T]). + + +formated_process_stats(Pid) -> + formated_process_stats("", Pid). + +formated_process_stats(Prefix, Pid) when is_list(Prefix) andalso is_pid(Pid) -> + try + begin + TotHeapSz = pi(Pid, total_heap_size), + HeapSz = pi(Pid, heap_size), + StackSz = pi(Pid, stack_size), + Reds = pi(Pid, reductions), + GCInfo = pi(Pid, garbage_collection), + MinBinVHeapSz = proplists:get_value(min_bin_vheap_size, GCInfo), + MinHeapSz = proplists:get_value(min_heap_size, GCInfo), + MinGCS = proplists:get_value(minor_gcs, GCInfo), + format("~n ~sTotal Heap Size: ~p" + "~n ~sHeap Size: ~p" + "~n ~sStack Size: ~p" + "~n ~sReductions: ~p" + "~n ~s[GC] Min Bin VHeap Size: ~p" + "~n ~s[GC] Min Heap Size: ~p" + "~n ~s[GC] Minor GCS: ~p", + [Prefix, TotHeapSz, + Prefix, HeapSz, + Prefix, StackSz, + Prefix, Reds, + Prefix, MinBinVHeapSz, + Prefix, MinHeapSz, + Prefix, MinGCS]) + end + catch + _:_:_ -> + "" + end. + + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + + + +%% ========================================================================== + +format(F, A) -> + lists:flatten(io_lib:format(F, A)). + +error(F) -> + error(F, []). + +error(F, A) -> + print(get(sname), "<ERROR> " ++ F, A). + +info(F) -> + info(F, []). + +info(F, A) -> + print(get(sname), "<INFO> " ++ F, A). + +print(undefined, F, A) -> + print("- ", F, A); +print(Prefix, F, A) -> + io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). + diff --git a/lib/kernel/test/socket_test_ttest_tcp_client.erl b/lib/kernel/test/socket_test_ttest_tcp_client.erl new file mode 100644 index 0000000000..f28819ca69 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_client.erl @@ -0,0 +1,687 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% ========================================================================== +%% +%% This is the "simple" client using gen_tcp. The client is supposed to be +%% as simple as possible in order to incur as little overhead as possible. +%% +%% There are three ways to run the client: active, passive or active-once. +%% +%% The client is the entity that controls the test, timing and counting. +%% +%% ========================================================================== +%% +%% Before the actual test starts, the client performs a "warmup". +%% The warmup has two functions. First, to ensure that everything is "loaded" +%% and, second, to calculate an approximate roundtrip time, in order to +%% "know" how many iterations we should make (to run for the expected time). +%% This is not intended to be exact, but just to ensure that all tests take +%% approx the same time to run. +%% +%% ========================================================================== + +-module(socket_test_ttest_tcp_client). + +-export([ + %% These are for the test suite + start_monitor/5, start_monitor/6, start_monitor/8, + + %% These are for starting in a shell when run "manually" + start/3, start/4, start/6, start/7, + stop/1 + ]). + +%% Internal exports +-export([ + do_start/9 + ]). + +-include_lib("kernel/include/inet.hrl"). +-include("socket_test_ttest.hrl"). +-include("socket_test_ttest_client.hrl"). + +-define(RECV_TIMEOUT, 10000). +-define(MAX_OUTSTANDING_DEFAULT_1, 100). +-define(MAX_OUTSTANDING_DEFAULT_2, 10). +-define(MAX_OUTSTANDING_DEFAULT_3, 3). + +-define(LIB, socket_test_ttest_lib). +-define(I(F), ?LIB:info(F)). +-define(I(F,A), ?LIB:info(F, A)). +-define(E(F,A), ?LIB:error(F, A)). +-define(F(F,A), ?LIB:format(F, A)). +-define(FORMAT_TIME(T), ?LIB:format_time(T)). +-define(T(), ?LIB:t()). +-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)). + +-type active() :: once | boolean(). +-type msg_id() :: 1..3. +-type max_outstanding() :: pos_integer(). +-type runtime() :: pos_integer(). + + +%% ========================================================================== + +start_monitor(Node, Notify, Transport, ServerInfo, Active) -> + start_monitor(Node, Notify, Transport, ServerInfo, Active, ?MSG_ID_DEFAULT). + +start_monitor(Node, Notify, Transport, ServerInfo, Active, 1 = MsgID) -> + start_monitor(Node, Notify, Transport, ServerInfo, Active, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start_monitor(Node, Notify, Transport, ServerInfo, Active, 2 = MsgID) -> + start_monitor(Node, Notify, Transport, ServerInfo, Active, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start_monitor(Node, Notify, Transport, ServerInfo, Active, 3 = MsgID) -> + start_monitor(Node, Notify, Transport, ServerInfo, Active, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +start_monitor(Node, Notify, Transport, ServerInfo, Active, + MsgID, MaxOutstanding, RunTime) + when (Node =/= node()) -> + Args = [false, + self(), Notify, + Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime], + case rpc:call(Node, ?MODULE, do_start, Args) of + {badrpc, _} = Reason -> + {error, Reason}; + {ok, Pid} when is_pid(Pid) -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef}}; + {error, _} = ERROR -> + ERROR + end; +start_monitor(_, Notify, Transport, ServerInfo, Active, + MsgID, MaxOutstanding, RunTime) -> + case do_start(false, + self(), Notify, + Transport, Active, ServerInfo, + MsgID, MaxOutstanding, RunTime) of + {ok, Pid} -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef}}; + {error, _} = ERROR -> + ERROR + end. + + +start(Transport, ServerInfo, Active) -> + start(Transport, ServerInfo, Active, ?MSG_ID_DEFAULT). + +start(Transport, ServerInfo, Active, 1 = MsgID) -> + start(false, + Transport, ServerInfo, Active, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start(Transport, ServerInfo, Active, 2 = MsgID) -> + start(false, + Transport, ServerInfo, Active, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start(Transport, ServerInfo, Active, 3 = MsgID) -> + start(false, + Transport, ServerInfo, Active, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +start(Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) -> + start(false, + Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime). + +start(Quiet, Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) -> + Notify = fun(R) -> present_results(R) end, + do_start(Quiet, + self(), Notify, + Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime). + + +-spec do_start(Quiet, + Parent, + Notify, + Transport, + ServerInfo, + Active, + MsgID, + MaxOutstanding, + RunTime) -> {ok, Pid} | {error, Reason} when + Quiet :: boolean(), + Parent :: pid(), + Notify :: function(), + Transport :: atom() | tuple(), + ServerInfo :: {inet:ip_address(), inet:port_number()} | string(), + Active :: active(), + MsgID :: msg_id(), + MaxOutstanding :: max_outstanding(), + RunTime :: runtime(), + Pid :: pid(), + Reason :: term(). + +do_start(Quiet, + Parent, Notify, + Transport, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) + when is_boolean(Quiet) andalso + is_pid(Parent) andalso + is_function(Notify) andalso + (is_atom(Transport) orelse is_tuple(Transport)) andalso + (is_boolean(Active) orelse (Active =:= once)) andalso + (is_tuple(ServerInfo) orelse is_list(ServerInfo)) andalso + (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso + (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso + (is_integer(RunTime) andalso (RunTime > 0)) -> + Starter = self(), + Init = fun() -> put(sname, "client"), + init(Quiet, + Starter, + Parent, + Notify, + Transport, Active, ServerInfo, + MsgID, MaxOutstanding, RunTime) + end, + {Pid, MRef} = spawn_monitor(Init), + receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; + {?MODULE, Pid, ok} -> + erlang:demonitor(MRef), + {ok, Pid}; + {?MODULE, Pid, {error, _} = ERROR} -> + erlang:demonitor(MRef, [flush]), + ERROR + end. + + +%% We should not normally stop this (it terminates when its done). +stop(Pid) when is_pid(Pid) -> + req(Pid, stop). + + +%% ========================================================================== + +init(Quiet, + Starter, + Parent, Notify, + Transport, Active, ServerInfo, + MsgID, MaxOutstanding, RunTime) -> + if + not Quiet -> + ?I("init with" + "~n Transport: ~p" + "~n Active: ~p" + "~n ServerInfo: ~s" + "~n Msg ID: ~p (=> 16 + ~w bytes)" + "~n Max Outstanding: ~p" + "~n (Suggested) Run Time: ~p ms", + [Transport, Active, + case ServerInfo of + {Addr, Port} -> + ?F("Addr: ~s, Port: ~w", [inet:ntoa(Addr), Port]); + Path -> + Path + end, + MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]); + true -> + ok + end, + {Mod, Connect} = process_transport(Transport), + case Connect(ServerInfo) of + {ok, Sock} -> + if not Quiet -> ?I("connected"); + true -> ok + end, + Starter ! {?MODULE, self(), ok}, + initial_activation(Mod, Sock, Active), + Results = loop(#{quiet => Quiet, + slogan => run, + runtime => RunTime, + start => ?T(), + parent => Parent, + mod => Mod, + sock => Sock, + active => Active, + msg_data => which_msg_data(MsgID), + outstanding => 0, + max_outstanding => MaxOutstanding, + sid => 1, + rid => 1, + scnt => 0, + rcnt => 0, + bcnt => 0, + num => undefined, + acc => <<>>}), + Notify(Results), + (catch Mod:close(Sock)), + exit(normal); + {error, Reason} -> + ?E("connect failed: ~p" + "~n ~p", [Reason, ServerInfo]), + exit({connect, Reason, ServerInfo}) + end. + +process_transport(Mod) when is_atom(Mod) -> + %% In this case we assume it to be a plain tcp socket + {Mod, fun({A, P}) -> Mod:connect(A, P) end}; +process_transport({Mod, #{domain := Domain} = Opts}) -> + Connect = + case Domain of + local -> fun(Path) -> Mod:connect(Path, Opts) end; + _ -> fun({A, P}) -> Mod:connect(A, P, Opts) end + end, + {Mod, Connect}. + + +which_msg_data(1) -> ?MSG_DATA1; +which_msg_data(2) -> ?MSG_DATA2; +which_msg_data(3) -> ?MSG_DATA3. + + +present_results(#{status := ok, + runtime := RunTime, + bcnt := ByteCnt, + cnt := NumIterations}) -> + ?I("Results: " + "~n Run Time: ~s" + "~n ByteCnt: ~s" + "~n NumIterations: ~s", + [?FORMAT_TIME(RunTime), + if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) -> + ?F("~w, ~w", [ByteCnt, RunTime]); + true -> + ?F("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + ?F("~p => ~p iterations / ms", + [NumIterations, NumIterations div RunTime]) + end]), + ok; +present_results(#{status := Failure, + runtime := RunTime, + sid := SID, + rid := RID, + scnt := SCnt, + rcnt := RCnt, + bcnt := BCnt, + num := Num}) -> + ?I("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + ?FORMAT_TIME(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]). + + + +loop(#{runtime := RunTime} = State) -> + erlang:start_timer(RunTime, self(), stop), + try do_loop(State) + catch + throw:Results -> + Results + end. + +do_loop(State) -> + do_loop( handle_message( msg_exchange(State) ) ). + +msg_exchange(#{rcnt := Num, num := Num} = State) -> + finish(ok, State); +msg_exchange(#{scnt := Num, num := Num} = State) -> + %% We are done sending more requests - now we will just await + %% the replies for the (still) outstanding replies. + msg_exchange( recv_reply(State) ); +msg_exchange(#{outstanding := Outstanding, + max_outstanding := MaxOutstanding} = State) + when (Outstanding < MaxOutstanding) -> + msg_exchange( send_request(State) ); +msg_exchange(State) -> + send_request( recv_reply(State) ). + + +finish(ok, + #{start := Start, bcnt := BCnt, num := Num}) -> + Stop = ?T(), + throw(#{status => ok, + runtime => ?TDIFF(Start, Stop), + bcnt => BCnt, + cnt => Num}); +finish(Reason, + #{start := Start, + sid := SID, rid := RID, + scnt := SCnt, rcnt := RCnt, bcnt := BCnt, + num := Num}) -> + Stop = ?T(), + throw(#{status => Reason, + runtime => ?TDIFF(Start, Stop), + sid => SID, + rid => RID, + scnt => SCnt, + rcnt => RCnt, + bcnt => BCnt, + num => Num}). + +send_request(#{mod := Mod, + sock := Sock, + sid := ID, + scnt := Cnt, + outstanding := Outstanding, + max_outstanding := MaxOutstanding, + msg_data := Data} = State) + when (MaxOutstanding > Outstanding) -> + SZ = size(Data), + Req = <<?TTEST_TAG:32, + ?TTEST_TYPE_REQUEST:32, + ID:32, + SZ:32, + Data/binary>>, + case Mod:send(Sock, Req) of + ok -> + State#{sid => next_id(ID), + scnt => Cnt + 1, + outstanding => Outstanding + 1}; + {error, Reason} -> + ?E("Failed sending request: ~p", [Reason]), + exit({send, Reason}) + end; +send_request(State) -> + State. + + + +recv_reply(#{mod := Mod, + sock := Sock, + rid := ID, + active := false, + bcnt := BCnt, + rcnt := Cnt, + outstanding := Outstanding} = State) -> + case recv_reply_message1(Mod, Sock, ID) of + {ok, MsgSz} -> + State#{rid => next_id(ID), + bcnt => BCnt + MsgSz, + rcnt => Cnt + 1, + outstanding => Outstanding - 1}; + + {error, timeout} -> + ?I("receive timeout"), + State; + + {error, Reason} -> + finish(Reason, State) + end; +recv_reply(#{mod := Mod, + sock := Sock, + rid := ID, + active := Active, + bcnt := BCnt, + scnt := SCnt, + rcnt := RCnt, + outstanding := Outstanding, + acc := Acc} = State) -> + case recv_reply_message2(Mod, Sock, ID, Acc) of + {ok, {MsgSz, NewAcc}} when is_integer(MsgSz) andalso is_binary(NewAcc) -> + maybe_activate(Mod, Sock, Active), + State#{rid => next_id(ID), + bcnt => BCnt + MsgSz, + rcnt => RCnt + 1, + outstanding => Outstanding - 1, + acc => NewAcc}; + + ok -> + State; + + {error, stop} -> + ?I("receive [~w] -> stop", [Active]), + %% This will have the effect that no more requests are sent... + State#{num => SCnt, stop_started => ?T()}; + + {error, timeout} -> + ?I("receive[~w] -> timeout", [Active]), + State; + + {error, Reason} -> + finish(Reason, State) + end. + + +%% This function reads exactly one (reply) message. No more no less. +recv_reply_message1(Mod, Sock, ID) -> + case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of + {ok, <<?TTEST_TAG:32, + ?TTEST_TYPE_REPLY:32, + ID:32, + SZ:32>> = Hdr} -> + %% Receive the ping-pong reply boby + case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of + {ok, Data} when (size(Data) =:= SZ) -> + {ok, size(Hdr) + size(Data)}; + {error, Reason2} -> + ?E("Failed reading body: " + "~n ~p: ~p", [Reason2]), + {error, {recv_body, Reason2}} + end; + + {ok, <<BadTag:32, + BadType:32, + BadID:32, + BadSZ:32>>} -> + {error, {invalid_hdr, + {?TTEST_TAG, BadTag}, + {?TTEST_TYPE_REPLY, BadType}, + {ID, BadID}, + BadSZ}}; + {ok, _InvHdr} -> + {error, invalid_hdr}; + + {error, Reason1} -> + ?E("Feiled reading header: " + "~n ~p", [Reason1]), + {error, {recv_hdr, Reason1}} + end. + + +%% This function first attempts to process the data we have already +%% accumulated. If that is not enough for a (complete) reply, it +%% will attempt to receive more. +recv_reply_message2(Mod, Sock, ID, Acc) -> + case process_acc_data(ID, Acc) of + ok -> + %% No or insufficient data, so get more + recv_reply_message3(Mod, Sock, ID, Acc); + + {ok, _} = OK -> % We already had a reply accumulated - no need to read more + OK; + + {error, _} = ERROR -> + ERROR + end. + +%% This function receives a "chunk" of data, then it tries to extract +%% one (reply) message from the accumulated and new data (combined). +recv_reply_message3(_Mod, Sock, ID, Acc) -> + receive + {timeout, _TRef, stop} -> + {error, stop}; + + {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + {error, closed}; + + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + {error, Reason}; + + {Tag, Sock, Msg} when (Tag =:= tcp) orelse + (Tag =:= socket) -> + process_acc_data(ID, <<Acc/binary, Msg/binary>>) + + after ?RECV_TIMEOUT -> + ?I("timeout when" + "~n ID: ~p" + "~n size(Acc): ~p", + [ID, size(Acc)]), + %% {error, timeout} + recv_reply_message3(_Mod, Sock, ID, Acc) + end. + + +process_acc_data(ID, <<?TTEST_TAG:32, + ?TTEST_TYPE_REPLY:32, + ID:32, + SZ:32, + Data/binary>>) when (SZ =< size(Data)) -> + <<_Body:SZ/binary, Rest/binary>> = Data, + {ok, {4*4+SZ, Rest}}; +process_acc_data(ID, <<BadTag:32, + BadType:32, + BadID:32, + BadSZ:32, + _Data/binary>>) + when ((BadTag =/= ?TTEST_TAG) orelse + (BadType =/= ?TTEST_TYPE_REPLY) orelse + (BadID =/= ID)) -> + {error, {invalid_hdr, + {?TTEST_TAG, BadTag}, + {?TTEST_TYPE_REPLY, BadType}, + {ID, BadID}, + BadSZ}}; +%% Not enough for an entire (reply) message +process_acc_data(_ID, _Data) -> + ok. + + +handle_message(#{quiet := Quiet, + parent := Parent, sock := Sock, scnt := SCnt} = State) -> + receive + {timeout, _TRef, stop} -> + if not Quiet -> ?I("STOP"); + true -> ok + end, + %% This will have the effect that no more requests are sent... + State#{num => SCnt, stop_started => ?T()}; + + {?MODULE, Ref, Parent, stop} -> + %% This *aborts* the test + reply(Parent, Ref, ok), + exit(normal); + + %% Only when active + {TagClosed, Sock, Reason} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + %% We should never get this (unless the server crashed) + exit({closed, Reason}); + + %% Only when active + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + exit({error, Reason}) + + after 0 -> + State + end. + + +initial_activation(_Mod, _Sock, false = _Active) -> + ok; +initial_activation(Mod, Sock, Active) -> + Mod:active(Sock, Active). + + +maybe_activate(Mod, Sock, once = Active) -> + Mod:active(Sock, Active); +maybe_activate(_, _, _) -> + ok. + + +%% ========================================================================== + +req(Pid, Req) -> + Ref = make_ref(), + Pid ! {?MODULE, Ref, Pid, Req}, + receive + {'EXIT', Pid, Reason} -> + {error, {exit, Reason}}; + {?MODULE, Ref, Reply} -> + Reply + end. + +reply(Pid, Ref, Reply) -> + Pid ! {?MODULE, Ref, Reply}. + + +%% ========================================================================== + +next_id(ID) when (ID < ?MAX_ID) -> + ID + 1; +next_id(_) -> + 1. + + +%% ========================================================================== + +%% t() -> +%% os:timestamp(). + +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp({_N1, _N2, N3} = TS) -> +%% {_Date, Time} = calendar:now_to_local_time(TS), +%% {Hour,Min,Sec} = Time, +%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", +%% [Hour, Min, Sec, round(N3/1000)]), +%% lists:flatten(FormatTS). + +%% %% Time is always in number os ms (milli seconds) +%% format_time(T) -> +%% f("~p", [T]). + + +%% ========================================================================== + +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). + +%% %% e(F) -> +%% %% i("<ERROR> " ++ F). + +%% e(F, A) -> +%% p(get(sname), "<ERROR> " ++ F, A). + +%% i(F) -> +%% i(F, []). + +%% i(F, A) -> +%% p(get(sname), "<INFO> " ++ F, A). + +%% p(undefined, F, A) -> +%% p("- ", F, A); +%% p(Prefix, F, A) -> +%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). diff --git a/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl new file mode 100644 index 0000000000..65a3a94d38 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_client_gen.erl @@ -0,0 +1,49 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_client_gen). + +-export([ + start/2, start/3, start/5, start/6, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). + +start(ServerInfo, Active) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, ServerInfo, Active). + +start(ServerInfo, Active, MsgID) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, ServerInfo, Active, MsgID). + +start(ServerInfo, Active, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start(false, + ?TRANSPORT_MOD, + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime). + +start(Quiet, ServerInfo, Active, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start(Quiet, + ?TRANSPORT_MOD, + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime). + +stop(Pid) -> + socket_test_ttest_tcp_client:stop(Pid). diff --git a/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl new file mode 100644 index 0000000000..2fb1242028 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_client_socket.erl @@ -0,0 +1,116 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_client_socket). + +-export([ + start/4, start/5, start/7, start/8, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). +-define(MOD(D, A, M), {?TRANSPORT_MOD, #{domain => D, + async => A, + method => M}}). + +start(Method, Async, Active, ServerInfo) + when is_list(ServerInfo) -> + Domain = local, + socket_test_ttest_tcp_client:start_monitor(?MOD(Domain, Async, Method), + ServerInfo, Active); +start(Method, Async, Active, ServerInfo = {Addr, _}) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + Domain = inet, + socket_test_ttest_tcp_client:start_monitor(?MOD(Domain, Async, Method), + ServerInfo, Active); +start(Method, Async, Active, ServerInfo = {Addr, _}) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + Domain = inet6, + socket_test_ttest_tcp_client:start_monitor(?MOD(Domain, Async, Method), + ServerInfo, Active). + +start(Method, Async, Active, ServerInfo, MsgID) + when is_list(ServerInfo) -> + %% This is just a simplification + Domain = local, + socket_test_ttest_tcp_client:start(?MOD(Domain, Async, Method), + ServerInfo, Active, MsgID); +start(Method, Async, Active, ServerInfo = {Addr, _}, MsgID) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + %% This is just a simplification + Domain = inet, + socket_test_ttest_tcp_client:start(?MOD(Domain, Async, Method), + Active, ServerInfo, MsgID); +start(Method, Async, Active, ServerInfo = {Addr, _}, MsgID) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + Domain = inet6, + socket_test_ttest_tcp_client:start(?MOD(Domain, Async, Method), + ServerInfo, Active, MsgID). + +start(Method, Async, Active, ServerInfo, MsgID, MaxOutstanding, RunTime) + when is_list(ServerInfo) -> + Domain = local, + socket_test_ttest_tcp_client:start(false, + ?MOD(Domain, Async, Method), + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime); +start(Method, Async, Active, ServerInfo = {Addr, _}, + MsgID, MaxOutstanding, RunTime) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + Domain = inet, + socket_test_ttest_tcp_client:start(false, + ?MOD(Domain, Async, Method), + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime); +start(Method, Async, Active, ServerInfo = {Addr, _}, + MsgID, MaxOutstanding, RunTime) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + Domain = inet6, + socket_test_ttest_tcp_client:start(false, + ?MOD(Domain, Async, Method), + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime). + +start(Quiet, Async, Active, Method, ServerInfo, MsgID, MaxOutstanding, RunTime) + when is_list(ServerInfo) -> + Domain = local, + socket_test_ttest_tcp_client:start(Quiet, + ?MOD(Domain, Async, Method), + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime); +start(Quiet, Async, Active, Method, ServerInfo = {Addr, _}, + MsgID, MaxOutstanding, RunTime) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + Domain = inet, + socket_test_ttest_tcp_client:start(Quiet, + ?MOD(Domain, Async, Method), + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime); +start(Quiet, Async, Active, Method, ServerInfo = {Addr, _}, + MsgID, MaxOutstanding, RunTime) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + Domain = inet6, + socket_test_ttest_tcp_client:start(Quiet, + ?MOD(Domain, Async, Method), + ServerInfo, Active, + MsgID, MaxOutstanding, RunTime). + +stop(Pid) -> + socket_test_ttest_client:stop(Pid). diff --git a/lib/kernel/test/socket_test_ttest_tcp_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_gen.erl new file mode 100644 index 0000000000..5d20e49359 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_gen.erl @@ -0,0 +1,137 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_gen). + +-export([ + accept/1, accept/2, + active/2, + close/1, + connect/2, connect/3, + controlling_process/2, + listen/0, listen/1, listen/2, + peername/1, + port/1, + recv/2, recv/3, + send/2, + shutdown/2, + sockname/1 + ]). + + +-define(LIB, socket_test_lib). + +%% ========================================================================== + +accept(Sock) -> + case gen_tcp:accept(Sock) of + {ok, NewSock} -> + {ok, NewSock}; + {error, _} = ERROR -> + ERROR + end. + +accept(Sock, Timeout) -> + case gen_tcp:accept(Sock, Timeout) of + {ok, NewSock} -> + {ok, NewSock}; + {error, _} = ERROR -> + ERROR + end. + + +active(Sock, NewActive) + when (is_boolean(NewActive) orelse (NewActive =:= once)) -> + inet:setopts(Sock, [{active, NewActive}]). + + +close(Sock) -> + gen_tcp:close(Sock). + + +connect(Addr, Port) -> + Opts = [binary, {packet, raw}, {active, false}, {buffer, 32*1024}], + do_connect(Addr, Port, Opts). + +connect(Addr, Port, #{domain := Domain}) -> + Opts = [Domain, binary, {packet, raw}, {active, false}, {buffer, 32*1024}], + do_connect(Addr, Port, Opts). + +do_connect(Addr, Port, Opts) -> + case gen_tcp:connect(Addr, Port, Opts) of + {ok, Sock} -> + {ok, Sock}; + {error, _} = ERROR -> + ERROR + end. + +controlling_process(Sock, NewPid) -> + gen_tcp:controlling_process(Sock, NewPid). + + +%% Create a listen socket +listen() -> + listen(0). + +listen(Port) -> + listen(Port, #{domain => inet}). + +listen(Port, #{domain := Domain}) when is_integer(Port) andalso (Port >= 0) -> + case ?LIB:which_local_host_info(Domain) of + {ok, #{addr := Addr}} -> + Opts = [Domain, + binary, {ip, Addr}, {packet, raw}, {active, false}, + {buffer, 32*1024}], + gen_tcp:listen(Port, Opts); + {error, _} = ERROR -> + ERROR + end. + + +peername(Sock) -> + inet:peername(Sock). + + +port(Sock) -> + inet:port(Sock). + + +recv(Sock, Length) -> + gen_tcp:recv(Sock, Length). +recv(Sock, Length, Timeout) -> + gen_tcp:recv(Sock, Length, Timeout). + + +send(Sock, Data) -> + gen_tcp:send(Sock, Data). + + +shutdown(Sock, How) -> + gen_tcp:shutdown(Sock, How). + + +sockname(Sock) -> + inet:sockname(Sock). + + +%% ========================================================================== + + + diff --git a/lib/kernel/test/socket_test_ttest_tcp_server.erl b/lib/kernel/test/socket_test_ttest_tcp_server.erl new file mode 100644 index 0000000000..2394dc7924 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_server.erl @@ -0,0 +1,684 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% ========================================================================== +%% +%% This is the "simple" server using gen_tcp. The server is supposed to be +%% as simple as possible in order to incur as little overhead as possible. +%% +%% There are three ways to run the server: active, passive or active-once. +%% +%% The server does only two things; accept connnections and then reply +%% to requests (actually the handler(s) does that). No timing or counting. +%% That is all done by the clients. +%% +%% ========================================================================== + +-module(socket_test_ttest_tcp_server). + +-export([ + %% This are for the test suite + start_monitor/3, + + %% This are for starting in a shell when run "manually" + start/2, + + stop/1 + ]). + +%% Internal exports +-export([ + do_start/3 + ]). + +-include_lib("kernel/include/inet.hrl"). +-include("socket_test_ttest.hrl"). + +-define(ACC_TIMEOUT, 5000). +-define(RECV_TIMEOUT, 5000). + +-define(LIB, socket_test_ttest_lib). +-define(I(F), ?LIB:info(F)). +-define(I(F,A), ?LIB:info(F, A)). +-define(E(F,A), ?LIB:error(F, A)). +-define(F(F,A), ?LIB:format(F, A)). +-define(FORMAT_TIME(T), ?LIB:format_time(T)). +-define(T(), ?LIB:t()). +-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)). + + +%% ========================================================================== + +start_monitor(Node, Transport, Active) when (Node =/= node()) -> + case rpc:call(Node, ?MODULE, do_start, [self(), Transport, Active]) of + {badrpc, _} = Reason -> + {error, Reason}; + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {{Pid, MRef}, AddrPort}}; + {error, _} = ERROR -> + ERROR + end; +start_monitor(_, Transport, Active) -> + case do_start(self(), Transport, Active) of + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {{Pid, MRef}, AddrPort}}; + {error, _} = ERROR -> + ERROR + end. + + + +start(Transport, Active) -> + do_start(self(), Transport, Active). + +%% Note that the Async option is actually only "used" for the +%% socket transport module (it details how to implement the +%% active feature). +do_start(Parent, Transport, Active) + when is_pid(Parent) andalso + (is_atom(Transport) orelse is_tuple(Transport)) andalso + (is_boolean(Active) orelse (Active =:= once)) -> + Starter = self(), + ServerInit = fun() -> + put(sname, "server"), + server_init(Starter, Parent, Transport, Active) + end, + {Pid, MRef} = spawn_monitor(ServerInit), + receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; + {?MODULE, Pid, {ok, AddrPort}} -> + erlang:demonitor(MRef), + {ok, {Pid, AddrPort}}; + {?MODULE, Pid, {error, _} = ERROR} -> + erlang:demonitor(MRef, [flush]), + ERROR + end. + + +stop(Pid) when is_pid(Pid) -> + req(Pid, stop). + + +%% ========================================================================== + +server_init(Starter, Parent, Transport, Active) -> + ?I("init with" + "~n Transport: ~p" + "~n Active: ~p", [Transport, Active]), + {Mod, Listen, StatsInterval} = process_transport(Transport, Active), + case Listen(0) of + {ok, LSock} -> + case Mod:port(LSock) of + {ok, PortOrPath} -> + Result = + if + is_integer(PortOrPath) -> + %% This is just for convenience + case Mod:sockname(LSock) of + {ok, {Addr, _}} -> + ?I("listening on:" + "~n Addr: ~p (~s)" + "~n Port: ~w" + "~n", [Addr, + inet:ntoa(Addr), + PortOrPath]), + {Addr, PortOrPath}; + {error, SNReason} -> + exit({sockname, SNReason}) + end; + is_list(PortOrPath) -> + ?I("listening on:" + "~n Path: ~s" + "~n", [PortOrPath]), + PortOrPath + end, + Starter ! {?MODULE, self(), {ok, Result}}, + server_loop(#{parent => Parent, + mod => Mod, + active => Active, + lsock => LSock, + port_or_path => PortOrPath, + handlers => [], + stats_interval => StatsInterval, + %% Accumulation + runtime => 0, + mcnt => 0, + bcnt => 0, + hcnt => 0 + }); + {error, PReason} -> + (catch Mod:close(LSock)), + exit({port, PReason}) + end; + {error, LReason} -> + exit({listen, LReason}) + end. + +process_transport(Mod, _) when is_atom(Mod) -> + {Mod, fun(Port) -> Mod:listen(Port) end, infinity}; +process_transport({Mod, #{stats_interval := T} = Opts}, Active) + when (Active =/= false) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts#{stats_to => self()}) end, T}; +process_transport({Mod, #{stats_interval := T} = Opts}, _Active) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts) end, T}; +process_transport({Mod, Opts}, _Active) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts) end, infinity}. + + + +server_loop(State) -> + server_loop( server_handle_message( server_accept(State, ?ACC_TIMEOUT), 0) ). + +server_accept(#{mod := Mod, lsock := LSock} = State, Timeout) -> + case Mod:accept(LSock, Timeout) of + {ok, Sock} -> + server_handle_accepted(State, Sock); + {error, timeout} when (Timeout =/= nowait) -> + State; + {error, AReason} -> + (catch Mod:close(LSock)), + exit({accept, AReason}) + end. + +%% server_accept(#{mod := Mod, +%% lsock := LSock} = State) -> +%% case Mod:accept(LSock, ?ACC_TIMEOUT) of +%% {ok, Sock} -> +%% server_handle_accepted(State, Sock); +%% {error, timeout} -> +%% State; +%% {error, AReason} -> +%% (catch Mod:close(LSock)), +%% exit({accept, AReason}) +%% end. + +server_handle_accepted(#{mod := Mod, + lsock := LSock, + active := Active, + handlers := Handlers} = State, + Sock) -> + ?I("accepted connection from ~s", + [case Mod:peername(Sock) of + {ok, Peer} -> + format_peername(Peer); + {error, _} -> + "-" + end]), + {Pid, _} = handler_start(), + ?I("handler ~p started -> try transfer socket control", [Pid]), + case Mod:controlling_process(Sock, Pid) of + ok -> + maybe_start_stats_timer(State, Pid), + ?I("server-accept: handler ~p started", [Pid]), + handler_continue(Pid, Mod, Sock, Active), + Handlers2 = [Pid | Handlers], + State#{handlers => Handlers2}; + {error, CPReason} -> + (catch Mod:close(Sock)), + (catch Mod:close(LSock)), + exit({controlling_process, CPReason}) + end. + + +format_peername({Addr, Port}) -> + case inet:gethostbyaddr(Addr) of + {ok, #hostent{h_name = N}} -> + ?F("~s (~s:~w)", [N, inet:ntoa(Addr), Port]); + {error, _} -> + ?F("~p, ~p", [Addr, Port]) + end; +format_peername(Path) when is_list(Path) -> + Path. + +maybe_start_stats_timer(#{active := Active, stats_interval := Time}, Handler) + when (Active =/= false) andalso (is_integer(Time) andalso (Time > 0)) -> + start_stats_timer(Time, "handler", Handler); +maybe_start_stats_timer(_, _) -> + ok. + +start_stats_timer(Time, ProcStr, Pid) -> + erlang:start_timer(Time, self(), {stats, Time, ProcStr, Pid}). + +server_handle_message(#{mod := Mod, + lsock := LSock, + parent := Parent, + handlers := H} = State, Timeout) -> + receive + {timeout, _TRef, {stats, Interval, ProcStr, Pid}} -> + case server_handle_stats(ProcStr, Pid) of + ok -> + start_stats_timer(Interval, ProcStr, Pid); + skip -> + ok + end, + State; + + {?MODULE, Ref, Parent, stop} -> + reply(Parent, Ref, ok), + lists:foreach(fun(P) -> handler_stop(P) end, H), + (catch Mod:close(LSock)), + exit(normal); + + {'DOWN', _MRef, process, Pid, Reason} -> + server_handle_down(Pid, Reason, State) + + after Timeout -> + State + end. + +server_handle_stats(ProcStr, Pid) -> + case ?LIB:formated_process_stats(Pid) of + "" -> + skip; + FormatedStats -> + ?I("Statistics for ~s ~p:~s", [ProcStr, Pid, FormatedStats]), + ok + end. + + +server_handle_down(Pid, Reason, #{handlers := Handlers} = State) -> + case lists:delete(Pid, Handlers) of + Handlers -> + ?I("unknown process ~p died", [Pid]), + State; + Handlers2 -> + server_handle_handler_down(Pid, Reason, State#{handlers => Handlers2}) + end. + + +server_handle_handler_down(Pid, + {done, RunTime, MCnt, BCnt}, + #{runtime := AccRunTime, + mcnt := AccMCnt, + bcnt := AccBCnt, + hcnt := AccHCnt} = State) -> + AccRunTime2 = AccRunTime + RunTime, + AccMCnt2 = AccMCnt + MCnt, + AccBCnt2 = AccBCnt + BCnt, + AccHCnt2 = AccHCnt + 1, + MsgCount2Str = + fun(RT, ART, MC, AMC) when (RT > 0) -> + ?F("~w => ~w (~w) msgs / ms", [MC, MC div RT, AMC div ART]); + (_, _, MC, AMC) -> + ?F("~w (~w)", [MC, AMC]) + end, + ByteCount2Str = + fun(RT, ART, BC, ABC) when (RT > 0) -> + ?F("~w => ~w (~w) bytes / ms", [BC, BC div RT, ABC div ART]); + (_, _, BC, ABC) -> + ?F("~w", [BC, ABC]) + end, + ?I("handler ~p (~w) done: " + "~n Run Time: ~s" + "~n Message Count: ~s" + "~n Byte Count: ~s", + [Pid, AccHCnt2, + ?FORMAT_TIME(RunTime), + MsgCount2Str(RunTime, AccRunTime2, MCnt, AccMCnt2), + ByteCount2Str(RunTime, AccRunTime2, BCnt, AccBCnt2)]), + State#{runtime => AccRunTime2, + mcnt => AccMCnt2, + bcnt => AccBCnt2, + hcnt => AccHCnt2}; +server_handle_handler_down(Pid, Reason, State) -> + ?I("handler ~p terminated: " + "~n ~p", [Pid, Reason]), + State. + + + +%% ========================================================================== + +handler_start() -> + Self = self(), + HandlerInit = fun() -> put(sname, "handler"), handler_init(Self) end, + spawn_monitor(HandlerInit). + +handler_continue(Pid, Mod, Sock, Active) -> + req(Pid, {continue, Mod, Sock, Active}). + +handler_stop(Pid) -> + req(Pid, stop). + +handler_init(Parent) -> + ?I("starting"), + receive + {?MODULE, Ref, Parent, {continue, Mod, Sock, Active}} -> + ?I("received continue"), + reply(Parent, Ref, ok), + handler_initial_activation(Mod, Sock, Active), + handler_loop(#{parent => Parent, + mod => Mod, + sock => Sock, + active => Active, + start => ?T(), + mcnt => 0, + bcnt => 0, + last_reply => none, + acc => <<>>}) + + after 5000 -> + ?I("timeout when message queue: " + "~n ~p" + "~nwhen" + "~n Parent: ~p", [process_info(self(), messages), Parent]), + handler_init(Parent) + end. + +handler_loop(State) -> + handler_loop( handler_handle_message( handler_recv_message(State) ) ). + +%% When passive, we read *one* request and then attempt to reply to it. +handler_recv_message(#{mod := Mod, + sock := Sock, + active := false, + mcnt := MCnt, + bcnt := BCnt, + last_reply := LID} = State) -> + case handler_recv_message2(Mod, Sock) of + {ok, {MsgSz, ID, Body}} -> + handler_send_reply(Mod, Sock, ID, Body), + State#{mcnt => MCnt + 1, + bcnt => BCnt + MsgSz, + last_reply => ID}; + {error, closed} -> + handler_done(State); + {error, timeout} -> + ?I("timeout when: " + "~n MCnt: ~p" + "~n BCnt: ~p" + "~n LID: ~p", [MCnt, BCnt, LID]), + State + end; + + +%% When "active" (once or true), we receive one data "message", which may +%% contain any number of requests or only part of a request. Then we +%% process this data together with whatever we had "accumulated" from +%% prevous messages. Each request will be extracted and replied to. If +%% there is some data left, not enough for a complete request, we store +%% this in 'acc' (accumulate it). +handler_recv_message(#{mod := Mod, + sock := Sock, + active := Active, + mcnt := MCnt, + bcnt := BCnt, + last_reply := LID, + acc := Acc} = State) -> + case handler_recv_message3(Mod, Sock, Acc, LID) of + {ok, {MCnt2, BCnt2, LID2}, NewAcc} -> + handler_maybe_activate(Mod, Sock, Active), + State#{mcnt => MCnt + MCnt2, + bcnt => BCnt + BCnt2, + last_reply => LID2, + acc => NewAcc}; + + {error, closed} -> + if + (size(Acc) =:= 0) -> + handler_done(State); + true -> + ?E("client done with partial message: " + "~n Last Reply Sent: ~w" + "~n Message Count: ~w" + "~n Byte Count: ~w" + "~n Partial Message: ~w bytes", + [LID, MCnt, BCnt, size(Acc)]), + exit({closed_with_partial_message, LID}) + end; + + {error, timeout} -> + ?I("timeout when: " + "~n MCnt: ~p" + "~n BCnt: ~p" + "~n LID: ~p" + "~n size(Acc): ~p", [MCnt, BCnt, LID, size(Acc)]), + State + end. + +handler_process_data(Acc, Mod, Sock, LID) -> + handler_process_data(Acc, Mod, Sock, 0, 0, LID). + +%% Extract each complete request, one at a time. +handler_process_data(<<?TTEST_TAG:32, + ?TTEST_TYPE_REQUEST:32, + ID:32, + SZ:32, + Rest/binary>>, + Mod, Sock, + MCnt, BCnt, _LID) when (size(Rest) >= SZ) -> + <<Body:SZ/binary, Rest2/binary>> = Rest, + case handler_send_reply(Mod, Sock, ID, Body) of + ok -> + handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+16+SZ, ID); + {error, _} = ERROR -> + ERROR + end; +handler_process_data(Data, _Mod, _Sock, MCnt, BCnt, LID) -> + {ok, {MCnt, BCnt, LID}, Data}. + + +handler_recv_message2(Mod, Sock) -> + case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of + {ok, <<?TTEST_TAG:32, + ?TTEST_TYPE_REQUEST:32, + ID:32, + SZ:32>> = Hdr} -> + case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of + {ok, Body} when (SZ =:= size(Body)) -> + {ok, {size(Hdr) + size(Body), ID, Body}}; + {error, BReason} -> + ?E("failed reading body (~w) of message ~w:" + "~n ~p", [SZ, ID, BReason]), + exit({recv, body, ID, SZ, BReason}) + end; + {error, timeout} = ERROR -> + ERROR; + {error, closed} = ERROR -> + ERROR; + {error, HReason} -> + ?E("Failed reading header of message:" + "~n ~p", [HReason]), + exit({recv, header, HReason}) + end. + + +handler_recv_message3(Mod, Sock, Acc, LID) -> + receive + {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + {error, closed}; + + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + {error, Reason}; + + {Tag, Sock, Msg} when (Tag =:= tcp) orelse + (Tag =:= socket) -> + handler_process_data(<<Acc/binary, Msg/binary>>, Mod, Sock, LID) + + after ?RECV_TIMEOUT -> + {error, timeout} + end. + + + +handler_send_reply(Mod, Sock, ID, Data) -> + SZ = size(Data), + Msg = <<?TTEST_TAG:32, + ?TTEST_TYPE_REPLY:32, + ID:32, + SZ:32, + Data/binary>>, + case Mod:send(Sock, Msg) of + ok -> + ok; + {error, Reason} -> + (catch Mod:close(Sock)), + exit({send, Reason}) + end. + + +handler_done(State) -> + handler_done(State, ?T()). + +handler_done(#{start := Start, + mod := Mod, + sock := Sock, + mcnt := MCnt, + bcnt := BCnt}, Stop) -> + (catch Mod:close(Sock)), + exit({done, ?TDIFF(Start, Stop), MCnt, BCnt}). + + +handler_handle_message(#{parent := Parent} = State) -> + receive + {'EXIT', Parent, Reason} -> + exit({parent_exit, Reason}) + after 0 -> + State + end. + + +handler_initial_activation(_Mod, _Sock, false = _Active) -> + ok; +handler_initial_activation(Mod, Sock, Active) -> + Mod:active(Sock, Active). + + +handler_maybe_activate(Mod, Sock, once = Active) -> + Mod:active(Sock, Active); +handler_maybe_activate(_, _, _) -> + ok. + + + +%% ========================================================================== + +%% which_addr() -> +%% case inet:getifaddrs() of +%% {ok, IfAddrs} -> +%% which_addrs(inet, IfAddrs); +%% {error, Reason} -> +%% exit({getifaddrs, Reason}) +%% end. + +%% which_addrs(_Family, []) -> +%% exit({getifaddrs, not_found}); +%% which_addrs(Family, [{"lo", _} | IfAddrs]) -> +%% %% Skip +%% which_addrs(Family, IfAddrs); +%% which_addrs(Family, [{"docker" ++ _, _} | IfAddrs]) -> +%% %% Skip docker +%% which_addrs(Family, IfAddrs); +%% which_addrs(Family, [{"br-" ++ _, _} | IfAddrs]) -> +%% %% Skip docker +%% which_addrs(Family, IfAddrs); +%% which_addrs(Family, [{"en" ++ _, IfOpts} | IfAddrs]) -> +%% %% Maybe take this one +%% case which_addr(Family, IfOpts) of +%% {ok, Addr} -> +%% Addr; +%% error -> +%% which_addrs(Family, IfAddrs) +%% end; +%% which_addrs(Family, [{_IfName, IfOpts} | IfAddrs]) -> +%% case which_addr(Family, IfOpts) of +%% {ok, Addr} -> +%% Addr; +%% error -> +%% which_addrs(Family, IfAddrs) +%% end. + +%% which_addr(_, []) -> +%% error; +%% which_addr(inet, [{addr, Addr}|_]) +%% when is_tuple(Addr) andalso (size(Addr) =:= 4) -> +%% {ok, Addr}; +%% which_addr(inet6, [{addr, Addr}|_]) +%% when is_tuple(Addr) andalso (size(Addr) =:= 8) -> +%% {ok, Addr}; +%% which_addr(Family, [_|IfOpts]) -> +%% which_addr(Family, IfOpts). + + +%% ========================================================================== + +req(Pid, Req) -> + Ref = make_ref(), + Pid ! {?MODULE, Ref, self(), Req}, + receive + {'EXIT', Pid, Reason} -> + {error, {exit, Reason}}; + {?MODULE, Ref, Reply} -> + Reply + end. + +reply(Pid, Ref, Reply) -> + Pid ! {?MODULE, Ref, Reply}. + + +%% ========================================================================== + +%% t() -> +%% os:timestamp(). + +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp({_N1, _N2, N3} = TS) -> +%% {_Date, Time} = calendar:now_to_local_time(TS), +%% {Hour,Min,Sec} = Time, +%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", +%% [Hour, Min, Sec, round(N3/1000)]), +%% lists:flatten(FormatTS). + +%% %% Time is always in number os ms (milli seconds) +%% format_time(T) -> +%% f("~p", [T]). + + +%% ========================================================================== + +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). + +%% e(F, A) -> +%% p(get(sname), "<ERROR> " ++ F, A). + +%% i(F) -> +%% i(F, []). + +%% i(F, A) -> +%% p(get(sname), "<INFO> " ++ F, A). + +%% p(undefined, F, A) -> +%% p("- ", F, A); +%% p(Prefix, F, A) -> +%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). + diff --git a/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl b/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl new file mode 100644 index 0000000000..fdf40f1369 --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_server_gen.erl @@ -0,0 +1,39 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_server_gen). + +-export([ + start/1, start/2, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). +-define(MOD(D), {?TRANSPORT_MOD, #{domain => D}}). + +start(Active) -> + start(inet, Active). + +start(Domain, Active) -> + socket_test_ttest_tcp_server:start(?MOD(Domain), Active). + + +stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). diff --git a/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl new file mode 100644 index 0000000000..4045bf4e4e --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_server_socket.erl @@ -0,0 +1,46 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_server_socket). + +-export([ + start/4, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). +%% -define(MOD(M), {?TRANSPORT_MOD, #{async => false, +%% method => M, +%% stats_interval => 10000}}). +-define(MOD(D,M,A), {?TRANSPORT_MOD, #{domain => D, + async => A, + method => M}}). + +start(Method, Domain, Async, Active) -> + socket_test_ttest_tcp_server:start(?MOD(Domain, Method, Async), Active). + %% {ok, {Pid, AddrPort}} -> + %% MRef = erlang:monitor(process, Pid), + %% {ok, {Pid, MRef, AddrPort}}; + %% {error, _} = ERROR -> + %% ERROR + %% end. + +stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). diff --git a/lib/kernel/test/socket_test_ttest_tcp_socket.erl b/lib/kernel/test/socket_test_ttest_tcp_socket.erl new file mode 100644 index 0000000000..a1e08e605c --- /dev/null +++ b/lib/kernel/test/socket_test_ttest_tcp_socket.erl @@ -0,0 +1,724 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket_test_ttest_tcp_socket). + +-export([ + accept/1, accept/2, + active/2, + close/1, + connect/1, connect/2, connect/3, + controlling_process/2, + listen/0, listen/1, listen/2, + port/1, + peername/1, + recv/2, recv/3, + send/2, + shutdown/2, + sockname/1 + ]). + + +-define(LIB, socket_test_lib). + +-define(READER_RECV_TIMEOUT, 1000). + +-define(DATA_MSG(Sock, Method, Data), + {socket, + #{sock => Sock, reader => self(), method => Method}, + Data}). + +-define(CLOSED_MSG(Sock, Method), + {socket_closed, + #{sock => Sock, reader => self(), method => Method}}). + +-define(ERROR_MSG(Sock, Method, Reason), + {socket_error, + #{sock => Sock, reader => self(), method => Method}, + Reason}). + + +%% ========================================================================== + +%% This does not really work. Its just a placeholder for the time being... + +%% getopt(Sock, Opt) when is_atom(Opt) -> +%% socket:getopt(Sock, socket, Opt). + +%% setopt(Sock, Opt, Value) when is_atom(Opt) -> +%% socket:setopts(Sock, socket, Opt, Value). + + +%% ========================================================================== + +%% The way we use server async its no point in doing a async accept call +%% (we do never actually run the test with more than one client). +accept(#{sock := LSock, opts := #{async := Async, + method := Method} = Opts}) -> + case socket:accept(LSock) of + {ok, Sock} -> + Self = self(), + Reader = spawn(fun() -> + reader_init(Self, Sock, Async, false, Method) + end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}}; + {error, _} = ERROR -> + ERROR + end. + +%% If a timeout has been explictly specified, then we do not use +%% async here. We will pass it on to the reader process. +accept(#{sock := LSock, opts := #{async := Async, + method := Method} = Opts}, Timeout) -> + case socket:accept(LSock, Timeout) of + {ok, Sock} -> + Self = self(), + Reader = spawn(fun() -> + reader_init(Self, Sock, Async, false, Method) + end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}}; + {error, _} = ERROR -> + ERROR + end. + + +active(#{reader := Pid}, NewActive) + when (is_boolean(NewActive) orelse (NewActive =:= once)) -> + Pid ! {?MODULE, active, NewActive}, + ok. + + +close(#{sock := Sock, reader := Pid}) -> + Pid ! {?MODULE, stop}, + Unlink = case socket:sockname(Sock) of + {ok, #{family := local, path := Path}} -> + fun() -> os:cmd("unlink " ++ Path), ok end; + _ -> + fun() -> ok end + end, + Res = socket:close(Sock), + Unlink(), + Res. + +%% Create a socket and connect it to a peer +connect(ServerPath) when is_list(ServerPath) -> + Domain = local, + ClientPath = mk_unique_path(), + LocalSA = #{family => Domain, + path => ClientPath}, + ServerSA = #{family => Domain, path => ServerPath}, + Opts = #{domain => Domain, + proto => default, + method => plain}, + Cleanup = fun() -> os:cmd("unlink " ++ ClientPath), ok end, + do_connect(LocalSA, ServerSA, Cleanup, Opts). + +connect(Addr, Port) when is_tuple(Addr) andalso is_integer(Port) -> + Domain = inet, + LocalSA = any, + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + Opts = #{domain => Domain, + proto => tcp, + method => plain}, + Cleanup = fun() -> ok end, + do_connect(LocalSA, ServerSA, Cleanup, Opts); +connect(ServerPath, + #{domain := local = Domain} = Opts) + when is_list(ServerPath) -> + ClientPath = mk_unique_path(), + LocalSA = #{family => Domain, + path => ClientPath}, + ServerSA = #{family => Domain, + path => ServerPath}, + Cleanup = fun() -> os:cmd("unlink " ++ ClientPath), ok end, + do_connect(LocalSA, ServerSA, Cleanup, Opts#{proto => default}). + +connect(Addr, Port, #{domain := Domain} = Opts) -> + LocalSA = any, + ServerSA = #{family => Domain, + addr => Addr, + port => Port}, + Cleanup = fun() -> ok end, + do_connect(LocalSA, ServerSA, Cleanup, Opts#{proto => tcp}). + +do_connect(LocalSA, ServerSA, Cleanup, #{domain := Domain, + proto := Proto, + async := Async, + method := Method} = Opts) -> + try + begin + Sock = + case socket:open(Domain, stream, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({error, {open, OReason}}) + end, + case socket:bind(Sock, LocalSA) of + {ok, _} -> + ok; + {error, BReason} -> + (catch socket:close(Sock)), + Cleanup(), + throw({error, {bind, BReason}}) + end, + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, CReason} -> + (catch socket:close(Sock)), + Cleanup(), + throw({error, {connect, CReason}}) + end, + Self = self(), + Reader = spawn(fun() -> + reader_init(Self, Sock, Async, false, Method) + end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}} + end + catch + throw:ERROR:_ -> + ERROR + end. + +mk_unique_path() -> + [NodeName | _] = string:tokens(atom_to_list(node()), [$@]), + ?LIB:f("/tmp/esock_~s_~w", [NodeName, erlang:system_time(nanosecond)]). + +maybe_start_stats_timer(#{stats_to := Pid, + stats_interval := T}, + Reader) when is_pid(Pid) -> + erlang:start_timer(T, Pid, {stats, T, "reader", Reader}); +maybe_start_stats_timer(_O, _) -> + ok. + +controlling_process(#{sock := Sock, reader := Pid}, NewPid) -> + case socket:setopt(Sock, otp, controlling_process, NewPid) of + ok -> + Pid ! {?MODULE, self(), controlling_process, NewPid}, + receive + {?MODULE, Pid, controlling_process} -> + ok + end; + {error, _} = ERROR -> + ERROR + end. + + +%% Create a listen socket +listen() -> + listen(0). + +listen(Port) when is_integer(Port) -> + listen(Port, #{domain => inet, async => false, method => plain}); +listen(Path) when is_list(Path) -> + listen(Path, #{domain => local, async => false, method => plain}). + +listen(0, #{domain := local} = Opts) -> + listen(mk_unique_path(), Opts); +listen(Path, #{domain := local = Domain} = Opts) + when is_list(Path) andalso (Path =/= []) -> + SA = #{family => Domain, + path => Path}, + Cleanup = fun() -> os:cmd("unlink " ++ Path), ok end, + do_listen(SA, Cleanup, Opts#{proto => default}); +listen(Port, #{domain := Domain} = Opts) + when is_integer(Port) andalso (Port >= 0) -> + %% Bind fills in the rest + case ?LIB:which_local_host_info(Domain) of + {ok, #{addr := Addr}} -> + SA = #{family => Domain, + addr => Addr, + port => Port}, + Cleanup = fun() -> ok end, + do_listen(SA, Cleanup, Opts#{proto => tcp}); + {error, _} = ERROR -> + ERROR + end. + +do_listen(SA, + Cleanup, + #{domain := Domain, proto := Proto, + async := Async, method := Method} = Opts) + when (Method =:= plain) orelse (Method =:= msg) andalso + is_boolean(Async) -> + try + begin + Sock = case socket:open(Domain, stream, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({error, {open, OReason}}) + end, + case socket:bind(Sock, SA) of + {ok, _} -> + ok; + {error, BReason} -> + (catch socket:close(Sock)), + Cleanup(), + throw({error, {bind, BReason}}) + end, + case socket:listen(Sock) of + ok -> + ok; + {error, LReason} -> + (catch socket:close(Sock)), + Cleanup(), + throw({error, {listen, LReason}}) + end, + {ok, #{sock => Sock, opts => Opts}} + end + catch + throw:{error, Reason}:_ -> + {error, Reason} + end. + + +port(#{sock := Sock}) -> + case socket:sockname(Sock) of + {ok, #{family := local, path := Path}} -> + {ok, Path}; + {ok, #{port := Port}} -> + {ok, Port}; + {error, _} = ERROR -> + ERROR + end. + + +peername(#{sock := Sock}) -> + case socket:peername(Sock) of + {ok, #{family := local, path := Path}} -> + {ok, Path}; + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + {error, _} = ERROR -> + ERROR + end. + + +recv(#{sock := Sock, method := plain}, Length) -> + socket:recv(Sock, Length); +recv(#{sock := Sock, method := msg}, Length) -> + case socket:recvmsg(Sock, Length, 0, [], infinity) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. + +recv(#{sock := Sock, method := plain}, Length, Timeout) -> + socket:recv(Sock, Length, Timeout); +recv(#{sock := Sock, method := msg}, Length, Timeout) -> + case socket:recvmsg(Sock, Length, 0, [], Timeout) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. + + +send(#{sock := Sock, method := plain}, Bin) -> + socket:send(Sock, Bin); +send(#{sock := Sock, method := msg}, Bin) -> + socket:sendmsg(Sock, #{iov => [Bin]}). + + +shutdown(#{sock := Sock}, How) -> + socket:shutdown(Sock, How). + + +sockname(#{sock := Sock}) -> + case socket:sockname(Sock) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + {error, _} = ERROR -> + ERROR + end. + + +%% ========================================================================== + +reader_init(ControllingProcess, Sock, Async, Active, Method) + when is_pid(ControllingProcess) andalso + is_boolean(Async) andalso + (is_boolean(Active) orelse (Active =:= once)) andalso + ((Method =:= plain) orelse (Method =:= msg)) -> + put(verbose, false), + MRef = erlang:monitor(process, ControllingProcess), + reader_loop(#{ctrl_proc => ControllingProcess, + ctrl_proc_mref => MRef, + async => Async, + select_info => undefined, + select_num => 0, % Count the number of select messages + active => Active, + sock => Sock, + method => Method}). + + +%% Never read +reader_loop(#{active := false, + ctrl_proc := Pid} = State) -> + receive + {?MODULE, stop} -> + reader_exit(State, stop); + + {?MODULE, Pid, controlling_process, NewPid} -> + OldMRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(OldMRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef -> + reader_exit(State, {ctrl_exit, Reason}); + _ -> + reader_loop(State) + end + end; + +%% Read *once* and then change to false +reader_loop(#{active := once, + async := false, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + case do_recv(Method, Sock) of + {ok, Data} -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State#{active => false}); + {error, timeout} -> + receive + {?MODULE, stop} -> + reader_exit(State, stop); + + {?MODULE, Pid, controlling_process, NewPid} -> + OldMRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(OldMRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef -> + reader_exit(State, {ctrl_exit, Reason}); + _ -> + reader_loop(State) + end + after 0 -> + reader_loop(State) + end; + + {error, closed} = E1 -> + Pid ! ?CLOSED_MSG(Sock, Method), + reader_exit(State, E1); + + {error, Reason} = E2 -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + reader_exit(State, E2) + end; +reader_loop(#{active := once, + async := true, + select_info := undefined, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + case do_recv(Method, Sock, nowait) of + {select, SelectInfo} -> + reader_loop(State#{select_info => SelectInfo}); + {ok, Data} -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State#{active => false}); + + {error, closed} = E1 -> + Pid ! ?CLOSED_MSG(Sock, Method), + reader_exit(State, E1); + + {error, Reason} = E2 -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + reader_exit(State, E2) + end; +reader_loop(#{active := once, + async := true, + select_info := {select_info, _, Ref}, + select_num := N, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + receive + {?MODULE, stop} -> + reader_exit(State, stop); + + {?MODULE, Pid, controlling_process, NewPid} -> + OldMRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(OldMRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef -> + reader_exit(State, {ctrl_exit, Reason}); + _ -> + reader_loop(State) + end; + + {'$socket', Sock, select, Ref} -> + case do_recv(Method, Sock, nowait) of + {ok, Data} when is_binary(Data) -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State#{active => false, + select_info => undefined, + select_num => N+1}); + + {error, closed} = E1 -> + Pid ! ?CLOSED_MSG(Sock, Method), + reader_exit(State, E1); + + {error, Reason} = E2 -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + reader_exit(State, E2) + end + end; + +%% Read and forward data continuously +reader_loop(#{active := true, + async := false, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + case do_recv(Method, Sock) of + {ok, Data} -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State); + {error, timeout} -> + receive + {?MODULE, stop} -> + reader_exit(State, stop); + + {?MODULE, Pid, controlling_process, NewPid} -> + OldMRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(OldMRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef -> + reader_exit(State, {ctrl_exit, Reason}); + _ -> + reader_loop(State) + end + after 0 -> + reader_loop(State) + end; + + {error, closed} = E1 -> + Pid ! ?CLOSED_MSG(Sock, Method), + reader_exit(State, E1); + + {error, Reason} = E2 -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + reader_exit(State, E2) + end; +reader_loop(#{active := true, + async := true, + select_info := undefined, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + case do_recv(Method, Sock) of + {select, SelectInfo} -> + reader_loop(State#{select_info => SelectInfo}); + {ok, Data} -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State); + + {error, closed} = E1 -> + Pid ! ?CLOSED_MSG(Sock, Method), + reader_exit(State, E1); + + {error, Reason} = E2 -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + reader_exit(State, E2) + end; +reader_loop(#{active := true, + async := true, + select_info := {select_info, _, Ref}, + select_num := N, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + receive + {?MODULE, stop} -> + reader_exit(State, stop); + + {?MODULE, Pid, controlling_process, NewPid} -> + OldMRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(OldMRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef -> + reader_exit(State, {ctrl_exit, Reason}); + _ -> + reader_loop(State) + end; + + {'$socket', Sock, select, Ref} -> + case do_recv(Method, Sock, nowait) of + {ok, Data} when is_binary(Data) -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State#{select_info => undefined, + select_num => N+1}); + + {error, closed} = E1 -> + Pid ! ?CLOSED_MSG(Sock, Method), + reader_exit(State, E1); + + {error, Reason} = E2 -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + reader_exit(State, E2) + end + end. + + +do_recv(Method, Sock) -> + do_recv(Method, Sock, ?READER_RECV_TIMEOUT). + +do_recv(plain, Sock, Timeout) -> + socket:recv(Sock, 0, Timeout); +do_recv(msg, Sock, Timeout) -> + case socket:recvmsg(Sock, 0, 0, [], Timeout) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {select, _} = SELECT -> + SELECT; + {error, _} = ERROR -> + ERROR + end. + + +reader_exit(#{async := false, active := Active}, stop) -> + vp("reader stopped when active: ~w", [Active]), + exit(normal); +reader_exit(#{async := true, + active := Active, + select_info := SelectInfo, + select_num := N}, stop) -> + vp("reader stopped when active: ~w" + "~n Current select info: ~p" + "~n Number of select messages: ~p", [Active, SelectInfo, N]), + exit(normal); +reader_exit(#{async := false, active := Active}, {ctrl_exit, normal}) -> + vp("reader ctrl exit when active: ~w", [Active]), + exit(normal); +reader_exit(#{async := true, + active := Active, + select_info := SelectInfo, + select_num := N}, {ctrl_exit, normal}) -> + vp("reader ctrl exit when active: ~w" + "~n Current select info: ~p" + "~n Number of select messages: ~p", [Active, SelectInfo, N]), + exit(normal); +reader_exit(#{async := false, active := Active}, {ctrl_exit, Reason}) -> + vp("reader exit when ctrl crash when active: ~w", [Active]), + exit({controlling_process, Reason}); +reader_exit(#{async := true, + active := Active, + select_info := SelectInfo, + select_num := N}, {ctrl_exit, Reason}) -> + vp("reader exit when ctrl crash when active: ~w" + "~n Current select info: ~p" + "~n Number of select messages: ~p", [Active, SelectInfo, N]), + exit({controlling_process, Reason}); +reader_exit(#{async := false, active := Active}, {error, closed}) -> + vp("reader exit when socket closed when active: ~w", [Active]), + exit(normal); +reader_exit(#{async := true, + active := Active, + select_info := SelectInfo, + select_num := N}, {error, closed}) -> + vp("reader exit when socket closed when active: ~w " + "~n Current select info: ~p" + "~n Number of select messages: ~p", [Active, SelectInfo, N]), + exit(normal); +reader_exit(#{async := false, active := Active}, {error, Reason}) -> + vp("reader exit when socket error when active: ~w", [Active]), + exit(Reason); +reader_exit(#{async := true, + active := Active, + select_info := SelectInfo, + select_num := N}, {error, Reason}) -> + vp("reader exit when socket error when active: ~w: " + "~n Current select info: ~p" + "~n Number of select messages: ~p", [Active, SelectInfo, N]), + exit(Reason). + + + + + + +%% ========================================================================== + +vp(F, A) -> + vp(get(verbose), F, A). + +vp(true, F, A) -> + p(F, A); +vp(_, _, _) -> + ok. + +p(F, A) -> + io:format(F ++ "~n", A). + diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index e578f3dde4..013cc28c40 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 6.5.2 +KERNEL_VSN = 7.0 diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml index 9f9d5a2c23..c2f72e8b2f 100644 --- a/lib/megaco/doc/src/notes.xml +++ b/lib/megaco/doc/src/notes.xml @@ -37,7 +37,47 @@ section is the version number of Megaco.</p> - <section><title>Megaco 3.18.8</title> + <section><title>Megaco 3.19</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p> + The preliminary version 3 codec(s) prev3a, prev3b and + prev3c has been deprecated and will be *removed* in OTP + 24. The encoding config option 'version3' will continue + to work until OTP 24.</p> + <p> + Own Id: OTP-16531</p> + </item> + </list> + </section> + +</section> + + <section><title>Megaco 3.18.8.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The mini parser could not properly decode some IPv6 + addresses.</p> + <p> + Own Id: OTP-16631 Aux Id: ERIERL-491 </p> + </item> + </list> + </section> + +</section> + +<section><title>Megaco 3.18.8</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/megaco/src/text/megaco_text_mini_parser.yrl b/lib/megaco/src/text/megaco_text_mini_parser.yrl index af3050a05b..2a4041867e 100644 --- a/lib/megaco/src/text/megaco_text_mini_parser.yrl +++ b/lib/megaco/src/text/megaco_text_mini_parser.yrl @@ -77,7 +77,7 @@ Nonterminals Terminals - %% 'AddToken' + 'AddToken' %% 'AndAUDITselectToken' 'AuditCapToken' 'AuditToken' @@ -106,7 +106,7 @@ Terminals %% 'EmergencyValueToken' 'ErrorToken' %% 'EventBufferToken' - %% 'EventsToken' + 'EventsToken' %% 'ExternalToken' 'FailoverToken' 'ForcedToken' @@ -273,7 +273,7 @@ pathName -> safeToken : ensure_pathName('$1') . safeToken -> safeToken2 : make_safe_token('$1') . -%% safeToken2 -> 'AddToken' : '$1' . +safeToken2 -> 'AddToken' : '$1' . safeToken2 -> 'AuditToken' : '$1' . safeToken2 -> 'AuditCapToken' : '$1' . safeToken2 -> 'AuditValueToken' : '$1' . @@ -298,7 +298,7 @@ safeToken2 -> 'EmbedToken' : '$1' . %% safeToken2 -> 'EmergencyOffToken' : '$1' . safeToken2 -> 'ErrorToken' : '$1' . %% safeToken2 -> 'EventBufferToken' : '$1' . -%% safeToken2 -> 'EventsToken' : '$1' . +safeToken2 -> 'EventsToken' : '$1' . %% safeToken2 -> 'ExternalToken' : '$1' . % v3 safeToken2 -> 'FailoverToken' : '$1' . safeToken2 -> 'ForcedToken' : '$1' . diff --git a/lib/megaco/test/megaco_codec_mini_SUITE.erl b/lib/megaco/test/megaco_codec_mini_SUITE.erl index 12113aae70..6c7f60db05 100644 --- a/lib/megaco/test/megaco_codec_mini_SUITE.erl +++ b/lib/megaco/test/megaco_codec_mini_SUITE.erl @@ -39,7 +39,32 @@ init_per_testcase/2, end_per_testcase/2, otp7672_msg01/1, - otp7672_msg02/1 + otp7672_msg02/1, + + otp16631_msg01/1, + otp16631_msg02/1, + otp16631_msg03/1, + otp16631_msg04/1, + otp16631_msg05/1, + otp16631_msg06/1, + otp16631_msg11/1, + otp16631_msg12/1, + otp16631_msg13/1, + otp16631_msg14/1, + otp16631_msg15/1, + otp16631_msg16/1, + otp16631_msg21/1, + otp16631_msg22/1, + otp16631_msg23/1, + otp16631_msg24/1, + otp16631_msg25/1, + otp16631_msg26/1, + otp16631_msg31/1, + otp16631_msg32/1, + otp16631_msg33/1, + otp16631_msg34/1, + otp16631_msg35/1, + otp16631_msg36/1 ]). @@ -62,15 +87,51 @@ all() -> groups() -> [ - {tickets, [], tickets_cases()} + {tickets, [], tickets_cases()}, + {otp7672, [], otp7672_cases()}, + {otp16631, [], otp16631_cases()} ]. tickets_cases() -> [ + {group, otp7672}, + {group, otp16631} + ]. + +otp7672_cases() -> + [ otp7672_msg01, otp7672_msg02 ]. +otp16631_cases() -> + [ + otp16631_msg01, + otp16631_msg02, + otp16631_msg03, + otp16631_msg04, + otp16631_msg05, + otp16631_msg06, + otp16631_msg11, + otp16631_msg12, + otp16631_msg13, + otp16631_msg14, + otp16631_msg15, + otp16631_msg16, + otp16631_msg21, + otp16631_msg22, + otp16631_msg23, + otp16631_msg24, + otp16631_msg25, + otp16631_msg26, + otp16631_msg31, + otp16631_msg32, + otp16631_msg33, + otp16631_msg34, + otp16631_msg35, + otp16631_msg36 + ]. + %% @@ -200,6 +261,342 @@ otp7672(Msg) -> end. + +%% -------------------------------------------------------------- +%% + +otp16631_msg01(suite) -> + []; +otp16631_msg01(Config) when is_list(Config) -> + d("otp16631_msg01 -> entry", []), + ok = otp16631( otp16631_msg01() ), + ok. + +otp16631_msg01() -> + otp16631_msg("a"). + + +%% -- + +otp16631_msg02(suite) -> + []; +otp16631_msg02(Config) when is_list(Config) -> + d("otp16631_msg02 -> entry", []), + ok = otp16631( otp16631_msg02() ), + ok. + +otp16631_msg02() -> + otp16631_msg("b"). + + +%% -- + +otp16631_msg03(suite) -> + []; +otp16631_msg03(Config) when is_list(Config) -> + d("otp16631_msg03 -> entry", []), + ok = otp16631( otp16631_msg03() ), + ok. + +otp16631_msg03() -> + otp16631_msg("c"). + + +%% -- + +otp16631_msg04(suite) -> + []; +otp16631_msg04(Config) when is_list(Config) -> + d("otp16631_msg04 -> entry", []), + ok = otp16631( otp16631_msg04() ), + ok. + +otp16631_msg04() -> + otp16631_msg("d"). + + +%% -- + +otp16631_msg05(suite) -> + []; +otp16631_msg05(Config) when is_list(Config) -> + d("otp16631_msg05 -> entry", []), + ok = otp16631( otp16631_msg05() ), + ok. + +otp16631_msg05() -> + otp16631_msg("e"). + + +%% -- + +otp16631_msg06(suite) -> + []; +otp16631_msg06(Config) when is_list(Config) -> + d("otp16631_msg06 -> entry", []), + ok = otp16631( otp16631_msg06() ), + ok. + +otp16631_msg06() -> + otp16631_msg("f"). + + +%% -- + +otp16631_msg11(suite) -> + []; +otp16631_msg11(Config) when is_list(Config) -> + d("otp16631_msg11 -> entry", []), + ok = otp16631( otp16631_msg11() ), + ok. + +otp16631_msg11() -> + otp16631_msg("000a"). + + +%% -- + +otp16631_msg12(suite) -> + []; +otp16631_msg12(Config) when is_list(Config) -> + d("otp16631_msg12 -> entry", []), + ok = otp16631( otp16631_msg12() ), + ok. + +otp16631_msg12() -> + otp16631_msg("000b"). + + +%% -- + +otp16631_msg13(suite) -> + []; +otp16631_msg13(Config) when is_list(Config) -> + d("otp16631_msg13 -> entry", []), + ok = otp16631( otp16631_msg13() ), + ok. + +otp16631_msg13() -> + otp16631_msg("000c"). + + +%% -- + +otp16631_msg14(suite) -> + []; +otp16631_msg14(Config) when is_list(Config) -> + d("otp16631_msg14 -> entry", []), + ok = otp16631( otp16631_msg14() ), + ok. + +otp16631_msg14() -> + otp16631_msg("000d"). + + +%% -- + +otp16631_msg15(suite) -> + []; +otp16631_msg15(Config) when is_list(Config) -> + d("otp16631_msg15 -> entry", []), + ok = otp16631( otp16631_msg15() ), + ok. + +otp16631_msg15() -> + otp16631_msg("000e"). + + +%% -- + +otp16631_msg16(suite) -> + []; +otp16631_msg16(Config) when is_list(Config) -> + d("otp16631_msg16 -> entry", []), + ok = otp16631( otp16631_msg16() ), + ok. + +otp16631_msg16() -> + otp16631_msg("000f"). + + +%% -- + +otp16631_msg21(suite) -> + []; +otp16631_msg21(Config) when is_list(Config) -> + d("otp16631_msg21 -> entry", []), + ok = otp16631( otp16631_msg21() ), + ok. + +otp16631_msg21() -> + otp16631_msg("0a12"). + + +%% -- + +otp16631_msg22(suite) -> + []; +otp16631_msg22(Config) when is_list(Config) -> + d("otp16631_msg22 -> entry", []), + ok = otp16631( otp16631_msg22() ), + ok. + +otp16631_msg22() -> + otp16631_msg("0b12"). + + +%% -- + +otp16631_msg23(suite) -> + []; +otp16631_msg23(Config) when is_list(Config) -> + d("otp16631_msg23 -> entry", []), + ok = otp16631( otp16631_msg23() ), + ok. + +otp16631_msg23() -> + otp16631_msg("0c12"). + + +%% -- + +otp16631_msg24(suite) -> + []; +otp16631_msg24(Config) when is_list(Config) -> + d("otp16631_msg24 -> entry", []), + ok = otp16631( otp16631_msg24() ), + ok. + +otp16631_msg24() -> + otp16631_msg("0d12"). + + +%% -- + +otp16631_msg25(suite) -> + []; +otp16631_msg25(Config) when is_list(Config) -> + d("otp16631_msg25 -> entry", []), + ok = otp16631( otp16631_msg25() ), + ok. + +otp16631_msg25() -> + otp16631_msg("0e12"). + + +%% -- + +otp16631_msg26(suite) -> + []; +otp16631_msg26(Config) when is_list(Config) -> + d("otp16631_msg26 -> entry", []), + ok = otp16631( otp16631_msg26() ), + ok. + +otp16631_msg26() -> + otp16631_msg("0f12"). + + +%% -- + +otp16631_msg31(suite) -> + []; +otp16631_msg31(Config) when is_list(Config) -> + d("otp16631_msg31 -> entry", []), + ok = otp16631( otp16631_msg31() ), + ok. + +otp16631_msg31() -> + otp16631_msg("a123"). + + +%% -- + +otp16631_msg32(suite) -> + []; +otp16631_msg32(Config) when is_list(Config) -> + d("otp16631_msg32 -> entry", []), + ok = otp16631( otp16631_msg32() ), + ok. + +otp16631_msg32() -> + otp16631_msg("b123"). + + +%% -- + +otp16631_msg33(suite) -> + []; +otp16631_msg33(Config) when is_list(Config) -> + d("otp16631_msg33 -> entry", []), + ok = otp16631( otp16631_msg33() ), + ok. + +otp16631_msg33() -> + otp16631_msg("c123"). + + +%% -- + +otp16631_msg34(suite) -> + []; +otp16631_msg34(Config) when is_list(Config) -> + d("otp16631_msg34 -> entry", []), + ok = otp16631( otp16631_msg34() ), + ok. + +otp16631_msg34() -> + otp16631_msg("d123"). + + +%% -- + +otp16631_msg35(suite) -> + []; +otp16631_msg35(Config) when is_list(Config) -> + d("otp16631_msg35 -> entry", []), + ok = otp16631( otp16631_msg35() ), + ok. + +otp16631_msg35() -> + otp16631_msg("e123"). + + +%% -- + +otp16631_msg36(suite) -> + []; +otp16631_msg36(Config) when is_list(Config) -> + d("otp16631_msg36 -> entry", []), + ok = otp16631( otp16631_msg36() ), + ok. + +otp16631_msg36() -> + otp16631_msg("f123"). + + +%% ----- + +otp16631( Msg ) -> + Bin = erlang:list_to_binary(Msg), + try megaco_compact_text_encoder:decode_mini_message([], dynamic, Bin) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + catch + C:E:S -> + {error, {C, E, S}} + end. + + +otp16631_msg(X) when is_list(X) -> + "!/1 [2409:8050:5005:1243:1011::" ++ X ++ + "] T=2523{C=-{SC=ROOT{SV{MT=RS,RE=901,PF=ETSI_BGF/2,V=3}}}}". + + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% p(F, A) -> diff --git a/lib/megaco/test/megaco_mess_SUITE.erl b/lib/megaco/test/megaco_mess_SUITE.erl index 8fb5c4a982..be523f42a2 100644 --- a/lib/megaco/test/megaco_mess_SUITE.erl +++ b/lib/megaco/test/megaco_mess_SUITE.erl @@ -266,6 +266,7 @@ ]). -endif. +-include_lib("common_test/include/ct.hrl"). -include_lib("megaco/include/megaco.hrl"). -include_lib("megaco/include/megaco_message_v1.hrl"). -include("megaco_test_lib.hrl"). @@ -511,18 +512,19 @@ init_per_testcase(Case, Config) -> init_per_testcase2(otp_7189 = Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), - init_per_testcase3(Case, [{tc_timeout, min(2)} |C]); + init_per_testcase3(Case, [{tc_timeout, min(tfactor(2, Config))} |C]); init_per_testcase2(request_and_no_reply = Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), - init_per_testcase3(Case, [{tc_timeout, min(2)} |C]); + init_per_testcase3(Case, [{tc_timeout, min(tfactor(2, Config))} |C]); init_per_testcase2(Case, Config) -> C = lists:keydelete(tc_timeout, 1, Config), - init_per_testcase3(Case, [{tc_timeout, min(1)} |C]). + init_per_testcase3(Case, [{tc_timeout, min(tfactor(1, Config))} |C]). init_per_testcase3(Case, Config) -> megaco_test_global_sys_monitor:reset_events(), megaco_test_lib:init_per_testcase(Case, Config). - + + end_per_testcase(Case, Config) -> @@ -539,6 +541,13 @@ end_per_testcase(Case, Config) -> min(M) -> ?MINS(M). +tfactor(T, Config) -> + case ?config(megaco_factor, Config) of + Factor when is_integer(Factor) andalso (Factor > 1) -> + T * Factor; + _ -> + T + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -10560,6 +10569,7 @@ otp_6442_resend_request2_mg_notify_request_ar(Rid, Tid, Cid) -> otp_6442_resend_reply1(suite) -> []; otp_6442_resend_reply1(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), Pre = fun() -> MgNode = make_node_name(mg), d("start (MG) node: ~p", [MgNode]), @@ -10567,14 +10577,14 @@ otp_6442_resend_reply1(Config) when is_list(Config) -> ok = ?START_NODES(Nodes, true), Nodes end, - Case = fun do_otp_6442_resend_reply1/1, + Case = fun(Nodes) -> do_otp_6442_resend_reply1(Nodes, Factor) end, Post = fun(Nodes) -> d("stop nodes"), ?STOP_NODES(lists:reverse(Nodes)) end, try_tc(request_and_no_reply, Pre, Case, Post). -do_otp_6442_resend_reply1([MgNode]) -> +do_otp_6442_resend_reply1([MgNode], Factor) -> d("[MG] start the simulator "), {ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode), @@ -10591,7 +10601,7 @@ do_otp_6442_resend_reply1([MgNode]) -> {ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq), i("await the transport module service change send_message event"), - Pid = otp_6442_expect(fun otp_6442_rsrp1_verify_scr_msg/1, 5000), + Pid = otp_6442_expect(fun otp_6442_rsrp1_verify_scr_msg/1, Factor * 5000), i("wait some before issuing the service change reply"), sleep(500), @@ -10610,7 +10620,7 @@ do_otp_6442_resend_reply1([MgNode]) -> i("await the transport module first notify-reply send_message event from MG: " "ignore"), - otp_6442_expect(fun otp_6442_rsrp1_verify_first_nr_msg/1, 5000), + otp_6442_expect(fun otp_6442_rsrp1_verify_first_nr_msg/1, Factor * 5000), i("await the transport module second notify-reply send_message event from MG: " "ack"), @@ -10955,6 +10965,7 @@ otp_6442_resend_reply1_err_desc(T) -> otp_6442_resend_reply2(suite) -> []; otp_6442_resend_reply2(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), Pre = fun() -> MgNode = make_node_name(mg), d("start (MG) node: ~p", [MgNode]), @@ -10962,14 +10973,14 @@ otp_6442_resend_reply2(Config) when is_list(Config) -> ok = ?START_NODES(Nodes, true), Nodes end, - Case = fun do_otp_6442_resend_reply2/1, + Case = fun(Nodes) -> do_otp_6442_resend_reply2(Nodes, Factor) end, Post = fun(Nodes) -> d("stop nodes"), ?STOP_NODES(lists:reverse(Nodes)) end, try_tc(otp6442rrep2, Pre, Case, Post). -do_otp_6442_resend_reply2([MgNode]) -> +do_otp_6442_resend_reply2([MgNode], Factor) -> d("[MG] start the simulator "), {ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode), @@ -10986,7 +10997,7 @@ do_otp_6442_resend_reply2([MgNode]) -> {ok, MgId} = megaco_test_megaco_generator:exec(Mg, MgEvSeq), i("await the transport module service change send_message event"), - Pid = otp_6442_expect(fun otp_6442_rsrp2_verify_scr_msg/1, 5000), + Pid = otp_6442_expect(fun otp_6442_rsrp2_verify_scr_msg/1, Factor * 5000), i("wait some before issuing the service change reply"), sleep(500), @@ -11004,7 +11015,7 @@ do_otp_6442_resend_reply2([MgNode]) -> megaco_test_generic_transport:incomming_message(Pid, NotifyRequest), i("await the transport module notify-reply send_message event from MG: ignore"), - otp_6442_expect(otp_6442_rsrp2_verify_first_nr_msg_fun(), 5000), + otp_6442_expect(otp_6442_rsrp2_verify_first_nr_msg_fun(), Factor * 5000), i("await the transport module notify-reply resend_message event from MG: ack"), {TransId, _, _} = diff --git a/lib/megaco/test/megaco_segment_SUITE.erl b/lib/megaco/test/megaco_segment_SUITE.erl index 08b86606de..a403c3309d 100644 --- a/lib/megaco/test/megaco_segment_SUITE.erl +++ b/lib/megaco/test/megaco_segment_SUITE.erl @@ -165,7 +165,7 @@ end_per_group(_Group, Config) -> init_per_testcase(Case, Config) -> process_flag(trap_exit, true), - p("init_per_suite -> entry with" + p("init_per_testcase -> entry with" "~n Config: ~p" "~n Nodes: ~p", [Config, erlang:nodes()]), @@ -175,7 +175,7 @@ init_per_testcase(Case, Config) -> end_per_testcase(Case, Config) -> process_flag(trap_exit, false), - p("end_per_suite -> entry with" + p("end_per_testcase -> entry with" "~n Config: ~p" "~n Nodes: ~p", [Config, erlang:nodes()]), @@ -806,17 +806,9 @@ send_segmented_msg_plain2(doc) -> "Second plain test that it is possible to send segmented messages. " "Send window = infinity. "; send_segmented_msg_plain2(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), + ct:timetrap(?MINS(1) + Factor * ?MINS(1)), Pre = fun() -> - %% We leave it commented out as test - %% All the other changes to the framework - %% may have "solved" the issues... - - %% <CONDITIONAL-SKIP> - %% Skippable = [{unix, [linux]}], - %% Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, - %% ?NON_PC_TC_MAYBE_SKIP(Config, Condition), - %% </CONDITIONAL-SKIP> - MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), d("start nodes: " @@ -827,20 +819,20 @@ send_segmented_msg_plain2(Config) when is_list(Config) -> ok = ?START_NODES(Nodes), Nodes end, - Case = fun do_send_segmented_msg_plain2/1, + Case = fun(X) -> do_send_segmented_msg_plain2(Factor, X) end, Post = fun(Nodes) -> d("stop nodes"), ?STOP_NODES(lists:reverse(Nodes)) end, try_tc(ssmp2, Pre, Case, Post). -do_send_segmented_msg_plain2([MgcNode, MgNode]) -> +do_send_segmented_msg_plain2(Factor, [MgcNode, MgNode]) -> d("[MGC] start the simulator "), {ok, Mgc} = megaco_test_tcp_generator:start_link("MGC", MgcNode), d("[MGC] create the event sequence"), - MgcEvSeq = ssmp2_mgc_event_sequence(text, tcp), + MgcEvSeq = ssmp2_mgc_event_sequence(Factor, text, tcp), i("wait some time before starting the MGC simulation"), sleep(1000), @@ -855,7 +847,7 @@ do_send_segmented_msg_plain2([MgcNode, MgNode]) -> {ok, Mg} = megaco_test_megaco_generator:start_link("MG", MgNode), d("[MG] create the event sequence"), - MgEvSeq = ssmp2_mg_event_sequence(text, tcp), + MgEvSeq = ssmp2_mg_event_sequence(Factor, text, tcp), i("wait some time before starting the MG simulation"), sleep(1000), @@ -883,7 +875,7 @@ do_send_segmented_msg_plain2([MgcNode, MgNode]) -> %% MGC generator stuff %% -ssmp2_mgc_event_sequence(text, tcp) -> +ssmp2_mgc_event_sequence(Factor, text, tcp) -> DecodeFun = ssmp2_mgc_decode_msg_fun(megaco_pretty_text_encoder, []), EncodeFun = ssmp2_mgc_encode_msg_fun(megaco_pretty_text_encoder, []), Mid = {deviceName,"mgc"}, @@ -908,6 +900,7 @@ ssmp2_mgc_event_sequence(text, tcp) -> SegmentRep1 = ssmp2_mgc_segment_reply_msg(Mid, TransId, 1, false), SegmentRep2 = ssmp2_mgc_segment_reply_msg(Mid, TransId, 2, true), TransAck = ssmp2_mgc_trans_ack_msg(Mid, TransId), + TO = fun(T) -> Factor * T end, EvSeq = [{debug, true}, {decode, DecodeFun}, {encode, EncodeFun}, @@ -915,15 +908,17 @@ ssmp2_mgc_event_sequence(text, tcp) -> {expect_accept, any}, {expect_receive, "service-change-request", {ScrVerifyFun, 5000}}, {send, "service-change-reply", ServiceChangeRep}, - {expect_nothing, timer:seconds(1)}, + {expect_nothing, ?SECS(1)}, {send, "notify request", NotifyReq}, - {expect_receive, "notify reply: segment 1", {NrVerifyFun1, 2000}}, + {expect_receive, "notify reply: segment 1", + {NrVerifyFun1, TO(?SECS(2))}}, {send, "segment reply 1", SegmentRep1}, - {expect_receive, "notify reply: segment 2", {NrVerifyFun2, 1000}}, + {expect_receive, "notify reply: segment 2", + {NrVerifyFun2, TO(?SECS(1))}}, {send, "segment reply 2", SegmentRep2}, {sleep, 100}, % {expect_nothing, 500}, {send, "transaction-ack", TransAck}, - {expect_closed, timer:seconds(5)}, + {expect_closed, TO(?SECS(5))}, disconnect ], EvSeq. @@ -1036,14 +1031,13 @@ ssmp2_mgc_verify_notify_reply_segment_msg_fun(SN, Last, ssmp2_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M, SN, Last, TransId, TermId, Cid) -> - io:format("ssmp2_mgc_verify_notify_reply_segment -> entry with" - "~n M: ~p" - "~n SN: ~p" - "~n Last: ~p" - "~n TransId: ~p" - "~n TermId: ~p" - "~n Cid: ~p" - "~n", [M, SN, Last, TransId, TermId, Cid]), + p("ssmp2_mgc_verify_notify_reply_segment -> entry with" + "~n M: ~p" + "~n SN: ~p" + "~n Last: ~p" + "~n TransId: ~p" + "~n TermId: ~p" + "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]), Body = case Mess of #'Message'{version = ?VERSION, @@ -1173,7 +1167,7 @@ ssmp2_mgc_trans_ack_msg(Mid, TransId) -> %% %% MG generator stuff %% -ssmp2_mg_event_sequence(text, tcp) -> +ssmp2_mg_event_sequence(Factor, text, tcp) -> Mid = {deviceName,"mg"}, RI = [ {port, 2944}, @@ -1187,7 +1181,8 @@ ssmp2_mg_event_sequence(text, tcp) -> Tid1 = #megaco_term_id{id = ["00000000","00000000","00000001"]}, Tid2 = #megaco_term_id{id = ["00000000","00000000","00000002"]}, NotifyReqVerify = ssmp2_mg_verify_notify_request_fun(Tid1, Tid2), - AckVerify = ssmp2_mg_verify_ack_fun(), + AckVerify = ssmp2_mg_verify_ack_fun(), + SECS = fun(T) -> ?SECS(Factor * T) end, EvSeq = [ {debug, true}, {megaco_trace, disable}, @@ -1206,12 +1201,12 @@ ssmp2_mg_event_sequence(text, tcp) -> {megaco_update_conn_info, protocol_version, ?VERSION}, {megaco_update_conn_info, segment_send, infinity}, {megaco_update_conn_info, max_pdu_size, 128}, - {sleep, 1000}, + {sleep, ?SECS(1)}, {megaco_callback, handle_trans_request, NotifyReqVerify}, - {megaco_callback, handle_trans_ack, AckVerify, 5000}, + {megaco_callback, handle_trans_ack, AckVerify, SECS(5)}, megaco_stop_user, megaco_stop, - {sleep, 1000} + {sleep, ?SECS(1)} ], EvSeq. @@ -1220,12 +1215,12 @@ ssmp2_mg_verify_handle_connect_fun() -> fun(Ev) -> ssmp2_mg_verify_handle_connect(Ev) end. ssmp2_mg_verify_handle_connect({handle_connect, CH, 1}) -> - io:format("ssmp2_mg_verify_handle_connect -> ok" - "~n CH: ~p~n", [CH]), + p("ssmp2_mg_verify_handle_connect -> ok" + "~n CH: ~p", [CH]), {ok, CH, ok}; ssmp2_mg_verify_handle_connect(Else) -> - io:format("ssmp2_mg_verify_handle_connect -> unknown" - "~n Else: ~p~n", [Else]), + p("ssmp2_mg_verify_handle_connect -> unknown" + "~n Else: ~p", [Else]), {error, Else, ok}. @@ -1235,14 +1230,13 @@ ssmp2_mg_verify_service_change_reply_fun() -> ssmp2_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) -> (catch ssmp2_mg_do_verify_scr(AR)); ssmp2_mg_verify_scr(Crap) -> - io:format("ssmp2_mg_verify_scr -> error: " - "~n Crap: ~p" - "~n", [Crap]), + p("ssmp2_mg_verify_scr -> error: " + "~n Crap: ~p", [Crap]), {error, Crap, ok}. ssmp2_mg_do_verify_scr(AR) -> - io:format("ssmp2_mg_do_verify_scr -> ok: " - "~n AR: ~p~n", [AR]), + p("ssmp2_mg_do_verify_scr -> ok: " + "~n AR: ~p", [AR]), CR = case AR of #'ActionReply'{commandReply = [CmdRep]} -> @@ -1304,30 +1298,27 @@ ssmp2_mg_verify_notify_request( {handle_trans_request, CH, V, ARs}, _Tid1, _Tid2) -> {error, {invalid_trans_request, {CH, V, ARs}}, ok}; ssmp2_mg_verify_notify_request(Crap, _Tid1, _Tid2) -> - io:format("ssmp2_mg_verify_notify_request -> unknown request" - "~n Tid1: ~p" - "~n Tid2: ~p" - "~n Crap: ~p" - "~n", [_Tid1, _Tid2, Crap]), + p("ssmp2_mg_verify_notify_request -> unknown request" + "~n Tid1: ~p" + "~n Tid2: ~p" + "~n Crap: ~p", [_Tid1, _Tid2, Crap]), {error, {unexpected_event, Crap}, ok}. ssmp2_mg_do_verify_notify_request(Tid1, Tid2, AR1, AR2) -> - io:format("ssmp2_mg_do_verify_notify_request -> ok" - "~n Tid1: ~p" - "~n Tid2: ~p" - "~n AR1: ~p" - "~n AR2: ~p" - "~n", [Tid1, Tid2, AR1, AR2]), + p("ssmp2_mg_do_verify_notify_request -> ok" + "~n Tid1: ~p" + "~n Tid2: ~p" + "~n AR1: ~p" + "~n AR2: ~p", [Tid1, Tid2, AR1, AR2]), ActionReply1 = ssmp2_mg_do_verify_notify_request(Tid1, AR1), ActionReply2 = ssmp2_mg_do_verify_notify_request(Tid2, AR2), Reply = {{handle_ack, ssmp2}, [ActionReply1, ActionReply2]}, {ok, [AR1, AR2], Reply}. ssmp2_mg_do_verify_notify_request(Tid, AR) -> - io:format("ssmp2_mg_do_verify_notify_request -> ok" - "~n Tid: ~p" - "~n AR: ~p" - "~n", [Tid, AR]), + p("ssmp2_mg_do_verify_notify_request -> ok" + "~n Tid: ~p" + "~n AR: ~p", [Tid, AR]), {Cid, CR} = case AR of #'ActionRequest'{contextId = CtxId, @@ -1375,9 +1366,8 @@ ssmp2_mg_verify_ack_fun() -> fun(Event) -> ssmp2_mg_verify_ack(Event) end. ssmp2_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp2}) -> - io:format("ssmp2_mg_verify_ack -> ok" - "~n CH: ~p" - "~n", [CH]), + p("ssmp2_mg_verify_ack -> ok" + "~n CH: ~p", [CH]), {ok, CH, ok}; ssmp2_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) -> {error, {unknown_ack_data, CrapAckData, CH}, ok}; @@ -1412,6 +1402,8 @@ send_segmented_msg_plain3(doc) -> "Third plain test that it is possible to send segmented messages. " "Send window = 1. "; send_segmented_msg_plain3(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), + ct:timetrap(?MINS(1) + Factor * ?MINS(1)), Pre = fun() -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), @@ -1612,9 +1604,8 @@ ssmp3_mgc_verify_service_change_req_msg_fun() -> end. ssmp3_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) -> - io:format("ssmp3_mgc_verify_service_change_req -> entry with" - "~n M: ~p" - "~n", [M]), + p("ssmp3_mgc_verify_service_change_req -> entry with" + "~n M: ~p", [M]), Body = case Mess of #'Message'{version = 1, @@ -1704,14 +1695,13 @@ ssmp3_mgc_verify_notify_reply_segment_msg_fun(SN, Last, ssmp3_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M, SN, Last, TransId, TermId, Cid) -> - io:format("ssmp3_mgc_verify_notify_reply_segment -> entry with" - "~n M: ~p" - "~n SN: ~p" - "~n Last: ~p" - "~n TransId: ~p" - "~n TermId: ~p" - "~n Cid: ~p" - "~n", [M, SN, Last, TransId, TermId, Cid]), + p("ssmp3_mgc_verify_notify_reply_segment -> entry with" + "~n M: ~p" + "~n SN: ~p" + "~n Last: ~p" + "~n TransId: ~p" + "~n TermId: ~p" + "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]), Body = case Mess of #'Message'{version = ?VERSION, @@ -1903,12 +1893,12 @@ ssmp3_mg_verify_handle_connect_fun() -> fun(Ev) -> ssmp3_mg_verify_handle_connect(Ev) end. ssmp3_mg_verify_handle_connect({handle_connect, CH, 1}) -> - io:format("ssmp3_mg_verify_handle_connect -> ok" - "~n CH: ~p~n", [CH]), + p("ssmp3_mg_verify_handle_connect -> ok" + "~n CH: ~p", [CH]), {ok, CH, ok}; ssmp3_mg_verify_handle_connect(Else) -> - io:format("ssmp3_mg_verify_handle_connect -> unknown" - "~n Else: ~p~n", [Else]), + p("ssmp3_mg_verify_handle_connect -> unknown" + "~n Else: ~p", [Else]), {error, Else, ok}. @@ -1918,14 +1908,13 @@ ssmp3_mg_verify_service_change_reply_fun() -> ssmp3_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) -> (catch ssmp3_mg_do_verify_scr(AR)); ssmp3_mg_verify_scr(Crap) -> - io:format("ssmp3_mg_verify_scr -> error: " - "~n Crap: ~p" - "~n", [Crap]), + p("ssmp3_mg_verify_scr -> error: " + "~n Crap: ~p", [Crap]), {error, Crap, ok}. ssmp3_mg_do_verify_scr(AR) -> - io:format("ssmp3_mg_do_verify_scr -> ok: " - "~n AR: ~p~n", [AR]), + p("ssmp3_mg_do_verify_scr -> ok: " + "~n AR: ~p", [AR]), CR = case AR of #'ActionReply'{commandReply = [CmdRep]} -> @@ -1988,21 +1977,18 @@ ssmp3_mg_verify_notify_request( {handle_trans_request, CH, V, ARs}, _Tids) -> {error, {invalid_trans_request, {CH, V, ARs}}, ok}; ssmp3_mg_verify_notify_request(Crap, _Tids) -> - io:format("ssmp3_mg_verify_notify_request -> unknown request" - "~n Crap: ~p" - "~n Tids: ~p" - "~n", [Crap, _Tids]), + p("ssmp3_mg_verify_notify_request -> unknown request" + "~n Crap: ~p" + "~n Tids: ~p", [Crap, _Tids]), {error, {unexpected_event, Crap}, ok}. ssmp3_mg_do_verify_notify_request(Tids, ARs) -> - io:format("ssmp3_mg_do_verify_notify_request -> ok" - "~n Tids: ~p" - "~n ARs: ~p" - "~n", [Tids, ARs]), + p("ssmp3_mg_do_verify_notify_request -> ok" + "~n Tids: ~p" + "~n ARs: ~p", [Tids, ARs]), ActionReplies = ssmp3_mg_do_verify_notify_request_ars(Tids, ARs), - io:format("ssmp3_mg_do_verify_notify_request -> ok" - "~n ActionReplies: ~p" - "~n", [ActionReplies]), + p("ssmp3_mg_do_verify_notify_request -> ok" + "~n ActionReplies: ~p", [ActionReplies]), Reply = {{handle_ack, ssmp3}, ActionReplies}, {ok, ARs, Reply}. @@ -2016,10 +2002,9 @@ ssmp3_mg_do_verify_notify_request_ars([Tid|Tids], [AR|ARs], Acc) -> ssmp3_mg_do_verify_notify_request_ars(Tids, ARs, [ActionReply|Acc]). ssmp3_mg_do_verify_notify_request_ar(Tid, AR) -> - io:format("ssmp3_mg_do_verify_notify_request_ar -> ok" - "~n Tid: ~p" - "~n AR: ~p" - "~n", [Tid, AR]), + p("ssmp3_mg_do_verify_notify_request_ar -> ok" + "~n Tid: ~p" + "~n AR: ~p", [Tid, AR]), {Cid, CR} = case AR of #'ActionRequest'{contextId = CtxId, @@ -2067,9 +2052,8 @@ ssmp3_mg_verify_ack_fun() -> fun(Event) -> ssmp3_mg_verify_ack(Event) end. ssmp3_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp3}) -> - io:format("ssmp3_mg_verify_ack -> ok" - "~n CH: ~p" - "~n", [CH]), + p("ssmp3_mg_verify_ack -> ok" + "~n CH: ~p", [CH]), {ok, CH, ok}; ssmp3_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) -> {error, {unknown_ack_data, CrapAckData, CH}, ok}; @@ -2105,7 +2089,7 @@ send_segmented_msg_plain4(doc) -> "Send window = 3. "; send_segmented_msg_plain4(Config) when is_list(Config) -> Factor = ?config(megaco_factor, Config), - ct:timetrap(Factor * ?SECS(60)), + ct:timetrap(Factor * ?MINS(1)), Pre = fun() -> MgcNode = make_node_name(mgc), MgNode = make_node_name(mg), @@ -2302,9 +2286,8 @@ ssmp4_mgc_verify_service_change_req_msg_fun() -> end. ssmp4_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) -> - io:format("ssmp4_mgc_verify_service_change_req -> entry with" - "~n M: ~p" - "~n", [M]), + p("ssmp4_mgc_verify_service_change_req -> entry with" + "~n M: ~p", [M]), Body = case Mess of #'Message'{version = 1, @@ -2394,14 +2377,13 @@ ssmp4_mgc_verify_notify_reply_segment_msg_fun(SN, Last, ssmp4_mgc_verify_notify_reply_segment(#'MegacoMessage'{mess = Mess} = M, SN, Last, TransId, TermId, Cid) -> - io:format("ssmp4_mgc_verify_notify_reply_segment -> entry with" - "~n M: ~p" - "~n SN: ~p" - "~n Last: ~p" - "~n TransId: ~p" - "~n TermId: ~p" - "~n Cid: ~p" - "~n", [M, SN, Last, TransId, TermId, Cid]), + p("ssmp4_mgc_verify_notify_reply_segment -> entry with" + "~n M: ~p" + "~n SN: ~p" + "~n Last: ~p" + "~n TransId: ~p" + "~n TermId: ~p" + "~n Cid: ~p", [M, SN, Last, TransId, TermId, Cid]), Body = case Mess of #'Message'{version = ?VERSION, @@ -2582,7 +2564,7 @@ ssmp4_mg_event_sequence(Factor, text, tcp) -> {megaco_update_conn_info, max_pdu_size, 128}, {sleep, 1000}, {megaco_callback, handle_trans_request, NotifyReqVerify}, - {megaco_callback, handle_trans_ack, AckVerify, TO(15000)}, + {megaco_callback, handle_trans_ack, AckVerify, TO(?SECS(15))}, megaco_stop_user, megaco_stop, {sleep, 1000} @@ -2594,12 +2576,12 @@ ssmp4_mg_verify_handle_connect_fun() -> fun(Ev) -> ssmp4_mg_verify_handle_connect(Ev) end. ssmp4_mg_verify_handle_connect({handle_connect, CH, 1}) -> - io:format("ssmp4_mg_verify_handle_connect -> ok" - "~n CH: ~p~n", [CH]), + p("ssmp4_mg_verify_handle_connect -> ok" + "~n CH: ~p", [CH]), {ok, CH, ok}; ssmp4_mg_verify_handle_connect(Else) -> - io:format("ssmp4_mg_verify_handle_connect -> unknown" - "~n Else: ~p~n", [Else]), + p("ssmp4_mg_verify_handle_connect -> unknown" + "~n Else: ~p", [Else]), {error, Else, ok}. @@ -2609,14 +2591,13 @@ ssmp4_mg_verify_service_change_reply_fun() -> ssmp4_mg_verify_scr({handle_trans_reply, _CH, 1, {ok, [AR]}, _}) -> (catch ssmp4_mg_do_verify_scr(AR)); ssmp4_mg_verify_scr(Crap) -> - io:format("ssmp4_mg_verify_scr -> error: " - "~n Crap: ~p" - "~n", [Crap]), + p("ssmp4_mg_verify_scr -> error: " + "~n Crap: ~p", [Crap]), {error, Crap, ok}. ssmp4_mg_do_verify_scr(AR) -> - io:format("ssmp4_mg_do_verify_scr -> ok: " - "~n AR: ~p~n", [AR]), + p("ssmp4_mg_do_verify_scr -> ok: " + "~n AR: ~p", [AR]), CR = case AR of #'ActionReply'{commandReply = [CmdRep]} -> @@ -2761,9 +2742,8 @@ ssmp4_mg_verify_ack_fun() -> fun(Event) -> ssmp4_mg_verify_ack(Event) end. ssmp4_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, ssmp4}) -> - io:format("ssmp4_mg_verify_ack -> ok" - "~n CH: ~p" - "~n", [CH]), + p("ssmp4_mg_verify_ack -> ok" + "~n CH: ~p", [CH]), {ok, CH, ok}; ssmp4_mg_verify_ack({handle_trans_ack, CH, ?VERSION, ok, CrapAckData}) -> {error, {unknown_ack_data, CrapAckData, CH}, ok}; @@ -2992,9 +2972,8 @@ ssmo1_mgc_verify_service_change_req_msg_fun() -> end. ssmo1_mgc_verify_service_change_req(#'MegacoMessage'{mess = Mess} = M) -> - io:format("ssmo1_mgc_verify_service_change_req -> entry with" - "~n M: ~p" - "~n", [M]), + p("ssmo1_mgc_verify_service_change_req -> entry with" + "~n M: ~p", [M]), Body = case Mess of #'Message'{version = 1, @@ -7896,12 +7875,12 @@ await_completion(Ids) -> ok; {error, {OK, ERROR}} -> d("ERROR => " - "~n OK: ~p" - "~n ERROR: ~p", [OK, ERROR]), + "~n OK: ~p" + "~n ERROR: ~p", [OK, ERROR]), ?ERROR({failed, ERROR}); {error, Reply} -> d("ERROR => " - "~n Reply: ~p", [Reply]), + "~n Reply: ~p", [Reply]), ?ERROR({failed, Reply}) end. diff --git a/lib/megaco/test/megaco_test_lib.erl b/lib/megaco/test/megaco_test_lib.erl index d73ed45add..97d408bee8 100644 --- a/lib/megaco/test/megaco_test_lib.erl +++ b/lib/megaco/test/megaco_test_lib.erl @@ -29,6 +29,7 @@ %% -compile(export_all). -export([ + proxy_call/3, log/4, error/3, @@ -71,6 +72,31 @@ %% ---------------------------------------------------------------- +%% Proxy Call +%% This is used when we need to assign a timeout to a call, but the +%% call itself does not provide such an argument. +%% +%% This has nothing to to with the proxy_start and proxy_init +%% functions below. + +proxy_call(F, Timeout, Default) + when is_function(F, 0) andalso + is_integer(Timeout) andalso (Timeout > 0) andalso + is_function(Default, 0) -> + {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end), + receive + {'DOWN', M, process, P, Reply} -> + Reply + after Timeout -> + erlang:demonitor(M, [flush]), + exit(P, kill), + Default() + end; +proxy_call(F, Timeout, Default) -> + proxy_call(F, Timeout, fun() -> Default end). + + +%% ---------------------------------------------------------------- %% Time related function %% @@ -164,6 +190,10 @@ os_base_skip(Skippable, OsFam, OsName) -> case lists:keysearch(OsFam, 1, Skippable) of {value, {OsFam, OsName}} -> true; + {value, {OsFam, Check}} when is_function(Check, 0) -> + Check(); + {value, {OsFam, Check}} when is_function(Check, 1) -> + Check(os:version()); {value, {OsFam, OsNames}} when is_list(OsNames) -> %% OsNames is a list of: %% [atom()|{atom(), function/0 | function/1}] @@ -443,11 +473,31 @@ pprint(F, A) -> init_per_suite(Config) -> + ct:timetrap(minutes(3)), + + try analyze_and_print_host_info() of + {Factor, HostInfo} when is_integer(Factor) -> + try maybe_skip(HostInfo) of + true -> + {skip, "Unstable host and/or os (or combo thererof)"}; + false -> + maybe_start_global_sys_monitor(Config), + [{megaco_factor, Factor} | Config] + catch + throw:{skip, _} = SKIP -> + SKIP + end + catch + throw:{skip, _} = SKIP -> + SKIP + end. + +maybe_skip(HostInfo) -> + %% We have some crap machines that causes random test case failures %% for no obvious reason. So, attempt to identify those without actually %% checking for the host name... - %% We have two "machines" we are checking for. Both are old installations - %% running on really slow VMs (the host machines are old and tired). + LinuxVersionVerify = fun(V) when (V > {3,6,11}) -> false; % OK - No skip @@ -458,6 +508,28 @@ init_per_suite(Config) -> _ -> false end; + (V) when (V =:= {3,4,20}) -> + case string:trim(os:cmd("cat /etc/issue")) of + "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip + true; + _ -> + false + end; + (V) when (V =:= {2,6,32}) -> + case string:trim(os:cmd("cat /etc/issue")) of + "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip + true; + _ -> + false + end; + (V) when (V =:= {2,6,10}) -> + case string:trim(os:cmd("cat /etc/issue")) of + "MontaVista" ++ _ -> % Stone age MontaVista => Skip + %% The real problem is that the machine is *very* slow + true; + _ -> + false + end; (V) when (V > {2,6,24}) -> false; % OK - No skip (_) -> @@ -478,37 +550,22 @@ init_per_suite(Config) -> %% This version is *not* ok: Skip true end, - %% We are "looking" for a specific machine (a VM) - %% which are *old and crappy" and slow, because it - %% causes a bunch of test cases to fail randomly. - %% But we don not want to test for the host name... - %% WinVersionVerify = - %% fun(V) when (V =:= {6,2,9200}) -> - %% try erlang:system_info(schedulers) of - %% 2 -> - %% true; - %% _ -> - %% false - %% catch - %% _:_:_ -> - %% true - %% end; - %% (_) -> - %% false - %% end, + SkipWindowsOnVirtual = + fun() -> + SysMan = win_sys_info_lookup(system_manufacturer, HostInfo), + case string:to_lower(SysMan) of + "vmware" ++ _ -> + true; + _ -> + false + end + end, COND = [ {unix, [{linux, LinuxVersionVerify}, - {darwin, DarwinVersionVerify}]}%% , - %% {win32, [{nt, WinVersionVerify}]} + {darwin, DarwinVersionVerify}]}, + {win32, SkipWindowsOnVirtual} ], - case os_based_skip(COND) of - true -> - {skip, "Unstable host and/or os (or combo thererof)"}; - false -> - Factor = analyze_and_print_host_info(), - maybe_start_global_sys_monitor(Config), - [{megaco_factor, Factor} | Config] - end. + os_based_skip(COND). %% We start the global system monitor unless explicitly disabled maybe_start_global_sys_monitor(Config) -> @@ -569,10 +626,6 @@ end_per_testcase(_Case, Config) -> %% the load for some test cases. Such as run time or number of %% iteraions. This only works for some OSes. %% -%% We make some calculations on Linux, OpenBSD and FreeBSD. -%% On SunOS we always set the factor to 2 (just to be on the safe side) -%% On all other os:es (mostly windows) we check the number of schedulers, -%% but at least the factor will be 2. analyze_and_print_host_info() -> {OsFam, OsName} = os:type(), Version = @@ -591,6 +644,8 @@ analyze_and_print_host_info() -> analyze_and_print_freebsd_host_info(Version); {unix, netbsd} -> analyze_and_print_netbsd_host_info(Version); + {unix, darwin} -> + analyze_and_print_darwin_host_info(Version); {unix, sunos} -> analyze_and_print_solaris_host_info(Version); {win32, nt} -> @@ -601,19 +656,7 @@ analyze_and_print_host_info() -> "~n Version: ~p" "~n Num Schedulers: ~s" "~n", [OsFam, OsName, Version, str_num_schedulers()]), - try erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - N when (N =< 6) -> - 2; - _ -> - 1 - catch - _:_:_ -> - 10 - end + {num_schedulers_to_factor(), []} end. str_num_schedulers() -> @@ -623,106 +666,285 @@ str_num_schedulers() -> _:_:_ -> "-" end. +num_schedulers_to_factor() -> + try erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + N when (N =< 6) -> + 2; + _ -> + 1 + catch + _:_:_ -> + 10 + end. -analyze_and_print_linux_host_info(Version) -> + + +linux_which_distro(Version) -> case file:read_file_info("/etc/issue") of {ok, _} -> - io:format("Linux: ~s" - "~n ~s" - "~n", - [Version, string:trim(os:cmd("cat /etc/issue"))]); + case [string:trim(S) || + S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of + [DistroStr|_] -> + io:format("Linux: ~s" + "~n ~s" + "~n", + [Version, DistroStr]), + case DistroStr of + "Wind River Linux" ++ _ -> + wind_river; + "MontaVista" ++ _ -> + montavista; + "Yellow Dog" ++ _ -> + yellow_dog; + _ -> + other + end; + X -> + io:format("Linux: ~s" + "~n ~p" + "~n", + [Version, X]), + other + end; _ -> io:format("Linux: ~s" - "~n", [Version]) - end, + "~n", [Version]), + other + end. + + +analyze_and_print_linux_host_info(Version) -> + Distro = linux_which_distro(Version), Factor = - case (catch linux_which_cpuinfo()) of + case (catch linux_which_cpuinfo(Distro)) of {ok, {CPU, BogoMIPS}} -> io:format("CPU: " "~n Model: ~s" - "~n BogoMIPS: ~s" + "~n BogoMIPS: ~w" "~n Num Schedulers: ~s" "~n", [CPU, BogoMIPS, str_num_schedulers()]), - %% We first assume its a float, and if not try integer - try list_to_float(string:trim(BogoMIPS)) of - F when F > 4000 -> + if + (BogoMIPS > 20000) -> 1; - F when F > 1000 -> + (BogoMIPS > 10000) -> 2; - F when F > 500 -> + (BogoMIPS > 5000) -> 3; - _ -> - 5 - catch - _:_:_ -> - try list_to_integer(string:trim(BogoMIPS)) of - I when I > 4000 -> - 1; - I when I > 1000 -> - 2; - I when I > 500 -> - 3; - _ -> - 5 - catch - _:_:_ -> - 5 % Be a "bit" conservative... - end + (BogoMIPS > 2000) -> + 5; + (BogoMIPS > 1000) -> + 8; + true -> + 10 end; {ok, CPU} -> io:format("CPU: " "~n Model: ~s" "~n Num Schedulers: ~s" "~n", [CPU, str_num_schedulers()]), - 2; % Be a "bit" conservative... + num_schedulers_to_factor(); _ -> - 5 % Be a "bit" (more) conservative... + 5 end, %% Check if we need to adjust the factor because of the memory try linux_which_meminfo() of AddFactor -> - Factor + AddFactor + {Factor + AddFactor, []} catch _:_:_ -> - Factor + {Factor, []} + end. + + +linux_cpuinfo_lookup(Key) when is_list(Key) -> + linux_info_lookup(Key, "/proc/cpuinfo"). + +linux_cpuinfo_cpu() -> + case linux_cpuinfo_lookup("cpu") of + [Model] -> + Model; + _ -> + "-" + end. + +linux_cpuinfo_motherboard() -> + case linux_cpuinfo_lookup("motherboard") of + [MB] -> + MB; + _ -> + "-" + end. + +linux_cpuinfo_bogomips() -> + case linux_cpuinfo_lookup("bogomips") of + BMips when is_list(BMips) -> + try lists:sum([bogomips_to_int(BM) || BM <- BMips]) + catch + _:_:_ -> + "-" + end; + _ -> + "-" end. -linux_which_cpuinfo() -> +linux_cpuinfo_total_bogomips() -> + case linux_cpuinfo_lookup("total bogomips") of + [TBM] -> + try bogomips_to_int(TBM) + catch + _:_:_ -> + "-" + end; + _ -> + "-" + end. + +bogomips_to_int(BM) -> + try list_to_float(BM) of + F -> + floor(F) + catch + _:_:_ -> + try list_to_integer(BM) of + I -> + I + catch + _:_:_ -> + throw(noinfo) + end + end. + +linux_cpuinfo_model() -> + case linux_cpuinfo_lookup("model") of + [M] -> + M; + _ -> + "-" + end. + +linux_cpuinfo_platform() -> + case linux_cpuinfo_lookup("platform") of + [P] -> + P; + _ -> + "-" + end. + +linux_cpuinfo_model_name() -> + case linux_cpuinfo_lookup("model name") of + [P|_] -> + P; + _X -> + "-" + end. + +linux_cpuinfo_processor() -> + case linux_cpuinfo_lookup("Processor") of + [P] -> + P; + _ -> + "-" + end. + +linux_which_cpuinfo(montavista) -> + CPU = + case linux_cpuinfo_cpu() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_motherboard() of + "-" -> + Model; + MB -> + Model ++ " (" ++ MB ++ ")" + end + end, + case linux_cpuinfo_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end; + +linux_which_cpuinfo(yellow_dog) -> + CPU = + case linux_cpuinfo_cpu() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_motherboard() of + "-" -> + Model; + MB -> + Model ++ " (" ++ MB ++ ")" + end + end, + {ok, CPU}; + +linux_which_cpuinfo(wind_river) -> + CPU = + case linux_cpuinfo_model() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_platform() of + "-" -> + Model; + Platform -> + Model ++ " (" ++ Platform ++ ")" + end + end, + case linux_cpuinfo_total_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end; + +linux_which_cpuinfo(other) -> %% Check for x86 (Intel or AMD) CPU = - try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of - ["model name", ModelName | _] -> - ModelName; - _ -> + case linux_cpuinfo_model_name() of + "-" -> %% ARM (at least some distros...) - try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of - ["Processor", Proc | _] -> - Proc; - _ -> + case linux_cpuinfo_processor() of + "-" -> %% Ok, we give up - throw(noinfo) - catch - _:_:_ -> - throw(noinfo) - end - catch - _:_:_ -> - throw(noinfo) + throw(noinfo); + Proc -> + Proc + end; + ModelName -> + ModelName end, - try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of - [_, BMips | _] -> - {ok, {CPU, BMips}}; + case linux_cpuinfo_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end. + +linux_meminfo_lookup(Key) when is_list(Key) -> + linux_info_lookup(Key, "/proc/meminfo"). + +linux_meminfo_memtotal() -> + case linux_meminfo_lookup("MemTotal") of + [X] -> + X; _ -> - {ok, CPU} - catch - _:_:_ -> - {ok, CPU} + "-" end. %% We *add* the value this return to the Factor. linux_which_meminfo() -> - try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of - [_, MemTotal] -> + case linux_meminfo_memtotal() of + "-" -> + 0; + MemTotal -> io:format("Memory:" "~n ~s" "~n", [MemTotal]), @@ -752,12 +974,7 @@ linux_which_meminfo() -> end; _X -> 0 - end; - _ -> - 0 - catch - _:_:_ -> - 0 + end end. @@ -843,11 +1060,11 @@ analyze_and_print_openbsd_host_info(Version) -> true -> 3 end, - CPUFactor + MemAddFactor + {CPUFactor + MemAddFactor, []} end catch _:_:_ -> - 1 + {5, []} end. @@ -930,21 +1147,22 @@ analyze_and_print_freebsd_host_info(Version) -> true -> 3 end, - CPUFactor + MemAddFactor + {CPUFactor + MemAddFactor, []} end catch _:_:_ -> io:format("CPU:" "~n Num Schedulers: ~w" "~n", [erlang:system_info(schedulers)]), - case erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - _ -> - 2 - end + Factor = case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end, + {Factor, []} end. analyze_freebsd_cpu(Extract) -> @@ -1067,21 +1285,22 @@ analyze_and_print_netbsd_host_info(Version) -> true -> 3 end, - CPUFactor + MemAddFactor + {CPUFactor + MemAddFactor, []} end catch _:_:_ -> io:format("CPU:" "~n Num Schedulers: ~w" "~n", [erlang:system_info(schedulers)]), - case erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - _ -> - 2 - end + Factor = case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end, + {Factor, []} end. analyze_netbsd_cpu(Extract) -> @@ -1120,6 +1339,289 @@ analyze_netbsd_item(Extract, Key, Process, Default) -> +%% Model Identifier: Macmini7,1 +%% Processor Name: Intel Core i5 +%% Processor Speed: 2,6 GHz +%% Number of Processors: 1 +%% Total Number of Cores: 2 +%% L2 Cache (per Core): 256 KB +%% L3 Cache: 3 MB +%% Hyper-Threading Technology: Enabled +%% Memory: 16 GB + +analyze_and_print_darwin_host_info(Version) -> + %% This stuff is for macOS. + %% If we ever tested on a pure darwin machine, + %% we need to find some other way to find some info... + %% Also, I suppose its possible that we for some other + %% reason *fail* to get the info... + case analyze_darwin_software_info() of + [] -> + io:format("Darwin:" + "~n Version: ~s" + "~n Num Schedulers: ~s" + "~n", [Version, str_num_schedulers()]), + {num_schedulers_to_factor(), []}; + SwInfo when is_list(SwInfo) -> + SystemVersion = analyze_darwin_sw_system_version(SwInfo), + KernelVersion = analyze_darwin_sw_kernel_version(SwInfo), + HwInfo = analyze_darwin_hardware_info(), + ModelName = analyze_darwin_hw_model_name(HwInfo), + ModelId = analyze_darwin_hw_model_identifier(HwInfo), + ProcName = analyze_darwin_hw_processor_name(HwInfo), + ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo), + NumProc = analyze_darwin_hw_number_of_processors(HwInfo), + NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo), + Memory = analyze_darwin_hw_memory(HwInfo), + io:format("Darwin:" + "~n System Version: ~s" + "~n Kernel Version: ~s" + "~n Model: ~s (~s)" + "~n Processor: ~s (~s, ~s, ~s)" + "~n Memory: ~s" + "~n Num Schedulers: ~s" + "~n", [SystemVersion, KernelVersion, + ModelName, ModelId, + ProcName, ProcSpeed, NumProc, NumCores, + Memory, + str_num_schedulers()]), + CPUFactor = analyze_darwin_cpu_to_factor(ProcName, + ProcSpeed, + NumProc, + NumCores), + MemFactor = analyze_darwin_memory_to_factor(Memory), + if (MemFactor =:= 1) -> + {CPUFactor, []}; + true -> + {CPUFactor + MemFactor, []} + end + end. + +analyze_darwin_sw_system_version(SwInfo) -> + proplists:get_value("system version", SwInfo, "-"). + +analyze_darwin_sw_kernel_version(SwInfo) -> + proplists:get_value("kernel version", SwInfo, "-"). + +analyze_darwin_software_info() -> + analyze_darwin_system_profiler("SPSoftwareDataType"). + +analyze_darwin_hw_model_name(HwInfo) -> + proplists:get_value("model name", HwInfo, "-"). + +analyze_darwin_hw_model_identifier(HwInfo) -> + proplists:get_value("model identifier", HwInfo, "-"). + +analyze_darwin_hw_processor_name(HwInfo) -> + proplists:get_value("processor name", HwInfo, "-"). + +analyze_darwin_hw_processor_speed(HwInfo) -> + proplists:get_value("processor speed", HwInfo, "-"). + +analyze_darwin_hw_number_of_processors(HwInfo) -> + proplists:get_value("number of processors", HwInfo, "-"). + +analyze_darwin_hw_total_number_of_cores(HwInfo) -> + proplists:get_value("total number of cores", HwInfo, "-"). + +analyze_darwin_hw_memory(HwInfo) -> + proplists:get_value("memory", HwInfo, "-"). + +analyze_darwin_hardware_info() -> + analyze_darwin_system_profiler("SPHardwareDataType"). + +%% This basically has the structure: "Key: Value" +%% But could also be (for example): +%% "Something:" (which we ignore) +%% "Key: Value1:Value2" +analyze_darwin_system_profiler(DataType) -> + %% First, make sure the program actually exist: + case os:cmd("which system_profiler") of + [] -> + []; + _ -> + D0 = os:cmd("system_profiler " ++ DataType), + D1 = string:tokens(D0, [$\n]), + D2 = [string:trim(S1) || S1 <- D1], + D3 = [string:tokens(S2, [$:]) || S2 <- D2], + analyze_darwin_system_profiler2(D3) + end. + +analyze_darwin_system_profiler2(L) -> + analyze_darwin_system_profiler2(L, []). + +analyze_darwin_system_profiler2([], Acc) -> + [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)]; +analyze_darwin_system_profiler2([[_]|T], Acc) -> + analyze_darwin_system_profiler2(T, Acc); +analyze_darwin_system_profiler2([[H1,H2]|T], Acc) -> + analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]); +analyze_darwin_system_profiler2([[H|TH0]|T], Acc) -> + %% Some value parts has ':' in them, so put them together + TH1 = colonize(TH0), + analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]). + +%% This is only called if the length is at least 2 +colonize([L1, L2]) -> + L1 ++ ":" ++ L2; +colonize([H|T]) -> + H ++ ":" ++ colonize(T). + + +%% The memory looks like this "<size> <unit>". Example: "2 GB" +analyze_darwin_memory_to_factor(Mem) -> + case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of + [_SzStr, "tb"] -> + 1; + [SzStr, "gb"] -> + try list_to_integer(SzStr) of + Sz when Sz < 2 -> + 20; + Sz when Sz < 4 -> + 10; + Sz when Sz < 8 -> + 5; + Sz when Sz < 16 -> + 2; + _ -> + 1 + catch + _:_:_ -> + 20 + end; + [_SzStr, "mb"] -> + 20; + _ -> + 20 + end. + + +%% The speed is a string: "<speed> <unit>" +%% the speed may be a float, which we transforms into an integer of MHz. +%% To calculate a factor based on processor speed, number of procs +%% and number of cores is ... not an exact ... science ... +analyze_darwin_cpu_to_factor(_ProcName, + ProcSpeedStr, NumProcStr, NumCoresStr) -> + Speed = + case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of + [SpeedStr, "mhz"] -> + try list_to_integer(SpeedStr) of + SpeedI -> + SpeedI + catch + _:_:_ -> + try list_to_float(SpeedStr) of + SpeedF -> + trunc(SpeedF) + catch + _:_:_ -> + -1 + end + end; + [SpeedStr, "ghz"] -> + try list_to_float(SpeedStr) of + SpeedF -> + trunc(1000*SpeedF) + catch + _:_:_ -> + try list_to_integer(SpeedStr) of + SpeedI -> + 1000*SpeedI + catch + _:_:_ -> + -1 + end + end; + _ -> + -1 + end, + NumProc = try list_to_integer(NumProcStr) of + NumProcI -> + NumProcI + catch + _:_:_ -> + 1 + end, + NumCores = try list_to_integer(NumCoresStr) of + NumCoresI -> + NumCoresI + catch + _:_:_ -> + 1 + end, + if + (Speed > 3000) -> + if + (NumProc =:= 1) -> + if + (NumCores < 2) -> + 5; + (NumCores < 4) -> + 3; + (NumCores < 6) -> + 2; + true -> + 1 + end; + true -> + if + (NumCores < 4) -> + 2; + true -> + 1 + end + end; + (Speed > 2000) -> + if + (NumProc =:= 1) -> + if + (NumCores < 2) -> + 8; + (NumCores < 4) -> + 5; + (NumCores < 6) -> + 3; + true -> + 1 + end; + true -> + if + (NumCores < 4) -> + 5; + (NumCores < 8) -> + 2; + true -> + 1 + end + end; + true -> + if + (NumProc =:= 1) -> + if + (NumCores < 2) -> + 10; + (NumCores < 4) -> + 7; + (NumCores < 6) -> + 5; + (NumCores < 8) -> + 3; + true -> + 1 + end; + true -> + if + (NumCores < 4) -> + 8; + (NumCores < 8) -> + 4; + true -> + 1 + end + end + end. + + analyze_and_print_solaris_host_info(Version) -> Release = case file:read_file_info("/etc/release") of @@ -1231,19 +1733,19 @@ analyze_and_print_solaris_host_info(Version) -> _:_:_ -> 10 end, - try erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - N when (N =< 6) -> - 2; - _ -> - 1 - catch - _:_:_ -> - 10 - end + MemFactor. + {try erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + N when (N =< 6) -> + 2; + _ -> + 1 + catch + _:_:_ -> + 10 + end + MemFactor, []}. analyze_and_print_win_host_info(Version) -> @@ -1315,7 +1817,7 @@ analyze_and_print_win_host_info(Version) -> _ -> 2 end, - CPUFactor + MemFactor. + {CPUFactor + MemFactor, SysInfo}. win_sys_info_lookup(Key, SysInfo) -> win_sys_info_lookup(Key, SysInfo, "-"). @@ -1330,14 +1832,25 @@ win_sys_info_lookup(Key, SysInfo, Def) -> %% This function only extracts the prop we actually care about! which_win_system_info() -> - SysInfo = os:cmd("systeminfo"), - try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), []) - catch - _:_:_ -> - io:format("Failed process System info: " - "~s~n", [SysInfo]), - [] - end. + F = fun() -> + try + begin + SysInfo = os:cmd("systeminfo"), + process_win_system_info( + string:tokens(SysInfo, [$\r, $\n]), []) + end + catch + C:E:S -> + io:format("Failed get or process System info: " + " Error Class: ~p" + " Error: ~p" + " Stack: ~p" + "~n", [C, E, S]), + [] + end + end, + proxy_call(F, minutes(1), + fun() -> throw({skip, "System info timeout"}) end). process_win_system_info([], Acc) -> Acc; @@ -1373,6 +1886,22 @@ process_win_system_info([H|T], Acc) -> end. +linux_info_lookup(Key, File) -> + try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of + Info -> + linux_info_lookup_collect(Key, Info, []) + catch + _:_:_ -> + "-" + end. + +linux_info_lookup_collect(_Key, [], Values) -> + lists:reverse(Values); +linux_info_lookup_collect(Key, [Key, Value|Rest], Values) -> + linux_info_lookup_collect(Key, Rest, [Value|Values]); +linux_info_lookup_collect(_, _, Values) -> + lists:reverse(Values). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Set kill timer diff --git a/lib/megaco/test/megaco_test_lib.hrl b/lib/megaco/test/megaco_test_lib.hrl index f6af199d4e..0777adbcd0 100644 --- a/lib/megaco/test/megaco_test_lib.hrl +++ b/lib/megaco/test/megaco_test_lib.hrl @@ -29,6 +29,8 @@ -define(LIB, megaco_test_lib). +-define(PCALL(F, T, D), ?LIB:proxy_call(F, T, D)). + -define(APPLY(Proxy, Fun), Proxy ! {apply, Fun}). diff --git a/lib/megaco/test/megaco_udp_SUITE.erl b/lib/megaco/test/megaco_udp_SUITE.erl index 05910e50a9..42d0060fcf 100644 --- a/lib/megaco/test/megaco_udp_SUITE.erl +++ b/lib/megaco/test/megaco_udp_SUITE.erl @@ -27,6 +27,7 @@ %%---------------------------------------------------------------------- %% Include files %%---------------------------------------------------------------------- +-include_lib("common_test/include/ct.hrl"). -include_lib("megaco/src/udp/megaco_udp.hrl"). -include("megaco_test_lib.hrl"). @@ -239,6 +240,8 @@ start_and_stop(doc) -> ["This test case sets up a connection and then cloises it. " "No data is sent. "]; start_and_stop(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), + ct:timetrap(Factor * ?SECS(45)), Pre = fun() -> p("create nodes"), ServerNode = make_node_name(server), @@ -247,20 +250,22 @@ start_and_stop(Config) when is_list(Config) -> ok = ?START_NODES(Nodes), Nodes end, - Case = fun do_start_and_stop/1, + Case = fun(X) -> do_start_and_stop(Factor, X) end, Post = fun(Nodes) -> p("stop nodes"), ?STOP_NODES(lists:reverse(Nodes)) end, try_tc(start_and_stop, Pre, Case, Post). -do_start_and_stop([ServerNode, ClientNode]) -> +do_start_and_stop(Factor, [ServerNode, ClientNode]) -> %% Create command sequences + TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end, + TO = TOCalc(?SECS(5)), p("create command sequences"), ServerPort = 2944, ServerCmds = start_and_stop_server_commands(ServerPort), {ok, ServerHost} = inet:gethostname(), - ClientCmds = start_and_stop_client_commands(ServerPort, ServerHost), + ClientCmds = start_and_stop_client_commands(TO, ServerPort, ServerHost), %% Start the test procs used in the test-case, one for each node p("start command handlers"), @@ -268,8 +273,8 @@ do_start_and_stop([ServerNode, ClientNode]) -> p("server command handler started: ~p", [Server]), Client = client_start_command_handler(ClientNode, ClientCmds), p("client command handler started: ~p", [Client]), - - ok = + + ok = receive {operational, Server} -> p("received listening message from server [~p] => " @@ -280,11 +285,11 @@ do_start_and_stop([ServerNode, ClientNode]) -> ?SKIP(Reason); {'EXIT', Client, {skip, Reason}} -> ?SKIP(Reason) - after 5000 -> + after TO -> {error, server_timeout} end, - ok = await_command_handler_completion([Server, Client], ?SECS(20)), + ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))), p("done"), ok. @@ -337,7 +342,7 @@ start_and_stop_server_commands(Port) -> ]. -start_and_stop_client_commands(ServerPort, _ServerHost) -> +start_and_stop_client_commands(TO, ServerPort, _ServerHost) -> Opts = [{port, ServerPort}], Self = self(), [ @@ -362,7 +367,7 @@ start_and_stop_client_commands(ServerPort, _ServerHost) -> #{id => 4, desc => "Await continue", cmd => fun(State) -> - client_await_continue_signal(State, 5000) + client_await_continue_signal(State, TO) end}, #{id => 5, @@ -399,6 +404,8 @@ sendreceive(suite) -> sendreceive(doc) -> ["Test send and receive with the UDP transport. "]; sendreceive(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), + ct:timetrap(Factor * ?SECS(30)), Pre = fun() -> p("create nodes"), ServerNode = make_node_name(server), @@ -407,20 +414,22 @@ sendreceive(Config) when is_list(Config) -> ok = ?START_NODES(Nodes), Nodes end, - Case = fun do_sendreceive/1, + Case = fun(X) -> do_sendreceive(Factor, X) end, Post = fun(Nodes) -> p("stop nodes"), ?STOP_NODES(lists:reverse(Nodes)) end, try_tc(sendreceive, Pre, Case, Post). -do_sendreceive([ServerNode, ClientNode]) -> +do_sendreceive(Factor, [ServerNode, ClientNode]) -> %% Create command sequences p("create command sequences"), + TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end, + TO = TOCalc(?SECS(5)), ServerPort = 2944, - ServerCmds = sendreceive_server_commands(ServerPort), + ServerCmds = sendreceive_server_commands(TO, ServerPort), {ok, ServerHost} = inet:gethostname(), - ClientCmds = sendreceive_client_commands(ServerPort, ServerHost), + ClientCmds = sendreceive_client_commands(TO, ServerPort, ServerHost), %% Start the test procs used in the test-case, one for each node p("start command handlers"), @@ -440,16 +449,16 @@ do_sendreceive([ServerNode, ClientNode]) -> ?SKIP(Reason); {'EXIT', Client, {skip, Reason}} -> ?SKIP(Reason) - after 5000 -> + after TO -> {error, server_timeout} end, - ok = await_command_handler_completion([Server, Client], ?SECS(20)), + ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))), p("done"), ok. -sendreceive_server_commands(Port) -> +sendreceive_server_commands(TO, Port) -> Opts = [{port, Port}], Self = self(), [ @@ -480,7 +489,7 @@ sendreceive_server_commands(Port) -> #{id => 5, desc => "Await initial message (ping)", cmd => fun(State) -> - server_await_initial_message(State, "ping", 5000) + server_await_initial_message(State, "ping", TO) end}, #{id => 6, @@ -492,7 +501,7 @@ sendreceive_server_commands(Port) -> #{id => 7, desc => "Await nothing before sending a message (hejsan)", cmd => fun(State) -> - server_await_nothing(State, 1000) + server_await_nothing(State, TO div 5) end}, #{id => 8, @@ -504,13 +513,13 @@ sendreceive_server_commands(Port) -> #{id => 9, desc => "Await reply (hoppsan) to message", cmd => fun(State) -> - server_await_message(State, "hoppsan", 1000) + server_await_message(State, "hoppsan", TO div 5) end}, #{id => 10, desc => "Await nothing before closing", cmd => fun(State) -> - server_await_nothing(State, 1000) + server_await_nothing(State, TO div 5) end}, #{id => 11, @@ -522,7 +531,7 @@ sendreceive_server_commands(Port) -> #{id => 12, desc => "Await nothing before stopping transport", cmd => fun(State) -> - server_await_nothing(State, 1000) + server_await_nothing(State, TO div 5) end}, #{id => 13, @@ -532,7 +541,7 @@ sendreceive_server_commands(Port) -> end} ]. -sendreceive_client_commands(ServerPort, ServerHost) -> +sendreceive_client_commands(TO, ServerPort, ServerHost) -> OwnPort = ServerPort+1, Opts = [{port, OwnPort}], Self = self(), @@ -558,7 +567,7 @@ sendreceive_client_commands(ServerPort, ServerHost) -> #{id => 4, desc => "Await continue", cmd => fun(State) -> - client_await_continue_signal(State, 5000) + client_await_continue_signal(State, TO) end}, #{id => 5, @@ -576,13 +585,13 @@ sendreceive_client_commands(ServerPort, ServerHost) -> #{id => 7, desc => "Await reply (pong) to initial message", cmd => fun(State) -> - client_await_message(State, "pong", 1000) + client_await_message(State, "pong", TO div 5) end}, #{id => 8, desc => "Await message (hejsan)", cmd => fun(State) -> - client_await_message(State, "hejsan", 5000) + client_await_message(State, "hejsan", TO) end}, #{id => 9, @@ -594,7 +603,7 @@ sendreceive_client_commands(ServerPort, ServerHost) -> #{id => 10, desc => "Await nothing before closing", cmd => fun(State) -> - client_await_nothing(State, 1000) + client_await_nothing(State, TO div 5) end}, #{id => 11, @@ -606,7 +615,7 @@ sendreceive_client_commands(ServerPort, ServerHost) -> #{id => 12, desc => "Await nothing before stopping transport", cmd => fun(State) -> - client_await_nothing(State, 1000) + client_await_nothing(State, TO div 5) end}, #{id => 13, @@ -624,6 +633,8 @@ block_unblock(suite) -> block_unblock(doc) -> ["Test the block/unblock functions of the UDP transport. "]; block_unblock(Config) when is_list(Config) -> + Factor = ?config(megaco_factor, Config), + ct:timetrap(Factor * ?MINS(1)), Pre = fun() -> p("create nodes"), ServerNode = make_node_name(server), @@ -632,20 +643,22 @@ block_unblock(Config) when is_list(Config) -> ok = ?START_NODES(Nodes), Nodes end, - Case = fun do_block_unblock/1, + Case = fun(X) -> do_block_unblock(Factor, X) end, Post = fun(Nodes) -> p("stop nodes"), ?STOP_NODES(lists:reverse(Nodes)) end, try_tc(block_unblock, Pre, Case, Post). -do_block_unblock([ServerNode, ClientNode]) -> +do_block_unblock(Factor, [ServerNode, ClientNode]) -> %% Create command sequences p("create command sequences"), + TOCalc = fun(BaseTO) -> to_calc(Factor, BaseTO) end, + TO = TOCalc(?SECS(5)), ServerPort = 2944, - ServerCmds = block_unblock_server_commands(ServerPort), + ServerCmds = block_unblock_server_commands(TO, ServerPort), {ok, ServerHost} = inet:gethostname(), - ClientCmds = block_unblock_client_commands(ServerPort, ServerHost), + ClientCmds = block_unblock_client_commands(TO, ServerPort, ServerHost), %% Start the test procs used in the test-case, one for each node p("start command handlers"), @@ -667,7 +680,7 @@ do_block_unblock([ServerNode, ClientNode]) -> ?SKIP(Reason1); {'EXIT', Client, {skip, Reason2}} -> ?SKIP(Reason2) - after 5000 -> + after TO -> {error, server_timeout} end, @@ -684,16 +697,16 @@ do_block_unblock([ServerNode, ClientNode]) -> ?SKIP(Reason3); {'EXIT', Client, {skip, Reason4}} -> ?SKIP(Reason4) - after 5000 -> + after TO -> {error, timeout} end, - ok = await_command_handler_completion([Server, Client], ?SECS(20)), + ok = await_command_handler_completion([Server, Client], TOCalc(?SECS(20))), p("done"), ok. -block_unblock_server_commands(Port) -> +block_unblock_server_commands(TO, Port) -> Opts = [{port, Port}], Self = self(), [ @@ -724,7 +737,7 @@ block_unblock_server_commands(Port) -> #{id => 5, desc => "Await initial message (ping)", cmd => fun(State) -> - server_await_initial_message(State, "ping", 5000) + server_await_initial_message(State, "ping", TO) end}, #{id => 6, @@ -736,7 +749,7 @@ block_unblock_server_commands(Port) -> #{id => 7, desc => "Await continue", cmd => fun(State) -> - server_await_continue_signal(State, 5000) + server_await_continue_signal(State, TO) end}, #{id => 8, @@ -748,19 +761,19 @@ block_unblock_server_commands(Port) -> #{id => 9, desc => "Await nothing before receiving (hoppsan) reply", cmd => fun(State) -> - server_await_nothing(State, 4000) + server_await_nothing(State, TO) end}, #{id => 10, desc => "Await reply (hoppsan) to message", cmd => fun(State) -> - server_await_message(State, "hoppsan", 2000) + server_await_message(State, "hoppsan", TO div 2) end}, #{id => 11, desc => "Await nothing before closing", cmd => fun(State) -> - server_await_nothing(State, 1000) + server_await_nothing(State, TO div 5) end}, #{id => 12, @@ -772,7 +785,7 @@ block_unblock_server_commands(Port) -> #{id => 13, desc => "Await nothing before stopping transport", cmd => fun(State) -> - server_await_nothing(State, 1000) + server_await_nothing(State, TO div 5) end}, #{id => 14, @@ -783,7 +796,7 @@ block_unblock_server_commands(Port) -> ]. -block_unblock_client_commands(ServerPort, ServerHost) -> +block_unblock_client_commands(TO, ServerPort, ServerHost) -> OwnPort = ServerPort+1, Opts = [{port, OwnPort}], Self = self(), @@ -809,7 +822,7 @@ block_unblock_client_commands(ServerPort, ServerHost) -> #{id => 4, desc => "Await continue", cmd => fun(State) -> - client_await_continue_signal(State, 5000) + client_await_continue_signal(State, TO) end}, #{id => 5, @@ -827,7 +840,7 @@ block_unblock_client_commands(ServerPort, ServerHost) -> #{id => 7, desc => "Await reply (pong) to initial message", cmd => fun(State) -> - client_await_message(State, "pong", 1000) + client_await_message(State, "pong", TO div 5) end}, #{id => 8, @@ -845,7 +858,7 @@ block_unblock_client_commands(ServerPort, ServerHost) -> #{id => 10, desc => "Await nothing before unblocking", cmd => fun(State) -> - client_await_nothing(State, 5000) + client_await_nothing(State, TO) end}, #{id => 11, @@ -857,7 +870,7 @@ block_unblock_client_commands(ServerPort, ServerHost) -> #{id => 8, desc => "Await message (hejsan)", cmd => fun(State) -> - client_await_message(State, "hejsan", 5000) + client_await_message(State, "hejsan", TO) end}, #{id => 9, @@ -869,7 +882,7 @@ block_unblock_client_commands(ServerPort, ServerHost) -> #{id => 10, desc => "Await nothing before closing", cmd => fun(State) -> - client_await_nothing(State, 1000) + client_await_nothing(State, TO) end}, #{id => 11, @@ -881,7 +894,7 @@ block_unblock_client_commands(ServerPort, ServerHost) -> #{id => 12, desc => "Await nothing before stopping transport", cmd => fun(State) -> - client_await_nothing(State, 1000) + client_await_nothing(State, TO) end}, #{id => 13, @@ -974,14 +987,17 @@ server_start_transport(State) when is_map(State) -> server_open(#{transport_ref := Ref} = State, Options) when is_list(Options) -> Opts = [{receive_handle, self()}, {module, ?MODULE} | Options], - case (catch megaco_udp:open(Ref, Opts)) of + try megaco_udp:open(Ref, Opts) of {ok, Socket, ControlPid} -> {ok, State#{handle => {socket, Socket}, % Temporary control_pid => ControlPid}}; {error, {could_not_open_udp_port, eaddrinuse}} -> {skip, {server, eaddrinuse}}; - Error -> - Error + {error, _} = ERROR -> + ERROR + catch + C:E:S -> + {error, {catched, C, E, S}} end. server_notify_operational(#{parent := Parent} = State) -> @@ -1080,14 +1096,17 @@ client_start_transport(State) when is_map(State) -> client_open(#{transport_ref := Ref} = State, Options) when is_list(Options) -> Opts = [{receive_handle, self()}, {module, ?MODULE} | Options], - case (catch megaco_udp:open(Ref, Opts)) of + try megaco_udp:open(Ref, Opts) of {ok, Socket, ControlPid} -> {ok, State#{handle => {socket, Socket}, control_pid => ControlPid}}; {error, {could_not_open_udp_port, eaddrinuse}} -> {skip, {client, eaddrinuse}}; - Error -> - Error + {error, _} = ERROR -> + ERROR + catch + C:E:S -> + {error, {catched, C, E, S}} end. client_await_continue_signal(#{parent := Parent} = State, Timeout) -> @@ -1196,6 +1215,13 @@ make_node_name(Name) -> end. +to_calc(1 = _Factor, BaseTO) when is_integer(BaseTO) andalso (BaseTO > 0) -> + BaseTO; +to_calc(Factor, BaseTO) when is_integer(Factor) andalso (Factor > 0) andalso + is_integer(BaseTO) andalso (BaseTO > 0) -> + trunc( ((Factor + 1) / 2) * BaseTO ). + + p(F) -> p(F, []). diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk index e9c21389bc..3a35c5d125 100644 --- a/lib/megaco/vsn.mk +++ b/lib/megaco/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = megaco -MEGACO_VSN = 3.18.8 +MEGACO_VSN = 3.19 PRE_VSN = APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)" diff --git a/lib/mnesia/doc/specs/.gitignore b/lib/mnesia/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/mnesia/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/mnesia/doc/src/Makefile b/lib/mnesia/doc/src/Makefile index f14fd33c7a..486993e76d 100644 --- a/lib/mnesia/doc/src/Makefile +++ b/lib/mnesia/doc/src/Makefile @@ -68,6 +68,7 @@ XML_GEN_FILES = $(XML_CHAPTER_GEN_FILES:%=$(XMLDIR)/%) IMAGE_FILES = \ company.gif -# ---------------------------------------------------- +SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) +TOP_SPECS_FILE = specs.xml include $(ERL_TOP)/make/doc.mk diff --git a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc index a53bac020b..3c41945285 100644 --- a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc +++ b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc @@ -298,7 +298,7 @@ <p>The function <c>insert_emp/3</c> creates a Functional Object (Fun). <c>Fun</c> is passed as a single argument to the function - <seemfa marker="mnesia#transaction/2">mnesia:transaction(Fun)</seemfa>. + <seemfa marker="mnesia#transaction/1">mnesia:transaction(Fun)</seemfa>. This means that <c>Fun</c> is run as a transaction with the following properties:</p> <list type="bulleted"> diff --git a/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc index c722646d33..335f09dd30 100644 --- a/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc +++ b/lib/mnesia/doc/src/Mnesia_chap4.xmlsrc @@ -220,7 +220,7 @@ forced to release all its locks and sleep for a while. The Fun in the transaction is evaluated once more.</p> <p>It is therefore important that the code inside the Fun given to - <seemfa marker="mnesia#transaction/2"><c>mnesia:transaction/1</c></seemfa> + <seemfa marker="mnesia#transaction/1"><c>mnesia:transaction/1</c></seemfa> is pure. Some strange results can occur if, for example, messages are sent by the transaction Fun. The following example illustrates this situation:</p> @@ -252,7 +252,7 @@ transaction. If no enclosing transaction (or other enclosing <c>Mnesia</c> activity) exists, they all fail.</p> <list type="bulleted"> - <item><seemfa marker="mnesia#transaction/2">mnesia:transaction(Fun) -> {aborted, Reason} |{atomic, Value}</seemfa> + <item><seemfa marker="mnesia#transaction/1">mnesia:transaction(Fun) -> {aborted, Reason} |{atomic, Value}</seemfa> executes one transaction with the functional object <c>Fun</c> as the single parameter. </item> @@ -498,14 +498,6 @@ <c>mnesia:dirty_prev/2</c> are synonyms. </item> <item> - <p><seemfa marker="mnesia#dirty_slot/2">mnesia:dirty_slot(Tab, Slot)</seemfa> - returns the list of records that are associated with <c>Slot</c> - in a table. It can be used to traverse a table in a manner - similar to the function <c>dirty_next/2</c>. A table has a - number of slots that range from zero to some unknown upper - bound. The function <c>dirty_slot/2</c> returns the special - atom <c>'$end_of_table'</c> when the end of the table is - reached.</p> <p>The behavior of this function is undefined if the table is written on while being traversed. The function @@ -664,7 +656,7 @@ mnesia:all_keys/1</seemfa>. <p>As previously described, a Functional Object (Fun) performing table access operations, as listed here, can be passed on as arguments to the function - <seemfa marker="mnesia#transaction/2">mnesia:transaction/1,2,3</seemfa>: + <seemfa marker="mnesia#transaction/1">mnesia:transaction/1,2,3</seemfa>: </p> <list type="bulleted"> <item> @@ -732,7 +724,7 @@ mnesia:all_keys/1</seemfa>. <item><c>ets</c></item> </list> <p>By passing the same "fun" as argument to the function - <seemfa marker="mnesia#sync_transaction/3">mnesia:sync_transaction(Fun [, Args])</seemfa> + <seemfa marker="mnesia#sync_transaction/1">mnesia:sync_transaction(Fun [, Args])</seemfa> it is performed in synced transaction context. Synced transactions wait until all active replicas has committed the transaction (to disc) before diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml index 9097724012..1932b7695e 100644 --- a/lib/mnesia/doc/src/mnesia.xml +++ b/lib/mnesia/doc/src/mnesia.xml @@ -11,7 +11,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software @@ -181,9 +181,67 @@ transaction before iterating over the table.</p> </description> + <datatypes> + <datatype> + <name name="table"/> + </datatype> + <datatype> + <name name="activity"/> + </datatype> + <datatype> + <name name="create_option"/> + </datatype> + <datatype> + <name name="storage_type"/> + </datatype> + <datatype> + <name name="t_result" n_vars="1"/> + </datatype> + <datatype> + <name name="result"/> + </datatype> + <datatype> + <name name="index_attr"/> + </datatype> + <datatype> + <name name="write_locks"/> + </datatype> + <datatype> + <name name="read_locks"/> + </datatype> + <datatype> + <name name="lock_kind"/> + </datatype> + <datatype> + <name name="select_continuation"/> + </datatype> + <datatype> + <name name="snmp_struct"/> + </datatype> + <datatype> + <name name="snmp_type"/> + </datatype> + <datatype> + <name name="tuple_of" n_vars="1"/> + </datatype> + <datatype> + <name name="config_key"/> + </datatype> + <datatype> + <name name="config_value"/> + </datatype> + <datatype> + <name name="config_result"/> + </datatype> + <datatype> + <name name="debug_level"/> + </datatype> + + </datatypes> + <funcs> <func> - <name since="">abort(Reason) -> transaction abort</name> + <name name="abort" arity="1" since=""/> <fsummary>Terminates the current transaction.</fsummary> <desc> <p>Makes the transaction silently @@ -195,7 +253,7 @@ </desc> </func> <func> - <name since="">activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name> + <name name="activate_checkpoint" arity="1" since=""/> <fsummary>Activates a checkpoint.</fsummary> <desc> <marker id="activate_checkpoint"></marker> @@ -259,7 +317,8 @@ </desc> </func> <func> - <name since="">activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name> + <name name="activity" arity="2" since=""/> + <!--<name name="activity" arity="3" since=""/>--> <fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary> <desc> <marker id="activity_2_3"></marker> @@ -271,7 +330,7 @@ </desc> </func> <func> - <name since="">activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name> + <name name="activity" arity="4" since=""/> <fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary> <desc> <marker id="activity_4"></marker> @@ -403,7 +462,7 @@ </desc> </func> <func> - <name since="">add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name> + <name name="add_table_copy" arity="3" since=""/> <fsummary>Copies a table to a remote node.</fsummary> <desc> <marker id="add_table_copy"></marker> @@ -420,7 +479,7 @@ mnesia:add_table_copy(person, Node, disc_copies)</code> </desc> </func> <func> - <name since="">add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name> + <name name="add_table_index" arity="2" since=""/> <fsummary>Creates an index for a table.</fsummary> <desc> <marker id="add_table_index"></marker> @@ -441,7 +500,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">all_keys(Tab) -> KeyList | transaction abort</name> + <name name="all_keys" arity="1" since=""/> <fsummary>Returns all keys in a table.</fsummary> <desc> <marker id="all_keys"></marker> @@ -453,7 +512,8 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">async_dirty(Fun [, Args]) -> ResultOfFun | exit(Reason)</name> + <name name="async_dirty" arity="1" since=""/> + <name name="async_dirty" arity="2" since=""/> <fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary> <desc> <marker id="async_dirty"></marker> @@ -493,7 +553,8 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name> + <name name="backup" arity="1" since=""/> + <name name="backup" arity="2" since=""/> <fsummary>Backs up all tables in the database.</fsummary> <desc> <marker id="backup"></marker> @@ -505,7 +566,8 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name> + <name name="backup_checkpoint" arity="2" since=""/> + <name name="backup_checkpoint" arity="3" since=""/> <fsummary>Backs up all tables in a checkpoint.</fsummary> <desc> <marker id="backup_checkpoint"></marker> @@ -520,7 +582,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name> + <name name="change_config" arity="2" since=""/> <fsummary>Changes a configuration parameter.</fsummary> <desc> <marker id="change_config"></marker> @@ -554,7 +616,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name> + <name name="change_table_access_mode" arity="2" since=""/> <fsummary>Changes the access mode for the table.</fsummary> <desc> <marker id="change_table_access_mode"></marker> @@ -568,7 +630,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name since="">change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name> + <name name="change_table_copy_type" arity="3" since=""/> <fsummary>Changes the storage type of a table.</fsummary> <desc> <marker id="change_table_copy_type"></marker> @@ -585,7 +647,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name since="">change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name> + <name name="change_table_load_order" arity="2" since=""/> <fsummary>Changes the load order priority for the table.</fsummary> <desc> <marker id="change_table_load_order"></marker> @@ -595,7 +657,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name since="OTP R14B03">change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name> + <name name="change_table_majority" arity="2" since="OTP R14B03"/> <fsummary>Changes the majority check setting for the table.</fsummary> <desc> <p><c>Majority</c> must be a boolean. Default is <c>false</c>. @@ -607,7 +669,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name since="">clear_table(Tab) -> {aborted, R} | {atomic, ok}</name> + <name name="clear_table" arity="1" since=""/> <fsummary>Deletes all entries in a table.</fsummary> <desc> <marker id="clear_table"></marker> @@ -615,7 +677,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name since="">create_schema(DiscNodes) -> ok | {error,Reason}</name> + <name name="create_schema" arity="1" since=""/> <fsummary>Creates a new schema on the specified nodes.</fsummary> <desc> <marker id="create_schema"></marker> @@ -637,7 +699,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name since="">create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name> + <name name="create_table" arity="2" since=""/> <fsummary>Creates a Mnesia table called <c>Name</c>with properties as described by argument <c>TabDef</c>.</fsummary> <desc> <marker id="create_table"></marker> @@ -799,7 +861,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">deactivate_checkpoint(Name) -> ok | {error, Reason}</name> + <name name="deactivate_checkpoint" arity="1" since=""/> <fsummary>Deactivates a checkpoint.</fsummary> <desc> <marker id="deactivate_checkpoint"></marker> @@ -811,7 +873,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name> + <name name="del_table_copy" arity="2" since=""/> <fsummary>Deletes the replica of table <c>Tab</c> at node <c>Node</c>.</fsummary> <desc> <marker id="del_table_copy"></marker> @@ -825,7 +887,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name> + <name name="del_table_index" arity="2" since=""/> <fsummary>Deletes an index in a table.</fsummary> <desc> <marker id="del_table_index"></marker> @@ -834,16 +896,16 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">delete({Tab, Key}) -> transaction abort | ok</name> + <name name="delete" arity="1" since=""/> <fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary> <desc> - <marker id="delete_2"></marker> + <marker id="delete_1"></marker> <p>Calls <c>mnesia:delete(Tab, Key, write)</c>.</p> </desc> </func> <func> - <name since="">delete(Tab, Key, LockKind) -> transaction abort | ok</name> - <fsummary>Deletes all records in table <c>Tab</c>with the key <c>Key</c>.</fsummary> + <name name="delete" arity="3" since=""/> + <fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary> <desc> <marker id="delete_3"></marker> <p>Deletes all records in table <c>Tab</c> with the key @@ -857,7 +919,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">delete_object(Record) -> transaction abort | ok</name> + <name name="delete_object" arity="1" since=""/> <fsummary>Delete a record.</fsummary> <desc> <marker id="delete_object_1"></marker> @@ -866,7 +928,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">delete_object(Tab, Record, LockKind) -> transaction abort | ok</name> + <name name="delete_object" arity="3" since=""/> <fsummary>Deletes a record.</fsummary> <desc> <marker id="delete_object_3"></marker> @@ -883,7 +945,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">delete_schema(DiscNodes) -> ok | {error,Reason}</name> + <name name="delete_schema" arity="1" since=""/> <fsummary>Deletes the schema on the given nodes.</fsummary> <desc> <marker id="delete_schema"></marker> @@ -904,7 +966,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name> + <name name="delete_table" arity="1" since=""/> <fsummary>Deletes permanently all replicas of table <c>Tab</c>.</fsummary> <desc> <marker id="delete_table"></marker> @@ -912,7 +974,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name> + <name name="dirty_all_keys" arity="1" since=""/> <fsummary>Dirty search for all record keys in table.</fsummary> <desc> <marker id="delete_all_keys"></marker> @@ -920,7 +982,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name> + <name name="dirty_delete" arity="1" since=""/> <fsummary>Dirty delete of a record.</fsummary> <desc> <marker id="dirty_delete"></marker> @@ -928,14 +990,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name> + <name name="dirty_delete" arity="2" since=""/> <fsummary>Dirty delete of a record.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:delete/3</c>.</p> </desc> </func> <func> - <name since="">dirty_delete_object(Record)</name> + <name name="dirty_delete_object" arity="1" since=""/> <fsummary>Dirty delete of a record.</fsummary> <desc> <marker id="dirty_delete_object_1"></marker> @@ -944,18 +1006,18 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_delete_object(Tab, Record)</name> + <name name="dirty_delete_object" arity="2" since=""/> <fsummary>Dirty delete of a record.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:delete_object/3</c>.</p> </desc> </func> <func> - <name since="">dirty_first(Tab) -> Key | exit({aborted, Reason})</name> + <name name="dirty_first" arity="1" since=""/> <fsummary>Returns the key for the first record in a table.</fsummary> <desc> <marker id="dirty_first"></marker> - <p>Records in <c>set</c> or <c>bag</c> tables are not ordered. + <p>Records in <c>set</c> or <c>bag</c> tables are not ordered. However, there is an ordering of the records that is unknown to the user. Therefore, a table can be traversed by this function with the function <c>mnesia:dirty_next/2</c>. @@ -967,7 +1029,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_index_match_object(Pattern, Pos)</name> + <name name="dirty_index_match_object" arity="2" since=""/> <fsummary>Dirty pattern match using index.</fsummary> <desc> <marker id="dirty_index_match_object_2"></marker> @@ -977,7 +1039,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_index_match_object(Tab, Pattern, Pos)</name> + <name name="dirty_index_match_object" arity="3" since=""/> <fsummary>Dirty pattern match using index.</fsummary> <desc> <p>Dirty equivalent of the function @@ -985,7 +1047,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_index_read(Tab, SecondaryKey, Pos)</name> + <name name="dirty_index_read" arity="3" since=""/> <fsummary>Dirty read using index.</fsummary> <desc> <marker id="dirty_index_read"></marker> @@ -994,7 +1056,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_last(Tab) -> Key | exit({aborted, Reason})</name> + <name name="dirty_last" arity="1" since=""/> <fsummary>Returns the key for the last record in a table.</fsummary> <desc> <marker id="dirty_last"></marker> @@ -1006,7 +1068,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name> + <name name="dirty_match_object" arity="1" since=""/> <fsummary>Dirty pattern match pattern.</fsummary> <desc> <marker id="dirty_match_object_1"></marker> @@ -1015,7 +1077,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name> + <name name="dirty_match_object" arity="2" since=""/> <fsummary>Dirty pattern match pattern.</fsummary> <desc> <p>Dirty equivalent of the function @@ -1023,7 +1085,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name> + <name name="dirty_next" arity="2" since=""/> <fsummary>Return the next key in a table.</fsummary> <desc> <marker id="dirty_next"></marker> @@ -1038,7 +1100,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name> + <name name="dirty_prev" arity="2" since=""/> <fsummary>Returns the previous key in a table.</fsummary> <desc> <marker id="dirty_prev"></marker> @@ -1050,7 +1112,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name> + <name name="dirty_read" arity="1" since=""/> <fsummary>Dirty read of records.</fsummary> <desc> <marker id="dirty_read"></marker> @@ -1058,14 +1120,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name> + <name name="dirty_read" arity="2" since=""/> <fsummary>Dirty read of records.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:read/3</c>.</p> </desc> </func> <func> - <name since="">dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name> + <name name="dirty_select" arity="2" since=""/> <fsummary>Dirty matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary> <desc> <marker id="dirty_select"></marker> @@ -1073,23 +1135,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name> - <fsummary>Returns the list of records that are associated with <c>Slot</c> in a table.</fsummary> - <desc> - <marker id="dirty_slot"></marker> - <p>Traverses a table in a - manner similar to the function <c>mnesia:dirty_next/2</c>. - A table has a number of slots that range from 0 (zero) to - an unknown upper bound. The function - <c>mnesia:dirty_slot/2</c> returns the special atom - <c>'$end_of_table'</c> when the end of the table is reached. - The behavior of this function is undefined if a write - operation is performed on the table while it is being - traversed.</p> - </desc> - </func> - <func> - <name since="">dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name> + <name name="dirty_update_counter" arity="2" since=""/> <fsummary>Dirty update of a counter record.</fsummary> <desc> <marker id="dirty_update_counter"></marker> @@ -1097,7 +1143,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name> + <name name="dirty_update_counter" arity="3" since=""/> <fsummary>Dirty update of a counter record.</fsummary> <desc> <p>Mnesia has no special counter records. However, @@ -1126,7 +1172,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_write(Record) -> ok | exit({aborted, Reason})</name> + <name name="dirty_write" arity="1" since=""/> <fsummary>Dirty write of a record.</fsummary> <desc> <marker id="dirty_write_1"></marker> @@ -1135,14 +1181,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name> + <name name="dirty_write" arity="2" since=""/> <fsummary>Dirty write of a record.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:write/3</c>.</p> </desc> </func> <func> - <name since="">dump_log() -> dumped</name> + <name name="dump_log" arity="0" since=""/> <fsummary>Performs a user-initiated dump of the local log file.</fsummary> <desc> <marker id="dump_log"></marker> @@ -1156,7 +1202,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name> + <name name="dump_tables" arity="1" since=""/> <fsummary>Dumps all RAM tables to disc.</fsummary> <desc> <marker id="dump_tables"></marker> @@ -1168,7 +1214,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">dump_to_textfile(Filename)</name> + <name name="dump_to_textfile" arity="1" since=""/> <fsummary>Dumps local tables into a text file.</fsummary> <desc> <marker id="dump_to_textfile"></marker> @@ -1181,7 +1227,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">error_description(Error) -> String</name> + <name name="error_description" arity="1" since=""/> <fsummary>Returns a string describing a particular Mnesia error.</fsummary> <desc> <marker id="error_description"></marker> @@ -1259,7 +1305,8 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">ets(Fun [, Args]) -> ResultOfFun | exit(Reason)</name> + <name name="ets" arity="1" since=""/> + <name name="ets" arity="2" since=""/> <fsummary>Calls the <c>Fun</c> in a raw context that is not protected by a transaction.</fsummary> <desc> <marker id="ets"></marker> @@ -1278,7 +1325,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">first(Tab) -> Key | transaction abort</name> + <name name="first" arity="1" since=""/> <fsummary>Returns the key for the first record in a table.</fsummary> <desc> <marker id="first"></marker> @@ -1293,7 +1340,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">foldl(Function, Acc, Table) -> NewAcc | transaction abort</name> + <name name="foldl" arity="3" since=""/> <fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary> <desc> <marker id="foldl"></marker> @@ -1306,7 +1353,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">foldr(Function, Acc, Table) -> NewAcc | transaction abort</name> + <name name="foldr" arity="3" since=""/> <fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary> <desc> <marker id="foldr"></marker> @@ -1317,7 +1364,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">force_load_table(Tab) -> yes | ErrorDescription</name> + <name name="force_load_table" arity="1" since=""/> <fsummary>Forces a table to be loaded into the system.</fsummary> <desc> <marker id="force_load_table"></marker> @@ -1335,7 +1382,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">index_match_object(Pattern, Pos) -> transaction abort | ObjList</name> + <name name="index_match_object" arity="2" since=""/> <fsummary>Matches records and uses index information.</fsummary> <desc> <marker id="index_match_object_2"></marker> @@ -1345,7 +1392,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name> + <name name="index_match_object" arity="4" since=""/> <fsummary>Matches records and uses index information.</fsummary> <desc> <marker id="index_match_object_4"></marker> @@ -1377,7 +1424,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name> + <name name="index_read" arity="3" since=""/> <fsummary>Reads records through index table.</fsummary> <desc> <marker id="index_read"></marker> @@ -1397,7 +1444,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">info() -> ok</name> + <name name="info" arity="0" since=""/> <fsummary>Prints system information on the terminal.</fsummary> <desc> <marker id="info"></marker> @@ -1408,7 +1455,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">install_fallback(Opaque) -> ok | {error,Reason}</name> + <name name="install_fallback" arity="1" since=""/> <fsummary>Installs a backup as fallback.</fsummary> <desc> <marker id="install_fallback_1"></marker> @@ -1417,7 +1464,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name> + <name name="install_fallback" arity="1" since=""/> <fsummary>Installs a backup as fallback.</fsummary> <desc> <p>Calls <c>mnesia:install_fallback(Opaque, Args)</c>, where @@ -1425,7 +1472,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">install_fallback(Opaque, Args) -> ok | {error,Reason}</name> + <name name="install_fallback" arity="2" since=""/> <fsummary>Installs a backup as fallback.</fsummary> <desc> <p>Installs a backup as fallback. The fallback is used to @@ -1483,7 +1530,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">is_transaction() -> boolean</name> + <name name="is_transaction" arity="0" since=""/> <fsummary>Checks if code is running in a transaction.</fsummary> <desc> <marker id="is_transaction"></marker> @@ -1492,7 +1539,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">last(Tab) -> Key | transaction abort</name> + <name name="last" arity="1" since=""/> <fsummary>Returns the key for the last record in a table.</fsummary> <desc> <p>Works exactly like @@ -1503,7 +1550,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">load_textfile(Filename)</name> + <name name="load_textfile" arity="1" since=""/> <fsummary>Loads tables from a text file.</fsummary> <desc> <marker id="load_textfile"></marker> @@ -1516,7 +1563,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name> + <name name="lock" arity="2" since=""/> <fsummary>Explicit grab lock.</fsummary> <desc> <marker id="lock"></marker> @@ -1605,7 +1652,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">match_object(Pattern) -> transaction abort | RecList</name> + <name name="match_object" arity="1" since=""/> <fsummary>Matches <c>Pattern</c> for records.</fsummary> <desc> <marker id="match_object_1"></marker> @@ -1614,7 +1661,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name> + <name name="match_object" arity="3" since=""/> <fsummary>Matches <c>Pattern</c> for records.</fsummary> <desc> <marker id="match_object_3"></marker> @@ -1639,7 +1686,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name> + <name name="move_table_copy" arity="3" since=""/> <fsummary>Moves the copy of table <c>Tab</c> from node <c>From</c> to node <c>To</c>.</fsummary> <desc> <marker id="move_table_copy"></marker> @@ -1653,7 +1700,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">next(Tab, Key) -> Key | transaction abort</name> + <name name="next" arity="2" since=""/> <fsummary>Returns the next key in a table.</fsummary> <desc> <marker id="next"></marker> @@ -1665,7 +1712,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">prev(Tab, Key) -> Key | transaction abort</name> + <name name="prev" arity="2" since=""/> <fsummary>Returns the previous key in a table.</fsummary> <desc> <p>Works exactly like @@ -1676,7 +1723,8 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">read({Tab, Key}) -> transaction abort | RecordList</name> + <name name="read" arity="1" since=""/> + <name name="read" arity="2" since=""/> <fsummary>Reads records(s) with a given key.</fsummary> <desc> <marker id="read_2"></marker> @@ -1684,14 +1732,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">read(Tab, Key) -> transaction abort | RecordList</name> - <fsummary>Reads records(s) with a given key.</fsummary> - <desc> - <p>Calls function <c>mnesia:read(Tab, Key, read)</c>.</p> - </desc> - </func> - <func> - <name since="">read(Tab, Key, LockKind) -> transaction abort | RecordList</name> + <name name="read" arity="3" since=""/> <fsummary>Reads records(s) with a given key.</fsummary> <desc> <marker id="read_3"></marker> @@ -1716,7 +1757,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">read_lock_table(Tab) -> ok | transaction abort</name> + <name name="read_lock_table" arity="1" since=""/> <fsummary>Sets a read lock on an entire table.</fsummary> <desc> <marker id="read_lock_table"></marker> @@ -1725,7 +1766,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">report_event(Event) -> ok</name> + <name name="report_event" arity="1" since=""/> <fsummary>Reports a user event to the Mnesia event handler.</fsummary> <desc> <marker id="report_event"></marker> @@ -1743,7 +1784,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name> + <name name="restore" arity="2" since=""/> <fsummary>Online restore of backup.</fsummary> <desc> <marker id="restore"></marker> @@ -1803,7 +1844,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">s_delete({Tab, Key}) -> ok | transaction abort</name> + <name name="s_delete" arity="1" since=""/> <fsummary>Sets sticky lock and delete records.</fsummary> <desc> <marker id="s_delete"></marker> @@ -1812,7 +1853,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">s_delete_object(Record) -> ok | transaction abort</name> + <name name="s_delete_object" arity="1" since=""/> <fsummary>Sets sticky lock and delete record.</fsummary> <desc> <marker id="s_delete_object"></marker> @@ -1822,7 +1863,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">s_write(Record) -> ok | transaction abort</name> + <name name="s_write" arity="1" since=""/> <fsummary>Writes <c>Record</c> and sets sticky lock.</fsummary> <desc> <marker id="s_write"></marker> @@ -1832,21 +1873,22 @@ mnesia:create_table(person, </desc> </func> <func> - <name since="">schema() -> ok</name> + <name name="schema" arity="0" since=""/> <fsummary>Prints information about all table definitions on the terminal.</fsummary> <desc> <p>Prints information about all table definitions on the terminal.</p> </desc> </func> <func> - <name since="">schema(Tab) -> ok</name> + <name name="schema" arity="1" since=""/> <fsummary>Prints information about one table definition on the terminal.</fsummary> <desc> <p>Prints information about one table definition on the terminal.</p> </desc> </func> <func> - <name since="">select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name> + <name name="select" arity="2" since=""/> + <name name="select" arity="3" since=""/> <fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary> <desc> <marker id="select_2_3"></marker> @@ -1884,7 +1926,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name> + <name name="select" arity="4" since=""/> <fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary> <desc> <marker id="select_4"></marker> @@ -1907,7 +1949,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name> + <name name="select" arity="1" since=""/> <fsummary>Continues selecting objects.</fsummary> <desc> <p>Selects more objects with the match specification initiated @@ -1919,7 +1961,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">set_debug_level(Level) -> OldLevel</name> + <name name="set_debug_level" arity="1" since=""/> <fsummary>Changes the internal debug level of Mnesia.</fsummary> <desc> <marker id="set_debug_level"></marker> @@ -1930,7 +1972,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">set_master_nodes(MasterNodes) -> ok | {error, Reason}</name> + <name name="set_master_nodes" arity="1" since=""/> <fsummary>Sets the master nodes for all tables.</fsummary> <desc> <marker id="set_master_nodes_1"></marker> @@ -1943,7 +1985,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name> + <name name="set_master_nodes" arity="2" since=""/> <fsummary>Sets the master nodes for a table.</fsummary> <desc> <marker id="set_master_nodes_2"></marker> @@ -1968,14 +2010,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name> + <name name="snmp_close_table" arity="1" since=""/> <fsummary>Removes the possibility for SNMP to manipulate the table.</fsummary> <desc> <p>Removes the possibility for SNMP to manipulate the table.</p> </desc> </func> <func> - <name since="">snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name> + <name name="snmp_get_mnesia_key" arity="2" since=""/> <fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary> <type> <v>Tab ::= atom()</v> @@ -1990,7 +2032,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name> + <name name="snmp_get_next_index" arity="2" since=""/> <fsummary>Gets the index of the next lexicographical row.</fsummary> <type> <v>Tab ::= atom()</v> @@ -2006,7 +2048,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name> + <name name="snmp_get_row" arity="2" since=""/> <fsummary>Retrieves a row indexed by an SNMP index.</fsummary> <type> <v>Tab ::= atom()</v> @@ -2019,7 +2061,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name since="">snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name> + <name name="snmp_open_table" arity="2" since=""/> <fsummary>Organizes a Mnesia table as an SNMP table.</fsummary> <type> <v>Tab ::= atom()</v> @@ -2073,7 +2115,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">start() -> ok | {error, Reason}</name> + <name name="start" arity="0" since=""/> <fsummary>Starts a local Mnesia system.</fsummary> <desc> <marker id="start"></marker> @@ -2115,7 +2157,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">stop() -> stopped</name> + <name name="stop" arity="0" since=""/> <fsummary>Stops Mnesia locally.</fsummary> <desc> <marker id="stop"></marker> @@ -2124,7 +2166,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name> + <name name="subscribe" arity="1" since=""/> <fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary> <desc> <marker id="subscribe"></marker> @@ -2134,7 +2176,8 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">sync_dirty(Fun [, Args]) -> ResultOfFun | exit(Reason)</name> + <name name="sync_dirty" arity="1" since=""/> + <name name="sync_dirty" arity="2" since=""/> <fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary> <desc> <marker id="sync_dirty"></marker> @@ -2150,7 +2193,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="OTP 17.0">sync_log() -> ok | {error, Reason}</name> + <name name="sync_log" arity="0" since="OTP 17.0"/> <fsummary>Performs a file sync of the local log file.</fsummary> <desc> <p>Ensures that the local transaction log file is synced to disk. @@ -2160,7 +2203,10 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">sync_transaction(Fun [, Args [, Retries]]) -> {aborted, Reason} | {atomic, ResultOfFun}</name> + <name name="sync_transaction" arity="1" since=""/> + <name name="sync_transaction" arity="2" clause_i="1" since=""/> + <name name="sync_transaction" arity="2" clause_i="2" since=""/> + <name name="sync_transaction" arity="3" since=""/> <fsummary>Synchronously executes a transaction.</fsummary> <desc> <marker id="sync_transaction"></marker> @@ -2173,7 +2219,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">system_info(InfoKey) -> Info | exit({aborted, Reason})</name> + <name name="system_info" arity="1" since=""/> <fsummary>Returns information about the Mnesia system.</fsummary> <desc> <marker id="system_info"></marker> @@ -2360,7 +2406,8 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">table(Tab [,[Option]]) -> QueryHandle</name> + <name name="table" arity="1" since=""/> + <name name="table" arity="2" since=""/> <fsummary>Return a QLC query handle.</fsummary> <desc> <marker id="table"></marker> @@ -2415,7 +2462,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name> + <name name="table_info" arity="2" since=""/> <fsummary>Returns local information about table.</fsummary> <desc> <marker id="table_info"></marker> @@ -2571,7 +2618,11 @@ mnesia:create_table(employee, </desc> </func> <func> - <name since="">transaction(Fun [, Args [, Retries]]) -> {aborted, Reason} | {atomic, ResultOfFun}</name> + <name name="transaction" arity="1" since=""/> + <name name="transaction" arity="2" clause_i="1" since=""/> + <name name="transaction" arity="2" clause_i="2" since=""/> + <name name="transaction" arity="3" since=""/> + <fsummary>Executes a transaction.</fsummary> <desc> <marker id="transaction"></marker> @@ -2593,8 +2644,8 @@ mnesia:create_table(employee, <code type="none"> add_family({family, F, M, Children}) -> ChildOids = lists:map(fun oid/1, Children), - Trans = fun() -> - mnesia:write(F#person{children = ChildOids}, + Trans = fun() -> + mnesia:write(F#person{children = ChildOids}, mnesia:write(M#person{children = ChildOids}, Write = fun(Child) -> mnesia:write(Child) end, lists:foreach(Write, Children) @@ -2639,7 +2690,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name> + <name name="transform_table" arity="4" since=""/> <fsummary>Changes format on all records in table <c>Tab</c>.</fsummary> <desc> <marker id="transform_table_4"></marker> @@ -2660,7 +2711,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name> + <name name="transform_table" arity="3" since=""/> <fsummary>Changes format on all records in table <c>Tab</c>.</fsummary> <desc> <p>Calls <c>mnesia:transform_table(Tab, Fun, @@ -2669,7 +2720,8 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name> + <name name="traverse_backup" arity="4" since=""/> + <name name="traverse_backup" arity="6" since=""/> <fsummary>Traversal of a backup.</fsummary> <desc> <marker id="traverse_backup"></marker> @@ -2691,7 +2743,7 @@ raise(Name, Amount) -> <c>{BackupItems,NewAcc}</c>, where <c>BackupItems</c> is a list of valid backup items, and <c>NewAcc</c> is a new accumulator value. The returned backup items are written - in the target backup. + in the target backup. </item> <item><c>LastAcc</c> is the last accumulator value. This is the last <c>NewAcc</c> value that was returned by <c>Fun</c>. @@ -2700,7 +2752,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">uninstall_fallback() -> ok | {error,Reason}</name> + <name name="uninstall_fallback" arity="0" since=""/> <fsummary>Uninstalls a fallback.</fsummary> <desc> <marker id="uninstall_fallback_0"></marker> @@ -2709,7 +2761,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">uninstall_fallback(Args) -> ok | {error,Reason}</name> + <name name="uninstall_fallback" arity="1" since=""/> <fsummary>Uninstalls a fallback.</fsummary> <desc> <p>Deinstalls a fallback before it @@ -2736,7 +2788,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name> + <name name="unsubscribe" arity="1" since=""/> <fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary> <desc> <marker id="unsubscribe"></marker> @@ -2746,7 +2798,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name> + <name name="wait_for_tables" arity="2" since=""/> <fsummary>Waits for tables to be accessible.</fsummary> <desc> <marker id="wait_for_tables"></marker> @@ -2757,7 +2809,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">wread({Tab, Key}) -> transaction abort | RecordList</name> + <name name="wread" arity="1" since=""/> <fsummary>Reads records with given key.</fsummary> <desc> <marker id="wread"></marker> @@ -2765,7 +2817,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">write(Record) -> transaction abort | ok</name> + <name name="write" arity="1" since=""/> <fsummary>Writes a record into the database.</fsummary> <desc> <marker id="write_1"></marker> @@ -2774,7 +2826,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">write(Tab, Record, LockKind) -> transaction abort | ok</name> + <name name="write" arity="3" since=""/> <fsummary>Writes a record into the database.</fsummary> <desc> <marker id="write_3"></marker> @@ -2790,7 +2842,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name since="">write_lock_table(Tab) -> ok | transaction abort</name> + <name name="write_lock_table" arity="1" since=""/> <fsummary>Sets write lock on an entire table.</fsummary> <desc> <marker id="write_lock_table"></marker> @@ -2874,7 +2926,7 @@ raise(Name, Amount) -> <item> <p><c>-mnesia dc_dump_limit Number</c>. Controls how often <c>disc_copies</c> tables are dumped from memory. - Tables are dumped when + Tables are dumped when <c>filesize(Log) > (filesize(Tab)/Dc_dump_limit)</c>. Lower values reduce CPU overhead but increase disk space and startup times. Default is 4.</p> @@ -3037,6 +3089,6 @@ raise(Name, Amount) -> <seeerl marker="mnesia:mnesia_registry">mnesia_registry(3)</seeerl>, <seeerl marker="stdlib:qlc">qlc(3)</seeerl></p> </section> - + </erlref> diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml index 5cda5ed72e..2fd59e6496 100644 --- a/lib/mnesia/doc/src/notes.xml +++ b/lib/mnesia/doc/src/notes.xml @@ -39,7 +39,51 @@ thus constitutes one section in this document. The title of each section is the version number of Mnesia.</p> - <section><title>Mnesia 4.16.3</title> + <section><title>Mnesia 4.17</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Make <c>mnesia:create_table/2</c> return correct badarg + value.</p> + <p> + Own Id: OTP-16072 Aux Id: PR-2320 </p> + </item> + <item> + <p> + Fixed a bug where mnesia was sometimes not waiting during + start for a commit decision on asymmetric transactions.</p> + <p> + Own Id: OTP-16634 Aux Id: PR-2610 ERL-1227 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove usage and documentation of old requests of the + I/O-protocol.</p> + <p> + Own Id: OTP-15695</p> + </item> + <item> + <p> + Avoid using <c>rpc</c> calls to do table reads, which + will reduce the load on rpc server and improve + performance.</p> + <p> + Own Id: OTP-16189</p> + </item> + </list> + </section> + +</section> + +<section><title>Mnesia 4.16.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/mnesia/doc/src/specs.xml b/lib/mnesia/doc/src/specs.xml new file mode 100644 index 0000000000..3d33a1e611 --- /dev/null +++ b/lib/mnesia/doc/src/specs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_mnesia.xml"/> + <xi:include href="../specs/specs_mnesia_frag_hash.xml"/> + <xi:include href="../specs/specs_mnesia_registry.xml"/> +</specs> diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl index 560ebca824..3f1b173e37 100644 --- a/lib/mnesia/src/mnesia.erl +++ b/lib/mnesia/src/mnesia.erl @@ -155,6 +155,7 @@ {'user_properties', proplists:proplist()}. -type t_result(Res) :: {'atomic', Res} | {'aborted', Reason::term()}. +-type result() :: ok | {'error', Reason::term()}. -type activity() :: 'ets' | 'async_dirty' | 'sync_dirty' | 'transaction' | 'sync_transaction' | {'transaction', Retries::non_neg_integer()} | {'sync_transaction', Retries::non_neg_integer()}. @@ -171,6 +172,7 @@ -type config_key() :: extra_db_nodes | dc_dump_limit. -type config_value() :: [node()] | number(). -type config_result() :: {ok, config_value()} | {error, term()}. +-type debug_level() :: 'none' | 'verbose' | 'debug' | 'trace'. -define(DEFAULT_ACCESS, ?MODULE). @@ -230,7 +232,7 @@ e_has_var(X, Pos) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Start and stop --spec start() -> 'ok' | {'error', term()}. +-spec start() -> result(). start() -> start([]). @@ -250,7 +252,7 @@ start_() -> {error, R} end. --spec start([{Option::atom(), Value::_}]) -> 'ok' | {'error', term()}. +-spec start([{Option::atom(), Value::_}]) -> result(). start(ExtraEnv) when is_list(ExtraEnv) -> case mnesia_lib:ensure_loaded(?APPLICATION) of ok -> @@ -281,8 +283,8 @@ stop() -> Other -> Other end. --spec change_config(Config::config_key(), Value::config_value()) -> - config_result(). +-spec change_config(Config, Value) -> config_result() when + Config :: config_key(), Value :: config_value(). change_config(extra_db_nodes, Ns) when is_list(Ns) -> mnesia_controller:connect_nodes(Ns); change_config(dc_dump_limit, N) when is_number(N), N > 0 -> @@ -299,6 +301,10 @@ change_config(BadKey, _BadVal) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Debugging + +-spec set_debug_level(Level :: debug_level()) -> + OldLevel :: debug_level(). + set_debug_level(Level) -> mnesia_subscr:set_debug_level(Level). @@ -371,7 +377,7 @@ transaction(Fun) -> -spec transaction(Fun, Retries) -> t_result(Res) when Fun :: fun(() -> Res), Retries :: non_neg_integer() | 'infinity'; - (Fun, [Arg::_]) -> t_result(Res) when + (Fun, Args::[Arg::_]) -> t_result(Res) when Fun :: fun((...) -> Res). transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 -> transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, async); @@ -392,9 +398,9 @@ sync_transaction(Fun) -> transaction(get(mnesia_activity_state), Fun, [], infinity, ?DEFAULT_ACCESS, sync). -spec sync_transaction(Fun, Retries) -> t_result(Res) when - Fun :: fun(() -> Res), + Fun :: fun(() -> Res) | fun((...) -> Res), Retries :: non_neg_integer() | 'infinity'; - (Fun, [Arg::_]) -> t_result(Res) when + (Fun, Args :: [Arg::_]) -> t_result(Res) when Fun :: fun((...) -> Res). sync_transaction(Fun, Retries) when is_integer(Retries), Retries >= 0 -> transaction(get(mnesia_activity_state), Fun, [], Retries, ?DEFAULT_ACCESS, sync); @@ -2637,18 +2643,18 @@ load_mnesia_or_abort() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Database mgt --spec create_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}. +-spec create_schema(Ns::[node()]) -> result(). create_schema(Ns) -> create_schema(Ns, []). --spec create_schema(Ns::[node()], [Prop]) -> 'ok' | {'error', Reason::term()} when +-spec create_schema(Ns::[node()], [Prop]) -> result() when Prop :: BackendType | IndexPlugin, BackendType :: {backend_types, [{Name::atom(), Module::module()}]}, IndexPlugin :: {index_plugins, [{{Name::atom()}, Module::module(), Function::atom()}]}. create_schema(Ns, Properties) -> mnesia_bup:create_schema(Ns, Properties). --spec delete_schema(Ns::[node()]) -> 'ok' | {'error', Reason::term()}. +-spec delete_schema(Ns::[node()]) -> result(). delete_schema(Ns) -> mnesia_schema:delete_schema(Ns). @@ -2656,12 +2662,12 @@ delete_schema(Ns) -> add_backend_type(Alias, Module) -> mnesia_schema:add_backend_type(Alias, Module). --spec backup(Dest::term()) -> 'ok' | {'error', Reason::term()}. +-spec backup(Dest::term()) -> result(). backup(Opaque) -> mnesia_log:backup(Opaque). -spec backup(Dest::term(), Mod::module()) -> - 'ok' | {'error', Reason::term()}. + result(). backup(Opaque, Mod) -> mnesia_log:backup(Opaque, Mod). @@ -2679,12 +2685,12 @@ traverse_backup(S, T, Fun, Acc) -> traverse_backup(S, SM, T, TM, F, A) -> mnesia_bup:traverse_backup(S, SM, T, TM, F, A). --spec install_fallback(Src::term()) -> 'ok' | {'error', Reason::term()}. +-spec install_fallback(Src::term()) -> result(). install_fallback(Opaque) -> mnesia_bup:install_fallback(Opaque). -spec install_fallback(Src::term(), Mod::module()|[Opt]) -> - 'ok' | {'error', Reason::term()} when + result() when Opt :: Module | Scope | Dir, Module :: {'module', Mod::module()}, Scope :: {'scope', 'global' | 'local'}, @@ -2692,11 +2698,11 @@ install_fallback(Opaque) -> install_fallback(Opaque, Mod) -> mnesia_bup:install_fallback(Opaque, Mod). --spec uninstall_fallback() -> 'ok' | {'error', Reason::term()}. +-spec uninstall_fallback() -> result(). uninstall_fallback() -> mnesia_bup:uninstall_fallback(). --spec uninstall_fallback(Args) -> 'ok' | {'error', Reason::term()} when +-spec uninstall_fallback(Args) -> result() when Args :: [{'mnesia_dir', Dir::string()}]. uninstall_fallback(Args) -> mnesia_bup:uninstall_fallback(Args). @@ -2707,16 +2713,17 @@ uninstall_fallback(Args) -> activate_checkpoint(Args) -> mnesia_checkpoint:activate(Args). --spec deactivate_checkpoint(Name::_) -> 'ok' | {'error', Reason::term()}. +-spec deactivate_checkpoint(Name::_) -> result(). deactivate_checkpoint(Name) -> mnesia_checkpoint:deactivate(Name). --spec backup_checkpoint(Name::_, Dest::_) -> 'ok' | {'error', Reason::term()}. +-spec backup_checkpoint(Name, Dest) -> result() when + Name :: term(), Dest :: term(). backup_checkpoint(Name, Opaque) -> mnesia_log:backup_checkpoint(Name, Opaque). --spec backup_checkpoint(Name::_, Dest::_, Mod::module()) -> - 'ok' | {'error', Reason::term()}. +-spec backup_checkpoint(Name, Dest, Mod) -> result() when + Name :: term(), Dest :: term(), Mod :: module(). backup_checkpoint(Name, Opaque, Mod) -> mnesia_log:backup_checkpoint(Name, Opaque, Mod). @@ -2743,7 +2750,8 @@ create_table(Name, Arg) -> delete_table(Tab) -> mnesia_schema:delete_table(Tab). --spec add_table_copy(Tab::table(), N::node(), ST::storage_type()) -> t_result(ok). +-spec add_table_copy(Tab, N, ST) -> t_result(ok) when + Tab :: table(), N::node(), ST::storage_type(). add_table_copy(Tab, N, S) -> mnesia_schema:add_table_copy(Tab, N, S). @@ -2755,10 +2763,12 @@ del_table_copy(Tab, N) -> move_table_copy(Tab, From, To) -> mnesia_schema:move_table(Tab, From, To). --spec add_table_index(Tab::table(), I::index_attr()) -> t_result(ok). +-spec add_table_index(Tab, I) -> t_result(ok) when + Tab :: table(), I :: index_attr(). add_table_index(Tab, Ix) -> mnesia_schema:add_table_index(Tab, Ix). --spec del_table_index(Tab::table(), I::index_attr()) -> t_result(ok). +-spec del_table_index(Tab, I) -> t_result(ok) when + Tab::table(), I::index_attr(). del_table_index(Tab, Ix) -> mnesia_schema:del_table_index(Tab, Ix). @@ -2842,7 +2852,7 @@ dump_tables(Tabs) -> %% allow the user to wait for some tables to be loaded -spec wait_for_tables([Tab::table()], TMO::timeout()) -> - 'ok' | {'timeout', [table()]} | {'error', Reason::term()}. + result() | {'timeout', [table()]}. wait_for_tables(Tabs, Timeout) -> mnesia_controller:wait_for_tables(Tabs, Timeout). @@ -2867,7 +2877,7 @@ change_table_load_order(T, O) -> change_table_majority(T, M) -> mnesia_schema:change_table_majority(T, M). --spec set_master_nodes(Ns::[node()]) -> 'ok' | {'error', Reason::term()}. +-spec set_master_nodes(Ns::[node()]) -> result(). set_master_nodes(Nodes) when is_list(Nodes) -> UseDir = system_info(use_dir), IsRunning = system_info(is_running), @@ -2906,8 +2916,7 @@ log_valid_master_nodes(Cstructs, Nodes, UseDir, IsRunning) -> Args = lists:map(Fun, Cstructs), mnesia_recover:log_master_nodes(Args, UseDir, IsRunning). --spec set_master_nodes(Tab::table(), Ns::[node()]) -> - 'ok' | {'error', Reason::term()}. +-spec set_master_nodes(Tab::table(), Ns::[node()]) -> result(). set_master_nodes(Tab, Nodes) when is_list(Nodes) -> UseDir = system_info(use_dir), IsRunning = system_info(is_running), @@ -2962,7 +2971,7 @@ set_master_nodes(Tab, Nodes) -> dump_log() -> mnesia_controller:sync_dump_log(user). --spec sync_log() -> 'ok' | {'error', Reason::term()}. +-spec sync_log() -> result(). sync_log() -> mnesia_monitor:sync_log(latest_log). @@ -3136,7 +3145,7 @@ snmp_filter_key(undefined, RowIndex, Tab, Store) -> load_textfile(F) -> mnesia_text:load_textfile(F). --spec dump_to_textfile(File :: file:filename()) -> 'ok' | 'error' | {'error', term()}. +-spec dump_to_textfile(File :: file:filename()) -> result() | 'error'. dump_to_textfile(F) -> mnesia_text:dump_to_textfile(F). diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl index 2ccea1ea6d..8749b625a1 100644 --- a/lib/mnesia/src/mnesia_recover.erl +++ b/lib/mnesia/src/mnesia_recover.erl @@ -449,6 +449,9 @@ wait_for_decision(D, InitBy, N) -> if Outcome =:= committed -> {Tid, committed}; Outcome =:= aborted -> {Tid, aborted}; + InitBy == startup -> + {ok, Res} = call({wait_for_decision, D}), + {Tid, Res}; Outcome =:= presume_abort -> case N > Max of true -> {Tid, aborted}; @@ -460,10 +463,7 @@ wait_for_decision(D, InitBy, N) -> %% Wait a while for active transactions %% to end and try again timer:sleep(100), - wait_for_decision(D, InitBy, N); - InitBy == startup -> - {ok, Res} = call({wait_for_decision, D}), - {Tid, Res} + wait_for_decision(D, InitBy, N) end. still_pending([Tid | Pending]) -> diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk index ae849f2771..ba006208cd 100644 --- a/lib/mnesia/vsn.mk +++ b/lib/mnesia/vsn.mk @@ -1 +1 @@ -MNESIA_VSN = 4.16.3 +MNESIA_VSN = 4.17 diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index b27de66984..2533f99bd5 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -32,6 +32,21 @@ <p>This document describes the changes made to the Observer application.</p> +<section><title>Observer 2.9.4</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Minor updates due to the new spawn improvements made.</p> + <p> + Own Id: OTP-16368 Aux Id: OTP-15251 </p> + </item> + </list> + </section> + +</section> + <section><title>Observer 2.9.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src index 55de6de0c6..ca65ee703e 100644 --- a/lib/observer/src/observer.app.src +++ b/lib/observer/src/observer.app.src @@ -66,7 +66,7 @@ {registered, []}, {applications, [kernel, stdlib]}, {env, []}, - {runtime_dependencies, ["wx-1.2","stdlib-@OTP-15251@","runtime_tools-1.8.14", - "kernel-@OTP-15251@","et-1.5","erts-@OTP-15251@"]}]}. + {runtime_dependencies, ["wx-1.2","stdlib-3.13","runtime_tools-1.8.14", + "kernel-7.0","et-1.5","erts-11.0"]}]}. diff --git a/lib/observer/src/observer_alloc_wx.erl b/lib/observer/src/observer_alloc_wx.erl index 1b144e05dc..a7b55f0f72 100644 --- a/lib/observer/src/observer_alloc_wx.erl +++ b/lib/observer/src/observer_alloc_wx.erl @@ -158,7 +158,7 @@ handle_info({refresh, Seq}, State#state.active andalso (catch wxWindow:refresh(Panel)), erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}), if Seq =:= (trunc(DispF)-1) -> - Req = rpc:async_call(Node, observer_backend, sys_info, []), + Req = request_info(Node), {noreply, State#state{time=Ti#ti{tick=Next}, async=Req}}; true -> {noreply, State#state{time=Ti#ti{tick=Next}}} @@ -193,6 +193,13 @@ code_change(_, _, State) -> %%%%%%%%%% +request_info(Node) -> + ReplyTo = self(), + spawn(fun() -> + Res = rpc:call(Node, observer_backend, sys_info, []), + ReplyTo ! {self(), {promise_reply, Res}} + end). + restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) -> case rpc:call(Node, observer_backend, sys_info, []) of {badrpc, _} -> State; diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index 6b733687b8..4de0cc113f 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 2.9.3 +OBSERVER_VSN = 2.9.4 diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c index fb4f61417e..996c122199 100644 --- a/lib/odbc/c_src/odbcserver.c +++ b/lib/odbc/c_src/odbcserver.c @@ -304,7 +304,7 @@ int main(void) static void spawn_sup(const char *port) { DWORD threadId; - (HANDLE)_beginthreadex(NULL, 0, supervise, port, 0, &threadId); + _beginthreadex(NULL, 0, supervise, port, 0, &threadId); } #elif defined(UNIX) static void spawn_sup(const char *port) diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h index 0461c57d1f..28bb2b9030 100644 --- a/lib/odbc/c_src/odbcserver.h +++ b/lib/odbc/c_src/odbcserver.h @@ -120,7 +120,7 @@ /*------------------------ TYPDEFS ----------------------------------*/ -typedef char byte; +typedef unsigned char byte; typedef int Boolean; typedef struct { diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml index 8d708162e4..d8c6126496 100644 --- a/lib/odbc/doc/src/notes.xml +++ b/lib/odbc/doc/src/notes.xml @@ -32,7 +32,35 @@ <p>This document describes the changes made to the odbc application. </p> - <section><title>ODBC 2.12.4</title> + <section><title>ODBC 2.13</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix various compiler warnings on 64-bit Windows.</p> + <p> + Own Id: OTP-15800</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Rewrite due to the removal of <c>erl_interface</c> legacy + functions.</p> + <p> + Own Id: OTP-16544 Aux Id: OTP-16328 </p> + </item> + </list> + </section> + +</section> + +<section><title>ODBC 2.12.4</title> <section><title>Improvements and New Features</title> <list> diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk index df6db09f2f..cf271f3505 100644 --- a/lib/odbc/vsn.mk +++ b/lib/odbc/vsn.mk @@ -1 +1 @@ -ODBC_VSN = 2.12.4 +ODBC_VSN = 2.13 diff --git a/lib/os_mon/c_src/nteventlog/elog_main.c b/lib/os_mon/c_src/nteventlog/elog_main.c index c31e3ef1ff..f1baaa4084 100644 --- a/lib/os_mon/c_src/nteventlog/elog_main.c +++ b/lib/os_mon/c_src/nteventlog/elog_main.c @@ -266,11 +266,11 @@ BOOL output_record(char *category, EVENTLOGRECORD *event){ strlen(PIPE_LOG_FORMAT) + PIPE_LOG_EXTRA); sprintf(buff,PIPE_LOG_FORMAT, - strlen(tbuff),tbuff, - strlen(category), category, - strlen(fac), fac, - strlen(sev), sev, - strlen(bigbuff), bigbuff); + (int)strlen(tbuff), tbuff, + (int)strlen(category), category, + (int)strlen(fac), fac, + (int)strlen(sev), sev, + (int)strlen(bigbuff), bigbuff); ret = data_to_pipe(buff,ackbuff, ACK_MAX); if(ret && strcmp(ackbuff,PIPE_LOG_ACK)) ret = FALSE; diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml index 63efa96e2f..b08ade9938 100644 --- a/lib/os_mon/doc/src/notes.xml +++ b/lib/os_mon/doc/src/notes.xml @@ -31,6 +31,33 @@ </header> <p>This document describes the changes made to the OS_Mon application.</p> +<section><title>Os_Mon 2.5.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix various compiler warnings on 64-bit Windows.</p> + <p> + Own Id: OTP-15800</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + </list> + </section> + +</section> + <section><title>Os_Mon 2.5.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk index 6081e181ff..e4b7574e02 100644 --- a/lib/os_mon/vsn.mk +++ b/lib/os_mon/vsn.mk @@ -1 +1 @@ -OS_MON_VSN = 2.5.1 +OS_MON_VSN = 2.5.2 diff --git a/lib/parsetools/doc/src/notes.xml b/lib/parsetools/doc/src/notes.xml index f8cd9b972d..3975c55c6c 100644 --- a/lib/parsetools/doc/src/notes.xml +++ b/lib/parsetools/doc/src/notes.xml @@ -31,6 +31,22 @@ </header> <p>This document describes the changes made to the Parsetools application.</p> +<section><title>Parsetools 2.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove usage and documentation of old requests of the + I/O-protocol.</p> + <p> + Own Id: OTP-15695</p> + </item> + </list> + </section> + +</section> + <section><title>Parsetools 2.1.8</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/parsetools/vsn.mk b/lib/parsetools/vsn.mk index 1a5201ce5d..c18fcbe762 100644 --- a/lib/parsetools/vsn.mk +++ b/lib/parsetools/vsn.mk @@ -1 +1 @@ -PARSETOOLS_VSN = 2.1.8 +PARSETOOLS_VSN = 2.2 diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1 index ff3250b383..e1d8f2e121 100644 --- a/lib/public_key/asn1/OTP-PKIX.asn1 +++ b/lib/public_key/asn1/OTP-PKIX.asn1 @@ -122,7 +122,9 @@ IMPORTS sha224WithRSAEncryption, sha256WithRSAEncryption, sha384WithRSAEncryption, - sha512WithRSAEncryption + sha512WithRSAEncryption, + id-RSASSA-PSS, + RSASSA-PSS-params FROM PKCS-1 { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) @@ -341,6 +343,7 @@ SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= { sha256-with-rsa-encryption | sha384-with-rsa-encryption | sha512-with-rsa-encryption | + rsassa-pss | ecdsa-with-sha1 | ecdsa-with-sha224 | ecdsa-with-sha256 | @@ -348,7 +351,7 @@ SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= { ecdsa-with-sha512 } SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= { - dsa | rsa-encryption | dh | kea | ec-public-key } + dsa | rsa-encryption | rsa-pss | dh | kea | ec-public-key } -- DSA Keys and Signatures @@ -430,6 +433,11 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= { ID sha512WithRSAEncryption TYPE NULL } + rsassa-pss SIGNATURE-ALGORITHM-CLASS ::= { + ID id-RSASSA-PSS + TYPE RSASSA-PSS-params } + + -- Certificate.signature -- See PKCS #1 (RFC 2313). XXX @@ -440,6 +448,11 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= { TYPE NULL PUBLIC-KEY-TYPE RSAPublicKey } + rsa-pss PUBLIC-KEY-ALGORITHM-CLASS ::= { + ID id-RSASSA-PSS + TYPE RSASSA-PSS-params + PUBLIC-KEY-TYPE RSAPublicKey } + -- -- Diffie-Hellman Keys -- @@ -494,6 +507,7 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= { ID ecdsa-with-SHA512 TYPE EcpkParameters } -- XXX Must be empty and not NULL + FIELD-ID-CLASS ::= CLASS { &id OBJECT IDENTIFIER UNIQUE, &Type } diff --git a/lib/public_key/asn1/PKCS-1.asn1 b/lib/public_key/asn1/PKCS-1.asn1 index 117eacd8ad..6fb7ccb981 100644 --- a/lib/public_key/asn1/PKCS-1.asn1 +++ b/lib/public_key/asn1/PKCS-1.asn1 @@ -1,10 +1,15 @@ +-- PKCS #1 v2.2 ASN.1 Module +-- Revised October 27, 2012 +-- (plain merged with previous version to support all that we need) + +-- This module has been checked for conformance with the +-- ASN.1 standard by the OSS ASN.1 Tools + PKCS-1 { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) modules(0) pkcs-1(1) } --- $Revision: 1.1 $ - DEFINITIONS EXPLICIT TAGS ::= BEGIN @@ -15,43 +20,74 @@ BEGIN -- gov(101) csor(3) nistalgorithm(4) modules(0) sha2(1) -- }; +-- ============================ +-- Basic object identifiers +-- ============================ +-- The DER encoding of this in hexadecimal is: +-- (0x)06 08 +-- 2A 86 48 86 F7 0D 01 01 + pkcs-1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } +-- +-- When rsaEncryption is used in an AlgorithmIdentifier the parameters +-- MUST be present and MUST be NULL. +-- rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 } +-- +-- When id-RSAES-OAEP is used in an AlgorithmIdentifier the parameters MUST +-- be present and MUST be RSAES-OAEP-params. +-- id-RSAES-OAEP OBJECT IDENTIFIER ::= { pkcs-1 7 } -id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 } +-- +-- When id-pSpecified is used in an AlgorithmIdentifier the parameters MUST +-- be an OCTET STRING. +-- +id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 } +-- +-- When id-RSASSA-PSS is used in an AlgorithmIdentifier the parameters MUST +-- be present and MUST be RSASSA-PSS-params. +-- id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 } -md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 } -md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } -sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } -sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } -sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } -sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } -sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 } +-- +-- When the following OIDs are used in an AlgorithmIdentifier the parameters +-- MUST be present and MUST be NULL. +-- +md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 } +md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 } +sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 } +sha224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 14 } +sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 } +sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 } +sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 } +sha512-224WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 15 } +sha512-256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 16 } -- ISO oid - equvivalent to sha1WithRSAEncryption sha-1WithRSAEncryption OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) sha-1WithRSAEncryption(29)} - -id-sha1 OBJECT IDENTIFIER ::= { - iso(1) identified-organization(3) oiw(14) secsig(3) - algorithms(2) 26 +-- +-- This OID really belongs in a module with the secsig OIDs. +-- +id-sha1 OBJECT IDENTIFIER ::= { + iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 } - +-- +-- OIDs for MD2 and MD5, allowed only in EMSA-PKCS1-v1_5. +-- id-md2 OBJECT IDENTIFIER ::= { - iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2 -} + iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2} id-md5 OBJECT IDENTIFIER ::= { - iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5 -} + iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5} +-- Additional oids that where included previously... id-hmacWithSHA224 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 8 } @@ -68,8 +104,6 @@ id-hmacWithSHA512 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 11 } -id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 } - id-sha224 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistalgorithm(4) hashalgs(2) 4 } @@ -86,68 +120,255 @@ id-sha512 OBJECT IDENTIFIER ::= { joint-iso-itu-t(2) country(16) us(840) organization(1) gov(101) csor(3) nistalgorithm(4) hashalgs(2) 3 } +-- +-- When id-mgf1 is used in an AlgorithmIdentifier the parameters MUST be +-- present and MUST be a HashAlgorithm, for example sha1. +-- +id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 } + +-- ================ +-- Useful types +-- ================ +ALGORITHM-IDENTIFIER ::= CLASS { + &id OBJECT IDENTIFIER UNIQUE, + &Type OPTIONAL +} -RSAPublicKey ::= SEQUENCE { - modulus INTEGER, -- n - publicExponent INTEGER -- e + WITH SYNTAX { OID &id [PARAMETERS &Type] } +-- Note: the parameter InfoObjectSet in the following definitions allows a +-- distinct information object set to be specified for sets of algorithms +-- such as: +-- DigestAlgorithms ALGORITHM-IDENTIFIER ::= { +-- { OID id-md2 PARAMETERS NULL }| +-- { OID id-md5 PARAMETERS NULL }| +-- { OID id-sha1 PARAMETERS NULL } +-- } +-- +AlgorithmIdentifier-PKCS1 { ALGORITHM-IDENTIFIER:InfoObjectSet } ::= SEQUENCE { + algorithm + ALGORITHM-IDENTIFIER.&id({InfoObjectSet}), + parameters + ALGORITHM-IDENTIFIER.&Type({InfoObjectSet}{@.algorithm}) OPTIONAL +} + +-- ============== +-- Algorithms +-- ============== +-- +-- Allowed EME-OAEP and EMSA-PSS digest algorithms. +-- +OAEP-PSSDigestAlgorithms ALGORITHM-IDENTIFIER ::= { + { OID id-sha1 PARAMETERS NULL }| + { OID id-sha256 PARAMETERS NULL }| + { OID id-sha384 PARAMETERS NULL }| + { OID id-sha512 PARAMETERS NULL }, + ... -- Allows for future expansion -- +} +-- +-- Allowed EMSA-PKCS1-v1_5 digest algorithms. +-- +PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= { + { OID id-md2 PARAMETERS NULL }| + { OID id-md5 PARAMETERS NULL }| + { OID id-sha1 PARAMETERS NULL }| + { OID id-sha256 PARAMETERS NULL }| + { OID id-sha384 PARAMETERS NULL }| + { OID id-sha512 PARAMETERS NULL } +} + +-- When id-md2 and id-md5 are used in an AlgorithmIdentifier, the +-- parameters field shall have a value of type NULL. + +-- When id-sha1, id-sha224, id-sha256, id-sha384, id-sha512, +-- id-sha512-224, and id-sha512-256 are used in an +-- AlgorithmIdentifier, the parameters (which are optional) SHOULD be +-- omitted, but if present, they SHALL have a value of type NULL. +-- However, implementations MUST accept AlgorithmIdentifier values +-- both without parameters and with NULL parameters. + +-- Exception: When formatting the DigestInfoValue in EMSA-PKCS1-v1_5 +-- (see Section 9.2), the parameters field associated with id-sha1, +-- id-sha224, id-sha256, id-sha384, id-sha512, id-sha512-224, and +-- id-sha512-256 SHALL have a value of type NULL. This is to +-- maintain compatibility with existing implementations and with the +-- numeric information values already published for EMSA-PKCS1-v1_5, +-- which are also reflected in IEEE 1363a. + +sha1 HashAlgorithm ::= { + algorithm id-sha1, + parameters SHA1Parameters : NULL +} + +HashAlgorithm ::= AlgorithmIdentifier-PKCS1 { {OAEP-PSSDigestAlgorithms} } +SHA1Parameters ::= NULL + +-- +-- Allowed mask generation function algorithms. +-- If the identifier is id-mgf1, the parameters are a HashAlgorithm. +-- +PKCS1MGFAlgorithms ALGORITHM-IDENTIFIER ::= { + { OID id-mgf1 PARAMETERS HashAlgorithm }, + ... -- Allows for future expansion -- +} + +-- +-- Default AlgorithmIdentifier for id-RSAES-OAEP.maskGenAlgorithm and +-- id-RSASSA-PSS.maskGenAlgorithm. +-- +mgf1SHA1 MaskGenAlgorithm ::= { + algorithm id-mgf1, + parameters HashAlgorithm : sha1 } +MaskGenAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1MGFAlgorithms} } + +-- +-- Allowed algorithms for pSourceAlgorithm. +-- +PKCS1PSourceAlgorithms ALGORITHM-IDENTIFIER ::= { + { OID id-pSpecified PARAMETERS EncodingParameters }, + ... -- Allows for future expansion -- +} + +EncodingParameters ::= OCTET STRING(SIZE(0..MAX)) + +-- +-- This identifier means that the label L is an empty string, so the digest +-- of the empty string appears in the RSA block before masking. +-- +pSpecifiedEmpty PSourceAlgorithm ::= { + algorithm id-pSpecified, + parameters EncodingParameters : emptyString +} +PSourceAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1PSourceAlgorithms} } + +emptyString EncodingParameters ::= ''H + +-- +-- Type identifier definitions for the PKCS #1 OIDs. +-- +PKCS1Algorithms ALGORITHM-IDENTIFIER ::= { + { OID rsaEncryption PARAMETERS NULL } | + { OID md2WithRSAEncryption PARAMETERS NULL } | + { OID md5WithRSAEncryption PARAMETERS NULL } | + { OID sha1WithRSAEncryption PARAMETERS NULL } | + { OID sha256WithRSAEncryption PARAMETERS NULL } | + { OID sha384WithRSAEncryption PARAMETERS NULL } | + { OID sha512WithRSAEncryption PARAMETERS NULL } | + { OID id-RSAES-OAEP PARAMETERS RSAES-OAEP-params } | + PKCS1PSourceAlgorithms | + { OID id-RSASSA-PSS PARAMETERS RSASSA-PSS-params } , + ... -- Allows for future expansion -- +} +-- =================== +-- Main structures +-- =================== +RSAPublicKey ::= SEQUENCE { + modulus INTEGER, -- n + publicExponent INTEGER -- e +} +-- +-- Representation of RSA private key with information for the CRT algorithm. +-- RSAPrivateKey ::= SEQUENCE { - version Version, - modulus INTEGER, -- n - publicExponent INTEGER, -- e - privateExponent INTEGER, -- d - prime1 INTEGER, -- p - prime2 INTEGER, -- q - exponent1 INTEGER, -- d mod (p-1) - exponent2 INTEGER, -- d mod (q-1) - coefficient INTEGER, -- (inverse of q) mod p - otherPrimeInfos OtherPrimeInfos OPTIONAL + version Version, + modulus INTEGER, -- n + publicExponent INTEGER, -- e + privateExponent INTEGER, -- d + prime1 INTEGER, -- p + prime2 INTEGER, -- q + exponent1 INTEGER, -- d mod (p-1) + exponent2 INTEGER, -- d mod (q-1) + coefficient INTEGER, -- (inverse of q) mod p + otherPrimeInfos OtherPrimeInfos OPTIONAL } Version ::= INTEGER { two-prime(0), multi(1) } - (CONSTRAINED BY { - -- version must be multi if otherPrimeInfos present -- - }) + (CONSTRAINED BY {-- version must be multi if otherPrimeInfos present --}) OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo OtherPrimeInfo ::= SEQUENCE { - prime INTEGER, -- ri - exponent INTEGER, -- di - coefficient INTEGER -- ti + prime INTEGER, -- ri + exponent INTEGER, -- di + coefficient INTEGER -- ti } -Algorithm ::= SEQUENCE { - algorithm OBJECT IDENTIFIER, - parameters ANY DEFINED BY algorithm OPTIONAL +-- +-- AlgorithmIdentifier.parameters for id-RSAES-OAEP. +-- Note that the tags in this Sequence are explicit. +-- + +RSAES-OAEP-params ::= SEQUENCE { + hashAlgorithm [0] HashAlgorithm DEFAULT sha1, + maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, + pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty } -AlgorithmNull ::= SEQUENCE { - algorithm OBJECT IDENTIFIER, - parameters NULL +-- +-- Identifier for default RSAES-OAEP algorithm identifier. +-- The DER Encoding of this is in hexadecimal: +-- (0x)30 0D +-- 06 09 +-- 2A 86 48 86 F7 0D 01 01 07 +-- 30 00 +-- Notice that the DER encoding of default values is "empty". +-- +rSAES-OAEP-Default-Identifier RSAES-AlgorithmIdentifier ::= { + algorithm id-RSAES-OAEP, + parameters RSAES-OAEP-params : { + hashAlgorithm sha1, + maskGenAlgorithm mgf1SHA1, + pSourceAlgorithm pSpecifiedEmpty + } } +RSAES-AlgorithmIdentifier ::= AlgorithmIdentifier-PKCS1 { + {PKCS1Algorithms} +} +-- +-- AlgorithmIdentifier.parameters for id-RSASSA-PSS. +-- Note that the tags in this Sequence are explicit. +-- RSASSA-PSS-params ::= SEQUENCE { - hashAlgorithm [0] Algorithm, -- DEFAULT sha1, - maskGenAlgorithm [1] Algorithm, -- DEFAULT mgf1SHA1, - saltLength [2] INTEGER DEFAULT 20, - trailerField [3] TrailerField DEFAULT trailerFieldBC + hashAlgorithm [0] HashAlgorithm DEFAULT sha1, + maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, + saltLength [2] INTEGER DEFAULT 20, + trailerField [3] TrailerField DEFAULT trailerFieldBC } TrailerField ::= INTEGER { trailerFieldBC(1) } -DigestInfo ::= SEQUENCE { - digestAlgorithm Algorithm, - digest OCTET STRING +-- +-- Identifier for default RSASSA-PSS algorithm identifier +-- The DER Encoding of this is in hexadecimal: +-- (0x)30 0D +-- 06 09 +-- 2A 86 48 86 F7 0D 01 01 0A +-- 30 00 +-- Notice that the DER encoding of default values is "empty". +-- +rSASSA-PSS-Default-Identifier RSASSA-AlgorithmIdentifier ::= { + algorithm id-RSASSA-PSS, + parameters RSASSA-PSS-params : { + hashAlgorithm sha1, + maskGenAlgorithm mgf1SHA1, + saltLength 20,trailerField trailerFieldBC + } +} +RSASSA-AlgorithmIdentifier ::= AlgorithmIdentifier-PKCS1 { + {PKCS1Algorithms} } -DigestInfoNull ::= SEQUENCE { - digestAlgorithm AlgorithmNull, +-- +-- Syntax for the EMSA-PKCS1-v1_5 hash identifier. +-- +DigestInfo ::= SEQUENCE { + digestAlgorithm DigestAlgorithm, digest OCTET STRING } +DigestAlgorithm ::= AlgorithmIdentifier-PKCS1 { {PKCS1-v1-5DigestAlgorithms} } -END -- PKCS1Definitions - +END -- PKCS1Definitions diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index c182a28c53..b15e745252 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -35,6 +35,30 @@ <file>notes.xml</file> </header> +<section><title>Public_Key 1.8</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Added support for RSA-PSS signature schemes</p> + <p> + Own Id: OTP-15247</p> + </item> + <item> + <p> + Calls of deprecated functions in the <seeguide + marker="crypto:new_api#the-old-api">Old Crypto + API</seeguide> are replaced by calls of their <seeguide + marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p> + <p> + Own Id: OTP-16346</p> + </item> + </list> + </section> + +</section> + <section><title>Public_Key 1.7.2</title> <section><title>Improvements and New Features</title> diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 9c5aaa9812..3e72f88894 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -100,6 +100,7 @@ <datatype> <name name="public_key"/> <name name="rsa_public_key"/> + <name name="rsa_pss_public_key"/> <name name="dsa_public_key"/> <name name="ec_public_key"/> <name name="ecpk_parameters"/> @@ -118,6 +119,7 @@ <datatype> <name name="private_key"/> <name name="rsa_private_key"/> + <name name="rsa_pss_private_key"/> <name name="dsa_private_key"/> <name name="ec_private_key"/> <desc> @@ -405,8 +407,8 @@ <v>CertChain = [der_encoded()]</v> <d>A list of DER-encoded certificates in trust order ending with the peer certificate.</d> <v>Options = proplists:proplist()</v> - <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-dsa', - rsa_public_key() | integer(), 'NULL' | 'Dss-Parms'{}}</v> + <v>PublicKeyInfo = {?'rsaEncryption' | ?'id-RSASSA-PSS'| ?'id-dsa', + rsa_public_key() | integer(), 'NULL' | 'RSASSA-PSS-params'{} | 'Dss-Parms'{}}</v> <v>PolicyTree = term()</v> <d>At the moment this is always an empty list as policies are not currently supported.</d> <v>Reason = cert_expired | invalid_issuer | invalid_signature | name_not_permitted | @@ -582,7 +584,15 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, <p> Extracts distribution points from the certificates extensions.</p> </desc> </func> - + + <func> + <name name="pkix_hash_type" arity="1" since="@master@"/> + <fsummary>Translates OID to Erlang digest type</fsummary> + <desc> + <p>Translates OID to Erlang digest type</p> + </desc> + </func> + <func> <name name="pkix_match_dist_point" arity="2" since="OTP 19.0"/> <fsummary>Checks whether the given distribution point matches the diff --git a/lib/public_key/doc/src/public_key_records.xml b/lib/public_key/doc/src/public_key_records.xml index 8075ecc4d1..3036d45c00 100644 --- a/lib/public_key/doc/src/public_key_records.xml +++ b/lib/public_key/doc/src/public_key_records.xml @@ -138,7 +138,22 @@ prime, % integer() exponent, % integer() coefficient % integer() - }. </code> + }. + +#'RSASSA-PSS-params'{hashAlgorithm, % #'HashAlgorithm'{}}, + maskGenAlgorithm, % #'MaskGenAlgorithm'{}}, + saltLength, % integer(), + trailerField, % integer() + }. + +#'HashAlgorithm'{algorithm, % oid() + parameters % defaults to asn1_NOVALUE + }. + +#'MaskGenAlgorithm'{algorithm, % oid() + parameters, % defaults to asn1_NOVALUE + }. + </code> </section> diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index a5836f6d07..1680845075 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -24,16 +24,29 @@ -include("public_key.hrl"). --export([init_validation_state/3, prepare_for_next_cert/2, - validate_time/3, validate_signature/6, - validate_issuer/4, validate_names/6, +-export([init_validation_state/3, + prepare_for_next_cert/2, + validate_time/3, + validate_signature/6, + validate_issuer/4, + validate_names/6, validate_extensions/4, - normalize_general_name/1, is_self_signed/1, - is_issuer/2, issuer_id/2, distribution_points/1, - is_fixed_dh_cert/1, verify_data/1, verify_fun/4, - select_extension/2, match_name/3, - extensions_list/1, cert_auth_key_id/1, time_str_2_gregorian_sec/1, - gen_test_certs/1, root_cert/2]). + normalize_general_name/1, + is_self_signed/1, + is_issuer/2, + issuer_id/2, + distribution_points/1, + is_fixed_dh_cert/1, + verify_data/1, + verify_fun/4, + select_extension/2, + match_name/3, + extensions_list/1, + cert_auth_key_id/1, + time_str_2_gregorian_sec/1, + gen_test_certs/1, + x509_pkix_sign_types/1, + root_cert/2]). -define(NULL, 0). @@ -503,6 +516,17 @@ gen_test_certs( DERCAs = ca_config(RootCert, CAsKeys), [{cert, DERCert}, {key, DERKey}, {cacerts, DERCAs}]. + +x509_pkix_sign_types(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS', + parameters = #'RSASSA-PSS-params'{hashAlgorithm = #'HashAlgorithm'{algorithm = Alg}}}) -> + Hash = public_key:pkix_hash_type(Alg), + {Hash, rsa_pss_pss, [{rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, -1}, + {rsa_mgf1_md, Hash}]}; +x509_pkix_sign_types(#'SignatureAlgorithm'{algorithm = Alg}) -> + {Hash, Sign} = public_key:pkix_sign_types(Alg), + {Hash, Sign, []}. + %%% -spec root_cert(string(), [cert_opt()]) -> test_root_cert(). %% @@ -511,13 +535,16 @@ root_cert(Name, Opts) -> PrivKey = gen_key(proplists:get_value(key, Opts, default_key_gen())), TBS = cert_template(), Issuer = subject("root", Name), + SignatureId = sign_algorithm(PrivKey, Opts), + SPI = public_key(PrivKey, SignatureId), + OTPTBS = TBS#'OTPTBSCertificate'{ - signature = sign_algorithm(PrivKey, Opts), + signature = SignatureId, issuer = Issuer, validity = validity(Opts), subject = Issuer, - subjectPublicKeyInfo = public_key(PrivKey), + subjectPublicKeyInfo = SPI, extensions = extensions(undefined, ca, Opts) }, #{cert => public_key:pkix_sign(OTPTBS, PrivKey), @@ -552,17 +579,21 @@ extensions_list(Extensions) -> extract_verify_data(OtpCert, DerCert) -> Signature = OtpCert#'OTPCertificate'.signature, - SigAlgRec = OtpCert#'OTPCertificate'.signatureAlgorithm, - SigAlg = SigAlgRec#'SignatureAlgorithm'.algorithm, + SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm, PlainText = encoded_tbs_cert(DerCert), - {DigestType,_} = public_key:pkix_sign_types(SigAlg), + {DigestType,_,_} = x509_pkix_sign_types(SigAlg), {DigestType, PlainText, Signature}. verify_signature(OtpCert, DerCert, Key, KeyParams) -> {DigestType, PlainText, Signature} = extract_verify_data(OtpCert, DerCert), case Key of #'RSAPublicKey'{} -> - public_key:verify(PlainText, DigestType, Signature, Key); + case KeyParams of + #'RSASSA-PSS-params'{} -> + public_key:verify(PlainText, DigestType, Signature, Key, verify_options(KeyParams)); + 'NULL' -> + public_key:verify(PlainText, DigestType, Signature, Key) + end; _ -> public_key:verify(PlainText, DigestType, Signature, {Key, KeyParams}) end. @@ -1119,6 +1150,8 @@ is_key(#'DSAPrivateKey'{}) -> true; is_key(#'RSAPrivateKey'{}) -> true; +is_key({#'RSAPrivateKey'{}, _}) -> + true; is_key(#'ECPrivateKey'{}) -> true; is_key(_) -> @@ -1174,10 +1207,18 @@ validity(Opts) -> #'Validity'{notBefore={generalTime, Format(DefFrom)}, notAfter ={generalTime, Format(DefTo)}}. -sign_algorithm(#'RSAPrivateKey'{}, Opts) -> - Type = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)), - #'SignatureAlgorithm'{algorithm = Type, - parameters = 'NULL'}; +sign_algorithm(#'RSAPrivateKey'{} = Key , Opts) -> + case proplists:get_value(rsa_padding, Opts, rsa_pkcs1_pss_padding) of + rsa_pkcs1_pss_padding -> + DigestId = rsa_digest_oid(proplists:get_value(digest, Opts, sha1)), + rsa_sign_algo(Key, DigestId, 'NULL'); + rsa_pss_rsae -> + DigestId = rsa_digest_oid(proplists:get_value(digest, Opts, sha256)), + rsa_sign_algo(Key, DigestId, 'NULL') + end; +sign_algorithm({#'RSAPrivateKey'{} = Key,#'RSASSA-PSS-params'{} = Params}, _Opts) -> + rsa_sign_algo(Key, ?'id-RSASSA-PSS', Params); + sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> #'SignatureAlgorithm'{algorithm = ?'id-dsa-with-sha1', parameters = {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; @@ -1185,6 +1226,16 @@ sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) -> Type = ecdsa_digest_oid(proplists:get_value(digest, Opts, sha1)), #'SignatureAlgorithm'{algorithm = Type, parameters = Parms}. + +rsa_sign_algo(#'RSAPrivateKey'{}, ?'id-RSASSA-PSS' = Type, #'RSASSA-PSS-params'{} = Params) -> + #'SignatureAlgorithm'{algorithm = Type, + parameters = Params}; +rsa_sign_algo(#'RSAPrivateKey'{}, Type, Parms) -> + #'SignatureAlgorithm'{algorithm = Type, + parameters = Parms}. + +rsa_digest_oid(Oid) when is_tuple(Oid) -> + Oid; rsa_digest_oid(sha1) -> ?'sha1WithRSAEncryption'; rsa_digest_oid(sha) -> @@ -1196,8 +1247,10 @@ rsa_digest_oid(sha384) -> rsa_digest_oid(sha256) -> ?'sha256WithRSAEncryption'; rsa_digest_oid(md5) -> - ?'md5WithRSAEncryption'. + ?'md5WithRSAEncryption'. +ecdsa_digest_oid(Oid) when is_tuple(Oid) -> + Oid; ecdsa_digest_oid(sha1) -> ?'ecdsa-with-SHA1'; ecdsa_digest_oid(sha) -> @@ -1229,12 +1282,13 @@ cert_chain(Role, IssuerCert, IssuerKey, [CAOpts | Rest], N, Acc) -> cert(Role, #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = Issuer}}, PrivKey, Key, Contact, Name, Opts, Type) -> TBS = cert_template(), + SignAlgoId = sign_algorithm(PrivKey, Opts), OTPTBS = TBS#'OTPTBSCertificate'{ - signature = sign_algorithm(PrivKey, Opts), + signature = SignAlgoId, issuer = Issuer, validity = validity(Opts), subject = subject(Contact, atom_to_list(Role) ++ Name), - subjectPublicKeyInfo = public_key(Key), + subjectPublicKeyInfo = public_key(Key, SignAlgoId), extensions = extensions(Role, Type, Opts) }, public_key:pkix_sign(OTPTBS, PrivKey). @@ -1251,19 +1305,33 @@ default_key_gen() -> {namedCurve, Oid} end. -public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> +public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}, + #'SignatureAlgorithm'{algorithm = ?rsaEncryption, + parameters = #'RSASSA-PSS-params'{} = Params}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters = Params}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +public_key({#'RSAPrivateKey'{modulus=N, publicExponent=E}, #'RSASSA-PSS-params'{} = Params}, + #'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS', + parameters = #'RSASSA-PSS-params'{} = Params}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-RSASSA-PSS', parameters= Params}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +public_key(#'RSAPrivateKey'{modulus=N, publicExponent=E}, _) -> Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Public}; -public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> +public_key(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}, _) -> Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}; public_key(#'ECPrivateKey'{version = _Version, privateKey = _PrivKey, parameters = Params, - publicKey = PubKey}) -> + publicKey = PubKey}, _) -> Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = #'ECPoint'{point = PubKey}}. @@ -1306,6 +1374,9 @@ add_default_extensions(Defaults0, Exts) -> end, Defaults0), Exts ++ Defaults. +encode_key({#'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}} = Key) -> + {Asn1Type, DER, _} = public_key:pem_entry_encode('PrivateKeyInfo', Key), + {Asn1Type, DER}; encode_key(#'RSAPrivateKey'{} = Key) -> {'RSAPrivateKey', public_key:der_encode('RSAPrivateKey', Key)}; encode_key(#'ECPrivateKey'{} = Key) -> @@ -1313,3 +1384,11 @@ encode_key(#'ECPrivateKey'{} = Key) -> encode_key(#'DSAPrivateKey'{} = Key) -> {'DSAPrivateKey', public_key:der_encode('DSAPrivateKey', Key)}. +verify_options(#'RSASSA-PSS-params'{saltLength = SaltLen, + maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = HashOid}}}) -> + HashAlgo = public_key:pkix_hash_type(HashOid), + [{rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, SaltLen}, + {rsa_mgf1_md, HashAlgo}]. diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl index cdb3723bf3..b556314ad1 100644 --- a/lib/public_key/src/pubkey_cert_records.erl +++ b/lib/public_key/src/pubkey_cert_records.erl @@ -110,7 +110,8 @@ supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey'; supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey'; supportedPublicKeyAlgorithms(?'dhpublicnumber') -> 'DHPublicKey'; supportedPublicKeyAlgorithms(?'id-keyExchangeAlgorithm') -> 'KEA-PublicKey'; -supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'. +supportedPublicKeyAlgorithms(?'id-ecPublicKey') -> 'ECPoint'; +supportedPublicKeyAlgorithms(?'id-RSASSA-PSS') -> 'RSAPublicKey'. supportedCurvesTypes(?'characteristic-two-field') -> characteristic_two_field; supportedCurvesTypes(?'prime-field') -> prime_field; diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index a47b7148e7..e1f5f7576e 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -40,7 +40,8 @@ sign/3, sign/4, verify/4, verify/5, generate_key/1, compute_key/2, compute_key/3, - pkix_sign/2, pkix_verify/2, + pkix_sign/2, pkix_verify/2, + pkix_hash_type/1, pkix_sign_types/1, pkix_is_self_signed/1, pkix_is_fixed_dh_cert/1, @@ -68,11 +69,12 @@ pki_asn1_type/0, asn1_type/0, ssh_file/0, der_encoded/0, key_params/0, digest_type/0, issuer_name/0, oid/0]). --type public_key() :: rsa_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() . --type private_key() :: rsa_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() . - +-type public_key() :: rsa_public_key() | rsa_pss_public_key() | dsa_public_key() | ec_public_key() | ed_public_key() . +-type private_key() :: rsa_private_key() | rsa_pss_private_key() | dsa_private_key() | ec_private_key() | ed_private_key() . -type rsa_public_key() :: #'RSAPublicKey'{}. --type rsa_private_key() :: #'RSAPrivateKey'{}. +-type rsa_private_key() :: #'RSAPrivateKey'{}. +-type rsa_pss_public_key() :: {#'RSAPublicKey'{}, #'RSASSA-PSS-params'{}}. +-type rsa_pss_private_key() :: { #'RSAPrivateKey'{}, #'RSASSA-PSS-params'{}}. -type dsa_private_key() :: #'DSAPrivateKey'{}. -type dsa_public_key() :: {integer(), #'Dss-Parms'{}}. -type ecpk_parameters() :: {ecParameters, #'ECParameters'{}} | {namedCurve, Oid::tuple()}. @@ -293,6 +295,12 @@ der_priv_key_decode({'PrivateKeyInfo', v1, {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', _}, PrivKey, _}) -> der_decode('RSAPrivateKey', PrivKey); der_priv_key_decode({'PrivateKeyInfo', v1, + {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS', + {asn1_OPENTYPE, Parameters}}, PrivKey, _}) -> + Key = der_decode('RSAPrivateKey', PrivKey), + Params = der_decode('RSASSA-PSS-params', Parameters), + {Key, Params}; +der_priv_key_decode({'PrivateKeyInfo', v1, {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa', {asn1_OPENTYPE, Parameters}}, PrivKey, _}) -> {params, #'Dss-Parms'{p=P, q=Q, g=G}} = der_decode('DSAParams', Parameters), X = der_decode('Prime-p', PrivKey), @@ -307,34 +315,40 @@ der_priv_key_decode(PKCS8Key) -> %% %% Description: Encodes a public key entity with asn1 DER encoding. %%-------------------------------------------------------------------- - der_encode('PrivateKeyInfo', #'DSAPrivateKey'{p=P, q=Q, g=G, x=X}) -> der_encode('PrivateKeyInfo', - {'PrivateKeyInfo', v1, - {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa', - {asn1_OPENTYPE, der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G})}}, + {'PrivateKeyInfo', v1, + {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-dsa', + {asn1_OPENTYPE, der_encode('Dss-Parms', #'Dss-Parms'{p=P, q=Q, g=G})}}, der_encode('Prime-p', X), asn1_NOVALUE}); der_encode('PrivateKeyInfo', #'RSAPrivateKey'{} = PrivKey) -> der_encode('PrivateKeyInfo', - {'PrivateKeyInfo', v1, - {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', {asn1_OPENTYPE, ?DER_NULL}}, - der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE}); + {'PrivateKeyInfo', v1, + {'PrivateKeyInfo_privateKeyAlgorithm', ?'rsaEncryption', + {asn1_OPENTYPE, ?DER_NULL}}, + der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE}); +der_encode('PrivateKeyInfo', {#'RSAPrivateKey'{} = PrivKey, Parameters}) -> + der_encode('PrivateKeyInfo', + {'PrivateKeyInfo', v1, + {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-RSASSA-PSS', + {asn1_OPENTYPE, der_encode('RSASSA-PSS-params', Parameters)}}, + der_encode('RSAPrivateKey', PrivKey), asn1_NOVALUE}); der_encode('PrivateKeyInfo', #'ECPrivateKey'{parameters = Parameters} = PrivKey) -> der_encode('PrivateKeyInfo', - {'PrivateKeyInfo', v1, + {'PrivateKeyInfo', v1, {'PrivateKeyInfo_privateKeyAlgorithm', ?'id-ecPublicKey', - {asn1_OPENTYPE, der_encode('EcpkParameters', Parameters)}}, - der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}), asn1_NOVALUE}); + {asn1_OPENTYPE, der_encode('EcpkParameters', Parameters)}}, + der_encode('ECPrivateKey', PrivKey#'ECPrivateKey'{parameters = asn1_NOVALUE}), + asn1_NOVALUE}); der_encode(Asn1Type, Entity) when (Asn1Type == 'PrivateKeyInfo') or (Asn1Type == 'EncryptedPrivateKeyInfo') -> try - {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity), - Encoded - catch + {ok, Encoded} = 'PKCS-FRAME':encode(Asn1Type, Entity), + Encoded + catch error:{badmatch, {error, _}} = Error -> - erlang:error(Error) - end; - + erlang:error(Error) + end; der_encode(Asn1Type, Entity) when is_atom(Asn1Type) -> try {ok, Encoded} = 'OTP-PUB-KEY':encode(Asn1Type, Entity), @@ -625,6 +639,22 @@ pkix_sign_types(?'ecdsa-with-SHA512') -> {sha512, ecdsa}. %%-------------------------------------------------------------------- +-spec pkix_hash_type(HashOid::oid()) -> DigestType:: md5 | crypto:sha1() | crypto:sha2(). + +pkix_hash_type(?'id-sha1') -> + sha; +pkix_hash_type(?'id-sha512') -> + sha512; +pkix_hash_type(?'id-sha384') -> + sha384; +pkix_hash_type(?'id-sha256') -> + sha256; +pkix_hash_type('id-sha224') -> + sha224; +pkix_hash_type('id-md5') -> + md5. + +%%-------------------------------------------------------------------- %% Description: Create digital signature. %%-------------------------------------------------------------------- -spec sign(Msg, DigestType, Key) -> @@ -768,12 +798,11 @@ pkix_match_dist_point(#'CertificateList'{ %% der encoded 'Certificate'{} %%-------------------------------------------------------------------- pkix_sign(#'OTPTBSCertificate'{signature = - #'SignatureAlgorithm'{algorithm = Alg} + #'SignatureAlgorithm'{} = SigAlg} = TBSCert, Key) -> - Msg = pkix_encode('OTPTBSCertificate', TBSCert, otp), - {DigestType, _} = pkix_sign_types(Alg), - Signature = sign(Msg, DigestType, Key), + {DigestType, _, Opts} = pubkey_cert:x509_pkix_sign_types(SigAlg), + Signature = sign(Msg, DigestType, format_pkix_sign_key(Key), Opts), Cert = #'OTPCertificate'{tbsCertificate= TBSCert, signatureAlgorithm = SigAlg, signature = Signature @@ -796,6 +825,11 @@ pkix_verify(DerCert, #'RSAPublicKey'{} = RSAKey) {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert), verify(PlainText, DigestType, Signature, RSAKey); +pkix_verify(DerCert, {#'RSAPublicKey'{} = RSAKey, #'RSASSA-PSS-params'{} = Params}) + when is_binary(DerCert) -> + {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert), + verify(PlainText, DigestType, Signature, RSAKey, rsa_opts(Params)); + pkix_verify(DerCert, Key = {#'ECPoint'{}, _}) when is_binary(DerCert) -> {DigestType, PlainText, Signature} = pubkey_cert:verify_data(DerCert), @@ -1257,7 +1291,11 @@ set_padding(Pad, Opts) -> T =/= rsa_pad] ]. - +format_pkix_sign_key({#'RSAPrivateKey'{} = Key, _}) -> + %% Params are handled in option arg + Key; +format_pkix_sign_key(Key) -> + Key. format_sign_key(Key = #'RSAPrivateKey'{}) -> {rsa, format_rsa_private_key(Key)}; format_sign_key(#'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) -> @@ -1289,6 +1327,15 @@ format_verify_key(#'DSAPrivateKey'{y=Y, p=P, q=Q, g=G}) -> format_verify_key(_) -> badarg. +rsa_opts(#'RSASSA-PSS-params'{maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = HashAlgoOid} + }}) -> + HashAlgo = pkix_hash_type(HashAlgoOid), + [{rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, -1}, + {rsa_mgf1_md, HashAlgo}]. + do_pem_entry_encode(Asn1Type, Entity, CipherInfo, Password) -> Der = der_encode(Asn1Type, Entity), DecryptDer = pubkey_pem:cipher(Der, CipherInfo, Password), @@ -1776,3 +1823,4 @@ format_details([]) -> no_relevant_crls; format_details(Details) -> Details. + diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index 1fd1d2fa76..3b2f1b7184 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -45,7 +45,9 @@ all() -> pkix, pkix_countryname, pkix_emailaddress, pkix_path_validation, pkix_iso_rsa_oid, pkix_iso_dsa_oid, pkix_dsa_sha2_oid, - pkix_crl, general_name, + pkix_crl, + pkix_hash_type, + general_name, pkix_verify_hostname_cn, pkix_verify_hostname_subjAltName, pkix_verify_hostname_subjAltName_IP, @@ -56,13 +58,13 @@ all() -> ]. groups() -> - [{pem_decode_encode, [], [dsa_pem, rsa_pem, ec_pem, encrypted_pem, + [{pem_decode_encode, [], [dsa_pem, rsa_pem, rsa_pss_pss_pem, ec_pem, encrypted_pem, dh_pem, cert_pem, pkcs7_pem, pkcs10_pem, ec_pem2, rsa_priv_pkcs8, dsa_priv_pkcs8, ec_priv_pkcs8, ec_pem_encode_generated, gen_ec_param_prime_field, gen_ec_param_char_2_field ]}, - {sign_verify, [], [rsa_sign_verify, dsa_sign_verify]} + {sign_verify, [], [rsa_sign_verify, rsa_pss_sign_verify, dsa_sign_verify]} ]. %%------------------------------------------------------------------- init_per_suite(Config) -> @@ -101,6 +103,18 @@ init_per_testcase(gen_ec_param_prime_field=TC, Config) -> init_per_testcase(gen_ec_param_char_2_field=TC, Config) -> init_per_testcase_gen_ec_param(TC, sect571r1, Config); +init_per_testcase(rsa_pss_sign_verify, Config) -> + Supports = crypto:supports(), + RSAOpts = proplists:get_value(rsa_opts, Supports), + + case lists:member(rsa_pkcs1_pss_padding, RSAOpts) + andalso lists:member(rsa_pss_saltlen, RSAOpts) + andalso lists:member(rsa_mgf1_md, RSAOpts) of + true -> + Config; + false -> + {skip, not_supported_by_crypto} + end; init_per_testcase(TestCase, Config) -> case TestCase of ec_pem_encode_generated -> @@ -204,6 +218,19 @@ rsa_pem(Config) when is_list(Config) -> RSARawPemNoEndNewLines = strip_superfluous_newlines(RSARawPem), RSARawPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PubEntry1])). +rsa_pss_pss_pem() -> + [{doc, "RSA PKCS8 RSASSA-PSS private key decode/encode"}]. +rsa_pss_pss_pem(Config) when is_list(Config) -> + Datadir = proplists:get_value(data_dir, Config), + {ok, RsaPem} = file:read_file(filename:join(Datadir, "rsa_pss_pss_key.pem")), + [{'PrivateKeyInfo', DerRSAKey, not_encrypted} = Entry0 ] = public_key:pem_decode(RsaPem), + {RSAKey, Parms} = public_key:der_decode('PrivateKeyInfo', DerRSAKey), + {RSAKey, Parms} = public_key:pem_entry_decode(Entry0), + true = check_entry_type(RSAKey, 'RSAPrivateKey'), + PrivEntry0 = public_key:pem_entry_encode('PrivateKeyInfo', {RSAKey, Parms}), + RSAPemNoEndNewLines = strip_superfluous_newlines(RsaPem), + RSAPemNoEndNewLines = strip_superfluous_newlines(public_key:pem_encode([PrivEntry0])). + rsa_priv_pkcs8() -> [{doc, "RSA PKCS8 private key decode/encode"}]. rsa_priv_pkcs8(Config) when is_list(Config) -> @@ -410,6 +437,24 @@ rsa_sign_verify(Config) when is_list(Config) -> true = public_key:verify(Msg, md5, RSASign1, PublicRSA). %%-------------------------------------------------------------------- +rsa_pss_sign_verify() -> + [{doc, "Checks that we can sign and verify rsa pss signatures."}]. +rsa_pss_sign_verify(Config) when is_list(Config) -> + CertChainConf = #{server_chain => + #{root => [{digest, sha256}, {hardcode_rsa_key(1), pss_params(sha256)}], + intermediates => [[]], + peer => [{digest, sha256}, {hardcode_rsa_key(2), pss_params(sha256)}]}, + client_chain => + #{root => [{digest, sha256}, {hardcode_rsa_key(3), pss_params(sha256)}], + intermediates => [[]], + peer => [{digest, sha256}, {hardcode_rsa_key(4), pss_params(sha256)}]}}, + #{client_config := ClientConf} = public_key:pkix_test_data(CertChainConf), + Cert = proplists:get_value(cert, ClientConf), + {#'RSAPrivateKey'{modulus=Mod, publicExponent=Exp}, Parms} = {hardcode_rsa_key(4), pss_params(sha256)}, + + public_key:pkix_verify(Cert, {#'RSAPublicKey'{modulus=Mod, publicExponent=Exp}, Parms}). + +%%-------------------------------------------------------------------- dsa_sign_verify() -> [{doc, "Checks that we can sign and verify dsa signatures."}]. @@ -848,6 +893,21 @@ general_name(Config) when is_list(Config) -> [{rfc822Name, DummyRfc822Name}], authorityCertSerialNumber = 1}). + +%%-------------------------------------------------------------------- + +pkix_hash_type() -> + [{doc, "Test API function pkix_hash_type/1"}]. + +pkix_hash_type(Config) when is_list(Config) -> + sha = public_key:pkix_hash_type(?'id-sha1'), + sha512 = public_key:pkix_hash_type(?'id-sha512'), + sha384 = public_key:pkix_hash_type(?'id-sha384'), + sha256 = public_key:pkix_hash_type(?'id-sha256'), + sha224 = public_key:pkix_hash_type('id-sha224'), + md5 = public_key:pkix_hash_type('id-md5'). + + %%-------------------------------------------------------------------- pkix_test_data_all_default() -> @@ -901,7 +961,7 @@ pkix_test_data(Config) when is_list(Config) -> public_key:pkix_test_data(#{server_chain => #{root => [], intermediates => [], - peer => [{key, hardcode_rsa_key()}]}, + peer => [{key, hardcode_rsa_key(1)}]}, client_chain => #{root => [{validity, {{Year-2, Month, Day}, {Year-1, Month, Day}}}], @@ -1089,9 +1149,7 @@ incorrect_countryname_pkix_cert() -> incorrect_emailaddress_pkix_cert() -> <<48,130,3,74,48,130,2,50,2,9,0,133,49,203,25,198,156,252,230,48,13,6,9,42,134, 72,134,247,13,1,1,5,5,0,48,103,49,11,48,9,6,3,85,4,6,19,2,65,85,49,19,48,17, 6,3,85,4,8,12,10,83,111,109,101,45,83,116,97,116,101,49,33,48,31,6,3,85,4,10, 12,24,73,110,116,101,114,110,101,116,32,87,105,100,103,105,116,115,32,80,116, 121,32,76,116,100,49,32,48,30,6,9,42,134,72,134,247,13,1,9,1,12,17,105,110, 118,97,108,105,100,64,101,109,97,105,108,46,99,111,109,48,30,23,13,49,51,49, 49,48,55,50,48,53,54,49,56,90,23,13,49,52,49,49,48,55,50,48,53,54,49,56,90, 48,103,49,11,48,9,6,3,85,4,6,19,2,65,85,49,19,48,17,6,3,85,4,8,12,10,83,111, 109,101,45,83,116,97,116,101,49,33,48,31,6,3,85,4,10,12,24,73,110,116,101, 114,110,101,116,32,87,105,100,103,105,116,115,32,80,116,121,32,76,116,100,49, 32,48,30,6,9,42,134,72,134,247,13,1,9,1,12,17,105,110,118,97,108,105,100,64, 101,109,97,105,108,46,99,111,109,48,130,1,34,48,13,6,9,42,134,72,134,247,13, 1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,190,243,49,213,219,60,232,105, 1,127,126,9,130,15,60,190,78,100,148,235,246,223,21,91,238,200,251,84,55,212, 78,32,120,61,85,172,0,144,248,5,165,29,143,79,64,178,51,153,203,76,115,238, 192,49,173,37,121,203,89,62,157,13,181,166,30,112,154,40,202,140,104,211,157, 73,244,9,78,236,70,153,195,158,233,141,42,238,2,143,160,225,249,27,30,140, 151,176,43,211,87,114,164,108,69,47,39,195,123,185,179,219,28,218,122,53,83, 77,48,81,184,14,91,243,12,62,146,86,210,248,228,171,146,225,87,51,146,155, 116,112,238,212,36,111,58,41,67,27,6,61,61,3,84,150,126,214,121,57,38,12,87, 121,67,244,37,45,145,234,131,115,134,58,194,5,36,166,52,59,229,32,47,152,80, 237,190,58,182,248,98,7,165,198,211,5,31,231,152,116,31,108,71,218,64,188, 178,143,27,167,79,15,112,196,103,116,212,65,197,94,37,4,132,103,91,217,73, 223,207,185,7,153,221,240,232,31,44,102,108,82,83,56,242,210,214,74,71,246, 177,217,148,227,220,230,4,176,226,74,194,37,2,3,1,0,1,48,13,6,9,42,134,72, 134,247,13,1,1,5,5,0,3,130,1,1,0,89,247,141,154,173,123,123,203,143,85,28,79, 73,37,164,6,17,89,171,224,149,22,134,17,198,146,158,192,241,41,253,58,230, 133,71,189,43,66,123,88,15,242,119,227,249,99,137,61,200,54,161,0,177,167, 169,114,80,148,90,22,97,78,162,181,75,93,209,116,245,46,81,232,64,157,93,136, 52,57,229,113,197,218,113,93,42,161,213,104,205,137,30,144,183,58,10,98,47, 227,177,96,40,233,98,150,209,217,68,22,221,133,27,161,152,237,46,36,179,59, 172,97,134,194,205,101,137,71,192,57,153,20,114,27,173,233,166,45,56,0,61, 205,45,202,139,7,132,103,248,193,157,184,123,43,62,172,236,110,49,62,209,78, 249,83,219,133,1,213,143,73,174,16,113,143,189,41,84,60,128,222,30,177,104, 134,220,52,239,171,76,59,176,36,113,176,214,118,16,44,235,21,167,199,216,200, 76,219,142,248,13,70,145,205,216,230,226,148,97,223,216,179,68,209,222,63, 140,137,24,164,192,149,194,79,119,247,75,159,49,116,70,241,70,116,11,40,119, 176,157,36,160,102,140,255,34,248,25,231,136,59>>. - - -hardcode_rsa_key() -> +hardcode_rsa_key(1) -> #'RSAPrivateKey'{ version = 'two-prime', modulus = 23995666614853919027835084074500048897452890537492185072956789802729257783422306095699263934587064480357348855732149402060270996295002843755712064937715826848741191927820899197493902093529581182351132392364214171173881547273475904587683433713767834856230531387991145055273426806331200574039205571401702219159773947658558490957010003143162250693492642996408861265758000254664396313741422909188635443907373976005987612936763564996605457102336549804831742940035613780926178523017685712710473543251580072875247250504243621640157403744718833162626193206685233710319205099867303242759099560438381385658382486042995679707669, @@ -1102,4 +1160,51 @@ hardcode_rsa_key() -> exponent1 = 119556097830058336212015217380447172615655659108450823901745048534772786676204666783627059584226579481512852103690850928442711896738555003036938088452023283470698275450886490965004917644550167427154181661417665446247398284583687678213495921811770068712485038160606780733330990744565824684470897602653233516609, exponent2 = 41669135975672507953822256864985956439473391144599032012999352737636422046504414744027363535700448809435637398729893409470532385959317485048904982111185902020526124121798693043976273393287623750816484427009887116945685005129205106462566511260580751570141347387612266663707016855981760014456663376585234613993, coefficient = 76837684977089699359024365285678488693966186052769523357232308621548155587515525857011429902602352279058920284048929101483304120686557782043616693940283344235057989514310975192908256494992960578961614059245280827077951132083993754797053182279229469590276271658395444955906108899267024101096069475145863928441, + otherPrimeInfos = asn1_NOVALUE}; + +hardcode_rsa_key(2) -> + #'RSAPrivateKey'{ + version = 'two-prime', + modulus = 21343679768589700771839799834197557895311746244621307033143551583788179817796325695589283169969489517156931770973490560582341832744966317712674900833543896521418422508485833901274928542544381247956820115082240721897193055368570146764204557110415281995205343662628196075590438954399631753508888358737971039058298703003743872818150364935790613286541190842600031570570099801682794056444451081563070538409720109449780410837763602317050353477918147758267825417201591905091231778937606362076129350476690460157227101296599527319242747999737801698427160817755293383890373574621116766934110792127739174475029121017282777887777, + publicExponent = 17, + privateExponent = 18832658619343853622211588088997845201745658451136447382185486691577805721584993260814073385267196632785528033211903435807948675951440868570007265441362261636545666919252206383477878125774454042314841278013741813438699754736973658909592256273895837054592950290554290654932740253882028017801960316533503857992358685308186680144968293076156011747178275038098868263178095174694099811498968993700538293188879611375604635940554394589807673542938082281934965292051746326331046224291377703201248790910007232374006151098976879987912446997911775904329728563222485791845480864283470332826504617837402078265424772379987120023773, + prime1 = 146807662748886761089048448970170315054939768171908279335181627815919052012991509112344782731265837727551849787333310044397991034789843793140419387740928103541736452627413492093463231242466386868459637115999163097726153692593711599245170083315894262154838974616739452594203727376460632750934355508361223110419, + prime2 = 145385325050081892763917667176962991350872697916072592966410309213561884732628046256782356731057378829876640317801978404203665761131810712267778698468684631707642938779964806354584156202882543264893826268426566901882487709510744074274965029453915224310656287149777603803201831202222853023280023478269485417083, + exponent1 = 51814469205489445090252393754177758254684624060673510353593515699736136004585238510239335081623236845018299924941168250963996835808180162284853901555621683602965806809675350150634081614988136541809283687999704622726877773856604093851236499993845033701707873394143336209718962603456693912094478414715725803677, + exponent2 = 51312467664734785681382706062457526359131540440966797517556579722433606376221663384746714140373192528191755406283051201483646739222992016094510128871300458249756331334105225772206172777487956446433115153562317730076172132768497908567634716277852432109643395464627389577600646306666889302334125933506877206029, + coefficient = 30504662229874176232343608562807118278893368758027179776313787938167236952567905398252901545019583024374163153775359371298239336609182249464886717948407152570850677549297935773605431024166978281486607154204888016179709037883348099374995148481968169438302456074511782717758301581202874062062542434218011141540, + otherPrimeInfos = asn1_NOVALUE}; +hardcode_rsa_key(3) -> + #'RSAPrivateKey'{ + version = 'two-prime', + modulus = 25089040456112869869472694987833070928503703615633809313972554887193090845137746668197820419383804666271752525807484521370419854590682661809972833718476098189250708650325307850184923546875260207894844301992963978994451844985784504212035958130279304082438876764367292331581532569155681984449177635856426023931875082020262146075451989132180409962870105455517050416234175675478291534563995772675388370042873175344937421148321291640477650173765084699931690748536036544188863178325887393475703801759010864779559318631816411493486934507417755306337476945299570726975433250753415110141783026008347194577506976486290259135429, + publicExponent = 17, + privateExponent = 8854955455098659953931539407470495621824836570223697404931489960185796768872145882893348383311931058684147950284994536954265831032005645344696294253579799360912014817761873358888796545955974191021709753644575521998041827642041589721895044045980930852625485916835514940558187965584358347452650930302268008446431977397918214293502821599497633970075862760001650736520566952260001423171553461362588848929781360590057040212831994258783694027013289053834376791974167294527043946669963760259975273650548116897900664646809242902841107022557239712438496384819445301703021164043324282687280801738470244471443835900160721870265, + prime1 = 171641816401041100605063917111691927706183918906535463031548413586331728772311589438043965564336865070070922328258143588739626712299625805650832695450270566547004154065267940032684307994238248203186986569945677705100224518137694769557564475390859269797990555863306972197736879644001860925483629009305104925823, + prime2 =146170909759497809922264016492088453282310383272504533061020897155289106805616042710009332510822455269704884883705830985184223718261139908416790475825625309815234508695722132706422885088219618698987115562577878897003573425367881351537506046253616435685549396767356003663417208105346307649599145759863108910523, + exponent1 = 60579464612132153154728441333538327425711971378777222246428851853999433684345266860486105493295364142377972586444050678378691780811632637288529186629507258781295583787741625893888579292084087601124818789392592131211843947578009918667375697196773859928702549128225990187436545756706539150170692591519448797349, + exponent2 = 137572620950115585809189662580789132500998007785886619351549079675566218169991569609420548245479957900898715184664311515467504676010484619686391036071176762179044243478326713135456833024206699951987873470661533079532774988581535389682358631768109586527575902839864474036157372334443583670210960715165278974609, + coefficient = 15068630434698373319269196003209754243798959461311186548759287649485250508074064775263867418602372588394608558985183294561315208336731894947137343239541687540387209051236354318837334154993136528453613256169847839789803932725339395739618592522865156272771578671216082079933457043120923342632744996962853951612, + otherPrimeInfos = asn1_NOVALUE}; +hardcode_rsa_key(4) -> + #'RSAPrivateKey'{ + version ='two-prime', + modulus = 28617237755030755643854803617273584643843067580642149032833640135949799721163782522787597288521902619948688786051081993247908700824196122780349730169173433743054172191054872553484065655968335396052034378669869864779940355219732200954630251223541048434478476115391643898092650304645086338265930608997389611376417609043761464100338332976874588396803891301015812818307951159858145399281035705713082131199940309445719678087542976246147777388465712394062188801177717719764254900022006288880246925156931391594131839991579403409541227225173269459173129377291869028712271737734702830877034334838181789916127814298794576266389, + publicExponent = 17, + privateExponent = 26933870828264240605980991639786903194205240075898493207372837775011576208154148256741268036255908348187001210401018346586267012540419880263858569570986761169933338532757527109161473558558433313931326474042230460969355628442100895016122589386862163232450330461545076609969553227901257730132640573174013751883368376011370428995523268034111482031427024082719896108094847702954695363285832195666458915142143884210891427766607838346722974883433132513540317964796373298134261669479023445911856492129270184781873446960437310543998533283339488055776892320162032014809906169940882070478200435536171854883284366514852906334641, + prime1 = 177342190816702392178883147766999616783253285436834252111702533617098994535049411784501174309695427674025956656849179054202187436663487378682303508229883753383891163725167367039879190685255046547908384208614573353917213168937832054054779266431207529839577747601879940934691505396807977946728204814969824442867, + prime2 = 161367340863680900415977542864139121629424927689088951345472941851682581254789586032968359551717004797621579428672968948552429138154521719743297455351687337112710712475376510559020211584326773715482918387500187602625572442687231345855402020688502483137168684570635690059254866684191216155909970061793538842967, + exponent1 = 62591361464718491357252875682470452982324688977706206627659717747211409835899792394529826226951327414362102349476180842659595565881230839534930649963488383547255704844176717778780890830090016428673547367746320007264898765507470136725216211681602657590439205035957626212244060728285168687080542875871702744541, + exponent2 = 28476589564178982426348978152495139111074987239250991413906989738532220221433456358759122273832412611344984605059935696803369847909621479954699550944415412431654831613301737157474154985469430655673456186029444871051571607533040825739188591886206320553618003159523945304574388238386685203984112363845918619347, + coefficient = 34340318160575773065401929915821192439103777558577109939078671096408836197675640654693301707202885840826672396546056002756167635035389371579540325327619480512374920136684787633921441576901246290213545161954865184290700344352088099063404416346968182170720521708773285279884132629954461545103181082503707725012, otherPrimeInfos = asn1_NOVALUE}. + +pss_params(sha256) -> + #'RSASSA-PSS-params'{ + hashAlgorithm = #'HashAlgorithm'{algorithm = ?'id-sha256'}, + maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = ?'id-sha256'} + }, + saltLength = 32, + trailerField = 1}. + diff --git a/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem b/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem new file mode 100644 index 0000000000..65032269c1 --- /dev/null +++ b/lib/public_key/test/public_key_SUITE_data/rsa_pss_pss_key.pem @@ -0,0 +1,29 @@ +-----BEGIN PRIVATE KEY----- +MIIE7wIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3 +DQEBCDALBglghkgBZQMEAgGiAwIBIASCBKkwggSlAgEAAoIBAQDDlygksUEAajpd +Vquo9XIAyTd9ZJ+55hNmhBfhn3lHz3ryPD+0XlgCE9qsKwfR7iYaqmnNilQnsxWp +MGXAgOlC1+w5zh8qHvrI5wX+A6U9N8leIOSgFuFNP0FMMG7I677QzRxGFqKX1o4V +73JWqnHCfnfHRyZY9xM0tYbJKNbRO7Hy4jKBPl3ptPHUoTltr4WYTOpgstcEamdi +iif+0U4bQvVltNg9pzFEjkAktTUGn92W5CgLnsbPXxBo6a/kUlHcgmhYbpOXEjCP +ufZLgsQo8iF2Bq8eWMEsByjr0chQjzrfZAUVtD8Hmh2uMVAPQFAHUkaLj2tHukL+ +s9tAaWKNAgMBAAECggEBAIzgfwWOtmb6HHfGSXY085wlUlZ696EKWsboNdtI5i4W +/1Mimi/sFC/K5SJFDCjlA4UJYZOuItdFYkCun1t8foaqx3cLQ98u2SuDWwmOzqG9 +YMjvoDy+viDJgtrBt8n4I0R5t/ezrgD3hPe/s/dAZRfVx6g9Ux2ZOLgqV57kT3X7 +6paEz3jrIMvuoXQCsi9Qh+eJQ23/sAcc7OHQ7uD8QJVudEBnSHQ+ttvOPXhr7tba +8NuNVa6E/KewkKHRAZqBTJolCVyPtWmvfaDwdJtunCvyR1w3Rv1adZLK4YRFz+vc +sOMK+K1c2aojA+/Fnba19inNq13j6Dwqmq8Ho7MZwHECgYEA6aSx7/93S1VGpxQ9 +KqFE4Fy9ylliC/hanc9qOcfEIo0tDus9lfpuPp+aOXML0msVkIfhCnaru32qtnaI +AQkIbPhSZFvC/i6BibpArXINbDzTS/46zZHehXskjWFGw+iRm/YI7MBuCmWzSnFO +YUwSKRIPKZKyXswFzP8RsQO/QbsCgYEA1k5SamQheuKdo/X40ShWTTOoDlpL4Sir +b2zTnEqlHyMv8c7w880hPf4P+0pqrKyf7jmEykJvp1qSAmyMUCWzrKTr8gQ2sMyb +zj90cEm++M5YIQh5lPJy4pGqmCliJXqkt+zT1xmnRASwMNQOnU2bBmXkve/ofb4M +dEwyig/nZFcCgYBLWPilTD6dhce+NBGxwMZkkKQIMKEk+RfIEs7QCXNgLSUdzZFT +36pT+caTxl1Go5AVxyw04qZpVZKLO1iK9O3Jrp9rjAgrTrYpw23+QWzAvjDqLfeq +ueMIKvlTus5GeacTo9mm+DvEkJ2sYTQEvrKQmilXn950IdmxDYUYD/xK5wKBgQDQ +5ON9BUGFUSQsUHVLG7CT7EhiRS41ubjyEfhrHm+53Ei9weQpIcjHbsERR8aXrmTu +h26i4QOI88XjSv+ymC19mfzLmcPdrnQpJL1RPvFCAZDyEhrBT1sg8rCBRcV/lv68 +scMEpuLecFt2HR5pwt3b7LJ9Wj8bYoctTaDt5va8XQKBgQDCr4hZB5haAcKmNm/g +PjlaLdrDEIuuBjxMzX1t3PXwsEene1cE731v6fbmrDUa8AuJyMY80xhGrTTDQfS3 +QOu/6wtcUv/JC/06OwEaUlT/kdYek+zYfBm3b1sKP3HVKSxCLTcPcC4aQoAFqbEy +3kuSVh03vVBdaP//qMPyeue17w== +-----END PRIVATE KEY----- diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index 0008cf7a16..0a4bb38f70 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1 +1 @@ -PUBLIC_KEY_VSN = 1.7.2 +PUBLIC_KEY_VSN = 1.8 diff --git a/lib/reltool/test/reltool_test_lib.erl b/lib/reltool/test/reltool_test_lib.erl index 033d952d0a..88e5244b8b 100644 --- a/lib/reltool/test/reltool_test_lib.erl +++ b/lib/reltool/test/reltool_test_lib.erl @@ -238,7 +238,7 @@ wait_for_close() -> end. erl_libs() -> - lists:sort([filename:absname(P) || P<-reltool_utils:erl_libs()]). + [filename:absname(P) || P<-reltool_utils:erl_libs()]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% A small test server, which can be run standalone in a shell diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml index f6cc85b4a0..c259f890ba 100644 --- a/lib/runtime_tools/doc/src/notes.xml +++ b/lib/runtime_tools/doc/src/notes.xml @@ -32,6 +32,27 @@ <p>This document describes the changes made to the Runtime_Tools application.</p> +<section><title>Runtime_Tools 1.15</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>Improved the presentation of allocations and carriers + in the <c>instrument</c> module.</p> + <p> + Own Id: OTP-16327</p> + </item> + <item> + <p> + Minor updates due to the new spawn improvements made.</p> + <p> + Own Id: OTP-16368 Aux Id: OTP-15251 </p> + </item> + </list> + </section> + +</section> + <section><title>Runtime_Tools 1.14</title> <section><title>Improvements and New Features</title> diff --git a/lib/runtime_tools/src/runtime_tools.app.src b/lib/runtime_tools/src/runtime_tools.app.src index b55d50d040..d7c2975a5b 100644 --- a/lib/runtime_tools/src/runtime_tools.app.src +++ b/lib/runtime_tools/src/runtime_tools.app.src @@ -29,5 +29,5 @@ {applications, [kernel, stdlib]}, {env, []}, {mod, {runtime_tools, []}}, - {runtime_dependencies, ["stdlib-@OTP-15251@","mnesia-4.12","kernel-@OTP-15251@", - "erts-@OTP-15251:OTP-16327@"]}]}. + {runtime_dependencies, ["stdlib-3.13","mnesia-4.12","kernel-7.0", + "erts-11.0"]}]}. diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk index c01dd60009..4bacd1f571 100644 --- a/lib/runtime_tools/vsn.mk +++ b/lib/runtime_tools/vsn.mk @@ -1 +1 @@ -RUNTIME_TOOLS_VSN = 1.14 +RUNTIME_TOOLS_VSN = 1.15 diff --git a/lib/sasl/doc/specs/.gitignore b/lib/sasl/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/sasl/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/sasl/doc/src/Makefile b/lib/sasl/doc/src/Makefile index 684fd2b5e4..249b278f6b 100644 --- a/lib/sasl/doc/src/Makefile +++ b/lib/sasl/doc/src/Makefile @@ -53,6 +53,8 @@ XML_FILES = \ $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_REF4_FILES) \ $(XML_REF6_FILES) $(XML_APPLICATION_FILES) +TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- include $(ERL_TOP)/make/doc.mk diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml index 5982ce005d..71705080be 100644 --- a/lib/sasl/doc/src/notes.xml +++ b/lib/sasl/doc/src/notes.xml @@ -31,6 +31,45 @@ </header> <p>This document describes the changes made to the SASL application.</p> +<section><title>SASL 4.0</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove usage and documentation of old requests of the + I/O-protocol.</p> + <p> + Own Id: OTP-15695</p> + </item> + <item> + <p> + <c>systools:make_script/2</c> now accepts the name of the + boot file to create, it is not restricted to only + <c>RelName.boot</c> or <c>start.boot</c>.</p> + <p> + <c>systools:make_tar/2</c> now accepts the option + <c>extra_files</c> to add any extra non release related + files to the tar file.</p> + <p> + Own Id: OTP-16561 Aux Id: PR-2420 </p> + </item> + <item> + <p> + <seemfa + marker="systools#make_tar/1"><c>systools:make_tar/1,2</c></seemfa> + now filters out any tools from erts if included in the + release tar ball. See the documentation for more details.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16603</p> + </item> + </list> + </section> + +</section> + <section><title>SASL 3.4.2</title> <section><title>Improvements and New Features</title> diff --git a/lib/sasl/doc/src/specs.xml b/lib/sasl/doc/src/specs.xml new file mode 100644 index 0000000000..f64b7df114 --- /dev/null +++ b/lib/sasl/doc/src/specs.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_systools.xml"/> +</specs> diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml index 483445f954..13878d379a 100644 --- a/lib/sasl/doc/src/systools.xml +++ b/lib/sasl/doc/src/systools.xml @@ -251,8 +251,8 @@ warnings are issued for calls to undefined functions.</p> <p>By default, errors and warnings are printed to tty and the function returns <c>ok</c> or <c>error</c>. If option - <c>silent</c> is specified, the function instead returns <c>{ok,Module,Warnings}</c> or <c>{error,Module,Error}</c>. + <c>silent</c> is specified, the function instead returns Warnings and errors can be converted to strings by calling <c>Module:format_warning(Warnings)</c> or <c>Module:format_error(Error)</c>.</p> @@ -265,25 +265,9 @@ </func> <func> - <name since="">make_tar(Name) -> Result</name> - <name since="">make_tar(Name, [Opt]) -> Result</name> + <name name="make_tar" arity="1" since=""/> + <name name="make_tar" arity="2" since=""/> <fsummary>Creates a release package.</fsummary> - <type> - <v>Name = string()</v> - <v>Opt = {dirs,[IncDir]} | {path,[Dir]} | {variables,[Var]} | {var_tar,VarTar} | {erts,Dir} | src_tests | exref | {exref,[App]} | silent | {outdir,Dir} | | no_warn_sasl | warnings_as_errors | {extra_files, ExtraFiles}</v> - <v> Dir = string()</v> - <v> IncDir = src | include | atom()</v> - <v> Var = {VarName,PreFix}</v> - <v> VarName = Prefix = string()</v> - <v> VarTar = include | ownfile | omit</v> - <v> Machine = atom()</v> - <v> App = atom()</v> - <v>Result = ok | error | {ok,Module,Warnings} | {error,Module,Error}</v> - <v> Module = atom()</v> - <v> Warning = Error = term()</v> - <v> ExtraFiles = [{NameInArchive, file:filename_all()}]</v> - <v> NameInArchive = string()</v> - </type> <desc> <p>Creates a release package file <c>Name.tar.gz</c>. This file must be uncompressed and unpacked on the target @@ -364,8 +348,10 @@ myapp-1/ebin/myapp.app specified using option <c>path</c>. In the case of <c>sys.config</c> it is not included if <c>sys.config.src</c> is found.</p> <p>If the release package is to contain a new Erlang runtime - system, the <c>bin</c> directory of the specified runtime - system <c>{erts,Dir}</c> is copied to <c>erts-ErtsVsn/bin</c>.</p> + system, the <c>erts-ErtsVsn/bin</c> directory of the specified runtime + system <c>{erts,Dir}</c> is copied to <c>erts-ErtsVsn/bin</c>. Some + erts executables are not copied by default, if you want to include all + executables you can give the <c>erts_all</c> option.</p> <p>All checks with function <seemfa marker="#make_script/1"><c>make_script</c></seemfa> are performed before the release package is created. diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src index b795123645..621454d6a4 100644 --- a/lib/sasl/src/sasl.appup.src +++ b/lib/sasl/src/sasl.appup.src @@ -21,6 +21,7 @@ %% versions from the following OTP releases: %% - OTP 21 %% - OTP 22 +%% - OTP 23 %% %% We also allow upgrade from, and downgrade to all %% versions that have branched off from the above @@ -34,7 +35,8 @@ {<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.4$">>,[restart_new_emulator]}, {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, - {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], [{<<"^3\\.2$">>,[restart_new_emulator]}, {<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, @@ -42,4 +44,5 @@ {<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.4$">>,[restart_new_emulator]}, {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, - {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. + {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. diff --git a/lib/sasl/src/systools.erl b/lib/sasl/src/systools.erl index 34eca6679f..a6aa4919ec 100644 --- a/lib/sasl/src/systools.erl +++ b/lib/sasl/src/systools.erl @@ -62,8 +62,32 @@ make_script(RelName, Opt) -> %% release package and erts specifies that the erts-Vsn/bin directory %% should be included in the release package and there it can be found. %%----------------------------------------------------------------- +-spec make_tar(Name) -> Result when + Name :: string(), + Result :: ok | error | {ok, Module :: module(), Warnings :: term()} | + {error, Module :: module(), Error :: term()}. make_tar(RelName) -> make_tar(RelName, []). +-spec make_tar(Name, Opts) -> Result when + Name :: string(), + Opts :: [Opt], + Opt :: {dirs,[IncDir]} | {path,[Dir]} | + {variables,[Var]} | {var_tar,VarTar} | + {erts,Dir} | erts_all | src_tests | exref | + {exref,[App]} | silent | {outdir,Dir} | + no_warn_sasl | warnings_as_errors | + {extra_files, ExtraFiles}, + Dir :: file:filename_all(), + IncDir :: src | include | atom(), + Var :: {VarName,PreFix}, + VarName :: string(), + PreFix :: string(), + VarTar :: include | ownfile | omit, + App :: atom(), + Result :: ok | error | {ok, Module :: module(), Warnings :: term()} | + {error, Module :: module(), Error :: term()}, + ExtraFiles :: [{NameInArchive, file:filename_all()}], + NameInArchive :: string(). make_tar(RelName, Opt) -> systools_make:make_tar(RelName, Opt). diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl index 1565366d92..a371239823 100644 --- a/lib/sasl/src/systools_make.erl +++ b/lib/sasl/src/systools_make.erl @@ -27,8 +27,7 @@ -export([format_error/1, format_warning/1]). --export([read_release/2, get_release/2, get_release/3, - get_release/4, pack_app/1]). +-export([read_release/2, get_release/2, get_release/3, pack_app/1]). -export([read_application/4]). @@ -47,11 +46,9 @@ -compile({inline,[{badarg,2}]}). -ifdef(USE_ESOCK). --define(ESOCK_SOCKET_MODS, [socket, socket_registry]). --define(ESOCK_NET_MODS, [prim_net]). +-define(ESOCK_MODS, [prim_net,prim_socket,socket_registry]). -else. --define(ESOCK_SOCKET_MODS, []). --define(ESOCK_NET_MODS, []). +-define(ESOCK_MODS, []). -endif. @@ -67,7 +64,6 @@ %% New options: {path,Path} can contain wildcards %% src_tests %% {variables,[{Name,AbsString}]} -%% {machine, jam | beam | vee} %% exref | {exref, [AppName]} %% no_warn_sasl %%----------------------------------------------------------------- @@ -100,7 +96,7 @@ make_script(RelName, Output, Flags) when is_list(RelName), Path1 = mk_path(Path0), % expand wildcards etc. Path = make_set(Path1 ++ code:get_path()), ModTestP = {member(src_tests, Flags),xref_p(Flags)}, - case get_release(RelName, Path, ModTestP, machine(Flags)) of + case get_release(RelName, Path, ModTestP) of {ok, Release, Appls, Warnings0} -> Warnings = wsasl(Flags, Warnings0), case systools_lib:werror(Flags, Warnings) of @@ -139,12 +135,6 @@ wsasl(Options, Warnings) -> badarg(BadArg, Args) -> erlang:error({badarg,BadArg}, Args). -machine(Flags) -> - case get_flag(machine,Flags) of - {machine, Machine} when is_atom(Machine) -> Machine; - _ -> false - end. - get_script_name(RelName, Flags) -> case get_flag(script_name,Flags) of {script_name,ScriptName} when is_list(ScriptName) -> ScriptName; @@ -364,7 +354,6 @@ add_apply_upgrade(Script,Args) -> %% src_tests %% exref | {exref, [AppName]} %% {variables,[{Name,AbsString}]} -%% {machine, jam | beam | vee} %% {var_tar, include | ownfile | omit} %% no_warn_sasl %% warnings_as_errors @@ -400,7 +389,7 @@ make_tar(RelName, Flags) when is_list(RelName), is_list(Flags) -> Path1 = mk_path(Path0), Path = make_set(Path1 ++ code:get_path()), ModTestP = {member(src_tests, Flags),xref_p(Flags)}, - case get_release(RelName, Path, ModTestP, machine(Flags)) of + case get_release(RelName, Path, ModTestP) of {ok, Release, Appls, Warnings0} -> Warnings = wsasl(Flags, Warnings0), case systools_lib:werror(Flags, Warnings) of @@ -432,17 +421,13 @@ make_tar(RelName, Flags) -> %%______________________________________________________________________ %% get_release(File, Path) -> %% get_release(File, Path, ModTestP) -> -%% get_release(File, Path, ModTestP, Machine) -> %% {ok, #release, [{{Name,Vsn},#application}], Warnings} | {error, What} get_release(File, Path) -> - get_release(File, Path, {false,false}, false). + get_release(File, Path, {false,false}). get_release(File, Path, ModTestP) -> - get_release(File, Path, ModTestP, false). - -get_release(File, Path, ModTestP, Machine) -> - case catch get_release1(File, Path, ModTestP, Machine) of + case catch get_release1(File, Path, ModTestP) of {error, Error} -> {error, ?MODULE, Error}; {'EXIT', Why} -> @@ -451,12 +436,12 @@ get_release(File, Path, ModTestP, Machine) -> Answer end. -get_release1(File, Path, ModTestP, Machine) -> +get_release1(File, Path, ModTestP) -> {ok, Release, Warnings1} = read_release(File, Path), {ok, Appls0} = collect_applications(Release, Path), {ok, Appls1} = check_applications(Appls0), {ok, Appls2} = sort_used_and_incl_appls(Appls1, Release), % OTP-4121, OTP-9984 - {ok, Warnings2} = check_modules(Appls2, Path, ModTestP, Machine), + {ok, Warnings2} = check_modules(Appls2, Path, ModTestP), {ok, Appls} = sort_appls(Appls2), {ok, Release, Appls, Warnings1 ++ Warnings2}. @@ -976,13 +961,13 @@ find_pos(N, Name, [_OtherAppl|OrderedAppls]) -> find_pos(N+1, Name, OrderedAppls). %%______________________________________________________________________ -%% check_modules(Appls, Path, TestP, Machine) -> +%% check_modules(Appls, Path, TestP) -> %% {ok, Warnings} | throw({error, What}) %% where Appls = [{App,Vsn}, #application}] %% performs logical checking that we can find all the modules %% etc. -check_modules(Appls, Path, TestP, Machine) -> +check_modules(Appls, Path, TestP) -> %% first check that all the module names are unique %% Make a list M1 = [{Mod,App,Dir}] M1 = [{Mod,App,A#application.dir} || @@ -990,7 +975,7 @@ check_modules(Appls, Path, TestP, Machine) -> Mod <- A#application.modules], case duplicates(M1) of [] -> - case check_mods(M1, Appls, Path, TestP, Machine) of + case check_mods(M1, Appls, Path, TestP) of {error, Errors} -> throw({error, {modules, Errors}}); Return -> @@ -1006,8 +991,8 @@ check_modules(Appls, Path, TestP, Machine) -> %% Use the module extension of the running machine as extension for %% the checked modules. -check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) -> - SrcTestRes = check_src(Modules, Appls, Path, SrcTestP, Machine), +check_mods(Modules, Appls, Path, {SrcTestP, XrefP}) -> + SrcTestRes = check_src(Modules, Appls, Path, SrcTestP), XrefRes = check_xref(Appls, Path, XrefP), Res = SrcTestRes ++ XrefRes, case filter(fun({error, _}) -> true; @@ -1023,8 +1008,8 @@ check_mods(Modules, Appls, Path, {SrcTestP, XrefP}, Machine) -> {error, Errors} end. -check_src(Modules, Appls, Path, true, Machine) -> - Ext = objfile_extension(Machine), +check_src(Modules, Appls, Path, true) -> + Ext = code:objfile_extension(), IncPath = create_include_path(Appls, Path), append(map(fun(ModT) -> {Mod,App,Dir} = ModT, @@ -1038,7 +1023,7 @@ check_src(Modules, Appls, Path, true, Machine) -> end end, Modules)); -check_src(_, _, _, _, _) -> +check_src(_, _, _, _) -> []. check_xref(_Appls, _Path, false) -> @@ -1136,11 +1121,6 @@ exists_xref(Flag) -> _ -> Flag end. -objfile_extension(false) -> - code:objfile_extension(); -objfile_extension(Machine) -> - "." ++ atom_to_list(Machine). - check_mod(Mod,App,Dir,Ext,IncPath) -> ObjFile = mod_to_filename(Dir, Mod, Ext), case file:read_file_info(ObjFile) of @@ -1578,12 +1558,23 @@ mandatory_modules() -> %% This is the modules that are preloaded into the Erlang system. preloaded() -> - %% Sorted - [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang, - erts_code_purger,erts_dirty_process_signal_handler, - erts_internal,erts_literal_area_collector, - init,persistent_term,prim_buffer,prim_eval,prim_file, - prim_inet] ++ ?ESOCK_NET_MODS ++ [prim_zip] ++ ?ESOCK_SOCKET_MODS ++ [zlib]. + lists:sort( + ?ESOCK_MODS ++ + [atomics,counters,erl_init,erl_prim_loader,erl_tracer,erlang, + erts_code_purger,erts_dirty_process_signal_handler, + erts_internal,erts_literal_area_collector, + init,persistent_term,prim_buffer,prim_eval,prim_file, + prim_inet,prim_zip,zlib]). + +%%______________________________________________________________________ +%% This is the erts binaries that should *not* be part of a systool:make_tar package + +erts_binary_filter() -> + Cmds = ["typer", "dialyzer", "ct_run", "yielding_c_fun", "erlc"], + case os:type() of + {unix,_} -> Cmds; + {win32,_} -> [ [Cmd, ".exe"] || Cmd <- Cmds] + end. %%______________________________________________________________________ %% Kernel processes; processes that are specially treated by the init @@ -1895,7 +1886,7 @@ add_appl(Name, Vsn, App, Tar, Variables, Flags, Var) -> Tar, AppDir, BinDir, - objfile_extension(machine(Flags))) + code:objfile_extension()) end. %%______________________________________________________________________ @@ -1974,18 +1965,26 @@ add_priv(ADir, ToDir, Tar) -> end. add_erts_bin(Tar, Release, Flags) -> - case get_flag(erts,Flags) of - {erts,ErtsDir} -> - EVsn = Release#release.erts_vsn, - FromDir = filename:join([to_list(ErtsDir), - "erts-" ++ EVsn, "bin"]), - dirp(FromDir), - ToDir = filename:join("erts-" ++ EVsn, "bin"), - add_to_tar(Tar, FromDir, ToDir); + case {get_flag(erts,Flags),member(erts_all,Flags)} of + {{erts,ErtsDir},true} -> + add_erts_bin(Tar, Release, ErtsDir, []); + {{erts,ErtsDir},false} -> + add_erts_bin(Tar, Release, ErtsDir, erts_binary_filter()); _ -> ok end. +add_erts_bin(Tar, Release, ErtsDir, Filters) -> + FlattenedFilters = [filename:flatten(Filter) || Filter <- Filters], + EVsn = Release#release.erts_vsn, + FromDir = filename:join([to_list(ErtsDir), + "erts-" ++ EVsn, "bin"]), + ToDir = filename:join("erts-" ++ EVsn, "bin"), + {ok, Bins} = file:list_dir(FromDir), + [add_to_tar(Tar, filename:join(FromDir,Bin), filename:join(ToDir,Bin)) + || Bin <- Bins, not lists:member(Bin, FlattenedFilters)], + ok. + %%______________________________________________________________________ %% Tar functions. @@ -2179,9 +2178,6 @@ cas([{variables, V} | Args], X) when is_list(V) -> error -> cas(Args, X++[{variables, V}]) end; -%%% machine ------------------------------------------------------------ -cas([{machine, M} | Args], X) when is_atom(M) -> - cas(Args, X); %%% exref -------------------------------------------------------------- cas([exref | Args], X) -> cas(Args, X); @@ -2248,6 +2244,8 @@ cat([{dirs, D} | Args], X) -> %%% erts --------------------------------------------------------------- cat([{erts, E} | Args], X) when is_list(E)-> cat(Args, X); +cat([erts_all | Args], X) -> + cat(Args, X); %%% src_tests ---------------------------------------------------- cat([src_tests | Args], X) -> cat(Args, X); @@ -2264,9 +2262,6 @@ cat([{var_tar, VT} | Args], X) when VT == include; VT == ownfile; VT == omit -> cat(Args, X); -%%% machine ------------------------------------------------------------ -cat([{machine, M} | Args], X) when is_atom(M) -> - cat(Args, X); %%% exref -------------------------------------------------------------- cat([exref | Args], X) -> cat(Args, X); diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl index fb1dc03dcd..3c60c0fa21 100644 --- a/lib/sasl/test/systools_SUITE.erl +++ b/lib/sasl/test/systools_SUITE.erl @@ -69,7 +69,7 @@ groups() -> [tar_options, relname_tar, normal_tar, no_mod_vsn_tar, system_files_tar, system_src_file_tar, invalid_system_files_tar, variable_tar, src_tests_tar, var_tar, exref_tar, link_tar, no_sasl_tar, - otp_9507_path_ebin, additional_files_tar]}, + otp_9507_path_ebin, additional_files_tar, erts_tar]}, {relup, [], [normal_relup, restart_relup, abnormal_relup, no_sasl_relup, no_appup_relup, bad_appup_relup, app_start_type_relup, regexp_relup @@ -1041,13 +1041,90 @@ additional_files_tar(Config) -> ok. - system_files_tar(cleanup,Config) -> Dir = ?privdir, file:delete(filename:join(Dir,"sys.config")), file:delete(filename:join(Dir,"relup")), ok. +erts_tar(Config) -> + + {ok, OldDir} = file:get_cwd(), + + {LatestDir, LatestName} = create_script(current_all,Config), + + ERTS_VSN = erlang:system_info(version), + ERTS_DIR = fname(["erts-" ++ ERTS_VSN,bin]), + + %% List of all expected executable files in erts/bin + %% This list needs to be kept up to date whenever a file is + %% added or removed. + {Default, Ignored} = + case os:type() of + {unix,_} -> + {["beam.smp","dyn_erl","epmd","erl","erl_call","erl_child_setup", + "erlexec","erl.src","escript","heart","inet_gethost","run_erl", + "start","start_erl.src","start.src","to_erl"], + ["ct_run","dialyzer","erlc","typer","yielding_c_fun"]}; + {win32, _} -> + {["beam.smp.pdb","erl.exe", + "erl.pdb","erl_log.exe","erlexec.dll","erlsrv.exe","heart.exe", + "start_erl.exe","werl.exe","beam.smp.dll", + "epmd.exe","erl.ini","erl_call.exe", + "erlexec.pdb","escript.exe","inet_gethost.exe","werl.pdb"], + ["dialyzer.exe","erlc.exe","yielding_c_fun.exe","ct_run.exe","typer.exe"]} + end, + + ErtsTarContent = + fun(TarName) -> + lists:sort( + [filename:basename(File) + || File <- tar_contents(TarName), + string:equal(filename:dirname(File),ERTS_DIR), + %% Filter out beam.*.smp.* + re:run(filename:basename(File), "beam\\.[^\\.]+\\.smp(\\.dll)?") == nomatch, + %% Filter out any erl_child_setup.* + re:run(filename:basename(File), "erl_child_setup\\..*") == nomatch + ]) + end, + + DataDir = filename:absname(?copydir), + LibDir = fname([DataDir, d_normal, lib]), + P = [fname([LibDir, 'db-2.1', ebin])], + + ok = file:set_cwd(LatestDir), + + {ok, _, []} = systools:make_script(LatestName, [silent, {path, P}, {script_name, "start"}]), + ok = systools:make_tar(LatestName, [{path, P}, {erts, code:root_dir()}]), + ErtsContent = ErtsTarContent(LatestName), + + case lists:sort(Default) of + ErtsContent -> + ok; + Expected -> + ct:pal("Content: ~p",[ErtsContent]), + ct:pal("Expected: ~p",[Expected]), + ct:fail("Incorrect erts bin content") + end, + + ok = systools:make_tar(LatestName, [{path, P}, + {erts, code:root_dir()}, + erts_all]), + ErtsAllContent = ErtsTarContent(LatestName), + + case lists:sort(Default ++ Ignored) of + ErtsAllContent -> + ok; + ExpectedIgn -> + ct:pal("Content: ~p",[ErtsAllContent]), + ct:pal("Expected: ~p",[ExpectedIgn]), + ct:fail("Incorrect erts bin content") + end, + + ok = file:set_cwd(OldDir), + ok. + + %% make_tar: Check that sys.config.src and not sys.config is included system_src_file_tar(Config) -> {ok, OldDir} = file:get_cwd(), @@ -2342,7 +2419,7 @@ delete_tree(Dir) -> end. tar_contents(Name) -> - {ok, Cont} = erl_tar:table(Name ++ ".tar.gz", [compressed]), + {ok, Cont} = erl_tar:table(tar_name(Name), [compressed]), Cont. tar_name(Name) -> diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk index fd045e49d5..9cab3fd792 100644 --- a/lib/sasl/vsn.mk +++ b/lib/sasl/vsn.mk @@ -1 +1 @@ -SASL_VSN = 3.4.2 +SASL_VSN = 4.0 diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml index d4d5dd2f35..52b0b26c84 100644 --- a/lib/snmp/doc/src/notes.xml +++ b/lib/snmp/doc/src/notes.xml @@ -34,7 +34,76 @@ </header> - <section><title>SNMP 5.5</title> + <section><title>SNMP 5.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + For manager, fix PrivParams for SNMPv3 USM with AES + privacy; * In `snmp_usm:do_decrypt/3`, pass full + UsmSecParams to `snmp_usm:try_decrypt/5` as expected by + AES clause. * Change `snmpm_usm:aes_encrypt/3` to use + EngineBoots and EngineTime as cached by + `snmpm_config:get_usm_eboots/1` and + `snmpm_config:get_usm_etime/1` instead of + `snmpm_config:get_engine_boots/0` and + `snmpm_config:get_engine_time/0`. This ensures correct + msgPrivacyParameters are sent when AES is used. * Add + test `snmp.snmp_manager_SUITE.usm_priv_aes/1` to avoid + regression.</p> + <p> + Own Id: OTP-16541 Aux Id: #2544 </p> + </item> + <item> + <p> + Invalid character in (manager) usm config entry generator + function.</p> + <p> + Own Id: OTP-16552 Aux Id: ERL-1196 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove usage and documentation of old requests of the + I/O-protocol.</p> + <p> + Own Id: OTP-15695</p> + </item> + <item> + <p> + Calls of deprecated functions in the <seeguide + marker="crypto:new_api#the-old-api">Old Crypto + API</seeguide> are replaced by calls of their <seeguide + marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p> + <p> + Own Id: OTP-16346</p> + </item> + <item> + <p> + Finalize deprecation. Already deprecated functions has a + "remove version 24" set and "new" functions added to list + of deprecated functions.</p> + <p> + Own Id: OTP-16463</p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + </list> + </section> + +</section> + +<section><title>SNMP 5.5</title> <section><title>Improvements and New Features</title> <list> diff --git a/lib/snmp/test/snmp_agent_SUITE.erl b/lib/snmp/test/snmp_agent_SUITE.erl index 62129c2543..c676bc487e 100644 --- a/lib/snmp/test/snmp_agent_SUITE.erl +++ b/lib/snmp/test/snmp_agent_SUITE.erl @@ -944,25 +944,26 @@ end_per_testcase(Case, Config) when is_list(Config) -> %% already failed, we will want to get as much of the logs %% as possible. So, set no timeout (infinity) and let the %% test framework take care of things... - DisplayLogTimeout = - case ?config(tc_status, Config) of - ok -> - ?SECS(30); - _ -> - infinity + %% But also, *don't* bother with this unless the test case + %% has failed! + case ?config(tc_status, Config) of + ok -> + ok; + _ -> + To = ?SECS(30), + Flag = process_flag(trap_exit, true), + Pid = spawn_link(fun() -> display_log(Config), exit(normal) end), + receive + {'EXIT', Pid, _} -> + process_flag(trap_exit, Flag), + ok + after To -> + ?WPRINT("Display Log process fail to complete in time" + "(~w msec): kill it", [To]), + process_flag(trap_exit, Flag), + exit(Pid, kill) + end end, - Flag = process_flag(trap_exit, true), - Pid = spawn_link(fun() -> display_log(Config), exit(normal) end), - receive - {'EXIT', Pid, _} -> - process_flag(trap_exit, Flag), - ok - after DisplayLogTimeout -> - ?WPRINT("Display Log process fail to complete in time (~w msec): " - "kill it", [DisplayLogTimeout]), - process_flag(trap_exit, Flag), - exit(Pid, kill) - end, Result = end_per_testcase1(Case, Config), @@ -1443,6 +1444,7 @@ msd_varm_mib_start(X) -> msm_varm_mib_start(X) -> %% <CONDITIONAL-SKIP> + %% This is a bit radioactive but... Skippable = [win32], Condition = fun() -> ?OS_BASED_SKIP(Skippable) end, ?NON_PC_TC_MAYBE_SKIP(X, Condition), @@ -6307,27 +6309,34 @@ otp_1131_3(X) -> %% Montavista Linux looks like a Debian distro (/etc/issue) LinuxVersionVerify = fun() -> - case os:cmd("uname -m") of + case string:to_lower(os:cmd("uname -m")) of "ppc" ++ _ -> case file:read_file_info("/etc/issue") of {ok, _} -> - case os:cmd("grep -i montavista /etc/issue") of - Info when (is_list(Info) andalso - (length(Info) > 0)) -> + case string:to_lower( + os:cmd("grep -i montavista /etc/issue")) of + "montavista" ++ _ -> case os:version() of {2, 6, 10} -> + ?IPRINT("(PPC Linux) kernel version check: " + "{2, 6, 10} => SKIP"), true; - _ -> + V -> + ?IPRINT("(PPC Linux) kernel version check: " + "~p != {2, 6, 10} => *NO* SKIP", [V]), false end; _ -> % Maybe plain Debian or Ubuntu + ?IPRINT("(PPC Linux) Not MontaVista => *NO* SKIP"), false end; _ -> %% Not a Debian based distro + ?IPRINT("(PPC Linux) Unknown distro => *NO* SKIP"), false end; _ -> + ?IPRINT("(Linux) Not PPC => *NO* SKIP"), false end end, diff --git a/lib/snmp/test/snmp_agent_mibs_SUITE.erl b/lib/snmp/test/snmp_agent_mibs_SUITE.erl index 150e015554..39946ba7d1 100644 --- a/lib/snmp/test/snmp_agent_mibs_SUITE.erl +++ b/lib/snmp/test/snmp_agent_mibs_SUITE.erl @@ -172,29 +172,28 @@ init_per_testcase2(size_check_ets2_bad_file1, Config) when is_list(Config) -> %% Create a bad file ok = file:write_file(join(DbDir, "snmpa_symbolic_store.db"), "calvin and hoppes play chess"), + Factor = ?config(snmp_factor, Config), + ct:timetrap(?MINS(1 + (Factor div 2))), Config; init_per_testcase2(size_check_ets3_bad_file1, Config) when is_list(Config) -> DbDir = ?config(db_dir, Config), %% Create a bad file ok = file:write_file(join(DbDir, "snmpa_symbolic_store.db"), "calvin and hoppes play chess"), + Factor = ?config(snmp_factor, Config), + ct:timetrap(?MINS(1 + (Factor div 2))), Config; init_per_testcase2(size_check_mnesia, Config) when is_list(Config) -> + Factor = ?config(snmp_factor, Config), + ct:timetrap(?MINS(1 + (Factor div 2))), Config; init_per_testcase2(cache_test, Config) when is_list(Config) -> - Min = timer:minutes(5), - Timeout = - case lists:keysearch(tc_timeout, 1, Config) of - {value, {tc_timeout, TcTimeout}} when TcTimeout < Min -> - Min; - {value, {tc_timeout, TcTimeout}} -> - TcTimeout; - _ -> - Min - end, - Dog = test_server:timetrap(Timeout), - [{watchdog, Dog} | Config]; + Factor = ?config(snmp_factor, Config), + ct:timetrap(?MINS(5 + (Factor div 2))), + Config; init_per_testcase2(_Case, Config) when is_list(Config) -> + Factor = ?config(snmp_factor, Config), + ct:timetrap(?MINS(1 + (Factor div 3))), Config. @@ -220,6 +219,10 @@ end_per_testcase1(_Case, Config) when is_list(Config) -> start_and_stop(suite) -> []; start_and_stop(Config) when is_list(Config) -> + tc_try(start_and_start, + fun() -> do_start_and_stop(Config) end). + +do_start_and_stop(_Config) -> Prio = normal, Verbosity = trace, @@ -238,6 +241,10 @@ start_and_stop(Config) when is_list(Config) -> load_unload(suite) -> []; load_unload(Config) when is_list(Config) -> + tc_try(load_unload, + fun() -> do_load_unload(Config) end). + +do_load_unload(Config) -> ?DBG("load_unload -> start", []), Prio = normal, @@ -365,49 +372,8 @@ do_size_check(Name, Config) -> do_size_check(Name, Init, Config). do_size_check(Name, Init, Config) -> - Pre = fun() -> - {ok, Node} = ?ALIB:start_node(unique(Name)), - ok = run_on(Node, Init), - Node - end, - Case = fun(Node) -> - monitor_node(Node, true), - Pid = spawn_link(Node, fun() -> do_size_check(Config) end), - receive - {nodedown, Node} = N -> - exit(N); - {'EXIT', Pid, normal} -> - monitor_node(Node, false), - ok; - {'EXIT', Pid, ok} -> - monitor_node(Node, false), - ok; - {'EXIT', Pid, Reason} -> - monitor_node(Node, false), - exit(Reason) - end - end, - Post = fun({Node, _}) -> - ?STOP_NODE(Node) - end, - ?TC_TRY(Name, Pre, Case, Post). + tc_try(Name, Init, fun() -> do_size_check(Config) end). -run_on(Node, F) when is_atom(Node) andalso is_function(F, 0) -> - monitor_node(Node, true), - Pid = spawn_link(Node, F), - receive - {nodedown, Node} = N -> - exit(N); - {'EXIT', Pid, normal} -> - monitor_node(Node, false), - ok; - {'EXIT', Pid, Reason} -> - monitor_node(Node, false), - Reason - end. - -unique(PreName) -> - list_to_atom(?F("~w_~w", [PreName, erlang:system_time(millisecond)])). do_size_check(Config) -> ?IPRINT("do_size_check -> start with" @@ -465,6 +431,10 @@ do_size_check(Config) -> me_lookup(suite) -> []; me_lookup(Config) when is_list(Config) -> + tc_try(me_lookup, + fun() -> do_me_lookup(Config) end). + +do_me_lookup(Config) -> Prio = normal, Verbosity = trace, MibDir = ?config(data_dir, Config), @@ -518,6 +488,10 @@ me_lookup(Config) when is_list(Config) -> which_mib(suite) -> []; which_mib(Config) when is_list(Config) -> + tc_try(which_mib, + fun() -> do_which_mib(Config) end). + +do_which_mib(Config) -> Prio = normal, Verbosity = trace, MibDir = ?config(data_dir, Config), @@ -574,7 +548,11 @@ which_mib(Config) when is_list(Config) -> cache_test(suite) -> []; cache_test(Config) when is_list(Config) -> - ?DBG("cache_test -> start", []), + tc_try(cache_test, + fun() -> do_cache_test(Config) end). + +do_cache_test(Config) -> + ?DBG("do_cache_test -> start", []), Prio = normal, Verbosity = trace, MibStorage = [{module, snmpa_mib_storage_ets}], @@ -660,15 +638,17 @@ walk(MibsPid) -> do_walk(MibsPid, Oid, MibView) -> - io:format("do_walk -> entry with" - "~n Oid: ~p" - "~n", [Oid]), + ?IPRINT("do_walk -> entry with" + "~n Oid: ~p" + "~n", [Oid]), case snmpa_mib:next(MibsPid, Oid, MibView) of {table, _, _, #me{oid = Oid}} -> + ?IPRINT("do_walk -> done for table (~p)", [Oid]), ok; {table, _, _, #me{oid = Next}} -> do_walk(MibsPid, Next, MibView); {variable, #me{oid = Oid}, _} -> + ?IPRINT("do_walk -> done for variable (~p)", [Oid]), ok; {variable, #me{oid = Next}, _} -> do_walk(MibsPid, Next, MibView) @@ -897,6 +877,65 @@ mib_storage() -> [{module, snmpa_mib_storage_ets}]. +%% -- + +tc_try(Name, TC) -> + tc_try(Name, fun() -> ok end, TC). + +tc_try(Name, Init, TC) + when is_atom(Name) andalso is_function(Init, 0) andalso is_function(TC, 0) -> + Pre = fun() -> + {ok, Node} = ?ALIB:start_node(unique(Name)), + ok = run_on(Node, Init), + Node + end, + Case = fun(Node) -> + monitor_node(Node, true), + Pid = spawn_link(Node, TC), + receive + {nodedown, Node} = N -> + exit(N); + {'EXIT', Pid, normal} -> + monitor_node(Node, false), + ok; + {'EXIT', Pid, ok} -> + monitor_node(Node, false), + ok; + {'EXIT', Pid, Reason} -> + monitor_node(Node, false), + exit(Reason) + end + end, + Post = fun(Node) -> + monitor_node(Node, true), + ?NPRINT("try stop node ~p", [Node]), + ?STOP_NODE(Node), + receive + {nodedown, Node} -> + ?NPRINT("node ~p stopped", [Node]), + ok + end + end, + ?TC_TRY(Name, Pre, Case, Post). + +run_on(Node, F) when is_atom(Node) andalso is_function(F, 0) -> + monitor_node(Node, true), + Pid = spawn_link(Node, F), + receive + {nodedown, Node} = N -> + exit(N); + {'EXIT', Pid, normal} -> + monitor_node(Node, false), + ok; + {'EXIT', Pid, Reason} -> + monitor_node(Node, false), + Reason + end. + +unique(PreName) -> + list_to_atom(?F("~w_~w", [PreName, erlang:system_time(millisecond)])). + + %% -- display_memory_usage(MibsPid) -> diff --git a/lib/snmp/test/snmp_manager_SUITE.erl b/lib/snmp/test/snmp_manager_SUITE.erl index 6e695f5ddf..6cc0b0ebde 100644 --- a/lib/snmp/test/snmp_manager_SUITE.erl +++ b/lib/snmp/test/snmp_manager_SUITE.erl @@ -68,34 +68,24 @@ info/1, usm_priv_aes/1, - simple_sync_get2/1, simple_sync_get3/1, - simple_async_get2/1, simple_async_get3/1, - simple_sync_get_next2/1, simple_sync_get_next3/1, - simple_async_get_next2/1, simple_async_get_next3_cbp_def/1, simple_async_get_next3_cbp_temp/1, simple_async_get_next3_cbp_perm/1, - simple_sync_set2/1, simple_sync_set3/1, - simple_async_set2/1, simple_async_set3_cbp_def/1, simple_async_set3_cbp_temp/1, simple_async_set3_cbp_perm/1, - simple_sync_get_bulk2/1, simple_sync_get_bulk3/1, - simple_async_get_bulk2/1, simple_async_get_bulk3_cbp_def/1, simple_async_get_bulk3_cbp_temp/1, simple_async_get_bulk3_cbp_perm/1, - misc_async2/1, - discovery/1, trap1/1, @@ -206,8 +196,7 @@ groups() -> {group, get_tests}, {group, get_next_tests}, {group, set_tests}, - {group, bulk_tests}, - {group, misc_request_tests} + {group, bulk_tests} ] }, {request_tests_mt, [], @@ -215,23 +204,18 @@ groups() -> {group, get_tests}, {group, get_next_tests}, {group, set_tests}, - {group, bulk_tests}, - {group, misc_request_tests} + {group, bulk_tests} ] }, {get_tests, [], [ - simple_sync_get2, simple_sync_get3, - simple_async_get2, simple_async_get3 ] }, {get_next_tests, [], [ - simple_sync_get_next2, simple_sync_get_next3, - simple_async_get_next2, simple_async_get_next3_cbp_def, simple_async_get_next3_cbp_temp, simple_async_get_next3_cbp_perm @@ -239,9 +223,7 @@ groups() -> }, {set_tests, [], [ - simple_sync_set2, simple_sync_set3, - simple_async_set2, simple_async_set3_cbp_def, simple_async_set3_cbp_temp, simple_async_set3_cbp_perm @@ -249,19 +231,12 @@ groups() -> }, {bulk_tests, [], [ - simple_sync_get_bulk2, simple_sync_get_bulk3, - simple_async_get_bulk2, simple_async_get_bulk3_cbp_def, simple_async_get_bulk3_cbp_temp, simple_async_get_bulk3_cbp_perm ] }, - {misc_request_tests, [], - [ - misc_async2 - ] - }, {event_tests, [], [ trap1, @@ -315,16 +290,11 @@ ipv6_tests() -> [ register_agent_old, simple_sync_get_next3, - simple_async_get2, simple_sync_get3, - simple_async_get_next2, simple_sync_set3, - simple_async_set2, - simple_sync_get_bulk2, simple_async_get_bulk3_cbp_def, simple_async_get_bulk3_cbp_temp, simple_async_get_bulk3_cbp_perm, - misc_async2, inform1, inform_swarm_cbp_def, inform_swarm_cbp_temp, @@ -529,18 +499,16 @@ init_per_testcase2(Case, Config) -> Family = proplists:get_value(ipfamily, Config, inet), + Factor = ?config(snmp_factor, Config), TO = case Case of inform3 -> - ?MINS(2); + ?MINS(2 + (Factor div 2)); InformSwarm when (InformSwarm =:= inform_swarm_cbp_def) orelse (InformSwarm =:= inform_swarm_cbp_temp) orelse (InformSwarm =:= inform_swarm_cbp_perm) -> - case ?config(snmp_factor, Config) of - N when is_integer(N) -> ?MINS(2*N); - _ -> ?MINS(2) - end; + ?MINS(1 + Factor); _ -> - ?MINS(1) + ?MINS(1 + (Factor div 2)) end, ?IPRINT("Set test case timetrap: ~p", [TO]), ct:timetrap(TO), @@ -564,15 +532,6 @@ init_per_testcase2(Case, Config) -> init_per_testcase3(Case, Config) -> ApiCases02 = [ - simple_sync_get2, - simple_async_get2, - simple_sync_get_next2, - simple_async_get_next2, - simple_sync_set2, - simple_async_set2, - simple_sync_get_bulk2, - simple_async_get_bulk2, - misc_async2, otp8395_1 ], ApiCases03 = @@ -702,15 +661,6 @@ end_per_testcase(Case, Config) when is_list(Config) -> end_per_testcase2(Case, Config) -> ApiCases02 = [ - simple_sync_get2, - simple_async_get2, - simple_sync_get_next2, - simple_async_get_next2, - simple_sync_set2, - simple_async_set2, - simple_sync_get_bulk2, - simple_async_get_bulk2, - misc_async2, otp8395_1 ], ApiCases03 = @@ -1159,20 +1109,15 @@ notify_started02(Config) when is_list(Config) -> notify_started02_cond(Config) -> LinuxVersionVerify = fun() -> - case os:cmd("uname -m") of - "i686" ++ _ -> - case os:version() of - {2, 6, Rev} when Rev >= 16 -> - false; - {2, Min, _} when Min > 6 -> - false; - {Maj, _, _} when Maj > 2 -> - false; - _ -> - true - end; - _ -> - false + case os:version() of + V when V > {2, 6, 16} -> + ?IPRINT("(Linux) kernel version check: " + "~p > {2, 6, 16} => *NO* SKIP", [V]), + false; + V -> + ?IPRINT("(Linux) kernel version check: " + "~p =< {2, 6, 16} => *SKIP*", [V]), + true end end, Skippable = [{unix, [{linux, LinuxVersionVerify}]}], @@ -2142,25 +2087,40 @@ do_register_agent3([ManagerNode], Config) -> %%====================================================================== -simple_sync_get2(doc) -> - ["Simple sync get-request - Version 2 API (TargetName)"]; -simple_sync_get2(suite) -> []; -simple_sync_get2(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_get2, - fun() -> do_simple_sync_get2(Config) end). +simple_sync_get3(doc) -> + ["Simple sync get-request - Version 3 API (TargetName and send-opts)"]; +simple_sync_get3(suite) -> []; +simple_sync_get3(Config) when is_list(Config) -> + ?TC_TRY(simple_sync_get3, + fun() -> do_simple_sync_get3(Config) end). -do_simple_sync_get2(Config) -> +do_simple_sync_get3(Config) -> ?IPRINT("starting with Config: " "~n ~p", [Config]), + Self = self(), + Msg = simple_sync_get3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], Get = fun(Node, TargetName, Oids) -> - mgr_user_sync_get(Node, TargetName, Oids) - end, - PostVerify = fun() -> ok end, - Res = do_simple_sync_get2(Config, Get, PostVerify), + mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) + end, + PostVerify = + fun() -> + receive + Msg -> + ok + end + end, + Res = do_simple_sync_get3(Config, Get, PostVerify), display_log(Config), Res. -do_simple_sync_get2(Config, Get, PostVerify) -> + +do_simple_sync_get3(Config, Get, PostVerify) -> ?IPRINT("starting with Config: " "~n ~p", [Config]), @@ -2169,15 +2129,15 @@ do_simple_sync_get2(Config, Get, PostVerify) -> ?IPRINT("issue get-request without loading the mib"), Oids1 = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance], - ?line ok = do_simple_sync_get2(Node, TargetName, Oids1, Get, PostVerify), + ?line ok = do_simple_sync_get3(Node, TargetName, Oids1, Get, PostVerify), ?IPRINT("issue get-request after first loading the mibs"), ?line ok = mgr_user_load_mib(Node, std_mib()), Oids2 = [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]], - ?line ok = do_simple_sync_get2(Node, TargetName, Oids2, Get, PostVerify), + ?line ok = do_simple_sync_get3(Node, TargetName, Oids2, Get, PostVerify), ok. -do_simple_sync_get2(Node, TargetName, Oids, Get, PostVerify) +do_simple_sync_get3(Node, TargetName, Oids, Get, PostVerify) when is_function(Get, 3) andalso is_function(PostVerify, 0) -> ?line {ok, Reply, _Rem} = Get(Node, TargetName, Oids), @@ -2213,43 +2173,6 @@ do_simple_sync_get2(Node, TargetName, Oids, Get, PostVerify) %%====================================================================== -simple_sync_get3(doc) -> - ["Simple sync get-request - Version 3 API (TargetName and send-opts)"]; -simple_sync_get3(suite) -> []; -simple_sync_get3(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_get3, - fun() -> do_simple_sync_get3(Config) end). - -do_simple_sync_get3(Config) -> - ?IPRINT("starting with Config: " - "~n ~p", [Config]), - Self = self(), - Msg = simple_sync_get3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - Get = fun(Node, TargetName, Oids) -> - mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) - end, - PostVerify = - fun() -> - receive - Msg -> - ok - end - end, - Res = do_simple_sync_get2(Config, Get, PostVerify), - display_log(Config), - Res. - - - - -%%====================================================================== - sag_verify({noError, 0, _Vbs}, any) -> ?IPRINT("verified [any]"), ok; @@ -2283,35 +2206,46 @@ sag_verify_vbs([Vb|_], [E|_]) -> %%====================================================================== -simple_async_get2(doc) -> - ["Simple (async) get-request - Version 2 API (TargetName)"]; -simple_async_get2(suite) -> []; -simple_async_get2(Config) when is_list(Config) -> - ?TC_TRY(simple_async_get2, - fun() -> do_simple_async_get2(Config) end). +simple_async_get3(doc) -> + ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"]; +simple_async_get3(suite) -> []; +simple_async_get3(Config) when is_list(Config) -> + ?TC_TRY(simple_async_get3, + fun() -> do_simple_async_get3(Config) end). -do_simple_async_get2(Config) -> +do_simple_async_get3(Config) -> ?IPRINT("starting with Config: " "~n ~p", [Config]), MgrNode = ?config(manager_node, Config), AgentNode = ?config(agent_node, Config), TargetName = ?config(manager_agent_target_name, Config), - Get = fun(Oids) -> async_g_exec2(MgrNode, TargetName, Oids) end, - PostVerify = fun(Res) -> Res end, - do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify), + Self = self(), + Msg = simple_async_get3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], + Get = fun(Oids) -> async_g_exec3(MgrNode, TargetName, Oids, SendOpts) end, + PostVerify = fun(ok) -> receive Msg -> ok end; + (Error) -> Error + end, + Res = do_simple_async_sync_get3(Config, MgrNode, AgentNode, + Get, PostVerify), display_log(Config), - ok. + Res. -do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify) -> +do_simple_async_sync_get3(Config, MgrNode, AgentNode, Get, PostVerify) -> ?line ok = mgr_user_load_mib(MgrNode, std_mib()), Test2Mib = test2_mib(Config), ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), ?line ok = agent_load_mib(AgentNode, Test2Mib), - do_simple_async_sync_get2(fun() -> mgr_info(MgrNode) end, + do_simple_async_sync_get3(fun() -> mgr_info(MgrNode) end, fun() -> agent_info(AgentNode) end, Get, PostVerify). -do_simple_async_sync_get2(MgrInfo, AgentInfo, Get, PostVerify) +do_simple_async_sync_get3(MgrInfo, AgentInfo, Get, PostVerify) when is_function(MgrInfo, 0) andalso is_function(AgentInfo, 0) andalso is_function(Get, 1) andalso @@ -2365,41 +2299,6 @@ do_simple_async_sync_get2(MgrInfo, AgentInfo, Get, PostVerify) ok. -async_g_exec2(Node, TargetName, Oids) -> - mgr_user_async_get(Node, TargetName, Oids). - - -%%====================================================================== - -simple_async_get3(doc) -> - ["Simple (async) get-request - Version 3 API (TargetName and send-opts)"]; -simple_async_get3(suite) -> []; -simple_async_get3(Config) when is_list(Config) -> - ?TC_TRY(simple_async_get3, - fun() -> do_simple_async_get3(Config) end). - -do_simple_async_get3(Config) -> - ?IPRINT("starting with Config: " - "~n ~p", [Config]), - MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), - TargetName = ?config(manager_agent_target_name, Config), - Self = self(), - Msg = simple_async_get3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - Get = fun(Oids) -> async_g_exec3(MgrNode, TargetName, Oids, SendOpts) end, - PostVerify = fun(ok) -> receive Msg -> ok end; - (Error) -> Error - end, - Res = do_simple_async_sync_get2(Config, MgrNode, AgentNode, Get, PostVerify), - display_log(Config), - Res. - async_g_exec3(Node, TargetName, Oids, SendOpts) -> mgr_user_async_get2(Node, TargetName, Oids, SendOpts). @@ -2440,27 +2339,35 @@ check_ssgn_vbs([Vb|_], [E|_]) -> %%====================================================================== -simple_sync_get_next2(doc) -> - ["Simple (sync) get_next-request - Version 2 API (TargetName)"]; -simple_sync_get_next2(suite) -> []; -simple_sync_get_next2(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_get_next2, - fun() -> do_simple_sync_get_next2(Config) end). - -do_simple_sync_get_next2(Config) -> +simple_sync_get_next3(doc) -> + ["Simple (sync) get_next-request - " + "Version 3 API (TargetName with send-opts)"]; +simple_sync_get_next3(suite) -> []; +simple_sync_get_next3(Config) when is_list(Config) -> + process_flag(trap_exit, true), + put(tname, ssgn3), ?IPRINT("starting with Config: " "~n ~p", [Config]), - + Self = self(), + Msg = simple_sync_get_next3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], GetNext = fun(Node, TargetName, Oids) -> - mgr_user_sync_get_next(Node, TargetName, Oids) + mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts) end, - PostVerify = fun(Res) -> Res end, - Res = do_simple_sync_get_next2(Config, GetNext, PostVerify), + PostVerify = fun(ok) -> receive Msg -> ok end; + (Error) -> Error + end, + do_simple_sync_get_next3(Config, GetNext, PostVerify), display_log(Config), - Res. + ok. -do_simple_sync_get_next2(Config, GetNext, PostVerify) +do_simple_sync_get_next3(Config, GetNext, PostVerify) when is_function(GetNext, 3) andalso is_function(PostVerify, 1) -> MgrNode = ?config(manager_node, Config), @@ -2550,7 +2457,6 @@ do_simple_sync_get_next2(Config, GetNext, PostVerify) GetNext, PostVerify), ok. - do_simple_get_next(N, Node, TargetName, Oids, Verify, GetNext, PostVerify) -> ?IPRINT("issue get-next command ~w", [N]), case GetNext(Node, TargetName, Oids) of @@ -2565,46 +2471,36 @@ do_simple_get_next(N, Node, TargetName, Oids, Verify, GetNext, PostVerify) -> end. + %%====================================================================== -simple_sync_get_next3(doc) -> - ["Simple (sync) get_next-request - " +simple_async_get_next3_cbp_def(doc) -> + ["Simple (async) get_next-request - " "Version 3 API (TargetName with send-opts)"]; -simple_sync_get_next3(suite) -> []; -simple_sync_get_next3(Config) when is_list(Config) -> - process_flag(trap_exit, true), - put(tname, ssgn3), - ?IPRINT("starting with Config: " - "~n ~p", [Config]), - Self = self(), - Msg = simple_sync_get_next3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - GetNext = fun(Node, TargetName, Oids) -> - mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts) - end, - PostVerify = fun(ok) -> receive Msg -> ok end; - (Error) -> Error - end, - do_simple_sync_get_next2(Config, GetNext, PostVerify), - display_log(Config), - ok. +simple_async_get_next3_cbp_def(suite) -> []; +simple_async_get_next3_cbp_def(Config) when is_list(Config) -> + simple_async_get_next3(ssgn2_cbp_def, Config). +simple_async_get_next3_cbp_temp(doc) -> + ["Simple (async) get_next-request - " + "Version 3 API (TargetName with send-opts)"]; +simple_async_get_next3_cbp_temp(suite) -> []; +simple_async_get_next3_cbp_temp(Config) when is_list(Config) -> + simple_async_get_next3(ssgn2_cbp_temp, Config). -%%====================================================================== +simple_async_get_next3_cbp_perm(doc) -> + ["Simple (async) get_next-request - " + "Version 3 API (TargetName with send-opts)"]; +simple_async_get_next3_cbp_perm(suite) -> []; +simple_async_get_next3_cbp_perm(Config) when is_list(Config) -> + simple_async_get_next3(ssgn2_cbp_perm, Config). -simple_async_get_next2(doc) -> - ["Simple (async) get_next-request - Version 2 API (TargetName)"]; -simple_async_get_next2(suite) -> []; -simple_async_get_next2(Config) when is_list(Config) -> - ?TC_TRY(simple_async_get_next2, - fun() -> do_simple_async_get_next2(Config) end). +simple_async_get_next3(Case, Config) when is_list(Config) -> + ?TC_TRY(Case, + fun() -> do_simple_async_get_next3(Config) end). -do_simple_async_get_next2(Config) -> +do_simple_async_get_next3(Config) -> + %% process_flag(trap_exit, true), ?IPRINT("starting with Config: " "~n ~p", [Config]), @@ -2616,15 +2512,28 @@ do_simple_async_get_next2(Config) -> Test2Mib = test2_mib(Config), ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), ?line ok = agent_load_mib(AgentNode, Test2Mib), + + Self = self(), + Msg = simple_async_get_next3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], + GetNext = fun(Oids) -> - async_gn_exec2(MgrNode, TargetName, Oids) + async_gn_exec3(MgrNode, TargetName, Oids, SendOpts) end, - PostVerify = fun(Res) -> Res end, - Res = do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify), + PostVerify = fun(ok) -> receive Msg -> ok end; + (Error) -> Error + end, + + Res = do_simple_async_get_next3(MgrNode, AgentNode, GetNext, PostVerify), display_log(Config), Res. -do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify) +do_simple_async_get_next3(MgrNode, AgentNode, GetNext, PostVerify) when is_function(GetNext, 1) andalso is_function(PostVerify, 1) -> ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2), ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1), @@ -2708,71 +2617,6 @@ do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify) ok. -async_gn_exec2(Node, TargetName, Oids) -> - mgr_user_async_get_next(Node, TargetName, Oids). - - -%%====================================================================== - -simple_async_get_next3_cbp_def(doc) -> - ["Simple (async) get_next-request - " - "Version 3 API (TargetName with send-opts)"]; -simple_async_get_next3_cbp_def(suite) -> []; -simple_async_get_next3_cbp_def(Config) when is_list(Config) -> - simple_async_get_next3(ssgn2_cbp_def, Config). - -simple_async_get_next3_cbp_temp(doc) -> - ["Simple (async) get_next-request - " - "Version 3 API (TargetName with send-opts)"]; -simple_async_get_next3_cbp_temp(suite) -> []; -simple_async_get_next3_cbp_temp(Config) when is_list(Config) -> - simple_async_get_next3(ssgn2_cbp_temp, Config). - -simple_async_get_next3_cbp_perm(doc) -> - ["Simple (async) get_next-request - " - "Version 3 API (TargetName with send-opts)"]; -simple_async_get_next3_cbp_perm(suite) -> []; -simple_async_get_next3_cbp_perm(Config) when is_list(Config) -> - simple_async_get_next3(ssgn2_cbp_perm, Config). - -simple_async_get_next3(Case, Config) when is_list(Config) -> - ?TC_TRY(Case, - fun() -> do_simple_async_get_next3(Config) end). - -do_simple_async_get_next3(Config) -> - %% process_flag(trap_exit, true), - ?IPRINT("starting with Config: " - "~n ~p", [Config]), - - MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), - TargetName = ?config(manager_agent_target_name, Config), - - ?line ok = mgr_user_load_mib(MgrNode, std_mib()), - Test2Mib = test2_mib(Config), - ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), - ?line ok = agent_load_mib(AgentNode, Test2Mib), - - Self = self(), - Msg = simple_async_get_next3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - - GetNext = fun(Oids) -> - async_gn_exec3(MgrNode, TargetName, Oids, SendOpts) - end, - PostVerify = fun(ok) -> receive Msg -> ok end; - (Error) -> Error - end, - - Res = do_simple_async_get_next2(MgrNode, AgentNode, GetNext, PostVerify), - display_log(Config), - Res. - async_gn_exec3(Node, TargetName, Oids, SendOpts) -> mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts). @@ -2792,27 +2636,36 @@ value_of_vavs([{_Oid, Val}|VAVs], Acc) -> %%====================================================================== -simple_sync_set2(doc) -> - ["Simple (sync) set-request - Version 2 API (TargetName)"]; -simple_sync_set2(suite) -> []; -simple_sync_set2(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_set2, - fun() -> do_simple_sync_set2(Config) end). +simple_sync_set3(doc) -> + ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"]; +simple_sync_set3(suite) -> []; +simple_sync_set3(Config) when is_list(Config) -> + ?TC_TRY(simple_sync_set3, + fun() -> do_simple_sync_set3(Config) end). -do_simple_sync_set2(Config) -> +do_simple_sync_set3(Config) -> ?IPRINT("starting with Config: " "~n ~p", [Config]), + Self = self(), + Msg = simple_sync_set3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], + Set = fun(Node, TargetName, VAVs) -> - mgr_user_sync_set(Node, TargetName, VAVs) + mgr_user_sync_set2(Node, TargetName, VAVs, SendOpts) end, - PostVerify = fun() -> ok end, + PostVerify = fun() -> receive Msg -> ok end end, - Res = do_simple_sync_set2(Config, Set, PostVerify), + Res = do_simple_sync_set3(Config, Set, PostVerify), display_log(Config), Res. -do_simple_sync_set2(Config, Set, PostVerify) +do_simple_sync_set3(Config, Set, PostVerify) when is_function(Set, 3) andalso is_function(PostVerify, 0) -> Node = ?config(manager_node, Config), @@ -2825,7 +2678,7 @@ do_simple_sync_set2(Config, Set, PostVerify) {?sysName_instance, s, Val11}, {?sysLocation_instance, s, Val12} ], - ?line ok = do_simple_set2(Node, TargetName, VAVs1, Set, PostVerify), + ?line ok = do_simple_set3(Node, TargetName, VAVs1, Set, PostVerify), ?IPRINT("issue set-request after first loading the mibs"), ?line ok = mgr_user_load_mib(Node, std_mib()), @@ -2835,10 +2688,10 @@ do_simple_sync_set2(Config, Set, PostVerify) {[sysName, 0], Val21}, {[sysLocation, 0], Val22} ], - ?line ok = do_simple_set2(Node, TargetName, VAVs2, Set, PostVerify), + ?line ok = do_simple_set3(Node, TargetName, VAVs2, Set, PostVerify), ok. -do_simple_set2(Node, TargetName, VAVs, Set, PostVerify) -> +do_simple_set3(Node, TargetName, VAVs, Set, PostVerify) -> [SysName, SysLoc] = value_of_vavs(VAVs), ?line {ok, Reply, _Rem} = Set(Node, TargetName, VAVs), @@ -2866,38 +2719,6 @@ do_simple_set2(Node, TargetName, VAVs, Set, PostVerify) -> %%====================================================================== -simple_sync_set3(doc) -> - ["Simple (sync) set-request - Version 3 API (TargetName with send-opts)"]; -simple_sync_set3(suite) -> []; -simple_sync_set3(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_set3, - fun() -> do_simple_sync_set3(Config) end). - -do_simple_sync_set3(Config) -> - ?IPRINT("starting with Config: " - "~n ~p", [Config]), - - Self = self(), - Msg = simple_sync_set3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - - Set = fun(Node, TargetName, VAVs) -> - mgr_user_sync_set2(Node, TargetName, VAVs, SendOpts) - end, - PostVerify = fun() -> receive Msg -> ok end end, - - Res = do_simple_sync_set2(Config, Set, PostVerify), - display_log(Config), - Res. - - -%%====================================================================== - sas_verify({noError, 0, _Vbs}, any) -> ?IPRINT("verified [any]"), ok; @@ -2930,17 +2751,31 @@ sas_verify_vbs([Vb|_], [E|_]) -> %%====================================================================== -simple_async_set2(doc) -> - ["Simple (async) set-request - Version 2 API (TargetName)"]; -simple_async_set2(suite) -> []; -simple_async_set2(Config) when is_list(Config) -> - ?TC_TRY(simple_async_set2, - fun() -> do_simple_async_set2(Config) end). +simple_async_set3_cbp_def(doc) -> + ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; +simple_async_set3_cbp_def(suite) -> []; +simple_async_set3_cbp_def(Config) when is_list(Config) -> + simple_async_set3(sas3_cbp_def, Config). + +simple_async_set3_cbp_temp(doc) -> + ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; +simple_async_set3_cbp_temp(suite) -> []; +simple_async_set3_cbp_temp(Config) when is_list(Config) -> + simple_async_set3(sas3_cbp_temp, Config). + +simple_async_set3_cbp_perm(doc) -> + ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; +simple_async_set3_cbp_perm(suite) -> []; +simple_async_set3_cbp_perm(Config) when is_list(Config) -> + simple_async_set3(sas3_cbp_perm, Config). + +simple_async_set3(Case, Config) -> + ?TC_TRY(Case, + fun() -> do_simple_async_set3(Config) end). -do_simple_async_set2(Config) -> +do_simple_async_set3(Config) -> ?IPRINT("starting with Config: " - "~n ~p" - "~n", [Config]), + "~n ~p~n", [Config]), MgrNode = ?config(manager_node, Config), AgentNode = ?config(agent_node, Config), @@ -2951,17 +2786,28 @@ do_simple_async_set2(Config) -> ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), ?line ok = agent_load_mib(AgentNode, Test2Mib), + Self = self(), + Msg = simple_async_set3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], + Set = fun(Oids) -> - async_s_exec2(MgrNode, TargetName, Oids) + async_s_exec3(MgrNode, TargetName, Oids, SendOpts) end, - PostVerify = fun(Res) -> Res end, + PostVerify = fun(ok) -> receive Msg -> ok end; + (Res) -> Res + end, - Res = do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify), + Res = do_simple_async_set3(MgrNode, AgentNode, Set, PostVerify), display_log(Config), Res. -do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify) -> +do_simple_async_set3(MgrNode, AgentNode, Set, PostVerify) -> Requests = [ {1, @@ -3005,68 +2851,6 @@ do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify) -> ok. -async_s_exec2(Node, TargetName, VAVs) -> - mgr_user_async_set(Node, TargetName, VAVs). - - -%%====================================================================== - -simple_async_set3_cbp_def(doc) -> - ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; -simple_async_set3_cbp_def(suite) -> []; -simple_async_set3_cbp_def(Config) when is_list(Config) -> - simple_async_set3(sas3_cbp_def, Config). - -simple_async_set3_cbp_temp(doc) -> - ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; -simple_async_set3_cbp_temp(suite) -> []; -simple_async_set3_cbp_temp(Config) when is_list(Config) -> - simple_async_set3(sas3_cbp_temp, Config). - -simple_async_set3_cbp_perm(doc) -> - ["Simple (async) set-request - Version 3 API (TargetName with send-opts)"]; -simple_async_set3_cbp_perm(suite) -> []; -simple_async_set3_cbp_perm(Config) when is_list(Config) -> - simple_async_set3(sas3_cbp_perm, Config). - -simple_async_set3(Case, Config) -> - ?TC_TRY(Case, - fun() -> do_simple_async_set3(Config) end). - -do_simple_async_set3(Config) -> - ?IPRINT("starting with Config: " - "~n ~p~n", [Config]), - - MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), - TargetName = ?config(manager_agent_target_name, Config), - - ?line ok = mgr_user_load_mib(MgrNode, std_mib()), - Test2Mib = test2_mib(Config), - ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), - ?line ok = agent_load_mib(AgentNode, Test2Mib), - - Self = self(), - Msg = simple_async_set3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - - Set = - fun(Oids) -> - async_s_exec3(MgrNode, TargetName, Oids, SendOpts) - end, - PostVerify = fun(ok) -> receive Msg -> ok end; - (Res) -> Res - end, - - Res = do_simple_async_set2(MgrNode, AgentNode, Set, PostVerify), - display_log(Config), - Res. - async_s_exec3(Node, TargetName, VAVs, SendOpts) -> mgr_user_async_set2(Node, TargetName, VAVs, SendOpts). @@ -3110,14 +2894,15 @@ check_ssgb_vbs([R|_], [E|_]) -> %%====================================================================== -simple_sync_get_bulk2(doc) -> - ["Simple (sync) get_bulk-request - Version 2 API (TargetName)"]; -simple_sync_get_bulk2(suite) -> []; -simple_sync_get_bulk2(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_get_bulk2, - fun() -> do_simple_sync_get_bulk2(Config) end). +simple_sync_get_bulk3(doc) -> + ["Simple (sync) get_bulk-request - " + "Version 3 API (TargetName with send-opts)"]; +simple_sync_get_bulk3(suite) -> []; +simple_sync_get_bulk3(Config) when is_list(Config) -> + ?TC_TRY(simple_sync_get_bulk3, + fun() -> do_simple_sync_get_bulk3(Config) end). -do_simple_sync_get_bulk2(Config) -> +do_simple_sync_get_bulk3(Config) -> ?IPRINT("starting with Config: " "~n ~p~n", [Config]), @@ -3125,32 +2910,43 @@ do_simple_sync_get_bulk2(Config) -> AgentNode = ?config(agent_node, Config), TargetName = ?config(manager_agent_target_name, Config), + Self = self(), + Msg = simple_async_set3, + Fun = fun() -> Self ! Msg end, + Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, + SendOpts = + [ + {extra, Extra} + ], + GetBulk = fun(NonRep, MaxRep, Oids) -> - mgr_user_sync_get_bulk(MgrNode, TargetName, - NonRep, MaxRep, Oids) + mgr_user_sync_get_bulk2(MgrNode, TargetName, + NonRep, MaxRep, Oids, SendOpts) end, - PostVerify = fun(Res) -> Res end, + PostVerify = fun(ok) -> receive Msg -> ok end; + (Res) -> Res + end, - Res = do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify), + Res = do_simple_sync_get_bulk3(Config, MgrNode, AgentNode, GetBulk, PostVerify), display_log(Config), Res. -do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> +do_simple_sync_get_bulk3(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> %% -- 1 -- - ?line ok = do_simple_get_bulk2(1, + ?line ok = do_simple_get_bulk3(1, 1, 1, [], fun verify_ssgb_reply1/1, GetBulk, PostVerify), %% -- 2 -- - ?line ok = do_simple_get_bulk2(2, + ?line ok = do_simple_get_bulk3(2, -1, 1, [], fun verify_ssgb_reply1/1, GetBulk, PostVerify), %% -- 3 -- - ?line ok = do_simple_get_bulk2(3, + ?line ok = do_simple_get_bulk3(3, -1, -1, [], fun verify_ssgb_reply1/1, GetBulk, PostVerify), @@ -3160,12 +2956,12 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> VF04 = fun(X) -> verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView]) end, - ?line ok = do_simple_get_bulk2(4, + ?line ok = do_simple_get_bulk3(4, 2, 0, [[sysDescr],[1,3,7,1]], VF04, GetBulk, PostVerify), %% -- 5 -- - ?line ok = do_simple_get_bulk2(5, + ?line ok = do_simple_get_bulk3(5, 1, 2, [[sysDescr],[1,3,7,1]], VF04, GetBulk, PostVerify), @@ -3175,7 +2971,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> [?sysDescr_instance, endOfMibView, ?sysObjectID_instance, endOfMibView]) end, - ?line ok = do_simple_get_bulk2(6, + ?line ok = do_simple_get_bulk3(6, 0, 2, [[sysDescr],[1,3,7,1]], VF06, GetBulk, PostVerify), @@ -3186,7 +2982,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> ?sysDescr_instance, endOfMibView, ?sysObjectID_instance, endOfMibView]) end, - ?line ok = do_simple_get_bulk2(7, + ?line ok = do_simple_get_bulk3(7, 2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]], VF07, @@ -3202,14 +2998,14 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> [?sysDescr_instance, ?sysDescr_instance]) end, - ?line ok = do_simple_get_bulk2(8, + ?line ok = do_simple_get_bulk3(8, 1, 2, [[sysDescr],[sysDescr],[tTooBig]], VF08, GetBulk, PostVerify), %% -- 9 -- - ?line ok = do_simple_get_bulk2(9, + ?line ok = do_simple_get_bulk3(9, 1, 12, [[tDescr2], [sysDescr]], fun verify_ssgb_reply1/1, @@ -3223,7 +3019,7 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> {?tGenErr1, 'NULL'}, {?sysDescr, 'NULL'}]) end, - ?line ok = do_simple_get_bulk2(10, + ?line ok = do_simple_get_bulk3(10, 2, 2, [[sysDescr], [sysObjectID], @@ -3240,14 +3036,14 @@ do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify) -> [{fl([TCnt2,2]), 100}, {fl([TCnt2,2]), endOfMibView}]) end, - ?line ok = do_simple_get_bulk2(11, + ?line ok = do_simple_get_bulk3(11, 0, 2, [[TCnt2, 1]], VF11, GetBulk, PostVerify), ok. -do_simple_get_bulk2(N, +do_simple_get_bulk3(N, NonRep, MaxRep, Oids, Verify, GetBulk, PostVerify) when is_function(Verify, 1) andalso @@ -3268,15 +3064,19 @@ do_simple_get_bulk2(N, %%====================================================================== -simple_sync_get_bulk3(doc) -> - ["Simple (sync) get_bulk-request - " +simple_async_get_bulk3_cbp_def(doc) -> + ["Simple (async) get_bulk-request - " "Version 3 API (TargetName with send-opts)"]; -simple_sync_get_bulk3(suite) -> []; -simple_sync_get_bulk3(Config) when is_list(Config) -> - ?TC_TRY(simple_sync_get_bulk3, - fun() -> do_simple_sync_get_bulk3(Config) end). +simple_async_get_bulk3_cbp_def(suite) -> []; +simple_async_get_bulk3_cbp_def(Config) when is_list(Config) -> + simple_async_get_bulk3(sagb3_cbp_def, Config). -do_simple_sync_get_bulk3(Config) -> +simple_async_get_bulk3(Case, Config) -> + ?TC_TRY(Case, + fun() -> do_simple_async_get_bulk3(Config) end). + +do_simple_async_get_bulk3(Config) -> + process_flag(trap_exit, true), ?IPRINT("starting with Config: " "~n ~p~n", [Config]), @@ -3284,8 +3084,13 @@ do_simple_sync_get_bulk3(Config) -> AgentNode = ?config(agent_node, Config), TargetName = ?config(manager_agent_target_name, Config), + ?line ok = mgr_user_load_mib(MgrNode, std_mib()), + Test2Mib = test2_mib(Config), + ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), + ?line ok = agent_load_mib(AgentNode, Test2Mib), + Self = self(), - Msg = simple_async_set3, + Msg = simple_async_get_bulk3, Fun = fun() -> Self ! Msg end, Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, SendOpts = @@ -3294,52 +3099,18 @@ do_simple_sync_get_bulk3(Config) -> ], GetBulk = - fun(NonRep, MaxRep, Oids) -> - mgr_user_sync_get_bulk2(MgrNode, TargetName, - NonRep, MaxRep, Oids, SendOpts) + fun(Data) -> + async_gb_exec3(MgrNode, TargetName, Data, SendOpts) end, - PostVerify = fun(ok) -> receive Msg -> ok end; + PostVerify = fun(ok) -> receive Msg -> ok end; (Res) -> Res end, - Res = do_simple_sync_get_bulk2(Config, MgrNode, AgentNode, GetBulk, PostVerify), + Res = do_simple_async_get_bulk3(MgrNode, AgentNode, GetBulk, PostVerify), display_log(Config), Res. - -%%====================================================================== - -simple_async_get_bulk2(doc) -> - ["Simple (async) get_bulk-request - Version 2 API (TargetName)"]; -simple_async_get_bulk2(suite) -> []; -simple_async_get_bulk2(Config) when is_list(Config) -> - ?TC_TRY(simple_async_get_bulk2, - fun() -> do_simple_async_get_bulk2(Config) end). - -do_simple_async_get_bulk2(Config) -> - ?IPRINT("starting with Config: " - "~p ~n", [Config]), - - MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), - TargetName = ?config(manager_agent_target_name, Config), - - ?line ok = mgr_user_load_mib(MgrNode, std_mib()), - Test2Mib = test2_mib(Config), - ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), - ?line ok = agent_load_mib(AgentNode, Test2Mib), - - GetBulk = - fun(Data) -> - async_gb_exec2(MgrNode, TargetName, Data) - end, - PostVerify = fun(Res) -> Res end, - - Res = do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify), - display_log(Config), - Res. - -do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify) -> +do_simple_async_get_bulk3(MgrNode, AgentNode, GetBulk, PostVerify) -> %% We re-use the verification functions from the ssgb test-case VF04 = fun(X) -> PostVerify( @@ -3465,58 +3236,6 @@ do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify) -> ok. -async_gb_exec2(Node, TargetName, {NR, MR, Oids}) -> - mgr_user_async_get_bulk(Node, TargetName, NR, MR, Oids). - - -%%====================================================================== - -simple_async_get_bulk3_cbp_def(doc) -> - ["Simple (async) get_bulk-request - " - "Version 3 API (TargetName with send-opts)"]; -simple_async_get_bulk3_cbp_def(suite) -> []; -simple_async_get_bulk3_cbp_def(Config) when is_list(Config) -> - simple_async_get_bulk3(sagb3_cbp_def, Config). - -simple_async_get_bulk3(Case, Config) -> - ?TC_TRY(Case, - fun() -> do_simple_async_get_bulk3(Config) end). - -do_simple_async_get_bulk3(Config) -> - process_flag(trap_exit, true), - ?IPRINT("starting with Config: " - "~n ~p~n", [Config]), - - MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), - TargetName = ?config(manager_agent_target_name, Config), - - ?line ok = mgr_user_load_mib(MgrNode, std_mib()), - Test2Mib = test2_mib(Config), - ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), - ?line ok = agent_load_mib(AgentNode, Test2Mib), - - Self = self(), - Msg = simple_async_get_bulk3, - Fun = fun() -> Self ! Msg end, - Extra = {?SNMPM_EXTRA_INFO_TAG, Fun}, - SendOpts = - [ - {extra, Extra} - ], - - GetBulk = - fun(Data) -> - async_gb_exec3(MgrNode, TargetName, Data, SendOpts) - end, - PostVerify = fun(ok) -> receive Msg -> ok end; - (Res) -> Res - end, - - Res = do_simple_async_get_bulk2(MgrNode, AgentNode, GetBulk, PostVerify), - display_log(Config), - Res. - async_gb_exec3(Node, TargetName, {NR, MR, Oids}, SendOpts) -> mgr_user_async_get_bulk2(Node, TargetName, NR, MR, Oids, SendOpts). @@ -3543,203 +3262,6 @@ simple_async_get_bulk3_cbp_perm(Config) when is_list(Config) -> %%====================================================================== -misc_async2(doc) -> - ["Misc (async) request(s) - Version 2 API (TargetName)"]; -misc_async2(suite) -> []; -misc_async2(Config) when is_list(Config) -> - ?TC_TRY(misc_async2, - fun() -> do_misc_async2(Config) end). - -do_misc_async2(Config) -> - ?IPRINT("starting with Config: " - "~n ~p" - "~n", [Config]), - - MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), - TargetName = ?config(manager_agent_target_name, Config), - - ?line ok = mgr_user_load_mib(MgrNode, std_mib()), - Test2Mib = test2_mib(Config), - ?line ok = mgr_user_load_mib(MgrNode, Test2Mib), - ?line ok = agent_load_mib(AgentNode, Test2Mib), - - ExecG = fun(Data) -> - async_g_exec2(MgrNode, TargetName, Data) - end, - - ExecGN = fun(Data) -> - async_gn_exec2(MgrNode, TargetName, Data) - end, - - ExecS = fun(Data) -> - async_s_exec2(MgrNode, TargetName, Data) - end, - - ExecGB = fun(Data) -> - async_gb_exec2(MgrNode, TargetName, Data) - end, - - ?line {ok, [TCnt2|_]} = mgr_user_name_to_oid(MgrNode, tCnt2), - ?line {ok, [TGenErr1|_]} = mgr_user_name_to_oid(MgrNode, tGenErr1), - ?line {ok, [TGenErr2|_]} = mgr_user_name_to_oid(MgrNode, tGenErr2), - ?line {ok, [TGenErr3|_]} = mgr_user_name_to_oid(MgrNode, tGenErr3), - ?line {ok, [TTooBig|_]} = mgr_user_name_to_oid(MgrNode, tTooBig), - - Requests = - [ - { 1, - [?sysObjectID_instance], - ExecG, - fun(X) -> - sag_verify(X, [?sysObjectID_instance]) - end - }, - { 2, - {1, 1, []}, - ExecGB, - fun verify_ssgb_reply1/1}, - { 3, - {-1, 1, []}, - ExecGB, - fun verify_ssgb_reply1/1}, - { 4, - [{?sysLocation_instance, s, "Stockholm"}, - {?sysName_instance, s, "Arne Anka"}], - ExecS, - fun(X) -> - sas_verify(X, [?sysLocation_instance, ?sysName_instance]) - end}, - { 5, - [[sysDescr], [1,3,7,1]], - ExecGN, - fun(X) -> - verify_ssgn_reply1(X, [?sysDescr_instance, endOfMibView]) - end}, - { 6, - [[sysObjectID, 0], [sysDescr, 0], [sysUpTime, 0]], - ExecG, - fun(X) -> - sag_verify(X, [?sysObjectID_instance, - ?sysDescr_instance, - ?sysUpTime_instance]) - end}, - { 7, - [TGenErr2], - ExecGN, - fun(X) -> - verify_ssgn_reply2(X, {genErr, 1, [TGenErr2]}) - end}, - { 8, - {2, 0, [[sysDescr],[1,3,7,1]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView]) - end}, - { 9, - {1, 2, [[sysDescr],[1,3,7,1]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply2(X, [?sysDescr_instance, endOfMibView]) - end}, - {10, - [TGenErr1], - ExecGN, - fun(X) -> - verify_ssgn_reply2(X, {genErr, 1, [TGenErr1]}) - end}, - {11, - {0, 2, [[sysDescr],[1,3,7,1]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply2(X, - [?sysDescr_instance, endOfMibView, - ?sysObjectID_instance, endOfMibView]) - end}, - {12, - [{[sysName, 0], "Gothenburg"}, - {[sysLocation, 0], "Sune Anka"}], - ExecS, - fun(X) -> - sas_verify(X, [?sysName_instance, ?sysLocation_instance]) - end}, - {13, - {2, 2, [[sysDescr],[1,3,7,1],[sysDescr],[1,3,7,1]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply2(X, - [?sysDescr_instance, endOfMibView, - ?sysDescr_instance, endOfMibView, - ?sysObjectID_instance, endOfMibView]) - end}, - {14, - {1, 2, [[sysDescr],[sysDescr],[tTooBig]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply2(X, - [?sysDescr_instance, - ?sysDescr_instance]) - end}, - {15, - {1, 12, [[tDescr2], [sysDescr]]}, - ExecGB, - fun verify_ssgb_reply1/1}, - {16, - {2, 2, [[sysDescr],[sysObjectID], [tGenErr1],[sysDescr]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply3(X, - [{?sysDescr, 'NULL'}, - {?sysObjectID, 'NULL'}, - {?tGenErr1, 'NULL'}, - {?sysDescr, 'NULL'}]) - end}, - {17, - [[sysDescr], TGenErr3], - ExecGN, - fun(X) -> - verify_ssgn_reply2(X, {genErr, 2, [TGenErr3]}) - end}, - {18, - {0, 2, [[TCnt2, 1]]}, - ExecGB, - fun(X) -> - verify_ssgb_reply2(X, - [{fl([TCnt2,2]), 100}, - {fl([TCnt2,2]), endOfMibView}]) - end}, - {19, - [TTooBig], - ExecGN, - fun(X) -> - verify_ssgn_reply2(X, {tooBig, 0, []}) - end}, - {20, - [TTooBig], - ExecGN, - fun(X) -> - verify_ssgn_reply2(X, {tooBig, 0, []}) - end} - ], - - ?IPRINT("manager info when starting test: " - "~n ~p", [mgr_info(MgrNode)]), - ?IPRINT("agent info when starting test: " - "~n ~p", [agent_info(AgentNode)]), - - ?line ok = async_exec(Requests, []), - - ?IPRINT("manager info when ending test: " - "~n ~p", [mgr_info(MgrNode)]), - ?IPRINT("agent info when ending test: " - "~n ~p", [agent_info(AgentNode)]), - - display_log(Config), - ok. - - -%%====================================================================== - discovery(suite) -> []; discovery(Config) when is_list(Config) -> ?SKIP(not_yet_implemented). @@ -4764,7 +4286,8 @@ do_inform_swarm(Config) -> "~p ~n", [Config]), MgrNode = ?config(manager_node, Config), - AgentNode = ?config(agent_node, Config), + AgentNode = ?config(agent_node, Config), + Factor = ?config(snmp_factor, Config), ?line ok = mgr_user_load_mib(MgrNode, snmpv2_mib()), Test2Mib = test2_mib(Config), @@ -4776,7 +4299,7 @@ do_inform_swarm(Config) -> ?line ok = agent_load_mib(AgentNode, Test2Mib), ?line ok = agent_load_mib(AgentNode, TestTrapMib), ?line ok = agent_load_mib(AgentNode, TestTrapv2Mib), - NumInforms = 2000, + NumInforms = 2000 div Factor, Collector = self(), @@ -4796,8 +4319,8 @@ do_inform_swarm(Config) -> {{inform2_tag1, N}, Collector}, "standard inform", []), - %% Sleep some [(N div 10)*100 ms] - %% every tenth notification + %% Sleep 1000 ms every 100th notif + %% Sleep 100 ms every 10th notif if N rem 100 == 0 -> Sleep = 1000, @@ -4836,11 +4359,11 @@ do_inform_swarm(Config) -> Commands = [ - {1, "Manager and agent info at start of test", Cmd1}, - {2, "Send notifcation(s) from agent", Cmd2}, - {3, "Await send-ack(s)/inform(s)/response(s)", Cmd3}, - {4, "Sleep some time (1 sec)", Cmd4}, - {5, "Manager and agent info after test completion", Cmd1} + {1, "Manager and agent info at start of test", Cmd1}, + {2, ?F("Send ~p notifcation(s) from agent", [NumInforms]), Cmd2}, + {3, "Await send-ack(s)/inform(s)/response(s)", Cmd3}, + {4, "Sleep some time (1 sec)", Cmd4}, + {5, "Manager and agent info after test completion", Cmd1} ], Res = command_handler(Commands), @@ -4885,7 +4408,7 @@ inform_swarm_collector(N, SentAckCnt, RecvCnt, RespCnt, Timeout) -> %% The manager has received the actual inform {async_event, From, {inform, Pid, Inform}} -> - ?IPRINT("received inform"), + ?IPRINT("received inform (~p of ~p)", [RecvCnt+1, N]), case Inform of {noError, 0, VBs} when is_list(VBs) -> Pid ! {handle_inform_response, From}, @@ -5001,7 +4524,7 @@ otp8395_1(Config) when is_list(Config) -> fun() -> do_otp8395_1(Config) end). do_otp8395_1(Config) -> - do_simple_sync_get2(Config). + do_simple_sync_get3(Config). %%====================================================================== @@ -5707,16 +5230,16 @@ mgr_user_load_mib(Node, Mib) -> %% mgr_user_sync_get(Node, Oids) -> %% mgr_user_sync_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids). -mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) -> - rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]). +%% mgr_user_sync_get(Node, TargetName, Oids) when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, sync_get, [TargetName, Oids]). mgr_user_sync_get2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) -> rcall(Node, snmp_manager_user, sync_get2, [TargetName, Oids, SendOpts]). %% mgr_user_async_get(Node, Oids) -> %% mgr_user_async_get(Node, ?LOCALHOST(), ?AGENT_PORT, Oids). -mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) -> - rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]). +%% mgr_user_async_get(Node, TargetName, Oids) when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, async_get, [TargetName, Oids]). mgr_user_async_get2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) -> @@ -5724,8 +5247,8 @@ mgr_user_async_get2(Node, TargetName, Oids, SendOpts) %% mgr_user_sync_get_next(Node, Oids) -> %% mgr_user_sync_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids). -mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) -> - rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]). +%% mgr_user_sync_get_next(Node, TargetName, Oids) when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, sync_get_next, [TargetName, Oids]). mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) -> @@ -5733,8 +5256,8 @@ mgr_user_sync_get_next2(Node, TargetName, Oids, SendOpts) %% mgr_user_async_get_next(Node, Oids) -> %% mgr_user_async_get_next(Node, ?LOCALHOST(), ?AGENT_PORT, Oids). -mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) -> - rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]). +%% mgr_user_async_get_next(Node, TargetName, Oids) when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, async_get_next, [TargetName, Oids]). mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts) when is_list(TargetName) -> @@ -5742,16 +5265,16 @@ mgr_user_async_get_next2(Node, TargetName, Oids, SendOpts) %% mgr_user_sync_set(Node, VAV) -> %% mgr_user_sync_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV). -mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) -> - rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]). +%% mgr_user_sync_set(Node, TargetName, VAV) when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, sync_set, [TargetName, VAV]). mgr_user_sync_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) -> rcall(Node, snmp_manager_user, sync_set2, [TargetName, VAV, SendOpts]). %% mgr_user_async_set(Node, VAV) -> %% mgr_user_async_set(Node, ?LOCALHOST(), ?AGENT_PORT, VAV). -mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) -> - rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]). +%% mgr_user_async_set(Node, TargetName, VAV) when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, async_set, [TargetName, VAV]). mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) -> rcall(Node, snmp_manager_user, async_set2, [TargetName, VAV, SendOpts]). @@ -5759,10 +5282,10 @@ mgr_user_async_set2(Node, TargetName, VAV, SendOpts) when is_list(TargetName) -> %% mgr_user_sync_get_bulk(Node, NonRep, MaxRep, Oids) -> %% mgr_user_sync_get_bulk(Node, ?LOCALHOST(), ?AGENT_PORT, %% NonRep, MaxRep, Oids). -mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids) - when is_list(TargetName) -> - rcall(Node, snmp_manager_user, sync_get_bulk, - [TargetName, NonRep, MaxRep, Oids]). +%% mgr_user_sync_get_bulk(Node, TargetName, NonRep, MaxRep, Oids) +%% when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, sync_get_bulk, +%% [TargetName, NonRep, MaxRep, Oids]). mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts) when is_list(TargetName) -> @@ -5772,10 +5295,10 @@ mgr_user_sync_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts) %% mgr_user_async_get_bulk(Node, NonRep, MaxRep, Oids) -> %% mgr_user_async_get_bulk(Node, ?LOCALHOST(), ?AGENT_PORT, %% NonRep, MaxRep, Oids). -mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids) - when is_list(TargetName) -> - rcall(Node, snmp_manager_user, async_get_bulk, - [TargetName, NonRep, MaxRep, Oids]). +%% mgr_user_async_get_bulk(Node, TargetName, NonRep, MaxRep, Oids) +%% when is_list(TargetName) -> +%% rcall(Node, snmp_manager_user, async_get_bulk, +%% [TargetName, NonRep, MaxRep, Oids]). mgr_user_async_get_bulk2(Node, TargetName, NonRep, MaxRep, Oids, SendOpts) when is_list(TargetName) -> diff --git a/lib/snmp/test/snmp_manager_user.erl b/lib/snmp/test/snmp_manager_user.erl index 409f87cf40..60a6844875 100644 --- a/lib/snmp/test/snmp_manager_user.erl +++ b/lib/snmp/test/snmp_manager_user.erl @@ -51,14 +51,14 @@ update_agent_info/3, which_all_agents/0, which_own_agents/0, load_mib/1, unload_mib/1, - sync_get/1, sync_get/2, sync_get2/3, - async_get/1, async_get/2, async_get2/3, - sync_get_next/1, sync_get_next/2, sync_get_next2/3, - async_get_next/1, async_get_next/2, async_get_next2/3, - sync_set/1, sync_set/2, sync_set2/3, - async_set/1, async_set/2, async_set2/3, - sync_get_bulk/3, sync_get_bulk/4, sync_get_bulk2/5, - async_get_bulk/3, async_get_bulk/4, async_get_bulk2/5, + sync_get2/3, + async_get2/3, + sync_get_next2/3, + async_get_next2/3, + sync_set2/3, + async_set2/3, + sync_get_bulk2/5, + async_get_bulk2/5, name_to_oid/1, oid_to_name/1, purify_oid/1 ]). @@ -159,90 +159,42 @@ unload_mib(Mib) -> %% -- -sync_get(Oids) -> - call({sync_get, Oids}). - -sync_get(TargetName, Oids) -> - call({sync_get, TargetName, Oids}). - sync_get2(TargetName, Oids, SendOpts) -> call({sync_get2, TargetName, Oids, SendOpts}). %% -- -async_get(Oids) -> - call({async_get, Oids}). - -async_get(TargetName, Oids) -> - call({async_get, TargetName, Oids}). - async_get2(TargetName, Oids, SendOpts) -> call({async_get2, TargetName, Oids, SendOpts}). %% -- -sync_get_next(Oids) -> - call({sync_get_next, Oids}). - -sync_get_next(TargetName, Oids) -> - call({sync_get_next, TargetName, Oids}). - sync_get_next2(TargetName, Oids, SendOpts) -> call({sync_get_next2, TargetName, Oids, SendOpts}). %% -- -async_get_next(Oids) -> - call({async_get_next, Oids}). - -async_get_next(TargetName, Oids) -> - call({async_get_next, TargetName, Oids}). - async_get_next2(TargetName, Oids, SendOpts) -> call({async_get_next2, TargetName, Oids, SendOpts}). %% -- -sync_set(VAV) -> - call({sync_set, VAV}). - -sync_set(TargetName, VAV) -> - call({sync_set, TargetName, VAV}). - sync_set2(TargetName, VAV, SendOpts) -> call({sync_set2, TargetName, VAV, SendOpts}). %% -- -async_set(VAV) -> - call({async_set, VAV}). - -async_set(TargetName, VAV) -> - call({async_set, TargetName, VAV}). - async_set2(TargetName, VAV, SendOpts) -> call({async_set2, TargetName, VAV, SendOpts}). %% -- -sync_get_bulk(NonRep, MaxRep, Oids) -> - call({sync_get_bulk, NonRep, MaxRep, Oids}). - -sync_get_bulk(TargetName, NonRep, MaxRep, Oids) -> - call({sync_get_bulk, TargetName, NonRep, MaxRep, Oids}). - sync_get_bulk2(TargetName, NonRep, MaxRep, Oids, SendOpts) -> call({sync_get_bulk2, TargetName, NonRep, MaxRep, Oids, SendOpts}). %% -- -async_get_bulk(NonRep, MaxRep, Oids) -> - call({async_get_bulk, NonRep, MaxRep, Oids}). - -async_get_bulk(TargetName, NonRep, MaxRep, Oids) -> - call({async_get_bulk, TargetName, NonRep, MaxRep, Oids}). - async_get_bulk2(TargetName, NonRep, MaxRep, Oids, SendOpts) -> call({async_get_bulk2, TargetName, NonRep, MaxRep, Oids, SendOpts}). @@ -377,23 +329,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - %% No agent specified, so send it to all of them - {{sync_get, Oids}, From, Ref} -> - d("loop -> received sync_get request " - "(for every agent of this user)"), - Res = [snmpm:sync_get(Id, TargetName, Oids) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{sync_get, TargetName, Oids}, From, Ref} when is_list(TargetName) -> - d("loop -> received sync_get request with" - "~n TargetName: ~p" - "~n Oids: ~p", [TargetName, Oids]), - Res = snmpm:sync_get(Id, TargetName, Oids), - reply(From, Res, Ref), - loop(S); - %% %% -- (async) get-request -- @@ -409,22 +344,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - %% No agent specified, so send it to all of them - {{async_get, Oids}, From, Ref} -> - d("loop -> received async_get request"), - Res = [snmpm:async_get(Id, TargetName, Oids) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{async_get, TargetName, Oids}, From, Ref} when is_list(TargetName) -> - d("loop -> received async_get request with" - "~n TargetName: ~p" - "~n Oids: ~p", [TargetName, Oids]), - Res = snmpm:async_get(Id, TargetName, Oids), - reply(From, Res, Ref), - loop(S); - %% %% -- (sync) get_next-request -- @@ -440,22 +359,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - %% No agent specified, so send it to all of them - {{sync_get_next, Oids}, From, Ref} -> - d("loop -> received sync_get_next request"), - Res = [snmpm:sync_get_next(Id, TargetName, Oids) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{sync_get_next, TargetName, Oids}, From, Ref} when is_list(TargetName) -> - d("loop -> received sync_get_next request with" - "~n TargetName: ~p" - "~n Oids: ~p", [TargetName, Oids]), - Res = snmpm:sync_get_next(Id, TargetName, Oids), - reply(From, Res, Ref), - loop(S); - %% %% -- (async) get_next-request -- @@ -471,22 +374,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - %% No agent specified, so send it to all of them - {{async_get_next, Oids}, From, Ref} -> - d("loop -> received async_get_next request"), - Res = [snmpm:async_get_next(Id, TargetName, Oids) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{async_get_next, TargetName, Oids}, From, Ref} when is_list(TargetName) -> - d("loop -> received async_get_next request with" - "~n TargetName: ~p" - "~n Oids: ~p", [TargetName, Oids]), - Res = snmpm:async_get_next(Id, TargetName, Oids), - reply(From, Res, Ref), - loop(S); - %% %% -- (sync) set-request -- @@ -502,19 +389,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - {{sync_set, VAV}, From, Ref} -> - d("loop -> received sync_set request"), - Res = [snmpm:sync_set(Id, TargetName, VAV) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{sync_set, TargetName, VAV}, From, Ref} when is_list(TargetName) -> - d("loop -> received sync_set request"), - Res = snmpm:sync_set(Id, TargetName, VAV), - reply(From, Res, Ref), - loop(S); - %% %% -- (async) set-request -- @@ -530,19 +404,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - {{async_set, VAV}, From, Ref} -> - d("loop -> received async_set request"), - Res = [snmpm:async_set(Id, TargetName, VAV) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{async_set, TargetName, VAV}, From, Ref} when is_list(TargetName) -> - d("loop -> received async_set request"), - Res = snmpm:async_set(Id, TargetName, VAV), - reply(From, Res, Ref), - loop(S); - %% %% -- (sync) get-bulk-request -- @@ -562,27 +423,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - %% No agent specified, so send it to all of them - {{sync_get_bulk, NonRep, MaxRep, Oids}, From, Ref} -> - d("loop -> received sync_get_bulk request with" - "~n NonRep: ~w" - "~n MaxRep: ~w" - "~n Oids: ~p", [NonRep, MaxRep, Oids]), - Res = [snmpm:sync_get_bulk(Id, TargetName, NonRep, MaxRep, Oids) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{sync_get_bulk, TargetName, NonRep, MaxRep, Oids}, From, Ref} when is_list(TargetName) -> - d("loop -> received sync_get_bulk request with" - "~n TargetName: ~p" - "~n NonRep: ~w" - "~n MaxRep: ~w" - "~n Oids: ~p", [TargetName, NonRep, MaxRep, Oids]), - Res = snmpm:sync_get_bulk(Id, TargetName, NonRep, MaxRep, Oids), - reply(From, Res, Ref), - loop(S); - %% %% -- (async) get-bulk-request -- @@ -602,27 +442,6 @@ loop(#state{parent = Parent, id = Id} = S) -> reply(From, Res, Ref), loop(S); - %% No agent specified, so send it to all of them - {{async_get_bulk, NonRep, MaxRep, Oids}, From, Ref} -> - d("loop -> received async_get_bulk request with" - "~n NonRep: ~w" - "~n MaxRep: ~w" - "~n Oids: ~p", [NonRep, MaxRep, Oids]), - Res = [snmpm:async_get_bulk(Id, TargetName, NonRep, MaxRep, Oids) || - TargetName <- snmpm:which_agents(Id)], - reply(From, Res, Ref), - loop(S); - - {{async_get_bulk, TargetName, NonRep, MaxRep, Oids}, From, Ref} when is_list(TargetName) -> - d("loop -> received async_get_bulk request with" - "~n TargetName: ~p" - "~n NonRep: ~w" - "~n MaxRep: ~w" - "~n Oids: ~p", [TargetName, NonRep, MaxRep, Oids]), - Res = snmpm:async_get_bulk(Id, TargetName, NonRep, MaxRep, Oids), - reply(From, Res, Ref), - loop(S); - %% %% -- logical name translation -- diff --git a/lib/snmp/test/snmp_manager_user_SUITE.erl b/lib/snmp/test/snmp_manager_user_SUITE.erl index eca0d8a4f9..bad746b29a 100644 --- a/lib/snmp/test/snmp_manager_user_SUITE.erl +++ b/lib/snmp/test/snmp_manager_user_SUITE.erl @@ -888,26 +888,17 @@ register_monitor_and_crash3(Conf) when is_list(Conf) -> put(tname,rlac3), %% <CONDITIONAL-SKIP> - %% The point of this is to catch machines running - %% SLES9 (2.6.5) LinuxVersionVerify = fun() -> - case os:cmd("uname -m") of - "i686" ++ _ -> -%% io:format("found an i686 machine, " -%% "now check version~n", []), - case os:version() of - {2, 6, Rev} when Rev >= 16 -> - true; - {2, Min, _} when Min > 6 -> - true; - {Maj, _, _} when Maj > 2 -> - true; - _ -> - false - end; - _ -> - true + case os:version() of + V when V > {2, 6, 16} -> + ?IPRINT("(Linux) kernel version check: " + "~p > {2, 6, 16} => *NO* SKIP", [V]), + false; + V -> + ?IPRINT("(Linux) kernel version check: " + "~p =< {2, 6, 16} => *SKIP*", [V]), + true end end, Skippable = [{unix, [{linux, LinuxVersionVerify}]}], @@ -922,10 +913,10 @@ register_monitor_and_crash3(Conf) when is_list(Conf) -> write_manager_conf(ConfDir), - Opts = [{server, [{verbosity, trace}]}, - {net_if, [{verbosity, trace}]}, + Opts = [{server, [{verbosity, trace}]}, + {net_if, [{verbosity, trace}]}, {note_store, [{verbosity, trace}]}, - {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}], + {config, [{verbosity, trace}, {dir, ConfDir}, {db_dir, DbDir}]}], ?IPRINT("try starting manager"), ?line ok = snmpm:start_link(Opts), diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl index fe6fef291d..76f424d25c 100644 --- a/lib/snmp/test/snmp_test_lib.erl +++ b/lib/snmp/test/snmp_test_lib.erl @@ -25,6 +25,7 @@ -export([tc_try/2, tc_try/3, tc_try/4, tc_try/5]). +-export([proxy_call/3]). -export([hostname/0, hostname/1, localhost/0, localhost/1, os_type/0, sz/1, display_suite_info/1]). -export([non_pc_tc_maybe_skip/4, @@ -257,6 +258,19 @@ tc_which_name() -> %% Misc functions %% +proxy_call(F, Timeout, Default) + when is_function(F, 0) andalso is_integer(Timeout) andalso (Timeout > 0) -> + {P, M} = erlang:spawn_monitor(fun() -> exit(F()) end), + receive + {'DOWN', M, process, P, Reply} -> + Reply + after Timeout -> + erlang:demonitor(M, [flush]), + exit(P, kill), + Default + end. + + hostname() -> hostname(node()). @@ -268,20 +282,6 @@ hostname(Node) -> [] end. -%% localhost() -> -%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()), -%% Ip. -%% localhost(Family) -> -%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family), -%% Ip. - -%% localhost() -> -%% {ok, Ip} = snmp_misc:ip(net_adm:localhost()), -%% Ip. -%% localhost(Family) -> -%% {ok, Ip} = snmp_misc:ip(net_adm:localhost(), Family), -%% Ip. - localhost() -> localhost(inet). @@ -467,6 +467,10 @@ os_base_skip(Skippable, OsFam, OsName) -> case lists:keysearch(OsFam, 1, Skippable) of {value, {OsFam, OsName}} -> true; + {value, {OsFam, Check}} when is_function(Check, 0) -> + Check(); + {value, {OsFam, Check}} when is_function(Check, 1) -> + Check(os:version()); {value, {OsFam, OsNames}} when is_list(OsNames) -> %% OsNames is a list of: %% [atom()|{atom(), function/0 | function/1}] @@ -503,7 +507,7 @@ has_support_ipv6() -> %% so for windows we need to use the old style... old_has_support_ipv6(); _ -> - socket:supports(ipv6) andalso has_valid_ipv6_address() + socket:is_supported(ipv6) andalso has_valid_ipv6_address() end. has_valid_ipv6_address() -> @@ -619,11 +623,31 @@ old_is_ipv6_host(Hostname) -> init_per_suite(Config) -> + ct:timetrap(minutes(2)), + + try analyze_and_print_host_info() of + {Factor, HostInfo} when is_integer(Factor) -> + try maybe_skip(HostInfo) of + true -> + {skip, "Unstable host and/or os (or combo thererof)"}; + false -> + snmp_test_global_sys_monitor:start(), + [{snmp_factor, Factor} | Config] + catch + throw:{skip, _} = SKIP -> + SKIP + end + catch + throw:{skip, _} = SKIP -> + SKIP + end. + +maybe_skip(HostInfo) -> + %% We have some crap machines that causes random test case failures %% for no obvious reason. So, attempt to identify those without actually %% checking for the host name... - %% We have two "machines" we are checking for. Both are old installations - %% running on really slow VMs (the host machines are old and tired). + LinuxVersionVerify = fun(V) when (V > {3,6,11}) -> false; % OK - No skip @@ -634,8 +658,30 @@ init_per_suite(Config) -> _ -> false end; + (V) when (V =:= {3,4,20}) -> + case string:trim(os:cmd("cat /etc/issue")) of + "Wind River Linux 5.0.1.0" ++ _ -> % *Old* Wind River => skip + true; + _ -> + false + end; + (V) when (V =:= {2,6,32}) -> + case string:trim(os:cmd("cat /etc/issue")) of + "Debian GNU/Linux 6.0 " ++ _ -> % Stone age Debian => Skip + true; + _ -> + false + end; (V) when (V > {2,6,24}) -> false; % OK - No skip + (V) when (V =:= {2,6,10}) -> + case string:trim(os:cmd("cat /etc/issue")) of + "MontaVista" ++ _ -> % Stone age MontaVista => Skip + %% The real problem is that the machine is *very* slow + true; + _ -> + false + end; (_) -> %% We are specifically checking for %% a *really* old gento... @@ -646,15 +692,28 @@ init_per_suite(Config) -> true end end, - COND = [{unix, [{linux, LinuxVersionVerify}]}], - case os_based_skip(COND) of - true -> - {skip, "Unstable host and/or os (or combo thererof)"}; - false -> - Factor = analyze_and_print_host_info(), - snmp_test_global_sys_monitor:start(), - [{snmp_factor, Factor} | Config] - end. + DarwinVersionVerify = + fun(V) when (V > {9, 8, 0}) -> + %% This version is OK: No Skip + false; + (_V) -> + %% This version is *not* ok: Skip + true + end, + SkipWindowsOnVirtual = + fun() -> + SysMan = win_sys_info_lookup(system_manufacturer, HostInfo), + case string:to_lower(SysMan) of + "vmware" ++ _ -> + true; + _ -> + false + end + end, + COND = [{unix, [{linux, LinuxVersionVerify}, + {darwin, DarwinVersionVerify}]}, + {win32, SkipWindowsOnVirtual}], + os_based_skip(COND). end_per_suite(Config) when is_list(Config) -> @@ -795,7 +854,7 @@ skip(Reason, Module, Line) -> %% when analyzing the test suite (results). %% It also returns a "factor" that can be used when deciding %% the load for some test cases. Such as run time or number of -%% iteraions. This only works for some OSes. +%% iterations. This only works for some OSes. %% %% We make some calculations on Linux, OpenBSD and FreeBSD. %% On SunOS we always set the factor to 2 (just to be on the safe side) @@ -819,6 +878,8 @@ analyze_and_print_host_info() -> analyze_and_print_freebsd_host_info(Version); {unix, netbsd} -> analyze_and_print_netbsd_host_info(Version); + {unix, darwin} -> + analyze_and_print_darwin_host_info(Version); {unix, sunos} -> analyze_and_print_solaris_host_info(Version); {win32, nt} -> @@ -829,112 +890,288 @@ analyze_and_print_host_info() -> "~n Version: ~p" "~n Num Schedulers: ~s" "~n", [OsFam, OsName, Version, str_num_schedulers()]), - try erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - N when (N =< 6) -> - 2; - _ -> - 1 - catch - _:_:_ -> - 10 - end + {num_schedulers_to_factor(), []} end. - -analyze_and_print_linux_host_info(Version) -> + +linux_which_distro(Version) -> case file:read_file_info("/etc/issue") of {ok, _} -> - io:format("Linux: ~s" - "~n ~s" - "~n", - [Version, string:trim(os:cmd("cat /etc/issue"))]); + case [string:trim(S) || + S <- string:tokens(os:cmd("cat /etc/issue"), [$\n])] of + [DistroStr|_] -> + io:format("Linux: ~s" + "~n ~s" + "~n", + [Version, DistroStr]), + case DistroStr of + "Wind River Linux" ++ _ -> + wind_river; + "MontaVista" ++ _ -> + montavista; + "Yellow Dog" ++ _ -> + yellow_dog; + _ -> + other + end; + X -> + io:format("Linux: ~s" + "~n ~p" + "~n", + [Version, X]), + other + end; _ -> io:format("Linux: ~s" - "~n", [Version]) - end, + "~n", [Version]), + other + end. + +analyze_and_print_linux_host_info(Version) -> + Distro = + case file:read_file_info("/etc/issue") of + {ok, _} -> + linux_which_distro(Version); + _ -> + io:format("Linux: ~s" + "~n", [Version]), + other + end, Factor = - case (catch linux_which_cpuinfo()) of + case (catch linux_which_cpuinfo(Distro)) of {ok, {CPU, BogoMIPS}} -> io:format("CPU: " - "~n Model: ~s" - "~n BogoMIPS: ~s" - "~n", [CPU, BogoMIPS]), - %% We first assume its a float, and if not try integer - try list_to_float(string:trim(BogoMIPS)) of - F when F > 1000 -> + "~n Model: ~s" + "~n BogoMIPS: ~w" + "~n Num Schedulers: ~s" + "~n", [CPU, BogoMIPS, str_num_schedulers()]), + if + (BogoMIPS > 20000) -> 1; - F when F > 1000 -> + (BogoMIPS > 10000) -> 2; - _ -> - 3 - catch - _:_:_ -> - %% - try list_to_integer(string:trim(BogoMIPS)) of - I when I > 1000 -> - 1; - I when I > 1000 -> - 2; - _ -> - 3 - catch - _:_:_ -> - 1 - end + (BogoMIPS > 5000) -> + 3; + (BogoMIPS > 2000) -> + 5; + (BogoMIPS > 1000) -> + 8; + true -> + 10 + end; + {ok, CPU} -> + io:format("CPU: " + "~n Model: ~s" + "~n Num Schedulers: ~s" + "~n", [CPU, str_num_schedulers()]), + NumChed = erlang:system_info(schedulers), + if + (NumChed > 2) -> + 2; + true -> + 5 end; _ -> - 1 + 5 end, %% Check if we need to adjust the factor because of the memory try linux_which_meminfo() of AddFactor -> io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]), - Factor + AddFactor + {Factor + AddFactor, []} catch _:_:_ -> io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]), - Factor + {Factor, []} + end. + + + +linux_cpuinfo_lookup(Key) when is_list(Key) -> + linux_info_lookup(Key, "/proc/cpuinfo"). + +linux_cpuinfo_cpu() -> + case linux_cpuinfo_lookup("cpu") of + [Model] -> + Model; + _ -> + "-" + end. + +linux_cpuinfo_motherboard() -> + case linux_cpuinfo_lookup("motherboard") of + [MB] -> + MB; + _ -> + "-" + end. + +linux_cpuinfo_bogomips() -> + case linux_cpuinfo_lookup("bogomips") of + BMips when is_list(BMips) -> + try lists:sum([bogomips_to_int(BM) || BM <- BMips]) + catch + _:_:_ -> + "-" + end; + _ -> + "-" + end. + +linux_cpuinfo_total_bogomips() -> + case linux_cpuinfo_lookup("total bogomips") of + [TBM] -> + try bogomips_to_int(TBM) + catch + _:_:_ -> + "-" + end; + _ -> + "-" + end. + +bogomips_to_int(BM) -> + try list_to_float(BM) of + F -> + floor(F) + catch + _:_:_ -> + try list_to_integer(BM) of + I -> + I + catch + _:_:_ -> + throw(noinfo) + end + end. + +linux_cpuinfo_model() -> + case linux_cpuinfo_lookup("model") of + [M] -> + M; + _ -> + "-" + end. + +linux_cpuinfo_platform() -> + case linux_cpuinfo_lookup("platform") of + [P] -> + P; + _ -> + "-" + end. + +linux_cpuinfo_model_name() -> + case linux_cpuinfo_lookup("model name") of + [P|_] -> + P; + _ -> + "-" end. -linux_which_cpuinfo() -> - %% Check for x86 (Intel or AMD) +linux_cpuinfo_processor() -> + case linux_cpuinfo_lookup("Processor") of + [P] -> + P; + _ -> + "-" + end. + + +linux_which_cpuinfo(montavista) -> CPU = - try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of - ["model name", ModelName | _] -> - ModelName; - _ -> + case linux_cpuinfo_cpu() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_motherboard() of + "-" -> + Model; + MB -> + Model ++ " (" ++ MB ++ ")" + end + end, + case linux_cpuinfo_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end; + +linux_which_cpuinfo(yellow_dog) -> + CPU = + case linux_cpuinfo_cpu() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_motherboard() of + "-" -> + Model; + MB -> + Model ++ " (" ++ MB ++ ")" + end + end, + {ok, CPU}; + +linux_which_cpuinfo(wind_river) -> + CPU = + case linux_cpuinfo_model() of + "-" -> + throw(noinfo); + Model -> + case linux_cpuinfo_platform() of + "-" -> + Model; + Platform -> + Model ++ " (" ++ Platform ++ ")" + end + end, + case linux_cpuinfo_total_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} + end; + +%% Check for x86 (Intel or AMD) +linux_which_cpuinfo(other) -> + CPU = + case linux_cpuinfo_model_name() of + "-" -> %% ARM (at least some distros...) - try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of - ["Processor", Proc | _] -> - Proc; - _ -> + case linux_cpuinfo_processor() of + "-" -> %% Ok, we give up - throw(noinfo) - catch - _:_:_ -> - throw(noinfo) - end - catch - _:_:_ -> - throw(noinfo) + throw(noinfo); + Proc -> + Proc + end; + ModelName -> + ModelName end, - try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of - [_, BMips | _] -> - {ok, {CPU, BMips}}; - _ -> - {ok, CPU} - catch - _:_:_ -> - {ok, CPU} + case linux_cpuinfo_bogomips() of + "-" -> + {ok, CPU}; + BMips -> + {ok, {CPU, BMips}} end. +linux_meminfo_lookup(Key) when is_list(Key) -> + linux_info_lookup(Key, "/proc/meminfo"). + +linux_meminfo_memtotal() -> + case linux_meminfo_lookup("MemTotal") of + [X] -> + X; + _ -> + "-" + end. + %% We *add* the value this return to the Factor. linux_which_meminfo() -> - try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of - [_, MemTotal] -> + case linux_meminfo_memtotal() of + "-" -> + 0; + MemTotal -> io:format("Memory:" "~n ~s" "~n", [MemTotal]), @@ -958,18 +1195,13 @@ linux_which_meminfo() -> (MemSz3 >= 4194304) -> 1; (MemSz3 >= 2097152) -> - 2; + 3; true -> - 3 + 5 end; _X -> 0 - end; - _ -> - 0 - catch - _:_:_ -> - 0 + end end. @@ -1056,12 +1288,12 @@ analyze_and_print_openbsd_host_info(Version) -> true -> 3 end, - CPUFactor + MemAddFactor + {CPUFactor + MemAddFactor, []} end catch _:_:_ -> io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]), - 1 + {2, []} end. @@ -1144,7 +1376,7 @@ analyze_and_print_freebsd_host_info(Version) -> true -> 3 end, - CPUFactor + MemAddFactor + {CPUFactor + MemAddFactor, []} end catch _:_:_ -> @@ -1152,14 +1384,15 @@ analyze_and_print_freebsd_host_info(Version) -> "~n Num Schedulers: ~s" "~n", [str_num_schedulers()]), io:format("TS Scale Factor: ~w~n", [timetrap_scale_factor()]), - case erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - _ -> - 2 - end + Factor = case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end, + {Factor, []} end. @@ -1283,21 +1516,22 @@ analyze_and_print_netbsd_host_info(Version) -> true -> 3 end, - CPUFactor + MemAddFactor + {CPUFactor + MemAddFactor, []} end catch _:_:_ -> io:format("CPU:" "~n Num Schedulers: ~w" "~n", [erlang:system_info(schedulers)]), - case erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - _ -> - 2 - end + Factor = case erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + _ -> + 2 + end, + {Factor, []} end. analyze_netbsd_cpu(Extract) -> @@ -1336,6 +1570,289 @@ analyze_netbsd_item(Extract, Key, Process, Default) -> +%% Model Identifier: Macmini7,1 +%% Processor Name: Intel Core i5 +%% Processor Speed: 2,6 GHz +%% Number of Processors: 1 +%% Total Number of Cores: 2 +%% L2 Cache (per Core): 256 KB +%% L3 Cache: 3 MB +%% Hyper-Threading Technology: Enabled +%% Memory: 16 GB + +analyze_and_print_darwin_host_info(Version) -> + %% This stuff is for macOS. + %% If we ever tested on a pure darwin machine, + %% we need to find some other way to find some info... + %% Also, I suppose its possible that we for some other + %% reason *fail* to get the info... + case analyze_darwin_software_info() of + [] -> + io:format("Darwin:" + "~n Version: ~s" + "~n Num Schedulers: ~s" + "~n", [Version, str_num_schedulers()]), + {num_schedulers_to_factor(), []}; + SwInfo when is_list(SwInfo) -> + SystemVersion = analyze_darwin_sw_system_version(SwInfo), + KernelVersion = analyze_darwin_sw_kernel_version(SwInfo), + HwInfo = analyze_darwin_hardware_info(), + ModelName = analyze_darwin_hw_model_name(HwInfo), + ModelId = analyze_darwin_hw_model_identifier(HwInfo), + ProcName = analyze_darwin_hw_processor_name(HwInfo), + ProcSpeed = analyze_darwin_hw_processor_speed(HwInfo), + NumProc = analyze_darwin_hw_number_of_processors(HwInfo), + NumCores = analyze_darwin_hw_total_number_of_cores(HwInfo), + Memory = analyze_darwin_hw_memory(HwInfo), + io:format("Darwin:" + "~n System Version: ~s" + "~n Kernel Version: ~s" + "~n Model: ~s (~s)" + "~n Processor: ~s (~s, ~s, ~s)" + "~n Memory: ~s" + "~n Num Schedulers: ~s" + "~n", [SystemVersion, KernelVersion, + ModelName, ModelId, + ProcName, ProcSpeed, NumProc, NumCores, + Memory, + str_num_schedulers()]), + CPUFactor = analyze_darwin_cpu_to_factor(ProcName, + ProcSpeed, + NumProc, + NumCores), + MemFactor = analyze_darwin_memory_to_factor(Memory), + if (MemFactor =:= 1) -> + {CPUFactor, []}; + true -> + {CPUFactor + MemFactor, []} + end + end. + +analyze_darwin_sw_system_version(SwInfo) -> + proplists:get_value("system version", SwInfo, "-"). + +analyze_darwin_sw_kernel_version(SwInfo) -> + proplists:get_value("kernel version", SwInfo, "-"). + +analyze_darwin_software_info() -> + analyze_darwin_system_profiler("SPSoftwareDataType"). + +analyze_darwin_hw_model_name(HwInfo) -> + proplists:get_value("model name", HwInfo, "-"). + +analyze_darwin_hw_model_identifier(HwInfo) -> + proplists:get_value("model identifier", HwInfo, "-"). + +analyze_darwin_hw_processor_name(HwInfo) -> + proplists:get_value("processor name", HwInfo, "-"). + +analyze_darwin_hw_processor_speed(HwInfo) -> + proplists:get_value("processor speed", HwInfo, "-"). + +analyze_darwin_hw_number_of_processors(HwInfo) -> + proplists:get_value("number of processors", HwInfo, "-"). + +analyze_darwin_hw_total_number_of_cores(HwInfo) -> + proplists:get_value("total number of cores", HwInfo, "-"). + +analyze_darwin_hw_memory(HwInfo) -> + proplists:get_value("memory", HwInfo, "-"). + +analyze_darwin_hardware_info() -> + analyze_darwin_system_profiler("SPHardwareDataType"). + +%% This basically has the structure: "Key: Value" +%% But could also be (for example): +%% "Something:" (which we ignore) +%% "Key: Value1:Value2" +analyze_darwin_system_profiler(DataType) -> + %% First, make sure the program actually exist: + case os:cmd("which system_profiler") of + [] -> + []; + _ -> + D0 = os:cmd("system_profiler " ++ DataType), + D1 = string:tokens(D0, [$\n]), + D2 = [string:trim(S1) || S1 <- D1], + D3 = [string:tokens(S2, [$:]) || S2 <- D2], + analyze_darwin_system_profiler2(D3) + end. + +analyze_darwin_system_profiler2(L) -> + analyze_darwin_system_profiler2(L, []). + +analyze_darwin_system_profiler2([], Acc) -> + [{string:to_lower(K), V} || {K, V} <- lists:reverse(Acc)]; +analyze_darwin_system_profiler2([[_]|T], Acc) -> + analyze_darwin_system_profiler2(T, Acc); +analyze_darwin_system_profiler2([[H1,H2]|T], Acc) -> + analyze_darwin_system_profiler2(T, [{H1, string:trim(H2)}|Acc]); +analyze_darwin_system_profiler2([[H|TH0]|T], Acc) -> + %% Some value parts has ':' in them, so put them together + TH1 = colonize(TH0), + analyze_darwin_system_profiler2(T, [{H, string:trim(TH1)}|Acc]). + +%% This is only called if the length is at least 2 +colonize([L1, L2]) -> + L1 ++ ":" ++ L2; +colonize([H|T]) -> + H ++ ":" ++ colonize(T). + + +%% The memory looks like this "<size> <unit>". Example: "2 GB" +analyze_darwin_memory_to_factor(Mem) -> + case [string:to_lower(S) || S <- string:tokens(Mem, [$\ ])] of + [_SzStr, "tb"] -> + 1; + [SzStr, "gb"] -> + try list_to_integer(SzStr) of + Sz when Sz < 2 -> + 20; + Sz when Sz < 4 -> + 10; + Sz when Sz < 8 -> + 5; + Sz when Sz < 16 -> + 2; + _ -> + 1 + catch + _:_:_ -> + 20 + end; + [_SzStr, "mb"] -> + 20; + _ -> + 20 + end. + + +%% The speed is a string: "<speed> <unit>" +%% the speed may be a float, which we transforms into an integer of MHz. +%% To calculate a factor based on processor speed, number of procs +%% and number of cores is ... not an exact ... science ... +analyze_darwin_cpu_to_factor(_ProcName, + ProcSpeedStr, NumProcStr, NumCoresStr) -> + Speed = + case [string:to_lower(S) || S <- string:tokens(ProcSpeedStr, [$\ ])] of + [SpeedStr, "mhz"] -> + try list_to_integer(SpeedStr) of + SpeedI -> + SpeedI + catch + _:_:_ -> + try list_to_float(SpeedStr) of + SpeedF -> + trunc(SpeedF) + catch + _:_:_ -> + -1 + end + end; + [SpeedStr, "ghz"] -> + try list_to_float(SpeedStr) of + SpeedF -> + trunc(1000*SpeedF) + catch + _:_:_ -> + try list_to_integer(SpeedStr) of + SpeedI -> + 1000*SpeedI + catch + _:_:_ -> + -1 + end + end; + _ -> + -1 + end, + NumProc = try list_to_integer(NumProcStr) of + NumProcI -> + NumProcI + catch + _:_:_ -> + 1 + end, + NumCores = try list_to_integer(NumCoresStr) of + NumCoresI -> + NumCoresI + catch + _:_:_ -> + 1 + end, + if + (Speed > 3000) -> + if + (NumProc =:= 1) -> + if + (NumCores < 2) -> + 5; + (NumCores < 4) -> + 3; + (NumCores < 6) -> + 2; + true -> + 1 + end; + true -> + if + (NumCores < 4) -> + 2; + true -> + 1 + end + end; + (Speed > 2000) -> + if + (NumProc =:= 1) -> + if + (NumCores < 2) -> + 8; + (NumCores < 4) -> + 5; + (NumCores < 6) -> + 3; + true -> + 1 + end; + true -> + if + (NumCores < 4) -> + 5; + (NumCores < 8) -> + 2; + true -> + 1 + end + end; + true -> + if + (NumProc =:= 1) -> + if + (NumCores < 2) -> + 10; + (NumCores < 4) -> + 7; + (NumCores < 6) -> + 5; + (NumCores < 8) -> + 3; + true -> + 1 + end; + true -> + if + (NumCores < 4) -> + 8; + (NumCores < 8) -> + 4; + true -> + 1 + end + end + end. + + analyze_and_print_solaris_host_info(Version) -> Release = case file:read_file_info("/etc/release") of @@ -1461,19 +1978,19 @@ analyze_and_print_solaris_host_info(Version) -> _:_:_ -> 10 end, - try erlang:system_info(schedulers) of - 1 -> - 10; - 2 -> - 5; - N when (N =< 6) -> - 2; - _ -> - 1 - catch - _:_:_ -> - 10 - end + MemFactor. + {try erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + N when (N =< 6) -> + 2; + _ -> + 1 + catch + _:_:_ -> + 10 + end + MemFactor, []}. @@ -1547,7 +2064,7 @@ analyze_and_print_win_host_info(Version) -> _ -> 2 end, - CPUFactor + MemFactor. + {CPUFactor + MemFactor, SysInfo}. win_sys_info_lookup(Key, SysInfo) -> win_sys_info_lookup(Key, SysInfo, "-"). @@ -1562,14 +2079,24 @@ win_sys_info_lookup(Key, SysInfo, Def) -> %% This function only extracts the prop we actually care about! which_win_system_info() -> - SysInfo = os:cmd("systeminfo"), - try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), []) - catch - _:_:_ -> - io:format("Failed process System info: " - "~s~n", [SysInfo]), - [] - end. + F = fun() -> + try + begin + SysInfo = os:cmd("systeminfo"), + process_win_system_info( + string:tokens(SysInfo, [$\r, $\n]), []) + end + catch + C:E:S -> + io:format("Failed get or process System info: " + " Error Class: ~p" + " Error: ~p" + " Stack: ~p" + "~n", [C, E, S]), + [] + end + end, + proxy_call(F, minutes(1), []). process_win_system_info([], Acc) -> Acc; @@ -1613,7 +2140,39 @@ str_num_schedulers() -> _:_:_ -> "-" end. - +num_schedulers_to_factor() -> + try erlang:system_info(schedulers) of + 1 -> + 10; + 2 -> + 5; + N when (N =< 6) -> + 2; + _ -> + 1 + catch + _:_:_ -> + 10 + end. + + +linux_info_lookup(Key, File) -> + try [string:trim(S) || S <- string:tokens(os:cmd("grep " ++ "\"" ++ Key ++ "\"" ++ " " ++ File), [$:,$\n])] of + Info -> + linux_info_lookup_collect(Key, Info, []) + catch + _:_:_ -> + "-" + end. + +linux_info_lookup_collect(_Key, [], Values) -> + lists:reverse(Values); +linux_info_lookup_collect(Key, [Key, Value|Rest], Values) -> + linux_info_lookup_collect(Key, Rest, [Value|Values]); +linux_info_lookup_collect(_, _, Values) -> + lists:reverse(Values). + + %% ---------------------------------------------------------------- %% Time related function @@ -1639,7 +2198,7 @@ sleep(MSecs) -> %% ---------------------------------------------------------------- %% Process utility function -%% +%% flush_mqueue() -> io:format("~p~n", [lists:reverse(flush_mqueue([]))]). @@ -1654,10 +2213,10 @@ flush_mqueue(MQ) -> trap_exit() -> - {trap_exit,Flag} = process_info(self(),trap_exit),Flag. + {trap_exit, Flag} = process_info(self(),trap_exit), Flag. trap_exit(Flag) -> - process_flag(trap_exit,Flag). + process_flag(trap_exit, Flag). diff --git a/lib/snmp/test/snmp_test_lib.hrl b/lib/snmp/test/snmp_test_lib.hrl index f4863c9a1e..a853d3cc09 100644 --- a/lib/snmp/test/snmp_test_lib.hrl +++ b/lib/snmp/test/snmp_test_lib.hrl @@ -52,10 +52,13 @@ -define(OS_BASED_SKIP(Skippable), ?LIB:os_based_skip(Skippable)). -define(NON_PC_TC_MAYBE_SKIP(Config, Condition), ?LIB:non_pc_tc_maybe_skip(Config, Condition, ?MODULE, ?LINE)). + -define(SKIP(Reason), ?LIB:skip(Reason, ?MODULE, ?LINE)). -define(FAIL(Reason), ?LIB:fail(Reason, ?MODULE, ?LINE)). -define(HAS_SUPPORT_IPV6(), ?LIB:has_support_ipv6()). +-define(PCALL(F, T, D), ?LIB:proxy_call(F, T, D)). + %% - Time macros - diff --git a/lib/snmp/test/snmp_test_manager.erl b/lib/snmp/test/snmp_test_manager.erl index 6ab5ce164c..fcbc0dff6b 100644 --- a/lib/snmp/test/snmp_test_manager.erl +++ b/lib/snmp/test/snmp_test_manager.erl @@ -193,22 +193,22 @@ handle_call(stop, _From, S) -> handle_call({sync_get, Oids}, _From, #state{agent_target_name = TargetName} = S) -> - Reply = (catch snmpm:sync_get(?USER, TargetName, Oids)), + Reply = (catch snmpm:sync_get2(?USER, TargetName, Oids)), {reply, Reply, S}; handle_call({sync_get_next, Oids}, _From, #state{agent_target_name = TargetName} = S) -> - Reply = (catch snmpm:sync_get_next(?USER, TargetName, Oids)), + Reply = (catch snmpm:sync_get_next2(?USER, TargetName, Oids)), {reply, Reply, S}; handle_call({sync_get_bulk, NR, MR, Oids}, _From, #state{agent_target_name = TargetName} = S) -> - Reply = (catch snmpm:sync_get_bulk(?USER, TargetName, NR, MR, Oids)), + Reply = (catch snmpm:sync_get_bulk2(?USER, TargetName, NR, MR, Oids)), {reply, Reply, S}; handle_call({sync_set, VarsAndVals}, _From, #state{agent_target_name = TargetName} = S) -> - Reply = (catch snmpm:sync_set(?USER, TargetName, VarsAndVals)), + Reply = (catch snmpm:sync_set2(?USER, TargetName, VarsAndVals)), {reply, Reply, S}; handle_call(Req, From, State) -> diff --git a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl index b5c08fb697..aacdcff504 100644 --- a/lib/snmp/test/snmp_to_snmpnet_SUITE.erl +++ b/lib/snmp/test/snmp_to_snmpnet_SUITE.erl @@ -435,9 +435,9 @@ erlang_manager_netsnmp_get(Config) when is_list(Config) -> {version, v2}, {sec_model, v2c}, {sec_level, noAuthNoPriv}]) || {Domain, Addr} <- Transports], Results = - [snmp_manager_user:sync_get( + [snmp_manager_user:sync_get2( TargetName++domain_suffix(Domain), - [?sysDescr_instance]) + [?sysDescr_instance], []) || {Domain, _} <- Transports], ct:pal("sync_get -> ~p", [Results]), snmp_manager_user:stop(), diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk index 84299ec250..39da86b2e1 100644 --- a/lib/snmp/vsn.mk +++ b/lib/snmp/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = snmp -SNMP_VSN = 5.5 +SNMP_VSN = 5.6 PRE_VSN = APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)" diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 4492b937a2..1cc25c00b3 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,247 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.10</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix error in ssh_sftpd typespec.</p> + <p> + Own Id: OTP-16363</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + The plug-in file ssh_file.erl, that is responsible for + default file handling, is re-factored, optimized and + re-written.</p> + <p> + Own Id: OTP-11688 Aux Id: OTP-12699 </p> + </item> + <item> + <p> + OpenSSH 6.5 introduced a new file representation of keys + called <url + href="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?annotate=1.1">openssh-key-v1</url>.</p> + <p> + OTP/SSH had an experimental implementation of this + format. That implementation is now improved and supported + with the exception of handling encrypted keys.</p> + <p> + Own Id: OTP-15434</p> + </item> + <item> + <p> + TCP/IP port forwarding, a.k.a tunneling a.k.a + tcp-forward/direct-tcp is implemented. In the OpenSSH + client, this corresponds to the options -L and -R.</p> + <p> + The client or server listens to a specified socket, and + when something connects to it with TCP/IP, that + connection is forwarded in an encrypted tunnel to the + peer. The peer then connects to a predefined IP/port pair + and then acts as a proxy.</p> + <p> + See the manual, <seemfa + marker="ssh:ssh#tcpip_tunnel_to_server/6"><c>ssh:tcpip_tunnel_to_server/6</c></seemfa> + and <seemfa + marker="ssh:ssh#tcpip_tunnel_from_server/6"><c>ssh:tcpip_tunnel_from_server/6</c></seemfa>.</p> + <p> + The functionality is disabled per default but can be + enabled when starting a daemon.</p> + <p> + Own Id: OTP-15998 Aux Id: PR-2376, PR-2368 </p> + </item> + <item> + <p> + The client-side of the supervisor tree (under sshc_sup) + was previously not complete; the channel handling + processes were handled with links but had no supervisors.</p> + <p> + This is now corrected with a client-side supervisor tree + under <c>sshc_sup</c>, similar to the server-side + supervisor tree under <c>sshd_sup</c>.</p> + <p> + Own Id: OTP-16026 Aux Id: PR-2368, (OTP-15998) </p> + </item> + <item> + <p> + The extension <url + href="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD">posix-rename@openssh.com</url> + is added to the <seemfa + marker="ssh:ssh_sftp#rename/3">ssh/sftp rename</seemfa> + operation.</p> + <p> + Own Id: OTP-16289 Aux Id: PR-2448 </p> + </item> + <item> + <p> + Calls of deprecated functions in the <seeguide + marker="crypto:new_api#the-old-api">Old Crypto + API</seeguide> are replaced by calls of their <seeguide + marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p> + <p> + Own Id: OTP-16346</p> + </item> + <item> + <p> + The default known_hosts file handling is improved to + include ports.</p> + <p> + The handling of the contents in that file is updated to + support the <url + href="https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT">full + syntax</url>, with exception of 1) the wildcard '?', 2) + wildcards in canonical names and 3) the option + '@cert-authority'</p> + <p> + Own Id: OTP-16506</p> + </item> + <item> + <p> + The MAC (Message Authorization Code) algorithms</p> + <list> <item>hmac-sha1-etm@openssh.com</item> + <item>hmac-sha2-256-etm@openssh.com</item> + <item>hmac-sha2-512-etm@openssh.com</item> </list> <p>are + implemented.</p> + <p> + Own Id: OTP-16508</p> + </item> + <item> + <p> + The key-exchange algorithms + <c>'diffie-hellman-group14-sha1'</c> and + <c>'diffie-hellman-group-exchange-sha1'</c> are disabled + per default. The reason is that SHA1 now is considered + insecure.</p> + <p> + They can be enabled if needed, see <seeapp + marker="ssh:SSH_app#algorithms">SSH (App)</seeapp>.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16509</p> + </item> + <item> + <p> + The public key algorithm <c>'ssh-dss'</c> is disabled per + default. The reason is that it is now considered as + insecure.</p> + <p> + It can be enabled if needed, see <seeapp + marker="ssh:SSH_app#algorithms">SSH (App)</seeapp>.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16510</p> + </item> + <item> + <p> + The public key <c>'ssh-rsa'</c> is now considered as + insecure because of its usage of SHA1.</p> + <p> + It is therefore deprecated and will no longer be enabled + per default in OTP-24.0.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16511</p> + </item> + <item> + <p> + An option <seetype + marker="ssh:ssh_file#optimize_key_lookup">optimize + (optimize_key_lookup)</seetype> is introduced for the + file interface ssh_file.erl</p> + <p> + The option enables the user to select between the default + handling which is fast but memory consuming vs memory + efficient but not as fast. The effect might be observable + only for large files.</p> + <p> + See the manual for <seemfa + marker="ssh:ssh_file#is_host_key/5">ssh_file:is_host_key/5</seemfa> + and <seemfa + marker="ssh:ssh_file#is_auth_key/3">ssh_file:is_auth_key/3</seemfa>.</p> + <p> + Own Id: OTP-16512</p> + </item> + <item> + <p> + The ssh agent is now implemented in the ssh_agent key + callback module. </p> + <p> + Enable with the the option <c> {key_cb, {ssh_agent, + []}}</c> in for example ssh:connect/3.</p> + <p> + See the <seeerl marker="ssh:ssh_agent">ssh_agent + manual</seeerl> for details.</p> + <p> + Own Id: OTP-16513</p> + </item> + <item> + <p> + Algorithm configuration could now be done in a .config + file.</p> + <p> + This is useful for example to enable an algorithm that is + disabled by default. It could now be enabled in an + .config-file without changing the code,</p> + <p> + See the SSH User's Guide chapter <seeguide + marker="ssh:configurations">"Configuration in + SSH"</seeguide>.</p> + <p> + Own Id: OTP-16540</p> + </item> + <item> + <p> + Documented which gen_tcp socket options can't be used in + calls to ssh:connect and ssh:daemon.</p> + <p> + Own Id: OTP-16589</p> + </item> + <item> + <p> + Added <seetype + marker="ssh:ssh#kb_int_fun_4">kb_int_fun_4()</seetype> to + the <seetype + marker="ssh:ssh#authentication_daemon_options">authentication_daemon_options()</seetype> + to enable generating dynamic keyboard-interactive prompts + from the user's state returned from the authentication + fun <seetype + marker="ssh:ssh#pwdfun_4">pwdfun_4()</seetype>.</p> + <p> + Own Id: OTP-16622 Aux Id: PR-2604 </p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.9.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Potential hazard between re-keying decision and socket + close.</p> + <p> + Own Id: OTP-16462 Aux Id: ERIERL-464 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.9</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -303,6 +544,22 @@ </section> +<section><title>Ssh 4.7.6.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Potential hazard between re-keying decision and socket + close.</p> + <p> + Own Id: OTP-16462 Aux Id: ERIERL-464 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.7.6.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index ca6a02117b..f7818b809a 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -179,6 +179,17 @@ <p>Options for <seemfa marker="#connect/3">clients</seemfa>. The individual options are further explained below or by following the hyperlinks. </p> + <p>Note that not every + <seetype marker="kernel:gen_tcp#connect_option">gen_tcp:connect_option()</seetype> + is accepted. See + <seemfa marker="ssh#set_sock_opts/2">set_sock_opts/2</seemfa> + for a list of prohibited options. + </p> + <p>Also note that setting a + <seetype marker="kernel:gen_tcp#connect_option">gen_tcp:connect_option()</seetype> + could change the socket in a way that impacts the ssh client's behaviour + negatively. You use it on your own risk. + </p> </desc> </datatype> @@ -341,6 +352,17 @@ <p>Options for <seemfa marker="#daemon/1">daemons</seemfa>. The individual options are further explained below or by following the hyperlinks. </p> + <p>Note that not every + <seetype marker="kernel:gen_tcp#listen_option">gen_tcp:listen_option()</seetype> + is accepted. See + <seemfa marker="ssh#set_sock_opts/2">set_sock_opts/2</seemfa> + for a list of prohibited options. + </p> + <p>Also note that setting a + <seetype marker="kernel:gen_tcp#listen_option">gen_tcp:listen_option()</seetype> + could change the socket in a way that impacts the ssh deamon's behaviour + negatively. You use it on your own risk. + </p> </desc> </datatype> @@ -514,6 +536,7 @@ <name name="prompt_texts"/> <name name="kb_int_tuple"/> <name name="kb_int_fun_3"/> + <name name="kb_int_fun_4"/> <name name="pwdfun_2"/> <name name="pwdfun_4"/> <desc> @@ -522,7 +545,7 @@ <item> <p>Sets the text strings that the daemon sends to the client for presentation to the user when using <c>keyboard-interactive</c> authentication.</p> - <p>If the fun/3 is used, it is called when the actual authentication occurs and may therefore + <p>If the fun/3 or fun/4 is used, it is called when the actual authentication occurs and may therefore return dynamic data like time, remote ip etc.</p> <p>The parameter <c>Echo</c> guides the client about need to hide the password.</p> <p>The default value is: @@ -874,7 +897,7 @@ <datatype> <name name="disconnectfun_common_option"/> <desc> - <p>Provides a fun to implement your own logging when the peer disconnects.</p> + <p>Provides a fun to implement your own logging or other handling at disconnects.</p> </desc> </datatype> @@ -1221,8 +1244,17 @@ <p>This function calls the <seemfa marker="kernel:inet#setopts/2">inet:setopts/2</seemfa>, read that documentation and for <seetype marker="kernel:gen_tcp#option">gen_tcp:option()</seetype>. - All gen_tcp socket options except <c>active</c>, <c>deliver</c>, <c>mode</c> and <c>packet</c> - are allowed. The excluded options are reserved by the SSH application. + </p> + <p> + All gen_tcp socket options except + </p> + <list> + <item><c>active</c></item> + <item><c>deliver</c></item> + <item><c>mode</c> and</item> + <item><c>packet</c></item> + </list> + <p>are allowed. The excluded options are reserved by the SSH application. </p> <warning> <p>This is an extremly dangerous function. You use it on your own risk.</p> diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index ffa2ec8c06..ad90a1a841 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -361,9 +361,11 @@ -type prompt_texts() :: kb_int_tuple() | kb_int_fun_3() + | kb_int_fun_4() . -type kb_int_fun_3() :: fun((Peer::ip_port(), User::string(), Service::string()) -> kb_int_tuple()). +-type kb_int_fun_4() :: fun((Peer::ip_port(), User::string(), Service::string(), State::any()) -> kb_int_tuple()). -type kb_int_tuple() :: {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}. -type pwdfun_2() :: fun((User::string(), Password::string()) -> boolean()) . diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 15e59dd1fe..960058fa38 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -217,6 +217,11 @@ ssh_dbg_format(connections, {call, {?MODULE,acceptor_init, [_Parent, Port, Address, _Opts, _AcceptTimeout]}}) -> [io_lib:format("Starting LISTENER on ~s:~p\n", [ntoa(Address),Port]) ]; +ssh_dbg_format(connections, {return_from, {?MODULE,acceptor_init,5}, _Ret}) -> + skip; + +ssh_dbg_format(connections, {call, {?MODULE,handle_connection,[_,_,_,_,_]}}) -> + skip; ssh_dbg_format(connections, {return_from, {?MODULE,handle_connection,5}, {error,Error}}) -> ["Starting connection to server failed:\n", io_lib:format("Error = ~p", [Error]) diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index a42f034f1b..19df20c9f1 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -32,10 +32,13 @@ -export([get_public_key/2, publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, - userauth_request_msg/1, handle_userauth_request/3, + userauth_request_msg/1, handle_userauth_request/3, ssh_msg_userauth_result/1, handle_userauth_info_request/2, handle_userauth_info_response/2 ]). +-behaviour(ssh_dbg). +-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/3]). + %%-------------------------------------------------------------------- %%% Internal application API %%-------------------------------------------------------------------- @@ -110,14 +113,13 @@ password_msg([#ssh{opts = Opts, not_ok -> {not_ok, Ssh}; _ -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "password", - data = - <<?BOOLEAN(?FALSE), - ?STRING(unicode:characters_to_binary(Password))>>}, - Ssh) + {#ssh_msg_userauth_request{user = User, + service = Service, + method = "password", + data = + <<?BOOLEAN(?FALSE), + ?STRING(unicode:characters_to_binary(Password))>>}, + Ssh} end. %% See RFC 4256 for info on keyboard-interactive @@ -128,13 +130,12 @@ keyboard_interactive_msg([#ssh{user = User, not_ok -> {not_ok,Ssh}; % No need to use a failed pwd once more _ -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "keyboard-interactive", - data = << ?STRING(<<"">>), - ?STRING(<<>>) >> }, - Ssh) + {#ssh_msg_userauth_request{user = User, + service = Service, + method = "keyboard-interactive", + data = << ?STRING(<<"">>), + ?STRING(<<>>) >> }, + Ssh} end. @@ -183,15 +184,14 @@ publickey_msg([SigAlg, #ssh{user = User, SigBlob = list_to_binary([?string(SigAlgStr), ?binary(Sig)]), - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "publickey", - data = [?TRUE, - ?string(SigAlgStr), - ?binary(PubKeyBlob), - ?binary(SigBlob)]}, - Ssh); + {#ssh_msg_userauth_request{user = User, + service = Service, + method = "publickey", + data = [?TRUE, + ?string(SigAlgStr), + ?binary(PubKeyBlob), + ?binary(SigBlob)]}, + Ssh}; _ -> {not_ok, Ssh} @@ -199,8 +199,8 @@ publickey_msg([SigAlg, #ssh{user = User, %%%---------------------------------------------------------------- service_request_msg(Ssh) -> - ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"}, - Ssh#ssh{service = "ssh-userauth"}). + {#ssh_msg_service_request{name = "ssh-userauth"}, + Ssh#ssh{service = "ssh-userauth"}}. %%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> @@ -210,24 +210,23 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> ?DISCONNECT(?SSH_DISCONNECT_ILLEGAL_USER_NAME, "Could not determine the users name"); User -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = "ssh-connection", - method = "none", - data = <<>>}, - Ssh#ssh{user = User, - userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys), - userauth_methods = none, - service = "ssh-connection"} - ) + {#ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "none", + data = <<>>}, + Ssh#ssh{user = User, + userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys), + userauth_methods = none, + service = "ssh-connection"} + } end. %%%---------------------------------------------------------------- %%% called by server handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"}, _, Ssh) -> - {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name}, - Ssh#ssh{service = "ssh-connection"})}; + {ok, {#ssh_msg_service_accept{name = Name}, + Ssh#ssh{service = "ssh-connection"}}}; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", @@ -239,12 +238,13 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, case check_password(User, Password, Opts, Ssh) of {true,Ssh1} -> {authorized, User, - ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)}; + {#ssh_msg_userauth_success{}, Ssh1} + }; {false,Ssh1} -> {not_authorized, {User, {error,"Bad user or password"}}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh1)} + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh1} + } end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -264,18 +264,18 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, %% or the old password was bad. {not_authorized, {User, {error,"Password change not supported"}}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh)}; + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh} + }; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", method = "none"}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, undefined}, - ssh_transport:ssh_packet( - #ssh_msg_userauth_failure{authentications = Methods, - partial_success = false}, Ssh)}; + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh} + }; handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", @@ -293,14 +293,14 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, case pre_verify_sig(User, KeyBlob, Opts) of true -> {not_authorized, {User, undefined}, - ssh_transport:ssh_packet( - #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), - key_blob = KeyBlob}, Ssh)}; + {#ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg), + key_blob = KeyBlob}, Ssh} + }; false -> {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh)} + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh} + } end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -318,13 +318,13 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, BAlg, KeyBlob, SigWLen, Ssh) of true -> {authorized, User, - ssh_transport:ssh_packet( - #ssh_msg_userauth_success{}, Ssh)}; + {#ssh_msg_userauth_success{}, Ssh} + }; false -> {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, Ssh)} + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh} + } end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -337,9 +337,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, case KbTriesLeft of N when N<1 -> {not_authorized, {User, {authmethod, "keyboard-interactive"}}, - ssh_transport:ssh_packet( - #ssh_msg_userauth_failure{authentications = Methods, - partial_success = false}, Ssh)}; + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh} + }; _ -> %% RFC4256 @@ -364,6 +364,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, Default; {_,_,_,_}=V -> V; + F when is_function(F, 4) -> + {_,PeerName} = Ssh#ssh.peer, + F(PeerName, User, "ssh-connection", Ssh#ssh.pwdfun_user_state); F when is_function(F) -> {_,PeerName} = Ssh#ssh.peer, F(PeerName, User, "ssh-connection") @@ -381,8 +384,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, >> }, {not_authorized, {User, undefined}, - ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User - })} + {Msg, Ssh#ssh{user = User}} + } end; handle_userauth_request(#ssh_msg_userauth_request{user = User, @@ -390,9 +393,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, method = Other}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, {authmethod, Other}}, - ssh_transport:ssh_packet( - #ssh_msg_userauth_failure{authentications = Methods, - partial_success = false}, Ssh)}. + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, Ssh} + }. %%%---------------------------------------------------------------- @@ -408,9 +411,9 @@ handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name, not_ok; Responses -> {ok, - ssh_transport:ssh_packet( - #ssh_msg_userauth_info_response{num_responses = NumPrompts, - data = Responses}, Ssh)} + {#ssh_msg_userauth_info_response{num_responses = NumPrompts, + data = Responses}, + Ssh}} end. %%%---------------------------------------------------------------- @@ -428,32 +431,30 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of {true,Ssh1} when SendOneEmpty==true -> - Msg = #ssh_msg_userauth_info_request{name = "", - instruction = "", - language_tag = "", - num_prompts = 0, - data = <<?BOOLEAN(?FALSE)>> - }, {authorized_but_one_more, User, - ssh_transport:ssh_packet(Msg, Ssh1)}; + {#ssh_msg_userauth_info_request{name = "", + instruction = "", + language_tag = "", + num_prompts = 0, + data = <<?BOOLEAN(?FALSE)>> + }, + Ssh1}}; {true,Ssh1} -> {authorized, User, - ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)}; + {#ssh_msg_userauth_success{}, Ssh1}}; {false,Ssh1} -> {not_authorized, {User, {error,"Bad user or password"}}, - ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ - authentications = Methods, - partial_success = false}, - Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)} - )} + {#ssh_msg_userauth_failure{authentications = Methods, + partial_success = false}, + Ssh1#ssh{kb_tries_left = max(KbTriesLeft-1, 0)}}} end; handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}}, #ssh{user = User} = Ssh) -> {authorized, User, - ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; + {#ssh_msg_userauth_success{}, Ssh}}; handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> @@ -619,3 +620,193 @@ write_if_nonempty(_, "") -> ok; write_if_nonempty(_, <<>>) -> ok; write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). +%%%---------------------------------------------------------------- +%%% Called just for the tracer ssh_dbg +ssh_msg_userauth_result(_R) -> ok. + +%%%################################################################ +%%%# +%%%# Tracing +%%%# + +ssh_dbg_trace_points() -> [authentication]. + +ssh_dbg_flags(authentication) -> [c]. + +ssh_dbg_on(authentication) -> dbg:tp(?MODULE, handle_userauth_request, 3, x), + dbg:tp(?MODULE, init_userauth_request_msg, 1, x), + dbg:tp(?MODULE, ssh_msg_userauth_result, 1, x), + dbg:tp(?MODULE, userauth_request_msg, 1, x). + +ssh_dbg_off(authentication) -> dbg:ctpg(?MODULE, handle_userauth_request, 3), + dbg:ctpg(?MODULE, init_userauth_request_msg, 1), + dbg:ctpg(?MODULE, ssh_msg_userauth_result, 1), + dbg:ctpg(?MODULE, userauth_request_msg, 1). + + + +%%% Server ---------------- +ssh_dbg_format(authentication, {call, {?MODULE,handle_userauth_request, [Req,_SessionID,Ssh]}}, + Stack) -> + {skip, [{Req,Ssh}|Stack]}; + + +ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3}, + {ok,{#ssh_msg_service_accept{name=Name},_Ssh}}}, + [{#ssh_msg_service_request{name=Name},_} | Stack]) -> + {skip, Stack}; + +ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3}, + {authorized,User,_Repl}}, + [{#ssh_msg_userauth_request{}=Req,Ssh}|Stack]) -> + {["AUTH srvr: Peer client authorized\n", + io_lib:format("user = ~p~n", [User]), + fmt_req(Req, Ssh)], + Stack}; + +ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3}, + {not_authorized,{User,_X},_Repl}}, + [{#ssh_msg_userauth_request{method="none"},Ssh}|Stack]) -> + Methods = Ssh#ssh.userauth_supported_methods, + {["AUTH srvr: Peer queries auth methods\n", + io_lib:format("user = ~p~nsupported methods = ~p ?", [User,Methods]) + ], + Stack}; + +ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3}, + {not_authorized,{User,_X}, Repl} + }, + [{#ssh_msg_userauth_request{method = "publickey", + data = <<?BYTE(?FALSE), _/binary>> + }=Req,Ssh}|Stack]) -> + {case Repl of + {#ssh_msg_userauth_pk_ok{}, _} -> + ["AUTH srvr: Answer - pub key supported\n"]; + {#ssh_msg_userauth_failure{}, _} -> + ["AUTH srvr: Answer - pub key not supported\n"]; + {Other, _} -> + ["AUTH srvr: Answer - strange answer\n", + io_lib:format("strange answer = ~p~n",[Other]) + ] + end + ++ [io_lib:format("user = ~p~n", [User]), + fmt_req(Req, Ssh)], + Stack}; + + +ssh_dbg_format(authentication, {call, {?MODULE,ssh_msg_userauth_result,[success]}}, + Stack) -> + {["AUTH client: Success"],Stack}; +ssh_dbg_format(authentication, {return_from, {?MODULE,ssh_msg_userauth_result,1}, _Result}, + Stack) -> + {skip, Stack}; + +ssh_dbg_format(authentication, {return_from, {?MODULE,handle_userauth_request,3}, + {not_authorized,{User,_X},_Repl}}, + [{#ssh_msg_userauth_request{}=Req,Ssh}|Stack]) -> + {["AUTH srvr: Peer client authorization failed\n", + io_lib:format("user = ~p~n", [User]), + fmt_req(Req, Ssh)], + Stack}; + +%%% Client ---------------- +ssh_dbg_format(authentication, {call, {?MODULE,init_userauth_request_msg, [#ssh{opts = Opts}]}}, + Stack) -> + {["AUTH client: Service ssh-userauth accepted\n", + case ?GET_OPT(user, Opts) of + undefined -> + io_lib:format("user = undefined *** ERROR ***", []); + User -> + io_lib:format("user = ~p", [User]) + end + ], + Stack}; +ssh_dbg_format(authentication, {return_from, {?MODULE,init_userauth_request_msg,1}, + {Repl = #ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "none"}, + _Ssh}}, + Stack) -> + {["AUTH client: Query for accepted methods\n", + io_lib:format("user = ~p", [User])], + [Repl|Stack]}; + +ssh_dbg_format(authentication, {call, {?MODULE,userauth_request_msg, + [#ssh{userauth_methods = Methods}]}}, + [ #ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = "none"} | Stack]) -> + {["AUTH client: Server supports\n", + io_lib:format("user = ~p~nmethods = ~p", [User,Methods])], + Stack}; + +ssh_dbg_format(authentication, {call, {?MODULE,userauth_request_msg,[_Ssh]}}, + Stack) -> + {skip,Stack}; + +ssh_dbg_format(authentication, {return_from, {?MODULE,userauth_request_msg,1}, + {send_disconnect, _Code, _Ssh}}, + Stack) -> + {skip,Stack}; +ssh_dbg_format(authentication, {return_from, {?MODULE,userauth_request_msg,1}, + {Method,{_Msg,_Ssh}}}, + Stack) -> + {["AUTH client: Try auth with\n", + io_lib:format("method = ~p", [Method])], + Stack}; + + + +ssh_dbg_format(authentication, Unhandled, Stack) -> + case Unhandled of + {call, {?MODULE,_F,_Args}} -> ok; + {return_from, {?MODULE,_F,_A}, _Resp} -> ok + end, + {["UNHANDLED AUTH FORMAT\n", + io_lib:format("Unhandled = ~p~nStack = ~p", [Unhandled,Stack])], + Stack}. + + +%%% Dbg helpers ---------------- + + +fmt_req(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", + method = Method, + data = Data}, + #ssh{kb_tries_left = KbTriesLeft, + userauth_supported_methods = Methods}) -> + [io_lib:format("req user = ~p~n" + "req method = ~p~n" + "supported methods = ~p", + [User,Method,Methods]), + case Method of + "none" -> ""; + "password" -> fmt_bool(Data); + "keyboard-interactive" -> fmt_kb_tries_left(KbTriesLeft); + "publickey" -> [case Data of + <<?BYTE(_), ?UINT32(ALen), Alg:ALen/binary, _/binary>> -> + io_lib:format("~nkey-type = ~p", [Alg]); + _ -> + "" + end]; + _ -> "" + end]. + + +fmt_kb_tries_left(N) when is_integer(N)-> + io_lib:format("~ntries left = ~p", [N-1]). + + +fmt_bool(<<?BYTE(Bool),_/binary>>) -> + io_lib:format("~nBool = ~s", + [case Bool of + ?TRUE -> "true"; + ?FALSE -> "false"; + _ -> io_lib:format("? (~p)",[Bool]) + end]); +fmt_bool(<<>>) -> + "". + + + diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index d6c8cf84ed..729ce67968 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -663,6 +663,9 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2). ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> ["Cli Terminating:\n", io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) - ]. + ]; +ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) -> + skip. + ?wr_record(state). diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index 6de897e1b2..5ffdf49e9b 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -419,12 +419,16 @@ ssh_dbg_format(channels, {return_from, {?MODULE,init,1}, {stop,Reason}}) -> ["Server Channel Start FAILED!\n", io_lib:format("Reason = ~p", [Reason]) ]; + ssh_dbg_format(channels, F) -> ssh_dbg_format(terminate, F); + ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> ["Server Channel Terminating:\n", io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) ]; +ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) -> + skip; ssh_dbg_format(channel_events, {call, {?MODULE,handle_call, [Call,From,State]}}) -> [hdr("is called", State), @@ -434,6 +438,7 @@ ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_call,3}, Ret}) -> ["Server Channel call returned:\n", io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) ]; + ssh_dbg_format(channel_events, {call, {?MODULE,handle_cast, [Cast,State]}}) -> [hdr("got cast", State), io_lib:format("Cast: ~p~n", [Cast]) @@ -442,6 +447,7 @@ ssh_dbg_format(channel_events, {return_from, {?MODULE,handle_cast,2}, Ret}) -> ["Server Channel cast returned:\n", io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret)]) ]; + ssh_dbg_format(channel_events, {call, {?MODULE,handle_info, [Info,State]}}) -> [hdr("got info", State), io_lib:format("Info: ~p~n", [Info]) diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 3ad7c182d5..5e5ed9b79a 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -63,7 +63,8 @@ adjust_window/3, close/2, disconnect/4, get_print_info/1, - set_sock_opts/2, get_sock_opts/2 + set_sock_opts/2, get_sock_opts/2, + prohibited_sock_option/1 ]). -type connection_ref() :: ssh:connection_ref(). @@ -365,11 +366,11 @@ retrieve(ConnectionHandler, Key) -> %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . set_sock_opts(ConnectionRef, SocketOptions) -> try lists:foldr(fun({Name,_Val}, Acc) -> - case lists:member(Name, [active, deliver, mode, packet]) of + case prohibited_sock_option(Name) of true -> [Name|Acc]; false -> Acc end - end, [], SocketOptions) + end, [], SocketOptions) of [] -> call(ConnectionRef, {set_sock_opts,SocketOptions}); @@ -380,6 +381,12 @@ set_sock_opts(ConnectionRef, SocketOptions) -> {error, badarg} end. +prohibited_sock_option(active) -> true; +prohibited_sock_option(deliver) -> true; +prohibited_sock_option(mode) -> true; +prohibited_sock_option(packet) -> true; +prohibited_sock_option(_) -> false. + %%-------------------------------------------------------------------- %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . get_sock_opts(ConnectionRef, SocketGetOptions) -> @@ -818,13 +825,13 @@ handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,cl %%% ######## {new_keys, client|server} #### %% First key exchange round: -handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) -> - {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D0) -> + {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params), %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1), - %% send_bytes(ExtInfo, D), + %% send_bytes(ExtInfo, D0), {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1), - send_bytes(MsgReq, D), - {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}}; + D = send_msg(MsgReq, D0#data{ssh_params = Ssh}), + {next_state, {ext_info,client,init}, D}; handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) -> {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), @@ -870,8 +877,8 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s "ssh-userauth" -> Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params, {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0), - send_bytes(Reply, D0), - {next_state, {userauth,server}, D0#data{ssh_params = Ssh}}; + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {next_state, {userauth,server}, D}; _ -> {Shutdown, D} = @@ -882,10 +889,12 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s end; handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client}, - #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) -> + #data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = D0) -> {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0), - send_bytes(Msg, State), - {next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}}; + D = send_msg(Msg, D0#data{ssh_params = Ssh, + auth_user = Ssh#ssh.user + }), + {next_state, {userauth,client}, D}; %%% ######## {userauth, client|server} #### @@ -901,8 +910,8 @@ handle_event(_, %% Probably the very first userauth_request but we deny unauthorized login {not_authorized, _, {Reply,Ssh}} = ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0), - send_bytes(Reply, D0), - {keep_state, D0#data{ssh_params = Ssh}}; + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {keep_state, D}; {"ssh-connection", "ssh-connection", Method} -> %% Userauth request with a method like "password" or so @@ -910,21 +919,24 @@ handle_event(_, true -> %% Yepp! we support this method case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of - {authorized, User, {Reply, Ssh}} -> - send_bytes(Reply, D0), - D0#data.starter ! ssh_connected, - connected_fun(User, Method, D0), + {authorized, User, {Reply, Ssh1}} -> + D = #data{ssh_params=Ssh} = + send_msg(Reply, D0#data{ssh_params = Ssh1}), + D#data.starter ! ssh_connected, + connected_fun(User, Method, D), {next_state, {connected,server}, - D0#data{auth_user = User, - ssh_params = Ssh#ssh{authenticated = true}}}; + D#data{auth_user=User, + %% Note: authenticated=true MUST NOT be sent + %% before send_msg! + ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" -> retry_fun(User, Reason, D0), - send_bytes(Reply, D0), - {next_state, {userauth_keyboard_interactive,server}, D0#data{ssh_params = Ssh}}; + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {next_state, {userauth_keyboard_interactive,server}, D}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> retry_fun(User, Reason, D0), - send_bytes(Reply, D0), - {keep_state, D0#data{ssh_params = Ssh}} + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {keep_state, D} end; false -> %% No we do not support this method (=/= none) @@ -952,6 +964,7 @@ handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) -> {keep_state, D}; handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) -> + ssh_auth:ssh_msg_userauth_result(success), D#data.starter ! ssh_connected, {next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}}; @@ -983,11 +996,11 @@ handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName= StateName, D0#data{ssh_params = Ssh}), {stop, Shutdown, D}; {"keyboard-interactive", {Msg, Ssh}} -> - send_bytes(Msg, D0), - {next_state, {userauth_keyboard_interactive,client}, D0#data{ssh_params = Ssh}}; + D = send_msg(Msg, D0#data{ssh_params = Ssh}), + {next_state, {userauth_keyboard_interactive,client}, D}; {_Method, {Msg, Ssh}} -> - send_bytes(Msg, D0), - {keep_state, D0#data{ssh_params = Ssh}} + D = send_msg(Msg, D0#data{ssh_params = Ssh}), + {keep_state, D} end; %%---- banner to client @@ -1002,39 +1015,46 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) - %%% ######## {userauth_keyboard_interactive, client|server} handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, - #data{ssh_params = Ssh0} = D) -> + #data{ssh_params = Ssh0} = D0) -> case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of {ok, {Reply, Ssh}} -> - send_bytes(Reply, D), - {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {next_state, {userauth_keyboard_interactive_info_response,client}, D}; not_ok -> - {next_state, {userauth,client}, D, [postpone]} + {next_state, {userauth,client}, D0, [postpone]} end; -handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) -> - case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of - {authorized, User, {Reply, Ssh}} -> - send_bytes(Reply, D), +handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) -> + case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of + {authorized, User, {Reply, Ssh1}} -> + D = #data{ssh_params=Ssh} = + send_msg(Reply, D0#data{ssh_params = Ssh1}), D#data.starter ! ssh_connected, connected_fun(User, "keyboard-interactive", D), {next_state, {connected,server}, D#data{auth_user = User, + %% Note: authenticated=true MUST NOT be sent + %% before send_msg! ssh_params = Ssh#ssh{authenticated = true}}}; {not_authorized, {User, Reason}, {Reply, Ssh}} -> - retry_fun(User, Reason, D), - send_bytes(Reply, D), - {next_state, {userauth,server}, D#data{ssh_params = Ssh}}; + retry_fun(User, Reason, D0), + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {next_state, {userauth,server}, D}; {authorized_but_one_more, _User, {Reply, Ssh}} -> - send_bytes(Reply, D), - {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}} + D = send_msg(Reply, D0#data{ssh_params = Ssh}), + {next_state, {userauth_keyboard_interactive_extra,server}, D} end; -handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) -> - {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params), - send_bytes(Reply, D), +handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D0) -> + {authorized, User, {Reply, Ssh1}} = + ssh_auth:handle_userauth_info_response({extra,Msg}, D0#data.ssh_params), + D = #data{ssh_params=Ssh} = + send_msg(Reply, D0#data{ssh_params = Ssh1}), D#data.starter ! ssh_connected, connected_fun(User, "keyboard-interactive", D), {next_state, {connected,server}, D#data{auth_user = User, + %% Note: authenticated=true MUST NOT be sent + %% before send_msg! ssh_params = Ssh#ssh{authenticated = true}}}; handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client}, @@ -1144,17 +1164,17 @@ handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User, end; -handle_event(enter, _OldState, {connected,_}=State, D) -> +handle_event(enter, OldState, {connected,_}=NewState, D) -> %% Entering the state where re-negotiation is possible - init_renegotiate_timers(State, D); + init_renegotiate_timers(OldState, NewState, D); -handle_event(enter, _OldState, {ext_info,_,renegotiate}=State, D) -> +handle_event(enter, OldState, {ext_info,_,renegotiate}=NewState, D) -> %% Could be hanging in exit_info state if nothing else arrives - init_renegotiate_timers(State, D); + init_renegotiate_timers(OldState, NewState, D); -handle_event(enter, {connected,_}, State, D) -> +handle_event(enter, {connected,_}=OldState, NewState, D) -> %% Exiting the state where re-negotiation is possible - pause_renegotiate_timers(State, D); + pause_renegotiate_timers(OldState, NewState, D); handle_event(cast, force_renegotiate, StateName, D) -> handle_event({timeout,renegotiate}, undefined, StateName, D); @@ -2112,25 +2132,32 @@ start_rekeying(Role, D0) -> {next_state, {kexinit,Role,renegotiate}, D}. -init_renegotiate_timers(State, D) -> +init_renegotiate_timers(_OldState, NewState, D) -> {RekeyTimeout,_MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), - {next_state, State, D, [{{timeout,renegotiate}, RekeyTimeout, none}, - {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}. + {next_state, NewState, D, [{{timeout,renegotiate}, RekeyTimeout, none}, + {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none} ]}. -pause_renegotiate_timers(State, D) -> - {next_state, State, D, [{{timeout,renegotiate}, infinity, none}, - {{timeout,check_data_size}, infinity, none} ]}. +pause_renegotiate_timers(_OldState, NewState, D) -> + {next_state, NewState, D, [{{timeout,renegotiate}, infinity, none}, + {{timeout,check_data_size}, infinity, none} ]}. check_data_rekeying(Role, D) -> - {ok, [{send_oct,SocketSentTotal}]} = inet:getstat(D#data.socket, [send_oct]), - SentSinceRekey = SocketSentTotal - D#data.last_size_rekey, - {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), - case check_data_rekeying_dbg(SentSinceRekey, MaxSent) of - true -> - start_rekeying(Role, D#data{last_size_rekey = SocketSentTotal}); - _ -> - %% Not enough data sent for a re-negotiation. Restart timer. + case inet:getstat(D#data.socket, [send_oct]) of + {ok, [{send_oct,SocketSentTotal}]} -> + SentSinceRekey = SocketSentTotal - D#data.last_size_rekey, + {_RekeyTimeout,MaxSent} = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), + case check_data_rekeying_dbg(SentSinceRekey, MaxSent) of + true -> + start_rekeying(Role, D#data{last_size_rekey = SocketSentTotal}); + _ -> + %% Not enough data sent for a re-negotiation. Restart timer. + {keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}} + end; + {error,_} -> + %% Socket closed, but before this module has handled that. Maybe + %% it is in the message queue. + %% Just go on like if there was not enough data transmitted to start re-keying: {keep_state, D, {{timeout,check_data_size}, ?REKEY_DATA_TIMOUT, none}} end. @@ -2489,20 +2516,22 @@ ssh_dbg_flags(disconnect) -> [c]. ssh_dbg_on(connections) -> dbg:tp(?MODULE, init_connection_handler, 3, x), ssh_dbg_on(terminate); ssh_dbg_on(connection_events) -> dbg:tp(?MODULE, handle_event, 4, x); -ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 2, x), - dbg:tpl(?MODULE, pause_renegotiate_timers, 2, x), +ssh_dbg_on(renegotiation) -> dbg:tpl(?MODULE, init_renegotiate_timers, 3, x), + dbg:tpl(?MODULE, pause_renegotiate_timers, 3, x), dbg:tpl(?MODULE, check_data_rekeying_dbg, 2, x), - dbg:tpl(?MODULE, start_rekeying, 2, x); + dbg:tpl(?MODULE, start_rekeying, 2, x), + dbg:tp(?MODULE, renegotiate, 1, x); ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 3, x); ssh_dbg_on(disconnect) -> dbg:tpl(?MODULE, send_disconnect, 7, x). ssh_dbg_off(disconnect) -> dbg:ctpl(?MODULE, send_disconnect, 7); ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 3); -ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 2), - dbg:ctpl(?MODULE, pause_renegotiate_timers, 2), +ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 3), + dbg:ctpl(?MODULE, pause_renegotiate_timers, 3), dbg:ctpl(?MODULE, check_data_rekeying_dbg, 2), - dbg:ctpl(?MODULE, start_rekeying, 2); + dbg:ctpl(?MODULE, start_rekeying, 2), + dbg:ctpg(?MODULE, renegotiate, 1); ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4); ssh_dbg_off(connections) -> dbg:ctpg(?MODULE, init_connection_handler, 3), ssh_dbg_off(terminate). @@ -2541,22 +2570,46 @@ ssh_dbg_format(connection_events, {return_from, {?MODULE,handle_event,4}, Ret}) io_lib:format("~p~n", [event_handler_result(Ret)]) ]; -ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[_State,D]}}) -> - ["Renegotiation init\n", - io_lib:format("rekey_limit: ~p ({ms,bytes})~ncheck_data_size: ~p (ms)~n", - [?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), +ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[OldState,NewState,D]}}) -> + ["Renegotiation: start timer (init_renegotiate_timers)\n", + io_lib:format("State: ~p --> ~p~n" + "rekey_limit: ~p ({ms,bytes})~n" + "check_data_size: ~p (ms)~n", + [OldState, NewState, + ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts), ?REKEY_DATA_TIMOUT]) ]; -ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[_State,_D]}}) -> - ["Renegotiation pause\n"]; +ssh_dbg_format(renegotiation, {return_from, {?MODULE,init_renegotiate_timers,3}, _Ret}) -> + skip; + +ssh_dbg_format(renegotiation, {call, {?MODULE,renegotiate,[ConnectionHandler]}}) -> + ["Renegotiation: renegotiation forced\n", + io_lib:format("~p:renegotiate(~p) called~n", + [?MODULE,ConnectionHandler]) + ]; +ssh_dbg_format(renegotiation, {return_from, {?MODULE,renegotiate,1}, _Ret}) -> + skip; + +ssh_dbg_format(renegotiation, {call, {?MODULE,pause_renegotiate_timers,[OldState,NewState,_D]}}) -> + ["Renegotiation: pause timers\n", + io_lib:format("State: ~p --> ~p~n", + [OldState, NewState]) + ]; +ssh_dbg_format(renegotiation, {return_from, {?MODULE,pause_renegotiate_timers,3}, _Ret}) -> + skip; + ssh_dbg_format(renegotiation, {call, {?MODULE,start_rekeying,[_Role,_D]}}) -> - ["Renegotiation start rekeying\n"]; + ["Renegotiation: start rekeying\n"]; +ssh_dbg_format(renegotiation, {return_from, {?MODULE,start_rekeying,2}, _Ret}) -> + skip; + ssh_dbg_format(renegotiation, {call, {?MODULE,check_data_rekeying_dbg,[SentSinceRekey, MaxSent]}}) -> - ["Renegotiation check data sent\n", + ["Renegotiation: check size of data sent\n", io_lib:format("TotalSentSinceRekey: ~p~nMaxBeforeRekey: ~p~nStartRekey: ~p~n", [SentSinceRekey, MaxSent, SentSinceRekey >= MaxSent]) ]; - +ssh_dbg_format(renegotiation, {return_from, {?MODULE,check_data_rekeying_dbg,2}, _Ret}) -> + skip; ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) -> @@ -2591,6 +2644,8 @@ ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, StateName, D]}}) - [Reason, StateName, ExtraInfo, state_data2proplist(D)]) ] end; +ssh_dbg_format(renegotiation, {return_from, {?MODULE,terminate,3}, _Ret}) -> + skip; ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect, [Code, Reason, DetailedText, Module, Line, StateName, _D]}}) -> @@ -2600,7 +2655,9 @@ ssh_dbg_format(disconnect, {call,{?MODULE,send_disconnect, " DetailedText =~n" " ~p", [Module, Line, StateName, Code, Reason, lists:flatten(DetailedText)]) - ]. + ]; +ssh_dbg_format(renegotiation, {return_from, {?MODULE,send_disconnect,7}, _Ret}) -> + skip. event_handler_result({next_state, NextState, _NewData}) -> diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 1809b14099..f8391224f8 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -54,6 +54,8 @@ start_tracer/0, start_tracer/1, on/1, on/0, off/1, off/0, + is_on/0, + is_off/0, go_on/0, %% Circular buffer cbuf_start/0, cbuf_start/1, @@ -70,6 +72,9 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). +%% Internal apply_after: +-export([ets_delete/2]). + -include("ssh.hrl"). -include("ssh_transport.hrl"). -include("ssh_connect.hrl"). @@ -82,12 +87,16 @@ -type trace_point() :: atom(). -type trace_points() :: [trace_point()]. +-type stack() :: list(term()). -callback ssh_dbg_trace_points() -> trace_points(). -callback ssh_dbg_flags(trace_point()) -> [atom()]. -callback ssh_dbg_on(trace_point() | trace_points()) -> term(). -callback ssh_dbg_off(trace_point() | trace_points()) -> term(). --callback ssh_dbg_format(trace_point(), term()) -> iolist(). +-callback ssh_dbg_format(trace_point(), term()) -> iolist() | skip. +-callback ssh_dbg_format(trace_point(), term(), stack()) -> {iolist() | skip, stack()}. + +-optional_callbacks([ssh_dbg_format/2, ssh_dbg_format/3]). % At least one of them are to be used %%%================================================================ @@ -134,10 +143,13 @@ start_tracer(WriteFun, InitAcc) when is_function(WriteFun, 3) -> %%%---------------------------------------------------------------- on() -> on(?ALL_DBG_TYPES). on(Type) -> switch(on, Type). - +is_on() -> gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT). + off() -> off(?ALL_DBG_TYPES). % A bit overkill... off(Type) -> switch(off, Type). +is_off() -> ?ALL_DBG_TYPES -- is_on(). + go_on() -> IsOn = gen_server:call(?SERVER, get_on, ?CALL_TIMEOUT), @@ -174,8 +186,42 @@ reduce_state(T) -> %%%---------------------------------------------------------------- init(_) -> + new_table(), {ok, #data{}}. + +new_table() -> + try + ets:new(?MODULE, [public, named_table]), + ok + catch + exit:badarg -> + ok + end. + + +get_proc_stack(Pid) when is_pid(Pid) -> + try ets:lookup_element(?MODULE, Pid, 2) + catch + error:badarg -> + %% Non-existing item + new_proc(Pid), + ets:insert(?MODULE, {Pid,[]}), + [] + end. + + +put_proc_stack(Pid, Data) when is_pid(Pid), + is_list(Data) -> + ets:insert(?MODULE, {Pid,Data}). + + +new_proc(Pid) when is_pid(Pid) -> + gen_server:cast(?SERVER, {new_proc,Pid}). + +ets_delete(Tab, Key) -> + catch ets:delete(Tab, Key). + %%%---------------------------------------------------------------- handle_call({switch,on,Types}, _From, D) -> NowOn = lists:usort(Types ++ D#data.types_on), @@ -196,10 +242,20 @@ handle_call(C, _From, D) -> {reply, {error,{unknown_call,C}}, D}. +handle_cast({new_proc,Pid}, D) -> + monitor(process, Pid), + {noreply, D}; + handle_cast(C, D) -> io:format('*** Unknown cast: ~p~n',[C]), {noreply, D}. + +handle_info({'DOWN', _MonitorRef, process, Pid, _Info}, D) -> + %% Universal real-time synchronization (there might be dbg msgs in the queue to the tracer): + timer:apply_after(20000, ?MODULE, ets_delete, [?MODULE, Pid]), + {noreply, D}; + handle_info(C, D) -> io:format('*** Unknown info: ~p~n',[C]), {noreply, D}. @@ -320,20 +376,60 @@ try_all_types_in_all_modules(TypesOn, Arg, WriteFun, Acc0) -> TS = trace_ts(Arg), PID = trace_pid(Arg), INFO = trace_info(Arg), - lists:foldl( - fun(Type, Acc1) -> - lists:foldl( - fun(SshMod,Acc) -> - try WriteFun("~n~s ~p ~s~n", - [lists:flatten(TS), - PID, - lists:flatten(SshMod:ssh_dbg_format(Type, INFO))], - Acc) - catch - _:_ -> Acc - end - end, Acc1, SshModules) - end, Acc0, TypesOn). + Acc = + lists:foldl( + fun(Type, Acc1) -> + lists:foldl( + fun(SshMod,Acc) -> + try + %% First, call without stack + SshMod:ssh_dbg_format(Type, INFO) + of + skip -> + %% Don't try to print this later + written; + Txt when is_list(Txt) -> + write_txt(WriteFun, TS, PID, Txt) + catch + error:E when E==undef ; E==function_clause ; element(1,E)==case_clause -> + try + %% then, call with stack + STACK = get_proc_stack(PID), + SshMod:ssh_dbg_format(Type, INFO, STACK) + of + {skip, NewStack} -> + %% Don't try to print this later + put_proc_stack(PID, NewStack), + written; + {Txt, NewStack} when is_list(Txt) -> + put_proc_stack(PID, NewStack), + write_txt(WriteFun, TS, PID, Txt) + catch + _:_ -> + %% and finally, signal for special formatting + %% if noone else formats it + Acc + end + end + end, Acc1, SshModules) + end, Acc0, TypesOn), + case Acc of + Acc0 -> + %% INFO :: any() + WriteFun("~n~s ~p DEBUG~n~p~n", [lists:flatten(TS),PID,INFO], Acc0); + written -> + Acc0 + end. + + + +write_txt(WriteFun, TS, PID, Txt) when is_list(Txt) -> + WriteFun("~n~s ~p ~s~n", + [lists:flatten(TS), + PID, + lists:flatten(Txt)], + written % this is returned + ). %%%---------------------------------------------------------------- wr_record(T, Fs, BL) when is_tuple(T) -> diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 30b0c89144..804775bd75 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -762,15 +762,26 @@ ssh_dbg_format(ssh_messages, {call,{?MODULE,encode,[Msg]}}) -> ["Going to send ",Name,":\n", wr_record(ssh_dbg:shrink_bin(Msg)) ]; +ssh_dbg_format(ssh_messages, {return_from, {?MODULE,encode,1}, _Ret}) -> + skip; + +ssh_dbg_format(ssh_messages, {call, {?MODULE,decode,[_]}}) -> + skip; ssh_dbg_format(ssh_messages, {return_from,{?MODULE,decode,1},Msg}) -> Name = string:to_upper(atom_to_list(element(1,Msg))), ["Received ",Name,":\n", wr_record(ssh_dbg:shrink_bin(Msg)) ]; + ssh_dbg_format(raw_messages, {call,{?MODULE,decode,[BytesPT]}}) -> ["Received plain text bytes (shown after decryption):\n", io_lib:format("~p",[BytesPT]) ]; +ssh_dbg_format(raw_messages, {return_from, {?MODULE,decode,1}, _Ret}) -> + skip; + +ssh_dbg_format(raw_messages, {call, {?MODULE,encode,[_]}}) -> + skip; ssh_dbg_format(raw_messages, {return_from,{?MODULE,encode,1},BytesPT}) -> ["Going to send plain text bytes (shown before encryption):\n", io_lib:format("~p",[BytesPT]) diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index cc9ef565d8..306eb86c23 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -261,22 +261,20 @@ config_val(Key, RoleCnfs, Opts) -> check_fun(Key, Defs) -> - #{chk := Fun} = maps:get(Key, Defs), - Fun. + case ssh_connection_handler:prohibited_sock_option(Key) of + false -> + #{chk := Fun} = maps:get(Key, Defs), + Fun; + true -> + fun(_,_) -> forbidden end + end. %%%================================================================ %%% %%% Check and save one option %%% - -%%% First some prohibited inet options: -save({K,V}, _, _) when K == reuseaddr ; - K == active - -> - forbidden_option(K, V); - -%%% then compatibility conversions: +%%% First compatibility conversions: save({allow_user_interaction,V}, Opts, Vals) -> save({user_interaction,V}, Opts, Vals); @@ -297,7 +295,12 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> {true, ModifiedValue} -> OptMap#{Key := ModifiedValue}; false -> - error({eoptions, {Key,Value}, "Bad value"}) + error({eoptions, {Key,Value}, "Bad value"}); + forbidden -> + error({eoptions, {Key,Value}, + io_lib:format("The option '~s' is used internally. The " + "user is not allowed to specify this option.", + [Key])}) catch %% An unknown Key (= not in the definition map) is %% regarded as an inet option: @@ -424,7 +427,8 @@ default(server) -> check_string(S3) andalso is_boolean(B); (F) -> - check_function3(F) + check_function3(F) orelse + check_function4(F) end, class => user_option }, @@ -1180,10 +1184,3 @@ error_if_empty([]) -> ok. %%%---------------------------------------------------------------- -forbidden_option(K,V) -> - Txt = io_lib:format("The option '~s' is used internally. The " - "user is not allowed to specify this option.", - [K]), - error({eoptions, {K,V}, Txt}). - -%%%---------------------------------------------------------------- diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 9ee6ed68c1..69fff09979 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -1843,7 +1843,9 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2). ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> ["Sftp Terminating:\n", io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) - ]. + ]; +ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) -> + skip. ?wr_record(state). diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 78277705d8..158ef3d5ae 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -964,6 +964,8 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2). ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> ["SftpD Terminating:\n", io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) - ]. + ]; +ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) -> + skip. ?wr_record(state). diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl index b5862b2395..be8a6aa8cc 100644 --- a/lib/ssh/src/ssh_shell.erl +++ b/lib/ssh/src/ssh_shell.erl @@ -200,6 +200,8 @@ ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 2). ssh_dbg_format(terminate, {call, {?MODULE,terminate, [Reason, State]}}) -> ["Shell Terminating:\n", io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)]) - ]. + ]; +ssh_dbg_format(terminate, {return_from, {?MODULE,terminate,2}, _Ret}) -> + skip. ?wr_record(state). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 86d4ef951c..bd901c99e0 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -2172,18 +2172,27 @@ ssh_dbg_off(ssh_messages) -> ssh_dbg_off(hello). +ssh_dbg_format(hello, {call,{?MODULE,hello_version_msg,[_]}}) -> + skip; ssh_dbg_format(hello, {return_from,{?MODULE,hello_version_msg,1},Hello}) -> ["Going to send hello message:\n", Hello ]; + ssh_dbg_format(hello, {call,{?MODULE,handle_hello_version,[Hello]}}) -> ["Received hello message:\n", Hello ]; +ssh_dbg_format(hello, {return_from,{?MODULE,handle_hello_version,1},_Ret}) -> + skip; + +ssh_dbg_format(alg, {call,{?MODULE,select_algorithm,[_,_,_,_]}}) -> + skip; ssh_dbg_format(alg, {return_from,{?MODULE,select_algorithm,4},{ok,Alg}}) -> ["Negotiated algorithms:\n", wr_record(Alg) ]; + ssh_dbg_format(raw_messages, X) -> ssh_dbg_format(hello, X); ssh_dbg_format(ssh_messages, X) -> ssh_dbg_format(hello, X). diff --git a/lib/ssh/test/.gitignore b/lib/ssh/test/.gitignore index c9d5f086b3..f0adbaf33f 100644 --- a/lib/ssh/test/.gitignore +++ b/lib/ssh/test/.gitignore @@ -1,4 +1,6 @@ +*COVER.html +ssh_sftp_SUITE_data/test_data* property_test/ssh_eqc_client_server_dirs/system property_test/ssh_eqc_client_server_dirs/user diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile index aafec6566e..9862071b5f 100644 --- a/lib/ssh/test/Makefile +++ b/lib/ssh/test/Makefile @@ -46,6 +46,7 @@ MODULES= \ ssh_protocol_SUITE \ ssh_property_test_SUITE \ ssh_pubkey_SUITE \ + ssh_renegotiate_SUITE \ ssh_sftp_SUITE \ ssh_sftpd_SUITE \ ssh_sftpd_erlclient_SUITE \ diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server.erl b/lib/ssh/test/property_test/ssh_eqc_client_server.erl index e0b739ab53..66a79c8a17 100644 --- a/lib/ssh/test/property_test/ssh_eqc_client_server.erl +++ b/lib/ssh/test/property_test/ssh_eqc_client_server.erl @@ -480,12 +480,9 @@ setup_rsa(Dir) -> erase_dir(user_dir(Dir)), file:make_dir(system_dir(Dir)), file:make_dir(user_dir(Dir)), - - file:copy(data_dir(Dir,"id_rsa"), user_dir(Dir,"id_rsa")), - file:copy(data_dir(Dir,"ssh_host_rsa_key"), system_dir(Dir,"ssh_host_rsa_key")), - file:copy(data_dir(Dir,"ssh_host_rsa_key"), system_dir(Dir,"ssh_host_rsa_key.pub")), - ssh_test_lib:setup_rsa_known_host(data_dir(Dir), user_dir(Dir)), - ssh_test_lib:setup_rsa_auth_keys(data_dir(Dir), user_dir(Dir)). + ct:log("Dir = ~p~ndata_dir = ~p~nsystem_dir = ~p~nuser = ~p~n", + [Dir,data_dir(Dir),system_dir(Dir),user_dir(Dir)]), + ssh_test_lib:setup_all_user_host_keys( data_dir(Dir), user_dir(Dir), system_dir(Dir)). data_dir(Dir, File) -> filename:join(Dir, File). system_dir(Dir, File) -> filename:join([Dir, "system", File]). diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa index d306f8b26e..24628e071b 100644 --- a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa @@ -1,13 +1,12 @@ -----BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ -APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod -/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP -kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW -JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD -OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt -+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e -uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX -Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE -ZU8w8Q+H7z0j+a+70x2iAw== +MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3 +uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D +txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd +CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs +picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr +c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI +hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4 +cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C +ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC +sQCY/I35vb6GUJn9tEdP -----END DSA PRIVATE KEY----- - diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub new file mode 100644 index 0000000000..018ef6f537 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256 new file mode 100644 index 0000000000..4b1eb12eaa --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49 +AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br +N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub new file mode 100644 index 0000000000..a0147e60fa --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384 new file mode 100644 index 0000000000..4e8aa40959 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S +PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd +ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF +8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub new file mode 100644 index 0000000000..41e722e545 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521 new file mode 100644 index 0000000000..7196f46e97 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q +9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5 +01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7 +XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf +pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub new file mode 100644 index 0000000000..8f059120bc --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ecdsa521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519 new file mode 100644 index 0000000000..401a3e4a9a --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us +aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw +AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L +ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub new file mode 100644 index 0000000000..a5c03b19c1 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448 new file mode 100644 index 0000000000..8ecfd710dc --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448 @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh +1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf +fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32 +BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ +m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub new file mode 100644 index 0000000000..cec0765a5d --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_ed448.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa index 9d7e0dd5fb..2202c2ead8 100644 --- a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU -DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl -zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB -AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V -TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3 -CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK -SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p -z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd -WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39 -sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3 -xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ -dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x -ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak= +MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w +W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7 +LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ +cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA +ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b +IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR +OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn +3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w +W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5 +SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1 +A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI +wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx +5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx +f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln +4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8 +gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di +d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp +jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY +SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg +xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi +b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF +xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6 +Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur +DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE +oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg== -----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub new file mode 100644 index 0000000000..b4084d320a --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256 new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384 new file mode 100644 index 0000000000..fb1a862ded --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw +mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3 +CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7 +Hneb/99fIYopdMH5NMnk60zGO1uZ2vc= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub new file mode 100644 index 0000000000..428d5fb7d7 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521 b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521 new file mode 100644 index 0000000000..3e51ec2ecd --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx +U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq +Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/ +vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5 +ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub new file mode 100644 index 0000000000..017a29f4da --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ecdsa_key521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key new file mode 100644 index 0000000000..13a8fcf491 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD +uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ +AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W +TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..156ef4045c --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key new file mode 100644 index 0000000000..31a7e4e8c3 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_server_dirs/ssh_host_ed448_key.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_agent_SUITE.erl b/lib/ssh/test/ssh_agent_SUITE.erl index 7efd1c6d88..836a61d389 100644 --- a/lib/ssh/test/ssh_agent_SUITE.erl +++ b/lib/ssh/test/ssh_agent_SUITE.erl @@ -48,6 +48,22 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok = ssh:stop(). +init_per_testcase(connect_with_ssh_agent, Config) -> + DataDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + AgentUserDir = filename:join(UserDir,"agent"), % just to separate them in the tests + % so we know that the right dir is used + file:make_dir(AgentUserDir), + %% Arrange the host keys in <priv_dir>/system + ct:log("Host keys setup for: ~p", + [ssh_test_lib:setup_all_host_keys(Config)]), + %% Copy the user's files used by the daemon + {ok,_} = file:copy(filename:join(DataDir,"authorized_keys"), + filename:join(UserDir,"authorized_keys")), + %% And copy the user's files used by the agent (and not by the user) + {ok,_} = file:copy(filename:join(DataDir,"id_rsa"), + filename:join(AgentUserDir,"id_rsa")), + Config; init_per_testcase(_TestCase, Config) -> Config. @@ -119,16 +135,18 @@ connect_with_ssh_agent() -> [{doc, "Connect with RSA key from SSH agent"}]. connect_with_ssh_agent(Config) -> - DataDir = proplists:get_value(data_dir, Config), - {ok, SocketPath} = ssh_agent_mock_server:start_link('rsa-sha2-256', DataDir), - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, DataDir}, - {user_dir, DataDir}]), - ConnectionRef = ssh_test_lib:connect(Host, Port, [{user_dir, DataDir}, - {silently_accept_hosts, true}, - {user_interaction, false}, - {auth_methods, "publickey"}, - {key_cb, {ssh_agent, [{socket_path, SocketPath}]}} - ]), + UserDir = PrivDir = proplists:get_value(priv_dir, Config), + AgentUserDir = filename:join(UserDir,"agent"), + SystemDir = filename:join(PrivDir, "system"), + {ok, SocketPath} = ssh_agent_mock_server:start_link('rsa-sha2-256', AgentUserDir), + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}]), + ConnectionRef = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir}, + {silently_accept_hosts, true}, + {user_interaction, false}, + {auth_methods, "publickey"}, + {key_cb, {ssh_agent, [{socket_path, SocketPath}]}} + ]), ssh:close(ConnectionRef), ssh:stop_daemon(Pid), ssh_agent_mock_server:stop(SocketPath). diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..51ab6fbd88 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,13 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK +wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q +diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA +l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X +skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF +Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP +ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah +/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U +ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W +Lv62jKcdskxNyz2NQoBx +-----END DSA PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..4dbb1305b0 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1,11 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j +YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2 +KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU +aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI +fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT +MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh +DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48 +wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2 +/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg== +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256 new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384 new file mode 100644 index 0000000000..fb1a862ded --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw +mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3 +CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7 +Hneb/99fIYopdMH5NMnk60zGO1uZ2vc= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub new file mode 100644 index 0000000000..428d5fb7d7 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521 new file mode 100644 index 0000000000..3e51ec2ecd --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx +U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq +Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/ +vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5 +ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub new file mode 100644 index 0000000000..017a29f4da --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ecdsa_key521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key new file mode 100644 index 0000000000..13a8fcf491 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD +uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ +AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W +TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..156ef4045c --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key new file mode 100644 index 0000000000..31a7e4e8c3 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_ed448_key.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub new file mode 100644 index 0000000000..75d2025c71 --- /dev/null +++ b/lib/ssh/test/ssh_agent_SUITE_data/ssh_host_rsa_key.pub @@ -0,0 +1,5 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8 +semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW +RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q== +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl index 06996d7e48..4832c0ad3b 100644 --- a/lib/ssh/test/ssh_algorithms_SUITE.erl +++ b/lib/ssh/test/ssh_algorithms_SUITE.erl @@ -35,7 +35,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}, - {timetrap,{seconds,60}}]. + {timetrap,{seconds,120}}]. all() -> %% [{group,kex},{group,cipher}... etc @@ -48,9 +48,9 @@ groups() -> SshdAlgos = extract_algos(ssh_test_lib:default_algorithms(sshd)), DoubleAlgos = - [{Tag, double(Algs)} || {Tag,Algs} <- ErlAlgos, - length(Algs) > 1, - lists:member(Tag, two_way_tags())], + [{Tag, double(Tag,Algs)} || {Tag,Algs} <- ErlAlgos, + length(Algs) > 1, + lists:member(Tag, two_way_tags())], TagGroupSet = [{Tag, [], group_members_for_tag(Tag,Algs,DoubleAlgos)} || {Tag,Algs} <- ErlAlgos, @@ -60,14 +60,14 @@ groups() -> TypeSSH = ssh_test_lib:ssh_type(), AlgoTcSet = - [{Alg, [parallel], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)} + [{Alg, [], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)} || {Tag,Algs} <- ErlAlgos ++ DoubleAlgos, Alg <- Algs], TagGroupSet ++ AlgoTcSet. tags() -> [kex,cipher,mac,compression,public_key]. -two_way_tags() -> [cipher,mac,compression]. +two_way_tags() -> [cipher,mac,compression, public_key]. %%-------------------------------------------------------------------- init_per_suite(Config) -> @@ -91,7 +91,10 @@ init_per_suite(Config) -> ssh_test_lib:installed_ssh_version("TIMEOUT"), ssh:default_algorithms(), crypto:info_lib(), - ssh_test_lib:default_algorithms(sshc), + ssh_test_lib:default_algorithms(sshc, + %% Use a fake system_dir to enable the test + %% daemon to start: + [{system_dir,proplists:get_value(data_dir,Config)}]), ssh_test_lib:default_algorithms(sshd), {?DEFAULT_DH_GROUP_MIN,?DEFAULT_DH_GROUP_NBITS,?DEFAULT_DH_GROUP_MAX}, public_key:dh_gex_group_sizes(), @@ -126,22 +129,31 @@ init_per_group(Group, Config) -> init_per_group(public_key=Tag, Alg, Config) -> + PA = + case split(Tag, Alg) of + [_] -> + [Alg]; + [A1,A2] -> + [A1,A2] + end, OtherAlgs = [{T,L} || {T,L} <- ssh_transport:supported_algorithms(), T=/=Tag], - ct:log("Init tests for public_key ~p~nOtherAlgs=~p",[Alg,OtherAlgs]), - PrefAlgs = {preferred_algorithms,[{Tag,[Alg]}|OtherAlgs]}, + ct:log("Init tests for public_key ~p~nOtherAlgs=~p",[PA,OtherAlgs]), + PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]}, %% Daemon started later in init_per_testcase try - setup_pubkey(Alg, + setup_pubkey(PA, [{pref_algs,PrefAlgs}, - {tag_alg,{Tag,Alg}} + {tag_alg,{Tag,PA}} | Config]) catch - _:_ -> {skip, io_lib:format("Unsupported: ~p",[Alg])} + _C:_E:_S -> + ct:log("Exception ~p:~p~n~p",[_C,_E,_S]), + {skip, io_lib:format("Unsupported: ~p",[Alg])} end; init_per_group(Tag, Alg, Config) -> PA = - case split(Alg) of + case split(Tag, Alg) of [_] -> [Alg]; [A1,A2] -> @@ -153,7 +165,7 @@ init_per_group(Tag, Alg, Config) -> PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]}, start_std_daemon([PrefAlgs], [{pref_algs,PrefAlgs}, - {tag_alg,{Tag,Alg}} + {tag_alg,{Tag,[Alg]}} | Config]). @@ -173,6 +185,7 @@ init_per_testcase(TC, Config) -> init_per_testcase(TC, {public_key,Alg}, Config) -> + ct:log("init_per_testcase TC=~p, Alg=~p",[TC,Alg]), ExtraOpts = case TC of simple_connect -> [{user_dir, proplists:get_value(priv_dir,Config)}]; @@ -180,21 +193,26 @@ init_per_testcase(TC, {public_key,Alg}, Config) -> [] end, Opts = pubkey_opts(Config) ++ ExtraOpts, - case {ssh_file:user_key(Alg,Opts), ssh_file:host_key(Alg,Opts)} of + {UserAlg,SrvrAlg} = + case Alg of + [A1,A2] -> {A1,A2}; + [A0] -> {A0,A0} + end, + case {ssh_file:user_key(UserAlg,Opts), ssh_file:host_key(SrvrAlg,Opts)} of {{ok,_}, {ok,_}} -> start_pubkey_daemon([proplists:get_value(pref_algs,Config) | ExtraOpts], [{extra_daemon,true}|Config]); {{ok,_}, {error,Err}} -> - ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]), + ct:log("SrvrAlg = ~p~nOpts = ~p",[SrvrAlg,Opts]), {skip, io_lib:format("No host key: ~p",[Err])}; {{error,Err}, {ok,_}} -> - ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]), + ct:log("UserAlg = ~p~nOpts = ~p",[UserAlg,Opts]), {skip, io_lib:format("No user key: ~p",[Err])}; _ -> - ct:log("Alg = ~p~nOpts = ~p",[Alg,Opts]), + ct:log("UserAlg = ~p SrvrAlg = ~p~nOpts = ~p",[UserAlg,SrvrAlg,Opts]), {skip, "Neither host nor user key"} end; @@ -244,17 +262,22 @@ simple_exec(Config) -> %%-------------------------------------------------------------------- %% A simple exec call simple_connect(Config) -> + ct:log("PrivDir ~p:~n~p~n~nPrivDir/system: ~p",[proplists:get_value(priv_dir,Config), + file:list_dir(proplists:get_value(priv_dir,Config)), + catch file:list_dir( + filename:join(proplists:get_value(priv_dir,Config), + system))]), {Host,Port} = proplists:get_value(srvr_addr, Config), {preferred_algorithms,AlgEntries} = proplists:get_value(pref_algs, Config), Opts = case proplists:get_value(tag_alg, Config) of - {public_key,Alg} -> [{pref_public_key_algs,[Alg]}, + {public_key,Alg} -> [{pref_public_key_algs,Alg}, {preferred_algorithms,AlgEntries}]; _ -> [{modify_algorithms,[{append,AlgEntries}]}] end, ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{silently_accept_hosts, true}, - {user_interaction, false} | + {user_interaction, false} | Opts]), ct:log("~p:~p connected! ~p",[?MODULE,?LINE,ConnectionRef]), ssh:close(ConnectionRef). @@ -280,7 +303,7 @@ try_exec_simple_group(Group, Config) -> %% Testing all default groups simple_exec_groups() -> - [{timetrap,{seconds,180}}]. + [{timetrap,{seconds,240}}]. simple_exec_groups(Config) -> Sizes = interpolate( public_key:dh_gex_group_sizes() ), @@ -317,7 +340,10 @@ sshc_simple_exec_os_cmd(Config) -> Result = ssh_test_lib:open_sshc(Host, Port, [" -C" " -o UserKnownHostsFile=",KnownHosts, + " -o CheckHostIP=no" " -o StrictHostKeyChecking=no" + " -q" + " -x" ], " 1+1."), Parent ! {result, self(), Result, "2"} @@ -342,7 +368,7 @@ sshc_simple_exec_os_cmd(Config) -> sshd_simple_exec(Config) -> ClientPubKeyOpts = case proplists:get_value(tag_alg,Config) of - {public_key,Alg} -> [{pref_public_key_algs,[Alg]}]; + {public_key,Alg} -> [{pref_public_key_algs,Alg}]; _ -> [] end, ConnectionRef = ssh_test_lib:connect(22, [{silently_accept_hosts, true}, @@ -395,13 +421,20 @@ sshd_simple_exec(Config) -> group_members_for_tag(Tag, Algos, DoubleAlgos) -> [{group,Alg} || Alg <- Algos++proplists:get_value(Tag,DoubleAlgos,[])]. -double(Algs) -> [concat(A1,A2) || A1 <- Algs, - A2 <- Algs, - A1 =/= A2]. +double(Tag, Algs) -> [concat(Tag,A1,A2) || A1 <- Algs, + A2 <- Algs, + A1 =/= A2]. -concat(A1, A2) -> list_to_atom(lists:concat([A1," + ",A2])). +concat(Tag, A1, A2) -> + list_to_atom(lists:concat(["D: ",Tag," ",A1," + ",A2])). -split(Alg) -> ssh_test_lib:to_atoms(string:tokens(atom_to_list(Alg), " + ")). +split(TagA, Alg) -> + Tag = atom_to_list(TagA), + ssh_test_lib:to_atoms( + case string:tokens(atom_to_list(Alg), " ") of + ["D:",Tag,A1,"+",A2] ->[A1,A2]; + Other -> Other + end). specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos, TypeSSH) -> case Tag of @@ -435,7 +468,7 @@ supports(Tag, Alg, Algos) -> lists:all(fun(A) -> lists:member(A, proplists:get_value(Tag, Algos,[])) end, - split(Alg)). + split(Tag, Alg)). extract_algos(Spec) -> @@ -475,21 +508,33 @@ pubkey_opts(Config) -> {system_dir, SystemDir}]. -setup_pubkey(Alg, Config) -> +setup_pubkey([AlgClient, AlgServer], Config) -> DataDir = proplists:get_value(data_dir, Config), UserDir = proplists:get_value(priv_dir, Config), + ssh_test_lib:del_dir_contents(UserDir), + ok = ssh_test_lib:setup_user_key(AlgClient, DataDir, UserDir), + _SysDir = ssh_test_lib:setup_host_key_create_dir(AlgServer, DataDir, UserDir), +try ct:log("~p:~p AlgClient=~p, AlgServer=~p~nPrivDir ~p:~n~p~n~nSYsDir=~p~nPrivDir/system: ~p", + [?MODULE,?LINE, + AlgClient, AlgServer, + proplists:get_value(priv_dir,Config), + file:list_dir(proplists:get_value(priv_dir,Config)), + _SysDir, + catch file:list_dir( + filename:join(proplists:get_value(priv_dir,Config), + system)) + ]) +catch _C:_E:_S -> + ct:log("~p:~p ~p:~p~n~p",[?MODULE,?LINE,_C,_E,_S]) +end, + Config; + +setup_pubkey([Alg], Config) -> + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), ct:log("Setup keys for ~p",[Alg]), - case Alg of - 'ssh-dss' -> ssh_test_lib:setup_dsa(DataDir, UserDir); - 'ssh-rsa' -> ssh_test_lib:setup_rsa(DataDir, UserDir); - 'rsa-sha2-256' -> ssh_test_lib:setup_rsa(DataDir, UserDir); - 'rsa-sha2-512' -> ssh_test_lib:setup_rsa(DataDir, UserDir); - 'ecdsa-sha2-nistp256' -> ssh_test_lib:setup_ecdsa("256", DataDir, UserDir); - 'ecdsa-sha2-nistp384' -> ssh_test_lib:setup_ecdsa("384", DataDir, UserDir); - 'ecdsa-sha2-nistp521' -> ssh_test_lib:setup_ecdsa("521", DataDir, UserDir); - 'ssh-ed25519' -> ssh_test_lib:setup_eddsa(ed25519, DataDir, UserDir); - 'ssh-ed448' -> ssh_test_lib:setup_eddsa(ed448, DataDir, UserDir) - end, + ssh_test_lib:setup_user_key(Alg, DataDir, PrivDir), + ssh_test_lib:setup_host_key_create_dir(Alg, DataDir, PrivDir), Config. diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa index 273866b2a6..24628e071b 100644 --- a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa +++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa @@ -1,12 +1,12 @@ -----BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDIaRzrxz7bSJXh6z+w1lfTW7sfYNMDNXsfN/nvt6Rqi95K4Q+8 -Xpa+LJ6oZCxfMto8w2fZ6uzikRCgBbvzos6L+GPKWhuaBTT5gYsEFphowWN9uPQy -BHwRApOkpZNObN+SnMRuO7lYpwMLnYJdLJTCOxam3mfAzTqdCnpgAWAidwIVANFI -VPGuBxMc84proP+r8X+8hT8RAoGBAI9A+KYH0LLFFIaxgDBA7xnnHIECQrhgiNIN -QfvFPH1Drf5He50OeXFEvR4gt7f3qScWxLUKrpBysS1huJHiJCOq+iREyfoqjtfc -zZT01XejDvX5ck+rz/kiWQk9cZwW6lqLtn3GxIcQvOnrzskT7YyFJ7fx3U98PxmY -qZrXhJEjAoGABFZL6Ztdx9A+7qd5s2A4T7W9fztsm85CxM4MuRuzR2U6zU/Z4D6U -OXiS9UZsz7yIQQfqrc5IgPmSa5og9oflQnQSQcRrY1XvpYLft1DOkH6+wdFf0OlG -hIKihHN+jvgbcEmJTHJwsiW2HJCH6qXK33s4lmthEeIqXuZPduaBnGICFQDKLb+y -afjRTP7FVHq+SBQUy8iqSQ== +MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3 +uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D +txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd +CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs +picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr +c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI +hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4 +cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C +ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC +sQCY/I35vb6GUJn9tEdP -----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub new file mode 100644 index 0000000000..018ef6f537 --- /dev/null +++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa index 1f8577e866..2202c2ead8 100644 --- a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa +++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAwT3nTd9tkKLVaqVcSpK04ID1VVCSCEOW0KXLienX4LD/q7lo -aOyuc044hDnv3P+nz2SNSR05N5dAFaKjCKw7DWz30HgrSbAFxW+KUHAG55brZMOF -oevJOlag8ih1AXjgpEcsJtGILshXyJKXA02IkeH62xeb/VUFgw1iRbbLblhcf2Mb -lyiTcWQZRei57cAA+IjjrxjGJxvG2eLyH1zy+BJa/1Lo4Kfk9tN0rfH1RBzJ5hjK -teQQa4dfc0WNRTdPM2Lzpab8DJw0C2EqO3lUu1JqOHkbDoLwEyX6mB3jRz9Kkh3Q -oSQG89+G7eePu+jl63qIhdLEUbC+JZlDlvlqEQIDAQABAoIBADmtaWGT45b9Eyge -rRpRCY3Mz+0j/EJdMiGaqtLCKj4VdmpiD6jpo/Qkj7fftxlGcWb0gzskbtSJ34XV -okXPalzKfnkJtRnsYPyaGzWBCn6LTD0qIrO+tbQk8Sr2Kl5DHwHJgIMhnT0hbRof -rtU8ihvI0GAeft+xRdDk6MUYF0YasqDDoiNTKFLTFu3bz7Jbh42zlmmu+u4PRO5b -AgxsBxvo9DdXC2lKTB/SKv1kWL2bysr6La3WBKmUwIuOEfLDuT3Vboy9orIDFog8 -QOmEgAZeR8PVw9MhkaHdosWJqL4G0kqIvlhf6rT5ZDUeKGBo2qmO48jHBCGJBFGw -FKJNoNUCgYEA6tjUiKCILKmVtkD7tJtQqEMs/NRDDX6/5uCfROqIMM6ABM3RqgRv -bMJffyGjXG+M86DxDgX0i25U8I0wmNpOU9P6qCgvhaOH+h+TNC37B6IZ+VwGxgDZ -LbdTTxA5WaKzDmwzlUHgAlf31jQ5j3Bf9Js7Tyz/X5fn3fd8i6Mwu6MCgYEA0qW6 -oIkRtzO0jkX5FHmQGuhHmhguzF3WXy/TcrAFQL5EsDQofbAUv70DxxFfufsfgPUP -wKpj/VhED2JFnqVV/68nArWl2eeLSX2gKrgHMy/9AoVYC1w/O2JPilbZXt1W0liT -SB4ZzCONom1m/wtaGjStjup2pSxcm05DKixD3rsCgYEAsKFygGwU31qRAmmvpm/m -YxdbH7FZ2S2KkdBBmei3k9XMTVCrr670Sx2KC6k2H9C6d4aFpuFtwuyxr9bRRTV0 -EfJuJMlMrLuJCuNyqJ0on94YoQbJBWUf8xVd8CooqDUJbQCOb2UDYV/eRFo1LJ/9 -W5DhM7SJQdGTj8uS/cc4YPcCgYEAkQ5DKA17v5bBfT++OFVF4OGXfQuuHll4J/A9 -Qbrowx7DGjuwrmy0vRyiH1FdhCrkFN+sy1YKqQlBRP69RnRAdmPdD0abQSTri94Q -j5pOivc+2Z+Nc7VAbdpTP8ZyxZrSEOOh+IWR6juJaxK/XF4q2+Tup33Z2gBkfSY1 -pjL5QcUCgYBZ1FQwPD1Z3EPXhqs7yVcOGp286BffEnd3xJyvqx13Jsd/LzmG8YzR -FLQ9XLRehs+exdt6yPEMcIkwWKiGPkJ4OXce9arbFp9ILb0Ppo5y41oBsrFV2BCv -NgrB5gTKtN/ipRWwND5zm+yHE3FJR8bgniTVtFF8z2XSWlWduE5gRA== +MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w +W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7 +LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ +cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA +ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b +IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR +OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn +3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w +W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5 +SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1 +A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI +wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx +5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx +f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln +4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8 +gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di +d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp +jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY +SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg +xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi +b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF +xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6 +Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur +DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE +oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg== -----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub new file mode 100644 index 0000000000..b4084d320a --- /dev/null +++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index ecec805696..1269ab393e 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -40,103 +40,64 @@ suite() -> [{ct_hooks,[ts_install_cth]}, - {timetrap,{seconds,40}}]. + {timetrap,{seconds,90}}]. all() -> - [{group, all_tests}, - daemon_already_started + [{group, all_tests} ]. %%%-define(PARALLEL, ). -define(PARALLEL, parallel). groups() -> - [{all_tests, [?PARALLEL], [{group, ssh_renegotiate_SUITE}, - {group, ssh_basic_SUITE}, - ssh_file_is_host_key, - ssh_file_is_host_key_misc, - ssh_file_is_auth_key - ]}, - {ssh_basic_SUITE, [], [app_test, - appup_test, - {group, dsa_key}, - {group, rsa_key}, - {group, ecdsa_sha2_nistp256_key}, - {group, ecdsa_sha2_nistp384_key}, - {group, ecdsa_sha2_nistp521_key}, - {group, ed25519_key}, - {group, ed448_key}, - {group, dsa_pass_key}, - {group, rsa_pass_key}, - {group, ecdsa_sha2_nistp256_pass_key}, - {group, ecdsa_sha2_nistp384_pass_key}, - {group, ecdsa_sha2_nistp521_pass_key}, - {group, host_user_key_differs}, - {group, key_cb}, - {group, internal_error}, - {group, rsa_host_key_is_actualy_ecdsa}, - daemon_already_started, - double_close, - daemon_opt_fd, - multi_daemon_opt_fd, - packet_size, - ssh_info_print, - {group, login_bad_pwd_no_retry}, - shell_exit_status, - setopts_getopts - ]}, - - {ssh_renegotiate_SUITE, [?PARALLEL], [rekey0, - rekey1, - rekey2, - rekey3, - rekey4, - rekey_limit_client, - rekey_limit_daemon, - rekey_time_limit_client, - rekey_time_limit_daemon, - norekey_limit_client, - norekey_limit_daemon, - renegotiate1, - renegotiate2]}, - - {dsa_key, [], [{group, basic}]}, - {rsa_key, [], [{group, basic}]}, - {ecdsa_sha2_nistp256_key, [], [{group, basic}]}, - {ecdsa_sha2_nistp384_key, [], [{group, basic}]}, - {ecdsa_sha2_nistp521_key, [], [{group, basic}]}, - {ed25519_key, [], [{group, basic}]}, - {ed448_key, [], [{group, basic}]}, - {rsa_host_key_is_actualy_ecdsa, [], [fail_daemon_start]}, - {host_user_key_differs, [?PARALLEL], [exec_key_differs1, - exec_key_differs2, - exec_key_differs3, - exec_key_differs_fail]}, - {dsa_pass_key, [], [pass_phrase]}, - {rsa_pass_key, [], [pass_phrase]}, - {ecdsa_sha2_nistp256_pass_key, [], [pass_phrase]}, - {ecdsa_sha2_nistp384_pass_key, [], [pass_phrase]}, - {ecdsa_sha2_nistp521_pass_key, [], [pass_phrase]}, + [{all_tests, [?PARALLEL], [{group, sequential}, + {group, p_basic}, + {group, internal_error}, + {group, login_bad_pwd_no_retry}, + {group, key_cb} + ]}, + + {sequential, [], [app_test, + appup_test, + daemon_already_started, + daemon_error_closes_port, % Should be re-written.. + double_close, + daemon_opt_fd, + multi_daemon_opt_fd, + packet_size, + ssh_info_print, + shell_exit_status, + setopts_getopts, + known_hosts, + ssh_file_is_host_key, + ssh_file_is_host_key_misc, + ssh_file_is_auth_key + ]}, + {key_cb, [?PARALLEL], [key_callback, key_callback_options]}, - {internal_error, [], [internal_error]}, + + {internal_error, [?PARALLEL], [internal_error]}, + {login_bad_pwd_no_retry, [?PARALLEL], [login_bad_pwd_no_retry1, - login_bad_pwd_no_retry2, - login_bad_pwd_no_retry3, - login_bad_pwd_no_retry4, - login_bad_pwd_no_retry5 - ]}, + login_bad_pwd_no_retry2, + login_bad_pwd_no_retry3, + login_bad_pwd_no_retry4, + login_bad_pwd_no_retry5 + ]}, - {basic, [], [{group,p_basic}, - shell, shell_no_unicode, shell_unicode_string, - close, - known_hosts - ]}, {p_basic, [?PARALLEL], [send, peername_sockname, - exec, exec_compressed, - exec_with_io_out, exec_with_io_in, - cli, - idle_time_client, idle_time_server, openssh_zlib_basic_test, - misc_ssh_options, inet_option, inet6_option]} + exec, exec_compressed, + exec_with_io_out, exec_with_io_in, + cli, + idle_time_client, idle_time_server, openssh_zlib_basic_test, + misc_ssh_options, inet_option, inet6_option + + ,shell, + shell_no_unicode, + shell_unicode_string, + close + + ]} ]. @@ -147,6 +108,8 @@ groups() -> init_per_suite(Config) -> ?CHECK_CRYPTO(begin ssh:start(), + ct:log("Pub keys setup for: ~p", + [ssh_test_lib:setup_all_user_host_keys(Config)]), Config end). @@ -154,282 +117,22 @@ end_per_suite(_Config) -> ssh:stop(). %%-------------------------------------------------------------------- -init_per_group(ssh_renegotiate_SUITE, Config) -> - [{preferred_algorithms, ssh:default_algorithms()} | Config]; -init_per_group(dsa_key, Config) -> - case lists:member('ssh-dss', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_dsa(DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(rsa_key, Config) -> - case lists:member('ssh-rsa', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_rsa(DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(rsa_host_key_is_actualy_ecdsa, Config) -> - case - lists:member('ssh-rsa', - ssh_transport:default_algorithms(public_key)) and - lists:member('ecdsa-sha2-nistp256', - ssh_transport:default_algorithms(public_key)) - of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir), - %% The following sets up bad rsa keys: - begin - UserDir = PrivDir, - System = filename:join(UserDir, "system"), - file:copy(filename:join(DataDir, "id_rsa"), filename:join(UserDir, "id_rsa")), - file:rename(filename:join(System, "ssh_host_ecdsa_key"), filename:join(System, "ssh_host_rsa_key")), - file:rename(filename:join(System, "ssh_host_ecdsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")), - ssh_test_lib:setup_rsa_known_host(DataDir, UserDir), - ssh_test_lib:setup_rsa_auth_keys(DataDir, UserDir) - end, - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ecdsa_sha2_nistp256_key, Config) -> - case lists:member('ecdsa-sha2-nistp256', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ecdsa_sha2_nistp384_key, Config) -> - case lists:member('ecdsa-sha2-nistp384', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_ecdsa("384", DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ecdsa_sha2_nistp521_key, Config) -> - case lists:member('ecdsa-sha2-nistp521', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_ecdsa("521", DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ed25519_key, Config) -> - case lists:member('ssh-ed25519', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_eddsa(ed25519, DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ed448_key, Config) -> - case lists:member('ssh-ed448', - ssh_transport:default_algorithms(public_key)) of - true -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_eddsa(ed448, DataDir, PrivDir), - Config; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(rsa_pass_key, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - case lists:member('ssh-rsa', - ssh_transport:default_algorithms(public_key)) - andalso - ssh_test_lib:setup_rsa_pass_phrase(DataDir, PrivDir, "Password") - of - true -> - [{pass_phrase, {rsa_pass_phrase, "Password"}}| Config]; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(dsa_pass_key, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - case lists:member('ssh-dss', - ssh_transport:default_algorithms(public_key)) - andalso - ssh_test_lib:setup_dsa_pass_phrase(DataDir, PrivDir, "Password") - of - true -> - [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config]; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ecdsa_sha2_nistp256_pass_key, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - case lists:member('ecdsa-sha2-nistp256', - ssh_transport:default_algorithms(public_key)) - andalso - ssh_test_lib:setup_ecdsa_pass_phrase("256", DataDir, PrivDir, "Password") - of - true -> - [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config]; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ecdsa_sha2_nistp384_pass_key, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - case lists:member('ecdsa-sha2-nistp384', - ssh_transport:default_algorithms(public_key)) - andalso - ssh_test_lib:setup_ecdsa_pass_phrase("384", DataDir, PrivDir, "Password") - of - true -> - [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config]; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(ecdsa_sha2_nistp521_pass_key, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - case lists:member('ecdsa-sha2-nistp521', - ssh_transport:default_algorithms(public_key)) - andalso - ssh_test_lib:setup_ecdsa_pass_phrase("521", DataDir, PrivDir, "Password") - of - true -> - [{pass_phrase, {ecdsa_pass_phrase, "Password"}}| Config]; - false -> - {skip, unsupported_pub_key} - end; -init_per_group(host_user_key_differs, Config) -> - Data = proplists:get_value(data_dir, Config), - Sys = filename:join(proplists:get_value(priv_dir, Config), system_rsa), - SysUsr = filename:join(Sys, user), - Usr = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256), - file:make_dir(Sys), - file:make_dir(SysUsr), - file:make_dir(Usr), - file:copy(filename:join(Data, "ssh_host_rsa_key"), filename:join(Sys, "ssh_host_rsa_key")), - file:copy(filename:join(Data, "ssh_host_rsa_key.pub"), filename:join(Sys, "ssh_host_rsa_key.pub")), - file:copy(filename:join(Data, "id_ecdsa256"), filename:join(Usr, "id_ecdsa")), - file:copy(filename:join(Data, "id_ecdsa256.pub"), filename:join(Usr, "id_ecdsa.pub")), - ssh_test_lib:setup_ecdsa_auth_keys("256", Data, SysUsr), - ssh_test_lib:setup_rsa_known_host(Sys, Usr), - Config; init_per_group(key_cb, Config) -> case lists:member('ssh-rsa', - ssh_transport:default_algorithms(public_key)) of + ssh_transport:supported_algorithms(public_key)) of true -> DataDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_rsa(DataDir, PrivDir), + ssh_test_lib:setup_user_key('ssh-rsa', DataDir, PrivDir), + ssh_test_lib:setup_host_key_create_dir('ssh-rsa', DataDir, PrivDir), Config; false -> {skip, unsupported_pub_key} end; -init_per_group(internal_error, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_rsa(DataDir, PrivDir), - ssh_test_lib:setup_dsa(DataDir, PrivDir), - ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir), - %% In the test case the key will be deleted after the daemon start: - %% ... file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")), - Config; -init_per_group(dir_options, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - %% Make unreadable dir: - Dir_unreadable = filename:join(PrivDir, "unread"), - ok = file:make_dir(Dir_unreadable), - {ok,F1} = file:read_file_info(Dir_unreadable), - ok = file:write_file_info(Dir_unreadable, - F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}), - %% Make readable file: - File_readable = filename:join(PrivDir, "file"), - ok = file:write_file(File_readable, <<>>), - - %% Check: - case {file:read_file_info(Dir_unreadable), - file:read_file_info(File_readable)} of - {{ok, Id=#file_info{type=directory, access=Md}}, - {ok, If=#file_info{type=regular, access=Mf}}} -> - AccessOK = - case {Md, Mf} of - {read, _} -> false; - {read_write, _} -> false; - {_, read} -> true; - {_, read_write} -> true; - _ -> false - end, - - case AccessOK of - true -> - %% Save: - [{unreadable_dir, Dir_unreadable}, - {readable_file, File_readable} - | Config]; - false -> - ct:log("File#file_info : ~p~n" - "Dir#file_info : ~p",[If,Id]), - {skip, "File or dir mode settings failed"} - end; - - NotDirFile -> - ct:log("{Dir,File} -> ~p",[NotDirFile]), - {skip, "File/Dir creation failed"} - end; init_per_group(_, Config) -> Config. -end_per_group(dsa_key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_dsa(PrivDir), - Config; -end_per_group(rsa_key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_rsa(PrivDir), - Config; -end_per_group(dsa_pass_key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_dsa(PrivDir), - Config; -end_per_group(rsa_pass_key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_rsa(PrivDir), - Config; -end_per_group(key_cb, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_rsa(PrivDir), - Config; -end_per_group(internal_error, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_rsa(PrivDir), - ssh_test_lib:clean_dsa(PrivDir), - Config; - end_per_group(_, Config) -> Config. %%-------------------------------------------------------------------- @@ -462,11 +165,6 @@ init_per_testcase(inet6_option, Config) -> init_per_testcase(_TestCase, Config) -> Config. -end_per_testcase(TestCase, Config) when TestCase == server_password_option; - TestCase == server_userpassword_option -> - UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), - ssh_test_lib:del_dirs(UserDir), - end_per_testcase(Config); end_per_testcase(TC, Config) when TC==shell_no_unicode ; TC==shell_unicode_string -> case proplists:get_value(sftpd, Config) of @@ -527,6 +225,7 @@ inet_option(Config) when is_list(Config) -> %%-------------------------------------------------------------------- %%% Test configuring IPv6 +inet6_option() -> [{timetrap,{seconds,30}}]. inet6_option(Config) when is_list(Config) -> SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), UserDir = proplists:get_value(priv_dir, Config), @@ -603,13 +302,16 @@ exec_with_io_out(Config) when is_list(Config) -> "io:write(hej).", infinity), case ssh_test_lib:receive_exec_result( [{ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"hej">>}}, - {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}}]) of + {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}}, + {ssh_cm, ConnectionRef, {eof, ChannelId0}}, + {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}}, + {ssh_cm, ConnectionRef, {closed, ChannelId0}} + ]) of expected -> ok; Other0 -> ct:fail(Other0) end, - ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0), ssh:close(ConnectionRef), ssh:stop_daemon(Pid). @@ -738,81 +440,6 @@ shell(Config) when is_list(Config) -> end. %%-------------------------------------------------------------------- -%%% Test that we could user different types of host pubkey and user pubkey -exec_key_differs1(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp256']). - -exec_key_differs2(Config) -> exec_key_differs(Config, ['ssh-dss','ecdsa-sha2-nistp256']). - -exec_key_differs3(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp384','ecdsa-sha2-nistp256']). - - - -exec_key_differs(Config, UserPKAlgs) -> - case lists:usort(['ssh-rsa'|UserPKAlgs]) - -- ssh_transport:supported_algorithms(public_key) - of - [] -> - process_flag(trap_exit, true), - SystemDir = filename:join(proplists:get_value(priv_dir, Config), system_rsa), - SystemUserDir = filename:join(SystemDir, user), - UserDir = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256), - - {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, SystemUserDir}, - {preferred_algorithms, - [{public_key,['ssh-rsa'|UserPKAlgs]}]}]), - ct:sleep(500), - - IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}, - {preferred_algorithms,[{public_key,['ssh-rsa']}]}, - {pref_public_key_algs,UserPKAlgs} - ]), - - - receive - {'EXIT', _, _} -> - ct:fail(no_ssh_connection); - ErlShellStart -> - ct:log("Erlang shell start: ~p~n", [ErlShellStart]), - do_shell(IO, Shell) - after - 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) - end; - - UnsupportedPubKeys -> - {skip, io_lib:format("~p unsupported",[UnsupportedPubKeys])} - end. - -%%-------------------------------------------------------------------- -exec_key_differs_fail(Config) when is_list(Config) -> - process_flag(trap_exit, true), - SystemDir = filename:join(proplists:get_value(priv_dir, Config), system_rsa), - SystemUserDir = filename:join(SystemDir, user), - UserDir = filename:join(proplists:get_value(priv_dir, Config), user_ecdsa_256), - - {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, SystemUserDir}, - {preferred_algorithms, - [{public_key,['ssh-rsa']}]}]), - ct:sleep(500), - - IO = ssh_test_lib:start_io_server(), - ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}, - {recv_ext_info, false}, - {preferred_algorithms,[{public_key,['ssh-rsa']}]}, - {pref_public_key_algs,['ssh-dss']}]), - receive - {'EXIT', _, _} -> - ok; - ErlShellStart -> - ct:log("Erlang shell start: ~p~n", [ErlShellStart]), - ct:fail(connection_not_rejected) - after - 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) - end. - -%%-------------------------------------------------------------------- cli(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), @@ -871,6 +498,11 @@ daemon_already_started(Config) when is_list(Config) -> %%-------------------------------------------------------------------- %%% Test that a failed daemon start does not leave the port open + +%%%%%%%%%%%%%%%%%%%%%% REWRITE! %%%%%%%%%%%%%%%%%%%% +%%% 1) check that {error,_} is not {error,eaddrinuse} +%%% 2) instead of ssh_test_lib:daemon second time, use gen_tcp:listen + daemon_error_closes_port(Config) -> GoodSystemDir = proplists:get_value(data_dir, Config), Port = inet_port(), @@ -886,6 +518,11 @@ daemon_error_closes_port(Config) -> ssh:stop_daemon(Pid) end. +inet_port() -> + {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]), + {ok, Port} = inet:port(Socket), + gen_tcp:close(Socket), + Port. %%-------------------------------------------------------------------- %%% check that known_hosts is updated correctly @@ -957,7 +594,7 @@ known_hosts(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- -ssh_file_is_host_key() -> [{timetrap,{seconds,120}}]. % Some machines are S L O W ! +ssh_file_is_host_key() -> [{timetrap,{seconds,240}}]. % Some machines are S L O W ! ssh_file_is_host_key(Config) -> Dir = ssh_test_lib:create_random_dir(Config), ct:log("Dir = ~p", [Dir]), @@ -1176,12 +813,13 @@ send(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), UserDir = proplists:get_value(priv_dir, Config), - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {preferred_algorithms, ssh_transport:supported_algorithms()}, {user_dir, UserDir}, {failfun, fun ssh_test_lib:failfun/2}]), ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + ssh_test_lib:connect(Host, Port, [{preferred_algorithms, ssh_transport:supported_algorithms()}, + {silently_accept_hosts, true}, {user_dir, UserDir}, {user_interaction, false}]), {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), @@ -1191,17 +829,6 @@ send(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -%%% -fail_daemon_start(Config) when is_list(Config) -> - process_flag(trap_exit, true), - SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), - UserDir = proplists:get_value(priv_dir, Config), - - {error,_} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {failfun, fun ssh_test_lib:failfun/2}]). - -%%-------------------------------------------------------------------- %%% Test ssh:connection_info([peername, sockname]) peername_sockname(Config) when is_list(Config) -> process_flag(trap_exit, true), @@ -1646,315 +1273,6 @@ setopts_getopts(Config) -> ssh:stop_daemon(Pid). -%%---------------------------------------------------------------------------- -%%% Idle timeout test -rekey0() -> [{timetrap,{seconds,90}}]. -rekey1() -> [{timetrap,{seconds,90}}]. -rekey2() -> [{timetrap,{seconds,90}}]. -rekey3() -> [{timetrap,{seconds,90}}]. -rekey4() -> [{timetrap,{seconds,90}}]. - -rekey0(Config) -> rekey_chk(Config, 0, 0). -rekey1(Config) -> rekey_chk(Config, infinity, 0). -rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0). -rekey3(Config) -> rekey_chk(Config, 0, infinity). -rekey4(Config) -> rekey_chk(Config, 0, {infinity,infinity}). - -rekey_chk(Config, RLdaemon, RLclient) -> - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, [{rekey_limit, RLdaemon}]), - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]), - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - - %% Make both sides send something: - {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - %% Check rekeying - timer:sleep(?REKEY_DATA_TMO), - ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10), - - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- -%%% Test rekeying by data volume - -rekey_limit_client() -> [{timetrap,{seconds,400}}]. -rekey_limit_client(Config) -> - Limit = 6000, - UserDir = proplists:get_value(priv_dir, Config), - DataFile = filename:join(UserDir, "rekey.data"), - Data = lists:duplicate(Limit+10,1), - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit}, - {max_random_length_padding,0}]), - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - %% Check that it doesn't rekey without data transfer - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), - - %% Check that datatransfer triggers rekeying - ok = ssh_sftp:write_file(SftpPid, DataFile, Data), - timer:sleep(?REKEY_DATA_TMO), - ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10), - - %% Check that datatransfer continues to trigger rekeying - ok = ssh_sftp:write_file(SftpPid, DataFile, Data), - timer:sleep(?REKEY_DATA_TMO), - ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10), - - %% Check that it doesn't rekey without data transfer - timer:sleep(?REKEY_DATA_TMO), - true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), - - %% Check that it doesn't rekey on a small datatransfer - ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), - - %% Check that it doesn't rekey without data transfer - timer:sleep(?REKEY_DATA_TMO), - true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), - - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - - - -rekey_limit_daemon() -> [{timetrap,{seconds,400}}]. -rekey_limit_daemon(Config) -> - Limit = 6000, - UserDir = proplists:get_value(priv_dir, Config), - DataFile1 = filename:join(UserDir, "rekey1.data"), - DataFile2 = filename:join(UserDir, "rekey2.data"), - file:write_file(DataFile1, lists:duplicate(Limit+10,1)), - file:write_file(DataFile2, "hi\n"), - - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit}, - {max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]), - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - %% Check that it doesn't rekey without data transfer - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - timer:sleep(?REKEY_DATA_TMO), - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - - %% Check that datatransfer triggers rekeying - {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1), - timer:sleep(?REKEY_DATA_TMO), - ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10), - - %% Check that datatransfer continues to trigger rekeying - {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1), - timer:sleep(?REKEY_DATA_TMO), - ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10), - - %% Check that it doesn't rekey without data transfer - timer:sleep(?REKEY_DATA_TMO), - true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), - - %% Check that it doesn't rekey on a small datatransfer - {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), - - %% Check that it doesn't rekey without data transfer - timer:sleep(?REKEY_DATA_TMO), - true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), - - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - - -%%-------------------------------------------------------------------- -%% Check that datatransfer in the other direction does not trigger re-keying -norekey_limit_client() -> [{timetrap,{seconds,400}}]. -norekey_limit_client(Config) -> - Limit = 6000, - UserDir = proplists:get_value(priv_dir, Config), - DataFile = filename:join(UserDir, "rekey3.data"), - file:write_file(DataFile, lists:duplicate(Limit+10,1)), - - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit}, - {max_random_length_padding,0}]), - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), - - {ok,_} = ssh_sftp:read_file(SftpPid, DataFile), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), - - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%% Check that datatransfer in the other direction does not trigger re-keying -norekey_limit_daemon() -> [{timetrap,{seconds,400}}]. -norekey_limit_daemon(Config) -> - Limit = 6000, - UserDir = proplists:get_value(priv_dir, Config), - DataFile = filename:join(UserDir, "rekey4.data"), - - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit}, - {max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]), - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), - - ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)), - timer:sleep(?REKEY_DATA_TMO), - true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), - - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- -%%% Test rekeying by time - -rekey_time_limit_client() -> [{timetrap,{seconds,400}}]. -rekey_time_limit_client(Config) -> - Minutes = ?REKEY_DATA_TMO div 60000, - GB = 1024*1000*1000, - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}}, - {max_random_length_padding,0}]), - rekey_time_limit(Pid, ConnectionRef). - -rekey_time_limit_daemon() -> [{timetrap,{seconds,400}}]. -rekey_time_limit_daemon(Config) -> - Minutes = ?REKEY_DATA_TMO div 60000, - GB = 1024*1000*1000, - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}}, - {max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]), - rekey_time_limit(Pid, ConnectionRef). - - -rekey_time_limit(Pid, ConnectionRef) -> - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - - timer:sleep(5000), - true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), - - %% Check that it rekeys when the max time + 30s has passed - timer:sleep(?REKEY_DATA_TMO + 30*1000), - ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10), - - %% Check that it does not rekey when nothing is transferred - timer:sleep(?REKEY_DATA_TMO + 30*1000), - ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10), - - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- - -%%% Test rekeying with simultaneous send request - -renegotiate1(Config) -> - UserDir = proplists:get_value(priv_dir, Config), - DataFile = filename:join(UserDir, "renegotiate1.data"), - - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - - {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort), - - ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]), - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - - {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), - - ok = ssh_sftp:write(SftpPid, Handle, "hi\n"), - - ssh_relay:hold(RelayPid, rx, 20, 1000), - ssh_connection_handler:renegotiate(ConnectionRef), - spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end), - - timer:sleep(2000), - - Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), - - false = (Kex2 == Kex1), - - ssh_relay:stop(RelayPid), - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- - -%%% Test rekeying with inflight messages from peer - -renegotiate2(Config) -> - UserDir = proplists:get_value(priv_dir, Config), - DataFile = filename:join(UserDir, "renegotiate2.data"), - - Algs = proplists:get_value(preferred_algorithms, Config), - {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, - {preferred_algorithms,Algs}]), - - {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort), - - ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]), - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - - Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), - - {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), - - ok = ssh_sftp:write(SftpPid, Handle, "hi\n"), - - ssh_relay:hold(RelayPid, rx, 20, infinity), - spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end), - %% need a small pause here to ensure ssh_sftp:write is executed - ct:sleep(10), - ssh_connection_handler:renegotiate(ConnectionRef), - ssh_relay:release(RelayPid, rx), - - timer:sleep(2000), - - Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), - - false = (Kex2 == Kex1), - - ssh_relay:stop(RelayPid), - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -2134,9 +1452,3 @@ new_do_shell_prompt(IO, N, Op, Str, More) -> ct:log("Matched prompt ~p",[N]), new_do_shell(IO, N, [{Op,Str}|More]). -%%-------------------------------------------------------------------- -inet_port() -> - {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]), - {ok, Port} = inet:port(Socket), - gen_tcp:close(Socket), - Port. diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa index d306f8b26e..24628e071b 100644 --- a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa @@ -1,13 +1,12 @@ -----BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ -APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod -/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP -kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW -JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD -OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt -+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e -uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX -Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE -ZU8w8Q+H7z0j+a+70x2iAw== +MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3 +uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D +txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd +CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs +picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr +c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI +hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4 +cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C +ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC +sQCY/I35vb6GUJn9tEdP -----END DSA PRIVATE KEY----- - diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub new file mode 100644 index 0000000000..018ef6f537 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa index 9d7e0dd5fb..2202c2ead8 100644 --- a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU -DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl -zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB -AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V -TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3 -CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK -SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p -z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd -WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39 -sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3 -xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ -dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x -ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak= +MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w +W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7 +LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ +cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA +ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b +IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR +OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn +3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w +W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5 +SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1 +A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI +wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx +5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx +f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln +4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8 +gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di +d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp +jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY +SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg +xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi +b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF +xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6 +Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur +DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE +oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg== -----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub new file mode 100644 index 0000000000..b4084d320a --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl index a8b3508583..f5f0a35bac 100644 --- a/lib/ssh/test/ssh_compat_SUITE.erl +++ b/lib/ssh/test/ssh_compat_SUITE.erl @@ -41,7 +41,7 @@ %%-------------------------------------------------------------------- suite() -> - [{timetrap,{seconds,60}}]. + [{timetrap,{seconds,90}}]. all() -> %% [check_docker_present] ++ @@ -534,21 +534,22 @@ result_of_exec(C, Ch, ExitStatus, Acc) -> chk_all_algos(FunctionName, CommonAlgs, Config, DoTestFun) when is_function(DoTestFun,2) -> ct:comment("~p algorithms",[length(CommonAlgs)]), %% Check each algorithm - Failed = + Nmax = length(CommonAlgs), + {_N,Failed} = lists:foldl( - fun({Tag,Alg}, FailedAlgos) -> - %% ct:log("Try ~p",[Alg]), + fun({Tag,Alg}, {N,FailedAlgos}) -> + ct:log("Try ~p ~p/~p",[{Tag,Alg},N,Nmax]), case DoTestFun(Tag,Alg) of {ok,C} -> ssh:close(C), - FailedAlgos; + {N+1,FailedAlgos}; ok -> - FailedAlgos; + {N+1,FailedAlgos}; Other -> ct:log("FAILED! ~p ~p: ~p",[Tag,Alg,Other]), - [{Alg,Other}|FailedAlgos] + {N+1, [{Alg,Other}|FailedAlgos]} end - end, [], CommonAlgs), + end, {1,[]}, CommonAlgs), ct:pal("~s", [format_result_table_use_all_algos(FunctionName, Config, CommonAlgs, Failed)]), case Failed of [] -> diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl index d428daaf59..c4621723f6 100644 --- a/lib/ssh/test/ssh_connection_SUITE.erl +++ b/lib/ssh/test/ssh_connection_SUITE.erl @@ -1270,37 +1270,6 @@ test_exec_is_enabled(ConnectionRef, Exec, Expect) -> {fail,"Exec Timeout"} end. - -%%%---------------------------------------------------------------- -get_channel_close_sequence(ConnectionRef, ChannelId) -> - Set = [eof, exit_status, closed], - get_channel_close_sequence(ConnectionRef, ChannelId, Set). - -get_channel_close_sequence(_ConnectionRef, _ChannelId, []) -> - ok; -get_channel_close_sequence(ConnectionRef, ChannelId, Set) -> - receive - {ssh_cm, ConnectionRef, Event} when element(2,Event) == ChannelId -> - try lists:member(element(1,Event), Set) - of - true -> - ct:log("get_channel_close_sequence: ~p received", [Event]), - get_channel_close_sequence(ConnectionRef, Event, Set--[element(1,Event)]); - false -> - ct:log("~p:~p Got unexpected ~p~nExpecting ~p",[?MODULE,?LINE,Event,Set]), - ct:fail("Strange response 1") - catch - _ :_ -> - ct:log("~p:~p Got unexpected ~p~nExpecting ~p",[?MODULE,?LINE,Event,Set]), - ct:fail("Strange response 2") - end; - Msg -> - ct:log("~p:~p Got unexpected ~p~nExpecting event from the set ~p",[?MODULE,?LINE,Msg,Set]), - ct:fail("Strange response 3") - after - 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) - end. - %%%---------------------------------------------------------------- big_cat_rx(ConnectionRef, ChannelId) -> big_cat_rx(ConnectionRef, ChannelId, []). diff --git a/lib/ssh/test/ssh_dbg_SUITE.erl b/lib/ssh/test/ssh_dbg_SUITE.erl index ab7918fa90..8df9c8093a 100644 --- a/lib/ssh/test/ssh_dbg_SUITE.erl +++ b/lib/ssh/test/ssh_dbg_SUITE.erl @@ -82,6 +82,8 @@ end_per_testcase(_TC, Config) -> ok after 5000 -> + ct:log("~p:~p Messages:~n~p", + [?MODULE,?LINE, process_info(self(),messages)]), ssh_dbg:stop(), ssh:stop_daemon(Pid), ct:fail("No '~s' debug message",[ExpectPfx]) @@ -448,10 +450,15 @@ dbg_SKIP(Ref, Prefixes) -> dbg_SKIP(Ref, Prefixes, UnexpectedAcc) -> receive + {Ref, [_, _C, Msg]} when is_tuple(Msg) -> + %% filter non ssh_dbg messages, for example from dbg:tp(..) etc + dbg_SKIP(Ref, Prefixes, UnexpectedAcc); {Ref, [_, _C, Msg]=M} -> case lists:any( fun(Pfx) -> - lists:prefix(Pfx, Msg) + try lists:prefix(Pfx, Msg) + catch _:_ -> false + end end, Prefixes) of true -> ct:log("Skip:~n~p", [M]), diff --git a/lib/ssh/test/ssh_engine_SUITE.erl b/lib/ssh/test/ssh_engine_SUITE.erl index 3adb23acdb..241fb255cc 100644 --- a/lib/ssh/test/ssh_engine_SUITE.erl +++ b/lib/ssh/test/ssh_engine_SUITE.erl @@ -81,7 +81,7 @@ end_per_suite(Config) -> %%-------------------------------------------------------------------- init_per_group(dsa_key, Config) -> case lists:member('ssh-dss', - ssh_transport:default_algorithms(public_key)) of + ssh_transport:supported_algorithms(public_key)) of true -> start_daemon(Config, 'ssh-dss', "dsa_private_key.pem"); false -> @@ -89,7 +89,7 @@ init_per_group(dsa_key, Config) -> end; init_per_group(rsa_key, Config) -> case lists:member('ssh-rsa', - ssh_transport:default_algorithms(public_key)) of + ssh_transport:supported_algorithms(public_key)) of true -> start_daemon(Config, 'ssh-rsa', "rsa_private_key.pem"); false -> @@ -102,9 +102,11 @@ start_daemon(Config, KeyType, KeyId) -> KeyCBOpts = [{engine, proplists:get_value(engine,Config)}, {KeyType, FullKeyId} ], - Opts = [{key_cb, {ssh_key_cb_engine_keys, KeyCBOpts}}], + Opts = [{key_cb, {ssh_key_cb_engine_keys, KeyCBOpts}}, + {modify_algorithms, [{append, [{public_key,[KeyType]}]}]} + ], {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts), - [{host_port,{Host,Port}}, {daemon_pid,Pid}| Config]. + [{host_port,{Host,Port}}, {daemon_pid,Pid}, {key_type,KeyType}| Config]. end_per_group(_, Config) -> @@ -118,7 +120,11 @@ end_per_group(_, Config) -> %% A simple exec call simple_connect(Config) -> {Host,Port} = proplists:get_value(host_port, Config), - CRef = ssh_test_lib:std_connect(Config, Host, Port, []), + KeyType = proplists:get_value(key_type, Config), + Opts = [ + {modify_algorithms, [{append, [{public_key,[KeyType]}]}]} + ], + CRef = ssh_test_lib:std_connect(Config, Host, Port, Opts), ssh:close(CRef). %%-------------------------------------------------------------------- @@ -146,8 +152,3 @@ load_engine() -> {error, Error} end. -start_std_daemon(Opts, Config) -> - ct:log("starting std_daemon",[]), - {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts), - ct:log("started ~p:~p ~p",[Host,Port,Opts]), - [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config]. diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 93c4d7b7a7..4368761d02 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -92,7 +92,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}, - {timetrap,{seconds,30}}]. + {timetrap,{seconds,60}}]. all() -> [connectfun_disconnectfun_server, @@ -128,9 +128,9 @@ all() -> id_string_own_string_server_trail_space, id_string_random_server, save_accepted_host_option, - {group, hardening_tests}, config_file, - config_file_modify_algorithms_order + config_file_modify_algorithms_order, + {group, hardening_tests} ]. groups() -> @@ -157,10 +157,8 @@ end_per_suite(_Config) -> %%-------------------------------------------------------------------- init_per_group(hardening_tests, Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_dsa(DataDir, PrivDir), - ssh_test_lib:setup_rsa(DataDir, PrivDir), + ct:log("Pub keys setup for: ~p", + [ssh_test_lib:setup_all_user_host_keys(Config)]), Config; init_per_group(dir_options, Config) -> PrivDir = proplists:get_value(priv_dir, Config), @@ -937,7 +935,7 @@ ssh_connect_arg4_timeout(_Config) -> Msp = ms_passed(T0), exit(Server,hasta_la_vista___baby), Low = 0.9*Timeout, - High = 2.5*Timeout, + High = 4.0*Timeout, ct:log("Timeout limits: ~.4f - ~.4f ms, timeout " "was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]), if diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa b/lib/ssh/test/ssh_options_SUITE_data/id_dsa index d306f8b26e..24628e071b 100644 --- a/lib/ssh/test/ssh_options_SUITE_data/id_dsa +++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa @@ -1,13 +1,12 @@ -----BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ -APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod -/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP -kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW -JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD -OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt -+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e -uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX -Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE -ZU8w8Q+H7z0j+a+70x2iAw== +MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3 +uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D +txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd +CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs +picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr +c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI +hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4 +cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C +ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC +sQCY/I35vb6GUJn9tEdP -----END DSA PRIVATE KEY----- - diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub new file mode 100644 index 0000000000..018ef6f537 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256 new file mode 100644 index 0000000000..4b1eb12eaa --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49 +AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br +N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub new file mode 100644 index 0000000000..a0147e60fa --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384 new file mode 100644 index 0000000000..4e8aa40959 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S +PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd +ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF +8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub new file mode 100644 index 0000000000..41e722e545 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521 new file mode 100644 index 0000000000..7196f46e97 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q +9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5 +01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7 +XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf +pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub new file mode 100644 index 0000000000..8f059120bc --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ecdsa521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519 new file mode 100644 index 0000000000..401a3e4a9a --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us +aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw +AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L +ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub new file mode 100644 index 0000000000..a5c03b19c1 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed448 b/lib/ssh/test/ssh_options_SUITE_data/id_ed448 new file mode 100644 index 0000000000..8ecfd710dc --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed448 @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh +1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf +fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32 +BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ +m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub new file mode 100644 index 0000000000..cec0765a5d --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_ed448.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa b/lib/ssh/test/ssh_options_SUITE_data/id_rsa index 9d7e0dd5fb..2202c2ead8 100644 --- a/lib/ssh/test/ssh_options_SUITE_data/id_rsa +++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU -DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl -zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB -AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V -TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3 -CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK -SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p -z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd -WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39 -sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3 -xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ -dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x -ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak= +MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w +W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7 +LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ +cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA +ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b +IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR +OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn +3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w +W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5 +SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1 +A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI +wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx +5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx +f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln +4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8 +gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di +d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp +jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY +SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg +xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi +b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF +xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6 +Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur +DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE +oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg== -----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub new file mode 100644 index 0000000000..b4084d320a --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256 new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384 new file mode 100644 index 0000000000..fb1a862ded --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw +mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3 +CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7 +Hneb/99fIYopdMH5NMnk60zGO1uZ2vc= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub new file mode 100644 index 0000000000..428d5fb7d7 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521 new file mode 100644 index 0000000000..3e51ec2ecd --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx +U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq +Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/ +vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5 +ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub new file mode 100644 index 0000000000..017a29f4da --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ecdsa_key521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key new file mode 100644 index 0000000000..13a8fcf491 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD +uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ +AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W +TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..156ef4045c --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key new file mode 100644 index 0000000000..31a7e4e8c3 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_ed448_key.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl index 228e0dcbd8..3a08523039 100644 --- a/lib/ssh/test/ssh_protocol_SUITE.erl +++ b/lib/ssh/test/ssh_protocol_SUITE.erl @@ -907,10 +907,8 @@ stop_apps(_Config) -> setup_dirs(Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_dsa(DataDir, PrivDir), - ssh_test_lib:setup_rsa(DataDir, PrivDir), + ct:log("Pub keys setup for: ~p", + [ssh_test_lib:setup_all_user_host_keys(Config)]), Config. system_dir(Config) -> filename:join(proplists:get_value(priv_dir, Config), system). diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa index d306f8b26e..24628e071b 100644 --- a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa @@ -1,13 +1,12 @@ -----BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ -APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod -/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP -kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW -JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD -OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt -+9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e -uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX -Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE -ZU8w8Q+H7z0j+a+70x2iAw== +MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3 +uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D +txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd +CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs +picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr +c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI +hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4 +cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C +ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC +sQCY/I35vb6GUJn9tEdP -----END DSA PRIVATE KEY----- - diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub new file mode 100644 index 0000000000..018ef6f537 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256 new file mode 100644 index 0000000000..4b1eb12eaa --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49 +AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br +N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub new file mode 100644 index 0000000000..a0147e60fa --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384 new file mode 100644 index 0000000000..4e8aa40959 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S +PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd +ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF +8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub new file mode 100644 index 0000000000..41e722e545 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521 new file mode 100644 index 0000000000..7196f46e97 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q +9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5 +01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7 +XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf +pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub new file mode 100644 index 0000000000..8f059120bc --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ecdsa521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519 new file mode 100644 index 0000000000..401a3e4a9a --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us +aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw +AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L +ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub new file mode 100644 index 0000000000..a5c03b19c1 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448 b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448 new file mode 100644 index 0000000000..8ecfd710dc --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448 @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh +1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf +fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32 +BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ +m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub new file mode 100644 index 0000000000..cec0765a5d --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_ed448.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa index 9d7e0dd5fb..2202c2ead8 100644 --- a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa @@ -1,15 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU -DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl -zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB -AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V -TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3 -CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK -SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p -z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd -WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39 -sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3 -xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ -dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x -ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak= +MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w +W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7 +LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ +cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA +ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b +IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR +OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn +3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w +W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5 +SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1 +A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI +wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx +5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx +f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln +4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8 +gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di +d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp +jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY +SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg +xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi +b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF +xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6 +Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur +DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE +oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg== -----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub new file mode 100644 index 0000000000..b4084d320a --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256 new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384 new file mode 100644 index 0000000000..fb1a862ded --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw +mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3 +CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7 +Hneb/99fIYopdMH5NMnk60zGO1uZ2vc= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub new file mode 100644 index 0000000000..428d5fb7d7 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521 new file mode 100644 index 0000000000..3e51ec2ecd --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx +U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq +Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/ +vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5 +ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub new file mode 100644 index 0000000000..017a29f4da --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ecdsa_key521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key new file mode 100644 index 0000000000..13a8fcf491 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD +uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ +AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W +TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..156ef4045c --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key new file mode 100644 index 0000000000..31a7e4e8c3 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/ssh_protocol_SUITE_data/ssh_host_ed448_key.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_pubkey_SUITE.erl b/lib/ssh/test/ssh_pubkey_SUITE.erl index fbc17e5028..f1afb496c1 100644 --- a/lib/ssh/test/ssh_pubkey_SUITE.erl +++ b/lib/ssh/test/ssh_pubkey_SUITE.erl @@ -36,7 +36,8 @@ suite() -> all() -> [{group, old_format}, - {group, new_format} + {group, new_format}, + {group, option_space} ]. @@ -51,19 +52,31 @@ all() -> connect_ecdsa_to_ecdsa ]). --define(tests_new, [connect_ecdsa_to_ed25519, - connect_rsa_to_ed25519, +-define(tests_new, [ connect_dsa_to_ed25519, - connect_ed25519_to_rsa, + connect_dsa_to_ed448, + connect_ecdsa_to_ed25519, + connect_ecdsa_to_ed448, connect_ed25519_to_dsa, connect_ed25519_to_ecdsa, - connect_ed25519_to_ed25519 - | ?tests_old]). + connect_ed25519_to_ed448, + connect_ed25519_to_ed25519, + connect_ed25519_to_rsa, + connect_ed448_to_dsa, + connect_ed448_to_ecdsa, + connect_ed448_to_ed25519, + connect_ed448_to_ed448, + connect_ed448_to_rsa, + connect_rsa_to_ed25519, + connect_rsa_to_ed448 + | ?tests_old % but taken from the new format directory + ]). groups() -> - [{new_format, [], ?tests_new}, - {old_format, [], ?tests_old++[{group,passphrase}]}, - {passphrase, [], ?tests_old} + [{new_format, [], ?tests_new}, + {old_format, [], ?tests_old++[{group,passphrase}]}, + {passphrase, [], ?tests_old}, + {option_space,[], [{group,new_format}]} ]. %%%---------------------------------------------------------------- @@ -71,7 +84,8 @@ init_per_suite(Config) -> ?CHECK_CRYPTO( begin ssh:start(), - [{client_opts,[]} + [{client_opts,[]}, + {daemon_opts,[]} | Config] end). @@ -89,6 +103,11 @@ init_per_group(old_format, Config) -> [{fmt,old_format}, {key_src_dir,Dir} | Config]; +init_per_group(option_space, Config) -> + extend_optsL([client_opts,daemon_opts], + [{key_cb, {ssh_file, [{optimize, space}]}}], + Config); + init_per_group(passphrase, Config0) -> case supported(hashs, md5) of true -> @@ -110,7 +129,11 @@ extend_opts(OptName, Value, Config) -> Opts = proplists:get_value(OptName, Config), replace_opt(OptName, [Value|Opts], Config). -extend_optsL(OptName, Values, Config) -> +extend_optsL(OptNames, Values, Config) when is_list(OptNames) -> + lists:foldl(fun(N, Cnf) -> + extend_optsL(N, Values, Cnf) + end, Config, OptNames); +extend_optsL(OptName, Values, Config) when is_atom(OptName) -> Opts = proplists:get_value(OptName, Config), replace_opt(OptName, Values ++ Opts, Config). @@ -131,6 +154,8 @@ init_per_testcase(connect_rsa_to_ecdsa, Config0) -> setup_user_system_dir(rsa, ecdsa, Config0); init_per_testcase(connect_rsa_to_ed25519, Config0) -> setup_user_system_dir(rsa, ed25519, Config0); +init_per_testcase(connect_rsa_to_ed448, Config0) -> + setup_user_system_dir(rsa, ed448, Config0); init_per_testcase(connect_dsa_to_rsa, Config0) -> setup_user_system_dir(dsa, rsa, Config0); init_per_testcase(connect_dsa_to_dsa, Config0) -> @@ -139,6 +164,8 @@ init_per_testcase(connect_dsa_to_ecdsa, Config0) -> setup_user_system_dir(dsa, ecdsa, Config0); init_per_testcase(connect_dsa_to_ed25519, Config0) -> setup_user_system_dir(dsa, ed25519, Config0); +init_per_testcase(connect_dsa_to_ed448, Config0) -> + setup_user_system_dir(dsa, ed448, Config0); init_per_testcase(connect_ecdsa_to_rsa, Config0) -> setup_user_system_dir(ecdsa, rsa, Config0); init_per_testcase(connect_ecdsa_to_dsa, Config0) -> @@ -147,6 +174,8 @@ init_per_testcase(connect_ecdsa_to_ecdsa, Config0) -> setup_user_system_dir(ecdsa, ecdsa, Config0); init_per_testcase(connect_ecdsa_to_ed25519, Config0) -> setup_user_system_dir(ecdsa, ed25519, Config0); +init_per_testcase(connect_ecdsa_to_ed448, Config0) -> + setup_user_system_dir(ecdsa, ed448, Config0); init_per_testcase(connect_ed25519_to_rsa, Config0) -> setup_user_system_dir(ed25519, rsa, Config0); init_per_testcase(connect_ed25519_to_dsa, Config0) -> @@ -155,6 +184,18 @@ init_per_testcase(connect_ed25519_to_ecdsa, Config0) -> setup_user_system_dir(ed25519, ecdsa, Config0); init_per_testcase(connect_ed25519_to_ed25519, Config0) -> setup_user_system_dir(ed25519, ed25519, Config0); +init_per_testcase(connect_ed25519_to_ed448, Config0) -> + setup_user_system_dir(ed25519, ed448, Config0); +init_per_testcase(connect_ed448_to_rsa, Config0) -> + setup_user_system_dir(ed448, rsa, Config0); +init_per_testcase(connect_ed448_to_dsa, Config0) -> + setup_user_system_dir(ed448, dsa, Config0); +init_per_testcase(connect_ed448_to_ecdsa, Config0) -> + setup_user_system_dir(ed448, ecdsa, Config0); +init_per_testcase(connect_ed448_to_ed25519, Config0) -> + setup_user_system_dir(ed448, ed25519, Config0); +init_per_testcase(connect_ed448_to_ed448, Config0) -> + setup_user_system_dir(ed448, ed448, Config0); init_per_testcase(_, Config) -> Config. @@ -176,6 +217,9 @@ connect_rsa_to_ecdsa(Config) -> connect_rsa_to_ed25519(Config) -> try_connect(Config). +connect_rsa_to_ed448(Config) -> + try_connect(Config). + connect_dsa_to_rsa(Config) -> try_connect(Config). @@ -188,6 +232,9 @@ connect_dsa_to_ecdsa(Config) -> connect_dsa_to_ed25519(Config) -> try_connect(Config). +connect_dsa_to_ed448(Config) -> + try_connect(Config). + connect_ecdsa_to_rsa(Config) -> try_connect(Config). @@ -200,6 +247,9 @@ connect_ecdsa_to_ecdsa(Config) -> connect_ecdsa_to_ed25519(Config) -> try_connect(Config). +connect_ecdsa_to_ed448(Config) -> + try_connect(Config). + connect_ed25519_to_rsa(Config) -> try_connect(Config). @@ -212,6 +262,24 @@ connect_ed25519_to_ecdsa(Config) -> connect_ed25519_to_ed25519(Config) -> try_connect(Config). +connect_ed25519_to_ed448(Config) -> + try_connect(Config). + +connect_ed448_to_rsa(Config) -> + try_connect(Config). + +connect_ed448_to_dsa(Config) -> + try_connect(Config). + +connect_ed448_to_ecdsa(Config) -> + try_connect(Config). + +connect_ed448_to_ed25519(Config) -> + try_connect(Config). + +connect_ed448_to_ed448(Config) -> + try_connect(Config). + %%%---------------------------------------------------------------- try_connect({skip,Reson}) -> @@ -220,9 +288,12 @@ try_connect(Config) -> SystemDir = proplists:get_value(system_dir, Config), UserDir = proplists:get_value(user_dir, Config), ClientOpts = proplists:get_value(client_opts, Config, []), + DaemonOpts = proplists:get_value(daemon_opts, Config, []), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}]), + {user_dir, UserDir} + | DaemonOpts]), + C = ssh_test_lib:connect(Host, Port, [{user_dir, UserDir}, {silently_accept_hosts, true}, {user_interaction, false} @@ -250,18 +321,32 @@ setup_user_system_dir(ClientAlg, ServerAlg, Config) -> HostSrcFile = filename:join(KeySrcDir, file(host,ServerAlg)), HostDstFile = filename:join(SystemDir, file(host,ServerAlg)), - {ok,_} = file:copy(HostSrcFile, HostDstFile), UserSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)), UserDstFile = filename:join(UserDir, file(user,ClientAlg)), - {ok,_} = file:copy(UserSrcFile, UserDstFile), UserPubSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)++".pub"), AuthorizedKeys = filename:join(UserDir, "authorized_keys"), - {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys), - [{system_dir,SystemDir}, - {user_dir,UserDir} | Config]; + try + {ok,_} = file:copy(UserSrcFile, UserDstFile), + {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys), + {ok,_} = file:copy(HostSrcFile, HostDstFile) + of + _ -> + ModAlgs = [{modify_algorithms, + [{append,[{public_key, + lists:usort([alg(ClientAlg), + alg(ServerAlg)])}]}]} + ], + [{system_dir,SystemDir}, + {user_dir,UserDir} + | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)] + catch + error:{badmatch,{error,enoent}}:S -> + ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]), + {skip, no_key_file_found} + end; false -> {skip, unsupported_algorithm} @@ -271,12 +356,20 @@ setup_user_system_dir(ClientAlg, ServerAlg, Config) -> file(host, dsa) -> "ssh_host_dsa_key"; file(host, ecdsa) -> "ssh_host_ecdsa_key"; file(host, ed25519) -> "ssh_host_ed25519_key"; +file(host, ed448) -> "ssh_host_ed448_key"; file(host, rsa) -> "ssh_host_rsa_key"; file(user, dsa) -> "id_dsa"; file(user, ecdsa) -> "id_ecdsa"; file(user, ed25519) -> "id_ed25519"; +file(user, ed448) -> "id_ed448"; file(user, rsa) -> "id_rsa". +alg(dsa) -> 'ssh-dss'; +alg(ecdsa) -> 'ecdsa-sha2-nistp256'; +alg(ed25519) -> 'ssh-ed25519'; +alg(ed448) -> 'ssh-ed448'; +alg(rsa) -> 'ssh-rsa'. + supported(public_keys, rsa) -> supported(public_key, 'ssh-rsa') orelse supported(public_key, 'rsa-sha2-256') orelse @@ -288,7 +381,7 @@ supported(public_keys, ecdsa) -> supported(public_key, 'ecdsa-sha2-nistp256') supported(public_keys, ed448) -> supported(public_key, 'ssh-ed448'); supported(public_keys, ed25519) -> supported(public_key, 'ssh-ed25519'); supported(Type, Alg) -> - case proplists:get_value(Type,ssh:default_algorithms()) of + case proplists:get_value(Type,ssh_transport:supported_algorithms()) of undefined -> lists:member(Alg, crypto:supports(Type)); L -> diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448 b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448 new file mode 100644 index 0000000000..fc8e79e8f0 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448 @@ -0,0 +1,15 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +A: Key +SomeKey: Very long \ +line \ +spanning more lines \ + +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/id_ed448.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key new file mode 100644 index 0000000000..fc8e79e8f0 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key @@ -0,0 +1,15 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +A: Key +SomeKey: Very long \ +line \ +spanning more lines \ + +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/ssh_pubkey_SUITE_data/new_format/ssh_host_ed448_key.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl new file mode 100644 index 0000000000..b08a5ab1eb --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl @@ -0,0 +1,393 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssh_renegotiate_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("kernel/include/inet.hrl"). +-include_lib("kernel/include/file.hrl"). +-include("ssh_test_lib.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-define(NEWLINE, <<"\r\n">>). + +-define(REKEY_DATA_TMO, 1 * 60000). % Should be multiples of 60000 + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{seconds,90}}]. + +all() -> + [{group, renegotiate} + ]. + +groups() -> + [{renegotiate, [parallel], [rekey0, + rekey1, + rekey2, + rekey3, + rekey4, + rekey_limit_client, + rekey_limit_daemon, + rekey_time_limit_client, + rekey_time_limit_daemon, + norekey_limit_client, + norekey_limit_daemon, + renegotiate1, + renegotiate2]} + ]. + +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + ?CHECK_CRYPTO(begin + ssh:start(), + ct:log("Pub keys setup for: ~p", + [ssh_test_lib:setup_all_user_host_keys(Config)]), + [{preferred_algorithms,ssh_transport:supported_algorithms()} + | Config] + end). + +end_per_suite(_Config) -> + ssh:stop(). + + +init_per_group(_, Config) -> Config. + +end_per_group(_, Config) -> Config. +%%---------------------------------------------------------------------------- +%%% Idle timeout test +rekey0() -> [{timetrap,{seconds,120}}]. +rekey1() -> [{timetrap,{seconds,120}}]. +rekey2() -> [{timetrap,{seconds,120}}]. +rekey3() -> [{timetrap,{seconds,120}}]. +rekey4() -> [{timetrap,{seconds,120}}]. + +rekey0(Config) -> rekey_chk(Config, 0, 0). +rekey1(Config) -> rekey_chk(Config, infinity, 0). +rekey2(Config) -> rekey_chk(Config, {infinity,infinity}, 0). +rekey3(Config) -> rekey_chk(Config, 0, infinity). +rekey4(Config) -> rekey_chk(Config, 0, {infinity,infinity}). + +rekey_chk(Config, RLdaemon, RLclient) -> + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, [{rekey_limit, RLdaemon}]), + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, RLclient}]), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + + %% Make both sides send something: + {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + %% Check rekeying + timer:sleep(?REKEY_DATA_TMO), + ?wait_match(false, Kex1==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10), + + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- +%%% Test rekeying by data volume + +rekey_limit_client() -> [{timetrap,{seconds,500}}]. +rekey_limit_client(Config) -> + Limit = 6000, + UserDir = proplists:get_value(priv_dir, Config), + DataFile = filename:join(UserDir, "rekey.data"), + Data = lists:duplicate(Limit+10,1), + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit}, + {max_random_length_padding,0}]), + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + %% Check that it doesn't rekey without data transfer + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), + + %% Check that datatransfer triggers rekeying + ok = ssh_sftp:write_file(SftpPid, DataFile, Data), + timer:sleep(?REKEY_DATA_TMO), + ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10), + + %% Check that datatransfer continues to trigger rekeying + ok = ssh_sftp:write_file(SftpPid, DataFile, Data), + timer:sleep(?REKEY_DATA_TMO), + ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10), + + %% Check that it doesn't rekey without data transfer + timer:sleep(?REKEY_DATA_TMO), + true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), + + %% Check that it doesn't rekey on a small datatransfer + ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), + + %% Check that it doesn't rekey without data transfer + timer:sleep(?REKEY_DATA_TMO), + true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), + + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + + + +rekey_limit_daemon() -> [{timetrap,{seconds,500}}]. +rekey_limit_daemon(Config) -> + Limit = 6000, + UserDir = proplists:get_value(priv_dir, Config), + DataFile1 = filename:join(UserDir, "rekey1.data"), + DataFile2 = filename:join(UserDir, "rekey2.data"), + file:write_file(DataFile1, lists:duplicate(Limit+10,1)), + file:write_file(DataFile2, "hi\n"), + + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit}, + {max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]), + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + %% Check that it doesn't rekey without data transfer + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + timer:sleep(?REKEY_DATA_TMO), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + + %% Check that datatransfer triggers rekeying + {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1), + timer:sleep(?REKEY_DATA_TMO), + ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10), + + %% Check that datatransfer continues to trigger rekeying + {ok,_} = ssh_sftp:read_file(SftpPid, DataFile1), + timer:sleep(?REKEY_DATA_TMO), + ?wait_match(false, Kex2==(Kex3=ssh_test_lib:get_kex_init(ConnectionRef)), Kex3, 2000, 10), + + %% Check that it doesn't rekey without data transfer + timer:sleep(?REKEY_DATA_TMO), + true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), + + %% Check that it doesn't rekey on a small datatransfer + {ok,_} = ssh_sftp:read_file(SftpPid, DataFile2), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), + + %% Check that it doesn't rekey without data transfer + timer:sleep(?REKEY_DATA_TMO), + true = (Kex3 == ssh_test_lib:get_kex_init(ConnectionRef)), + + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + + +%%-------------------------------------------------------------------- +%% Check that datatransfer in the other direction does not trigger re-keying +norekey_limit_client() -> [{timetrap,{seconds,500}}]. +norekey_limit_client(Config) -> + Limit = 6000, + UserDir = proplists:get_value(priv_dir, Config), + DataFile = filename:join(UserDir, "rekey3.data"), + file:write_file(DataFile, lists:duplicate(Limit+10,1)), + + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, Limit}, + {max_random_length_padding,0}]), + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), + + {ok,_} = ssh_sftp:read_file(SftpPid, DataFile), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), + + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%% Check that datatransfer in the other direction does not trigger re-keying +norekey_limit_daemon() -> [{timetrap,{seconds,500}}]. +norekey_limit_daemon(Config) -> + Limit = 6000, + UserDir = proplists:get_value(priv_dir, Config), + DataFile = filename:join(UserDir, "rekey4.data"), + + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, Limit}, + {max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]), + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), + + ok = ssh_sftp:write_file(SftpPid, DataFile, lists:duplicate(Limit+10,1)), + timer:sleep(?REKEY_DATA_TMO), + true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), + + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- +%%% Test rekeying by time + +rekey_time_limit_client() -> [{timetrap,{seconds,500}}]. +rekey_time_limit_client(Config) -> + Minutes = ?REKEY_DATA_TMO div 60000, + GB = 1024*1000*1000, + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, {Minutes, GB}}, + {max_random_length_padding,0}]), + rekey_time_limit(Pid, ConnectionRef). + +rekey_time_limit_daemon() -> [{timetrap,{seconds,500}}]. +rekey_time_limit_daemon(Config) -> + Minutes = ?REKEY_DATA_TMO div 60000, + GB = 1024*1000*1000, + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[{rekey_limit, {Minutes, GB}}, + {max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{max_random_length_padding,0}]), + rekey_time_limit(Pid, ConnectionRef). + + +rekey_time_limit(Pid, ConnectionRef) -> + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + + timer:sleep(5000), + true = (Kex1 == ssh_test_lib:get_kex_init(ConnectionRef)), + + %% Check that it rekeys when the max time + 30s has passed + timer:sleep(?REKEY_DATA_TMO + 30*1000), + ?wait_match(false, Kex1==(Kex2=ssh_test_lib:get_kex_init(ConnectionRef)), Kex2, 2000, 10), + + %% Check that it does not rekey when nothing is transferred + timer:sleep(?REKEY_DATA_TMO + 30*1000), + ?wait_match(false, Kex2==ssh_test_lib:get_kex_init(ConnectionRef), [], 2000, 10), + + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- + +%%% Test rekeying with simultaneous send request + +renegotiate1(Config) -> + UserDir = proplists:get_value(priv_dir, Config), + DataFile = filename:join(UserDir, "renegotiate1.data"), + + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + + {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort), + + ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]), + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + + {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), + + ok = ssh_sftp:write(SftpPid, Handle, "hi\n"), + + ssh_relay:hold(RelayPid, rx, 20, 1000), + ssh_connection_handler:renegotiate(ConnectionRef), + spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end), + + timer:sleep(2000), + + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), + + false = (Kex2 == Kex1), + + ssh_relay:stop(RelayPid), + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- + +%%% Test rekeying with inflight messages from peer + +renegotiate2(Config) -> + UserDir = proplists:get_value(priv_dir, Config), + DataFile = filename:join(UserDir, "renegotiate2.data"), + + Algs = proplists:get_value(preferred_algorithms, Config), + {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[{max_random_length_padding,0}, + {preferred_algorithms,Algs}]), + + {ok,RelayPid,_,RPort} = ssh_relay:start_link({0,0,0,0}, 0, Host, DPort), + + ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]), + {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + + {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), + + ok = ssh_sftp:write(SftpPid, Handle, "hi\n"), + + ssh_relay:hold(RelayPid, rx, 20, infinity), + spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end), + %% need a small pause here to ensure ssh_sftp:write is executed + ct:sleep(10), + ssh_connection_handler:renegotiate(ConnectionRef), + ssh_relay:release(RelayPid, rx), + + timer:sleep(2000), + + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), + + false = (Kex2 == Kex1), + + ssh_relay:stop(RelayPid), + ssh_sftp:stop_channel(SftpPid), + ssh:close(ConnectionRef), + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa new file mode 100644 index 0000000000..24628e071b --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDIywHurUpOq6kZuMn+XlRzR4hAxF6qwSkuEqkV7iHnLQ0kIwf3 +uAmjFDhuEsQ8653SLxGVvTNp+KFFgDXiLqgM7TPUwDnpbvzEZHPAU+/zPt4sdY2D +txBfJwT2SFlK6HPOxOcxdDuD+/a59sh8hk/YVOU7ZTcBVsVG8Got4UcF5QIVAPGd +CPDQKSTlPiM9OwBB1+9p11k5AoGARLxw4l17mET9cU0uf4Ppe5nsCbODJv44ZrSs +picvypGVLrLcN5KWbm3vjRFCQ5LFunAG3FwLC2Sh0CH6TemoIfRPsRHR7wvpBGdr +c693UlMOis/mcmvNMQAzuQNW9WrxdzsvWR/r5s6NEHWqKUJGXSPi2d+Ijq/mCOmI +hzLzyiACgYEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4 +cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+C +ROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNgCFEjA7wTC +sQCY/I35vb6GUJn9tEdP +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub new file mode 100644 index 0000000000..018ef6f537 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAMjLAe6tSk6rqRm4yf5eVHNHiEDEXqrBKS4SqRXuIectDSQjB/e4CaMUOG4SxDzrndIvEZW9M2n4oUWANeIuqAztM9TAOelu/MRkc8BT7/M+3ix1jYO3EF8nBPZIWUroc87E5zF0O4P79rn2yHyGT9hU5TtlNwFWxUbwai3hRwXlAAAAFQDxnQjw0Ckk5T4jPTsAQdfvaddZOQAAAIBEvHDiXXuYRP1xTS5/g+l7mewJs4Mm/jhmtKymJy/KkZUustw3kpZube+NEUJDksW6cAbcXAsLZKHQIfpN6agh9E+xEdHvC+kEZ2tzr3dSUw6Kz+Zya80xADO5A1b1avF3Oy9ZH+vmzo0QdaopQkZdI+LZ34iOr+YI6YiHMvPKIAAAAIEAsTRcHZqZlamr0PM7jKt2edCpcd8rEFGtWuescebc6Ga5JGSv7Ue4cdYKpAjT10Mns1WYaU9t6ZR+6ARP7DkzzDmS1elwkRu21T+b81PmeZwaEJxgqr+CROQVHgzpqMqEx8ic3c/juxZpRrCAlRCjCWSJLDMobBQvtfyG0qsleNg= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256 new file mode 100644 index 0000000000..4b1eb12eaa --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJfCaBKIIKhjbJl5F8BedqlXOQYDX5ba9Skypllmx/w+oAoGCCqGSM49 +AwEHoUQDQgAE49RbK2xQ/19ji3uDPM7uT4692LbwWF1TiaA9vUuebMGazoW/98br +N9xZu0L1AWwtEjs3kmJDTB7eJEGXnjUAcQ== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub new file mode 100644 index 0000000000..a0147e60fa --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOPUWytsUP9fY4t7gzzO7k+Ovdi28FhdU4mgPb1LnmzBms6Fv/fG6zfcWbtC9QFsLRI7N5JiQ0we3iRBl541AHE= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384 new file mode 100644 index 0000000000..4e8aa40959 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCYXb6OSAZyXRfLXOtMo43za197Hdc/T0YKjgQQjwDt6rlRwqTh7v7S +PV2kXwNGdWigBwYFK4EEACKhZANiAARN2khlJUOOIiwsWHEALwDieeZR96qL4pUd +ci7aeGaczdUK5jOA9D9zmBZtSYTfO8Cr7ekVghDlcWAIJ/BXcswgQwSEQ6wyfaTF +8FYfyr4l3u9IirsnyaFzeIgeoNis8Gw= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub new file mode 100644 index 0000000000..41e722e545 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBE3aSGUlQ44iLCxYcQAvAOJ55lH3qovilR1yLtp4ZpzN1QrmM4D0P3OYFm1JhN87wKvt6RWCEOVxYAgn8FdyzCBDBIRDrDJ9pMXwVh/KviXe70iKuyfJoXN4iB6g2KzwbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521 new file mode 100644 index 0000000000..7196f46e97 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEFMadoz4ckEcClfqXa2tiUuYkJdDfwq+/iFQcpt8ESuEd26IY/vm47Q +9UzbPkO4ou8xkNsQ3WvCRQBBWtn5O2kUU6AHBgUrgQQAI6GBiQOBhgAEAde5BRu5 +01/jS0jRk212xsb2DxPrxNpgp6IMCV8TA4Eps+8bSqHB091nLiBcP422HXYfuCd7 +XDjSs8ihcmhp0hCRASLqZR9EzW9W/SOt876May1Huj5X+WSO6RLe7vPn9vmf7kHf +pip6m7M7qp2qGgQ3q2vRwS2K/O6156ohiOlmuuFs +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub new file mode 100644 index 0000000000..8f059120bc --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ecdsa521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHXuQUbudNf40tI0ZNtdsbG9g8T68TaYKeiDAlfEwOBKbPvG0qhwdPdZy4gXD+Nth12H7gne1w40rPIoXJoadIQkQEi6mUfRM1vVv0jrfO+jGstR7o+V/lkjukS3u7z5/b5n+5B36YqepuzO6qdqhoEN6tr0cEtivzuteeqIYjpZrrhbA== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519 new file mode 100644 index 0000000000..401a3e4a9a --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnwAAAJg3+6xpN/us +aQAAAAtzc2gtZWQyNTUxOQAAACDm9P8/gC0IOKmwHLSvkmEtS2Xx0RRqUDqC6wY6UgDVnw +AAAEBzC/Z2WGJhZ3l3tIBnUc6DCbp+lXY2yc2RRpWQTdf8sub0/z+ALQg4qbActK+SYS1L +ZfHRFGpQOoLrBjpSANWfAAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub new file mode 100644 index 0000000000..a5c03b19c1 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOb0/z+ALQg4qbActK+SYS1LZfHRFGpQOoLrBjpSANWf uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448 b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448 new file mode 100644 index 0000000000..8ecfd710dc --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448 @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA53OqeePNaG/NJmoMbELhskKrAHNhLZ6AQm1WjbpMoseNl/OFh +1xznExpUPqTLX36fHYsAaWRHABQAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtf +fp8diwBpZEcAFAAAAAByzSPST3FCdOdENDI3uTKQ9RH2Ql+Y5kRZ/yA+iYUIP/32 +BQBVOrwOBc0CGEvbicTM1n4YeVEmfrMo3OqeePNaG/NJmoMbELhskKrAHNhLZ6AQ +m1WjbpMoseNl/OFh1xznExpUPqTLX36fHYsAaWRHABQAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub new file mode 100644 index 0000000000..cec0765a5d --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_ed448.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADnc6p5481ob80magxsQuGyQqsAc2EtnoBCbVaNukyix42X84WHXHOcTGlQ+pMtffp8diwBpZEcAFAA= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa new file mode 100644 index 0000000000..2202c2ead8 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAztjiyj2tdfkji0fewWS0kABg0IABgG20NvL1PnHJLr98we7w +W7f3j27EGjW/ApuycsWXXKi0L82q8uDicoHHb3JI2JkT70oi0yG1Dx/zwPN+dkA7 +LBT1J3UK2hJTFPhp855CwY/ss9xpBsd1Fv3zuHifEqNGljeg1PjmQ3pNhxA/M0aZ +cLnfIUdZ5Hr+t+4es3zaWo4tLBKmZu6BkVGQKPGXeMkIAMtJlG24l7qKDRkR5TYA +ZT7P8Vn7hnuFuCNbrJSm686GawBxTQXom23dg9UcWxoHB7UiHFoR6j0bQAX+4R7b +IwculRDcvzrgCu6u06oFILwY7MlsxpX9hGTl1wIDAQABAoIBAFeP6pmQeICrYceR +OhQGLIWVE2bP+VLDnflw6i5v/qlieE6kdm1tOEgorK0nuV9CR81cJdIcvIJL/yTn +3BR7KdDcwUenrY+rg4h7CWmIrigtK4ilciccDBeS7XAZN8B11GxDv6Cu65XMJU2w +W7nK8URTE4vRQI1QqS3e26MPAAi/LVOt3ZPI6zg/GHEwnq0IVSQAOndLBr/IWZk5 +SANrkfwX8WS7/UxZgDptT9dyUQ5Pnj5mieTlIvBwyczdhZ7RDa8HdCSHW3xF83V1 +A0pkn6+TRojumYyr4RrPQj6htE64Hgx9w1Dv/UINjPXl5mGlbxQHMWGzlqD/qpyI +wg7RakECgYEA+9ARZpHfEFz+EEFi8l9J+BtJDo00WaKCOZHh5UJ8W+NreqSd8nSx +5u6wYwMJjRX2Hwv+FBEhxGbo1+ff6p++cYmiSlDtN2XRCDkBWvvGlxu55BDULrhx +f8lqaV3XGmOy2rQusp8hiHmkmPJCSVj3oJqQnbqJ2zahXAx1rTPwHqECgYEA0kln +4h+ZkZ+aldOMGF0d0txTcTqZvsSVKiFTSD9of/fiSDqb6xtLT2+ys6FZoFL9lyK8 +gtqH642CDQ+3WT6Nmn4kMF5HNVpEuCeRDeRhiquWeKaAQDyvZ5ym1+Cn3GhsO7Di +d2LJKV5hOoN77loVY5nwnUVIJ0h+WLf0T7DTCXcCgYEAiNT7X50MdTvS4splFgcp +jqRlAn9AXySrVtUqxwVlxhjCIpapLUK0GSTCvEq+OeghIaXGnujgTHUPOaNKTZgY +SGHdyjxHar7s42b2kZYWx63NSVzLr8eSBTpRlIflhvV+DtGyPmWyNxLCmkmqM2kg +xii3RL5EgtYgwIAUwdVjOYECgYBRPlsMWfkS8f7fc+PkZdVn6gey71kHAxw+MrHi +b90H09Vw4nPq2Zi3EAiSrfvanTWsdpcuVw+8See89B16NViwH5wLs+D/E+kI3QCF +xX6J/NEdu/ZA2zFJbpRnQzyXQyDNzwEv7tKZUQVvfe0boWIyIP99Q48k3jUyQZ/6 +Se6+8QKBgQCXl8H2K3CsZxoujKLb2qoEOPbxJQ2hxoMTS5XuQECReIVsNuptWrur +DF8WJi/B6AqwRX1P3l56RNwqB1yDBqv0QVLpU7vU/FmWqLWTn0r3AvM74qftvfAE +oa31wcYoCqPJoKgCG7TThLhNt2v5hL7sVgZNO0ueAiHhJbFLaf7ceg== +-----END RSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub new file mode 100644 index 0000000000..b4084d320a --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDO2OLKPa11+SOLR97BZLSQAGDQgAGAbbQ28vU+cckuv3zB7vBbt/ePbsQaNb8Cm7JyxZdcqLQvzary4OJygcdvckjYmRPvSiLTIbUPH/PA8352QDssFPUndQraElMU+GnznkLBj+yz3GkGx3UW/fO4eJ8So0aWN6DU+OZDek2HED8zRplwud8hR1nkev637h6zfNpaji0sEqZm7oGRUZAo8Zd4yQgAy0mUbbiXuooNGRHlNgBlPs/xWfuGe4W4I1uslKbrzoZrAHFNBeibbd2D1RxbGgcHtSIcWhHqPRtABf7hHtsjBy6VENy/OuAK7q7TqgUgvBjsyWzGlf2EZOXX uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..51ab6fbd88 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,13 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK +wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q +diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA +l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X +skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF +Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP +ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah +/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U +ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W +Lv62jKcdskxNyz2NQoBx +-----END DSA PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..4dbb1305b0 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1,11 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j +YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2 +KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU +aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI +fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT +MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh +DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48 +wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2 +/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg== +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256 new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key256.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384 new file mode 100644 index 0000000000..fb1a862ded --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDArxbDfh3p1okrD9wQw6jJ4d4DdlBPD5GqXE8bIeRJiK41Sh40LgvPw +mkqEDSXK++CgBwYFK4EEACKhZANiAAScl43Ih2lWTDKrSox5ve5uiTXil4smsup3 +CfS1XPjKxgBAmlfBim8izbdrT0BFdQzz2joduNMtpt61wO4rGs6jm0UP7Kim9PC7 +Hneb/99fIYopdMH5NMnk60zGO1uZ2vc= +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub new file mode 100644 index 0000000000..428d5fb7d7 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key384.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJyXjciHaVZMMqtKjHm97m6JNeKXiyay6ncJ9LVc+MrGAECaV8GKbyLNt2tPQEV1DPPaOh240y2m3rXA7isazqObRQ/sqKb08Lsed5v/318hiil0wfk0yeTrTMY7W5na9w== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521 new file mode 100644 index 0000000000..3e51ec2ecd --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB8O1BFkl2HQjQLRLonEZ97da/h39DMa9/0/hvPZWAI8gUPEQcHxRx +U7b09p3Zh+EBbMFq8+1ae9ds+ZTxE4WFSvKgBwYFK4EEACOhgYkDgYYABAAlWVjq +Bzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/ +vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5 +ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub new file mode 100644 index 0000000000..017a29f4da --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ecdsa_key521.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAlWVjqBzg7Wt4gE6UNb1lRE2cnlmH2L/A5uo6qZRx5lPnSKOxEhxSb/Oay1+9d6KRdrh6/vlhd9SHDBhLcAPDvWgBnJIEj92Q3pXX4JtoitL0yl+SvvU+vUh966mzHShHzj8p5ccOgPkPNoA70yrpGzkIhPezpZOQdCaOXj/jFqNCTDg== uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key new file mode 100644 index 0000000000..13a8fcf491 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD +uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ +AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W +TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..156ef4045c --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key new file mode 100644 index 0000000000..31a7e4e8c3 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key @@ -0,0 +1,10 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAAAlz +c2gtZWQ0NDgAAAA5X9dEm1m0Yf0s54fsYWrUah2hNCSFpw4fig6nXYDpZ3jt8SR2 +m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAA0AAAEREAABERAAAACXNzaC1lZDQ0OAAA +ADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHl +D2zR+hq+r+glYYAAAABybIKlYsuAjRDWMr6JyFE+v2ySnzTd+oyfY8mWDvbjSKNS +jIo/zC8ETjmj/FuUSS+PAy51SaIAmPlbX9dEm1m0Yf0s54fsYWrUah2hNCSFpw4f +ig6nXYDpZ3jt8SR2m0bHBhvWeD3x5Q9s0foavq/oJWGAAAAAAAECAwQ= +-----END OPENSSH PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub new file mode 100644 index 0000000000..8c390dcb58 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_ed448_key.pub @@ -0,0 +1 @@ +ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key new file mode 100644 index 0000000000..79968bdd7d --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key @@ -0,0 +1,16 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337 +zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB +6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB +AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW +NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++ +udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW +WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt +n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5 +sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY ++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt +64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB +m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT +tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR +-----END RSA PRIVATE KEY----- + diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub new file mode 100644 index 0000000000..75d2025c71 --- /dev/null +++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub @@ -0,0 +1,5 @@ +---- BEGIN SSH2 PUBLIC KEY ---- +AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8 +semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW +RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q== +---- END SSH2 PUBLIC KEY ---- diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl index 852801d013..4edc28aa96 100644 --- a/lib/ssh/test/ssh_sftpd_SUITE.erl +++ b/lib/ssh/test/ssh_sftpd_SUITE.erl @@ -80,9 +80,9 @@ groups() -> init_per_suite(Config) -> ?CHECK_CRYPTO( begin - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_dsa(DataDir, PrivDir), + ssh:start(), + ct:log("Pub keys setup for: ~p", + [ssh_test_lib:setup_all_user_host_keys(Config)]), %% to make sure we don't use public-key-auth %% this should be tested by other test suites UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), @@ -91,8 +91,6 @@ init_per_suite(Config) -> end). end_per_suite(Config) -> - SysDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_dsa(SysDir), UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), file:del_dir(UserDir), ssh:stop(). diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl index dbf79e3537..c4fb21f483 100644 --- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl +++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl @@ -64,15 +64,12 @@ init_per_suite(Config) -> {ok, FileInfo} = file:read_file_info(FileName), ok = file:write_file_info(FileName, FileInfo#file_info{mode = 8#400}), + ct:log("Pub keys setup for: ~p", + [ssh_test_lib:setup_all_user_host_keys(Config)]), Config end). -end_per_suite(Config) -> - UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), - file:del_dir(UserDir), - SysDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_rsa(SysDir), - ssh_test_lib:clean_dsa(SysDir), +end_per_suite(_Config) -> ok. %%-------------------------------------------------------------------- @@ -83,10 +80,10 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. %%-------------------------------------------------------------------- - init_per_testcase(TestCase, Config) -> ssh:start(), - DataDir = proplists:get_value(data_dir, Config), + UserDir = PrivDir = proplists:get_value(priv_dir, Config), + SysDir = filename:join(PrivDir,"system"), Options = case atom_to_list(TestCase) of @@ -110,9 +107,10 @@ init_per_testcase(TestCase, Config) -> [] end, - {Sftpd, Host, Port} = ssh_test_lib:daemon([{preferred_algorithms, ssh_transport:supported_algorithms()}, - {system_dir, DataDir}, - {user_dir, DataDir}, + {Sftpd, Host, Port} = ssh_test_lib:daemon([{preferred_algorithms, + ssh_transport:supported_algorithms()}, + {system_dir, SysDir}, + {user_dir, UserDir}, {user_passwords, [{?USER,?PASSWD}]} | Options]), @@ -120,7 +118,7 @@ init_per_testcase(TestCase, Config) -> ssh_sftp:start_channel(Host, Port, [{silently_accept_hosts, true}, {preferred_algorithms, ssh_transport:supported_algorithms()}, - {user_dir, DataDir}, + {user_dir, UserDir}, {timeout, 30000}]), TmpConfig = lists:keydelete(sftp, 1, Config), NewConfig = lists:keydelete(sftpd, 1, TmpConfig), @@ -178,7 +176,8 @@ quit(Config) when is_list(Config) -> timer:sleep(5000), {ok, NewSftp, _Conn} = ssh_sftp:start_channel(Host, Port, [{silently_accept_hosts, true}, - {preferred_algorithms, ssh_transport:supported_algorithms()}, + {preferred_algorithms, + ssh_transport:supported_algorithms()}, {user_dir, UserDir}, {user, ?USER}, {password, ?PASSWD}]), diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index ebf60bc1d0..f16c0d6278 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -294,25 +294,37 @@ rcv_lingering(Timeout) -> receive_exec_result([]) -> expected; receive_exec_result(Msgs) when is_list(Msgs) -> - ct:log("Expect data! ~p", [Msgs]), + ct:log("~p:~p Expect data! ~p", [?MODULE,?FUNCTION_NAME,Msgs]), receive Msg -> - case lists:member(Msg, Msgs) of + case lists:member(Msg, Msgs) + orelse lists:member({optional,Msg}, Msgs) + of true -> - ct:log("Collected data ~p", [Msg]), - receive_exec_result(Msgs--[Msg]); + ct:log("~p:~p Collected data ~p", [?MODULE,?FUNCTION_NAME,Msg]), + receive_exec_result(Msgs--[Msg,{optional,Msg}]); false -> case Msg of {ssh_cm,_,{data,_,1, Data}} -> - ct:log("StdErr: ~p~n", [Data]), + ct:log("~p:~p StdErr: ~p~n", [?MODULE,?FUNCTION_NAME,Data]), receive_exec_result(Msgs); Other -> - ct:log("Other ~p", [Other]), + ct:log("~p:~p Other ~p", [?MODULE,?FUNCTION_NAME,Other]), {unexpected_msg, Other} end end after - 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) + 30000 -> + case lists:all(fun(M) -> + is_tuple(M) andalso (element(1,M) == optional) + end, Msgs) + of + false -> + ct:fail("timeout ~p:~p",[?MODULE,?FUNCTION_NAME]); + true -> + ct:log("~p:~p Only optional messages expected!~n ~p", [?MODULE,?FUNCTION_NAME,Msgs]), + expected + end end; receive_exec_result(Msg) -> receive_exec_result([Msg]). @@ -325,26 +337,11 @@ receive_exec_result_or_fail(Msg) -> end. receive_exec_end(ConnectionRef, ChannelId) -> - Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}}, - ExitStatus = {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}}, - Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}}, - case receive_exec_result(ExitStatus) of - {unexpected_msg, Eof} -> %% Open ssh seems to not allways send these messages - %% in the same order! - ct:log("2: Collected data ~p", [Eof]), - case receive_exec_result(ExitStatus) of - expected -> - expected = receive_exec_result(Closed); - {unexpected_msg, Closed} -> - ct:log("3: Collected data ~p", [Closed]) - end; - expected -> - ct:log("4: Collected data ~p", [ExitStatus]), - expected = receive_exec_result(Eof), - expected = receive_exec_result(Closed); - Other -> - ct:fail({unexpected_msg, Other}) - end. + receive_exec_result( + [{ssh_cm, ConnectionRef, {eof, ChannelId}}, + {optional, {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}}}, + {ssh_cm, ConnectionRef, {closed, ChannelId}} + ]). receive_exec_result(Data, ConnectionRef, ChannelId) -> Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}}, @@ -354,21 +351,6 @@ receive_exec_result(Data, ConnectionRef, ChannelId) -> expected = receive_exec_result(Closed). -setup_ssh_auth_keys(RSAFile, DSAFile, Dir) -> - Entries = ssh_file_entry(RSAFile) ++ ssh_file_entry(DSAFile), - AuthKeys = public_key:ssh_encode(Entries , auth_keys), - AuthKeysFile = filename:join(Dir, "authorized_keys"), - file:write_file(AuthKeysFile, AuthKeys). - -ssh_file_entry(PubFile) -> - case file:read_file(PubFile) of - {ok, Ssh} -> - [{Key, _}] = public_key:ssh_decode(Ssh, public_key), - [{Key, [{comment, "Test"}]}]; - _ -> - [] - end. - failfun(_User, {authmethod,none}) -> ok; failfun(User, Reason) -> @@ -378,236 +360,31 @@ hostname() -> {ok,Host} = inet:gethostname(), Host. -known_hosts(BR) -> - KnownHosts = ssh_file:file_name(user, "known_hosts", []), - B = KnownHosts ++ "xxx", - case BR of - backup -> - file:rename(KnownHosts, B); - restore -> - file:delete(KnownHosts), - file:rename(B, KnownHosts) - end. - -setup_dsa(DataDir, UserDir) -> - file:copy(filename:join(DataDir, "id_dsa"), filename:join(UserDir, "id_dsa")), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")), - file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")), -ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), - setup_dsa_known_host(DataDir, UserDir), - setup_dsa_auth_keys(DataDir, UserDir). - -setup_rsa(DataDir, UserDir) -> - file:copy(filename:join(DataDir, "id_rsa"), filename:join(UserDir, "id_rsa")), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")), - file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")), -ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), - setup_rsa_known_host(DataDir, UserDir), - setup_rsa_auth_keys(DataDir, UserDir). - -setup_ecdsa(Size, DataDir, UserDir) -> - file:copy(filename:join(DataDir, "id_ecdsa"++Size), filename:join(UserDir, "id_ecdsa")), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")), - file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")), -ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), - setup_ecdsa_known_host(Size, System, UserDir), - setup_ecdsa_auth_keys(Size, DataDir, UserDir). - -setup_eddsa(Alg, DataDir, UserDir) -> - {IdPriv, _IdPub, HostPriv, HostPub} = - case Alg of - ed25519 -> {"id_ed25519", "id_ed25519.pub", "ssh_host_ed25519_key", "ssh_host_ed25519_key.pub"}; - ed448 -> {"id_ed448", "id_ed448.pub", "ssh_host_ed448_key", "ssh_host_ed448_key.pub"} - end, - file:copy(filename:join(DataDir, IdPriv), filename:join(UserDir, IdPriv)), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, HostPriv), filename:join(System, HostPriv)), - file:copy(filename:join(DataDir, HostPub), filename:join(System, HostPub)), -ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file:list_dir(DataDir), System, file:list_dir(System), UserDir, file:list_dir(UserDir)]), - setup_eddsa_known_host(HostPub, DataDir, UserDir), - setup_eddsa_auth_keys(Alg, DataDir, UserDir). - -clean_dsa(UserDir) -> - del_dirs(filename:join(UserDir, "system")), - file:delete(filename:join(UserDir,"id_dsa")), - file:delete(filename:join(UserDir,"known_hosts")), - file:delete(filename:join(UserDir,"authorized_keys")). - -clean_rsa(UserDir) -> - del_dirs(filename:join(UserDir, "system")), - file:delete(filename:join(UserDir,"id_rsa")), - file:delete(filename:join(UserDir,"known_hosts")), - file:delete(filename:join(UserDir,"authorized_keys")). - -setup_dsa_pass_phrase(DataDir, UserDir, Phrase) -> - try - {ok, KeyBin} = file:read_file(filename:join(DataDir, "id_dsa")), - setup_pass_phrase(KeyBin, filename:join(UserDir, "id_dsa"), Phrase), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, "ssh_host_dsa_key"), filename:join(System, "ssh_host_dsa_key")), - file:copy(filename:join(DataDir, "ssh_host_dsa_key.pub"), filename:join(System, "ssh_host_dsa_key.pub")), - setup_dsa_known_host(DataDir, UserDir), - setup_dsa_auth_keys(DataDir, UserDir) - of - _ -> true - catch - _:_ -> false - end. +del_dirs(Dir) -> + del_dir_contents(Dir), + file:del_dir(Dir), + ok. -setup_rsa_pass_phrase(DataDir, UserDir, Phrase) -> - try - {ok, KeyBin} = file:read_file(filename:join(DataDir, "id_rsa")), - setup_pass_phrase(KeyBin, filename:join(UserDir, "id_rsa"), Phrase), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, "ssh_host_rsa_key"), filename:join(System, "ssh_host_rsa_key")), - file:copy(filename:join(DataDir, "ssh_host_rsa_key.pub"), filename:join(System, "ssh_host_rsa_key.pub")), - setup_rsa_known_host(DataDir, UserDir), - setup_rsa_auth_keys(DataDir, UserDir) - of - _ -> true - catch - _:_ -> false - end. -setup_ecdsa_pass_phrase(Size, DataDir, UserDir, Phrase) -> - try - {ok, KeyBin} = - case file:read_file(F=filename:join(DataDir, "id_ecdsa"++Size)) of - {error,E} -> - ct:log("Failed (~p) to read ~p~nFiles: ~p", [E,F,file:list_dir(DataDir)]), - file:read_file(filename:join(DataDir, "id_ecdsa")); - Other -> - Other - end, - setup_pass_phrase(KeyBin, filename:join(UserDir, "id_ecdsa"), Phrase), - System = filename:join(UserDir, "system"), - file:make_dir(System), - file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size), filename:join(System, "ssh_host_ecdsa_key")), - file:copy(filename:join(DataDir, "ssh_host_ecdsa_key"++Size++".pub"), filename:join(System, "ssh_host_ecdsa_key.pub")), - setup_ecdsa_known_host(Size, System, UserDir), - setup_ecdsa_auth_keys(Size, DataDir, UserDir) - of - _ -> true - catch - _:_ -> false +del_dir_contents(Dir) -> + case file:list_dir(Dir) of + {ok, Files} -> + do_del_files(Dir, Files); + _ -> + ok end. -setup_pass_phrase(KeyBin, OutFile, Phrase) -> - [{KeyType, _,_} = Entry0] = public_key:pem_decode(KeyBin), - Key = public_key:pem_entry_decode(Entry0), - Salt = crypto:strong_rand_bytes(8), - Entry = public_key:pem_entry_encode(KeyType, Key, - {{"DES-CBC", Salt}, Phrase}), - Pem = public_key:pem_encode([Entry]), - file:write_file(OutFile, Pem). - -setup_dsa_known_host(SystemDir, UserDir) -> - {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_dsa_key.pub")), - [{Key, _}] = public_key:ssh_decode(SshBin, public_key), - setup_known_hosts(Key, UserDir). - -setup_rsa_known_host(SystemDir, UserDir) -> - {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_rsa_key.pub")), - [{Key, _}] = public_key:ssh_decode(SshBin, public_key), - setup_known_hosts(Key, UserDir). - -setup_ecdsa_known_host(_Size, SystemDir, UserDir) -> - {ok, SshBin} = file:read_file(filename:join(SystemDir, "ssh_host_ecdsa_key.pub")), - [{Key, _}] = public_key:ssh_decode(SshBin, public_key), - setup_known_hosts(Key, UserDir). - -setup_eddsa_known_host(HostPub, SystemDir, UserDir) -> - {ok, SshBin} = file:read_file(filename:join(SystemDir, HostPub)), - [{Key, _}] = public_key:ssh_decode(SshBin, public_key), - setup_known_hosts(Key, UserDir). - -setup_known_hosts(Key, UserDir) -> - {ok, Hostname} = inet:gethostname(), - {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet), - IP = lists:concat([A, ".", B, ".", C, ".", D]), - HostNames = [{hostnames,[Hostname, IP]}], - KnownHosts = [{Key, HostNames}], - KnownHostsEnc = public_key:ssh_encode(KnownHosts, known_hosts), - KHFile = filename:join(UserDir, "known_hosts"), - file:write_file(KHFile, KnownHostsEnc). - -setup_dsa_auth_keys(Dir, UserDir) -> - {ok, Pem} = file:read_file(filename:join(Dir, "id_dsa")), - DSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))), - PKey = DSA#'DSAPrivateKey'.y, - P = DSA#'DSAPrivateKey'.p, - Q = DSA#'DSAPrivateKey'.q, - G = DSA#'DSAPrivateKey'.g, - Dss = #'Dss-Parms'{p=P, q=Q, g=G}, - setup_auth_keys([{{PKey, Dss}, [{comment, "Test"}]}], UserDir). - -setup_rsa_auth_keys(Dir, UserDir) -> - {ok, Pem} = file:read_file(filename:join(Dir, "id_rsa")), - RSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))), - #'RSAPrivateKey'{publicExponent = E, modulus = N} = RSA, - PKey = #'RSAPublicKey'{publicExponent = E, modulus = N}, - setup_auth_keys([{ PKey, [{comment, "Test"}]}], UserDir). - -setup_ecdsa_auth_keys(Size, Dir, UserDir) -> - {ok, Pem} = - case file:read_file(F=filename:join(Dir, "id_ecdsa"++Size)) of - {error,E} -> - ct:log("Failed (~p) to read ~p~nFiles: ~p", [E,F,file:list_dir(Dir)]), - file:read_file(filename:join(Dir, "id_ecdsa")); - Other -> - Other - end, - ECDSA = public_key:pem_entry_decode(hd(public_key:pem_decode(Pem))), - #'ECPrivateKey'{publicKey = Q, - parameters = Param = {namedCurve,_Id0}} = ECDSA, - PKey = #'ECPoint'{point = Q}, - setup_auth_keys([{ {PKey,Param}, [{comment, "Test"}]}], UserDir). - -setup_eddsa_auth_keys(Alg, Dir, UserDir) -> - SshAlg = case Alg of - ed25519 -> 'ssh-ed25519'; - ed448 -> 'ssh-ed448' - end, - {ok, {ed_pri,Alg,Pub,_}} = ssh_file:user_key(SshAlg, [{user_dir,Dir}]), - setup_auth_keys([{{ed_pub,Alg,Pub}, [{comment, "Test"}]}], UserDir). - -setup_auth_keys(Keys, Dir) -> - AuthKeys = public_key:ssh_encode(Keys, auth_keys), - AuthKeysFile = filename:join(Dir, "authorized_keys"), - ok = file:write_file(AuthKeysFile, AuthKeys), - AuthKeys. - -write_auth_keys(Keys, Dir) -> - AuthKeysFile = filename:join(Dir, "authorized_keys"), - file:write_file(AuthKeysFile, Keys). +do_del_files(Dir, Files) -> + lists:foreach(fun(File) -> + FullPath = filename:join(Dir,File), + case filelib:is_dir(FullPath) of + true -> + del_dirs(FullPath); + false -> + file:delete(FullPath) + end + end, Files). -del_dirs(Dir) -> - case file:list_dir(Dir) of - {ok, []} -> - file:del_dir(Dir); - {ok, Files} -> - lists:foreach(fun(File) -> - FullPath = filename:join(Dir,File), - case filelib:is_dir(FullPath) of - true -> - del_dirs(FullPath), - file:del_dir(FullPath); - false -> - file:delete(FullPath) - end - end, Files); - _ -> - ok - end. openssh_sanity_check(Config) -> ssh:start(), @@ -625,34 +402,6 @@ openssh_sanity_check(Config) -> {skip, Str} end. -openssh_supports(ClientOrServer, Tag, Alg) when ClientOrServer == sshc ; - ClientOrServer == sshd -> - SSH_algos = ssh_test_lib:default_algorithms(ClientOrServer), - L = proplists:get_value(Tag, SSH_algos, []), - lists:member(Alg, L) orelse - lists:member(Alg, proplists:get_value(client2server, L, [])) orelse - lists:member(Alg, proplists:get_value(server2client, L, [])). - -%%-------------------------------------------------------------------- -%% Check if we have a "newer" ssh client that supports these test cases - -ssh_client_supports_Q() -> - 0 == check_ssh_client_support2( - ?MODULE:open_port({spawn, "ssh -Q cipher"}) - ). - -check_ssh_client_support2(P) -> - receive - {P, {data, _A}} -> - check_ssh_client_support2(P); - {P, {exit_status, E}} -> - ct:log("~p:~p exit_status:~n~p",[?MODULE,?LINE,E]), - E - after 5000 -> - ct:log("Openssh command timed out ~n"), - -1 - end. - %%%-------------------------------------------------------------------- %%% Probe a server or a client about algorithm support @@ -1189,3 +938,132 @@ mk_dir_path(DirPath) -> %%ct:log("~p:~p return Other ~p ~ts", [?MODULE,?LINE,Other,DirPath]), Other end. + +%%%---------------------------------------------------------------- +%%% New + +setup_all_user_host_keys(Config) -> + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + setup_all_user_host_keys(DataDir, PrivDir). + +setup_all_user_host_keys(DataDir, PrivDir) -> + setup_all_user_host_keys(DataDir, PrivDir, filename:join(PrivDir,"system")). + +setup_all_user_host_keys(DataDir, UserDir, SysDir) -> + lists:foldl(fun(Alg, OkAlgs) -> + try + ok = ssh_test_lib:setup_user_key(Alg, DataDir, UserDir), + ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir) + of + ok -> [Alg|OkAlgs] + catch + error:{badmatch, {error,enoent}} -> + OkAlgs; + C:E:S -> + ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p", + [?MODULE,?FUNCTION_NAME,Alg,C,E,S]), + OkAlgs + end + end, [], ssh_transport:supported_algorithms(public_key)). + + +setup_all_host_keys(Config) -> + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + setup_all_host_keys(DataDir, filename:join(PrivDir,"system")). + +setup_all_host_keys(DataDir, SysDir) -> + lists:foldl(fun(Alg, OkAlgs) -> + try + ok = ssh_test_lib:setup_host_key(Alg, DataDir, SysDir) + of + ok -> [Alg|OkAlgs] + catch + error:{badmatch, {error,enoent}} -> + OkAlgs; + C:E:S -> + ct:log("Exception in ~p:~p for alg ~p: ~p:~p~n~p", + [?MODULE,?FUNCTION_NAME,Alg,C,E,S]), + OkAlgs + end + end, [], ssh_transport:supported_algorithms(public_key)). + +setup_user_key(SshAlg, DataDir, UserDir) -> + file:make_dir(UserDir), + %% Copy private user key to user's dir + {ok,_} = file:copy(filename:join(DataDir, file_base_name(user_src,SshAlg)), + filename:join(UserDir, file_base_name(user,SshAlg))), + %% Setup authorized_keys in user's dir + {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub")), + ok = file:write_file(filename:join(UserDir, "authorized_keys"), + io_lib:format("~n~s~n",[Pub]), + [append]), + ?ct_log_show_file( filename:join(DataDir, file_base_name(user_src,SshAlg)++".pub") ), + ?ct_log_show_file( filename:join(UserDir, "authorized_keys") ), + ok. + +setup_host_key_create_dir(SshAlg, DataDir, BaseDir) -> + SysDir = filename:join(BaseDir,"system"), + ct:log("~p:~p SshAlg=~p~nDataDir = ~p~nBaseDir = ~p~nSysDir = ~p",[?MODULE,?LINE,SshAlg, DataDir, BaseDir,SysDir]), + file:make_dir(SysDir), + setup_host_key(SshAlg, DataDir, SysDir), + SysDir. + +setup_host_key(SshAlg, DataDir, SysDir) -> + mk_dir_path(SysDir), + %% Copy private host key to system's dir + {ok,_} = file:copy(filename:join(DataDir, file_base_name(system_src,SshAlg)), + filename:join(SysDir, file_base_name(system,SshAlg))), + ?ct_log_show_file( filename:join(SysDir, file_base_name(system,SshAlg)) ), + ok. + +setup_known_host(SshAlg, DataDir, UserDir) -> + {ok,Pub} = file:read_file(filename:join(DataDir, file_base_name(system_src,SshAlg)++".pub")), + S = lists:join(" ", lists:reverse(tl(lists:reverse(string:tokens(binary_to_list(Pub), " "))))), + ok = file:write_file(filename:join(UserDir, "known_hosts"), + io_lib:format("~p~n",[S])), + ?ct_log_show_file( filename:join(UserDir, "known_hosts") ), + ok. + + +get_addr_str() -> + {ok, Hostname} = inet:gethostname(), + {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet), + IP = lists:concat([A, ".", B, ".", C, ".", D]), + lists:concat([Hostname,",",IP]). + + +file_base_name(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa"; +file_base_name(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa"; +file_base_name(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa"; +file_base_name(user, 'rsa-sha2-256' ) -> "id_rsa"; +file_base_name(user, 'rsa-sha2-384' ) -> "id_rsa"; +file_base_name(user, 'rsa-sha2-512' ) -> "id_rsa"; +file_base_name(user, 'ssh-dss' ) -> "id_dsa"; +file_base_name(user, 'ssh-ed25519' ) -> "id_ed25519"; +file_base_name(user, 'ssh-ed448' ) -> "id_ed448"; +file_base_name(user, 'ssh-rsa' ) -> "id_rsa"; + +file_base_name(user_src, 'ecdsa-sha2-nistp256') -> "id_ecdsa256"; +file_base_name(user_src, 'ecdsa-sha2-nistp384') -> "id_ecdsa384"; +file_base_name(user_src, 'ecdsa-sha2-nistp521') -> "id_ecdsa521"; +file_base_name(user_src, Alg) -> file_base_name(user, Alg); + +file_base_name(system, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; +file_base_name(system, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; +file_base_name(system, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key"; +file_base_name(system, 'rsa-sha2-256' ) -> "ssh_host_rsa_key"; +file_base_name(system, 'rsa-sha2-384' ) -> "ssh_host_rsa_key"; +file_base_name(system, 'rsa-sha2-512' ) -> "ssh_host_rsa_key"; +file_base_name(system, 'ssh-dss' ) -> "ssh_host_dsa_key"; +file_base_name(system, 'ssh-ed25519' ) -> "ssh_host_ed25519_key"; +file_base_name(system, 'ssh-ed448' ) -> "ssh_host_ed448_key"; +file_base_name(system, 'ssh-rsa' ) -> "ssh_host_rsa_key"; + +file_base_name(system_src, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key256"; +file_base_name(system_src, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key384"; +file_base_name(system_src, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key521"; +file_base_name(system_src, Alg) -> file_base_name(system, Alg). + +%%%---------------------------------------------------------------- diff --git a/lib/ssh/test/ssh_test_lib.hrl b/lib/ssh/test/ssh_test_lib.hrl index b9af2ecb5d..098ee79044 100644 --- a/lib/ssh/test/ssh_test_lib.hrl +++ b/lib/ssh/test/ssh_test_lib.hrl @@ -49,3 +49,13 @@ -define(wait_match(Pattern, FunctionCall), ?wait_match(Pattern, FunctionCall, ok) ). +%%------------------------------------------------------------------------- +%% Write file into log +%%------------------------------------------------------------------------- + +-define(ct_log_show_file(File), + (fun(File__) -> + {ok,Contents__} = file:read_file(File__), + ct:log("~p:~p Show file~n~s =~n~s~n", + [?MODULE,?LINE,File__, Contents__]) + end)(File)). diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index 5aa3824702..426efbb5e3 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -81,10 +81,6 @@ end_per_suite(_Config) -> ok. init_per_group(erlang_server, Config) -> - DataDir = proplists:get_value(data_dir, Config), - UserDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:setup_dsa_known_host(DataDir, UserDir), - ssh_test_lib:setup_rsa_known_host(DataDir, UserDir), Config; init_per_group(G, Config) when G==tunnel_distro_server ; G==tunnel_distro_client -> @@ -102,11 +98,6 @@ init_per_group(erlang_client, Config) -> init_per_group(_, Config) -> Config. -end_per_group(erlang_server, Config) -> - UserDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_dsa(UserDir), - ssh_test_lib:clean_rsa(UserDir), - Config; end_per_group(_, Config) -> Config. @@ -166,10 +157,16 @@ exec_with_io_in_sshc(Config) when is_list(Config) -> {failfun, fun ssh_test_lib:failfun/2}]), ct:sleep(500), + PrivDir = proplists:get_value(priv_dir, Config), + KnownHosts = filename:join(PrivDir, "known_hosts"), ExecStr = "\"io:read('% ').\"", Cmd = "echo howdy. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port, - " -o StrictHostKeyChecking=no" - " -x", % Disable X forwarding + [" -o UserKnownHostsFile=", KnownHosts, + " -o CheckHostIP=no" + " -o StrictHostKeyChecking=no" + " -q" + " -x" % Disable X forwarding + ], ExecStr), ct:pal("Cmd = ~p~n",[Cmd]), case os:cmd(Cmd) of @@ -194,9 +191,15 @@ exec_direct_with_io_in_sshc(Config) when is_list(Config) -> ]), ct:sleep(500), + PrivDir = proplists:get_value(priv_dir, Config), + KnownHosts = filename:join(PrivDir, "known_hosts"), Cmd = "echo ciao. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port, - " -o StrictHostKeyChecking=no" - " -x", % Disable X forwarding + [" -o UserKnownHostsFile=", KnownHosts, + " -o CheckHostIP=no" + " -o StrictHostKeyChecking=no" + " -q" + " -x" % Disable X forwarding + ], "'? '"), ct:pal("Cmd = ~p~n",[Cmd]), case os:cmd(Cmd) of @@ -214,7 +217,6 @@ erlang_server_openssh_client_renegotiate(Config) -> _PubKeyAlg = ssh_rsa, SystemDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), - KnownHosts = filename:join(PrivDir, "known_hosts"), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, {failfun, fun ssh_test_lib:failfun/2}]), @@ -225,9 +227,13 @@ erlang_server_openssh_client_renegotiate(Config) -> Data = lists:duplicate(trunc(1.1*RenegLimitK*1024), $a), ok = file:write_file(DataFile, Data), + KnownHosts = filename:join(PrivDir, "known_hosts"), Cmd = ssh_test_lib:open_sshc_cmd(Host, Port, [" -o UserKnownHostsFile=", KnownHosts, - " -o StrictHostKeyChecking=no", + " -o CheckHostIP=no" + " -o StrictHostKeyChecking=no" + " -q" + " -x", " -o RekeyLimit=",integer_to_list(RenegLimitK),"K"]), @@ -268,7 +274,6 @@ erlang_server_openssh_client_renegotiate(Config) -> tunnel_out_non_erlclient_erlserver(Config) -> SystemDir = proplists:get_value(data_dir, Config), PrivDir = proplists:get_value(priv_dir, Config), - KnownHosts = filename:join(PrivDir, "known_hosts"), {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true}, {system_dir, SystemDir}, @@ -278,9 +283,13 @@ tunnel_out_non_erlclient_erlserver(Config) -> ListenHost = {127,0,0,1}, ListenPort = 2345, + KnownHosts = filename:join(PrivDir, "known_hosts"), Cmd = ssh_test_lib:open_sshc_cmd(Host, Port, [" -o UserKnownHostsFile=", KnownHosts, - " -o StrictHostKeyChecking=no", + " -o CheckHostIP=no" + " -o StrictHostKeyChecking=no" + " -q" + " -x", " -R ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]), spawn(fun() -> ct:log(["ssh command:\r\n ",Cmd],[]), @@ -295,7 +304,6 @@ tunnel_out_non_erlclient_erlserver(Config) -> tunnel_in_non_erlclient_erlserver(Config) -> SystemDir = proplists:get_value(data_dir, Config), UserDir = proplists:get_value(priv_dir, Config), - KnownHosts = filename:join(UserDir, "known_hosts"), {_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true}, {system_dir, SystemDir}, {failfun, fun ssh_test_lib:failfun/2}]), @@ -304,10 +312,14 @@ tunnel_in_non_erlclient_erlserver(Config) -> ListenHost = {127,0,0,1}, ListenPort = 2345, + KnownHosts = filename:join(UserDir, "known_hosts"), Cmd = ssh_test_lib:open_sshc_cmd(Host, Port, [" -o UserKnownHostsFile=", KnownHosts, - " -o StrictHostKeyChecking=no", + " -o CheckHostIP=no" + " -o StrictHostKeyChecking=no" + " -q" + " -x", " -L ",integer_to_list(ListenPort),":127.0.0.1:",integer_to_list(ToPort)]), spawn(fun() -> ct:log(["ssh command:\r\n ",Cmd],[]), @@ -519,22 +531,6 @@ extra_logout() -> ok end. -%%-------------------------------------------------------------------- -%% Check if we have a "newer" ssh client that supports these test cases -check_ssh_client_support(Config) -> - case ssh_test_lib:ssh_client_supports_Q() of - true -> - ssh:start(), - Config; - _ -> - {skip, "test case not supported by ssh client"} - end. - -comment(AtomList) -> - ct:comment( - string:join(lists:map(fun erlang:atom_to_list/1, AtomList), - ", ")). - %%%---------------------------------------------------------------- no_forwarding() -> %%% Check if the ssh of the OS has tunneling enabled diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key new file mode 100644 index 0000000000..2979ea88ed --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMe4MDoit0t8RzSVPwkCBemQ9fhXL+xnTSAWISw8HNCioAoGCCqGSM49 +AwEHoUQDQgAEo2q7U3P6r0W5WGOLtM78UQtofM9UalEhiZeDdiyylsR/RR17Op0s +VPGSADLmzzgcucLEKy17j2S+oz42VUJy5A== +-----END EC PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub new file mode 100644 index 0000000000..85dc419345 --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKNqu1Nz+q9FuVhji7TO/FELaHzPVGpRIYmXg3YsspbEf0UdezqdLFTxkgAy5s84HLnCxCste49kvqM+NlVCcuQ= uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key new file mode 100644 index 0000000000..13a8fcf491 --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQAAAJi+h4O7voeD +uwAAAAtzc2gtZWQyNTUxOQAAACBJSOuiYGWaO9lye8Bgafod1kw8P6cV3Xb2qJgCB6yJfQ +AAAEBaOcJfGPNemKc1wPHTCmM4Kwvh6dZ0CqY14UT361UnN0lI66JgZZo72XJ7wGBp+h3W +TDw/pxXddvaomAIHrIl9AAAAE3VhYmhuaWxAZWx4YWRsajNxMzIBAg== +-----END OPENSSH PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub new file mode 100644 index 0000000000..156ef4045c --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_ed25519_key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 uabhnil@elxadlj3q32 diff --git a/lib/ssh/test/ssh_upgrade_SUITE.erl b/lib/ssh/test/ssh_upgrade_SUITE.erl index 4417962d26..78657b2014 100644 --- a/lib/ssh/test/ssh_upgrade_SUITE.erl +++ b/lib/ssh/test/ssh_upgrade_SUITE.erl @@ -61,9 +61,7 @@ init_per_suite(Config0) -> end_per_suite(Config) -> ct_release_test:cleanup(Config), - ssh:stop(), - UserDir = proplists:get_value(priv_dir, Config), - ssh_test_lib:clean_rsa(UserDir). + ssh:stop(). init_per_testcase(_TestCase, Config) -> Config. diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 70307d6039..732c3f8766 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,4 +1,4 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.9 +SSH_VSN = 4.10 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 93d58939f2..8eed6a9d1a 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -27,6 +27,177 @@ </header> <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 10.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix a bug that causes cross-build failure.</p> + <p> + This change excludes the ssl.d dependency file from the + source tar balls.</p> + <p> + Own Id: OTP-16562 Aux Id: ERL-1168 </p> + </item> + <item> + <p> + Correct translation of OpenSSL legacy names for two + legacy cipher suites</p> + <p> + Own Id: OTP-16573 Aux Id: ERIERL-477 </p> + </item> + <item> + <p> + Correct documentation for PSK identity and SRP username.</p> + <p> + Own Id: OTP-16585</p> + </item> + <item> + <p> + Make sure client hostname check is run when client uses + its own verify_fun</p> + <p> + Own Id: OTP-16626 Aux Id: ERL-1232 </p> + </item> + <item> + <p> + Improved signature selection mechanism in TLS 1.3 for + increased interoperability.</p> + <p> + Own Id: OTP-16638 Aux Id: ERL-1206 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Drop support for SSL-3.0. Support for this legacy TLS + version has not been enabled by default since OTP 19. Now + all code to support it has been removed, that is SSL-3.0 + protocol version can not be used and is considered + invalid.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-14790</p> + </item> + <item> + <p> + Added support for RSA-PSS signature schemes</p> + <p> + Own Id: OTP-15247</p> + </item> + <item> + <p> + Improve interoperability by implementing the middlebox + compatiblity mode.</p> + <p> + The middlebox compatibility mode makes the TLS 1.3 + handshake look more like a TLS 1.2 handshake and + increases the chance of successfully establishing TLS 1.3 + connections through legacy middleboxes.</p> + <p> + Own Id: OTP-15589</p> + </item> + <item> + <p> + Utilize new properties of <seemfa + marker="erts:erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data()</c></seemfa> + for performance improvement of Erlang distribution over + TLS.</p> + <p> + Own Id: OTP-16127 Aux Id: OTP-15618 </p> + </item> + <item> + <p> + Calls of deprecated functions in the <seeguide + marker="crypto:new_api#the-old-api">Old Crypto + API</seeguide> are replaced by calls of their <seeguide + marker="crypto:new_api#the-new-api">substitutions</seeguide>.</p> + <p> + Own Id: OTP-16346</p> + </item> + <item> + <p> + Implement cipher suite TLS_AES_128_CCM_8_SHA256.</p> + <p> + Own Id: OTP-16391</p> + </item> + <item> + <p> + This change adds TLS-1.3 to the list of default supported + versions. That is, TLS-1.3 and TLS-1.2 are configured + when ssl option 'versions' is not explicitly set.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-16400</p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p> + Extended ssl:versions so that it lists supported, + available and implemented TLS/DTLS versions.</p> + <p> + Own Id: OTP-16519</p> + </item> + <item> + <p> + Added new option exclusive for ssl:cipher_suites/2,3</p> + <p> + Own Id: OTP-16532</p> + </item> + <item> + <p> + Avoid DoS attack against stateful session_tickets by + making session ticket ids unpredictable.</p> + <p> + Own Id: OTP-16533</p> + </item> + <item> + <p> + Add support for the max_fragment_length extension (RFC + 6066).</p> + <p> + Own Id: OTP-16547 Aux Id: PR-2547 </p> + </item> + <item> + <p> + Add srp_username in ssl:connection_info, update the + document with types of this function.</p> + <p> + Own Id: OTP-16584</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.6.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix timing bug that could cause ssl sockets to become + unresponsive after an ssl:recv/3 call timed out</p> + <p> + Own Id: OTP-16619 Aux Id: ERL-1213 </p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.6.1</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -557,6 +728,22 @@ </section> +<section><title>SSL 9.2.3.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix timing bug that could cause ssl sockets to become + unresponsive after an ssl:recv/3 call timed out</p> + <p> + Own Id: OTP-16619 Aux Id: ERL-1213 </p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.2.3.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 566b5e7eed..1424c795a6 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -449,8 +449,8 @@ <code> fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | {revoked, atom()}} | - {extension, #'Extension'{}}, InitialUserState :: term()) -> - {valid, UserState :: term()} | {valid_peer, UserState :: term()} | + {extension, #'Extension'{}} | valid | valid_peer, InitialUserState :: term()) -> + {valid, UserState :: term()} | {fail, Reason :: term()} | {unknown, UserState :: term()}. </code> @@ -658,9 +658,9 @@ fun(Chain::[public_key:der_encoded()]) -> <desc><p>The lookup fun is to defined as follows:</p> <code> -fun(psk, PSKIdentity ::string(), UserState :: term()) -> +fun(psk, PSKIdentity :: binary(), UserState :: term()) -> {ok, SharedSecret :: binary()} | error; -fun(srp, Username :: string(), UserState :: term()) -> +fun(srp, Username :: binary(), UserState :: term()) -> {ok, {SRPParams :: srp_param_type(), Salt :: binary(), DerivedKey :: binary()}} | error. </code> @@ -879,6 +879,15 @@ fun(srp, Username :: string(), UserState :: term()) -> </datatype> <datatype> + <name name="max_fragment_length"/> + <desc> + <p>Specifies the maximum fragment length the client + is prepared to accept from the server. + See <url href="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</url></p> + </desc> + </datatype> + + <datatype> <name name="client_psk_identity"/> <desc> <p>Specifies the identity the client presents to the server. @@ -1006,8 +1015,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </desc> </datatype> - <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER </datatype_title> - + <datatype_title>TLS/DTLS OPTION DESCRIPTIONS - SERVER</datatype_title> <datatype> <name name="server_option"/> @@ -1260,6 +1268,39 @@ fun(srp, Username :: string(), UserState :: term()) -> </p></note> </desc> </datatype> + + <datatype> + <name name="connection_info"/> + </datatype> + + <datatype> + <name name="common_info"/> + </datatype> + + <datatype> + <name name="curve_info"/> + </datatype> + + <datatype> + <name name="ssl_options_info"/> + </datatype> + + <datatype> + <name name="security_info"/> + </datatype> + + <datatype> + <name name="connection_info_items"/> + </datatype> + + <datatype> + <name name="connection_info_item"/> + </datatype> + + <datatype> + <name name="tls_options_name"/> + </datatype> + </datatypes> <!-- @@ -1293,21 +1334,31 @@ fun(srp, Username :: string(), UserState :: term()) -> <func> <name name="cipher_suites" arity="2" since="OTP 20.3"/> - <fsummary>Returns a list of all default or - all supported cipher suites.</fsummary> - <desc><p>Returns all default or all supported (except anonymous), - or all anonymous cipher suites for a - TLS version</p> - - <note><p>The cipher suites returned by this function are the - cipher suites that the OTP ssl application can support provided - that they are supported by the cryptolib linked with the OTP - crypto application. Use <seemfa - marker="#filter_cipher_suites/2"> ssl:filter_cipher_suites(Suites, - []).</seemfa> to filter the list for the current - cryptolib. Note that cipher suites may be filtered out because - they are too old or too new depending on the - cryptolib</p></note> + <fsummary>Returns a list of cipher suites.</fsummary> + <desc><p>Lists all possible cipher suites corresponding to + <c>Description</c> that are available. The + <c>exclusive</c> option will exclusively list cipher suites + introduced in <c>Version</c> whereas the the other options + are inclusive from the lowest possible version to + <c>Version</c>. The <c>all</c> options includes all suites + except the anonymous. + </p> + + <note><p>TLS-1.3 has no overlapping cipher suites with previous + TLS versions, that is the result of + <c>cipher_suites(all, 'tlsv1.3').</c> contains a separate set of + suites that can be used with TLS-1.3 an other set that can be used + if a lower version is negotiated. No anonymous suites are + supported by TLS-1.3.</p> + + <p>Also note that the cipher suites returned + by this function are the cipher suites that the OTP ssl + application can support provided that they are supported by the + cryptolib linked with the OTP crypto application. Use <seemfa + marker="#filter_cipher_suites/2"> ssl:filter_cipher_suites(Suites, + []).</seemfa> to filter the list for the current cryptolib. Note + that cipher suites may be filtered out because they are too old or + too new depending on the cryptolib</p></note> </desc> </func> @@ -1836,18 +1887,19 @@ fun(srp, Username :: string(), UserState :: term()) -> <func> <name since="OTP R14B" name="versions" arity="0" /> - <fsummary>Returns version information relevant for the - SSL application.</fsummary> + <fsummary>Lists information, mainly concerning TLS/DTLS versions, + in runtime for debugging and testing purposes.</fsummary> <desc> - <p>Returns version information relevant for the SSL - application.</p> + <p>Lists information, mainly concerning TLS/DTLS versions, + in runtime for debugging and testing purposes. + </p> <taglist> <tag><c>app_vsn</c></tag> <item>The application version of the SSL application.</item> - <tag><c>default_supported</c></tag> - <item>TLS versions supported by default if crypto lib - support is sufficent· + <tag><c>supported</c></tag> + <item>TLS versions supported with current application environment + and crypto library configuration. Overridden by a version option on <seemfa marker="#connect/2"> connect/[2,3,4]</seemfa>, <seemfa marker="#listen/2"> listen/2</seemfa>, and <seemfa @@ -1856,9 +1908,9 @@ fun(srp, Username :: string(), UserState :: term()) -> marker="#connection_information/1">connection_information/1 </seemfa>.</item> - <tag><c>default_supported_dtls</c></tag> - <item>DTLS versions supported by default if crypto lib - support is sufficent· + <tag><c>supported_dtls</c></tag> + <item>DTLS versions supported with current application environment + and crypto library configuration. Overridden by a version option on <seemfa marker="#connect/2"> connect/[2,3,4]</seemfa>, <seemfa marker="#listen/2"> listen/2</seemfa>, and <seemfa @@ -1868,19 +1920,23 @@ fun(srp, Username :: string(), UserState :: term()) -> </seemfa>.</item> <tag><c>available</c></tag> - <item>All TLS versions than can be supported by the SSL application. + <item>All TLS versions supported with the + linked crypto library. </item> <tag><c>available_dtls</c></tag> - <item>All DTLS versions than can be supported by the SSL application.</item> + <item>All DTLS versions supported with the + linked crypto library.</item> - <tag><c>crypto_support</c></tag> - <item>TLS versions than has sufficient crypto lib - support through the Crypto applications currently linked crypto lib.</item> - - <tag><c>crypto_support_dtls</c></tag> - <item>All DTLS versions than has sufficient crypto lib - support through the Crypto applications currently linked crypto lib.</item> + <tag><c>implemented</c></tag> + <item>All TLS versions supported by the SSL + application if linked with a crypto library with the + necessary support.</item> + + <tag><c>implemented_dtls</c></tag> + <item>All DTLS versions supported by the SSL + application if linked with a crypto library with the + necessary support.</item> </taglist> </desc> diff --git a/lib/ssl/doc/src/standards_compliance.xml b/lib/ssl/doc/src/standards_compliance.xml index abbcfc46ce..7a803a6195 100644 --- a/lib/ssl/doc/src/standards_compliance.xml +++ b/lib/ssl/doc/src/standards_compliance.xml @@ -133,10 +133,8 @@ <item>Key Exchange: ECDHE</item> <item>Groups: all standard groups supported for the Diffie-Hellman key exchange</item> <item>Ciphers: all cipher suites are supported</item> - <item>Signature Algorithms: rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, - ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, - rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pkcs1_sha1 and ecdsa_sha1</item> - <item>Certificates: RSA (it MUST use the rsaEncryption OID) and ECDSA keys</item> + <item>Signature Algorithms: All algorithms form RFC 8456</item> + <item>Certificates: RSA and ECDSA keys</item> </list> <p>Other notable features:</p> <list type="bulleted"> @@ -180,7 +178,7 @@ <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">RSASSA-PSS signature schemes</cell> <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -305,8 +303,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">max_fragment_length (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>ssl-@OTP-16547@</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -444,8 +442,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">max_fragment_length (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>ssl-@OTP-16547@</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -704,8 +702,8 @@ </url> </cell> <cell align="left" valign="middle"><em>Client</em></cell> - <cell align="left" valign="middle"><em>PC</em></cell> - <cell align="left" valign="middle"><em>22.1</em></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -776,20 +774,20 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_pss_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_pss_sha384</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_pss_sha512</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -879,20 +877,20 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_pss_sha256</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_pss_sha384</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">rsa_pss_pss_sha512</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>23</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1234,8 +1232,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">max_fragment_length (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>ssl-@OTP-16547@</em></cell> </row> <row> <cell align="left" valign="middle"></cell> @@ -1301,8 +1299,8 @@ <row> <cell align="left" valign="middle"></cell> <cell align="left" valign="middle">max_fragment_length (RFC6066)</cell> - <cell align="left" valign="middle"><em>NC</em></cell> - <cell align="left" valign="middle"></cell> + <cell align="left" valign="middle"><em>C</em></cell> + <cell align="left" valign="middle"><em>ssl-@OTP-16547@</em></cell> </row> <row> <cell align="left" valign="middle"></cell> diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index 0b99cdde52..d53b73a747 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -155,7 +155,7 @@ EXTRA_ERLC_FLAGS = +warn_unused_vars ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ -pz $(EBIN) \ -pz $(ERL_TOP)/lib/public_key/ebin \ - $(EXTRA_ERLC_FLAGS) + $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\" # ---------------------------------------------------- # Targets diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 975dc5fc4e..5d2f3a4253 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -448,9 +448,12 @@ init({call, From}, {start, Timeout}, Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Session#session.session_id, Renegotiation, Cert), + MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), Version = Hello#client_hello.client_version, HelloVersion = dtls_record:hello_version(Version, Versions), - State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}), + State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}, + connection_states = ConnectionStates1}), {State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}), State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion session = Session, @@ -580,8 +583,9 @@ hello(internal, #server_hello{} = Hello, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, connection_env = #connection_env{negotiated_version = ReqVersion}, connection_states = ConnectionStates0, + session = #session{session_id = OldId}, ssl_options = SslOptions} = State) -> - case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of + case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of #alert{} = Alert -> handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> @@ -1107,9 +1111,11 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, connection_states = ConnectionStates0, ssl_options = #{log_level := LogLevel}} = State0, Epoch) -> - %% TODO remove hardcoded Max size + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), {Encoded, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight), Version, 1400, Epoch, ConnectionStates0), + encode_handshake_flight(lists:reverse(Flight), Version, MaxSize, Epoch, ConnectionStates0), send(Transport, Socket, Encoded), ssl_logger:debug(LogLevel, outbound, 'record', Encoded), {State0#state{connection_states = ConnectionStates}, []}; @@ -1123,8 +1129,11 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, connection_states = ConnectionStates0, ssl_options = #{log_level := LogLevel}} = State0, Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), {HsBefore, ConnectionStates1} = - encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch, ConnectionStates0), + encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch, ConnectionStates0), {EncChangeCipher, ConnectionStates} = encode_change_cipher(ChangeCipher, Version, Epoch, ConnectionStates1), send(Transport, Socket, [HsBefore, EncChangeCipher]), @@ -1141,12 +1150,15 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, connection_states = ConnectionStates0, ssl_options = #{log_level := LogLevel}} = State0, Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), {HsBefore, ConnectionStates1} = - encode_handshake_flight(lists:reverse(Flight0), Version, 1400, Epoch-1, ConnectionStates0), + encode_handshake_flight(lists:reverse(Flight0), Version, MaxSize, Epoch-1, ConnectionStates0), {EncChangeCipher, ConnectionStates2} = encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates1), {HsAfter, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates2), + encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates2), send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), ssl_logger:debug(LogLevel, outbound, 'record', [HsBefore]), ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), @@ -1162,10 +1174,13 @@ send_handshake_flight(#state{static_env = #static_env{socket = Socket, connection_states = ConnectionStates0, ssl_options = #{log_level := LogLevel}} = State0, Epoch) -> + PMTUEstimate = 1400, %% TODO make configurable + #{current_write := #{max_fragment_length := MaxFragmentLength}} = ConnectionStates0, + MaxSize = min(MaxFragmentLength, PMTUEstimate), {EncChangeCipher, ConnectionStates1} = encode_change_cipher(ChangeCipher, Version, Epoch-1, ConnectionStates0), {HsAfter, ConnectionStates} = - encode_handshake_flight(lists:reverse(Flight1), Version, 1400, Epoch, ConnectionStates1), + encode_handshake_flight(lists:reverse(Flight1), Version, MaxSize, Epoch, ConnectionStates1), send(Transport, Socket, [EncChangeCipher, HsAfter]), ssl_logger:debug(LogLevel, outbound, 'record', [EncChangeCipher]), ssl_logger:debug(LogLevel, outbound, 'record', [HsAfter]), diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 861b1aacb4..157e3814c2 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -30,7 +30,7 @@ -include("ssl_alert.hrl"). %% Handshake handling --export([client_hello/7, client_hello/8, cookie/4, hello/4, +-export([client_hello/7, client_hello/8, cookie/4, hello/5, hello/4, hello_verify_request/2]). %% Handshake encoding @@ -97,15 +97,16 @@ hello(#server_hello{server_version = Version, random = Random, compression_method = Compression, session_id = SessionId, extensions = HelloExt}, #{versions := SupportedVersions} = SslOpt, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, OldId) -> + IsNew = ssl_session:is_new(OldId, SessionId), case dtls_record:is_acceptable_version(Version, SupportedVersions) of true -> handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, - ConnectionStates0, Renegotiation); + ConnectionStates0, Renegotiation, IsNew); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) - end; + end. hello(#client_hello{client_version = ClientVersion} = Hello, #{versions := Versions} = SslOpts, Info, Renegotiation) -> @@ -212,7 +213,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites, HelloExt, dtls_v1:corresponding_tls_version(Version), SslOpts, Session0, - ConnectionStates0, Renegotiation) of + ConnectionStates0, Renegotiation, + Session0#session.is_resumable) of {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} catch throw:Alert -> @@ -220,11 +222,11 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, end. handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) -> try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, Compression, HelloExt, dtls_v1:corresponding_tls_version(Version), - SslOpt, ConnectionStates0, Renegotiation) of + SslOpt, ConnectionStates0, Renegotiation, IsNew) of {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} catch throw:Alert -> diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index ee0ce2d22a..16542a8eb3 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -215,8 +215,26 @@ encode_change_cipher_spec(Version, Epoch, ConnectionStates) -> %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- encode_data(Data, Version, ConnectionStates) -> - #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), - encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates). + #{epoch := Epoch, max_fragment_length := MaxFragmentLength} + = ssl_record:current_connection_state(ConnectionStates, write), + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, + case iolist_size(Data) of + N when N > MaxLength -> + Frags = tls_record:split_iovec(erlang:iolist_to_iovec(Data), MaxLength), + {RevCipherText, ConnectionStates1} = + lists:foldl(fun(Frag, {Acc, CS0}) -> + {CipherText, CS1} = + encode_plain_text(?APPLICATION_DATA, Version, Epoch, Frag, CS0), + {[CipherText|Acc], CS1} + end, {[], ConnectionStates}, Frags), + {lists:reverse(RevCipherText), ConnectionStates1}; + _ -> + encode_plain_text(?APPLICATION_DATA, Version, Epoch, Data, ConnectionStates) + end. encode_plain_text(Type, Version, Epoch, Data, ConnectionStates) -> Write0 = get_connection_state_by_epoch(Epoch, ConnectionStates, write), @@ -393,7 +411,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_fragment_length => undefined }. get_dtls_records_aux({DataTag, StateName, _, Versions} = Vinfo, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index bebf35a20f..f08cb96d17 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -73,5 +73,5 @@ {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, {mod, {ssl_app, []}}, - {runtime_dependencies, ["stdlib-3.5","public_key-1.7.2","kernel-6.0", + {runtime_dependencies, ["stdlib-3.5","public_key-1.8","kernel-6.0", "erts-10.0","crypto-4.2", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 5ba731dd54..7e52cf573d 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -34,6 +34,11 @@ -include("ssl_handshake.hrl"). -include("ssl_srp.hrl"). +%% Needed to make documentation rendering happy +-ifndef(VSN). +-define(VSN,"unknown"). +-endif. + %% Application handling -export([start/0, start/1, @@ -102,6 +107,9 @@ -deprecated({ssl_accept, '_', "use ssl_handshake/1,2,3 instead"}). +-deprecated({cipher_suites, 0, "use cipher_suites/2,3 instead"}). +-deprecated({cipher_suites, 1, "use cipher_suites/2,3 instead"}). + -removed([{negotiated_next_protocol,1, "use ssl:negotiated_protocol/1 instead"}]). -removed([{connection_info,1, @@ -344,7 +352,9 @@ -type custom_verify() :: {Verifyfun :: fun(), InitialUserState :: any()}. -type crl_check() :: boolean() | peer | best_effort. --type crl_cache_opts() :: [any()]. +-type crl_cache_opts() :: {Module :: atom(), + {DbHandle :: internal | term(), + Args :: list()}}. -type handshake_size() :: integer(). -type hibernate_after() :: timeout(). -type root_fun() :: fun(). @@ -385,6 +395,7 @@ {psk_identity, client_psk_identity()} | {srp_identity, client_srp_identity()} | {server_name_indication, sni()} | + {max_fragment_length, max_fragment_length()} | {customize_hostname_check, customize_hostname_check()} | {signature_algs, client_signature_algs()} | {fallback, fallback()} | @@ -407,6 +418,7 @@ -type client_srp_identity() :: srp_identity(). -type customize_hostname_check() :: list(). -type sni() :: HostName :: hostname() | disable. +-type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096. -type client_signature_algs() :: signature_algs(). -type fallback() :: boolean(). -type ssl_imp() :: new | old. @@ -458,10 +470,37 @@ alpn => app_level_protocol(), srp => binary(), next_protocol => app_level_protocol(), + max_frag_enum => 1..4, ec_point_formats => [0..2], elliptic_curves => [public_key:oid()], sni => hostname()}. % exported %% ------------------------------------------------------------------------------------------------------- +-type connection_info() :: [common_info() | curve_info() | ssl_options_info() | security_info()]. +-type common_info() :: {protocol, protocol_version()} | + {session_id, session_id()} | + {session_resumption, boolean()} | + {selected_cipher_suite, erl_cipher_suite()} | + {sni_hostname, term()} | + {srp_username, term()}. +-type curve_info() :: {ecc, {named_curve, term()}}. +-type ssl_options_info() :: tls_option(). +-type security_info() :: {client_random, binary()} | + {server_random, binary()} | + {master_secret, binary()}. +-type connection_info_items() :: [connection_info_item()]. +-type connection_info_item() :: protocol | + session_id | + session_resumption | + selected_cipher_suite | + sni_hostname | + srp_username | + ecc | + client_random | + server_random | + master_secret | + tls_options_name(). +-type tls_options_name() :: atom(). +%% ------------------------------------------------------------------------------------------------------- %%%-------------------------------------------------------------------- %%% API @@ -900,9 +939,7 @@ controlling_process(#sslsocket{pid = {Listen, %%-------------------------------------------------------------------- -spec connection_information(SslSocket) -> {ok, Result} | {error, reason()} when SslSocket :: sslsocket(), - Result :: [{OptionName, OptionValue}], - OptionName :: atom(), - OptionValue :: any(). + Result :: connection_info(). %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- @@ -921,10 +958,8 @@ connection_information(#sslsocket{pid = {dtls,_}}) -> %%-------------------------------------------------------------------- -spec connection_information(SslSocket, Items) -> {ok, Result} | {error, reason()} when SslSocket :: sslsocket(), - Items :: [OptionName], - Result :: [{OptionName, OptionValue}], - OptionName :: atom(), - OptionValue :: any(). + Items :: connection_info_items(), + Result :: connection_info(). %% %% Description: Return SSL information for the connection %%-------------------------------------------------------------------- @@ -1010,45 +1045,46 @@ cipher_suites(all) -> [ssl_cipher_format:suite_legacy(Suite) || Suite <- available_suites(all)]. %%-------------------------------------------------------------------- --spec cipher_suites(Supported, Version) -> ciphers() when - Supported :: default | all | anonymous, +-spec cipher_suites(Description, Version) -> ciphers() when + Description :: default | all | exclusive | anonymous, Version :: protocol_version(). %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version) when Version == 'tlsv1.3'; +cipher_suites(Description, Version) when Version == 'tlsv1.3'; Version == 'tlsv1.2'; Version == 'tlsv1.1'; Version == tlsv1 -> - cipher_suites(Base, tls_record:protocol_version(Version)); -cipher_suites(Base, Version) when Version == 'dtlsv1.2'; + cipher_suites(Description, tls_record:protocol_version(Version)); +cipher_suites(Description, Version) when Version == 'dtlsv1.2'; Version == 'dtlsv1'-> - cipher_suites(Base, dtls_record:protocol_version(Version)); -cipher_suites(Base, Version) -> - [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Base, Version)]. + cipher_suites(Description, dtls_record:protocol_version(Version)); +cipher_suites(Description, Version) -> + [ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Description, Version)]. %%-------------------------------------------------------------------- --spec cipher_suites(Supported, Version, rfc | openssl) -> [string()] when - Supported :: default | all | anonymous, +-spec cipher_suites(Description, Version, rfc | openssl) -> [string()] when + Description :: default | all | exclusive | anonymous, Version :: protocol_version(). %% Description: Returns all default and all supported cipher suites for a %% TLS/DTLS version %%-------------------------------------------------------------------- -cipher_suites(Base, Version, StringType) when Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1 -> - cipher_suites(Base, tls_record:protocol_version(Version), StringType); -cipher_suites(Base, Version, StringType) when Version == 'dtlsv1.2'; +cipher_suites(Description, Version, StringType) when Version == 'tlsv1.3'; + Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1 -> + cipher_suites(Description, tls_record:protocol_version(Version), StringType); +cipher_suites(Description, Version, StringType) when Version == 'dtlsv1.2'; Version == 'dtlsv1'-> - cipher_suites(Base, dtls_record:protocol_version(Version), StringType); -cipher_suites(Base, Version, rfc) -> - [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite)) - || Suite <- supported_suites(Base, Version)]; -cipher_suites(Base, Version, openssl) -> - [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite)) - || Suite <- supported_suites(Base, Version)]. + cipher_suites(Description, dtls_record:protocol_version(Version), StringType); +cipher_suites(Description, Version, rfc) -> + [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite)) + || Suite <- supported_suites(Description, Version)]; +cipher_suites(Description, Version, openssl) -> + [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite)) + || Suite <- supported_suites(Description, Version)]. %%-------------------------------------------------------------------- -spec filter_cipher_suites(Suites, Filters) -> Ciphers when @@ -1316,31 +1352,36 @@ sockname(#sslsocket{pid = [Pid| _], fd = {Transport, Socket,_,_}}) when is_pid(P %%--------------------------------------------------------------- -spec versions() -> [VersionInfo] when VersionInfo :: {ssl_app, string()} | - {supported | available, [tls_version()]} | - {supported_dtls | available_dtls, [dtls_version()]}. + {supported | available | implemented, [tls_version()]} | + {supported_dtls | available_dtls | implemented_dtls, [dtls_version()]}. %% %% Description: Returns a list of relevant versions. %%-------------------------------------------------------------------- versions() -> - TLSVsns = tls_record:supported_protocol_versions(), - DTLSVsns = dtls_record:supported_protocol_versions(), - SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- TLSVsns], - SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- DTLSVsns], - AvailableTLSVsns = ?ALL_AVAILABLE_VERSIONS, - AvailableDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, - CryptoSupVersionsTLS = [Vsn || Vsn <- AvailableTLSVsns, - tls_record:sufficient_crypto_support(Vsn)], - CryptoSupVersionsDTLS = [Vsn || Vsn <- AvailableDTLSVsns, - tls_record:sufficient_crypto_support(tls_record:protocol_version( - dtls_v1:corresponding_tls_version( - dtls_record:protocol_version(Vsn))))], - - [{ssl_app, "9.2"}, {default_supported, SupportedTLSVsns}, - {default_supported_dtls, SupportedDTLSVsns}, + ConfTLSVsns = tls_record:supported_protocol_versions(), + ConfDTLSVsns = dtls_record:supported_protocol_versions(), + ImplementedTLSVsns = ?ALL_AVAILABLE_VERSIONS, + ImplementedDTLSVsns = ?ALL_AVAILABLE_DATAGRAM_VERSIONS, + + TLSCryptoSupported = fun(Vsn) -> + tls_record:sufficient_crypto_support(Vsn) + end, + DTLSCryptoSupported = fun(Vsn) -> + tls_record:sufficient_crypto_support(dtls_v1:corresponding_tls_version(Vsn)) + end, + SupportedTLSVsns = [tls_record:protocol_version(Vsn) || Vsn <- ConfTLSVsns, TLSCryptoSupported(Vsn)], + SupportedDTLSVsns = [dtls_record:protocol_version(Vsn) || Vsn <- ConfDTLSVsns, DTLSCryptoSupported(Vsn)], + + AvailableTLSVsns = [Vsn || Vsn <- ImplementedTLSVsns, TLSCryptoSupported(tls_record:protocol_version(Vsn))], + AvailableDTLSVsns = [Vsn || Vsn <- ImplementedDTLSVsns, DTLSCryptoSupported(dtls_record:protocol_version(Vsn))], + + [{ssl_app, ?VSN}, + {supported, SupportedTLSVsns}, + {supported_dtls, SupportedDTLSVsns}, {available, AvailableTLSVsns}, {available_dtls, AvailableDTLSVsns}, - {crypto_support, CryptoSupVersionsTLS}, - {crypto_support_dtls, CryptoSupVersionsDTLS} + {implemented, ImplementedTLSVsns}, + {implemented_dtls, ImplementedDTLSVsns} ]. %%--------------------------------------------------------------- @@ -1509,6 +1550,8 @@ available_suites(all) -> Version = tls_record:highest_protocol_version([]), ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)). +supported_suites(exclusive, {3,Minor}) -> + tls_v1:exclusive_suites(Minor); supported_suites(default, Version) -> ssl_cipher:suites(Version); supported_suites(all, Version) -> @@ -1600,7 +1643,9 @@ handle_option(anti_replay = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = validate_option(Option, default_value(Option, Rules)), OptionsMap#{Option => Value}; handle_option(anti_replay = Option, Value0, - #{session_tickets := SessionTickets} = OptionsMap, #{rules := Rules}) -> + #{session_tickets := SessionTickets, + versions := Versions} = OptionsMap, #{rules := Rules}) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), assert_option_dependency(Option, session_tickets, [SessionTickets], [stateless]), case SessionTickets of stateless -> @@ -1609,6 +1654,13 @@ handle_option(anti_replay = Option, Value0, _ -> OptionsMap#{Option => default_value(Option, Rules)} end; +handle_option(beast_mitigation = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(beast_mitigation = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts, verify := Verify, verify_fun := VerifyFun} = OptionsMap, _Env) @@ -1627,17 +1679,20 @@ handle_option(cacertfile = Option, unbound, #{cacerts := CaCerts, handle_option(cacertfile = Option, Value0, OptionsMap, _Env) -> Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; -handle_option(ciphers = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := Rules}) -> - Value = handle_cipher_option(default_value(Option, Rules), HighestVersion), +handle_option(ciphers = Option, unbound, #{versions := Versions} = OptionsMap, #{rules := Rules}) -> + Value = handle_cipher_option(default_value(Option, Rules), Versions), OptionsMap#{Option => Value}; -handle_option(ciphers = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> - Value = handle_cipher_option(Value0, HighestVersion), +handle_option(ciphers = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + Value = handle_cipher_option(Value0, Versions), OptionsMap#{Option => Value}; handle_option(client_renegotiation = Option, unbound, OptionsMap, #{role := Role}) -> Value = default_option_role(server, true, Role), OptionsMap#{Option => Value}; -handle_option(client_renegotiation = Option, Value0, OptionsMap, #{role := Role}) -> +handle_option(client_renegotiation = Option, Value0, + #{versions := Versions} = OptionsMap, #{role := Role}) -> assert_role(server_only, Role, Option, Value0), + assert_option_dependency(Option, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; handle_option(eccs = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) -> @@ -1677,13 +1732,50 @@ handle_option(key_update_at = Option, Value0, #{versions := Versions} = OptionsM assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; +handle_option(next_protocols_advertised = Option, unbound, OptionsMap, + #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(next_protocols_advertised = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(next_protocols_advertised, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(next_protocol_selector = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = default_value(Option, Rules), OptionsMap#{Option => Value}; -handle_option(next_protocol_selector = Option, Value0, OptionsMap, _Env) -> +handle_option(next_protocol_selector = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(client_preferred_next_protocols, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = make_next_protocol_selector( validate_option(client_preferred_next_protocols, Value0)), OptionsMap#{Option => Value}; +handle_option(padding_check = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(padding_check = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(psk_identity = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(psk_identity = Option, Value0, #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(secure_renegotiate = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(secure_renegotiate= Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(secure_renegotiate, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) -> Value = case Role of @@ -1693,14 +1785,20 @@ handle_option(reuse_session = Option, unbound, OptionsMap, #{role := Role}) -> fun(_, _, _, _) -> true end end, OptionsMap#{Option => Value}; -handle_option(reuse_session = Option, Value0, OptionsMap, _Env) -> +handle_option(reuse_session = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(reuse_session, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; %% TODO: validate based on role handle_option(reuse_sessions = Option, unbound, OptionsMap, #{rules := Rules}) -> Value = validate_option(Option, default_value(Option, Rules)), OptionsMap#{Option => Value}; -handle_option(reuse_sessions = Option, Value0, OptionsMap, _Env) -> +handle_option(reuse_sessions = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(reuse_sessions, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), Value = validate_option(Option, Value0), OptionsMap#{Option => Value}; handle_option(server_name_indication = Option, unbound, OptionsMap, #{host := Host, @@ -1754,25 +1852,48 @@ handle_option(sni_fun = Option, Value0, OptionsMap, _Env) -> throw({error, {conflict_options, [sni_fun, sni_hosts]}}) end, OptionsMap#{Option => Value}; +handle_option(srp_identity = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(srp_identity = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(srp_identity, versions, Versions, + ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(supported_groups = Option, unbound, #{versions := [HighestVersion|_]} = OptionsMap, #{rules := _Rules}) -> Value = handle_supported_groups_option(groups(default), HighestVersion), OptionsMap#{Option => Value}; -handle_option(supported_groups = Option, Value0, #{versions := [HighestVersion|_]} = OptionsMap, _Env) -> +handle_option(supported_groups = Option, Value0, + #{versions := [HighestVersion|_] = Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), Value = handle_supported_groups_option(Value0, HighestVersion), OptionsMap#{Option => Value}; +handle_option(use_ticket = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(use_ticket = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1.3']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; +handle_option(user_lookup_fun = Option, unbound, OptionsMap, #{rules := Rules}) -> + Value = validate_option(Option, default_value(Option, Rules)), + OptionsMap#{Option => Value}; +handle_option(user_lookup_fun = Option, Value0, + #{versions := Versions} = OptionsMap, _Env) -> + assert_option_dependency(Option, versions, Versions, ['tlsv1','tlsv1.1','tlsv1.2']), + Value = validate_option(Option, Value0), + OptionsMap#{Option => Value}; handle_option(verify = Option, unbound, OptionsMap, #{rules := Rules}) -> handle_verify_option(default_value(Option, Rules), OptionsMap); handle_option(verify = _Option, Value, OptionsMap, _Env) -> handle_verify_option(Value, OptionsMap); - handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, #{rules := Rules}) - when Verify =:= verify_none orelse - Verify =:= 0 -> + when Verify =:= verify_none -> OptionsMap#{Option => default_value(Option, Rules)}; handle_option(verify_fun = Option, unbound, #{verify := Verify} = OptionsMap, _Env) - when Verify =:= verify_peer orelse - Verify =:= 1 orelse - Verify =:= 2 -> + when Verify =:= verify_peer -> OptionsMap#{Option => undefined}; handle_option(verify_fun = Option, Value0, OptionsMap, _Env) -> Value = validate_option(Option, Value0), @@ -1923,27 +2044,39 @@ assert_role_value(server, Option, Value, ServerValues, _) -> throw({error, {options, role, {Option, {Value, {server, ServerValues}}}}}) end. - assert_option_dependency(Option, OptionDep, Values0, AllowedValues) -> - %% special handling for version - Values = - case OptionDep of - versions -> - lists:map(fun tls_record:protocol_version/1, Values0); - _ -> - Values0 - end, - Set1 = sets:from_list(Values), - Set2 = sets:from_list(AllowedValues), - case sets:size(sets:intersection(Set1, Set2)) > 0 of + case is_dtls_configured(Values0) of true -> + %% TODO: Check option dependency for DTLS ok; false -> - %% Message = build_error_message(Option, OptionDep, AllowedValues), - %% throw({error, {options, Message}}) - throw({error, {options, dependency, {Option, {OptionDep, AllowedValues}}}}) + %% special handling for version + Values = + case OptionDep of + versions -> + lists:map(fun tls_record:protocol_version/1, Values0); + _ -> + Values0 + end, + Set1 = sets:from_list(Values), + Set2 = sets:from_list(AllowedValues), + case sets:size(sets:intersection(Set1, Set2)) > 0 of + true -> + ok; + false -> + throw({error, {options, dependency, + {Option, {OptionDep, AllowedValues}}}}) + end end. +is_dtls_configured(Versions) -> + Fun = fun (Version) when Version =:= {254, 253} orelse + Version =:= {254, 255} -> + true; + (_) -> + false + end, + lists:any(Fun, Versions). validate_option(versions, Versions) -> validate_versions(Versions, Versions); @@ -1974,8 +2107,6 @@ validate_option(partial_chain, Value) when is_function(Value) -> Value; validate_option(fail_if_no_peer_cert, Value) when is_boolean(Value) -> Value; -validate_option(verify_client_once, Value) when is_boolean(Value) -> - Value; validate_option(depth, Value) when is_integer(Value), Value >= 0, Value =< 255-> Value; @@ -2136,6 +2267,13 @@ validate_option(server_name_indication, undefined) -> validate_option(server_name_indication, disable) -> disable; +%% RFC 6066, Section 4 +validate_option(max_fragment_length, I) when I == ?MAX_FRAGMENT_LENGTH_BYTES_1; I == ?MAX_FRAGMENT_LENGTH_BYTES_2; + I == ?MAX_FRAGMENT_LENGTH_BYTES_3; I == ?MAX_FRAGMENT_LENGTH_BYTES_4 -> + I; +validate_option(max_fragment_length, undefined) -> + undefined; + validate_option(sni_hosts, []) -> []; validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) -> @@ -2365,8 +2503,8 @@ emulated_options(Protocol, Opts) -> dtls_socket:emulated_options(Opts) end. -handle_cipher_option(Value, Version) when is_list(Value) -> - try binary_cipher_suites(Version, Value) of +handle_cipher_option(Value, Versions) when is_list(Value) -> + try binary_cipher_suites(Versions, Value) of Suites -> Suites catch @@ -2376,37 +2514,44 @@ handle_cipher_option(Value, Version) when is_list(Value) -> throw({error, {options, {ciphers, Value}}}) end. -binary_cipher_suites(Version, []) -> +binary_cipher_suites([{3,4} = Version], []) -> + %% Defaults to all supported suites that does + %% not require explicit configuration TLS-1.3 + %% only mode. + default_binary_suites(exclusive, Version); +binary_cipher_suites([Version| _], []) -> %% Defaults to all supported suites that does %% not require explicit configuration - default_binary_suites(Version); -binary_cipher_suites(Version, [Map|_] = Ciphers0) when is_map(Map) -> + default_binary_suites(default, Version); +binary_cipher_suites(Versions, [Map|_] = Ciphers0) when is_map(Map) -> Ciphers = [ssl_cipher_format:suite_map_to_bin(C) || C <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> Ciphers = [ssl_cipher_format:suite_map_to_bin(tuple_to_map(C)) || C <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites([Version |_] = Versions, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> All = ssl_cipher:all_suites(Version) ++ ssl_cipher:anonymous_suites(Version), case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of [] -> %% Defaults to all supported suites that does %% not require explicit configuration - default_binary_suites(Version); + binary_cipher_suites(Versions, []); Ciphers -> Ciphers end; -binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> +binary_cipher_suites(Versions, [Head | _] = Ciphers0) when is_list(Head) -> %% Format: ["RC4-SHA","RC4-MD5"] Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, Ciphers0) -> + binary_cipher_suites(Versions, Ciphers); +binary_cipher_suites(Versions, Ciphers0) -> %% Format: "RC4-SHA:RC4-MD5" Ciphers = [ssl_cipher_format:suite_openssl_str_to_map(C) || C <- string:lexemes(Ciphers0, ":")], - binary_cipher_suites(Version, Ciphers). + binary_cipher_suites(Versions, Ciphers). -default_binary_suites(Version) -> +default_binary_suites(exclusive, {_, Minor}) -> + ssl_cipher:filter_suites(tls_v1:exclusive_suites(Minor)); +default_binary_suites(default, Version) -> ssl_cipher:filter_suites(ssl_cipher:suites(Version)). tuple_to_map({Kex, Cipher, Mac}) -> @@ -2541,19 +2686,14 @@ assert_proplist([Value | _]) -> throw({option_not_a_key_value_tuple, Value}). -handle_verify_option(verify_none, #{fail_if_no_peer_cert := _FailIfNoPeerCert} = OptionsMap) -> - OptionsMap#{verify => verify_none, - fail_if_no_peer_cert => false}; -handle_verify_option(verify_peer, #{fail_if_no_peer_cert := FailIfNoPeerCert} = OptionsMap) -> - OptionsMap#{verify => verify_peer, - fail_if_no_peer_cert => FailIfNoPeerCert}; -%% Handle 0, 1, 2 for backwards compatibility -handle_verify_option(0, OptionsMap) -> - handle_verify_option(verify_none, OptionsMap); -handle_verify_option(1, OptionsMap) -> - handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => false}); -handle_verify_option(2, OptionsMap) -> - handle_verify_option(verify_peer, OptionsMap#{fail_if_no_peer_cert => true}); +handle_verify_option(verify_none, #{fail_if_no_peer_cert := false} = OptionsMap) -> + OptionsMap#{verify => verify_none}; +handle_verify_option(verify_none, #{fail_if_no_peer_cert := true}) -> + throw({error, {options, incompatible, + {verify, verify_none}, + {fail_if_no_peer_cert, true}}}); +handle_verify_option(verify_peer, OptionsMap) -> + OptionsMap#{verify => verify_peer}; handle_verify_option(Value, _) -> throw({error, {options, {verify, Value}}}). diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 6d718dfef9..ade1d396cd 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -133,7 +133,7 @@ file_to_crls(File, DbHandle) -> [Bin || {'CertificateList', Bin, not_encrypted} <- List]. %%-------------------------------------------------------------------- --spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid, +-spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid | valid_peer, term()) -> {valid, term()} | {fail, tuple()} | {unknown, term()}. @@ -321,6 +321,10 @@ public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorith public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'rsaEncryption'}, subjectPublicKey = Key}) -> Key; +public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-RSASSA-PSS', + parameters = Params}, + subjectPublicKey = Key}) -> + {Key, Params}; public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-dsa', parameters = {params, Params}}, subjectPublicKey = Key}) -> diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index e9eb78203c..f08a7e6b00 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,18 +34,43 @@ -include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/2, security_parameters/3, security_parameters_1_3/2, - cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/6, aead_decrypt/6, - suites/1, all_suites/1, crypto_support_filters/0, - chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, - srp_suites/1, srp_suites_anon/1, - rc4_suites/1, des_suites/1, rsa_suites/1, - filter/3, filter_suites/1, filter_suites/2, - hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, - random_bytes/1, calc_mac_hash/4, calc_mac_hash/6, - is_stream_ciphersuite/1, signature_scheme/1, - scheme_to_components/1, hash_size/1, effective_key_bits/1, - key_material/1, signature_algorithm_to_scheme/1]). +-export([security_parameters/2, + security_parameters/3, + security_parameters_1_3/2, + cipher_init/3, + nonce_seed/2, + decipher/6, + cipher/5, + aead_encrypt/6, + aead_decrypt/6, + suites/1, + all_suites/1, + crypto_support_filters/0, + anonymous_suites/1, + psk_suites/1, + psk_suites_anon/1, + srp_suites/1, + srp_suites_anon/1, + rc4_suites/1, + des_suites/1, + rsa_suites/1, + filter/3, + filter_suites/1, + filter_suites/2, + hash_algorithm/1, + sign_algorithm/1, + is_acceptable_hash/2, + is_fallback/1, + random_bytes/1, + calc_mac_hash/4, + calc_mac_hash/6, + is_stream_ciphersuite/1, + signature_scheme/1, + scheme_to_components/1, + hash_size/1, + effective_key_bits/1, + key_material/1, + signature_algorithm_to_scheme/1]). %% RFC 8446 TLS 1.3 -export([generate_client_shares/1, @@ -303,7 +328,6 @@ suites({_, Minor}) -> all_suites({3, _} = Version) -> suites(Version) - ++ chacha_suites(Version) ++ psk_suites(Version) ++ srp_suites(Version) ++ rsa_suites(Version) @@ -312,20 +336,6 @@ all_suites({3, _} = Version) -> all_suites(Version) -> dtls_v1:all_suites(Version). -%%-------------------------------------------------------------------- --spec chacha_suites(ssl_record:ssl_version() | integer()) -> - [ssl_cipher_format:cipher_suite()]. -%% -%% Description: Returns list of the chacha cipher suites, only supported -%% if explicitly set by user for now due to interop problems, proably need -%% to be fixed in crypto. -%%-------------------------------------------------------------------- -chacha_suites({3, _}) -> - [?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256]; -chacha_suites(_) -> - []. %%-------------------------------------------------------------------- -spec anonymous_suites(ssl_record:ssl_version() | integer()) -> @@ -982,8 +992,20 @@ scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}; %% Handling legacy signature algorithms scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}. - -%% TODO: Add support for ed25519, ed448, rsa_pss* +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS', + parameters = #'RSASSA-PSS-params'{ + maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = HashAlgo}}}) -> + #'HashAlgorithm'{algorithm = HashOid} = HashAlgo, + case public_key:pkix_hash_type(HashOid) of + sha256 -> + rsa_pss_pss_sha256; + sha384 -> + rsa_pss_pss_sha384; + sha512 -> + rsa_pss_pss_sha512 + end; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha256WithRSAEncryption}) -> rsa_pkcs1_sha256; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha384WithRSAEncryption}) -> @@ -1001,8 +1023,18 @@ signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'sha-1WithRSAEn signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?sha1WithRSAEncryption}) -> rsa_pkcs1_sha1; signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'ecdsa-with-SHA1'}) -> - ecdsa_sha1. - + ecdsa_sha1; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed25519'}) -> + eddsa_ed25519; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-Ed448'}) -> + eddsa_ed448; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption', + parameters = ?NULL}) -> + rsa_pkcs1_sha1; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'rsaEncryption'}) -> + rsa_pss_rsae; +signature_algorithm_to_scheme(#'SignatureAlgorithm'{algorithm = ?'id-RSASSA-PSS'}) -> + rsa_pss_pss. %% RFC 5246: 6.2.3.2. CBC Block Cipher %% diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 8e2e794280..8ee9261c38 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1652,7 +1652,10 @@ connection_info(#state{static_env = #static_env{protocol_cb = Connection}, handshake_env = #handshake_env{sni_hostname = SNIHostname, resumption = Resumption}, session = #session{session_id = SessionId, - cipher_suite = CipherSuite, ecc = ECCCurve}, + cipher_suite = CipherSuite, + srp_username = SrpUsername, + ecc = ECCCurve}, + connection_states = #{current_write := CurrentWrite}, connection_env = #connection_env{negotiated_version = {_,_} = Version}, ssl_options = Opts}) -> RecordCB = record_cb(Connection), @@ -1665,12 +1668,19 @@ connection_info(#state{static_env = #static_env{protocol_cb = Connection}, _ -> [] end, + + MFLInfo = case maps:get(max_fragment_length, CurrentWrite, undefined) of + MaxFragmentLength when is_integer(MaxFragmentLength) -> + [{max_fragment_length, MaxFragmentLength}]; + _ -> + [] + end, [{protocol, RecordCB:protocol_version(Version)}, {session_id, SessionId}, {session_resumption, Resumption}, - {cipher_suite, ssl_cipher_format:suite_legacy(CipherSuiteDef)}, {selected_cipher_suite, CipherSuiteDef}, - {sni_hostname, SNIHostname} | CurveInfo] ++ ssl_options_list(Opts). + {sni_hostname, SNIHostname}, + {srp_username, SrpUsername} | CurveInfo] ++ MFLInfo ++ ssl_options_list(Opts). security_info(#state{connection_states = ConnectionStates}) -> #{security_parameters := @@ -2807,6 +2817,9 @@ ssl_options_list([{erl_dist, _}|T], Acc) -> ssl_options_list(T, Acc); ssl_options_list([{renegotiate_at, _}|T], Acc) -> ssl_options_list(T, Acc); +ssl_options_list([{max_fragment_length, _}|T], Acc) -> + %% skip max_fragment_length from options since it is taken above from connection_states + ssl_options_list(T, Acc); ssl_options_list([{ciphers = Key, Value}|T], Acc) -> ssl_options_list(T, [{Key, lists:map( diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 89bbdd0f54..b7e9e769ea 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -67,6 +67,7 @@ %% Ext handling hello, %%:: #client_hello{} | #server_hello{} sni_hostname = undefined, + max_frag_enum :: undefined | {max_frag_enum, integer()}, expecting_next_protocol_negotiation = false ::boolean(), next_protocol = undefined :: undefined | binary(), alpn = undefined, %% Used in TLS 1.3 diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index e1d629a3e3..c17062d888 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -48,7 +48,7 @@ %% Create handshake messages -export([hello_request/0, server_hello/4, server_hello_done/0, certificate/4, client_certificate_verify/6, certificate_request/5, key_exchange/3, - finished/5, next_protocol/1]). + finished/5, next_protocol/1, digitally_signed/5]). %% Handle handshake messages -export([certify/7, certificate_verify/6, verify_signature/5, @@ -73,11 +73,11 @@ %% Extensions handling -export([client_hello_extensions/7, - handle_client_hello_extensions/9, %% Returns server hello extensions - handle_server_hello_extensions/9, select_curve/2, select_curve/3, + handle_client_hello_extensions/10, %% Returns server hello extensions + handle_server_hello_extensions/10, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, select_hashsign_algs/3, empty_extensions/2, add_server_share/3, - add_alpn/2, add_selected_version/1, decode_alpn/1 + add_alpn/2, add_selected_version/1, decode_alpn/1, max_frag_enum/1 ]). -export([get_cert_params/1, @@ -171,7 +171,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version, false -> Hashes = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), - Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey), + Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo), #certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}} end. @@ -400,27 +400,34 @@ certificate_verify(Signature, PublicKeyInfo, Version, %% %% Description: Checks that a public_key signature is valid. %%-------------------------------------------------------------------- -verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) -> - true; -verify_signature({3, Minor}, Hash, {HashAlgo, rsa_pss_rsae}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) - when Minor >= 3 -> - public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]); -verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) +verify_signature({3, 4}, Hash, {HashAlgo, SignAlgo}, Signature, + {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae; + SignAlgo == rsa_pss_pss -> + Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + public_key:verify(Hash, HashAlgo, Signature, PubKey, Options); +verify_signature({3, 3}, Hash, {HashAlgo, SignAlgo}, Signature, + {_, PubKey, PubKeyParams}) when SignAlgo == rsa_pss_rsae; + SignAlgo == rsa_pss_pss -> + Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options); +verify_signature({3, Minor}, Hash, {HashAlgo, SignAlgo}, Signature, {?rsaEncryption, PubKey, PubKeyParams}) when Minor >= 3 -> - public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey); -verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) -> + Options = verify_options(SignAlgo, HashAlgo, PubKeyParams), + public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey, Options); +verify_signature({3, Minor}, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) when Minor =< 2 -> case public_key:decrypt_public(Signature, PubKey, [{rsa_pad, rsa_pkcs1_padding}]) of Hash -> true; - _ -> false + _ -> false end; -verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> - public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); +verify_signature({3, 4}, Hash, {HashAlgo, _SignAlgo}, Signature, {?'id-ecPublicKey', PubKey, PubKeyParams}) -> + public_key:verify(Hash, HashAlgo, Signature, {PubKey, PubKeyParams}); verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> + public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); +verify_signature({3, Minor}, _Hash, {_HashAlgo, anon}, _Signature, _) when Minor =< 3 -> + true; +verify_signature({3, Minor}, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) when Minor =< 3-> public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). %%-------------------------------------------------------------------- @@ -695,6 +702,10 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) -> ?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(HostLen), HostnameBin/binary, Acc/binary>>); +encode_extensions([#max_frag_enum{enum = MaxFragEnum} | Rest], Acc) -> + ExtLength = 1, + encode_extensions(Rest, <<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(ExtLength), ?BYTE(MaxFragEnum), + Acc/binary>>); encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> Versions = encode_versions(Versions0), VerLen = byte_size(Versions), @@ -1088,7 +1099,8 @@ client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renego add_tls12_extensions(_Version, #{alpn_advertised_protocols := AlpnAdvertisedProtocols, next_protocol_selector := NextProtocolSelector, - server_name_indication := ServerNameIndication} = SslOpts, + server_name_indication := ServerNameIndication, + max_fragment_length := MaxFragmentLength} = SslOpts, ConnectionStates, Renegotiation) -> SRP = srp_user(SslOpts), @@ -1099,7 +1111,8 @@ add_tls12_extensions(_Version, next_protocol_negotiation => encode_client_protocol_negotiation(NextProtocolSelector, Renegotiation), - sni => sni(ServerNameIndication) + sni => sni(ServerNameIndication), + max_frag_enum => max_frag_enum(MaxFragmentLength) }. @@ -1284,18 +1297,27 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, alpn_preferred_protocols := ALPNPreferredProtocols} = Opts, #session{cipher_suite = NegotiatedCipherSuite, compression_method = Compression} = Session0, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, IsResumed) -> Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0), + MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, - ConnectionStates0, Renegotiation, SecureRenegotation), + ConnectionStates1, Renegotiation, SecureRenegotation), Empty = empty_extensions(Version, server_hello), + %% RFC 6066 - server doesn't include max_fragment_length for resumed sessions + ServerMaxFragEnum = if IsResumed -> + undefined; + true -> + MaxFragEnum + end, ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server, ConnectionStates, Renegotiation), ec_point_formats => server_ecc_extension(Version, - maps:get(ec_point_formats, Exts, undefined)) + maps:get(ec_point_formats, Exts, undefined)), + max_frag_enum => ServerMaxFragEnum }, %% If we receive an ALPN extension and have ALPN configured for this connection, @@ -1318,13 +1340,28 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, Exts, Version, #{secure_renegotiate := SecureRenegotation, next_protocol_selector := NextProtoSelector}, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, IsNew) -> ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, CipherSuite, undefined, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), + %% RFC 6066: handle received/expected maximum fragment length + if IsNew -> + ServerMaxFragEnum = maps:get(max_frag_enum, Exts, undefined), + #{current_write := #{max_fragment_length := ConnMaxFragLen}} = ConnectionStates, + ClientMaxFragEnum = max_frag_enum(ConnMaxFragLen), + + if ServerMaxFragEnum == ClientMaxFragEnum -> + ok; + true -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; + true -> + ok + end, + %% If we receive an ALPN extension then this is the protocol selected, %% otherwise handle the NPN extension. ALPN = maps:get(alpn, Exts, undefined), @@ -1391,7 +1428,7 @@ select_hashsign({#hash_sign_algos{hash_sign_algos = ClientHashSigns}, Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> ClientSignatureSchemes = get_signature_scheme(ClientSignatureSchemes0), - {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert), + {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert), SignAlgo = sign_algo(SignAlgo0), PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), @@ -1445,7 +1482,7 @@ select_hashsign(#certificate_request{ Cert, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3-> - {SignAlgo0, Param, PublicKeyAlgo0} = get_cert_params(Cert), + {SignAlgo0, Param, PublicKeyAlgo0, _} = get_cert_params(Cert), SignAlgo = sign_algo(SignAlgo0), PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), @@ -1468,7 +1505,7 @@ select_hashsign(#certificate_request{ ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) end; select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Version) -> - {_, _, PublicKeyAlgo0} = get_cert_params(Cert), + {_, _, PublicKeyAlgo0, _} = get_cert_params(Cert), PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), %% Check cert even for TLS 1.0/1.1 @@ -1484,14 +1521,23 @@ select_hashsign(#certificate_request{certificate_types = Types}, Cert, _, Versio %% - signature algorithm %% - parameters of the signature algorithm %% - public key algorithm (key type) +%% - RSA key size in bytes get_cert_params(Cert) -> #'OTPCertificate'{tbsCertificate = TBSCert, signatureAlgorithm = {_,SignAlgo, Param}} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}} = + #'OTPSubjectPublicKeyInfo'{algorithm = {_, PublicKeyAlgo, _}, + subjectPublicKey = PublicKey} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - {SignAlgo, Param, PublicKeyAlgo}. - + RSAKeySize = + case PublicKey of + #'RSAPublicKey'{modulus = Modulus} -> + %% Get RSA key size in bytes + byte_size(binary:encode_unsigned(Modulus)); + _ -> + undefined + end, + {SignAlgo, Param, PublicKeyAlgo, RSAKeySize}. get_signature_scheme(undefined) -> undefined; @@ -1553,6 +1599,8 @@ extension_value(#hash_sign_algos{hash_sign_algos = Algos}) -> Algos; extension_value(#alpn{extension_data = Data}) -> Data; +extension_value(#max_frag_enum{enum = Enum}) -> + Enum; extension_value(#next_protocol_negotiation{extension_data = Data}) -> Data; extension_value(#srp{username = Name}) -> @@ -1680,9 +1728,10 @@ validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, SslState) end, {Role, CertDbHandle, CertDbRef, {ServerNameIndication, CustomizeHostCheck}, CRLCheck, CRLDbHandle}}. -apply_user_fun(Fun, OtpCert, VerifyResult, UserState0, +apply_user_fun(Fun, OtpCert, VerifyResult0, UserState0, {_, CertDbHandle, CertDbRef, _, CRLCheck, CRLDbHandle} = SslState, CertPath, LogLevel) when - (VerifyResult == valid) or (VerifyResult == valid_peer) -> + (VerifyResult0 == valid) or (VerifyResult0 == valid_peer) -> + VerifyResult = maybe_check_hostname(OtpCert, VerifyResult0, SslState), case Fun(OtpCert, VerifyResult, UserState0) of {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) -> case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, @@ -1705,6 +1754,16 @@ apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState, _CertPath, {unknown, {SslState, UserState}} end. +maybe_check_hostname(OtpCert, valid_peer, SslState) -> + case ssl_certificate:validate(OtpCert, valid_peer, SslState) of + {valid, _} -> + valid_peer; + {fail, Reason} -> + Reason + end; +maybe_check_hostname(_, valid, _) -> + valid. + handle_path_validation_error({bad_cert, unknown_ca} = Reason, PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef) -> handle_incomplete_chain(PeerCert, Chain, Opts, Options, CertDbHandle, CertsDbRef, Reason); @@ -1780,30 +1839,64 @@ path_validation_alert({bad_cert, unknown_ca}) -> path_validation_alert(Reason) -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason). -digitally_signed(Version, Hashes, HashAlgo, PrivateKey) -> - try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of +digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) -> + try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey, SignAlgo) of Signature -> Signature catch error:badkey-> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))) end. -do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine) - when Minor >= 3 -> - crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine)); -do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> - public_key:sign({digest, Hash}, HashAlgo, Key); -do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> - public_key:encrypt_private(Hash, Key, - [{rsa_pad, rsa_pkcs1_padding}]); -do_digitally_signed({3, _}, Hash, _, - #{algorithm := rsa} = Engine) -> + +do_digitally_signed({3, Minor}, Hash, _, + #{algorithm := rsa} = Engine, rsa) when Minor =< 2-> crypto:private_encrypt(rsa, Hash, maps:remove(algorithm, Engine), rsa_pkcs1_padding); -do_digitally_signed({3, _}, Hash, HashAlgo, #{algorithm := Alg} = Engine) -> - crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine)); -do_digitally_signed(_Version, Hash, HashAlgo, Key) -> +do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) + when Minor > 3 -> + Options = signature_options(SignAlgo, HashAlgo), + crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options); +do_digitally_signed({3, Minor}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) + when Minor > 3 -> + Options = signature_options(SignAlgo, HashAlgo), + crypto:sign(Alg, HashAlgo, Hash, maps:remove(algorithm, Engine), Options); +do_digitally_signed({3, 3}, Hash, HashAlgo, #{algorithm := Alg} = Engine, SignAlgo) -> + Options = signature_options(SignAlgo, HashAlgo), + crypto:sign(Alg, HashAlgo, {digest, Hash}, maps:remove(algorithm, Engine), Options); +do_digitally_signed({3, 4}, Hash, HashAlgo, {#'RSAPrivateKey'{} = Key, + #'RSASSA-PSS-params'{}}, SignAlgo) -> + Options = signature_options(SignAlgo, HashAlgo), + public_key:sign(Hash, HashAlgo, Key, Options); +do_digitally_signed({3, 4}, Hash, HashAlgo, Key, SignAlgo) -> + Options = signature_options(SignAlgo, HashAlgo), + public_key:sign(Hash, HashAlgo, Key, Options); +do_digitally_signed({3, Minor}, Hash, HashAlgo, Key, SignAlgo) when Minor >= 3 -> + Options = signature_options(HashAlgo, SignAlgo), + public_key:sign({digest,Hash}, HashAlgo, Key, Options); +do_digitally_signed({3, Minor}, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key, rsa) when Minor =< 2 -> + public_key:encrypt_private(Hash, Key, + [{rsa_pad, rsa_pkcs1_padding}]); +do_digitally_signed(_Version, Hash, HashAlgo, Key, _SignAlgo) -> public_key:sign({digest, Hash}, HashAlgo, Key). + +signature_options(SignAlgo, HashAlgo) when SignAlgo =:= rsa_pss_rsae orelse + SignAlgo =:= rsa_pss_pss -> + pss_options(HashAlgo); +signature_options(_, _) -> + []. + +verify_options(SignAlgo, HashAlgo, _KeyParams) + when SignAlgo =:= rsa_pss_rsae orelse + SignAlgo =:= rsa_pss_pss -> + pss_options(HashAlgo); +verify_options(_, _, _) -> + []. + +pss_options(HashAlgo) -> + %% of the digest algorithm: rsa_pss_saltlen = -1 + [{rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, -1}, + {rsa_mgf1_md, HashAlgo}]. bad_key(#'DSAPrivateKey'{}) -> unacceptable_dsa_key; @@ -2160,7 +2253,7 @@ enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, server_key_exchange_hash(HashAlgo, <<ClientRandom/binary, ServerRandom/binary, EncParams/binary>>), - Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), + Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey, SignAlgo), #server_key_params{params = Params, params_bin = EncParams, hashsign = {HashAlgo, SignAlgo}, @@ -2574,6 +2667,10 @@ decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), decode_extensions(Rest, Version, MessageType, Acc#{sni => dec_sni(NameList)}); +decode_extensions(<<?UINT16(?MAX_FRAGMENT_LENGTH_EXT), ?UINT16(1), ?BYTE(MaxFragEnum), Rest/binary>>, + Version, MessageType, Acc) -> + %% RFC 6066 Section 4 + decode_extensions(Rest, Version, MessageType, Acc#{max_frag_enum => #max_frag_enum{enum = MaxFragEnum}}); decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 -> <<?BYTE(_),Versions/binary>> = ExtData, @@ -2935,6 +3032,13 @@ handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> false -> handle_alpn_extension(Tail, ClientProtocols) end. +handle_mfl_extension(#max_frag_enum{enum = Enum}=MaxFragEnum) when Enum >= 1, Enum =< 4 -> + MaxFragEnum; +handle_mfl_extension(#max_frag_enum{}) -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); +handle_mfl_extension(_) -> + undefined. + handle_next_protocol(undefined, _NextProtocolSelector, _Renegotiating) -> undefined; @@ -3153,6 +3257,18 @@ sni(disable) -> sni(Hostname) -> #sni{hostname = Hostname}. +%% convert max_fragment_length (in bytes) to the RFC 6066 ENUM +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_1) -> + #max_frag_enum{enum = 1}; +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_2) -> + #max_frag_enum{enum = 2}; +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_3) -> + #max_frag_enum{enum = 3}; +max_frag_enum(?MAX_FRAGMENT_LENGTH_BYTES_4) -> + #max_frag_enum{enum = 4}; +max_frag_enum(undefined) -> + undefined. + renegotiation_info(_, client, _, false) -> #renegotiation_info{renegotiated_connection = undefined}; renegotiation_info(_RecordCB, server, ConnectionStates, false) -> @@ -3270,7 +3386,7 @@ empty_extensions() -> empty_extensions({3,4}, client_hello) -> #{ sni => undefined, - %% max_fragment_length => undefined, + %% max_frag_enum => undefined, %% status_request => undefined, elliptic_curves => undefined, signature_algs => undefined, diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index a772567846..ac397a8d88 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -389,8 +389,18 @@ hostname = undefined }). +%% enum{ 2^9(1), 2^10(2), 2^11(3), 2^12(4), (255) } MaxFragmentLength; +-define(MAX_FRAGMENT_LENGTH_EXT, 1). +-define(MAX_FRAGMENT_LENGTH_BYTES_1, 512). +-define(MAX_FRAGMENT_LENGTH_BYTES_2, 1024). +-define(MAX_FRAGMENT_LENGTH_BYTES_3, 2048). +-define(MAX_FRAGMENT_LENGTH_BYTES_4, 4096). + +-record(max_frag_enum, { + enum = undefined %% contains the enum value 1..4 + }). + %% Other possible values from RFC 6066, not supported --define(MAX_FRAGMENT_LENGTH, 1). -define(CLIENT_CERTIFICATE_URL, 2). -define(TRUSTED_CA_KEYS, 3). -define(TRUNCATED_HMAC, 4). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index cb41742404..2da39b199f 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -25,7 +25,6 @@ -include_lib("public_key/include/public_key.hrl"). --define(VSN, "8.2.6"). -define(SECRET_PRINTOUT, "***"). -type reason() :: any(). @@ -155,6 +154,7 @@ log_level => {notice, [versions]}, max_handshake_size => {?DEFAULT_MAX_HANDSHAKE_SIZE, [versions]}, middlebox_comp_mode => {true, [versions]}, + max_fragment_length => {undefined, [versions]}, next_protocol_selector => {undefined, [versions]}, next_protocols_advertised => {undefined, [versions]}, padding_check => {true, [versions]}, @@ -177,12 +177,9 @@ supported_groups => {undefined, [versions]}, use_ticket => {undefined, [versions]}, user_lookup_fun => {undefined, [versions]}, - validate_extensions_fun => {undefined, [versions]}, verify => {verify_none, [versions, fail_if_no_peer_cert, - partial_chain, - verify_client_once]}, - verify_client_once => {false, [versions]}, + partial_chain]}, verify_fun => { {fun(_,{bad_cert, _}, UserState) -> diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index 867d2cfc5a..c19c6eeea9 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -40,6 +40,7 @@ set_renegotiation_flag/2, set_client_verify_data/3, set_server_verify_data/3, + set_max_fragment_length/2, empty_connection_state/2, initial_connection_state/2, record_protocol_role/1, step_encryption_state/1]). @@ -203,6 +204,33 @@ set_renegotiation_flag(Flag, #{current_read := CurrentRead0, pending_write => PendingWrite}. %%-------------------------------------------------------------------- +-spec set_max_fragment_length(term(), connection_states()) -> connection_states(). +%% +%% Description: Set maximum fragment length in all connection states +%%-------------------------------------------------------------------- +set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum}, + #{current_read := CurrentRead0, + current_write := CurrentWrite0, + pending_read := PendingRead0, + pending_write := PendingWrite0} + = ConnectionStates) -> + MaxFragmentLength = if MaxFragEnum == 1 -> ?MAX_FRAGMENT_LENGTH_BYTES_1; + MaxFragEnum == 2 -> ?MAX_FRAGMENT_LENGTH_BYTES_2; + MaxFragEnum == 3 -> ?MAX_FRAGMENT_LENGTH_BYTES_3; + MaxFragEnum == 4 -> ?MAX_FRAGMENT_LENGTH_BYTES_4 + end, + CurrentRead = CurrentRead0#{max_fragment_length => MaxFragmentLength}, + CurrentWrite = CurrentWrite0#{max_fragment_length => MaxFragmentLength}, + PendingRead = PendingRead0#{max_fragment_length => MaxFragmentLength}, + PendingWrite = PendingWrite0#{max_fragment_length => MaxFragmentLength}, + ConnectionStates#{current_read => CurrentRead, + current_write => CurrentWrite, + pending_read => PendingRead, + pending_write => PendingWrite}; +set_max_fragment_length(_,ConnectionStates) -> + ConnectionStates. + +%%-------------------------------------------------------------------- -spec set_client_verify_data(current_read | current_write | current_both, binary(), connection_states())-> connection_states(). @@ -424,7 +452,8 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_fragment_length => undefined }. empty_security_params(ConnectionEnd = ?CLIENT) -> @@ -461,7 +490,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_fragment_length => undefined }. initial_security_params(ConnectionEnd) -> diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index c0555046c3..9aa598daed 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -154,6 +154,8 @@ -define(MAX_COMPRESSED_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+1024)). -define(MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+2048)). -define(TLS13_MAX_CIPHER_TEXT_LENGTH, (?MAX_PLAIN_TEXT_LENGTH+256)). +-define(MAX_PADDING_LENGTH,256). +-define(MAX_MAC_LENGTH,32). %% -record(protocol_version, { %% major, % unit 8 diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 4a02d34a6b..5a41b69e54 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -604,8 +604,11 @@ init({call, From}, {start, Timeout}, %% Update pre_shared_key extension with binders (TLS 1.3) Hello1 = tls_handshake_1_3:maybe_add_binders(Hello, TicketData, HelloVersion), + MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined), + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), + {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello1, HelloVersion, ConnectionStates0, Handshake0), + encode_handshake(Hello1, HelloVersion, ConnectionStates1, Handshake0), tls_socket:send(Transport, Socket, BinMsg), ssl_logger:debug(LogLevel, outbound, 'handshake', Hello1), @@ -718,8 +721,9 @@ hello(internal, #server_hello{} = Hello, connection_env = #connection_env{negotiated_version = ReqVersion} = CEnv, static_env = #static_env{role = client}, handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + session = #session{session_id = OldId}, ssl_options = SslOptions} = State) -> - case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of + case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation, OldId) of #alert{} = Alert -> %%TODO ssl_connection:handle_own_alert(Alert, ReqVersion, hello, State#state{connection_env = @@ -1164,7 +1168,8 @@ next_tls_record(Data, StateName, _ -> State0#state.connection_env#connection_env.negotiated_version end, - case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of + #{current_write := #{max_fragment_length := MaxFragLen}} = State0#state.connection_states, + case tls_record:get_tls_records(Data, Versions, Buf0, MaxFragLen, SslOpts) of {Records, Buf1} -> CT1 = CT0 ++ Records, next_record(StateName, State0#state{protocol_buffers = @@ -1196,10 +1201,18 @@ handle_info({Protocol, _, Data}, StateName, handle_info({PassiveTag, Socket}, StateName, #state{static_env = #static_env{socket = Socket, passive_tag = PassiveTag}, + start_or_recv_from = From, + protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, protocol_specific = PS - } = State) -> - next_event(StateName, no_record, - State#state{protocol_specific = PS#{active_n_toggle => true}}); + } = State0) -> + case (From =/= undefined) andalso (CTs == []) of + true -> + {Record, State} = activate_socket(State0#state{protocol_specific = PS#{active_n_toggle => true}}), + next_event(StateName, Record, State); + false -> + next_event(StateName, no_record, + State0#state{protocol_specific = PS#{active_n_toggle => true}}) + end; handle_info({CloseTag, Socket}, StateName, #state{static_env = #static_env{ role = Role, diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index b440d65706..0b5ff98474 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -218,6 +218,8 @@ wait_ee(internal, #change_cipher_spec{}, State, _Module) -> tls_connection:next_event(?FUNCTION_NAME, no_record, State); wait_ee(internal, #encrypted_extensions{} = EE, State0, _Module) -> case tls_handshake_1_3:do_wait_ee(EE, State0) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, wait_ee, State0); {State1, NextState} -> tls_connection:next_event(NextState, no_record, State1) end; diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index f279e041be..897133846f 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -36,7 +36,7 @@ -include_lib("kernel/include/logger.hrl"). %% Handshake handling --export([client_hello/9, hello/4]). +-export([client_hello/9, hello/5, hello/4]). %% Handshake encoding -export([encode_handshake/2]). @@ -95,11 +95,11 @@ client_hello(_Host, _Port, ConnectionStates, }. %%-------------------------------------------------------------------- --spec hello(#server_hello{} | #client_hello{}, ssl_options(), +-spec hello(#server_hello{}, ssl_options(), ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(), atom(), ssl_record:connection_states(), binary() | undefined, ssl:kex_algo()}, - boolean()) -> + boolean(), #session{}) -> {tls_record:tls_version(), ssl:session_id(), ssl_record:connection_states(), alpn | npn, binary() | undefined}| {tls_record:tls_version(), {resumed | new, #session{}}, @@ -117,7 +117,7 @@ client_hello(_Host, _Port, ConnectionStates, %% values. hello(#server_hello{server_version = {Major, Minor}, random = <<_:24/binary,Down:8/binary>>}, - #{versions := [{M,N}|_]}, _, _) + #{versions := [{M,N}|_]}, _, _, _) when (M > 3 orelse M =:= 3 andalso N >= 4) andalso %% TLS 1.3 client (Major =:= 3 andalso Minor =:= 3 andalso %% Negotiating TLS 1.2 Down =:= ?RANDOM_OVERRIDE_TLS12) orelse @@ -131,7 +131,7 @@ hello(#server_hello{server_version = {Major, Minor}, %% equal to the second value if the ServerHello indicates TLS 1.1 or below. hello(#server_hello{server_version = {Major, Minor}, random = <<_:24/binary,Down:8/binary>>}, - #{versions := [{M,N}|_]}, _, _) + #{versions := [{M,N}|_]}, _, _, _) when (M =:= 3 andalso N =:= 3) andalso %% TLS 1.2 client (Major =:= 3 andalso Minor < 3 andalso %% Negotiating TLS 1.1 or prior Down =:= ?RANDOM_OVERRIDE_TLS11) -> @@ -157,7 +157,7 @@ hello(#server_hello{server_version = LegacyVersion, #server_hello_selected_version{selected_version = Version} = HelloExt} }, #{versions := SupportedVersions} = SslOpt, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, OldId) -> %% In TLS 1.3, the TLS server indicates its version using the "supported_versions" extension %% (Section 4.2.1), and the legacy_version field MUST be set to 0x0303, which is the version %% number for TLS 1.2. @@ -171,10 +171,11 @@ hello(#server_hello{server_version = LegacyVersion, true -> case Version of {3,3} -> + IsNew = ssl_session:is_new(OldId, SessionId), %% TLS 1.2 ServerHello with "supported_versions" (special case) handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, - ConnectionStates0, Renegotiation); + ConnectionStates0, Renegotiation, IsNew); SelectedVersion -> %% TLS 1.3 {next_state, wait_sh, SelectedVersion} @@ -191,17 +192,30 @@ hello(#server_hello{server_version = Version, session_id = SessionId, extensions = HelloExt}, #{versions := SupportedVersions} = SslOpt, - ConnectionStates0, Renegotiation) -> + ConnectionStates0, Renegotiation, OldId) -> + IsNew = ssl_session:is_new(OldId, SessionId), case tls_record:is_acceptable_version(Version, SupportedVersions) of true -> handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, - ConnectionStates0, Renegotiation); + ConnectionStates0, Renegotiation, IsNew); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) - end; + end. +%%-------------------------------------------------------------------- +-spec hello(#client_hello{}, ssl_options(), + ssl_record:connection_states() | {inet:port_number(), #session{}, db_handle(), + atom(), ssl_record:connection_states(), + binary() | undefined, ssl:kex_algo()}, + boolean()) -> + {tls_record:tls_version(), ssl:session_id(), + ssl_record:connection_states(), alpn | npn, binary() | undefined}| + {tls_record:tls_version(), {resumed | new, #session{}}, + ssl_record:connection_states(), binary() | undefined, + HelloExt::map(), {ssl:hash(), ssl:sign_algo()} | + undefined} | {atom(), atom()} | {atom(), atom(), tuple()} | #alert{}. %% TLS 1.2 Server %% - If "supported_versions" is present (ClientHello): %% - Select version from "supported_versions" (ignore ClientHello.legacy_version) @@ -338,7 +352,8 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites, HelloExt, Version, SslOpts, Session0, ConnectionStates0, - Renegotiation) of + Renegotiation, + Session0#session.is_resumable) of {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} @@ -348,11 +363,11 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, - Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation, IsNew) -> try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, Compression, HelloExt, Version, SslOpt, ConnectionStates0, - Renegotiation) of + Renegotiation, IsNew) of {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} catch throw:Alert -> diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 982d0fa787..3dc01fbcb0 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -117,14 +117,22 @@ server_hello_random(hello_retry_request, _) -> ?HELLO_RETRY_REQUEST_RANDOM. -encrypted_extensions(#state{handshake_env = #handshake_env{alpn = undefined}}) -> - #encrypted_extensions{ - extensions = #{} - }; -encrypted_extensions(#state{handshake_env = #handshake_env{alpn = ALPNProtocol}}) -> - Extensions = ssl_handshake:add_alpn(#{}, ALPNProtocol), +encrypted_extensions(#state{handshake_env = HandshakeEnv}) -> + E0 = #{}, + E1 = case HandshakeEnv#handshake_env.alpn of + undefined -> + E0; + ALPNProtocol -> + ssl_handshake:add_alpn(#{}, ALPNProtocol) + end, + E2 = case HandshakeEnv#handshake_env.max_frag_enum of + undefined -> + E1; + MaxFragEnum -> + E1#{max_frag_enum => MaxFragEnum} + end, #encrypted_extensions{ - extensions = Extensions + extensions = E2 }. @@ -214,7 +222,7 @@ certificate_verify(PrivateKey, SignatureScheme, ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, - {HashAlgo, _, _} = + {HashAlgo, SignAlgo, _} = ssl_cipher:scheme_to_components(SignatureScheme), Context = lists:reverse(Messages), @@ -225,7 +233,7 @@ certificate_verify(PrivateKey, SignatureScheme, %% Digital signatures use the hash function defined by the selected signature %% scheme. - case sign(THash, ContextString, HashAlgo, PrivateKey) of + case sign(THash, ContextString, HashAlgo, PrivateKey, SignAlgo) of {ok, Signature} -> {ok, #certificate_verify_1_3{ algorithm = SignatureScheme, @@ -487,24 +495,9 @@ certificate_entry(DER) -> %% 79 %% 00 %% 0101010101010101010101010101010101010101010101010101010101010101 -sign(THash, Context, HashAlgo, #'ECPrivateKey'{} = PrivateKey) -> +sign(THash, Context, HashAlgo, PrivateKey, SignAlgo) -> Content = build_content(Context, THash), - try digitally_signed(Content, HashAlgo, PrivateKey) of - Signature -> - {ok, Signature} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} - end; -sign(THash, Context, HashAlgo, PrivateKey) -> - Content = build_content(Context, THash), - - %% The length of the Salt MUST be equal to the length of the output - %% of the digest algorithm: rsa_pss_saltlen = -1 - try digitally_signed(Content, HashAlgo, PrivateKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]) of + try ssl_handshake:digitally_signed({3,4}, Content, HashAlgo, PrivateKey, SignAlgo) of Signature -> {ok, Signature} catch @@ -512,25 +505,9 @@ sign(THash, Context, HashAlgo, PrivateKey) -> {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. - -verify(THash, Context, HashAlgo, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> +verify(THash, Context, HashAlgo, SignAlgo, Signature, PublicKeyInfo) -> Content = build_content(Context, THash), - try public_key:verify(Content, HashAlgo, Signature, {PublicKey, PublicKeyParams}) of - Result -> - {ok, Result} - catch - error:badarg -> - {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} - end; -verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyParams}) -> - Content = build_content(Context, THash), - - %% The length of the Salt MUST be equal to the length of the output - %% of the digest algorithm: rsa_pss_saltlen = -1 - try public_key:verify(Content, HashAlgo, Signature, PublicKey, - [{rsa_padding, rsa_pkcs1_pss_padding}, - {rsa_pss_saltlen, -1}, - {rsa_mgf1_md, HashAlgo}]) of + try ssl_handshake:verify_signature({3, 4}, Content, {HashAlgo, SignAlgo}, Signature, PublicKeyInfo) of Result -> {ok, Result} catch @@ -538,7 +515,6 @@ verify(THash, Context, HashAlgo, Signature, {?rsaEncryption, PublicKey, _PubKeyP {error, ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, badarg)} end. - build_content(Context, THash) -> Prefix = binary:copy(<<32>>, 64), <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>. @@ -553,13 +529,14 @@ build_content(Context, THash) -> do_start(#client_hello{cipher_suites = ClientCiphers, session_id = SessionId, extensions = Extensions} = _Hello, - #state{connection_states = _ConnectionStates0, + #state{connection_states = ConnectionStates0, ssl_options = #{ciphers := ServerCiphers, signature_algs := ServerSignAlgs, supported_groups := ServerGroups0, alpn_preferred_protocols := ALPNPreferredProtocols, honor_cipher_order := HonorCipherOrder}, session = #session{own_certificate = Cert}} = State0) -> + ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), ServerGroups = get_supported_groups(ServerGroups0), @@ -580,6 +557,7 @@ do_start(#client_hello{cipher_suites = ClientCiphers, {Ref,Maybe} = maybe(), try + %% Handle ALPN extension if ALPN is configured ALPNProtocol = Maybe(handle_alpn(ALPNPreferredProtocols, ClientALPN)), @@ -588,17 +566,15 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. Cipher = Maybe(select_cipher_suite(HonorCipherOrder, ClientCiphers, ServerCiphers)), - Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_client_key_share(ClientGroups, ClientShares)), - - {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), + {PublicKeyAlgo, SignAlgo, SignHash, RSAKeySize} = get_certificate_params(Cert), %% Check if client supports signature algorithm of server certificate Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)), %% Select signature algorithm (used in CertificateVerify message). - SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)), + SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs)), %% Select client public key. If no public key found in ClientShares or %% ClientShares is empty, trigger HelloRetryRequest as we were able @@ -609,7 +585,17 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - State1 = update_start_state(State0, + State1 = case maps:get(max_frag_enum, Extensions, undefined) of + MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) -> + ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0), + HsEnv1 = (State0#state.handshake_env)#handshake_env{max_frag_enum = MaxFragEnum}, + State0#state{handshake_env = HsEnv1, + connection_states = ConnectionStates1}; + _ -> + State0 + end, + + State2 = update_start_state(State1, #{cipher => Cipher, key_share => KeyShare, session_id => SessionId, @@ -624,12 +610,12 @@ do_start(#client_hello{cipher_suites = ClientCiphers, %% message if it is able to find an acceptable set of parameters but the %% ClientHello does not contain sufficient information to proceed with %% the handshake. - case Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)) of + case Maybe(send_hello_retry_request(State2, ClientPubKey, KeyShare, SessionId)) of {_, start} = NextStateTuple -> NextStateTuple; {_, negotiated} = NextStateTuple -> %% Exclude any incompatible PSKs. - PSK = Maybe(handle_pre_shared_key(State1, OfferedPSKs, Cipher)), + PSK = Maybe(handle_pre_shared_key(State2, OfferedPSKs, Cipher)), Maybe(session_resumption(NextStateTuple, PSK)) end catch @@ -933,6 +919,9 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> {Ref, Maybe} = maybe(), try + %% RFC 6066: handle received/expected maximum fragment length + Maybe(maybe_max_fragment_length(Extensions, State0)), + %% Go to state 'wait_finished' if using PSK. Maybe(maybe_resumption(State0)), @@ -943,7 +932,9 @@ do_wait_ee(#encrypted_extensions{extensions = Extensions}, State0) -> {State1, wait_cert_cr} catch {Ref, {State, StateName}} -> - {State, StateName} + {State, StateName}; + {Ref, #alert{} = Alert} -> + Alert end. @@ -983,6 +974,16 @@ maybe_hello_retry_request(#server_hello{random = ?HELLO_RETRY_REQUEST_RANDOM} = maybe_hello_retry_request(_, _) -> ok. +maybe_max_fragment_length(Extensions, State) -> + ServerMaxFragEnum = maps:get(max_frag_enum, Extensions, undefined), + ClientMaxFragEnum = ssl_handshake:max_frag_enum( + maps:get(max_fragment_length, State#state.ssl_options, undefined)), + if ServerMaxFragEnum == ClientMaxFragEnum -> + ok; + true -> + {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)} + end. + maybe_resumption(#state{handshake_env = #handshake_env{resumption = true}} = State) -> {error, {State, wait_finished}}; @@ -1250,7 +1251,7 @@ process_certificate_request(#certificate_request_1_3{ ServerSignAlgsCert = get_signature_scheme_list( maps:get(signature_algs_cert, Extensions, undefined)), - {_PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), + {_PublicKeyAlgo, SignAlgo, SignHash, _} = get_certificate_params(Cert), %% Check if server supports signature algorithm of client certificate case check_cert_sign_algo(SignAlgo, SignHash, ServerSignAlgs, ServerSignAlgsCert) of @@ -1850,7 +1851,7 @@ verify_certificate_verify(#state{ ssl_record:pending_connection_state(ConnectionStates, write), #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, - {HashAlgo, _, _} = + {HashAlgo, SignAlg, _} = ssl_cipher:scheme_to_components(SignatureScheme), Messages = get_handshake_context_cv(HHistory), @@ -1864,7 +1865,7 @@ verify_certificate_verify(#state{ %% Digital signatures use the hash function defined by the selected signature %% scheme. - case verify(THash, ContextString, HashAlgo, Signature, PublicKeyInfo) of + case verify(THash, ContextString, HashAlgo, SignAlg, Signature, PublicKeyInfo) of {ok, true} -> {ok, {State0, wait_finished}}; {ok, false} -> @@ -2026,7 +2027,7 @@ select_cipher_suite(_, [], _) -> select_cipher_suite(true, ClientCiphers, ServerCiphers) -> select_cipher_suite(false, ServerCiphers, ClientCiphers); select_cipher_suite(false, [Cipher|ClientCiphers], ServerCiphers) -> - case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso + case lists:member(Cipher, tls_v1:exclusive_suites(4)) andalso lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; @@ -2068,11 +2069,11 @@ check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> %% DSA keys are not supported by TLS 1.3 -select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> +select_sign_algo(dsa, _RSAKeySize, _ClientSignAlgs, _ServerSignAlgs) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_public_key)}; -select_sign_algo(_, [], _) -> +select_sign_algo(_, _RSAKeySize, [], _) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; -select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> +select_sign_algo(PublicKeyAlgo, RSAKeySize, [C|ClientSignAlgs], ServerSignAlgs) -> {_, S, _} = ssl_cipher:scheme_to_components(C), %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed %% TLS handshake messages: filter sha-1 and rsa_pkcs1. @@ -2082,16 +2083,48 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> %% RSASSA-PSS PSS algorithms: If the public key is carried in an X.509 certificate, %% it MUST use the RSASSA-PSS OID. case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae) - orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_pss) + orelse (PublicKeyAlgo =:= rsa_pss_pss andalso S =:= rsa_pss_pss) orelse (PublicKeyAlgo =:= ecdsa andalso S =:= ecdsa)) andalso lists:member(C, ServerSignAlgs) of true -> - {ok, C}; + validate_key_compatibility(PublicKeyAlgo, RSAKeySize, + [C|ClientSignAlgs], ServerSignAlgs); false -> - select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs) + select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs) end. +validate_key_compatibility(PublicKeyAlgo, RSAKeySize, [C|ClientSignAlgs], ServerSignAlgs) + when PublicKeyAlgo =:= rsa orelse + PublicKeyAlgo =:= rsa_pss_pss -> + case is_rsa_key_compatible(RSAKeySize, C) of + true -> + {ok, C}; + false -> + select_sign_algo(PublicKeyAlgo, RSAKeySize, ClientSignAlgs, ServerSignAlgs) + end; +validate_key_compatibility(_, _, [C|_], _) -> + {ok, C}. + +is_rsa_key_compatible(KeySize, SigAlg) -> + {Hash, _, _} = ssl_cipher:scheme_to_components(SigAlg), + HashSize = ssl_cipher:hash_size(Hash), + + %% OpenSSL crypto lib defines a limit on the size of the random salt + %% in PSS signatures based on the size of signing RSA key. + %% If the limit is unchecked, it causes handshake failures when the + %% configured certificates contain short (e.g. 1024-bit) RSA keys. + %% For more information see the OpenSSL crypto library + %% (rsa_pss:c{77,86}). + %% TODO: Move this check into crypto. Investigate if this is a bug in + %% OpenSSL crypto lib. + if (KeySize < (HashSize + 2)) -> + false; + (HashSize > (KeySize - HashSize - 2)) -> + false; + true -> + true + end. do_check_cert_sign_algo(_, _, []) -> {error, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm)}; @@ -2106,10 +2139,8 @@ do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) -> %% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures. -%% TODO: Uncomment when rsa_pss signatures are supported in certificates -%% compare_sign_algos(rsa_pss, Hash, Algo, Hash) -%% when Algo =:= rsa_pss_pss -> -%% true; +compare_sign_algos(rsa_pss_pss, Hash, rsa_pss_pss, Hash) -> + true; %% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or %% signature schemes. compare_sign_algos(rsa, Hash, Algo, Hash) @@ -2121,23 +2152,28 @@ compare_sign_algos(Algo, Hash, Algo, Hash) -> compare_sign_algos(_, _, _, _) -> false. - get_certificate_params(Cert) -> - {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert), - {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0), - %% Convert hash to new format - SignHash = case SignHash0 of - sha -> - sha1; - H -> H - end, - PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), - {PublicKeyAlgo, SignAlgo, SignHash}. - - + {SignAlgo0, Param, SubjectPublicKeyAlgo0, RSAKeySize} = + ssl_handshake:get_cert_params(Cert), + {SignHash, SignAlgo} = oids_to_atoms(SignAlgo0, Param), + SubjectPublicKeyAlgo = public_key_algo(SubjectPublicKeyAlgo0), + {SubjectPublicKeyAlgo, SignAlgo, SignHash, RSAKeySize}. + +oids_to_atoms(?'id-RSASSA-PSS', #'RSASSA-PSS-params'{maskGenAlgorithm = + #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = HashOid}}}) -> + Hash = public_key:pkix_hash_type(HashOid), + {Hash, rsa_pss_pss}; +oids_to_atoms(SignAlgo, _) -> + case public_key:pkix_sign_types(SignAlgo) of + {sha, Sign} -> + {sha1, Sign}; + {_,_} = Algs -> + Algs + end. %% Note: copied from ssl_handshake public_key_algo(?'id-RSASSA-PSS') -> - rsa_pss; + rsa_pss_pss; public_key_algo(?rsaEncryption) -> rsa; public_key_algo(?'id-ecPublicKey') -> @@ -2411,30 +2447,3 @@ process_user_tickets([H|T], Acc, N) -> %% (see Section 4.6.1), modulo 2^32. obfuscate_ticket_age(TicketAge, AgeAdd) -> (TicketAge + AgeAdd) rem round(math:pow(2,32)). - - -digitally_signed(Msg, HashAlgo, PrivateKey) -> - digitally_signed(Msg, HashAlgo, PrivateKey, []). - -digitally_signed(Msg, HashAlgo, PrivateKey, Options) -> - try do_digitally_signed(Msg, HashAlgo, PrivateKey, Options) of - Signature -> - Signature - catch - error:_ -> - {error, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))} - end. - -do_digitally_signed(Msg, HashAlgo, #{algorithm := Alg} = Engine, Options) -> - crypto:sign(Alg, HashAlgo, Msg, maps:remove(algorithm, Engine), Options); -do_digitally_signed(Msg, HashAlgo, Key, Options) -> - public_key:sign(Msg, HashAlgo, Key, Options). - -bad_key(#'RSAPrivateKey'{}) -> - unacceptable_rsa_key; -bad_key(#'ECPrivateKey'{}) -> - unacceptable_ecdsa_key; -bad_key(#{algorithm := rsa}) -> - unacceptable_rsa_key; -bad_key(#{algorithm := ecdsa}) -> - unacceptable_ecdsa_key. diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index dfdc0bd50b..8d67be687a 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -33,12 +33,12 @@ -include_lib("kernel/include/logger.hrl"). %% Handling of incoming data --export([get_tls_records/4, init_connection_states/2]). +-export([get_tls_records/5, init_connection_states/2]). %% Encoding TLS records -export([encode_handshake/3, encode_alert_record/3, encode_change_cipher_spec/2, encode_data/3]). --export([encode_plain_text/4, split_iovec/1]). +-export([encode_plain_text/4, split_iovec/2]). %% Decoding -export([decode_cipher_text/4]). @@ -55,7 +55,8 @@ -export_type([tls_version/0, tls_atom_version/0]). -type tls_version() :: ssl_record:ssl_version(). --type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'. +-type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2' | 'tlsv1.3'. +-type tls_max_frag_len() :: undefined | 512 | 1024 | 2048 | 4096. -compile(inline). @@ -83,6 +84,7 @@ init_connection_states(Role, BeastMitigation) -> binary(), [tls_version()] | tls_version(), Buffer0 :: binary() | {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}, + tls_max_frag_len(), ssl_options()) -> {Records :: [#ssl_tls{}], Buffer :: {'undefined' | #ssl_tls{}, {[binary()],non_neg_integer(),[binary()]}}} | @@ -92,10 +94,10 @@ init_connection_states(Role, BeastMitigation) -> %% Description: Given old buffer and new data from TCP, packs up a records %% data %%-------------------------------------------------------------------- -get_tls_records(Data, Versions, Buffer, SslOpts) when is_binary(Buffer) -> - parse_tls_records(Versions, {[Data],byte_size(Data),[]}, SslOpts, undefined); -get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, SslOpts) -> - parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, SslOpts, Hdr). +get_tls_records(Data, Versions, Buffer, MaxFragLen, SslOpts) when is_binary(Buffer) -> + parse_tls_records(Versions, {[Data],byte_size(Data),[]}, MaxFragLen, SslOpts, undefined); +get_tls_records(Data, Versions, {Hdr, {Front,Size,Rear}}, MaxFragLen, SslOpts) -> + parse_tls_records(Versions, {Front,Size + byte_size(Data),[Data|Rear]}, MaxFragLen, SslOpts, Hdr). %%==================================================================== %% Encoding @@ -112,12 +114,18 @@ encode_handshake(Frag, {3, 4}, ConnectionStates) -> encode_handshake(Frag, Version, #{current_write := #{beast_mitigation := BeastMitigation, + max_fragment_length := MaxFragmentLength, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, case iolist_size(Frag) of - N when N > ?MAX_PLAIN_TEXT_LENGTH -> - Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation), + N when N > MaxLength -> + Data = split_iovec(erlang:iolist_to_iovec(Frag), Version, BCA, BeastMitigation, MaxLength), encode_fragments(?HANDSHAKE, Version, Data, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) @@ -155,10 +163,16 @@ encode_data(Data, {3, 4}, ConnectionStates) -> tls_record_1_3:encode_data(Data, ConnectionStates); encode_data(Data, Version, #{current_write := #{beast_mitigation := BeastMitigation, + max_fragment_length := MaxFragmentLength, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> - Fragments = split_iovec(Data, Version, BCA, BeastMitigation), + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, + Fragments = split_iovec(Data, Version, BCA, BeastMitigation, MaxLength), encode_fragments(?APPLICATION_DATA, Version, Fragments, ConnectionStates). %%==================================================================== @@ -442,11 +456,11 @@ hello_version([Highest|_]) when Highest >= {3,3} -> hello_version(Versions) -> lowest_protocol_version(Versions). -split_iovec([]) -> +split_iovec([], _) -> []; -split_iovec(Data) -> - {Part,Rest} = split_iovec(Data, ?MAX_PLAIN_TEXT_LENGTH, []), - [Part|split_iovec(Rest)]. +split_iovec(Data, MaximumFragmentLength) -> + {Part,Rest} = split_iovec(Data, MaximumFragmentLength, []), + [Part|split_iovec(Rest, MaximumFragmentLength)]. %%-------------------------------------------------------------------- %%% Internal functions @@ -461,7 +475,8 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> mac_secret => undefined, secure_renegotiation => undefined, client_verify_data => undefined, - server_verify_data => undefined + server_verify_data => undefined, + max_fragment_length => undefined }. %% Used by logging to recreate the received bytes @@ -470,88 +485,92 @@ build_tls_record(#ssl_tls{type = Type, version = {MajVer, MinVer}, fragment = Fr <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer),?UINT16(Length), Fragment/binary>>. -parse_tls_records(Versions, Q, SslOpts, undefined) -> - decode_tls_records(Versions, Q, SslOpts, [], undefined, undefined, undefined); -parse_tls_records(Versions, Q, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) -> - decode_tls_records(Versions, Q, SslOpts, [], Type, Version, Length). +parse_tls_records(Versions, Q, MaxFragLen, SslOpts, undefined) -> + decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], undefined, undefined, undefined); +parse_tls_records(Versions, Q, MaxFragLen, SslOpts, #ssl_tls{type = Type, version = Version, fragment = Length}) -> + decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [], Type, Version, Length). %% Generic code path -decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, undefined, _Version, _Length) -> +decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, undefined, _Version, _Length) -> if 5 =< Size -> {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(5, Q0), - validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length); + validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length); 3 =< Size -> {<<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(3, Q0), - validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); + validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); 1 =< Size -> {<<?BYTE(Type)>>, Q} = binary_from_front(1, Q0), - validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, undefined, undefined); + validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, undefined, undefined); true -> - validate_tls_records_type(Versions, Q0, SslOpts, Acc, undefined, undefined, undefined) + validate_tls_records_type(Versions, Q0, MaxFragLen, SslOpts, Acc, undefined, undefined, undefined) end; -decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, undefined, _Length) -> +decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, undefined, _Length) -> if 4 =< Size -> {<<?BYTE(MajVer),?BYTE(MinVer), ?UINT16(Length)>>, Q} = binary_from_front(4, Q0), - validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, Length); + validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, Length); 2 =< Size -> {<<?BYTE(MajVer),?BYTE(MinVer)>>, Q} = binary_from_front(2, Q0), - validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); + validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, {MajVer,MinVer}, undefined); true -> - validate_tls_record_version(Versions, Q0, SslOpts, Acc, Type, undefined, undefined) + validate_tls_record_version(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, undefined, undefined) end; -decode_tls_records(Versions, {_,Size,_} = Q0, SslOpts, Acc, Type, Version, undefined) -> +decode_tls_records(Versions, {_,Size,_} = Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined) -> if 2 =< Size -> {<<?UINT16(Length)>>, Q} = binary_from_front(2, Q0), - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); true -> - validate_tls_record_length(Versions, Q0, SslOpts, Acc, Type, Version, undefined) + validate_tls_record_length(Versions, Q0, MaxFragLen, SslOpts, Acc, Type, Version, undefined) end; -decode_tls_records(Versions, Q, SslOpts, Acc, Type, Version, Length) -> - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length). +decode_tls_records(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) -> + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length). -validate_tls_records_type(_Versions, Q, _SslOpts, Acc, undefined, _Version, _Length) -> +validate_tls_records_type(_Versions, Q, _MaxFragLen, _SslOpts, Acc, undefined, _Version, _Length) -> {lists:reverse(Acc), {undefined, Q}}; -validate_tls_records_type(Versions, Q, SslOpts, Acc, Type, Version, Length) -> +validate_tls_records_type(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) -> if ?KNOWN_RECORD_TYPE(Type) -> - validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); true -> %% Not ?KNOWN_RECORD_TYPE(Type) ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, {unsupported_record_type, Type}) end. -validate_tls_record_version(_Versions, Q, _SslOpts, Acc, Type, undefined, _Length) -> +validate_tls_record_version(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, undefined, _Length) -> {lists:reverse(Acc), {#ssl_tls{type = Type, version = undefined, fragment = undefined}, Q}}; -validate_tls_record_version(Versions, Q, SslOpts, Acc, Type, Version, Length) -> +validate_tls_record_version(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length) -> case Versions of _ when is_list(Versions) -> case is_acceptable_version(Version, Versions) of true -> - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); false -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version}) end; {3, 4} when Version =:= {3, 3} -> - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); Version -> %% Exact version match - validate_tls_record_length(Versions, Q, SslOpts, Acc, Type, Version, Length); + validate_tls_record_length(Versions, Q, MaxFragLen, SslOpts, Acc, Type, Version, Length); _ -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {unsupported_version, Version}) end. -validate_tls_record_length(_Versions, Q, _SslOpts, Acc, Type, Version, undefined) -> +validate_tls_record_length(_Versions, Q, _MaxFragLen, _SslOpts, Acc, Type, Version, undefined) -> {lists:reverse(Acc), {#ssl_tls{type = Type, version = Version, fragment = undefined}, Q}}; -validate_tls_record_length(Versions, {_,Size0,_} = Q0, +validate_tls_record_length(Versions, {_,Size0,_} = Q0, MaxFragLen, #{log_level := LogLevel} = SslOpts, Acc, Type, Version, Length) -> - Max = max_len(Versions), + Max = if is_integer(MaxFragLen) -> + MaxFragLen + ?MAX_PADDING_LENGTH + ?MAX_MAC_LENGTH; + true -> + max_len(Versions) + end, if Length =< Max -> if @@ -560,7 +579,7 @@ validate_tls_record_length(Versions, {_,Size0,_} = Q0, {Fragment, Q} = binary_from_front(Length, Q0), Record = #ssl_tls{type = Type, version = Version, fragment = Fragment}, ssl_logger:debug(LogLevel, inbound, 'record', Record), - decode_tls_records(Versions, Q, SslOpts, [Record|Acc], undefined, undefined, undefined); + decode_tls_records(Versions, Q, MaxFragLen, SslOpts, [Record|Acc], undefined, undefined, undefined); true -> {lists:reverse(Acc), {#ssl_tls{type = Type, version = Version, fragment = Length}, Q0}} @@ -669,20 +688,20 @@ encode_fragments(_Type, _Version, _Data, CS, _CompS, _CipherS, _Seq, _CipherFrag %% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are %% not vulnerable to this attack. -split_iovec(Data, Version, BCA, one_n_minus_one) +split_iovec(Data, Version, BCA, one_n_minus_one, MaxLength) when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse {3, 0} == Version) -> {Part, RestData} = split_iovec(Data, 1, []), - [Part|split_iovec(RestData)]; + [Part|split_iovec(RestData, MaxLength)]; %% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 %% splitting. -split_iovec(Data, Version, BCA, zero_n) +split_iovec(Data, Version, BCA, zero_n, MaxLength) when (BCA =/= ?RC4) andalso ({3, 1} == Version orelse {3, 0} == Version) -> {Part, RestData} = split_iovec(Data, 0, []), - [Part|split_iovec(RestData)]; -split_iovec(Data, _Version, _BCA, _BeatMitigation) -> - split_iovec(Data). + [Part|split_iovec(RestData, MaxLength)]; +split_iovec(Data, _Version, _BCA, _BeatMitigation, MaxLength) -> + split_iovec(Data, MaxLength). split_iovec([Bin|Data] = Bin_Data, SplitSize, Acc) -> BinSize = byte_size(Bin), diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index 89f2a484ff..a9ba415099 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -43,11 +43,17 @@ % %% Description: Encodes a handshake message to send on the tls-1.3-socket. %%-------------------------------------------------------------------- -encode_handshake(Frag, ConnectionStates) -> +encode_handshake(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} = + ConnectionStates) -> + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + %% TODO: Consider padding here + ?MAX_PLAIN_TEXT_LENGTH + end, case iolist_size(Frag) of - N when N > ?MAX_PLAIN_TEXT_LENGTH -> - %% TODO: Consider padding here - Data = tls_record:split_iovec(Frag), + N when N > MaxLength -> + Data = tls_record:split_iovec(erlang:iolist_to_iovec(Frag), MaxLength), encode_iolist(?HANDSHAKE, Data, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Frag, ConnectionStates) @@ -69,8 +75,14 @@ encode_alert_record(#alert{level = Level, description = Description}, %% %% Description: Encodes data to send on the ssl-socket. %%-------------------------------------------------------------------- -encode_data(Frag, ConnectionStates) -> - Data = tls_record:split_iovec(Frag), +encode_data(Frag, #{current_write := #{max_fragment_length := MaxFragmentLength}} = + ConnectionStates) -> + MaxLength = if is_integer(MaxFragmentLength) -> + MaxFragmentLength; + true -> + ?MAX_PLAIN_TEXT_LENGTH + end, + Data = tls_record:split_iovec(Frag, MaxLength), encode_iolist(?APPLICATION_DATA, Data, ConnectionStates). encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) -> diff --git a/lib/ssl/src/tls_server_session_ticket.erl b/lib/ssl/src/tls_server_session_ticket.erl index a804f81eaa..9517cc5afd 100644 --- a/lib/ssl/src/tls_server_session_ticket.erl +++ b/lib/ssl/src/tls_server_session_ticket.erl @@ -81,8 +81,8 @@ init(Args) -> handle_call({new_session_ticket, Prf, MasterSecret}, _From, #state{nonce = Nonce, lifetime = LifeTime, - stateful = #{}} = State0) -> - Id = stateful_psk_id(), + stateful = #{id_generator := IdGen}} = State0) -> + Id = stateful_psk_ticket_id(IdGen), PSK = tls_v1:pre_shared_key(MasterSecret, ticket_nonce(Nonce), Prf), SessionTicket = new_session_ticket(Id, Nonce, LifeTime), State = stateful_ticket_store(Id, SessionTicket, Prf, PSK, State0), @@ -166,7 +166,8 @@ inital_state([stateful, Lifetime, TicketStoreSize|_]) -> nonce = 0, stateful = #{db => stateful_store(), max => TicketStoreSize, - ref_index => #{} + ref_index => #{}, + id_generator => crypto:strong_rand_bytes(16) } }. @@ -295,8 +296,13 @@ stateful_living_ticket({TimeStamp,_}, Lived < LifeTime. -stateful_psk_id() -> - term_to_binary(make_ref()). +stateful_psk_ticket_id(Key) -> + Unique = erlang:unique_integer(), + %% Obfuscate to avoid DoS attack possiblities + %% that could invalidate tickets and render them + %% unusable. This id should be unpredictable + %% and unique but have no other cryptographic requirements. + crypto:crypto_one_time(aes_128_ecb, Key, <<Unique:128>>, true). %%%=================================================================== %%% Stateless ticket diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 8237886f27..8e6807d0ab 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -29,22 +29,52 @@ -include("ssl_internal.hrl"). -include("ssl_record.hrl"). --export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, hmac_hash/3, - setup_keys/8, suites/1, prf/5, - ecc_curves/1, ecc_curves/2, oid_to_enum/1, enum_to_oid/1, - default_signature_algs/1, signature_algs/2, - default_signature_schemes/1, signature_schemes/2, - groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]). - --export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4, - key_schedule/3, key_schedule/4, create_info/3, - external_binder_key/2, resumption_binder_key/2, - client_early_traffic_secret/3, early_exporter_master_secret/3, - client_handshake_traffic_secret/3, server_handshake_traffic_secret/3, - client_application_traffic_secret_0/3, server_application_traffic_secret_0/3, - exporter_master_secret/3, resumption_master_secret/3, - update_traffic_secret/2, calculate_traffic_keys/3, - transcript_hash/2, finished_key/2, finished_verify_data/3, pre_shared_key/3]). +-export([master_secret/4, + finished/5, + certificate_verify/3, + mac_hash/7, + hmac_hash/3, + setup_keys/8, + suites/1, + exclusive_suites/1, + prf/5, + ecc_curves/1, + ecc_curves/2, + oid_to_enum/1, + enum_to_oid/1, + default_signature_algs/1, + signature_algs/2, + default_signature_schemes/1, + signature_schemes/2, + groups/1, + groups/2, + group_to_enum/1, + enum_to_group/1, + default_groups/1]). + +-export([derive_secret/4, + hkdf_expand_label/5, + hkdf_extract/3, + hkdf_expand/4, + key_schedule/3, + key_schedule/4, + create_info/3, + external_binder_key/2, + resumption_binder_key/2, + client_early_traffic_secret/3, + early_exporter_master_secret/3, + client_handshake_traffic_secret/3, + server_handshake_traffic_secret/3, + client_application_traffic_secret_0/3, + server_application_traffic_secret_0/3, + exporter_master_secret/3, + resumption_master_secret/3, + update_traffic_secret/2, + calculate_traffic_keys/3, + transcript_hash/2, + finished_key/2, + finished_verify_data/3, + pre_shared_key/3]). -type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 | sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 | @@ -453,7 +483,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, %% TODO 1.3 same as above? --spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()]. +-spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()]. suites(Minor) when Minor == 1; Minor == 2 -> [ @@ -472,31 +502,57 @@ suites(Minor) when Minor == 1; Minor == 2 -> ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA ]; suites(3) -> + exclusive_suites(3) ++ suites(2); + +suites(4) -> + exclusive_suites(4) ++ suites(3). + +exclusive_suites(4) -> + [?TLS_AES_256_GCM_SHA384, + ?TLS_AES_128_GCM_SHA256, + ?TLS_CHACHA20_POLY1305_SHA256, + ?TLS_AES_128_CCM_SHA256, + ?TLS_AES_128_CCM_8_SHA256 + ]; +exclusive_suites(3) -> [?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 @@ -505,25 +561,24 @@ suites(3) -> %% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384, %% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256, %% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256 - ] ++ suites(2); - -suites(4) -> - [?TLS_AES_256_GCM_SHA384, - ?TLS_AES_128_GCM_SHA256, - ?TLS_CHACHA20_POLY1305_SHA256, - ?TLS_AES_128_CCM_SHA256, - ?TLS_AES_128_CCM_8_SHA256 - ] ++ suites(3); + ]; +exclusive_suites(Minor) when Minor == 1; Minor == 2 -> + [ + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, -suites('TLS_v1.3') -> - [?TLS_AES_256_GCM_SHA384, - ?TLS_AES_128_GCM_SHA256, - ?TLS_CHACHA20_POLY1305_SHA256, - ?TLS_AES_128_CCM_SHA256, - ?TLS_AES_128_CCM_8_SHA256 + ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA ]. - signature_algs({3, 4}, HashSigns) -> signature_algs({3, 3}, HashSigns); signature_algs({3, 3}, HashSigns) -> diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index f4324f2f13..27b7598991 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -54,6 +54,7 @@ MODULES = \ ssl_npn_SUITE \ openssl_npn_SUITE\ openssl_sni_SUITE\ + ssl_mfl_SUITE \ ssl_renegotiate_SUITE\ openssl_renegotiate_SUITE\ openssl_reject_SUITE\ diff --git a/lib/ssl/test/openssl_alpn_SUITE.erl b/lib/ssl/test/openssl_alpn_SUITE.erl index fc18d053aa..409c90b0a8 100644 --- a/lib/ssl/test/openssl_alpn_SUITE.erl +++ b/lib/ssl/test/openssl_alpn_SUITE.erl @@ -129,7 +129,7 @@ init_per_testcase(TestCase, Config) -> special_init(erlang_client_alpn_openssl_server_alpn_renegotiate, Config) -> {ok, Version} = application:get_env(ssl, protocol_version), - case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of + case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of {skip, _} = Skip -> Skip; Config -> @@ -137,7 +137,7 @@ special_init(erlang_client_alpn_openssl_server_alpn_renegotiate, Config) -> end; special_init(erlang_server_alpn_openssl_client_alpn_renegotiate, Config) -> {ok, Version} = application:get_env(ssl, protocol_version), - case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of + case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of {skip, _} = Skip -> Skip; Config -> @@ -299,7 +299,7 @@ start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callba {from, self()}, {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), + {options, ClientOpts}]), Callback(Client, OpensslPort), @@ -368,7 +368,7 @@ start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Ca {from, self()}, {mfa, {ssl_test_lib, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, - {options, [{reuse_sessions, false} | ClientOpts]}]), + {options, ClientOpts}]), Callback(Client, OpensslPort), diff --git a/lib/ssl/test/openssl_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_cipher_suite_SUITE.erl index 048fbb1e92..5246cc028e 100644 --- a/lib/ssl/test/openssl_cipher_suite_SUITE.erl +++ b/lib/ssl/test/openssl_cipher_suite_SUITE.erl @@ -27,7 +27,7 @@ -include_lib("common_test/include/ct.hrl"). --define(DEFAULT_TIMEOUT, {seconds, 30}). +-define(DEFAULT_TIMEOUT, {seconds, 6}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- diff --git a/lib/ssl/test/openssl_client_cert_SUITE.erl b/lib/ssl/test/openssl_client_cert_SUITE.erl index d04ba601cf..3c97822915 100644 --- a/lib/ssl/test/openssl_client_cert_SUITE.erl +++ b/lib/ssl/test/openssl_client_cert_SUITE.erl @@ -48,6 +48,8 @@ groups() -> {dsa, [], all_version_tests()}, {rsa_1_3, [], all_version_tests() ++ tls_1_3_tests() ++ [unsupported_sign_algo_client_auth, unsupported_sign_algo_cert_client_auth]}, + {rsa_pss_rsae, [], all_version_tests() ++ tls_1_3_tests()}, + {rsa_pss_pss, [], all_version_tests() ++ tls_1_3_tests()}, {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} ]. @@ -71,6 +73,8 @@ pre_tls_1_3_protocol_groups() -> tls_1_3_protocol_groups() -> [{group, rsa_1_3}, + {group, rsa_pss_rsae}, + {group, rsa_pss_pss}, {group, ecdsa_1_3}]. tls_1_3_tests() -> @@ -139,6 +143,28 @@ init_per_group(Group, Config0) when Group == rsa; [] -> {skip, {no_sup, Group, Version}} end; +init_per_group(Alg, Config) when Alg == rsa_pss_rsae; + Alg == rsa_pss_pss -> + Supports = crypto:supports(), + RSAOpts = proplists:get_value(rsa_opts, Supports), + + case lists:member(rsa_pkcs1_pss_padding, RSAOpts) + andalso lists:member(rsa_pss_saltlen, RSAOpts) + andalso lists:member(rsa_mgf1_md, RSAOpts) + andalso ssl_test_lib:is_sane_oppenssl_pss(Alg) + of + true -> + #{client_config := COpts, + server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(Alg, [], Config, ""), + [{cert_key_alg, Alg} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + false -> + {skip, "Missing crypto or OpenSSL support"} + end; init_per_group(Group, Config0) when Group == ecdsa; Group == ecdsa_1_3 -> PKAlg = crypto:supports(public_keys), diff --git a/lib/ssl/test/openssl_npn_SUITE.erl b/lib/ssl/test/openssl_npn_SUITE.erl index 11b2e46358..a37a4bf1f6 100644 --- a/lib/ssl/test/openssl_npn_SUITE.erl +++ b/lib/ssl/test/openssl_npn_SUITE.erl @@ -88,30 +88,10 @@ end_per_suite(_Config) -> ssl_test_lib:kill_openssl(). init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - Config - end. + ssl_test_lib:init_per_group_openssl(GroupName, Config). end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. + ssl_test_lib:end_per_group(GroupName, Config). init_per_testcase(TestCase, Config) -> ct:timetrap({seconds, 10}), @@ -119,10 +99,10 @@ init_per_testcase(TestCase, Config) -> special_init(erlang_client_openssl_server_npn_renegotiate, Config) -> {ok, Version} = application:get_env(ssl, protocol_version), - ssl_test_lib:check_sane_openssl_renegotaite(Config, Version); + ssl_test_lib:check_sane_openssl_renegotiate(Config, Version); special_init(erlang_server_openssl_client_npn_renegotiate, Config) -> {ok, Version} = application:get_env(ssl, protocol_version), - case ssl_test_lib:check_sane_openssl_renegotaite(Config, Version) of + case ssl_test_lib:check_sane_openssl_renegotiate(Config, Version) of Config -> ssl_test_lib:openssl_allows_client_renegotiate(Config); Skip -> diff --git a/lib/ssl/test/openssl_reject_SUITE.erl b/lib/ssl/test/openssl_reject_SUITE.erl index a637035f3c..d451f8437e 100644 --- a/lib/ssl/test/openssl_reject_SUITE.erl +++ b/lib/ssl/test/openssl_reject_SUITE.erl @@ -74,30 +74,10 @@ end_per_suite(_Config) -> ssl_test_lib:kill_openssl(). init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - Config - end. + ssl_test_lib:init_per_group_openssl(GroupName, Config). end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. + ssl_test_lib:end_per_group(GroupName, Config). init_per_testcase(TestCase, Config) -> ct:timetrap({seconds, 10}), diff --git a/lib/ssl/test/openssl_renegotiate_SUITE.erl b/lib/ssl/test/openssl_renegotiate_SUITE.erl index 78cd4446fc..66dfdc8115 100644 --- a/lib/ssl/test/openssl_renegotiate_SUITE.erl +++ b/lib/ssl/test/openssl_renegotiate_SUITE.erl @@ -96,32 +96,20 @@ end_per_suite(_Config) -> ssl_test_lib:kill_openssl(). init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of + case ssl_test_lib:check_sane_openssl_version(GroupName) of true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:check_sane_openssl_renegotiate( - ssl_test_lib:init_tls_version(GroupName, Config), - GroupName); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - Config + case ssl_test_lib:check_sane_openssl_renegotiate(Config, GroupName) of + {skip,_} = Skip -> + Skip; + _ -> + ssl_test_lib:init_per_group_openssl(GroupName, Config) + end; + false -> + {skip, {atom_to_list(GroupName) ++ " not supported by OpenSSL"}} end. - end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. + ssl_test_lib:end_per_group(GroupName, Config). + init_per_testcase(erlang_client_openssl_server_nowrap_seqnum, Config) -> ct:timetrap(?DEFAULT_TIMEOUT), ssl_test_lib:openssl_allows_client_renegotiate(Config); diff --git a/lib/ssl/test/openssl_server_cert_SUITE.erl b/lib/ssl/test/openssl_server_cert_SUITE.erl index 9d8e095460..805da27510 100644 --- a/lib/ssl/test/openssl_server_cert_SUITE.erl +++ b/lib/ssl/test/openssl_server_cert_SUITE.erl @@ -49,6 +49,8 @@ groups() -> %% TODO: Create proper conf of openssl server %%++ [unsupported_sign_algo_client_auth, %% unsupported_sign_algo_cert_client_auth]}, + {rsa_pss_rsae, [], all_version_tests() ++ tls_1_3_tests()}, + {rsa_pss_pss, [], all_version_tests() ++ tls_1_3_tests()}, {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} ]. @@ -72,6 +74,8 @@ pre_tls_1_3_protocol_groups() -> tls_1_3_protocol_groups() -> [{group, rsa_1_3}, + {group, rsa_pss_rsae}, + {group, rsa_pss_pss}, {group, ecdsa_1_3}]. tls_1_3_tests() -> @@ -151,6 +155,28 @@ init_per_group(rsa_1_3 = Group, Config0) -> [] -> {skip, {no_sup, Group, Version}} end; +init_per_group(Alg, Config) when Alg == rsa_pss_rsae; + Alg == rsa_pss_pss -> + Supports = crypto:supports(), + RSAOpts = proplists:get_value(rsa_opts, Supports), + + case lists:member(rsa_pkcs1_pss_padding, RSAOpts) + andalso lists:member(rsa_pss_saltlen, RSAOpts) + andalso lists:member(rsa_mgf1_md, RSAOpts) + andalso ssl_test_lib:is_sane_oppenssl_pss(Alg) + of + true -> + #{client_config := COpts, + server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(Alg, [], Config, ""), + [{cert_key_alg, Alg} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + false -> + {skip, "Missing crypto or OpenSSL support"} + end; init_per_group(ecdsa = Group, Config0) -> PKAlg = crypto:supports(public_keys), case lists:member(ecdsa, PKAlg) andalso (lists:member(ecdh, PKAlg) orelse diff --git a/lib/ssl/test/openssl_session_SUITE.erl b/lib/ssl/test/openssl_session_SUITE.erl index e528936eef..ae66cdeb51 100644 --- a/lib/ssl/test/openssl_session_SUITE.erl +++ b/lib/ssl/test/openssl_session_SUITE.erl @@ -28,6 +28,7 @@ -define(SLEEP, 1000). -define(EXPIRE, 10). +-define(TIMEOUT, {seconds, 120}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -93,31 +94,10 @@ end_per_suite(_Config) -> ssl_test_lib:kill_openssl(). init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:supports_ssl_tls_version(GroupName) of - true -> - case ssl_test_lib:check_sane_openssl_version(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, openssl_does_not_support_version} - end; - false -> - {skip, openssl_does_not_support_version} - end; - _ -> - ssl:start(), - Config - end. + ssl_test_lib:init_per_group_openssl(GroupName, Config). end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. + ssl_test_lib:end_per_group(GroupName, Config). init_per_testcase(reuse_session_erlang_client, Config) -> ct:timetrap(?EXPIRE * 1000 * 5), @@ -132,17 +112,17 @@ init_per_testcase(reuse_session_erlang_server, Config) -> true -> case ssl_test_lib:openssl_sane_dtls_session_reuse() of true -> - ct:timetrap({seconds, 10}), + ct:timetrap(?TIMEOUT), Config; false -> {skip, "Broken OpenSSL DTLS session reuse"} end; false -> - ct:timetrap({seconds, 10}), + ct:timetrap(?TIMEOUT), Config end; -init_per_testcase(TestCase, Config) -> - ct:timetrap({seconds, 10}), +init_per_testcase(_TestCase, Config) -> + ct:timetrap(?TIMEOUT), Config. end_per_testcase(reuse_session_erlang_client, Config) -> diff --git a/lib/ssl/test/openssl_sni_SUITE.erl b/lib/ssl/test/openssl_sni_SUITE.erl index 3010eabf4e..070d94245e 100644 --- a/lib/ssl/test/openssl_sni_SUITE.erl +++ b/lib/ssl/test/openssl_sni_SUITE.erl @@ -214,11 +214,13 @@ send_and_hostname(SSLSocket) -> end. openssl_client_args(Version, Hostname, Port) -> - ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)]. + ssl_test_lib:maybe_force_ipv4(["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)]). openssl_client_args(Version, Hostname, Port, ServerName) -> - ["s_client", "-connect", Hostname ++ ":" ++ - integer_to_list(Port), ssl_test_lib:version_flag(Version), "-servername", ServerName]. + ssl_test_lib:maybe_force_ipv4(["s_client", "-connect", Hostname ++ ":" ++ + integer_to_list(Port), + ssl_test_lib:version_flag(Version), "-servername", ServerName]). check_openssl_sni_support(Config) -> HelpText = os:cmd("openssl s_client --help"), diff --git a/lib/ssl/test/ssl_alpn_SUITE.erl b/lib/ssl/test/ssl_alpn_SUITE.erl index 6fb08671c1..424776293a 100644 --- a/lib/ssl/test/ssl_alpn_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_SUITE.erl @@ -61,7 +61,6 @@ alpn_tests() -> client_alpn_and_server_alpn, client_alpn_and_server_no_support, client_no_support_and_server_alpn, - client_alpn_npn_and_server_alpn, client_renegotiate, session_reused ]. @@ -69,7 +68,8 @@ alpn_tests() -> alpn_npn_coexist() -> [ client_alpn_npn_and_server_alpn_npn, - client_alpn_and_server_alpn_npn + client_alpn_and_server_alpn_npn, + client_alpn_npn_and_server_alpn ]. diff --git a/lib/ssl/test/ssl_api_SUITE.erl b/lib/ssl/test/ssl_api_SUITE.erl index a80363227f..9856c5db0f 100644 --- a/lib/ssl/test/ssl_api_SUITE.erl +++ b/lib/ssl/test/ssl_api_SUITE.erl @@ -43,9 +43,14 @@ all() -> groups() -> [ - {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ handshake_paus_tests()) -- - [dh_params, honor_server_cipher_order, honor_client_cipher_order, - new_options_in_handshake, handshake_continue_tls13_client]) + {'tlsv1.3', [], ((gen_api_tests() ++ tls13_group() ++ + handshake_paus_tests()) -- + [dh_params, + honor_server_cipher_order, + honor_client_cipher_order, + new_options_in_handshake, + handshake_continue_tls13_client, + invalid_options]) ++ (since_1_2() -- [conf_signature_algs])}, {'tlsv1.2', [], gen_api_tests() ++ since_1_2() ++ handshake_paus_tests() ++ pre_1_3()}, {'tlsv1.1', [], gen_api_tests() ++ handshake_paus_tests() ++ pre_1_3()}, @@ -68,7 +73,8 @@ since_1_2() -> pre_1_3() -> [ - default_reject_anonymous + default_reject_anonymous, + connection_information_with_srp ]. gen_api_tests() -> [ @@ -140,10 +146,10 @@ tls13_group() -> client_options_negative_dependency_stateless, client_options_negative_dependency_role, server_options_negative_version_gap, - server_options_negative_dependency_role + server_options_negative_dependency_role, + invalid_options_tls13 ]. - init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of @@ -197,6 +203,14 @@ init_per_testcase(handshake_continue_tls13_client, Config) -> false -> {skip, "Missing crypto support: TLS 1.3 not supported"} end; +init_per_testcase(connection_information_with_srp, Config) -> + PKAlg = proplists:get_value(public_keys, crypto:supports()), + case lists:member(srp, PKAlg) of + true -> + Config; + false -> + {skip, "Missing SRP crypto support"} + end; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 10}), @@ -304,6 +318,56 @@ connection_information(Config) when is_list(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +connection_information_with_srp() -> + [{doc,"Test the result of API function ssl:connection_information/1" + "includes srp_username."}]. +connection_information_with_srp(Config) when is_list(Config) -> + run_conn_info_srp_test(srp_anon, 'aes_128_cbc', Config). + +run_conn_info_srp_test(Kex, Cipher, Config) -> + Version = ssl_test_lib:protocol_version(Config), + TestCiphers = ssl_test_lib:test_ciphers(Kex, Cipher, Version), + + case TestCiphers of + [] -> + {skip, {not_sup, Kex, Cipher, Version}}; + [TestCipher | _T] -> + do_run_conn_info_srp_test(TestCipher, Version, Config) + end. + +do_run_conn_info_srp_test(ErlangCipherSuite, Version, Config) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + SOpts = [{user_lookup_fun, {fun ssl_test_lib:user_lookup/3, undefined}}], + COpts = [{srp_identity, {"Test-User", "secret"}}], + + ServerOpts = ssl_test_lib:ssl_options(SOpts, Config), + ClientOpts = ssl_test_lib:ssl_options(COpts, Config), + + ct:log("Erlang Cipher Suite is: ~p~n", [ErlangCipherSuite]), + + Server = ssl_test_lib:start_server([ + {node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, check_srp_in_connection_information, [<<"Test-User">>, server]}}, + {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} | + ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, check_srp_in_connection_information, [<<"Test-User">>, client]}}, + {options, [{versions, [Version]}, {ciphers, [ErlangCipherSuite]} | + ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + %%-------------------------------------------------------------------- secret_connection_info() -> @@ -1554,7 +1618,6 @@ invalid_options(Config) when is_list(Config) -> {verify, 4}, {verify_fun, function}, {fail_if_no_peer_cert, 0}, - {verify_client_once, 1}, {depth, four}, {certfile, 'cert.pem'}, {keyfile,'key.pem' }, @@ -1572,6 +1635,20 @@ invalid_options(Config) when is_list(Config) -> {active, trice}, {key, 'key.pem' }], + TestOpts2 = + [{[{anti_replay, '10k'}], + %% anti_replay is a server only option but tested with client + %% for simplicity + {options,dependency,{anti_replay,{versions,['tlsv1.3']}}}}, + {[{supported_groups, []}], + {options,dependency,{supported_groups,{versions,['tlsv1.3']}}}}, + {[{use_ticket, [<<1,2,3,4>>]}], + {options,dependency,{use_ticket,{versions,['tlsv1.3']}}}}, + {[{verify, verify_none}, {fail_if_no_peer_cert, true}], + {options, incompatible, + {verify, verify_none}, + {fail_if_no_peer_cert, true}}}], + [begin Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, @@ -1585,7 +1662,13 @@ invalid_options(Config) when is_list(Config) -> Check(Client, Server, TestOpt), ok end || TestOpt <- TestOpts], + + [begin + start_client_negative(Config, TestOpt, ErrorMsg), + ok + end || {TestOpt, ErrorMsg} <- TestOpts2], ok. + %%------------------------------------------------------------------- default_reject_anonymous()-> @@ -1853,6 +1936,103 @@ getstat(Config) when is_list(Config) -> {options, ClientOpts}]), ssl_test_lib:check_result(Server, ok, Client, ok). +invalid_options_tls13() -> + [{doc, "Test invalid options with TLS 1.3"}]. +invalid_options_tls13(Config) when is_list(Config) -> + TestOpts = + [{{beast_mitigation, one_n_minus_one}, + {options, dependency, + {beast_mitigation,{versions,[tlsv1]}}}, + common}, + + {{next_protocols_advertised, [<<"http/1.1">>]}, + {options, dependency, + {next_protocols_advertised, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + server}, + + {{client_preferred_next_protocols, + {client, [<<"http/1.1">>]}}, + {options, dependency, + {client_preferred_next_protocols, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + client}, + + {{client_renegotiation, false}, + {options, dependency, + {client_renegotiation, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + server + }, + + {{padding_check, false}, + {options, dependency, + {padding_check,{versions,[tlsv1]}}}, + common}, + + {{psk_identity, "Test-User"}, + {options, dependency, + {psk_identity,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + common}, + + {{user_lookup_fun, + {fun ssl_test_lib:user_lookup/3, <<1,2,3>>}}, + {options, dependency, + {user_lookup_fun,{versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + common}, + + {{reuse_session, fun(_,_,_,_) -> false end}, + {options, dependency, + {reuse_session, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + server}, + + {{reuse_session, <<1,2,3,4>>}, + {options, dependency, + {reuse_session, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + client}, + + {{reuse_sessions, true}, + {options, dependency, + {reuse_sessions, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + common}, + + {{secure_renegotiate, false}, + {options, dependency, + {secure_renegotiate, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + common}, + + {{srp_identity, false}, + {options, dependency, + {srp_identity, + {versions,[tlsv1,'tlsv1.1','tlsv1.2']}}}, + client} + ], + + Fun = fun(Option, ErrorMsg, Type) -> + case Type of + server -> + start_server_negative(Config, + [Option,{versions, ['tlsv1.3']}], + ErrorMsg); + client -> + start_client_negative(Config, + [Option,{versions, ['tlsv1.3']}], + ErrorMsg); + common -> + start_server_negative(Config, + [Option,{versions, ['tlsv1.3']}], + ErrorMsg), + start_client_negative(Config, + [Option,{versions, ['tlsv1.3']}], + ErrorMsg) + end + end, + [Fun(Option, ErrorMsg, Type) || {Option, ErrorMsg, Type} <- TestOpts]. + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ @@ -1875,7 +2055,18 @@ secret_connection_info_result(Socket) -> {ok, [{protocol, Protocol}]} = ssl:connection_information(Socket, [protocol]), {ok, ConnInfo} = ssl:connection_information(Socket, [client_random, server_random, master_secret]), check_connection_info(Protocol, ConnInfo). - + +check_srp_in_connection_information(_Socket, _Username, client) -> + ok; +check_srp_in_connection_information(Socket, Username, server) -> + {ok, Info} = ssl:connection_information(Socket), + ct:log("Info ~p~n", [Info]), + case proplists:get_value(srp_username, Info, not_found) of + Username -> + ok; + not_found -> + ct:fail(srp_username_not_found) + end. %% In TLS 1.3 the master_secret field is used to store multiple secrets from the key schedule and it is a tuple. %% client_random and server_random are not used in the TLS 1.3 key schedule. diff --git a/lib/ssl/test/ssl_app_env_SUITE.erl b/lib/ssl/test/ssl_app_env_SUITE.erl index 498bed2573..d337dabb69 100644 --- a/lib/ssl/test/ssl_app_env_SUITE.erl +++ b/lib/ssl/test/ssl_app_env_SUITE.erl @@ -25,7 +25,7 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include_lib("ssl/src/ssl_api.hrl"). - +-define(TIMEOUT, {seconds, 5}). -define(SLEEP, 500). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -94,14 +94,14 @@ init_per_testcase(internal_active_1, Config) -> application:set_env(ssl, internal_active_n, 1), ssl_test_lib:set_protocol_versions(Version), ssl:start(), - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), ssl_test_lib:ct_log_supported_protocol_versions(Config), Config; init_per_testcase(protocol_versions, Config) -> Version = ssl_test_lib:protocol_version(Config), ssl_test_lib:set_protocol_versions(Version), ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; init_per_testcase(empty_protocol_versions, Config) -> ssl:stop(), @@ -111,10 +111,10 @@ init_per_testcase(empty_protocol_versions, Config) -> application:set_env(ssl, dtls_protocol_version, []), ssl:start(), ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; init_per_testcase(_TestCase, Config) -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(_, Config) -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 18c984d76b..2ca6c7de3d 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -125,7 +125,7 @@ appup(Config) when is_list(Config) -> version_option() -> [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. version_option(Config) when is_list(Config) -> - Versions = proplists:get_value(default_supported, ssl:versions()), + Versions = proplists:get_value(supported, ssl:versions()), [version_option_test(Config, Version) || Version <- Versions]. %%-------------------------------------------------------------------- @@ -174,13 +174,13 @@ connect_twice(Config) when is_list(Config) -> defaults(Config) when is_list(Config)-> Versions = ssl:versions(), false = lists:member(sslv3, proplists:get_value(available, Versions)), - false = lists:member(sslv3, proplists:get_value(default_supported, Versions)), + false = lists:member(sslv3, proplists:get_value(supported, Versions)), true = lists:member('tlsv1', proplists:get_value(available, Versions)), - false = lists:member('tlsv1', proplists:get_value(default_supported, Versions)), + false = lists:member('tlsv1', proplists:get_value(supported, Versions)), true = lists:member('tlsv1.1', proplists:get_value(available, Versions)), - false = lists:member('tlsv1.1', proplists:get_value(default_supported, Versions)), + false = lists:member('tlsv1.1', proplists:get_value(supported, Versions)), true = lists:member('tlsv1.2', proplists:get_value(available, Versions)), - true = lists:member('tlsv1.2', proplists:get_value(default_supported, Versions)), + true = lists:member('tlsv1.2', proplists:get_value(supported, Versions)), false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), @@ -189,8 +189,8 @@ defaults(Config) when is_list(Config)-> true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)), true = lists:member('dtlsv1.2', proplists:get_value(available_dtls, Versions)), true = lists:member('dtlsv1', proplists:get_value(available_dtls, Versions)), - true = lists:member('dtlsv1.2', proplists:get_value(default_supported_dtls, Versions)), - false = lists:member('dtlsv1', proplists:get_value(default_supported_dtls, Versions)). + true = lists:member('dtlsv1.2', proplists:get_value(supported_dtls, Versions)), + false = lists:member('dtlsv1', proplists:get_value(supported_dtls, Versions)). fallback() -> @@ -421,7 +421,7 @@ tls_versions_option(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), - Supported = proplists:get_value(default_supported, ssl:versions()), + Supported = proplists:get_value(supported, ssl:versions()), Available = proplists:get_value(available, ssl:versions()), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, diff --git a/lib/ssl/test/ssl_cert_SUITE.erl b/lib/ssl/test/ssl_cert_SUITE.erl index bcf2086cca..2fe470e281 100644 --- a/lib/ssl/test/ssl_cert_SUITE.erl +++ b/lib/ssl/test/ssl_cert_SUITE.erl @@ -43,15 +43,20 @@ all() -> groups() -> [ {'tlsv1.3', [], tls_1_3_protocol_groups()}, - {'tlsv1.2', [], tls_1_2_protocol_groups()}, + {'tlsv1.2', [], tls_1_2_protocol_groups() -- [{group,rsa_pss_pss}]}, {'tlsv1.1', [], ssl_protocol_groups()}, {'tlsv1', [], ssl_protocol_groups()}, - {'dtlsv1.2', [], tls_1_2_protocol_groups()}, + {'dtlsv1.2', [], tls_1_2_protocol_groups() -- [{group,rsa_pss_rsae}, {group,rsa_pss_pss}]}, {'dtlsv1', [], ssl_protocol_groups()}, {rsa, [], all_version_tests() ++ rsa_tests() ++ pre_tls_1_3_rsa_tests()}, {ecdsa, [], all_version_tests()}, {dsa, [], all_version_tests()}, - {rsa_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()}, + {rsa_1_3, [], all_version_tests() ++ rsa_tests() ++ + tls_1_3_tests() ++ tls_1_3_rsa_tests() ++ [basic_rsa_1024]}, + {rsa_pss_rsae, [], all_version_tests() ++ rsa_tests()}, + {rsa_pss_rsae_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()}, + {rsa_pss_pss, [], all_version_tests() ++ rsa_tests()}, + {rsa_pss_pss_1_3, [], all_version_tests() ++ rsa_tests() ++ tls_1_3_tests() ++ tls_1_3_rsa_tests()}, {ecdsa_1_3, [], all_version_tests() ++ tls_1_3_tests()} ]. @@ -62,11 +67,17 @@ ssl_protocol_groups() -> tls_1_2_protocol_groups() -> [{group, rsa}, {group, ecdsa}, - {group, dsa}]. + {group, dsa}, + {group, rsa_pss_rsae}, + {group, rsa_pss_pss} + ]. tls_1_3_protocol_groups() -> [{group, rsa_1_3}, - {group, ecdsa_1_3}]. + {group, ecdsa_1_3}, + {group, rsa_pss_rsae_1_3}, + {group, rsa_pss_pss_1_3} + ]. tls_1_3_tests() -> [ @@ -103,6 +114,7 @@ all_version_tests() -> client_auth_allow_partial_chain, client_auth_do_not_allow_partial_chain, client_auth_partial_chain_fun_fail, + client_auth_sni, missing_root_cert_no_auth, missing_root_cert_auth, missing_root_cert_auth_user_verify_fun_accept, @@ -118,7 +130,6 @@ all_version_tests() -> extended_key_usage_auth, extended_key_usage_client_auth, cert_expired, - client_auth_once, no_auth_key_identifier_ext, no_auth_key_identifier_ext_keyEncipherment ]. @@ -140,7 +151,8 @@ end_per_suite(_Config) -> init_per_group(Group, Config0) when Group == rsa; Group == rsa_1_3 -> - Config = ssl_test_lib:make_rsa_cert(Config0), + Config1 = ssl_test_lib:make_rsa_cert(Config0), + Config = ssl_test_lib:make_rsa_1024_cert(Config1), COpts = proplists:get_value(client_rsa_opts, Config), SOpts = proplists:get_value(server_rsa_opts, Config), [{cert_key_alg, rsa} | @@ -149,6 +161,30 @@ init_per_group(Group, Config0) when Group == rsa; {server_cert_opts, SOpts} | lists:delete(server_cert_opts, lists:delete(client_cert_opts, Config))])]; + +init_per_group(Alg, Config) when Alg == rsa_pss_rsae; + Alg == rsa_pss_pss; + Alg == rsa_pss_rsae_1_3; + Alg == rsa_pss_pss_1_3 -> + + Supports = crypto:supports(), + RSAOpts = proplists:get_value(rsa_opts, Supports), + + case lists:member(rsa_pkcs1_pss_padding, RSAOpts) + andalso lists:member(rsa_pss_saltlen, RSAOpts) + andalso lists:member(rsa_mgf1_md, RSAOpts) of + true -> + #{client_config := COpts, + server_config := SOpts} = ssl_test_lib:make_rsa_pss_pem(rsa_alg(Alg), [], Config, ""), + [{cert_key_alg, rsa_alg(Alg)} | + lists:delete(cert_key_alg, + [{client_cert_opts, COpts}, + {server_cert_opts, SOpts} | + lists:delete(server_cert_opts, + lists:delete(client_cert_opts, Config))])]; + false -> + {skip, "Missing EC crypto support"} + end; init_per_group(Group, Config0) when Group == ecdsa; Group == ecdsa_1_3 -> @@ -264,6 +300,12 @@ client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> ssl_cert_tests:client_auth_partial_chain_fun_fail(Config). %%-------------------------------------------------------------------- +client_auth_sni() -> + ssl_cert_tests:client_auth_sni(). +client_auth_sni(Config) when is_list(Config) -> + ssl_cert_tests:client_auth_sni(Config). + +%%-------------------------------------------------------------------- missing_root_cert_no_auth() -> ssl_cert_tests:missing_root_cert_no_auth(). missing_root_cert_no_auth(Config) when is_list(Config) -> @@ -655,41 +697,6 @@ cert_expired(Config) when is_list(Config) -> ssl_test_lib:check_client_alert(Server, Client, certificate_expired). %%-------------------------------------------------------------------- -client_auth_once() -> - [{doc,"Test server option verify_client_once"}]. - -client_auth_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_cert_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_cert_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{verify, verify_peer}, - {verify_client_once, true} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Server, ok, Client0, ok), - Server ! {listen, {mfa, {ssl_test_lib, send_recv_result_active, []}}}, - ssl_test_lib:close(Client0), - Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, ClientOpts}]), - - ssl_test_lib:check_result(Client1, ok, Server, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client1). - -%%-------------------------------------------------------------------- no_auth_key_identifier_ext() -> [{doc, "Test cert that does not have authorityKeyIdentifier extension"}]. @@ -815,7 +822,7 @@ unsupported_sign_algo_cert_client_auth(Config) -> ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, {verify, verify_peer}, - {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256]}, + {signature_algs, [rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pss_rsae_sha256, rsa_pss_pss_sha256]}, %% Skip rsa_pkcs1_sha256! {signature_algs_cert, [rsa_pkcs1_sha384, rsa_pkcs1_sha512]}, {fail_if_no_peer_cert, true}|ServerOpts0], @@ -894,6 +901,19 @@ hello_retry_client_auth_empty_cert_rejected(Config) -> ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, certificate_required). %%-------------------------------------------------------------------- +basic_rsa_1024() -> + [{doc, "TLS 1.3 (Basic): Test if connection can be established using 1024 bits RSA keys in certificates."}]. + +basic_rsa_1024(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_1024_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_1024_opts, Config), + ServerOpts1 = [{versions, ['tlsv1.2','tlsv1.3']}|ServerOpts0], + ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0], + ServerOpts = [{verify, verify_peer}, + {fail_if_no_peer_cert, true} | ServerOpts1], + ssl_test_lib:basic_test(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- %% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- two_digits_str(N) when N < 10 -> @@ -918,3 +938,10 @@ n_version(Version) when Version == 'tlsv1.2'; n_version(Version) when Version == 'dtlsv1.2'; Version == 'dtlsv1' -> dtls_record:protocol_version(Version). + +rsa_alg(rsa_pss_rsae_1_3) -> + rsa_pss_rsae; +rsa_alg(rsa_pss_pss_1_3) -> + rsa_pss_pss; +rsa_alg(Atom) -> + Atom. diff --git a/lib/ssl/test/ssl_cert_tests.erl b/lib/ssl/test/ssl_cert_tests.erl index c88daa2185..657ccd2079 100644 --- a/lib/ssl/test/ssl_cert_tests.erl +++ b/lib/ssl/test/ssl_cert_tests.erl @@ -162,6 +162,39 @@ client_auth_partial_chain_fun_fail(Config) when is_list(Config) -> ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, unknown_ca). %%-------------------------------------------------------------------- +client_auth_sni() -> + [{doc, "Check that sni check works with user verify_fun"}]. +client_auth_sni(Config) when is_list(Config) -> + ServerOpts0 = ssl_test_lib:ssl_options(server_cert_opts, Config), + + FunAndState = {fun(valid_peer, {bad_cert, unknown_ca}, UserState) -> + {valid_peer, UserState}; + (_,{bad_cert, _} = Reason, _) -> + {fail, Reason}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + + ClientOpts0 = ssl_test_lib:ssl_options(client_cert_opts, Config), + ClientOpts = [{verify, verify_peer}, {verify_fun, FunAndState + }, {server_name_indication, "localhost"} | ClientOpts0], + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts0)), + [{_,_,_}, {_, IntermidiateCA, _} | _] = public_key:pem_decode(ServerCAs), + + ServerOpts = [{cacerts, [IntermidiateCA]} | + proplists:delete(cacertfile, ServerOpts0)], + %% Basic test if hostname check is not performed the connection will succeed + ssl_test_lib:basic_alert(ClientOpts, ServerOpts0, Config, handshake_failure), + %% Also test that user verify_fun is run. + %% If user verify fun is not used the ALERT will be unknown_ca + ssl_test_lib:basic_alert(ClientOpts, ServerOpts, Config, handshake_failure). + +%%-------------------------------------------------------------------- missing_root_cert_no_auth() -> [{doc,"Test that the client succeds if the ROOT CA is unknown in verify_none mode"}]. diff --git a/lib/ssl/test/ssl_cipher_suite_SUITE.erl b/lib/ssl/test/ssl_cipher_suite_SUITE.erl index 4b19314a7a..71628e9b40 100644 --- a/lib/ssl/test/ssl_cipher_suite_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_suite_SUITE.erl @@ -27,6 +27,8 @@ -include_lib("common_test/include/ct.hrl"). +-define(TIMEOUT, {seconds, 5}). + %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- @@ -267,7 +269,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_3des_ede_cbc; SupCiphers = proplists:get_value(ciphers, crypto:supports()), case lists:member(des_ede3, SupCiphers) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing 3DES crypto support"} @@ -283,7 +285,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_rc4_128; SupCiphers = proplists:get_value(ciphers, crypto:supports()), case lists:member(rc4, SupCiphers) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing RC4 crypto support"} @@ -296,7 +298,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_aes_128_ccm_8; SupCiphers = proplists:get_value(ciphers, crypto:supports()), case lists:member(aes_128_ccm, SupCiphers) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing AES_128_CCM crypto support"} @@ -309,7 +311,7 @@ init_per_testcase(TestCase, Config) when TestCase == psk_aes_256_ccm_8; SupCiphers = proplists:get_value(ciphers, crypto:supports()), case lists:member(aes_256_ccm, SupCiphers) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing AES_256_CCM crypto support"} @@ -321,7 +323,7 @@ init_per_testcase(aes_256_gcm_sha384, Config) -> (lists:member(sha384, SupHashs)) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing AES_256_GCM_SHA384 crypto support"} @@ -333,7 +335,7 @@ init_per_testcase(aes_128_gcm_sha256, Config) -> (lists:member(sha256, SupHashs)) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing AES_128_GCM_SHA256 crypto support"} @@ -345,7 +347,7 @@ init_per_testcase(chacha20_poly1305_sha256, Config) -> (lists:member(sha256, SupHashs)) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing chacha20_poly1305_sha256 crypto support"} @@ -356,7 +358,7 @@ init_per_testcase(aes_128_ccm_sha256, Config) -> case (lists:member(aes_128_ccm, SupCiphers)) andalso (lists:member(sha256, SupHashs)) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing AES_128_CCM_SHA256 crypto support"} @@ -367,7 +369,7 @@ init_per_testcase(aes_128_ccm_8_sha256, Config) -> case (lists:member(aes_128_ccm, SupCiphers)) andalso (lists:member(sha256, SupHashs)) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, "Missing AES_128_CCM_8_SHA256 crypto support"} @@ -377,7 +379,7 @@ init_per_testcase(TestCase, Config) -> SupCiphers = proplists:get_value(ciphers, crypto:supports()), case lists:member(Cipher, SupCiphers) of true -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; _ -> {skip, {Cipher, SupCiphers}} diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index 8678d43450..5e0da22eb0 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -27,6 +27,8 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). +-define(TIMEOUT, {seconds, 30}). + %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- @@ -159,7 +161,7 @@ init_per_testcase(Case, Config0) -> {CertOpts, Config} = init_certs(CertDir, idp_crl, Config), case make_certs:all(DataDir, CertDir, CertOpts) of {ok, _} -> - ct:timetrap({seconds, 6}), + ct:timetrap(?TIMEOUT), [{cert_dir, CertDir} | Config]; _ -> end_per_testcase(Case, Config0), diff --git a/lib/ssl/test/ssl_mfl_SUITE.erl b/lib/ssl/test/ssl_mfl_SUITE.erl new file mode 100644 index 0000000000..bcc2b24651 --- /dev/null +++ b/lib/ssl/test/ssl_mfl_SUITE.erl @@ -0,0 +1,513 @@ +%% +-module(ssl_mfl_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 500). +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'dtlsv1.2'}, + {group, 'dtlsv1'}]. + +groups() -> + [{'tlsv1.3', [], tls_mfl_1_3()}, + {'tlsv1.2', [], tls_mfl()}, + {'tlsv1.1', [], tls_mfl()}, + {'tlsv1', [], tls_mfl()}, + {'dtlsv1.2', [], tls_mfl()}, + {'dtlsv1', [], tls_mfl()} + ]. + +tls_mfl_common() -> + [mfl_client_option, mfl_server_option, mfl_openssl_client, mfl_openssl_server, handshake_continue]. + +tls_mfl() -> + tls_mfl_common() ++ [reuse_session, reuse_session_erlang_server, reuse_session_erlang_client]. + +tls_mfl_1_3() -> + tls_mfl_common(). + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl_test_lib:clean_start(), + ssl:clear_pem_cache(), + Config = ssl_test_lib:make_rsa_cert(Config0), + ssl_test_lib:cert_options(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(GroupName, Config) -> + ssl_test_lib:init_per_group_openssl(GroupName, Config). + +end_per_group(GroupName, Config) -> + ssl_test_lib:end_per_group(GroupName, Config). + +init_per_testcase(TestCase, Config) when TestCase == mfl_openssl_server; + TestCase == mfl_openssl_client; + TestCase == reuse_session_erlang_server; + TestCase == reuse_session_erlang_client -> + case os:cmd("openssl version") of + %% Max fragmentation support introduced in OpenSSL 1.1.1 + "OpenSSL 1.1.1" ++ _ = OpenSSLVersion -> + case lists:member({protocol, dtls}, Config) of + true -> + %% Fixed but not yet released https://github.com/openssl/openssl/commit/dfbaef6 + {skip, "Broken DTLS max fragmentation support in "++OpenSSLVersion}; + false -> + init_per_testcase(all, Config) + end; + Other -> + {skip, "No/unknown max fragmentation support in "++Other} + end; +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +nyi(Config) when is_list(Config) -> + {skip, "NYI"}. + +%-------------------------------------------------------------------------------- +%% check max_fragment_length option on the client is accepted +%% and both sides can successfully send > MFL +mfl_client_option(Config) when is_list(Config) -> + ok = run_mfl_handshake(Config, 512), + ok = run_mfl_handshake(Config, 1024), + ok = run_mfl_handshake(Config, 2048), + ok = run_mfl_handshake(Config, 4096), + ok = run_mfl_handshake(Config, undefined), + ok. + +%-------------------------------------------------------------------------------- +%% check max_fragment_length option on the server is ignored +%% and both sides can successfully send > 512 bytes +mfl_server_option(Config) when is_list(Config) -> + Data = "mfl_server_options " ++ lists:duplicate(512, $x), + run_mfl_handshake(Config, undefined, Data, [], [{max_fragment_length, 512}]). + +%-------------------------------------------------------------------------------- +%% check max_fragment_length interworking with openssl server +mfl_openssl_server(Config) when is_list(Config) -> + mfl_openssl_server(512, Config), + mfl_openssl_server(2048, Config). + +%-------------------------------------------------------------------------------- +%% check max_fragment_length interworking with openssl client +mfl_openssl_client(Config) when is_list(Config) -> + mfl_openssl_client(1024, Config), + mfl_openssl_client(4096, Config). + +%-------------------------------------------------------------------------------- +%% check max_fragment_length option on the client is accepted and reused +reuse_session(Config) when is_list(Config) -> + MFL = 512, + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config) ++ + [{max_fragment_length, MFL}], + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). + +%%-------------------------------------------------------------------- + +reuse_session_erlang_server(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + MFL = 512, + Data = "reuse_session_erlang_server " ++ lists:duplicate(MFL, $r), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, active_recv, [length(Data)]}}, + {reconnect_times, 5}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-connect", ssl_test_lib:hostname_format(Hostname) + ++ ":" ++ integer_to_list(Port), + "-tlsextdebug", "-4", "-maxfraglen", integer_to_list(MFL), + ssl_test_lib:version_flag(Version), + "-reconnect"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + run_mfl_openssl(Server, OpensslPort, MFL, Data), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpensslPort). + +%%-------------------------------------------------------------------- + +reuse_session_erlang_client(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + MFL = 512, + Data = "reuse_session_erlang_client " ++ lists:duplicate(MFL, $r), + ClientOpts = [{max_fragment_length, 512} | ClientOpts0], + + Version = ssl_test_lib:protocol_version(Config), + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + CACertFile = proplists:get_value(cacertfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-tlsextdebug", "-cert", CertFile,"-key", KeyFile, "-CAfile", CACertFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + OpensslProtocol = case proplists:get_value(protocol, Config) of + undefined -> + tls; + ConfigProtocol -> + ConfigProtocol + end, + + ssl_test_lib:wait_for_openssl_server(Port, OpensslProtocol), + + Client0 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, + {options, [{reuse_sessions, save}, {verify, verify_peer}| ClientOpts]}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + %% quit s_server's current session so we can interact with the next client + true = port_command(OpensslPort, "q\n"), + ssl_test_lib:close(Client0), + + flush(), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_session, SID} | ClientOpts]}]), + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + ErlRecvFun = fun() -> + Data = ssl_test_lib:check_active_receive(Client1, Data) + end, + run_mfl_openssl(Client1, OpensslPort, MFL, Data, ErlRecvFun), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client1). + +%%-------------------------------------------------------------------- + +handshake_continue(Config) when is_list(Config) -> + ok = run_mfl_handshake_continue(Config, 1024), + ok = run_mfl_handshake_continue(Config, undefined), + ok. + +run_mfl_handshake_continue(Config, MFL) -> + Data = if is_integer(MFL) -> + "hello world" ++ lists:duplicate(MFL, $u); + true -> + "hello world" ++ lists:duplicate(999, $x) + end, + ClientExtraOpts = [{handshake, hello}, {max_fragment_length, MFL}], + ServerExtraOpts = [{handshake, hello}], + ExtraStartOpts = [{continue_options, [{want_ext, self()}]}], + MflEnum = mfl_enum(MFL), + PostF = fun(Server, Client) -> + receive {Server, {ext, ServerExt}} -> + ct:log("Server handshake Ext ~p~n", [ServerExt]), + MflEnum = maps:get(max_frag_enum, ServerExt, undefined) + end, + receive {Client, {ext, ClientExt}} -> + ct:log("Client handshake Ext ~p~n", [ClientExt]), + case maps:get(server_hello_selected_version, ClientExt, undefined) of + {3,4} -> + %% For TLS 1.3 the ssl {handshake, hello} API is inconsistent: + %% the server gets all the extensions CH+EE, but the client only CH + ignore; + _ -> + MflEnum = maps:get(max_frag_enum, ClientExt, undefined) + end + end, + ok + end, + + run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts, ExtraStartOpts, ExtraStartOpts, PostF). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +run_mfl_handshake(Config, MFL) when is_integer(MFL) -> + Data = "hello world" ++ lists:duplicate(MFL, $0), + ClientExtraOpts = [{max_fragment_length, MFL}], + run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, []); +run_mfl_handshake(Config, MFL) -> + Data = "hello world" ++ lists:duplicate(512, $9), + ClientExtraOpts = [], + run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, []). + +run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts) -> + run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts, [], [], fun(_,_) -> ok end). + +run_mfl_handshake(Config, MFL, Data, ClientExtraOpts, ServerExtraOpts, ClientExtraStartOpts, ServerExtraStartOpts, + PostFun) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ClientOpts = ClientExtraOpts ++ ClientOpts0, + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ServerExtraOpts ++ ServerOpts0, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, assert_mfl_and_send_first, [MFL, Data]}}, + {options, ServerOpts} | ServerExtraStartOpts]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, assert_mfl_and_recv_first, [MFL, Data]}}, + {options, ClientOpts} | ClientExtraStartOpts]), + + ok = PostFun(Server, Client), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +assert_mfl(Socket, undefined) -> + InfoMFL = ssl:connection_information(Socket, [max_fragment_length]), + ct:log("Connection MFL ~p, Expecting: [] ~n", [InfoMFL]), + {ok, []} = InfoMFL; +assert_mfl(Socket, MFL) -> + InfoMFL = ssl:connection_information(Socket, [max_fragment_length]), + ct:log("Connection MFL ~p, Expecting: ~p ~n", [InfoMFL, MFL]), + {ok, [{max_fragment_length, ConnMFL}]} = InfoMFL, + ConnMFL = MFL. + +assert_mfl_and_send_first(Socket, MFL, Data) -> + assert_mfl(Socket, MFL), + ssl_send(Socket, Data), + ssl_receive(Socket, "Got it"++lists:reverse(Data)). + +assert_mfl_and_recv_first(Socket, MFL, Data) -> + assert_mfl(Socket, MFL), + ssl_receive(Socket, Data), + ssl_send(Socket, lists:reverse(Data)). + +ssl_send(Socket, Data) -> + ssl:send(Socket, Data). + +ssl_receive(Socket, Data) -> + ssl_receive(Socket, Data, []). + +ssl_receive(Socket, Data, Buffer) -> + receive + {ssl, Socket, MoreData} -> + ct:log("Received ~p~n",[MoreData]), + NewBuffer = Buffer ++ MoreData, + case NewBuffer of + Data -> + ssl:send(Socket, "Got it"), + ok; + _ -> + ssl_receive(Socket, Data, NewBuffer) + end; + Other -> + ct:fail({unexpected_message, Other}) + after 4000 -> + ct:fail({did_not_get, Data}) + end. + +%% ------------------------------------------------------------ +mfl_openssl_server(MFL, Config) -> + Data = "mfl_openssl_server " ++ lists:duplicate(MFL, $s), + Fun = fun(C,S) -> run_mfl_openssl(C, S, MFL, Data) end, + ssl_test_lib:start_erlang_client_and_openssl_server_with_opts(Config, + [{max_fragment_length, MFL}], + ["-tlsextdebug", "-tlsextdebug"], + Data, Fun). + +%% ------------------------------------------------------------ +mfl_openssl_client(MFL, Config) -> + Data = "mfl_openssl_client " ++ lists:duplicate(MFL, $c), + Fun = fun(S,C) -> run_mfl_openssl(S, C, MFL, Data) end, + ClientArgs = ["-tlsextdebug", "-4", "-maxfraglen", integer_to_list(MFL)], + ssl_test_lib:start_erlang_server_and_openssl_client_with_opts(Config, + [], + ClientArgs, + Data, Fun). + +%% ------------------------------------------------------------ +run_mfl_openssl(ErlProc, OpenSSL, MFL, Data) -> + ErlRecvFun = fun() -> + receive + {ErlProc, Data} -> + ok + after 1000 -> + flush(true), + error(timeout) + end + end, + run_mfl_openssl(ErlProc, OpenSSL, MFL, Data, ErlRecvFun). + +run_mfl_openssl(ErlProc, OpenSSL, MFL, Data, ErlRecvFun) -> + MFL = get_openssl_max_fragment_length(OpenSSL), + + true = port_command(OpenSSL, Data), + ErlRecvFun(), + + ErlProc ! get_socket, + ErlSocket = receive + {ErlProc, {socket, ErlSocket0}} -> + ErlSocket0 + end, + assert_mfl(ErlSocket, MFL), + + RData = lists:reverse(Data), + flush(), + ssl:send(ErlSocket, RData), + RData = ssl_test_lib:active_recv(OpenSSL, length(RData)), + ok. + +%% ------------------------------------------------------------ +flush() -> + flush(false). +flush(Noisy) -> + receive Rx -> + if Noisy -> + io:format("~p:~p: ~999p~n", [self(), ?FUNCTION_NAME, Rx]); + true -> + ignore + end, + flush(Noisy) + after 100 -> + ok + end. + +%% ------------------------------------------------------------ +get_openssl_max_fragment_length(Port) -> + get_openssl_max_fragment_length(Port, []). + +get_openssl_max_fragment_length(Port, Acc) -> + receive + {Port, {data, Data}} -> + get_openssl_max_fragment_length_line(Port, Acc++Data) + after 1000 -> + error(timeout) + end. + +get_openssl_max_fragment_length_line(Port, Acc) -> + case get_line(Acc) of + more -> + get_openssl_max_fragment_length(Port, Acc); + {"TLS "++TlsInfo, Acc2} -> + get_openssl_max_fragment_length_tlsinfo(TlsInfo, Port, Acc2); + {_Discard, Acc2} -> + get_openssl_max_fragment_length_line(Port, Acc2) + end. + +get_openssl_max_fragment_length_tlsinfo("client extension "++ExtInfo, Port, Acc) -> + get_openssl_max_fragment_length_ext(ExtInfo, Port, Acc); +get_openssl_max_fragment_length_tlsinfo("server extension "++ExtInfo, Port, Acc) -> + get_openssl_max_fragment_length_ext(ExtInfo, Port, Acc); +get_openssl_max_fragment_length_tlsinfo(_Acc, Port, Acc) -> + get_openssl_max_fragment_length_line(Port, Acc). + +get_openssl_max_fragment_length_ext("\"max fragment length\" (id=1), len=1"=Ext, Port, Acc) -> + case get_line(Acc) of + more -> + receive + {Port, {data, Data}} -> + Acc1 = Acc++Data, + get_openssl_max_fragment_length_ext(Ext, Port, Acc1) + after 1000 -> + error(timeout) + end; + {"0000 - 01 "++_, _} -> + 512; + {"0000 - 02 "++_, _} -> + 1024; + {"0000 - 03 "++_, _} -> + 2048; + {"0000 - 04 "++_, _} -> + 4096 + end; +get_openssl_max_fragment_length_ext(_Acc, Port, Acc2) -> + get_openssl_max_fragment_length_line(Port, Acc2). + + +get_line(Data) -> + get_line(Data, []). + +get_line([$\r|T], A) -> + get_line(T, A); +get_line([$\n|T], A) -> + {lists:reverse(A), T}; +get_line([], _) -> + more; +get_line([H|T], A) -> + get_line(T, [H|A]). + + +get_openssl_data(Port, Exp) -> + get_openssl_data(Port, Exp, []). + +get_openssl_data(_Port, Exp, Exp) -> + ok; +get_openssl_data(Port, Exp, Acc) -> + case lists:prefix(Acc, Exp) of + true -> + receive + {Port, {data, Data}} -> + get_openssl_data(Port, Exp, Acc++Data) + after 1000 -> + error(timeout) + end; + false -> + ct:fail({get_openssl_data, {{expected, Exp}, {got, Acc}}}) + end. + +%% RFC 6066 +mfl_enum(512) -> 1; +mfl_enum(1024) -> 2; +mfl_enum(2048) -> 3; +mfl_enum(4096) -> 4; +mfl_enum(undefined) -> undefined. diff --git a/lib/ssl/test/ssl_npn_SUITE.erl b/lib/ssl/test/ssl_npn_SUITE.erl index 94bd21418c..b3c93c19fb 100644 --- a/lib/ssl/test/ssl_npn_SUITE.erl +++ b/lib/ssl/test/ssl_npn_SUITE.erl @@ -24,7 +24,7 @@ %% Note: This directive should only be used in test suites. -compile(export_all). -include_lib("common_test/include/ct.hrl"). - +-define(TIMEOUT, {seconds, 5}). -define(SLEEP, 500). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -100,7 +100,7 @@ end_per_group(GroupName, Config) -> init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), - ct:timetrap({seconds, 10}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(_TestCase, Config) -> diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index cc265e5849..2c6f169fd0 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -41,7 +41,7 @@ -define(MANY, 1000). -define(SOME, 50). --define(BASE_TIMEOUT_SECONDS, 5). +-define(BASE_TIMEOUT_SECONDS, 20). -define(SOME_SCALE, 2). -define(MANY_SCALE, 3). @@ -2148,7 +2148,7 @@ server_packet_decode(Socket, Packet) -> {ssl, Socket, Packet} -> ok; Other1 -> exit({?LINE, Other1}) end, - ok = ssl:send(Socket, Packet), + spawn(fun() -> ssl:send(Socket, Packet) end), receive {ssl, Socket, Packet} -> ok; Other2 -> exit({?LINE, Other2}) diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index f771ec8cf9..6b3df7ec3e 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -24,9 +24,8 @@ -compile(export_all). -include_lib("common_test/include/ct.hrl"). - --define(TIMEOUT, 600000). - +-define(TIMEOUT, {seconds, 20}). +-define(TIMEOUT_LONG, {seconds, 80}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -127,7 +126,7 @@ init_per_testcase(TestCase, Config) "sparc-sun-solaris2.10" -> {skip,"Will take to long time on an old Sparc"}; _ -> - ct:timetrap({seconds, 90}), + ct:timetrap(?TIMEOUT_LONG), Config end; @@ -140,11 +139,11 @@ init_per_testcase(TestCase, Config) TestCase == client_echos_passive_chunk_big; TestCase == client_echos_active_once_big; TestCase == client_echos_active_big -> - ct:timetrap({seconds, 60}), + ct:timetrap(?TIMEOUT_LONG), Config; init_per_testcase(_TestCase, Config) -> - ct:timetrap({seconds, 15}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(_TestCase, Config) -> @@ -726,7 +725,7 @@ client_active_once_server_close( send(_Socket, _Data, 0, _) -> ok; send(Socket, Data, Count, RecvEcho) -> - ok = ssl:send(Socket, Data), + spawn(fun() -> ssl:send(Socket, Data) end), RecvEcho(), send(Socket, Data, Count - 1, RecvEcho). diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl index db51b5a0a0..60e71501fa 100644 --- a/lib/ssl/test/ssl_session_SUITE.erl +++ b/lib/ssl/test/ssl_session_SUITE.erl @@ -552,4 +552,4 @@ connection_states(Random) -> client_random = Random, server_random = undefined, exportable = undefined}, - sequence_number => 0,server_verify_data => undefined}}. + sequence_number => 0,server_verify_data => undefined,max_fragment_length => undefined}}. diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index 1bafb5f4d0..f6b527aaf9 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -28,8 +28,7 @@ -include_lib("common_test/include/ct.hrl"). -define(SLEEP, 1000). --define(TIMEOUT, 60000). --define(LONG_TIMEOUT, 600000). +-define(TIMEOUT, {seconds, 20}). -define(MAX_TABLE_SIZE, 5). -behaviour(ssl_session_cache_api). @@ -123,18 +122,18 @@ init_per_testcase(session_cleanup, Config) -> application:set_env(ssl, session_lifetime, 5), ssl:start(), ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 20}), + ct:timetrap(?TIMEOUT), Config; init_per_testcase(client_unique_session, Config) -> - ct:timetrap({seconds, 40}), + ct:timetrap(?TIMEOUT), ssl_test_lib:ct_log_supported_protocol_versions(Config), Config; init_per_testcase(save_specific_session, Config) -> Versions = ssl_test_lib:protocol_version(Config), ssl_test_lib:clean_start(), ssl_test_lib:set_protocol_versions(Versions), - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; init_per_testcase(max_table_size, Config) -> Versions = ssl_test_lib:protocol_version(Config), @@ -161,7 +160,7 @@ init_customized_session_cache(Type, Config) -> Config)), ets:new(ssl_test, [named_table, public, set]), ets:insert(ssl_test, {type, Type}), - ct:timetrap({seconds, 20}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(session_cache_process_list, Config) -> @@ -240,16 +239,13 @@ session_cleanup(Config) when is_list(Config) -> ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), SessionInfo = receive {Server, Info} -> Info end, - %% Make sure session is registered - ct:sleep(?SLEEP*2), - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index f14ea5fae3..4660a98ca5 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -27,13 +27,15 @@ -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/inet.hrl"). +-define(TIMEOUT, {seconds, 6}). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [{group, 'tlsv1.2'}, + [{group, 'tlsv1.3'}, + {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, {group, 'dtlsv1.2'}, @@ -42,6 +44,7 @@ all() -> groups() -> [ + {'tlsv1.3', [], sni_tests()}, {'tlsv1.2', [], sni_tests()}, {'tlsv1.1', [], sni_tests()}, {'tlsv1', [], sni_tests()}, @@ -85,26 +88,10 @@ init_per_suite(Config0) -> {skip, "Crypto did not start"} end. init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - case ssl_test_lib:sufficient_crypto_support(GroupName) of - true -> - ssl_test_lib:init_tls_version(GroupName, Config); - false -> - {skip, "Missing crypto support"} - end; - _ -> - ssl:start(), - Config - end. + ssl_test_lib:init_per_group(GroupName, Config). end_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of - true -> - ssl_test_lib:clean_tls_version(Config); - false -> - Config - end. + ssl_test_lib:end_per_group(GroupName, Config). end_per_suite(_) -> ssl:stop(), @@ -113,12 +100,12 @@ end_per_suite(_) -> init_per_testcase(customize_hostname_check, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ssl_test_lib:clean_start(), - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config; init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(_TestCase, Config) -> diff --git a/lib/ssl/test/ssl_socket_SUITE.erl b/lib/ssl/test/ssl_socket_SUITE.erl index d648f2f9e1..47d3a72988 100644 --- a/lib/ssl/test/ssl_socket_SUITE.erl +++ b/lib/ssl/test/ssl_socket_SUITE.erl @@ -25,7 +25,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). - +-define(TIMEOUT, {seconds, 5}). -define(SLEEP, 500). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -87,7 +87,7 @@ end_per_group(_GroupName, Config) -> Config. init_per_testcase(raw_inet_option, Config) -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), case os:type() of {unix,linux} -> Config; @@ -95,7 +95,7 @@ init_per_testcase(raw_inet_option, Config) -> {skip, "Raw options are platform-specific"} end; init_per_testcase(_TestCase, Config) -> - ct:timetrap({seconds, 5}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(_TestCase, Config) -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 4cacc91a04..d93338be15 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -95,7 +95,9 @@ init_per_group(GroupName, Config) -> init_per_group_openssl(GroupName, Config) -> case is_tls_version(GroupName) andalso sufficient_crypto_support(GroupName) of true -> - case check_sane_openssl_version(GroupName) of + case check_sane_openssl_version(GroupName) + andalso maybe_legacy_tls_version_support(GroupName, Config) + of true -> [{version, GroupName}|init_tls_version(GroupName, Config)]; false -> @@ -264,6 +266,9 @@ do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid) -> Pid ! {self(), Reason} end, do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid); + get_socket -> + Pid ! {self(), {socket, AcceptSocket}}, + do_run_server_core(ListenSocket, AcceptSocket, Opts, Transport, Pid); listen -> run_server(ListenSocket, Opts); {listen, MFA} -> @@ -311,7 +316,7 @@ connect(ListenSocket, Node, _N, _, Timeout, SslOpts, cancel) -> ct:log("~p:~p~nssl:handshake@~p ret ~p",[?MODULE,?LINE, Node,Result]), Result end; -connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) -> +connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts0) -> ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), ct:log("~p:~p~nssl:handshake(~p,~p,~p)~n", [?MODULE,?LINE, AcceptSocket, SslOpts,Timeout]), @@ -320,10 +325,21 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) -> {ok, Socket0, Ext} -> [_|_] = maps:get(sni, Ext), ct:log("Ext ~p:~n", [Ext]), - ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]), + ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of + {value, {_, WantExt}, ContOpts1} -> + if is_pid(WantExt) -> + WantExt ! {self(), {ext, Ext}}; + true -> + ignore + end, + ContOpts1; + _ -> + ContOpts0 + end, + ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]), case ssl:handshake_continue(Socket0, ContOpts, Timeout) of {ok, Socket} -> - connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts); + connect(ListenSocket, Node, N-1, Socket, Timeout, SslOpts, ContOpts0); Error -> ct:log("~p:~p~nssl:handshake_continue@~p ret ~p",[?MODULE,?LINE, Node,Error]), Error @@ -376,8 +392,6 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), - Data = "From openssl to erlang", - Port = ssl_test_lib:inet_port(node()), CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), @@ -815,6 +829,9 @@ client_loop_core(Socket, Pid, Transport) -> Pid ! {self(), Reason} end, client_loop_core(Socket, Pid, Transport); + get_socket -> + Pid ! {self(), {socket, Socket}}, + client_loop_core(Socket, Pid, Transport); close -> ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]), Transport:close(Socket); @@ -835,9 +852,20 @@ client_cont_loop(_Node, Host, Port, Pid, Transport, Options, cancel, _Opts) -> Pid ! {connect_failed, Reason} end; -client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts, Opts) -> +client_cont_loop(_Node, Host, Port, Pid, Transport, Options, ContOpts0, Opts) -> case Transport:connect(Host, Port, Options) of - {ok, Socket0, _} -> + {ok, Socket0, Ext} -> + ContOpts = case lists:keytake(want_ext, 1, ContOpts0) of + {value, {_, WantExt}, ContOpts1} -> + if is_pid(WantExt) -> + WantExt ! {self(), {ext, Ext}}; + true -> + ignore + end, + ContOpts1; + _ -> + ContOpts0 + end, ct:log("~p:~p~nClient: handshake_continue(~p, ~p, infinity) ~n", [?MODULE, ?LINE, Socket0, ContOpts]), case Transport:handshake_continue(Socket0, ContOpts) of {ok, Socket} -> @@ -1211,6 +1239,18 @@ default_cert_chain_conf() -> %% Use only default options [[],[],[]]. +make_rsa_pss_pem(Alg, _UserConf, Config, Suffix) -> + DefClientConf = chain_spec(client, Alg, []), + DefServerConf = chain_spec(server, Alg, []), + CertChainConf = new_format([{client_chain, DefClientConf}, {server_chain, DefServerConf}]), + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), atom_to_list(Alg) ++ Suffix]), + GenCertData = public_key:pkix_test_data(CertChainConf), + Conf = x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + CConf = proplists:get_value(client_config, Conf), + SConf = proplists:get_value(server_config, Conf), + #{server_config => SConf, + client_config => CConf}. gen_conf(ClientChainType, ServerChainType, UserClient, UserServer) -> gen_conf(ClientChainType, ServerChainType, UserClient, UserServer, ?DEFAULT_CURVE). @@ -1287,8 +1327,33 @@ chain_spec(_Role, ecdsa, Curve) -> chain_spec(_Role, rsa, _) -> Digest = {digest, appropriate_sha(crypto:supports())}, [[Digest, {key, hardcode_rsa_key(1)}], - [Digest, {key, hardcode_rsa_key(2)}], - [Digest, {key, hardcode_rsa_key(3)}]]; + [Digest, {key, hardcode_rsa_key(2)}], + [Digest, {key, hardcode_rsa_key(3)}]]; +chain_spec(_Role, 'rsa-1024', _) -> + Digest = {digest, appropriate_sha(crypto:supports())}, + [[Digest, {key, hardcode_rsa_1024_key(1)}], + [Digest, {key, hardcode_rsa_1024_key(2)}], + [Digest, {key, hardcode_rsa_1024_key(3)}]]; +chain_spec(client, rsa_pss_rsae, _) -> + Digest = {digest, sha256}, + [[Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(1)}], + [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(2)}], + [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(3)}]]; +chain_spec(server, rsa_pss_rsae, _) -> + Digest = {digest, sha256}, + [[Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(4)}], + [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(5)}], + [Digest, {rsa_padding, rsa_pss_rsae}, {key, hardcode_rsa_key(6)}]]; +chain_spec(client, rsa_pss_pss, _) -> + Digest = {digest, sha256}, + [[Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(1), pss_params(sha256)}}], + [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(2), pss_params(sha256)}}], + [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(3), pss_params(sha256)}}]]; +chain_spec(server, rsa_pss_pss, _) -> + Digest = {digest, sha256}, + [[Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(4), pss_params(sha256)}}], + [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(5), pss_params(sha256)}}], + [Digest, {rsa_padding, rsa_pss_pss}, {key, {hardcode_rsa_key(6), pss_params(sha256)}}]]; chain_spec(_Role, dsa, _) -> Digest = {digest, appropriate_sha(crypto:supports())}, [[Digest, {key, hardcode_dsa_key(1)}], @@ -1404,6 +1469,31 @@ make_rsa_cert(Config) -> false -> Config end. + +make_rsa_1024_cert(Config) -> + CryptoSupport = crypto:supports(), + case proplists:get_bool(rsa, proplists:get_value(public_keys, CryptoSupport)) of + true -> + ClientFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa-1024"]), + ServerFileBase = filename:join([proplists:get_value(priv_dir, Config), "rsa-1024"]), + ClientChain = proplists:get_value(client_chain, Config, default_cert_chain_conf()), + ServerChain = proplists:get_value(server_chain, Config, default_cert_chain_conf()), + CertChainConf = gen_conf('rsa-1024', 'rsa-1024', ClientChain, ServerChain), + GenCertData = public_key:pkix_test_data(CertChainConf), + [{server_config, ServerConf}, + {client_config, ClientConf}] = + x509_test:gen_pem_config_files(GenCertData, ClientFileBase, ServerFileBase), + [{server_rsa_1024_opts, [{ssl_imp, new},{reuseaddr, true} | ServerConf]}, + + {server_rsa_1024_verify_opts, [{ssl_imp, new}, {reuseaddr, true}, + {verify, verify_peer} | ServerConf]}, + {client_rsa_1024_opts, ClientConf}, + {client_rsa_1024_verify_opts, [{verify, verify_peer} |ClientConf]} + | Config]; + false -> + Config + end. + appropriate_sha(CryptoSupport) -> Hashes = proplists:get_value(hashs, CryptoSupport), case lists:member(sha256, Hashes) of @@ -1696,51 +1786,31 @@ ecc_test_error(COpts, SOpts, CECCOpts, SECCOpts, Config) -> Client = start_client_ecc_error(erlang, Port, COpts, CECCOpts, Config), check_server_alert(Server, Client, insufficient_security). -start_basic_client(openssl, Version, Port, ClientOpts) -> - Cert = proplists:get_value(certfile, ClientOpts), - Key = proplists:get_value(keyfile, ClientOpts), - CA = proplists:get_value(cacertfile, ClientOpts), - Groups0 = proplists:get_value(groups, ClientOpts), - Exe = "openssl", - Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port), - ssl_test_lib:version_flag(Version), - "-CAfile", CA, "-host", "localhost", "-msg", "-debug"], - Args1 = - case Groups0 of - undefined -> - Args0; - G -> - Args0 ++ ["-groups", G] - end, - Args = - case {Cert, Key} of - {C, K} when C =:= undefined orelse - K =:= undefined -> - Args1; - {C, K} -> - Args1 ++ ["-cert", C, "-key", K] - end, - - OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - true = port_command(OpenSslPort, "Hello world"), - OpenSslPort. - start_client(openssl, Port, ClientOpts, Config) -> Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), Groups0 = proplists:get_value(groups, ClientOpts), CertArgs = openssl_cert_options(ClientOpts, client), + Exe = "openssl", Args0 = case Groups0 of undefined -> - ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version), + ["s_client", + "-verify", "2", + "-port", integer_to_list(Port), cipher_flag(Version), ciphers(Ciphers, Version), - ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]; + ssl_test_lib:version_flag(Version)] + ++ CertArgs + ++ ["-msg", "-debug"]; Group -> - ["s_client", "-verify", "2", "-port", integer_to_list(Port), cipher_flag(Version), + ["s_client", + "-verify", "2", + "-port", integer_to_list(Port), cipher_flag(Version), ciphers(Ciphers, Version), "-groups", Group, - ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"] + ssl_test_lib:version_flag(Version)] + ++CertArgs + ++ ["-msg", "-debug"] end, Args = maybe_force_ipv4(Args0), OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -1796,15 +1866,16 @@ start_server(openssl, ClientOpts, ServerOpts, Config) -> CertArgs = openssl_cert_options(ServerOpts, server), Ciphers = proplists:get_value(ciphers, ClientOpts, ssl:cipher_suites(default,Version)), Groups0 = proplists:get_value(groups, ServerOpts), + SigAlgs = proplists:get_value(openssl_sigalgs, Config, undefined), Args = case Groups0 of undefined -> ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version), ciphers(Ciphers, Version), - ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"]; + ssl_test_lib:version_flag(Version)] ++ sig_algs(SigAlgs) ++ CertArgs ++ ["-msg", "-debug"]; Group -> ["s_server", "-accept", integer_to_list(Port), cipher_flag(Version), ciphers(Ciphers, Version), "-groups", Group, - ssl_test_lib:version_flag(Version)] ++ CertArgs ++ ["-msg", "-debug"] + ssl_test_lib:version_flag(Version)] ++ sig_algs(SigAlgs) ++ CertArgs ++ ["-msg", "-debug"] end, OpenSslPort = portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), @@ -1821,6 +1892,11 @@ start_server(erlang, _, ServerOpts, Config) -> {options, [{verify, verify_peer}, {versions, Versions} | ServerOpts]}]), {Server, inet_port(Server)}. +sig_algs(undefined) -> + []; +sig_algs(SigAlgs) -> + ["-sigalgs " ++ SigAlgs]. + cipher_flag('tlsv1.3') -> "-ciphersuites"; cipher_flag(_) -> @@ -2266,6 +2342,39 @@ is_dtls_version('dtlsv1') -> is_dtls_version(_) -> false. +maybe_legacy_tls_version_support(Version, Config0) when + Version == 'tlsv1'; + Version == 'tlsv1.1' -> + %% Check if legacy version is supported + Config = ssl_test_lib:make_rsa_cert(Config0), + ServerOpts = proplists:get_value(server_rsa_opts, Config), + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Exe = "openssl", + Args = ["s_server", "-accept", + integer_to_list(Port), "-CAfile", CaCertFile, + "-cert", CertFile,"-key", KeyFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + ssl_test_lib:wait_for_openssl_server(Port, tls), + + case ssl:connect("localhost", Port, [{versions, [Version]}]) of + {ok, Socket} -> + ssl:close(Socket), + close_port(OpensslPort), + true; + {error, {tls_alert, {protocol_version, _}}} -> + close_port(OpensslPort), + false + end; +maybe_legacy_tls_version_support('dtlsv1', Config) -> + maybe_legacy_tls_version_support('tlsv1.1', Config); +maybe_legacy_tls_version_support(_, _) -> + %% Not a legacy version + true. + init_tls_version(Version, Config) when Version == 'dtlsv1.2'; Version == 'dtlsv1' -> ssl:stop(), @@ -2470,7 +2579,7 @@ active_recv(Socket, N, Acc) -> filter_openssl_debug_data(Bytes) -> re:replace(Bytes, "(read.*\n|write to.*\n|[\\dabcdefABCDEF]{4,4} -.*\n|>>> .*\n|<<< .*\n| \\d\\d.*\n|KEYUPDATE\n|.*Read BLOCK\n)*", - "", [{return, list}]). + "", [global,{return, list}]). active_once_recv(_Socket, 0) -> ok; @@ -2561,6 +2670,21 @@ is_sane_oppenssl_client() -> true end. +is_sane_oppenssl_pss(rsa_pss_pss) -> + case portable_cmd("openssl",["version"]) of + "OpenSSL 1.1.1" ++ Rest -> + hd(Rest) >= $c; + _ -> + false + end; +is_sane_oppenssl_pss(rsa_pss_rsae) -> + case portable_cmd("openssl",["version"]) of + "OpenSSL 1.1.1" ++ _ -> + true; + _ -> + false + end. + is_fips(openssl) -> VersionStr = portable_cmd("openssl",["version"]), case re:split(VersionStr, "fips") of @@ -2910,10 +3034,10 @@ supports_ssl_tls_version(Version) when Version == sslv2; case ubuntu_legacy_support() of true -> case portable_cmd("openssl", ["version"]) of - "OpenSSL 1.1" ++ _ -> - false; - "OpenSSL 1" ++ _ -> + "OpenSSL 1.0.1" ++ _ -> Version =/= sslv2; + "OpenSSL 1" ++ _ -> + false; %% Appears to be broken "OpenSSL 0.9.8.o" ++ _ -> false; @@ -3149,6 +3273,41 @@ hardcode_rsa_key(6) -> coefficient = 81173034184183681160439870161505779100040258708276674532866007896310418779840630960490793104541748007902477778658270784073595697910785917474138815202903114440800310078464142273778315781957021015333260021813037604142367434117205299831740956310682461174553260184078272196958146289378701001596552915990080834227, otherPrimeInfos = asn1_NOVALUE}. +hardcode_rsa_1024_key(1) -> + #'RSAPrivateKey'{version = 'two-prime', + modulus = 152618920709346576506952607098028299458615405194120516804067302859774798720862572082626851690572130284910454988859007980367926204341637028795420927026111160369130942718840998292351565050537705794496742217762844103737634290634532232714374862322877076125650783658974242305324207239909234718160759907957502819181, + publicExponent = 17, + privateExponent = 89775835711380339121736827704722529093303179525953245178863119329279293365213277695662853935630664873476738228740592929628191884906845311056129957074183020957315463095429495020547731127789657232144654051871515007759243605000909583210051114028049068215595185959728886310943042856399988846590947179831354428913, + prime1 = 13018105310773689694711101111767176661493882304979760063552973933059514785910240943852845097923711145970844208861778343060919395218474310542285865516544653, + prime2 = 11723589344682921162046319310366118627005968525349821205037572987102618200031016344115630095736447992996683226273798377973464634035204645607416378683745377, + exponent1 = 7657709006337464526300647712804221565584636649988094155031161137093832227006024084619320645837477144688731887565751966506423173657926065024874038539143913, + exponent2 = 11033966442054514034867124056815170472476205670917478781211833399625993600029191853285298913634303993408643036492986708680907890856663195865803650525878001, + coefficient = 7357357483264399363785138527396251818499941660605442417644885251395376792981387533016821796011101917057636813775613592220898054882923958484000235934554630, + otherPrimeInfos = asn1_NOVALUE}; +hardcode_rsa_1024_key(2) -> + #'RSAPrivateKey'{version = 'two-prime', + modulus = 132417984874904377472239436563253515498607309816574784497785056464473431603604973287322746340055062696030016903830406088140534281534301418467490242269156926775506208514027213826501153438861284871625076651352798208559277520683414148048437439635357639033850360133068980157555507518934285770383924814915583919331, + publicExponent = 17, + privateExponent = 116839398419033274240211267555811925439947626308742456909810343939241263179651447018225952652989761202379426679850358313065177307236148310412491390237491385620149263549211570156731410125598364338974865883306073709062002620705336269289633237348474049621806833904576124689232282666798030505410189805859996211233, + prime1 = 12354286715326546399270830019697416039683060665495490476376955068446562229853736822465010796530936501225722243114286822522048306078247961653481711526701259, + prime2 = 10718383661165041035044708868932433765604392896488115438294667272770655136522450030638962957185192634722652306257889603065114923772949624056219896061512009, + exponent1 = 5087059235722695576170341772816583075163613215204025490272863851713290329939773985720886798571562088740003276576471044567902243679278572445551292981582871, + exponent2 = 6304931565391200608849828746430843391531995821463597316643921925159208903836735312140566445403054491013324886034052707685361719866440955327188174153830593, + coefficient = 6764088858264512915296172980190092445938774052616013205418164952211827027745702759906572599388571087295432259160097016323193144471211837074613329649320009, + otherPrimeInfos = asn1_NOVALUE}; +hardcode_rsa_1024_key(3) -> + #'RSAPrivateKey'{version = 'two-prime', + modulus = 132603582566987335579015215397416921308461253540735107996254563101087328483405996961761145905021132317760270172654141110354018131670337351296871719192630978670273323069438897632586026697023844069174787494970866246368200405578784055149230641370998125414763230872277095376893138420738507940599560410343688278361, + publicExponent = 17, + privateExponent = 124803371827752786427308438021098278878551768038338925172945471153964544454970350081657549087078712769656724868380368103862605300395611624749996912181299722918452562565562892031863847812293655197586374503957768862684015202213024002730410420619423541154205461118764880018581745374583581669240937327152309672753, + prime1 = 12202483472094988277172439292742673588688995751099198683383744575043357099902468606144011463115716181768777309695574163698153032647393450605174909802187971, + prime2 = 10866934003248540047676291395653788246732743513485317053446021859209870346149779563425451397497222238159656279714782986335807210805023580459325334557063091, + exponent1 = 3588965727086761257991893909630198114320292867970352553936395463248046205853667237101179842092857700520228620498698283440633244896292191354463208765349403, + exponent2 = 4474619883690575313749061162916265748654659093788071727889538412615828966061673937881068222498856215712799644588440053197097086802068533130310431876437743, + coefficient = 6440880395775803235356940314241907933534073137546236980469653455119937607298142560546736915150573386382326185901566797818281064505978928392351326571984856, + otherPrimeInfos = asn1_NOVALUE}. + + hardcode_dsa_key(1) -> {'DSAPrivateKey',0, 99438313664986922963487511141216248076486724382260996073922424025828494981416579966171753999204426907349400798052572573634137057487829150578821328280864500098312146772602202702021153757550650696224643730869835650674962433068943942837519621267815961566259265204876799778977478160416743037274938277357237615491, @@ -3432,3 +3591,31 @@ set_protocol_versions(_, undefined) -> ok; set_protocol_versions(AppVar, Value) -> application:set_env(ssl, AppVar, Value). + +openssl_sigalgs(rsa_pss_pss, Config) -> + [{openssl_sigalgs, "rsa_pss_rsae_sha256:rsa_pss_pss_sha256"} | + proplists:delete(openssl_sigalgs, Config)]; +openssl_sigalgs(_, Config) -> + proplists:delete(openssl_sigalgs, Config). + +pss_params(sha256) -> + #'RSASSA-PSS-params'{ + hashAlgorithm = #'HashAlgorithm'{algorithm = ?'id-sha256'}, + maskGenAlgorithm = #'MaskGenAlgorithm'{algorithm = ?'id-mgf1', + parameters = #'HashAlgorithm'{algorithm = ?'id-sha256'} + }, + saltLength = 32, + trailerField = 1}. + +test_ciphers(Kex, Cipher, Version) -> + ssl:filter_cipher_suites( + ssl:cipher_suites(all, Version) ++ ssl:cipher_suites(anonymous, Version), + [{key_exchange, + fun(K) when K == Kex -> true; + (_) -> false + end}, + {cipher, + fun(C) when C == Cipher -> true; + (_) -> false + end}]). + diff --git a/lib/ssl/test/tls_1_3_record_SUITE.erl b/lib/ssl/test/tls_1_3_record_SUITE.erl index fc7572cdfa..8bf1097e55 100644 --- a/lib/ssl/test/tls_1_3_record_SUITE.erl +++ b/lib/ssl/test/tls_1_3_record_SUITE.erl @@ -112,7 +112,7 @@ encode_decode(_Config) -> <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, undefined}, - sequence_number => 0,server_verify_data => undefined}}, + sequence_number => 0,server_verify_data => undefined},max_fragment_length => undefined}, PlainText = [11, <<0,2,175>>, @@ -168,7 +168,8 @@ encode_decode(_Config) -> #{current_write => #{security_parameters => #security_parameters{cipher_suite = ?TLS_NULL_WITH_NULL_NULL}, - sequence_number => 0 + sequence_number => 0, + max_fragment_length => undefined } }, diff --git a/lib/ssl/test/tls_api_SUITE.erl b/lib/ssl/test/tls_api_SUITE.erl index 01de312a81..6804b09687 100644 --- a/lib/ssl/test/tls_api_SUITE.erl +++ b/lib/ssl/test/tls_api_SUITE.erl @@ -29,6 +29,7 @@ -include_lib("ssl/src/ssl_api.hrl"). -include_lib("ssl/src/tls_handshake.hrl"). +-define(TIMEOUT, {seconds, 10}). -define(SLEEP, 500). %%-------------------------------------------------------------------- @@ -115,7 +116,7 @@ end_per_group(GroupName, Config) -> init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), - ct:timetrap({seconds, 10}), + ct:timetrap(?TIMEOUT), Config. end_per_testcase(_TestCase, Config) -> diff --git a/lib/ssl/test/x509_test.erl b/lib/ssl/test/x509_test.erl index dd774d4c7c..e99c35e249 100644 --- a/lib/ssl/test/x509_test.erl +++ b/lib/ssl/test/x509_test.erl @@ -64,6 +64,8 @@ do_gen_pem_config_files(Config, CertFile, KeyFile, CAFile) -> cert_entry(Cert) -> {'Certificate', Cert, not_encrypted}. +key_entry({'PrivateKeyInfo', DERKeyInfo}) -> + {'PrivateKeyInfo', DERKeyInfo, not_encrypted}; key_entry({'RSAPrivateKey', DERKey}) -> {'RSAPrivateKey', DERKey, not_encrypted}; key_entry({'DSAPrivateKey', DERKey}) -> diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index c85d1fa173..14ba8ed31c 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 9.6.1 +SSL_VSN = 10.0 diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml index 50e1dea52b..b481596379 100644 --- a/lib/stdlib/doc/src/c.xml +++ b/lib/stdlib/doc/src/c.xml @@ -120,7 +120,7 @@ </func> <func> - <name name="h" arity="1" since="OTP @OTP-16222@"/> + <name name="h" arity="1" since="OTP 23.0"/> <fsummary>Module help information</fsummary> <type name="h_return"/> <desc> @@ -129,7 +129,7 @@ </func> <func> - <name name="h" arity="2" since="OTP @OTP-16222@"/> + <name name="h" arity="2" since="OTP 23.0"/> <fsummary>Function help information</fsummary> <type name="h_return"/> <type name="hf_return"/> @@ -139,7 +139,7 @@ </func> <func> - <name name="h" arity="3" since="OTP @OTP-16222@"/> + <name name="h" arity="3" since="OTP 23.0"/> <fsummary>Function help information</fsummary> <type name="h_return"/> <type name="hf_return"/> @@ -149,7 +149,7 @@ </func> <func> - <name name="ht" arity="1" since="OTP @OTP-16222@"/> + <name name="ht" arity="1" since="OTP 23.0"/> <fsummary>Type help information</fsummary> <type name="h_return"/> <desc> @@ -158,7 +158,7 @@ </func> <func> - <name name="ht" arity="2" since="OTP @OTP-16222@"/> + <name name="ht" arity="2" since="OTP 23.0"/> <fsummary>Type help information</fsummary> <type name="h_return"/> <type name="ht_return"/> @@ -168,7 +168,7 @@ </func> <func> - <name name="ht" arity="3" since="OTP @OTP-16222@"/> + <name name="ht" arity="3" since="OTP 23.0"/> <fsummary>Type help information</fsummary> <type name="h_return"/> <type name="ht_return"/> diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml index 3a5e4cb619..2ddff240d2 100644 --- a/lib/stdlib/doc/src/gen_event.xml +++ b/lib/stdlib/doc/src/gen_event.xml @@ -524,9 +524,9 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name since="OTP @OTP-16120@">start_monitor() -> Result</name> - <name since="OTP @OTP-16120@">start_monitor(EventMgrName | Options) -> Result</name> - <name since="OTP @OTP-16120@">start_monitor(EventMgrName, Options) -> Result</name> + <name since="OTP 23.0">start_monitor() -> Result</name> + <name since="OTP 23.0">start_monitor(EventMgrName | Options) -> Result</name> + <name since="OTP 23.0">start_monitor(EventMgrName, Options) -> Result</name> <fsummary>Create a stand-alone event manager process.</fsummary> <type> <v>EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}</v> diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 4ab8360fd1..4abb91439e 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -551,8 +551,8 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name since="OTP @OTP-16120@">start_monitor(Module, Args, Options) -> Result</name> - <name since="OTP @OTP-16120@">start_monitor(ServerName, Module, Args, Options) -> Result</name> + <name since="OTP 23.0">start_monitor(Module, Args, Options) -> Result</name> + <name since="OTP 23.0">start_monitor(ServerName, Module, Args, Options) -> Result</name> <fsummary>Create a standalone <c>gen_server</c> process.</fsummary> <type> <v>ServerName = {local,Name} | {global,GlobalName}</v> diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 1674773c64..fa3f20535d 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -1250,7 +1250,7 @@ handle_event(_, _, State, Data) -> </p> </item> <tag> - <c>push_callback_module</c><br /> + <c>push_callback_module</c> </tag> <item> <p> @@ -2049,8 +2049,8 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="start_monitor" arity="3" since="OTP @OTP-16120@"/> - <name name="start_monitor" arity="4" since="OTP @OTP-16120@"/> + <name name="start_monitor" arity="3" since="OTP 23.0"/> + <name name="start_monitor" arity="4" since="OTP 23.0"/> <fsummary>Create a standalone <c>gen_statem</c> process.</fsummary> <desc> <p> diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index f1737f071b..24a8d4c089 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -31,6 +31,209 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 3.13</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Compiling a match specification with excessive nesting + caused the runtime system to crash due to scheduler stack + exhaustion. Instead of crashing the runtime system, + effected functions will now raise a <c>system_limit</c> + error exception in this situation.</p> + <p> + Own Id: OTP-16431 Aux Id: ERL-592 </p> + </item> + <item> + <p> Initialization of record fields using <c>_</c> is no + longer allowed if the number of affected fields is zero. + </p> + <p> + Own Id: OTP-16516</p> + </item> + <item> + <p> Fix bugs in <c>eval_bits</c>. </p> + <p> + Own Id: OTP-16545</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Improved the printout of single line logger events for + most of the OTP behaviours in STDLIB and Kernel. This + includes <c>proc_lib</c>, <c>gen_server</c>, + <c>gen_event</c>, <c>gen_statem</c>, <c>gen_fsm</c>, + <c>supervisor</c>, <c>supervisor_bridge</c> and + <c>application</c>.</p> + <p> + Improved the <seeerl + marker="kernel:logger_formatter#chars_limit"><c>chars_limit</c></seeerl> + and <seeerl + marker="kernel:logger_formatter#depth"><c>depth</c></seeerl> + handling in <c>proc_lib</c> and when formatting of + exceptions.</p> + <p> + Own Id: OTP-15299</p> + </item> + <item> + <p> + Remove usage and documentation of old requests of the + I/O-protocol.</p> + <p> + Own Id: OTP-15695</p> + </item> + <item> + <p>Improved ETS scalability of concurrent calls that + change the size of a table, like <c>ets:insert/2</c> and + <c>ets:delete/2</c>.</p> <p>This performance feature was + implemented for <c>ordered_set</c> in OTP 22.0 and does + now apply for all ETS table types.</p> <p>The improved + scalability may come at the cost of longer latency of + <c>ets:info(T,size)</c> and <c>ets:info(T,memory)</c>. A + new table option <c>decentralized_counters</c> has + therefore been added. It is default <c>true</c> for + <c>ordered_set</c> with <c>write_concurrency</c> enabled + and default <c>false</c> for all other table types.</p> + <p> + Own Id: OTP-15744 Aux Id: OTP-15623, PR-2229 </p> + </item> + <item> + <p> Handle Unicode filenames in the <c>zip</c> module. + </p> + <p> + Own Id: OTP-16005 Aux Id: ERL-1003, ERL-1150 </p> + </item> + <item> + <p> + Unicode support was updated to the Unicode 12.1 standard.</p> + <p> + Own Id: OTP-16073 Aux Id: PR-2339 </p> + </item> + <item> + <p> + All of the modules <seemfa + marker="stdlib:proc_lib#start_monitor/3"><c>proc_lib</c></seemfa>, + <seemfa + marker="stdlib:gen_server#start_monitor/3"><c>gen_server</c></seemfa>, + <seemfa + marker="stdlib:gen_statem#start_monitor/3"><c>gen_statem</c></seemfa>, + and <seemfa + marker="stdlib:gen_event#start_monitor/0"><c>gen_event</c></seemfa> + have been extended with a <c>start_monitor()</c> + function. For more information, see the documentation of + <c>start_monitor()</c> for these modules.</p> + <p> + Own Id: OTP-16120 Aux Id: ERIERL-402, PR-2427 </p> + </item> + <item> + <p> + Updates for new <c>erlang:term_to_iovec()</c> BIF.</p> + <p> + Own Id: OTP-16128 Aux Id: OTP-15618 </p> + </item> + <item> + <p>Documented a quirk regarding extraction from file + descriptors in <c>erl_tar</c>.</p> + <p> + Own Id: OTP-16171 Aux Id: ERL-1057 </p> + </item> + <item> + <p> + Added <c>ok</c> as return value to + <c>gen_server:reply/2</c></p> + <p> + Own Id: OTP-16210 Aux Id: PR-2411 </p> + </item> + <item> + <p>New functions have been added to <seeerl + marker="c"><c>c(3)</c></seeerl> for printing embedded + documentation for Erlang modules. The functions are:</p> + <taglist> <tag>h/1,2,3</tag> <item>Print the + documentation for a Module:Function/Arity.</item> + <tag>ht/1,2,3</tag> <item>Print the type documentation + for a Module:Type/Arity.</item> </taglist> <p>The + embedded documentation is created when building the + Erlang/OTP documentation.</p> + <p> + Own Id: OTP-16222</p> + </item> + <item> + <p> Add <c>indent</c> and <c>linewidth</c> to the options + of the <c>erl_pp</c> module's functions. </p> + <p> + Own Id: OTP-16276 Aux Id: PR-2443 </p> + </item> + <item> + <p> + Minor updates due to the new spawn improvements made.</p> + <p> + Own Id: OTP-16368 Aux Id: OTP-15251 </p> + </item> + <item> + <p>The compiler will now raise a warning when inlining is + used in modules that load NIFs.</p> + <p> + Own Id: OTP-16429 Aux Id: ERL-303 </p> + </item> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + <item> + <p> Extend <c>erl_parse:abstract/1,2</c> to handle + external fun expressions (<c>fun M:F/A</c>). </p> + <p> + Own Id: OTP-16480</p> + </item> + <item> + <p>Added <c>filelib:safe_relative_path/2</c> to replace + <c>filename:safe_relative_path/1</c>, which did not + safely handle symbolic links.</p> + <p><c>filename:safe_relative_path/1</c> has been + deprecated.</p> + <p> + Own Id: OTP-16483 Aux Id: PR-2542 </p> + </item> + <item> + <p> + The module <c>shell_docs</c> has been added. The module + contains functions for rendering, validating and + normalizing embedded documentation.</p> + <p> + Own Id: OTP-16500</p> + </item> + <item> + <p> + Module and function auto-completion in the shell now + looks at all available modules instead of only those + loaded. A module is considered available if it either is + loaded already or would be loaded if called.</p> + <p> + The auto-completion has also been expanded to work in the + new <c>h/1,2,3</c> function in <c>c(3)</c>.</p> + <p> + Own Id: OTP-16501 Aux Id: OTP-16494, OTP-16222, + OTP-16406, OTP-16499, OTP-16500, PR-2545, ERL-708 </p> + </item> + <item> + <p>Updated the internal <c>pcre</c> library to + <c>8.44</c>.</p> + <p> + Own Id: OTP-16557</p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.12.1</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -653,6 +856,27 @@ </section> +<section><title>STDLIB 3.8.2.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + <seemfa marker="stdlib:re#run/3">re:run(Subject, RE, + [unicode])</seemfa> returned <c>nomatch</c> instead of + failing with a <c>badarg</c> error exception when + <c>Subject</c> contained illegal utf8 and <c>RE</c> was + passed as a binary. This has been corrected along with + corrections of reduction counting in <c>re:run()</c> + error cases.</p> + <p> + Own Id: OTP-16553</p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.8.2.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml index 709f157b14..aa649a280a 100644 --- a/lib/stdlib/doc/src/proc_lib.xml +++ b/lib/stdlib/doc/src/proc_lib.xml @@ -360,9 +360,9 @@ init(Parent) -> </func> <func> - <name name="start_monitor" arity="3" since="OTP @OTP-16120@"/> - <name name="start_monitor" arity="4" since="OTP @OTP-16120@"/> - <name name="start_monitor" arity="5" since="OTP @OTP-16120@"/> + <name name="start_monitor" arity="3" since="OTP 23.0"/> + <name name="start_monitor" arity="4" since="OTP 23.0"/> + <name name="start_monitor" arity="5" since="OTP 23.0"/> <fsummary>Start a new process synchronously.</fsummary> <desc> <p> diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml index b601c2397d..62ce63ec33 100644 --- a/lib/stdlib/doc/src/shell.xml +++ b/lib/stdlib/doc/src/shell.xml @@ -119,6 +119,9 @@ <section> <title>Shell Commands</title> + <p>The commands below are the built-in shell commands that are always + available. In most system the commands listed in the <seeerl marker="c">c(3)</seeerl> + module are also available in the shell.</p> <taglist> <tag><c>b()</c></tag> <item> @@ -740,8 +743,8 @@ q - quit erlang <p>If you want an Erlang node to have a remote job active from the start (rather than the default local job), start Erlang with flag - <c>-remsh</c>, for example, - <c>erl -sname this_node -remsh other_node@other_host</c></p> + <seecom marker="erts:erl#remsh"><c>-remsh</c></seecom>, for example, + <c>erl -remsh other_node@other_host</c></p> </section> <section> diff --git a/lib/stdlib/doc/src/shell_docs.xml b/lib/stdlib/doc/src/shell_docs.xml index 4a39ef9eef..c8fba1b43e 100644 --- a/lib/stdlib/doc/src/shell_docs.xml +++ b/lib/stdlib/doc/src/shell_docs.xml @@ -32,11 +32,11 @@ <rev>A</rev> <file>shell_docs.xml</file> </header> - <module since="OTP @OTP-16222@">shell_docs</module> - <modulesummary>Functions used to render documentation for a shell.</modulesummary> + <module since="OTP 23.0">shell_docs</module> + <modulesummary>Functions used to render EEP-48 style documentation for a shell.</modulesummary> <description> <p>This module can be used to render function and type documentation - to be printed in a shell. It can only render documentation of the format + to be printed in a shell. It can only render EEP-48 documentation of the format <c>application/erlang+html</c>. For more information about this format see <seeguide marker="erl_docgen:doc_storage">Documentation Storage</seeguide> in Erl_Docgen's User's Guide. @@ -48,6 +48,8 @@ <name name="docs_v1"/> </datatype> <datatype> + <name name="chunk_element_block_type"/> + <name name="chunk_element_inline_type"/> <name name="chunk_element_type"/> <desc> <p> @@ -68,38 +70,53 @@ <funcs> <func> - <name name="render" arity="2" since="OTP @OTP-16222@"/> + <name name="render" arity="2" since="OTP 23.0"/> <fsummary>Render the documentation for a module.</fsummary> <desc> <p>Render the documentation for a module.</p> </desc> </func> <func> - <name name="render" arity="3" since="OTP @OTP-16222@"/> - <name name="render" arity="4" since="OTP @OTP-16222@"/> + <name name="render" arity="3" since="OTP 23.0"/> + <name name="render" arity="4" since="OTP 23.0"/> <fsummary>Render the documentation for a function.</fsummary> <desc> <p>Render the documentation for a function.</p> </desc> </func> <func> - <name name="render_type" arity="2" since="OTP @OTP-16222@"/> + <name name="render_type" arity="2" since="OTP 23.0"/> <fsummary>Render a list of all available types in a module.</fsummary> <desc> <p>Render a list of all available types in a module.</p> </desc> </func> <func> - <name name="render_type" arity="3" since="OTP @OTP-16222@"/> - <name name="render_type" arity="4" since="OTP @OTP-16222@"/> + <name name="render_type" arity="3" since="OTP 23.0"/> + <name name="render_type" arity="4" since="OTP 23.0"/> <fsummary>Render the documentation of a type in a module.</fsummary> <desc> <p>Render the documentation of a type in a module.</p> </desc> </func> + <func> + <name name="render_callback" arity="2" since="OTP 23.0"/> + <fsummary>Render a list of all available callbacks in a module.</fsummary> + <desc> + <p>Render a list of all available callbacks in a module.</p> + </desc> + </func> + <func> + <name name="render_callback" arity="3" since="OTP 23.0"/> + <name name="render_callback" arity="4" since="OTP 23.0"/> + <fsummary>Render the documentation of a callback in a module.</fsummary> + <desc> + <p>Render the documentation of a callback in a module.</p> + </desc> + </func> <func> - <name name="validate" arity="1" since="OTP @OTP-16222@"/> + <name name="validate" arity="1" since="OTP 23.0"/> <fsummary>Validate the documentation</fsummary> <desc> <p>This function can be used to do a basic validation of @@ -108,7 +125,7 @@ </func> <func> - <name name="normalize" arity="1" since="OTP @OTP-16222@"/> + <name name="normalize" arity="1" since="OTP 23.0"/> <fsummary>Normalize the documentation</fsummary> <desc> <p>This function can be used to do whitespace normalization diff --git a/lib/stdlib/examples/erl_id_trans.erl b/lib/stdlib/examples/erl_id_trans.erl index eab2ec4164..a707c45eb9 100644 --- a/lib/stdlib/examples/erl_id_trans.erl +++ b/lib/stdlib/examples/erl_id_trans.erl @@ -193,9 +193,6 @@ pattern({map_field_exact,Line,K,V}) -> Ke = expr(K), Ve = pattern(V), {map_field_exact,Line,Ke,Ve}; -%%pattern({struct,Line,Tag,Ps0}) -> -%% Ps1 = pattern_list(Ps0), -%% {struct,Line,Tag,Ps1}; pattern({record,Line,Name,Pfs0}) -> Pfs1 = pattern_fields(Pfs0), {record,Line,Name,Pfs1}; @@ -433,9 +430,6 @@ expr({map_field_exact,Line,K,V}) -> Ke = expr(K), Ve = expr(V), {map_field_exact,Line,Ke,Ve}; -%%expr({struct,Line,Tag,Es0}) -> -%% Es1 = pattern_list(Es0), -%% {struct,Line,Tag,Es1}; expr({record_index,Line,Name,Field0}) -> Field1 = expr(Field0), {record_index,Line,Name,Field1}; diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl index 6af3951604..fa7bf8bfbc 100644 --- a/lib/stdlib/src/c.erl +++ b/lib/stdlib/src/c.erl @@ -30,7 +30,7 @@ lc_batch/0, lc_batch/1, i/3,pid/3,m/0,m/1,mm/0,lm/0, bt/1, q/0, - h/1,h/2,h/3,ht/1,ht/2,ht/3, + h/1,h/2,h/3,ht/1,ht/2,ht/3,hcb/1,hcb/2,hcb/3, erlangrc/0,erlangrc/1,bi/1, flush/0, regs/0, uptime/0, nregs/0,pwd/0,ls/0,ls/1,cd/1,memory/1,memory/0, xm/1]). @@ -154,8 +154,9 @@ c(SrcFile, NewOpts, Filter, BeamFile, Info) -> safe_recompile(SrcFile, Options, BeamFile). -type h_return() :: ok | {error, missing | {unknown_format, unicode:chardata()}}. --type ht_return() :: h_return() | {error, type_missing}. -type hf_return() :: h_return() | {error, function_missing}. +-type ht_return() :: h_return() | {error, type_missing}. +-type hcb_return() :: h_return() | {error, callback_missing}. -spec h(module()) -> h_return(). h(Module) -> @@ -224,10 +225,49 @@ ht(Module,Type,Arity) -> Error end. +-spec hcb(module()) -> h_return(). +hcb(Module) -> + case code:get_doc(Module) of + {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} -> + format_docs(shell_docs:render_callback(Module, Docs)); + {ok, #docs_v1{ format = Enc }} -> + {error, {unknown_format, Enc}}; + Error -> + Error + end. + +-spec hcb(module(),Callback :: atom()) -> hcb_return(). +hcb(Module,Callback) -> + case code:get_doc(Module) of + {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} -> + format_docs(shell_docs:render_callback(Module, Callback, Docs)); + {ok, #docs_v1{ format = Enc }} -> + {error, {unknown_format, Enc}}; + Error -> + Error + end. + +-spec hcb(module(),Callback :: atom(),arity()) -> + hcb_return(). +hcb(Module,Callback,Arity) -> + case code:get_doc(Module) of + {ok, #docs_v1{ format = ?NATIVE_FORMAT } = Docs} -> + format_docs(shell_docs:render_callback(Module, Callback, Arity, Docs)); + {ok, #docs_v1{ format = Enc }} -> + {error, {unknown_format, Enc}}; + Error -> + Error + end. + format_docs({error,_} = E) -> E; format_docs(Docs) -> - format("~ts",[Docs]). + {match, Lines} = re:run(Docs,"(.+\n|\n)",[unicode,global,{capture,all_but_first,binary}]), + _ = paged_output(fun(Line,_) -> + format("~ts",Line), + {1,undefined} + end, undefined, Lines), + ok. old_options(Info) -> case lists:keyfind(options, 1, Info) of @@ -557,58 +597,54 @@ ni() -> i(all_procs()). -spec i([pid()]) -> 'ok'. i(Ps) -> - i(Ps, length(Ps)). - --spec i([pid()], non_neg_integer()) -> 'ok'. - -i(Ps, N) when N =< 100 -> - iformat("Pid", "Initial Call", "Heap", "Reds", - "Msgs"), - iformat("Registered", "Current Function", "Stack", "", - ""), - {R,M,H,S} = foldl(fun(Pid, {R0,M0,H0,S0}) -> - {A,B,C,D} = display_info(Pid), - {R0+A,M0+B,H0+C,S0+D} - end, {0,0,0,0}, Ps), - iformat("Total", "", w(H), w(R), w(M)), - iformat("", "", w(S), "", ""); -i(Ps, N) -> - iformat("Pid", "Initial Call", "Heap", "Reds", - "Msgs"), - iformat("Registered", "Current Function", "Stack", "", - ""), - paged_i(Ps, {0,0,0,0}, N, 50). - -paged_i([], {R,M,H,S}, _, _) -> - iformat("Total", "", w(H), w(R), w(M)), - iformat("", "", w(S), "", ""); -paged_i(Ps, Acc, N, Page) -> - {Pids, Rest, N1} = - if N > Page -> - {L1,L2} = lists:split(Page, Ps), - {L1,L2,N-Page}; - true -> - {Ps, [], 0} - end, - NewAcc = foldl(fun(Pid, {R,M,H,S}) -> - {A,B,C,D} = display_info(Pid), - {R+A,M+B,H+C,S+D} - end, Acc, Pids), - case Rest of - [_|_] -> - choice(fun() -> paged_i(Rest, NewAcc, N1, Page) end); - [] -> - paged_i([], NewAcc, 0, Page) + iformat("Pid", "Initial Call", "Heap", "Reds", "Msgs"), + iformat("Registered", "Current Function", "Stack", "", ""), + case paged_output(fun(Pid, {R,M,H,S}) -> + {A,B,C,D} = display_info(Pid), + {2,{R+A,M+B,H+C,S+D}} + end, 2, {0,0,0,0}, Ps) of + {R,M,H,S} -> + iformat("Total", "", w(H), w(R), w(M)), + iformat("", "", w(S), "", ""); + less -> + ok end. -choice(F) -> - case get_line('(c)ontinue (q)uit -->', "c\n") of +paged_output(Fun, Acc, Items) -> + paged_output(Fun, 0, Acc, Items). +paged_output(Fun, CurrLine, Acc, Items) -> + Limit = + case io:rows() of + {ok, Rows} -> Rows-2; + _ -> 100 + end, + paged_output(Fun, CurrLine, Limit, Acc, Items). + +paged_output(PrintFun, CurrLine, Limit, Acc, Items) when CurrLine >= Limit -> + case more() of + more -> + paged_output(PrintFun, 0, Limit, Acc, Items); + less -> + less + end; +paged_output(PrintFun, CurrLine, Limit, Acc, [H|T]) -> + {Lines, NewAcc} = PrintFun(H, Acc), + paged_output(PrintFun, CurrLine+Lines, Limit, NewAcc, T); +paged_output(_, _, _, Acc, []) -> + Acc. + +more() -> + case get_line('more (y/n)? (y) ', "y\n") of "c\n" -> - F(); + more; + "y\n" -> + more; "q\n" -> - quit; + less; + "n\n" -> + less; _ -> - choice(F) + more() end. get_line(P, Default) -> diff --git a/lib/stdlib/src/digraph.erl b/lib/stdlib/src/digraph.erl index 8a4df95027..58d493cf54 100644 --- a/lib/stdlib/src/digraph.erl +++ b/lib/stdlib/src/digraph.erl @@ -230,7 +230,7 @@ in_neighbours(G, V) -> Edges :: [edge()]. in_edges(G, V) -> - ets:select(G#digraph.ntab, [{{{in, V}, '$1'}, [], ['$1']}]). + [E || {{in, _}, E} <- ets:lookup(G#digraph.ntab, {in, V})]. -spec out_degree(G, V) -> non_neg_integer() when G :: graph(), @@ -255,7 +255,7 @@ out_neighbours(G, V) -> Edges :: [edge()]. out_edges(G, V) -> - ets:select(G#digraph.ntab, [{{{out, V}, '$1'}, [], ['$1']}]). + [E || {{out, _}, E} <- ets:lookup(G#digraph.ntab, {out, V})]. -spec add_edge(G, V1, V2) -> edge() | {'error', add_edge_err_rsn()} when G :: graph(), diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 5351490b1a..6200978d4d 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -20,10 +20,6 @@ %% Purpose: Expand records into tuples. Also add explicit module %% names to calls to imported functions and BIFs. -%% N.B. Although structs (tagged tuples) are not yet allowed in the -%% language there is code included in pattern/2 and expr/3 (commented out) -%% that handles them. - -module(erl_expand_records). -export([module/2]). @@ -125,9 +121,6 @@ pattern({map_field_exact,Line,K0,V0}, St0) -> {K,St1} = expr(K0, St0), {V,St2} = pattern(V0, St1), {{map_field_exact,Line,K,V},St2}; -%%pattern({struct,Line,Tag,Ps}, St0) -> -%% {TPs,TPsvs,St1} = pattern_list(Ps, St0), -%% {{struct,Line,Tag,TPs},TPsvs,St1}; pattern({record_index,Line,Name,Field}, St) -> {index_expr(Line, Field, Name, record_fields(Name, St)),St}; pattern({record,Line0,Name,Pfs}, St0) -> @@ -310,9 +303,6 @@ expr({map_field_exact,Line,K0,V0}, St0) -> {K,St1} = expr(K0, St0), {V,St2} = expr(V0, St1), {{map_field_exact,Line,K,V},St2}; -%%expr({struct,Line,Tag,Es0}, Vs, St0) -> -%% {Es1,Esvs,Esus,St1} = expr_list(Es0, Vs, St0), -%% {{struct,Line,Tag,Es1},Esvs,Esus,St1}; expr({record_index,Line,Name,F}, St) -> I = index_expr(Line, F, Name, record_fields(Name, St)), expr(I, St); diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index f5059ac710..6ff5e23ee3 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -311,7 +311,6 @@ bif(is_process_alive, 1) -> true; bif(is_atom, 1) -> true; bif(is_boolean, 1) -> true; bif(is_binary, 1) -> true; -bif(is_bitstr, 1) -> true; bif(is_bitstring, 1) -> true; bif(is_float, 1) -> true; bif(is_function, 1) -> true; @@ -348,7 +347,6 @@ bif(max,2) -> true; bif(min,2) -> true; bif(module_loaded, 1) -> true; bif(monitor, 2) -> true; -bif(monitor, 3) -> true; bif(monitor_node, 2) -> true; bif(node, 0) -> true; bif(node, 1) -> true; @@ -465,7 +463,6 @@ old_bif(is_process_alive, 1) -> true; old_bif(is_atom, 1) -> true; old_bif(is_boolean, 1) -> true; old_bif(is_binary, 1) -> true; -old_bif(is_bitstr, 1) -> true; old_bif(is_bitstring, 1) -> true; old_bif(is_float, 1) -> true; old_bif(is_function, 1) -> true; diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 4892dd5131..7c717e47d1 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -20,9 +20,6 @@ %% %% Do necessary checking of Erlang code. -%% N.B. All the code necessary for checking structs (tagged tuples) is -%% here. Just comment out the lines in pattern/2, gexpr/3 and expr/3. - -module(erl_lint). -export([module/1,module/2,module/3,format_error/1]). @@ -1646,8 +1643,6 @@ pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) -> pattern_list(Ps, Vt, Old, Bvt, St); pattern({map,_Line,Ps}, Vt, Old, Bvt, St) -> pattern_map(Ps, Vt, Old, Bvt, St); -%%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) -> -%% pattern_list(Ps, Vt, Old, Bvt, St); pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) -> {Vt1,St1} = check_record(Line, Name, St, @@ -2266,8 +2261,6 @@ is_gexpr({string,_L,_S}, _Info) -> true; is_gexpr({nil,_L}, _Info) -> true; is_gexpr({cons,_L,H,T}, Info) -> is_gexpr_list([H,T], Info); is_gexpr({tuple,_L,Es}, Info) -> is_gexpr_list(Es, Info); -%%is_gexpr({struct,_L,_Tag,Es}, Info) -> -%% is_gexpr_list(Es, Info); is_gexpr({map,_L,Es}, Info) -> is_map_fields(Es, Info); is_gexpr({map,_L,Src,Es}, Info) -> diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl index 387fa395ce..651c601bb0 100644 --- a/lib/stdlib/src/erl_pp.erl +++ b/lib/stdlib/src/erl_pp.erl @@ -560,8 +560,6 @@ lexpr({bc,_,E,Qs}, _Prec, Opts) -> %% {list,[{step,'<<',Lcl},'>>']}; lexpr({tuple,_,Elts}, _, Opts) -> tuple(Elts, Opts); -%%lexpr({struct,_,Tag,Elts}, _, Opts) -> -%% {first,format("~w", [Tag]),tuple(Elts, Opts)}; lexpr({record_index, _, Name, F}, Prec, Opts) -> {P,R} = preop_prec('#'), Nl = record_name(Name), diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 11594483e0..b6531d9b5c 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -245,6 +245,10 @@ obsolete(snmpm, sync_set, 5) -> {deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"}; obsolete(snmpm, sync_set, 6) -> {deprecated, "use snmpm:sync_set2/4 instead.", "OTP 25"}; +obsolete(ssl, cipher_suites, 0) -> + {deprecated, "use cipher_suites/2,3 instead", "OTP 24"}; +obsolete(ssl, cipher_suites, 1) -> + {deprecated, "use cipher_suites/2,3 instead", "OTP 24"}; obsolete(sys, get_debug, 3) -> {deprecated, "incorrectly documented and only for internal use. Can often be replaced with sys:get_log/1"}; obsolete(wxCalendarCtrl, enableYearChange, 1) -> @@ -466,7 +470,7 @@ obsolete(crypto, stream_init, _) -> obsolete(filename, find_src, _) -> {deprecated, "use filelib:find_source/1,3 instead", "OTP 24"}; obsolete(ssl, ssl_accept, _) -> - {deprecated, "use ssl_handshake/1,2,3 instead"}; + {deprecated, "use ssl_handshake/1,2,3 instead", "OTP 24"}; obsolete(asn1ct, decode, _) -> {removed, "use Mod:decode/2 instead"}; obsolete(asn1ct, encode, _) -> @@ -494,6 +498,14 @@ obsolete(os_mon_mib, _, _) -> obsolete(_,_,_) -> no. -dialyzer({no_match, obsolete_type/3}). +obsolete_type(crypto, retired_cbc_cipher_aliases, 0) -> + {deprecated, "Use aes_*_cbc or des_ede3_cbc"}; +obsolete_type(crypto, retired_cfb_cipher_aliases, 0) -> + {deprecated, "Use aes_*_cfb8, aes_*_cfb128 or des_ede3_cfb"}; +obsolete_type(crypto, retired_ctr_cipher_aliases, 0) -> + {deprecated, "Use aes_*_ctr"}; +obsolete_type(crypto, retired_ecb_cipher_aliases, 0) -> + {deprecated, "Use aes_*_ecb"}; obsolete_type(erl_scan, column, 0) -> {removed, "use erl_anno:column() instead"}; obsolete_type(erl_scan, line, 0) -> diff --git a/lib/stdlib/src/otp_internal.hrl b/lib/stdlib/src/otp_internal.hrl index ace1fa5cc1..17e15da68f 100644 --- a/lib/stdlib/src/otp_internal.hrl +++ b/lib/stdlib/src/otp_internal.hrl @@ -26,7 +26,7 @@ -export([obsolete/3, obsolete_type/3]). -type tag() :: 'deprecated' | 'removed'. %% | 'experimental'. --type mfas() :: mfa() | {atom(), atom(), [byte()]}. +-type mfas() :: mfa() | {atom(), atom(), [byte()]} | string(). -type release() :: string(). -spec obsolete(module(), atom(), arity()) -> diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl index e65340e663..4819beb62b 100644 --- a/lib/stdlib/src/shell_default.erl +++ b/lib/stdlib/src/shell_default.erl @@ -28,7 +28,7 @@ erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1, y/1, y/2, xm/1, bt/1, q/0, - h/1, h/2, h/3, ht/1, ht/2, ht/3, + h/1, h/2, h/3, ht/1, ht/2, ht/3, hcb/1, hcb/2, hcb/3, ni/0, nregs/0]). -export([ih/0,iv/0,im/0,ii/1,ii/2,iq/1,ini/1,ini/2,inq/1,ib/2,ib/3, @@ -49,8 +49,11 @@ help() -> format("h(Mod,Func)-- help about function in module\n"), format("h(Mod,Func,Arity) -- help about function with arity in module\n"), format("ht(Mod) -- help about a module's types\n"), - format("ht(Mod,Func) -- help about type in module\n"), - format("ht(Mod,Func,Arity) -- help about type with arity in module\n"), + format("ht(Mod,Type) -- help about type in module\n"), + format("ht(Mod,Type,Arity) -- help about type with arity in module\n"), + format("hcb(Mod) -- help about a module's callbacks\n"), + format("hcb(Mod,CB) -- help about callback in module\n"), + format("hcb(Mod,CB,Arity) -- help about callback with arity in module\n"), format("history(N) -- set how many previous commands to keep\n"), format("results(N) -- set how many previous command results to keep\n"), format("catch_exception(B) -- how exceptions are handled\n"), @@ -89,6 +92,9 @@ h(M,F,A) -> c:h(M,F,A). ht(M) -> c:ht(M). ht(M,F) -> c:ht(M,F). ht(M,F,A) -> c:ht(M,F,A). +hcb(M) -> c:hcb(M). +hcb(M,F) -> c:hcb(M,F). +hcb(M,F,A) -> c:hcb(M,F,A). i() -> c:i(). i(X,Y,Z) -> c:i(X,Y,Z). l(Mod) -> c:l(Mod). diff --git a/lib/stdlib/src/shell_docs.erl b/lib/stdlib/src/shell_docs.erl index ad93268fb2..03e7c47309 100644 --- a/lib/stdlib/src/shell_docs.erl +++ b/lib/stdlib/src/shell_docs.erl @@ -23,38 +23,41 @@ -export([render/2, render/3, render/4]). -export([render_type/2, render_type/3, render_type/4]). +-export([render_callback/2, render_callback/3, render_callback/4]). %% Used by chunks.escript in erl_docgen -export([validate/1, normalize/1]). %% Convinience functions --export([get_doc/1, get_doc/3, get_type_doc/3]). +-export([get_doc/1, get_doc/3, get_type_doc/3, get_callback_doc/3]). -record(config, { docs, io_opts = io:getopts(), io_columns = element(2,io:columns()) }). --define(ALL_ELEMENTS,[a,p,'div',h1,h2,h3,i,br,em,pre,code,ul,ol,li,dl,dt,dd]). +-define(ALL_ELEMENTS,[a,p,'div',br,h1,h2,h3,i,em,pre,code,ul,ol,li,dl,dt,dd]). %% inline elements are: --define(INLINE,[i,br,em,code,a]). +-define(INLINE,[i,em,code,a]). -define(IS_INLINE(ELEM),(((ELEM) =:= a) orelse ((ELEM) =:= code) - orelse ((ELEM) =:= i) orelse ((ELEM) =:= br) - orelse ((ELEM) =:= em))). + orelse ((ELEM) =:= i) orelse ((ELEM) =:= em))). %% non-inline elements are: --define(BLOCK,[p,'div',pre,ul,ol,li,dl,dt,dd,h1,h2,h3]). +-define(BLOCK,[p,'div',pre,br,ul,ol,li,dl,dt,dd,h1,h2,h3]). -define(IS_BLOCK(ELEM),not ?IS_INLINE(ELEM)). -define(IS_PRE(ELEM),(((ELEM) =:= pre))). --type chunk_element_type() :: a | p | 'div' | i | br | em | pre | code | ul | - ol | li | dl | dt | dd. --type chunk_element_attr() :: {atom(),unicode:chardata()}. --type chunk_element_attrs() :: [chunk_element_attr()]. +%% If you update the below types, make sure to update the documentation in +%% erl_docgen/doc/src/doc_storage.xml as well!!! +-type docs_v1() :: #docs_v1{}. +-type chunk_elements() :: [chunk_element()]. -type chunk_element() :: {chunk_element_type(),chunk_element_attrs(), chunk_elements()} | binary(). --type chunk_elements() :: [chunk_element()]. --type docs_v1() :: #docs_v1{}. - +-type chunk_element_attrs() :: [chunk_element_attr()]. +-type chunk_element_attr() :: {atom(),unicode:chardata()}. +-type chunk_element_type() :: chunk_element_inline_type() | chunk_element_block_type(). +-type chunk_element_inline_type() :: a | code | em | i. +-type chunk_element_block_type() :: p | 'div' | br | pre | ul | + ol | li | dl | dt | dd | h1 | h2 | h3. -spec validate(Module) -> ok when Module :: module() | docs_v1(). @@ -71,28 +74,80 @@ validate(#docs_v1{ module_doc = MDocs, docs = AllDocs }) -> true = lists:all(fun(Elem) -> ?IS_INLINE(Elem) end, ?INLINE), true = lists:all(fun(Elem) -> ?IS_BLOCK(Elem) end, ?BLOCK), - _ = maps:map(fun(_Key,MDoc) -> validate(MDoc,[]) end, MDocs), + _ = validate_docs(MDocs), lists:foreach(fun({_,_Anno, Sig, Docs, _Meta}) -> - case lists:all(fun erlang:is_binary/1, Sig) of - false -> throw({invalid_signature,Sig}); - true -> ok - end, - maps:map(fun(_Key,Doc) -> validate(Doc,[]) end, Docs) - end, AllDocs), + case lists:all(fun erlang:is_binary/1, Sig) of + false -> throw({invalid_signature,Sig}); + true -> ok + end, + validate_docs(Docs) + end, AllDocs), + ok. + +validate_docs(hidden) -> + ok; +validate_docs(none) -> + ok; +validate_docs(#{} = MDocs) -> + _ = maps:map(fun(_Key,MDoc) -> validate_docs(MDoc,[]) end, MDocs), ok. -validate([H|T],Path) when is_tuple(H) -> - _ = validate(H,Path), - validate(T,Path); -validate({Tag,Attr,Content},Path) -> +validate_docs([H|T],Path) when is_tuple(H) -> + _ = validate_docs(H,Path), + validate_docs(T,Path); +validate_docs({br,Attr,Content} = Br,Path) -> + if Attr =:= [], Content =:= [] -> + ok; + true -> + throw({content_to_allowed_in_br,Br,Path}) + end; +validate_docs({Tag,Attr,Content},Path) -> + + %% Test that we only have li's within ul and ol + case (Tag =/= li) andalso (length(Path) > 0) andalso ((hd(Path) =:= ul) orelse (hd(Path) =:= ol)) of + true -> + throw({only_li_allowed_within_ul_or_ol,Tag,Path}); + _ -> + ok + end, + + %% Test that we only have dd's and dt's within dl + case (Tag =/= dd) andalso (Tag =/= dt) andalso (length(Path) > 0) andalso (hd(Path) =:= dl) of + true -> + throw({only_dd_or_dt_allowed_within_dl,Tag,Path}); + _ -> + ok + end, + + %% Test that we do not have p's within p's case Tag =:= p andalso lists:member(p, Path) of true -> - throw({nested,p,not_allowed}); + throw({nested_p_not_allowed,Tag,Path}); + false -> + ok + end, + %% Test that there are no block tags within a pre, h1, h2 or h3 + case lists:member(pre,Path) or lists:member(h1,Path) or + lists:member(h2,Path) or lists:member(h3,Path) of + true when ?IS_BLOCK(Tag) -> + throw({cannot_put_block_tag_within_pre,Tag,Path}); + _ -> + ok + end, + %% Test that a block tag is not within an inline tag + case lists:member(Tag,?BLOCK) of + true -> + case lists:any(fun(P) -> ?IS_INLINE(P) end, Path) of + true -> + throw({cannot_put_inline_tag_outside_block, Tag, Path}); + false -> + ok + end; false -> ok end, case lists:member(Tag,?ALL_ELEMENTS) of false -> - throw({invalid_tag,Tag}); + throw({invalid_tag,Tag,Path}); true -> ok end, @@ -100,10 +155,10 @@ validate({Tag,Attr,Content},Path) -> true -> ok; false -> throw({invalid_attribute,{Tag,Attr}}) end, - validate(Content,[Tag | Path]); -validate([Chars | T], Path) when is_binary(Chars) -> - validate(T, Path); -validate([],_) -> + validate_docs(Content,[Tag | Path]); +validate_docs([Chars | T], Path) when is_binary(Chars) -> + validate_docs(T, Path); +validate_docs([],_) -> ok. %% Follows algorithm described here: @@ -119,13 +174,13 @@ normalize(Docs) -> normalize_trim(Bin,true) when is_binary(Bin) -> %% Remove any whitespace (except \n) before or after a newline - NoSpace = re:replace(Bin,"[^\\S\n]*\n+[^\\S\n]*","\n",[global]), + NoSpace = re:replace(Bin,"[^\\S\n]*\n+[^\\S\n]*","\n",[unicode,global]), %% Replace any tabs with space - NoTab = re:replace(NoSpace,"\t"," ",[global]), + NoTab = re:replace(NoSpace,"\t"," ",[unicode,global]), %% Replace any newlines with space - NoNewLine = re:replace(NoTab,"\\v"," ",[global]), + NoNewLine = re:replace(NoTab,"\\v"," ",[unicode,global]), %% Replace any sequences of \s with a single " " - re:replace(NoNewLine,"\\s+"," ",[global,{return,binary}]); + re:replace(NoNewLine,"\\s+"," ",[unicode,global,{return,binary}]); normalize_trim(Bin,false) when is_binary(Bin) -> Bin; normalize_trim([{pre,Attr,Content}|T],Trim) -> @@ -148,38 +203,41 @@ normalize_trim([],_Trim) -> normalize_space([{Pre,Attr,Content}|T]) when ?IS_PRE(Pre) -> [{Pre,Attr,trim_first_and_last(Content,$\n)} | normalize_space(T)]; normalize_space([{Block,Attr,Content}|T]) when ?IS_BLOCK(Block) -> - [{Block,Attr,trim_first_and_last(trim_inline(Content),$ )} | normalize_space(T)]; -normalize_space([B]) when is_binary(B) -> - trim_first_and_last([B],$ ); -normalize_space([E|T]) -> - [E|normalize_space(T)]; + [{Block,Attr,normalize_space(Content)} | normalize_space(T)]; normalize_space([]) -> - []. + []; +normalize_space(Elems) -> + {InlineElems, T} = + lists:splitwith(fun(E) -> + is_binary(E) orelse (is_tuple(E) andalso ?IS_INLINE(element(1,E))) + end, Elems), + trim_inline(InlineElems) ++ normalize_space(T). trim_inline(Content) -> {NewContent,_} = trim_inline(Content,false), - NewContent. + trim_first_and_last(NewContent,$ ). trim_inline([Bin|T],false) when is_binary(Bin) -> LastElem = binary:at(Bin,byte_size(Bin)-1), - {NewT, NewState} = trim_inline(T,LastElem =:= $ ), - {[Bin | NewT],NewState}; + case trim_inline(T,LastElem =:= $ ) of + {[B2 | NewT],NewState} when is_binary(B2) -> + {[<<Bin/binary,B2/binary>>|NewT],NewState}; + {NewT, NewState} -> + {[Bin|NewT],NewState} + end; trim_inline([<<" ">>|T],true) -> - trim_inline(T,false); + trim_inline(T,true); trim_inline([<<" ",Bin/binary>>|T],true) when is_binary(Bin) -> - trim_inline([Bin | T],false); + trim_inline([Bin | T],true); trim_inline([Bin|T],true) when is_binary(Bin) -> trim_inline([Bin|T],false); -trim_inline([{Elem,Attr,Content}|T],TrimSpace) when ?IS_INLINE(Elem) -> +trim_inline([{Elem,Attr,Content}|T],TrimSpace) -> {NewContent,ContentTrimSpace} = trim_inline(Content,TrimSpace), {NewT,TTrimSpace} = trim_inline(T,ContentTrimSpace), - {[{Elem,Attr,NewContent} | NewT], TTrimSpace}; -trim_inline([{Elem1,_A1,_C1} = B1,<<" ">>,{Elem2,_A2,_C2} = B2|T],TrimSpace) - when ?IS_BLOCK(Elem1),?IS_BLOCK(Elem2) -> - trim_inline([B1,B2|T],TrimSpace); -trim_inline([{Elem,_Attr,_Content} = Block|T],_TrimSpace) when ?IS_BLOCK(Elem) -> - [NewBlock] = normalize_space([Block]), - {NewT,TTrimSpace} = trim_inline(T,false), - {[NewBlock | NewT], TTrimSpace}; + if NewContent == [] -> + {NewT, TTrimSpace}; + true -> + {[{Elem,Attr,NewContent} | NewT], TTrimSpace} + end; trim_inline([],TrimSpace) -> {[],TrimSpace}. @@ -205,6 +263,8 @@ trim_first([Bin|T],false,What) when is_binary(Bin) -> end; trim_first([{Elem,Attr,Content} = Tag|T],false,What) -> case trim_first(Content,false,What) of + {[],true} -> + {T,true}; {NewContent,true} -> {[{Elem,Attr,NewContent}|T],true}; {Content,false} -> @@ -233,8 +293,12 @@ trim_last([{Elem,Attr,Content} = Tag|T],What) -> {NewT,true} -> {[Tag | NewT],true}; {T,false} -> - {NewContent,NewState} = trim_last(Content,What), - {[{Elem,Attr,NewContent}|T],NewState} + case trim_last(Content,What) of + {[],NewState} -> + {T,NewState}; + {NewContent,NewState} -> + {[{Elem,Attr,NewContent}|T],NewState} + end end; trim_last([],_What) -> {[],false}. @@ -265,9 +329,9 @@ get_doc(Module, Function, Arity) -> [{F,A,S,get_local_doc({F,A},D),M} || {F,A,S,D,M} <- FnFunctions]. -spec render(Module :: module(), Docs :: docs_v1()) -> unicode:chardata(). -render(Module, #docs_v1{ module_doc = ModuleDoc, metadata = MD } = D) -> - render_docs([["\t",atom_to_binary(Module)]], - get_local_doc(Module, ModuleDoc), MD, D). +render(Module, #docs_v1{ module_doc = ModuleDoc } = D) -> + render_headers_and_docs([[{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]}]], + get_local_doc(Module, ModuleDoc), D). -spec render(Module :: module(), Function :: function(), Docs :: docs_v1()) -> unicode:chardata() | {error,function_missing}. @@ -309,18 +373,13 @@ get_type_doc(Module, Type, Arity) -> [{F,A,S,get_local_doc(F, D),M} || {F,A,S,D,M} <- FnFunctions]. -spec render_type(Module :: module(), Docs :: docs_v1()) -> unicode:chardata(). -render_type(Module, #docs_v1{ docs = Docs } = D) -> - render_type_signatures(Module, - lists:filter(fun({{type, _, _},_Anno,_Sig,_Doc,_Meta}) -> - true; - (_) -> - false - end, Docs), D). +render_type(Module, D) -> + render_signature_listing(Module, type, D). -spec render_type(Module :: module(), Type :: atom(), Docs :: docs_v1()) -> unicode:chardata() | {error,type_missing}. render_type(_Module, Type, #docs_v1{ docs = Docs } = D) -> - render_type_docs( + render_typecb_docs( lists:filter(fun({{type, T, _},_Anno,_Sig,_Doc,_Meta}) -> T =:= Type; (_) -> @@ -330,13 +389,56 @@ render_type(_Module, Type, #docs_v1{ docs = Docs } = D) -> -spec render_type(Module :: module(), Type :: atom(), Arity :: arity(), Docs :: docs_v1()) -> unicode:chardata() | {error,type_missing}. render_type(_Module, Type, Arity, #docs_v1{ docs = Docs } = D) -> - render_type_docs( + render_typecb_docs( lists:filter(fun({{type, T, A},_Anno,_Sig,_Doc,_Meta}) -> T =:= Type andalso A =:= Arity; (_) -> false end, Docs), D). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% API function for dealing with the callback documentation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec get_callback_doc(Module :: module(), Callback :: atom(), Arity :: arity()) -> + [{{Callback,Arity}, Anno, Signature, chunk_elements(), Metadata}] when + Callback :: atom(), + Arity :: arity(), + Anno :: erl_anno:anno(), + Signature :: [binary()], + Metadata :: #{}. +get_callback_doc(Module, Callback, Arity) -> + {ok, #docs_v1{ docs = Docs } } = code:get_doc(Module), + FnFunctions = + lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) -> + T =:= Callback andalso A =:= Arity; + (_) -> + false + end, Docs), + [{F,A,S,get_local_doc(F, D),M} || {F,A,S,D,M} <- FnFunctions]. + +-spec render_callback(Module :: module(), Docs :: docs_v1()) -> unicode:chardata(). +render_callback(Module, D) -> + render_signature_listing(Module, callback, D). + +-spec render_callback(Module :: module(), Callback :: atom(), Docs :: docs_v1()) -> + unicode:chardata() | {error,callback_missing}. +render_callback(_Module, Callback, #docs_v1{ docs = Docs } = D) -> + render_typecb_docs( + lists:filter(fun({{callback, T, _},_Anno,_Sig,_Doc,_Meta}) -> + T =:= Callback; + (_) -> + false + end, Docs), D). + +-spec render_callback(Module :: module(), Callback :: atom(), Arity :: arity(), + Docs :: docs_v1()) -> unicode:chardata() | {error,callback_missing}. +render_callback(_Module, Callback, Arity, #docs_v1{ docs = Docs } = D) -> + render_typecb_docs( + lists:filter(fun({{callback, T, A},_Anno,_Sig,_Doc,_Meta}) -> + T =:= Callback andalso A =:= Arity; + (_) -> + false + end, Docs), D). %% Get the docs in the correct locale if it exists. get_local_doc(MissingMod, Docs) when is_atom(MissingMod) -> @@ -359,58 +461,127 @@ get_local_doc(Missing, None) when None =:= none; None =:= #{} -> %%% Functions for rendering reference documentation render_function([], _D) -> {error,function_missing}; -render_function(FDocs, D) -> - [render_docs(render_signature(Func), get_local_doc({F,A},Doc), Meta, D) - || {{_,F,A},_Anno,_Sig,Doc,Meta} = Func <- lists:sort(FDocs)]. - -%% Render the signature of either function or a type, or anything else really. -render_signature({{_Type,_F,_A},_Anno,_Sig,_Docs,#{ signature := Specs }}) -> - [erl_pp:attribute(Spec,[{encoding,utf8}]) || Spec <- Specs]; -render_signature({{_Type,_F,_A},_Anno,Sigs,_Docs,_Meta}) -> - [Sig || Sig <- Sigs]. - -render_since(#{ since := Vsn }) -> - ["\n\nSince: ",Vsn]; -render_since(_) -> +render_function(FDocs, #docs_v1{ docs = Docs } = D) -> + Grouping = + lists:foldl( + fun({_Group,_Anno,_Sig,_Doc,#{ equiv := Group }} = Func,Acc) -> + Members = maps:get(Group, Acc, []), + Acc#{ Group => [Func|Members] }; + ({Group, _Anno, _Sig, _Doc, _Meta} = Func, Acc) -> + Members = maps:get(Group, Acc, []), + Acc#{ Group => [Func|Members] } + end, #{}, lists:sort(FDocs)), + lists:map( + fun({{_,F,A} = Group,Members}) -> + Signatures = lists:flatmap(fun render_signature/1,lists:reverse(Members)), + case lists:search(fun({_,_,_,Doc,_}) -> + Doc =/= #{} + end, Members) of + {value, {_,_,_,Doc,_Meta}} -> + render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D); + false -> + case lists:keyfind(Group, 1, Docs) of + false -> + render_headers_and_docs(Signatures, get_local_doc({F,A},none), D); + {_,_,_,Doc,_} -> + render_headers_and_docs(Signatures, get_local_doc({F,A},Doc), D) + end + end + end, maps:to_list(Grouping)). + +%% Render the signature of either function, type, or anything else really. +render_signature({{_Type,_F,_A},_Anno,_Sigs,_Docs,#{ signature := Specs } = Meta}) -> + lists:flatmap( + fun(ASTSpec) -> + PPSpec = erl_pp:attribute(ASTSpec,[{encoding,utf8}]), + Spec = + case ASTSpec of + {_Attribute, _Line, opaque, _} -> + %% We do not want show the internals of the opaque type + hd(string:split(PPSpec,"::")); + _ -> + PPSpec + end, + BinSpec = + unicode:characters_to_binary( + string:trim(Spec, trailing, "\n")), + [{pre,[],[{em,[],BinSpec}]}|render_meta(Meta)] + end, Specs); +render_signature({{_Type,_F,_A},_Anno,Sigs,_Docs,Meta}) -> + lists:flatmap( + fun(Sig) -> + [{h2,[],[<<"  "/utf8,Sig/binary>>]}|render_meta(Meta)] + end, Sigs). + +render_meta(M) -> + case render_meta_(M) of + [] -> []; + Meta -> + [[{dl,[],Meta}]] + end. +render_meta_(#{ since := Vsn } = M) -> + [{dt,[],<<"Since">>},{dd,[],[Vsn]} + | render_meta_(maps:remove(since, M))]; +render_meta_(#{ deprecated := Depr } = M) -> + [{dt,[],<<"Deprecated">>},{dd,[],[Depr]} + | render_meta_(maps:remove(deprecated, M))]; +render_meta_(_) -> []. -render_docs(Headers, DocContents, MD, D = #config{}) -> - init_ansi(D), - try - {Doc,_} = trimnl(render_docs(DocContents,[],0,2,D)), - [sansi(bold), - [io_lib:format("~n~ts",[Header]) || Header <- Headers], - ransi(bold), - render_since(MD), - io_lib:format("~n~n~ts",[Doc])] - after - clean_ansi() - end; -render_docs(Headers, DocContents, MD, D) -> - render_docs(Headers, DocContents, MD, #config{ docs = D }). +render_headers_and_docs(Headers, DocContents, D) -> + ["\n",render_docs( + lists:flatmap( + fun(Header) -> + [{br,[],[]},Header] + end,Headers), 0, D), + "\n", + render_docs(DocContents,2,D)]. + +%%% Functions for rendering type/callback documentation +render_signature_listing(Module, Type, #docs_v1{ docs = Docs } = D) -> + Slogan = [{h2,[],[<<"\t",(atom_to_binary(Module))/binary>>]},{br,[],[]}], + case lists:filter(fun({{T, _, _},_Anno,_Sig,_Doc,_Meta}) -> + Type =:= T + end, Docs) of + [] -> + render_docs( + Slogan ++ [<<"There are no ",(atom_to_binary(Type))/binary,"s " + "in this module">>], D); + Headers -> + Hdr = lists:flatmap( + fun(Header) -> + [{br,[],[]},render_signature(Header)] + end,Headers), + render_docs( + Slogan ++ + [{p,[],[<<"These ",(atom_to_binary(Type))/binary,"s " + "are documented in this module:">>]}, + {br,[],[]}, Hdr], D) + end. + +render_typecb_docs([], _D) -> + {error,type_missing}; +render_typecb_docs(TypeCBs, #config{} = D) when is_list(TypeCBs) -> + [render_typecb_docs(TypeCB, D) || TypeCB <- TypeCBs]; +render_typecb_docs({{_,F,A},_,_Sig,Docs,_Meta} = TypeCB, #config{} = D) -> + render_headers_and_docs(render_signature(TypeCB), get_local_doc({F,A},Docs), D); +render_typecb_docs(Docs, D) -> + render_typecb_docs(Docs, #config{ docs = D }). -%%% Functions for rendering type documentation -render_type_signatures(Module, Types, D = #config{}) -> +%%% General rendering functions +render_docs(DocContents, D) -> + render_docs(DocContents, 0, D). +render_docs(DocContents, Ind, D = #config{}) -> init_ansi(D), try - [sansi(bold),"\t",atom_to_list(Module),ransi(bold),"\n\n", - [render_signature(Type) || Type <- Types ]] + {Doc,_} = trimnl(render_docs(DocContents, [], 0, Ind, D)), + Doc after clean_ansi() end; -render_type_signatures(Module, Types, D) -> - render_type_signatures(Module, Types, #config{ docs = D }). - -render_type_docs([], _D) -> - {error,type_missing}; -render_type_docs(Types, #config{} = D) when is_list(Types) -> - [render_type_docs(Type, D) || Type <- Types]; -render_type_docs({{_,F,A},_,_Sig,Docs,Meta} = Type, #config{} = D) -> - render_docs(render_signature(Type), get_local_doc({F,A},Docs), Meta, D); -render_type_docs(Docs, D) -> - render_type_docs(Docs, #config{ docs = D }). +render_docs(DocContents, Ind, D) -> + render_docs(DocContents, Ind, #config{ docs = D }). -%%% General rendering functions render_docs(Elems,State,Pos,Ind,D) when is_list(Elems) -> lists:mapfoldl(fun(Elem,P) -> % io:format("Elem: ~p (~p) (~p,~p)~n",[Elem,State,P,Ind]), @@ -443,10 +614,10 @@ render_docs(Elem,State,Pos,Ind,D) -> {unicode:chardata(), Pos :: non_neg_integer()}. render_element({IgnoreMe,_,Content}, State, Pos, Ind,D) - when IgnoreMe =:= a; IgnoreMe =:= anno -> + when IgnoreMe =:= a -> render_docs(Content, State, Pos, Ind,D); -%% Catch h1, h2 and h3 before the padding is done as there reset padding +%% Catch h1, h2 and h3 before the padding is done as they reset padding render_element({h1,_,Content},State,0 = Pos,_Ind,D) -> trimnlnl(render_element({code,[],[{em,[],Content}]}, State, Pos, 0, D)); render_element({h2,_,Content},State,0 = Pos,_Ind,D) -> @@ -461,7 +632,7 @@ render_element({'div',[{class,What}],Content},State,Pos,Ind,D) -> {Docs,_} = render_docs(Content, ['div'|State], 0, Ind+2, D), trimnlnl([pad(Ind - Pos),string:titlecase(What),":\n",Docs]); render_element({Tag,_,Content},State,Pos,Ind,D) when Tag =:= p; Tag =:= 'div' -> - trimnlnl(render_docs(Content, [Tag|State], Pos, Ind,D)); + trimnlnl(render_docs(Content, [Tag|State], Pos, Ind, D)); render_element(Elem,State,Pos,Ind,D) when Pos < Ind -> % io:format("Pad: ~p~n",[Ind - Pos]), @@ -481,8 +652,8 @@ render_element({i,_,Content},State,Pos,Ind,D) -> %% Just ignore i as ansi does not have cursive style render_docs(Content, State, Pos, Ind,D); -render_element({br,[],[]},_State,_Pos,_Ind,_D) -> - nl(""); +render_element({br,[],[]},_State,Pos,_Ind,_D) -> + {"",Pos}; render_element({em,_,Content},State,Pos,Ind,D) -> Bold = sansi(bold), @@ -550,7 +721,13 @@ render_element(B, State, Pos, Ind,#config{ io_columns = Cols }) when is_binary(B end; render_element({Tag,Attr,Content}, State, Pos, Ind,D) -> - throw({unhandled,{Tag,Attr,Content,Pos,Ind}}), + case lists:member(Tag,?ALL_ELEMENTS) of + true -> + throw({unhandled_element,Tag,Attr,Content}); + false -> + %% We ignore tags that we do not care about + ok + end, render_docs(Content, State, Pos, Ind,D). render_words(Words,[_,types|State],Pos,Ind,Acc,Cols) -> @@ -560,8 +737,10 @@ render_words(Words,[_,types|State],Pos,Ind,Acc,Cols) -> render_words([Word|T],State,Pos,Ind,Acc,Cols) when is_binary(Word) -> WordLength = string:length(Word), NewPos = WordLength + Pos, + %% We do not want to add a newline if this word is only a punctuation + IsPunct = is_tuple(re:run(Word,"^\\W$",[unicode])), if - NewPos > (Cols - 10 - Ind) -> + NewPos > (Cols - 10 - Ind), Word =/= <<>>, not IsPunct -> %% Word does not fit, time to add a newline and also pad to Indent level render_words(T,State,WordLength+Ind+1,Ind,[[[pad(Ind), Word]]|Acc],Cols); true -> @@ -601,9 +780,14 @@ get_bullet(_State,latin1) -> <<" * ">>; get_bullet(State,unicode) -> %% Fancy bullet point logic! - lists:nth(length([l || l <- State]), - [<<" • "/utf8>>,<<" ○ "/utf8>>, - <<" ◼ "/utf8>>,<<" ◻ "/utf8>>]). + case length([l || l <- State]) of + Level when Level > 4 -> + get_bullet(State, latin1); + Level -> + lists:nth(Level, + [<<" • "/utf8>>,<<" ○ "/utf8>>, + <<" ◼ "/utf8>>,<<" ◻ "/utf8>>]) + end. % Look for the length of the last line of a string lastline(Str) -> diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index 774f7eaa9c..b59e3b28c0 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -109,6 +109,6 @@ dets]}, {applications, [kernel]}, {env, []}, - {runtime_dependencies, ["sasl-3.0","kernel-@OTP-15251@","erts-@OTP-15251:OTP-16431@","crypto-3.3", + {runtime_dependencies, ["sasl-3.0","kernel-7.0","erts-11.0","crypto-3.3", "compiler-5.0"]} ]}. diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index 6d6ee14d29..85fba9ebbd 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -21,6 +21,7 @@ %% versions from the following OTP releases: %% - OTP 21 %% - OTP 22 +%% - OTP 23 %% %% We also allow upgrade from, and downgrade to all %% versions that have branched off from the above @@ -35,6 +36,7 @@ {<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^3\\.12$">>,[restart_new_emulator]}, {<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.12\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^3\\.5$">>,[restart_new_emulator]}, {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, @@ -59,6 +61,7 @@ {<<"^3\\.11\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^3\\.12$">>,[restart_new_emulator]}, {<<"^3\\.12\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.12\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, {<<"^3\\.5$">>,[restart_new_emulator]}, {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, diff --git a/lib/stdlib/test/digraph_SUITE.erl b/lib/stdlib/test/digraph_SUITE.erl index b5d3452616..ce0bc90f1c 100644 --- a/lib/stdlib/test/digraph_SUITE.erl +++ b/lib/stdlib/test/digraph_SUITE.erl @@ -31,7 +31,7 @@ init_per_group/2,end_per_group/2]). -export([opts/1, degree/1, path/1, cycle/1, vertices/1, - edges/1, data/1, otp_3522/1, otp_3630/1, otp_8066/1]). + edges/1, data/1, otp_3522/1, otp_3630/1, otp_8066/1, vertex_names/1]). -export([spawn_graph/2]). @@ -41,10 +41,10 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [opts, degree, path, cycle, {group, misc}, - {group, tickets}]. + {group, tickets}, vertex_names]. groups() -> - [{misc, [], [vertices, edges, data]}, + [{misc, [], [vertices, edges, data, vertex_names]}, {tickets, [], [otp_3522, otp_3630, otp_8066]}]. init_per_suite(Config) -> @@ -337,6 +337,51 @@ otp_8066(Config) when is_list(Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +vertex_names(Config) when is_list(Config) -> + %% Check that a node named '_' does not lead to wildcard matches + %% in ets. + + G = digraph:new([acyclic]), + A = digraph:add_vertex(G, 'A'), + B = digraph:add_vertex(G, '_'), + AB = digraph:add_edge(G, A, B), + + %% Link A -> B + 1 = digraph:out_degree(G, A), + 1 = digraph:in_degree(G, B), + 0 = digraph:out_degree(G, B), + 0 = digraph:in_degree(G, A), + [B] = digraph:out_neighbours(G, A), + [A] = digraph:in_neighbours(G, B), + [] = digraph:out_neighbours(G, B), + [] = digraph:in_neighbours(G, A), + [AB] = digraph:out_edges(G, A), + [AB] = digraph:in_edges(G, B), + [] = digraph:out_edges(G, B), + [] = digraph:in_edges(G, A), + + %% Reverse the edge + digraph:del_edge(G, AB), + BA = digraph:add_edge(G, B, A), + + 1 = digraph:out_degree(G, B), + 1 = digraph:in_degree(G, A), + 0 = digraph:out_degree(G, A), + 0 = digraph:in_degree(G, B), + [A] = digraph:out_neighbours(G, B), + [B] = digraph:in_neighbours(G, A), + [] = digraph:out_neighbours(G, A), + [] = digraph:in_neighbours(G, B), + [BA] = digraph:out_edges(G, B), + [BA] = digraph:in_edges(G, A), + [] = digraph:out_edges(G, A), + [] = digraph:in_edges(G, B), + + digraph:delete(G), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + sane(G) -> sane1(G), erase(sane) =:= undefined. diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 1bde1b3d74..76dee868e9 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -453,19 +453,24 @@ stop7(Config) -> stop8(Config) -> Node = gen_statem_stop8, {ok,NodeName} = ct_slave:start(Node), - Dir = filename:dirname(code:which(?MODULE)), - rpc:call(NodeName, code, add_path, [Dir]), - {ok,Pid} = - rpc:call( - NodeName, gen_statem,start, - [?MODULE,start_arg(Config, []),[]]), - ok = gen_statem:stop(Pid), - false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), - noproc = - ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), - {ok,NodeName} = ct_slave:stop(Node), + Statem = + try + Dir = filename:dirname(code:which(?MODULE)), + rpc:block_call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:block_call( + NodeName, gen_statem,start, + [?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(Pid), + false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), + Pid + after + {ok,NodeName} = ct_slave:stop(Node) + end, {{nodedown,NodeName},{sys,terminate,_}} = - ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2), + ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2), ok. %% Registered name on remote node @@ -474,21 +479,26 @@ stop9(Config) -> LocalSTM = {local,Name}, Node = gen_statem__stop9, {ok,NodeName} = ct_slave:start(Node), - STM = {Name,NodeName}, - Dir = filename:dirname(code:which(?MODULE)), - rpc:call(NodeName, code, add_path, [Dir]), - {ok,Pid} = - rpc:call( - NodeName, gen_statem, start, - [LocalSTM,?MODULE,start_arg(Config, []),[]]), - ok = gen_statem:stop(STM), - undefined = rpc:call(NodeName,erlang,whereis,[Name]), - false = rpc:call(NodeName,erlang,is_process_alive,[Pid]), - noproc = - ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), - {ok,NodeName} = ct_slave:stop(Node), + Statem = + try + STM = {Name,NodeName}, + Dir = filename:dirname(code:which(?MODULE)), + rpc:block_call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:block_call( + NodeName, gen_statem, start, + [LocalSTM,?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(STM), + undefined = rpc:block_call(NodeName,erlang,whereis,[Name]), + false = rpc:block_call(NodeName,erlang,is_process_alive,[Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + STM + after + {ok,NodeName} = ct_slave:stop(Node) + end, {{nodedown,NodeName},{sys,terminate,_}} = - ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), + ?EXPECT_FAILURE(gen_statem:stop(Statem), Reason2), ok. %% Globally registered name on remote node @@ -496,18 +506,21 @@ stop10(Config) -> Node = gen_statem_stop10, STM = {global,to_stop}, {ok,NodeName} = ct_slave:start(Node), - Dir = filename:dirname(code:which(?MODULE)), - rpc:call(NodeName,code,add_path,[Dir]), - {ok,Pid} = - rpc:call( - NodeName, gen_statem, start, - [STM,?MODULE,start_arg(Config, []),[]]), - global:sync(), - ok = gen_statem:stop(STM), - false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), - noproc = - ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), - {ok,NodeName} = ct_slave:stop(Node), + try + Dir = filename:dirname(code:which(?MODULE)), + rpc:block_call(NodeName,code,add_path,[Dir]), + {ok,Pid} = + rpc:block_call( + NodeName, gen_statem, start, + [STM,?MODULE,start_arg(Config, []),[]]), + global:sync(), + ok = gen_statem:stop(STM), + false = rpc:block_call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1) + after + {ok,NodeName} = ct_slave:stop(Node) + end, noproc = ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), ok. @@ -521,10 +534,10 @@ abnormal1(Config) -> gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), %% timeout call. - delayed = gen_statem:call(Name, {delayed_answer,1}, 100), + delayed = gen_statem:call(Name, {delayed_answer,100}, 2000), {timeout,_} = ?EXPECT_FAILURE( - gen_statem:call(Name, {delayed_answer,1000}, 10), + gen_statem:call(Name, {delayed_answer,2000}, 100), Reason), ok = gen_statem:stop(Name), ?t:sleep(1100), @@ -1424,7 +1437,7 @@ hibernate(Config) -> {ok,Pid0} = gen_statem:start_link( ?MODULE, start_arg(Config, hiber_now), []), - is_in_erlang_hibernate(Pid0), + wait_erlang_hibernate(Pid0), stop_it(Pid0), receive {'EXIT',Pid0,normal} -> ok @@ -1437,23 +1450,23 @@ hibernate(Config) -> true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)), hibernating = gen_statem:call(Pid, hibernate_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), good_morning = gen_statem:call(Pid, wakeup_sync), is_not_in_erlang_hibernate(Pid), hibernating = gen_statem:call(Pid, hibernate_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), please_just_five_more = gen_statem:call(Pid, snooze_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), good_morning = gen_statem:call(Pid, wakeup_sync), is_not_in_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, hibernate_async), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, wakeup_async), is_not_in_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, hibernate_async), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, snooze_async), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, wakeup_async), is_not_in_erlang_hibernate(Pid), @@ -1461,14 +1474,14 @@ hibernate(Config) -> true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid, current_function)), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), 'alive!' = gen_statem:call(Pid, 'alive?'), true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid, current_function)), Pid ! hibernate_now, - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), 'alive!' = gen_statem:call(Pid, 'alive?'), true = @@ -1476,34 +1489,34 @@ hibernate(Config) -> erlang:process_info(Pid, current_function)), hibernating = gen_statem:call(Pid, hibernate_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), good_morning = gen_statem:call(Pid, wakeup_sync), is_not_in_erlang_hibernate(Pid), hibernating = gen_statem:call(Pid, hibernate_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), please_just_five_more = gen_statem:call(Pid, snooze_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), good_morning = gen_statem:call(Pid, wakeup_sync), is_not_in_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, hibernate_async), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, wakeup_async), is_not_in_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, hibernate_async), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, snooze_async), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), ok = gen_statem:cast(Pid, wakeup_async), is_not_in_erlang_hibernate(Pid), hibernating = gen_statem:call(Pid, hibernate_sync), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), sys:suspend(Pid), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), sys:resume(Pid), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), receive after 1000 -> ok end, - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), good_morning = gen_statem:call(Pid, wakeup_sync), is_not_in_erlang_hibernate(Pid), @@ -1519,15 +1532,16 @@ hibernate(Config) -> %% Auto-hibernation timeout auto_hibernate(Config) -> OldFl = process_flag(trap_exit, true), - HibernateAfterTimeout = 100, + HibernateAfterTimeout = 1000, {ok,Pid} = gen_statem:start_link( - ?MODULE, start_arg(Config, []), [{hibernate_after, HibernateAfterTimeout}]), + ?MODULE, start_arg(Config, []), + [{hibernate_after, HibernateAfterTimeout}]), %% After init test is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), %% After info test Pid ! {hping, self()}, receive @@ -1538,7 +1552,7 @@ auto_hibernate(Config) -> end, is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), %% After cast test ok = gen_statem:cast(Pid, {hping, self()}), receive @@ -1549,42 +1563,42 @@ auto_hibernate(Config) -> end, is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), %% After call test hpong = gen_statem:call(Pid, hping), is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), %% Timer test 1 - TimerTimeout1 = 50, - ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout1}), + TimerTimeout1 = HibernateAfterTimeout div 2, + ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout1}), is_not_in_erlang_hibernate(Pid), timer:sleep(TimerTimeout1), is_not_in_erlang_hibernate(Pid), receive - {Pid, htimer_armed} -> + {Pid, htimer_timeout} -> ok after 1000 -> ct:fail(timer1) end, is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), %% Timer test 2 - TimerTimeout2 = 150, - ok = gen_statem:call(Pid, {arm_htimer, self(), TimerTimeout2}), + TimerTimeout2 = HibernateAfterTimeout * 2, + ok = gen_statem:call(Pid, {start_htimer, self(), TimerTimeout2}), is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), receive - {Pid, htimer_armed} -> + {Pid, htimer_timeout} -> ok - after 1000 -> + after TimerTimeout2 -> ct:fail(timer2) end, is_not_in_erlang_hibernate(Pid), timer:sleep(HibernateAfterTimeout), - is_in_erlang_hibernate(Pid), + wait_erlang_hibernate(Pid), stop_it(Pid), process_flag(trap_exit, OldFl), receive @@ -1594,38 +1608,38 @@ auto_hibernate(Config) -> end, ok = verify_empty_msgq(). -is_in_erlang_hibernate(Pid) -> + +wait_erlang_hibernate(Pid) -> receive after 1 -> ok end, - is_in_erlang_hibernate_1(200, Pid). + wait_erlang_hibernate_1(200, Pid). -is_in_erlang_hibernate_1(0, Pid) -> +wait_erlang_hibernate_1(0, Pid) -> ct:log("~p\n", [erlang:process_info(Pid, current_function)]), - ct:fail(not_in_erlang_hibernate_3); -is_in_erlang_hibernate_1(N, Pid) -> + ct:fail(should_be_in_erlang_hibernate_3); +wait_erlang_hibernate_1(N, Pid) -> {current_function,MFA} = erlang:process_info(Pid, current_function), case MFA of {erlang,hibernate,3} -> ok; _ -> receive after 10 -> ok end, - is_in_erlang_hibernate_1(N-1, Pid) + wait_erlang_hibernate_1(N-1, Pid) end. is_not_in_erlang_hibernate(Pid) -> receive after 1 -> ok end, is_not_in_erlang_hibernate_1(200, Pid). -is_not_in_erlang_hibernate_1(0, Pid) -> - ct:log("~p\n", [erlang:process_info(Pid, current_function)]), - ct:fail(not_in_erlang_hibernate_3); +is_not_in_erlang_hibernate_1(0, _Pid) -> + ct:fail(should_not_be_in_erlang_hibernate_3); is_not_in_erlang_hibernate_1(N, Pid) -> {current_function,MFA} = erlang:process_info(Pid, current_function), case MFA of - {erlang,hibernate,3} -> + {erlang,hibernate,3} -> receive after 10 -> ok end, is_not_in_erlang_hibernate_1(N-1, Pid); - _ -> - ok + _ -> + ok end. @@ -2340,13 +2354,13 @@ init(stop_shutdown) -> {stop,shutdown}; init(sleep) -> ?t:sleep(1000), - {ok,idle,data}; + init_sup({ok,idle,data}); init(hiber) -> - {ok,hiber_idle,[]}; + init_sup({ok,hiber_idle,[]}); init(hiber_now) -> - {ok,hiber_idle,[],[hibernate]}; + init_sup({ok,hiber_idle,[],[hibernate]}); init({data, Data}) -> - {ok,idle,Data}; + init_sup({ok,idle,Data}); init({callback_mode,CallbackMode,Arg}) -> ets:new(?MODULE, [named_table,private]), ets:insert(?MODULE, {callback_mode,CallbackMode}), @@ -2356,14 +2370,35 @@ init({map_statem,#{init := Init}=Machine,Modes}) -> ets:insert(?MODULE, {callback_mode,[handle_event_function|Modes]}), case Init() of {ok,State,Data,Ops} -> - {ok,State,[Data|Machine],Ops}; + init_sup({ok,State,[Data|Machine],Ops}); {ok,State,Data} -> - {ok,State,[Data|Machine]}; + init_sup({ok,State,[Data|Machine]}); Other -> - Other + init_sup(Other) end; init([]) -> - {ok,idle,data}. + init_sup({ok,idle,data}). + +%% Supervise state machine parent i.e the test case, and if it dies +%% (fails due to some reason), kill the state machine, +%% just to not leak resources (process, name, ETS table, etc...) +%% +init_sup(Result) -> + Parent = gen:get_parent(), + Statem = self(), + _Supervisor = + spawn( + fun () -> + StatemRef = monitor(process, Statem), + ParentRef = monitor(process, Parent), + receive + {'DOWN', StatemRef, _, _, Reason} -> + exit(Reason); + {'DOWN', ParentRef, _, _, _} -> + exit(Statem, kill) + end + end), + Result. callback_mode() -> try ets:lookup(?MODULE, callback_mode) of @@ -2396,10 +2431,10 @@ idle(cast, {hping,Pid}, Data) -> {keep_state, Data}; idle({call, From}, hping, _Data) -> {keep_state_and_data, [{reply, From, hpong}]}; -idle({call, From}, {arm_htimer, Pid, Timeout}, _Data) -> - {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {arm_htimer, Pid}}]}; -idle(timeout, {arm_htimer, Pid}, _Data) -> - Pid ! {self(), htimer_armed}, +idle({call, From}, {start_htimer, Pid, Timeout}, _Data) -> + {keep_state_and_data, [{reply, From, ok}, {timeout, Timeout, {htimer, Pid}}]}; +idle(timeout, {htimer, Pid}, _Data) -> + Pid ! {self(), htimer_timeout}, keep_state_and_data; idle(cast, {connect,Pid}, Data) -> Pid ! accept, diff --git a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl index 1bcd08867f..1de7de527b 100644 --- a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl +++ b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl @@ -31,6 +31,24 @@ start(Opts) -> gen_statem:start({local, ?MODULE}, ?MODULE, [], Opts). init([]) -> + %% Supervise state machine parent i.e the test case, and if it dies + %% (fails due to some reason), kill the state machine, + %% just to not leak resources (process, name, ETS table, etc...) + %% + Parent = gen:get_parent(), + Statem = self(), + _Supervisor = + spawn( + fun () -> + StatemRef = monitor(process, Statem), + ParentRef = monitor(process, Parent), + receive + {'DOWN', StatemRef, _, _, Reason} -> + exit(Reason); + {'DOWN', ParentRef, _, _, _} -> + exit(Statem, kill) + end + end), {ok, start, #{}}. callback_mode() -> diff --git a/lib/stdlib/test/property_test/shell_docs_prop.erl b/lib/stdlib/test/property_test/shell_docs_prop.erl new file mode 100644 index 0000000000..83b62e8837 --- /dev/null +++ b/lib/stdlib/test/property_test/shell_docs_prop.erl @@ -0,0 +1,135 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2020. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(shell_docs_prop). +-export([prop_render/0]). + +%%% This will include the .hrl file for the installed testing tool: +-include_lib("common_test/include/ct_property_test.hrl"). +-include_lib("kernel/include/eep48.hrl"). +-compile([export_all]). + +prop_render() -> + numtests(10000, + ?FORALL(Doc, + ?LET(Blocks,blocks(), + #docs_v1{ module_doc = #{ <<"en">> => Blocks }, docs = [] } + ), + begin + try + shell_docs:render(test, Doc), + shell_docs:validate(Doc), + N1 = shell_docs:normalize(maps:get(<<"en">>,Doc#docs_v1.module_doc)), + N1 = shell_docs:normalize(N1), + true + catch C:E:ST -> + ct:pal("~p ~p:~p ~p~n",[Doc,C,E,ST]), + false + end + end)). + +-define(LIMIT,5). + +blocks() -> + blocks([]). + +blocks([l|_] = S) -> + ?LAZY( + frequency( + [{2,[]}, + {2,[{li,[],blocks([li | S])}]}]) + ); +blocks([dl|_] = S) -> + ?LAZY( + frequency( + [{2,[]}, + {2,[{dt,[],blocks([dt | S])}]}, + {2,[{dd,[],blocks([dd | S])}]}]) + ); +blocks(S) -> + ?LAZY( + frequency( + [{?LIMIT div 2,[]}, + {max(1,?LIMIT - length(S)), + ?LET(Lst,[block(S)|blocks(S)],lists:flatten(Lst))}, + {max(1,?LIMIT - length(S)), + ?LET(Lst,[inlines()|blocks(S)],lists:flatten(Lst))} + ] + )). + +inlines() -> + inlines([]). +inlines(S) -> + ?LAZY( + frequency( + [{?LIMIT,[]}, + {max(1,?LIMIT - length(S)),[inline(S)|inlines(S)]} + ] + )). + +block(S) -> + frequency( + fmax('div',3,S,{'div',oneof([[],[{class,<<"Warning">>}]]), blocks(['div'|S])}) ++ + fmax(p,1,S,{p,[],blocks([p|S])}) ++ + fmax(l,3,S,{ul,[],blocks([l|S])}) ++ + fmax(l,3,S,{ol,[],blocks([l|S])}) ++ + fmax(dl,3,S,{dl,[],blocks([dl|S])}) ++ + fmax(['div',l,dl],0,S,{h1,[],inlines(['div'|S])}) ++ + fmax(['div',l,dl],0,S,{h2,[],inlines(['div'|S])}) ++ + fmax(['div',l,dl],1,S,{h3,[],inlines(['div'|S])}) ++ + [{5,{br,[],[]}}] + ). + +inline(S) -> + frequency( + fmax(i,1,S,{i,[],?LAZY(inlines([i|S]))}) ++ + fmax(code,1,S,{code,[],?LAZY(inlines([code|S]))}) ++ + fmax(a,1,S,{a,[],?LAZY(inlines([a|S]))}) ++ + fmax(em,1,S,{em,[],?LAZY(inlines([em|S]))}) ++ + [{10,characters()}]). + +characters() -> + ?LET(Str,list(frequency([{10,printable_character()},{1,char()}])), + unicode:characters_to_binary(Str)). + +printable_character() -> + oneof([integer($\040,$\176), + integer(16#A0, 16#D800-1), + integer(16#DFFF+1,16#FFFE-1), + integer(16#FFFF+1,16#10FFFF), + $\n,$\r,$\t,$\v,$\b,$\f,$\e]). + +fmax(What,Depth,S,E) when not is_list(What) -> + fmax([What],Depth,S,E); +fmax(What,Depth,S,E) -> + Cnt = + lists:foldl( + fun(E,Cnt) -> + case lists:member(E,What) of + true -> + Cnt+1; + false -> + Cnt + end + end, 0, S), + if Depth-Cnt =< 0 -> + []; + true -> + [{10 - (Depth-Cnt),E}] + end. diff --git a/lib/stdlib/test/re_SUITE_data/testoutput2 b/lib/stdlib/test/re_SUITE_data/testoutput2 index 4ccda27201..f5d32d6ad0 100644 --- a/lib/stdlib/test/re_SUITE_data/testoutput2 +++ b/lib/stdlib/test/re_SUITE_data/testoutput2 @@ -5614,9 +5614,8 @@ No match 123456\P No match -//KF>testsavedregex +//S-KF>testsavedregex Compiled pattern written to testsavedregex -Study data written to testsavedregex /abc/IS>testsavedregex Capturing subpattern count = 0 diff --git a/lib/stdlib/test/shell_docs_SUITE.erl b/lib/stdlib/test/shell_docs_SUITE.erl index d8241f7530..11282db3e7 100644 --- a/lib/stdlib/test/shell_docs_SUITE.erl +++ b/lib/stdlib/test/shell_docs_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2019. All Rights Reserved. +%% Copyright Ericsson AB 2020. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -18,35 +18,39 @@ %% %CopyrightEnd% %% -module(shell_docs_SUITE). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2]). --export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2]). --export([render/1, links/1]). +-export([render/1, links/1, normalize/1, render_prop/1]). --include_lib("kernel/include/eep48.hrl"). - -init_per_testcase(_Case, Config) -> - Config. +-export([render_all/1]). -end_per_testcase(_Case, _Config) -> - ok. +-include_lib("kernel/include/eep48.hrl"). +-include_lib("stdlib/include/assert.hrl"). suite() -> [{timetrap,{minutes,10}}]. all() -> - [render, links]. + [render, links, normalize, {group, prop}]. groups() -> - []. + [{prop,[],[render_prop]}]. +%% Include a spec here in order to test that specs of undocumented functions +%% is rendered correctly. +-spec init_per_suite(Config1) -> Config2 when + Config1 :: list({atom(),term()}), + Config2 :: list({atom(),term()}). init_per_suite(Config) -> + {ok, ?MODULE} = c:c(?MODULE,[debug_info]), Config. end_per_suite(_Config) -> ok. +init_per_group(prop, Config) -> + ct_property_test:init_per_suite(Config); init_per_group(_GroupName, Config) -> Config. @@ -59,6 +63,7 @@ render(_Config) -> try shell_docs:render(Mod, D), shell_docs:render_type(Mod, D), + shell_docs:render_callback(Mod, D), [try shell_docs:render(Mod, F, A, D) catch _E:R:ST -> @@ -72,12 +77,25 @@ render(_Config) -> io:format("Failed to render type ~p:~p/~p~n~p:~p~n~p~n", [Mod,T,A,R,ST,shell_docs:get_type_doc(Mod,T,A)]), erlang:raise(error,R,ST) - end || {{type,T,A},_,_,_,_} <- Docs] + end || {{type,T,A},_,_,_,_} <- Docs], + [try + shell_docs:render_callback(Mod, T, A, D) + catch _E:R:ST -> + io:format("Failed to render callback ~p:~p/~p~n~p:~p~n~p~n", + [Mod,T,A,R,ST,shell_docs:get_callback_doc(Mod,T,A)]), + erlang:raise(error,R,ST) + end || {{callback,T,A},_,_,_,_} <- Docs] catch throw:R:ST -> io:format("Failed to render ~p~n~p:~p~n",[Mod,R,ST]), exit(R) end - end). + end), + ok. + +render_prop(Config) -> +% dbg:tracer(),dbg:p(all,c),dbg:tpl(shell_docs_prop,[]), + ct_property_test:quickcheck( + shell_docs_prop:prop_render(),Config). links(_Config) -> docsmap( @@ -132,6 +150,17 @@ check_links(Mod, [C|T]) when is_binary(C) -> check_links(_, []) -> ok. +normalize(_Config) -> + ?assertMatch( + [{p,[],[{em,[],[<<"a ">>,{code,[],[<<"b ">>]},<<"c">>]}]}], + shell_docs:normalize([{p,[],[{em,[],[<<" a ">>,{code,[],[<<" b ">>]},<<" c">>]}]}]) + ), + ?assertMatch( + [{'div',[],[<<"!">>]}], + shell_docs:normalize([{'div',[],[{code,[],[<<" ">>,{i,[],[<<" ">>]}]},<<" !">>]}]) + ), + ok. + %% Special binary_to_atom that deals with <<"'and'">> b2a(Bin) -> case erl_scan:string(binary_to_list(Bin)) of @@ -139,21 +168,61 @@ b2a(Bin) -> {ok,[{A,_}],_} -> A end. +%% Testing functions +render_all(Dir) -> + file:make_dir(Dir), + docsmap( + fun(Mod, #docs_v1{ docs = Docs } = D) -> + SMod = atom_to_list(Mod), + file:write_file(filename:join(Dir,SMod ++ ".txt"), + unicode:characters_to_binary(shell_docs:render(Mod, D))), + file:write_file(filename:join(Dir,SMod ++ "_type.txt"), + unicode:characters_to_binary(shell_docs:render_type(Mod, D))), + file:write_file(filename:join(Dir,SMod ++ "_cb.txt"), + unicode:characters_to_binary(shell_docs:render_callback(Mod, D))), + lists:foreach( + fun({{function,Name,Arity},_Anno,_Sig,_Doc,_Meta}) -> + FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_func.txt", + ok = file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_", + [global,{return,list}])), + unicode:characters_to_binary(shell_docs:render(Mod, Name, Arity, D))); + ({{type,Name,Arity},_Anno,_Sig,_Doc,_Meta}) -> + FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_type.txt", + ok = file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_", + [global,{return,list}])), + unicode:characters_to_binary(shell_docs:render_type(Mod, Name, Arity, D))); + ({{callback,Name,Arity},_Anno,_Sig,_Doc,_Meta}) -> + FName = SMod ++ "_"++atom_to_list(Name)++"_"++integer_to_list(Arity)++"_cb.txt", + file:write_file(filename:join(Dir,re:replace(FName,"[/:]","_", + [global,{return,list}])), + unicode:characters_to_binary(shell_docs:render_callback(Mod, Name, Arity, D))) + end, Docs) + end). + docsmap(Fun) -> - lists:map(fun F({Mod,_,_}) -> - F(Mod); - F(Mod) when is_list(Mod) -> - F(list_to_atom(Mod)); - F(Mod) -> - case code:get_doc(Mod) of - {error, missing} -> - ok; - {error, cover_compiled} -> - ok; - {error, eacces} -> - %% This can happen in BSD's for some reason... - ok; - {ok, Docs} -> - Fun(Mod, Docs) + lists:map( + fun F({Mod,_,_}) -> + F(Mod); + F(Mod) when is_list(Mod) -> + F(list_to_atom(Mod)); + F(Mod) -> + case code:get_doc(Mod) of + {error, missing} -> + ok; + {error, cover_compiled} -> + ok; + {error, E} when E =:= eperm; E =:= eacces -> + %% This can happen in BSD's for some reason... + ok; + {error, eisdir} -> + %% Uhm? + ok; + {ok, Docs} -> + try + Fun(Mod, Docs) + catch E:R:ST -> + io:format("Failed to render ~p~n~p:~p:~p~n",[Mod,E,R,ST]), + erlang:raise(E,R,ST) end - end, code:all_available()). + end + end, code:all_available()). diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index 02eee400bf..3aa3690d12 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.12.1 +STDLIB_VSN = 3.13 diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml index 9963ac41ae..52f085bf0c 100644 --- a/lib/syntax_tools/doc/src/notes.xml +++ b/lib/syntax_tools/doc/src/notes.xml @@ -32,6 +32,28 @@ <p>This document describes the changes made to the Syntax_Tools application.</p> +<section><title>Syntax_Tools 2.3</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> Remove incomplete support for <c>cond</c> + expressions. </p> + <p> + Own Id: OTP-15925 Aux Id: PR-2304 </p> + </item> + <item> + <p> + Improved indentation for code generated with + <c>erl_prettypr</c> and <c>tidier</c>.</p> + <p> + Own Id: OTP-16386 Aux Id: PR-2451 </p> + </item> + </list> + </section> + +</section> + <section><title>Syntax_Tools 2.2.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk index 9e6967d45d..87167529c3 100644 --- a/lib/syntax_tools/vsn.mk +++ b/lib/syntax_tools/vsn.mk @@ -1 +1 @@ -SYNTAX_TOOLS_VSN = 2.2.1 +SYNTAX_TOOLS_VSN = 2.3 diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index 728343a86f..f4d2f0772f 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -31,6 +31,33 @@ </header> <p>This document describes the changes made to the Tools application.</p> +<section><title>Tools 3.4</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Updates for new <c>erlang:term_to_iovec()</c> BIF.</p> + <p> + Own Id: OTP-16128 Aux Id: OTP-15618 </p> + </item> + <item> + <p>Improved the presentation of allocations and carriers + in the <c>instrument</c> module.</p> + <p> + Own Id: OTP-16327</p> + </item> + <item> + <p> + Minor updates due to the new spawn improvements made.</p> + <p> + Own Id: OTP-16368 Aux Id: OTP-15251 </p> + </item> + </list> + </section> + +</section> + <section><title>Tools 3.3.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src index beb5b98e15..f0c7ec1ead 100644 --- a/lib/tools/src/tools.app.src +++ b/lib/tools/src/tools.app.src @@ -43,6 +43,6 @@ ] }, {runtime_dependencies, ["stdlib-3.4","runtime_tools-1.8.14", - "kernel-5.4","erts-9.1","compiler-5.0", "erts-@OTP-16327@"]} + "kernel-5.4","erts-9.1","compiler-5.0", "erts-11.0"]} ] }. diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl index 898d20f560..f5d68c20f0 100644 --- a/lib/tools/test/fprof_SUITE.erl +++ b/lib/tools/test/fprof_SUITE.erl @@ -501,7 +501,11 @@ cpu_create_file_slow(Config) when is_list(Config) -> %% {ok, [T, P]} = parse(AnalysisFile), io:format("~p~n~n~p~n", [P, ets:tab2list(T)]), - ok = verify(T, P), + try + ok = verify(T, P) + catch throw:{negative,_Weird} -> + io:format("Aborted as counter is negative because of bad cpu_time") + end, Proc = pid_to_list(self()), case P of [{analysis_options, _}, @@ -520,13 +524,7 @@ cpu_create_file_slow(Config) when is_list(Config) -> io:format("cpu_ts:~w, fprof:~w~n", [Acc, Acc1]), {comment, io_lib:format("~p% cpu utilization", [100*divide(Acc,Acc1)])}; {'EXIT', not_supported} -> - case {os:type(), os:version()} of - {{unix, sunos}, {Major, Minor, _}} - when Major >= 5, Minor >= 7 -> - ct:fail(Result); - _ -> - {skipped, "not_supported"} - end; + {skipped, "not_supported"}; _ -> ct:fail(Result) end, @@ -633,6 +631,8 @@ verify(Tab, [{analysis_options, _}, {Proc, Cnt_P, Acc_P, Own_P} = Clocks when Acc_P >= Own_P -> Clocks; + {_, _, Acc_p, _} = Weird when Acc_p < 0 -> + throw({negative, Weird}); Weird -> throw({error, [?MODULE, ?LINE, Weird]}) end diff --git a/lib/tools/test/prof_bench_SUITE.erl b/lib/tools/test/prof_bench_SUITE.erl index e8c4642d37..c0b9a7913c 100644 --- a/lib/tools/test/prof_bench_SUITE.erl +++ b/lib/tools/test/prof_bench_SUITE.erl @@ -32,7 +32,7 @@ suite() -> - [{timetrap,{minutes,10}}]. + [{timetrap,{minutes,15}}]. all() -> [overhead]. diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index 66f2c03149..b9f4811392 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 3.3.1 +TOOLS_VSN = 3.4 diff --git a/lib/wx/c_src/egl_impl.cpp b/lib/wx/c_src/egl_impl.cpp index 61e05ee6f1..06c1de9cb6 100644 --- a/lib/wx/c_src/egl_impl.cpp +++ b/lib/wx/c_src/egl_impl.cpp @@ -48,6 +48,7 @@ int egl_initiated = 0; #define OPENGLU_LIB L"glu32.dll" typedef HMODULE DL_LIB_P; typedef WCHAR DL_CHAR; +#define DL_STR_FMT "%S" void * dlsym(HMODULE Lib, const char *func) { void * funcp; if((funcp = (void *) GetProcAddress(Lib, func))) @@ -67,6 +68,7 @@ void dlclose(HMODULE Lib) { #else typedef void * DL_LIB_P; typedef char DL_CHAR; +# define DL_STR_FMT "%s" # ifdef _MACOSX # define OPENGL_LIB "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib" # define OPENGLU_LIB "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib" @@ -125,7 +127,7 @@ int load_gl_functions() { // dlclose(LIBhandle); // fprintf(stderr, "OPENGL library is loaded\r\n"); } else { - fprintf(stderr, "Could NOT load OpenGL library: %s\r\n", DLName); + fprintf(stderr, "Could NOT load OpenGL library: " DL_STR_FMT "\r\n", DLName); }; DLName = (DL_CHAR *) OPENGLU_LIB; @@ -154,7 +156,7 @@ int load_gl_functions() { // dlclose(LIBhandle); // fprintf(stderr, "GLU library is loaded\r\n"); } else { - fprintf(stderr, "Could NOT load OpenGL GLU library: %s\r\n", DLName); + fprintf(stderr, "Could NOT load OpenGL GLU library: " DL_STR_FMT "\r\n", DLName); }; return 1; diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml index 7dcfbb1588..b9012054a8 100644 --- a/lib/wx/doc/src/notes.xml +++ b/lib/wx/doc/src/notes.xml @@ -32,6 +32,33 @@ <p>This document describes the changes made to the wxErlang application.</p> +<section><title>Wx 1.9.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix various compiler warnings on 64-bit Windows.</p> + <p> + Own Id: OTP-15800</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>Refactored the internal handling of deprecated and + removed functions.</p> + <p> + Own Id: OTP-16469</p> + </item> + </list> + </section> + +</section> + <section><title>Wx 1.9</title> <section><title>Improvements and New Features</title> diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk index b498d21f3f..552e09ee2a 100644 --- a/lib/wx/vsn.mk +++ b/lib/wx/vsn.mk @@ -1 +1 @@ -WX_VSN = 1.9 +WX_VSN = 1.9.1 diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml index 997af9d037..d8b2852097 100644 --- a/lib/xmerl/doc/src/notes.xml +++ b/lib/xmerl/doc/src/notes.xml @@ -32,6 +32,22 @@ <p>This document describes the changes made to the Xmerl application.</p> +<section><title>Xmerl 1.3.25</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug that the function name didn't get + normalized in some case which left white spaces in links. + </p> + <p> + Own Id: OTP-16617</p> + </item> + </list> + </section> + +</section> + <section><title>Xmerl 1.3.24</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk index 79be4c8a95..8711ed946f 100644 --- a/lib/xmerl/vsn.mk +++ b/lib/xmerl/vsn.mk @@ -1 +1 @@ -XMERL_VSN = 1.3.24 +XMERL_VSN = 1.3.25 |