From 6f5e6b0ef376f59d14d798dd17e26ed115ca4e7c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 4 Sep 2023 12:38:52 +0200 Subject: [PATCH 1/4] [erts|esock] Add dgram connect for Windows Add support for connecting DGRAM sockets on Windows. OTP-18762 --- erts/emulator/nifs/win32/win_socket_asyncio.c | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/erts/emulator/nifs/win32/win_socket_asyncio.c b/erts/emulator/nifs/win32/win_socket_asyncio.c index 78e93262226c..bb6ad47960b1 100644 --- a/erts/emulator/nifs/win32/win_socket_asyncio.c +++ b/erts/emulator/nifs/win32/win_socket_asyncio.c @@ -2051,7 +2051,50 @@ ERL_NIF_TERM esaio_connect_dgram(ErlNifEnv* env, ESockAddress* addrP, SOCKLEN_T addrLen) { - return enif_make_badarg(env); + int save_errno; + ErlNifPid self; + + ESOCK_ASSERT( enif_self(env, &self) != NULL ); + + if (! IS_OPEN(descP->writeState)) + return esock_make_error_closed(env); + + if (descP->connectorP != NULL) { + /* Connect in progress */ + + return esock_make_error(env, esock_atom_already); + } + + /* No connect in progress */ + + if (addrP == NULL) { + /* Connect without an address is not allowed + */ + return esock_raise_invalid(env, esock_atom_state); + } + + /* Initial connect call, with address */ + + if (sock_connect(descP->sock, (struct sockaddr*) addrP, addrLen) == 0) { + /* Success! */ + SSDBG( descP, ("WIN-ESAIO", + "essio_connect_dgram {%d} -> connected\r\n", + descP->sock) ); + + descP->writeState |= ESOCK_STATE_CONNECTED; + + return esock_atom_ok; + } + + /* Connect returned error */ + save_errno = sock_errno(); + + SSDBG( descP, + ("WIN-ESAIO", "esaio_connect_dgram {%d} -> error: %d\r\n", + descP->sock, save_errno) ); + + return esock_make_error_errno(env, save_errno); + } From 1f29ba49939ab5255c9a40211d84c7d86a7e20e1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 11 Sep 2023 14:56:19 +0200 Subject: [PATCH 2/4] [kernel|esock] Misc (commented) debug Also fixed 'looking for up non-loopback addresses'. OTP-18762 --- lib/kernel/src/gen_udp.erl | 9 ++++- lib/kernel/src/gen_udp_socket.erl | 9 ++++- lib/kernel/src/inet.erl | 55 ++++++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/lib/kernel/src/gen_udp.erl b/lib/kernel/src/gen_udp.erl index 9debede39b1b..e74ec05b238f 100644 --- a/lib/kernel/src/gen_udp.erl +++ b/lib/kernel/src/gen_udp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2022. All Rights Reserved. +%% Copyright Ericsson AB 1997-2023. 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. @@ -29,6 +29,8 @@ -define(module_socket(Handler, Handle), {'$inet', (Handler), (Handle)}). +%% -define(DBG(T), erlang:display({{self(), ?MODULE, ?LINE, ?FUNCTION_NAME}, T})). + -type option() :: {active, true | false | once | -32768..32767} | {add_membership, membership()} | @@ -377,15 +379,20 @@ connect(?module_socket(GenUdpMod, _) = S, Address, Port) GenUdpMod:?FUNCTION_NAME(S, Address, Port); connect(S, Address, Port) when is_port(S) -> + %% ?DBG([{address, Address}, {port, Port}]), case inet_db:lookup_socket(S) of {ok, Mod} -> + %% ?DBG([{mod, Mod}]), case Mod:getaddr(Address) of {ok, IP} -> + %% ?DBG([{ip, IP}]), Mod:connect(S, IP, Port); Error -> + %% ?DBG(['getaddr', {error, Error}]), Error end; Error -> + %% ?DBG(['lookup', {error, Error}]), Error end. diff --git a/lib/kernel/src/gen_udp_socket.erl b/lib/kernel/src/gen_udp_socket.erl index 32224989d452..29a0a76ec40d 100644 --- a/lib/kernel/src/gen_udp_socket.erl +++ b/lib/kernel/src/gen_udp_socket.erl @@ -124,17 +124,22 @@ close_server(Server) -> %% -- connect ---------------------------------------------------------------- connect(?MODULE_socket(_Server, Socket), Address, Port) -> + %% ?DBG([{address, Address}, {port, Port}]), {Mod, _} = inet:udp_module([], Address), Domain = domain(Mod), + %% ?DBG([{mod, Mod}, {domain, Domain}]), try begin Dest = case Mod:getaddr(Address) of {ok, IP} when (Domain =:= local) -> + %% ?DBG([{ip, IP}]), dest2sockaddr(IP); {ok, IP} -> + %% ?DBG([{ip, IP}]), dest2sockaddr({IP, Port}); {error, _Reason} = ERROR -> + %% ?DBG(['getaddr', {error, ERROR}]), throw(ERROR) end, case os:type() of @@ -149,6 +154,7 @@ connect(?MODULE_socket(_Server, Socket), Address, Port) -> socket:connect(Socket, Dest) end; _ -> + %% ?DBG(['try connect']), socket:connect(Socket, Dest) end end @@ -331,8 +337,9 @@ which_default_bind_address2(Domain) -> %% Pick first *non-loopback* interface that is 'up' UpNonLoopbackAddrs = [Addr || - #{flags := Flags} = Addr <- + #{flags := Flags, addr := #{addr := _A}} = Addr <- Addrs, + %% (element(1, A) =/= 169) andalso (not lists:member(loopback, Flags)) andalso lists:member(up, Flags)], %% ?DBG([{up_non_loopback_addrs, UpNonLoopbackAddrs}]), diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 587a87cbbebb..fa826d761f1f 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -642,8 +642,10 @@ gethostbyname(Name,Family,Timeout) -> _ = stop_timer(Timer), Res. -gethostbyname_tm(Name,Family,Timer) -> +gethostbyname_tm(Name, Family, Timer) -> + %% ?DBG([{name, Name}, {family, Family}, {timer, Timer}]), Opts0 = inet_db:res_option(lookup), + %% ?DBG([{opts0, Opts0}]), Opts = case (lists:member(native, Opts0) orelse lists:member(string, Opts0) orelse @@ -653,6 +655,7 @@ gethostbyname_tm(Name,Family,Timer) -> false -> [string|Opts0] end, + %% ?DBG([{opts, Opts}]), gethostbyname_tm(Name, Family, Timer, Opts). @@ -819,15 +822,22 @@ getaddr(Address, Family) -> {'ok', ip_address()} | {'error', posix()}. getaddr(Address, Family, Timeout) -> + %% ?DBG([{address, Address}, {family, Family}, {timeout, Timeout}]), Timer = start_timer(Timeout), - Res = getaddr_tm(Address, Family, Timer), - _ = stop_timer(Timer), + Res = getaddr_tm(Address, Family, Timer), + %% ?DBG([{res, Res}]), + _ = stop_timer(Timer), Res. getaddr_tm(Address, Family, Timer) -> + %% ?DBG([{address, Address}, {family, Family}, {timer, Timer}]), case getaddrs_tm(Address, Family, Timer) of - {ok, [IP|_]} -> {ok, IP}; - Error -> Error + {ok, [IP|_]} -> + %% ?DBG([{ip, IP}]), + {ok, IP}; + Error -> + %% ?DBG([{error, Error}]), + Error end. -spec getaddrs(Host, Family) -> @@ -1553,6 +1563,7 @@ getaddrs_tm({A,B,C,D,E,F,G,H} = IP, Fam, _) -> getaddrs_tm(Address, Family, Timer) when is_atom(Address) -> getaddrs_tm(atom_to_list(Address), Family, Timer); getaddrs_tm(Address, Family, Timer) -> + %% ?DBG([{address, Address}, {family, Family}, {timer, Timer}]), case inet_parse:visible_string(Address) of false -> {error,einval}; @@ -1560,8 +1571,12 @@ getaddrs_tm(Address, Family, Timer) -> %% Address is a host name or a valid IP address, %% either way check it with the resolver. case gethostbyname_tm(Address, Family, Timer) of - {ok,Ent} -> {ok,Ent#hostent.h_addr_list}; - Error -> Error + {ok, Ent} -> + %% ?DBG([{ent, Ent}]), + {ok, Ent#hostent.h_addr_list}; + Error -> + %% ?DBG([{error, Error}]), + Error end end. @@ -1569,32 +1584,47 @@ getaddrs_tm(Address, Family, Timer) -> %% gethostbyname with option search %% gethostbyname_tm(Name, Type, Timer, [string|_]=Opts) -> + %% ?DBG([string, {name, Name}, {type, Type}, {timer, Timer}]), Result = gethostbyname_string(Name, Type), gethostbyname_tm(Name, Type, Timer, Opts, Result); gethostbyname_tm(Name, Type, Timer, [dns|_]=Opts) -> + %% ?DBG([dns, {name, Name}, {type, Type}, {timer, Timer}]), Result = inet_res:gethostbyname_tm(Name, Type, Timer), + %% ?DBG([{result, Result}]), gethostbyname_tm(Name, Type, Timer, Opts, Result); gethostbyname_tm(Name, Type, Timer, [file|_]=Opts) -> + %% ?DBG([file, {name, Name}, {type, Type}, {timer, Timer}]), Result = inet_hosts:gethostbyname(Name, Type), + %% ?DBG([{result, Result}]), gethostbyname_tm(Name, Type, Timer, Opts, Result); gethostbyname_tm(Name, Type, Timer, [yp|_]=Opts) -> + %% ?DBG([yp, {name, Name}, {type, Type}, {timer, Timer}]), gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [nis|_]=Opts) -> + %% ?DBG([nis, {name, Name}, {type, Type}, {timer, Timer}]), gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [nisplus|_]=Opts) -> + %% ?DBG([niplus, {name, Name}, {type, Type}, {timer, Timer}]), gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [wins|_]=Opts) -> + %% ?DBG([wins, {name, Name}, {type, Type}, {timer, Timer}]), gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [native|_]=Opts) -> + %% ?DBG([native, {name, Name}, {type, Type}, {timer, Timer}]), gethostbyname_tm_native(Name, Type, Timer, Opts); gethostbyname_tm(Name, Type, Timer, [_|Opts]) -> + %% ?DBG([{name, Name}, {type, Type}, {timer, Timer}]), gethostbyname_tm(Name, Type, Timer, Opts); %% Make sure we always can look up our own hostname. gethostbyname_tm(Name, Type, Timer, []) -> + %% ?DBG([{name, Name}, {type, Type}, {timer, Timer}]), Result = gethostbyname_self(Name, Type), + %% ?DBG([{result, Result}]), gethostbyname_tm(Name, Type, Timer, [], Result). gethostbyname_tm(Name, Type, Timer, Opts, Result) -> + %% ?DBG([string, {name, Name}, {type, Type}, {timer, Timer}, + %% {opts, Opts}, {result, Result}]), case Result of {ok,_} -> Result; @@ -1607,19 +1637,24 @@ gethostbyname_tm(Name, Type, Timer, Opts, Result) -> end. gethostbyname_tm_native(Name, Type, Timer, Opts) -> + %% ?DBG([{name, Name}, {type, Type}, {timer, Timer}, {opts, Opts}]), %% Fixme: add (global) timeout to gethost_native Result = inet_gethost_native:gethostbyname(Name, Type), + %% ?DBG([{result, Result}]), gethostbyname_tm(Name, Type, Timer, Opts, Result). gethostbyname_self(Name, Type) when is_atom(Name) -> + %% ?DBG([{name, Name}, {type, Type}]), gethostbyname_self(atom_to_list(Name), Type); gethostbyname_self(Name, Type) when is_list(Name), Type =:= inet; is_list(Name), Type =:= inet6 -> - N = inet_db:tolower(Name), + %% ?DBG([{name, Name}, {type, Type}]), + N = inet_db:tolower(Name), Self = inet_db:gethostname(), + %% ?DBG([{n, N}, {self, Self}]), %% %% This is the final fallback that pretends /etc/hosts has got %% a line for the hostname on the loopback address. @@ -1629,14 +1664,17 @@ gethostbyname_self(Name, Type) %% case inet_db:tolower(Self) of N -> + %% ?DBG([{n, N}]), {ok, make_hostent( Self, [translate_ip(loopback, Type)], [], Type)}; _ -> case inet_db:res_option(domain) of "" -> + %% ?DBG(['res option empty domain']), {error,nxdomain}; Domain -> + %% ?DBG([{domain, Domain}]), FQDN = lists:append([Self,".",Domain]), case inet_db:tolower(FQDN) of N -> @@ -1645,6 +1683,7 @@ gethostbyname_self(Name, Type) FQDN, [translate_ip(loopback, Type)], [], Type)}; _ -> + %% ?DBG(['invalid domain', {fqdn, FQDN}]), {error,nxdomain} end end From 46b4027d38f0fdbcd36c26820ae3820fa821d53d Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Mon, 11 Sep 2023 14:57:28 +0200 Subject: [PATCH 3/4] [kernel|gen-udp|test] Add and tweaked connect test case The 'connect' test case was added and tweaked for Windows. OTP-18762 --- lib/kernel/test/gen_udp_SUITE.erl | 60 ++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index 8ba6ec8e2ca6..7b010c2ecbf5 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -22,6 +22,22 @@ %% Test the behavior of gen_udp. Testing udp is really a very unfunny task, %% because udp is not deterministic. %% +%% (cd /mnt/c/$LOCAL_TESTS/26/kernel_test/ && $ERL_TOP/bin/win32/erl.exe -sname kernel-26-tester -pa c:$LOCAL_TESTS/26/test_server) +%% application:set_env(kernel, test_inet_backends, true). +%% S = fun() -> ts:run(kernel, gen_udp_SUITE, [batch]) end. +%% S = fun(SUITE) -> ts:run(kernel, SUITE, [batch]) end. +%% S = fun() -> ct:run_test([{suite, gen_udp_SUITE}]) end. +%% S = fun(SUITE) -> ct:run_test([{suite, SUITE}]) end. +%% G = fun(GROUP) -> ts:run(kernel, gen_udp_SUITE, {group, GROUP}, [batch]) end. +%% G = fun(SUITE, GROUP) -> ts:run(kernel, SUITE, {group, GROUP}, [batch]) end. +%% G = fun(GROUP) -> ct:run_test([{suite, gen_udp_SUITE}, {group, GROUP}]) end. +%% G = fun(SUITE, GROUP) -> ct:run_test([{suite, SUITE}, {group, GROUP}]) end. +%% T = fun(TC) -> ts:run(kernel, gen_udp_SUITE, TC, [batch]) end. +%% T = fun(TC) -> ct:run_test([{suite, gen_udp_SUITE}, {testcase, TC}]) end. +%% T = fun(S, TC) -> ct:run_test([{suite, S}, {testcase, TC}]) end. +%% T = fun(S, G, TC) -> ct:run_test([{suite, S}, {group, G}, {testcase, TC}]) end. +%% + -module(gen_udp_SUITE). -include_lib("common_test/include/ct.hrl"). @@ -1885,13 +1901,11 @@ recv_close(Config) when is_list(Config) -> %% Test that connect/3 has effect. connect(Config) when is_list(Config) -> ?TC_TRY(?FUNCTION_NAME, - %% *Currently* not implemented - fun() -> is_not_windows() end, fun() -> do_connect(Config) end). do_connect(Config) when is_list(Config) -> ?P("begin"), - Addr = {127,0,0,1}, + {ok, Addr} = ?LIB:which_local_addr(inet), ?P("try create first socket"), {ok, S1} = ?OPEN(Config, 0), {ok, P1} = inet:port(S1), @@ -1907,11 +1921,18 @@ do_connect(Config) when is_list(Config) -> ?P("sleep some"), ct:sleep({seconds, 5}), - ?P("try some doomed connect targets: ~p", [P1]), - {error, nxdomain} = gen_udp:connect(S2, "", ?CLOSED_PORT), - {error, nxdomain} = gen_udp:connect(S2, '', ?CLOSED_PORT), - {error, nxdomain} = gen_udp:connect(S2, ".", ?CLOSED_PORT), - {error, nxdomain} = gen_udp:connect(S2, '.', ?CLOSED_PORT), + case os:type() of + {win32, nt} -> + ok; + _ -> + ?P("try some doomed connect targets: ~p", [P1]), + {error, nxdomain} = gen_udp:connect(S2, "", ?CLOSED_PORT), + {error, nxdomain} = gen_udp:connect(S2, '', ?CLOSED_PORT), + {error, nxdomain} = gen_udp:connect(S2, ".", ?CLOSED_PORT), + {error, nxdomain} = gen_udp:connect(S2, '.', ?CLOSED_PORT), + ok + end, + ?P("try connect second socket to: ~p, ~p", [Addr, P1]), ok = gen_udp:connect(S2, Addr, P1), ?P("try send on second socket"), @@ -1933,30 +1954,37 @@ do_connect(Config) when is_list(Config) -> reconnect(Config) when is_list(Config) -> ?TC_TRY(?FUNCTION_NAME, - %% *Currently* not implemented + %% %% *Currently* not implemented fun() -> is_not_windows() end, fun () -> do_reconnect(Config) end). do_reconnect(Config) -> - LoopAddr = {127,0,0,1}, + {ok, Addr} = ?LIB:which_local_addr(inet), + %% LoopAddr = {127,0,0,1}, XtrnAddr = {8,8,8,8}, DestPort = 53, - {S, Port} = open_port_0(Config, []), + {S, Port} = open_port_0(Config, [{ip, Addr}]), ?P("Socket: ~w", [S]), %% Connect to a loopback destination - ok = gen_udp:connect(S, LoopAddr, DestPort), - {ok, {LoopAddr,DestPort}} = inet:peername(S), + %% ok = gen_udp:connect(S, LoopAddr, DestPort), + ok = gen_udp:connect(S, Addr, DestPort), + %% {ok, {LoopAddr,DestPort}} = inet:peername(S), + %% {ok, {LocalAddr,Port}} = inet:sockname(S), + {ok, {Addr,DestPort}} = inet:peername(S), {ok, {LocalAddr,Port}} = inet:sockname(S), - ?P("Socket addr: ~w", [LocalAddr]), + ?P("Socket (local) addr: ~w", [LocalAddr]), %% Reconnect to external destination ok = gen_udp:connect(S, XtrnAddr, DestPort), {ok, {XtrnAddr,DestPort}} = inet:peername(S), {ok, {RoutableAddr,Port}} = inet:sockname(S), + ?P("Socket (routable) addr: ~p, port: ~p", [RoutableAddr, Port]), %% We should have a non-loopback address here true = RoutableAddr =/= LocalAddr, %% Reconnect to loopback - ok = gen_udp:connect(S, LoopAddr, DestPort), - {ok, {LoopAddr,DestPort}} = inet:peername(S), + %% ok = gen_udp:connect(S, LoopAddr, DestPort), + ok = gen_udp:connect(S, Addr, DestPort), + %% {ok, {LoopAddr,DestPort}} = inet:peername(S), + {ok, {Addr,DestPort}} = inet:peername(S), {ok, {LocalAddr,Port}} = inet:sockname(S), ok = inet:close(S). From 4d217f6cb2e29fc7871f082391a42f4ea0752a6c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 15 Sep 2023 12:00:24 +0200 Subject: [PATCH 4/4] [kernel|esock|test] Add simple dgram connect test case OTP-18762 --- lib/kernel/test/socket_SUITE.erl | 330 ++++++++++++++++++++++++++++++- 1 file changed, 329 insertions(+), 1 deletion(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 6626e7af6027..15d52cdc91a4 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -143,6 +143,8 @@ api_b_sendmsg_iov_stream_inet/1, api_b_sendmsg_iov_stream_inet6/1, api_b_sendmsg_iov_stream_local/1, + api_b_dgram_connect_udp4/1, + api_b_dgram_connect_udp6/1, %% *** API sendfile *** api_sendfile_inet/1, @@ -1020,7 +1022,9 @@ api_basic_cases() -> api_b_sendmsg_iov_dgram_local, api_b_sendmsg_iov_stream_inet, api_b_sendmsg_iov_stream_inet6, - api_b_sendmsg_iov_stream_local + api_b_sendmsg_iov_stream_local, + api_b_dgram_connect_udp4, + api_b_dgram_connect_udp6 ]. api_sendfile_cases() -> @@ -4918,6 +4922,330 @@ api_b_sendmsg_iov_stream(Domain) -> end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically connect, send and receive using the "common" functions +%% (send and recv) on an IPv4 UDP (dgram) socket. +api_b_dgram_connect_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_ipv4() end, + fun() -> + InitState = #{domain => inet, + type => dgram, + proto => udp}, + ok = api_b_dgram_connect(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically connect, send and receive using the "common" functions +%% (send and recv) on an IPv6 UDP (dgram) socket. +api_b_dgram_connect_udp6(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(?FUNCTION_NAME, + fun() -> has_support_ipv6() end, + fun() -> + InitState = #{domain => inet6, + type => dgram, + proto => udp}, + ok = api_b_dgram_connect(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_dgram_connect(InitState) -> + PeerSeq = + [ + %% *** 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 => "open", + cmd => fun(#{domain := Domain, + type := Type, + proto := Protocol} = State) -> + case socket:open(Domain, Type, Protocol) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, eprotonosupport = Reason} + when (Reason =:= epfnosupport) orelse + (Reason =:= eprotonosupport) orelse + (Reason =:= esocktnosupport) -> + ?SEV_IPRINT("failed open: ~p => SKIP", + [Reason]), + {skip, Reason}; + {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, State#{sock_sa => LSA}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("failed binding: " + "~n ~p", [Reason]), + ERROR + end; + (#{sock := Sock, local_sa := LSA} = State) -> + case sock_bind(Sock, LSA#{port => 0}) of + ok -> + Port = sock_port(Sock), + {ok, State#{sock_sa => LSA#{port => Port}}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock_sa := SSA}) -> + ?SEV_ANNOUNCE_READY(Tester, init, SSA), + ok + end}, + + %% The actual test + #{desc => "await continue (peer sa)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of + {ok, PeerSA} -> + ?SEV_IPRINT("Peer SA:" + "~n ~p", [PeerSA]), + {ok, State#{peer_sa => PeerSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "connect", + cmd => fun(#{sock := Sock, peer_sa := PSA}) -> + ?SEV_IPRINT("try connect"), + socket:connect(Sock, PSA) + end}, + + #{desc => "announce ready (connected)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connected), + ok + end}, + + %% The actual test + #{desc => "await continue (send and recv)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, + tester, send_and_recv) of + {ok, Role} -> + ?SEV_IPRINT("role: ~p", [Role]), + {ok, State#{role => Role}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "send (client) or recv (server) request", + cmd => fun(#{sock := Sock, role := client}) -> + ?SEV_IPRINT("[client] try send request"), + socket:send(Sock, ?BASIC_REQ); + (#{sock := Sock, role := server}) -> + ?SEV_IPRINT("[server] try recv request"), + case socket:recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "recv (client) send (server) reply", + cmd => fun(#{sock := Sock, role := client}) -> + ?SEV_IPRINT("[client] try recv reply"), + case socket:recv(Sock) of + {ok, ?BASIC_REP} -> + ok; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, role := server}) -> + ?SEV_IPRINT("[server] try send reply"), + socket:send(Sock, ?BASIC_REP) + end}, + + #{desc => "announce ready (send_and_recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_and_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", + 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 peer 1", + cmd => fun(#{peer_1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor peer 2", + cmd => fun(#{peer_2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + #{desc => "order peer 1 start", + cmd => fun(#{peer_1 := Pid}) -> + ?SEV_ANNOUNCE_START(Pid) + end}, + #{desc => "order peer 2 start", + cmd => fun(#{peer_2 := Pid}) -> + ?SEV_ANNOUNCE_START(Pid) + end}, + + #{desc => "await peer 1 ready (init)", + cmd => fun(#{peer_1 := Pid} = State) -> + {ok, PeerSA} = ?SEV_AWAIT_READY(Pid, peer1, init), + {ok, State#{peer_1_sa => PeerSA}} + end}, + #{desc => "await peer 2 ready (init)", + cmd => fun(#{peer_2 := Pid} = State) -> + {ok, PeerSA} = ?SEV_AWAIT_READY(Pid, peer2, init), + {ok, State#{peer_2_sa => PeerSA}} + end}, + + + #{desc => "order peer 1 to continue (with connect)", + cmd => fun(#{peer_1 := Pid, peer_2_sa := PeerSA} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect, PeerSA), + ok + end}, + #{desc => "order peer 2 to continue (with connect)", + cmd => fun(#{peer_2 := Pid, peer_1_sa := PeerSA} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect, PeerSA), + ok + end}, + + + #{desc => "await peer 1 ready (connected)", + cmd => fun(#{peer_1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, peer1, connected) + end}, + #{desc => "await peer 2 ready (connected)", + cmd => fun(#{peer_2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, peer2, connected) + end}, + + + + %% We should pick one to issue the request + %% (and the other is then to *await* the request + %% and respond). + + + + #{desc => "order peer 1 to continue (with send_and_recv)", + cmd => fun(#{peer_1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_and_recv, client), + ok + end}, + #{desc => "order peer 2 to continue (with send_and_recv)", + cmd => fun(#{peer_2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_and_recv, server), + ok + end}, + + + #{desc => "await peer 1 ready (send_and_recv)", + cmd => fun(#{peer_1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, peer1, send_and_recv) + end}, + #{desc => "await peer 2 ready (send_and_recv)", + cmd => fun(#{peer_2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, peer2, send_and_recv) + end}, + + + ?SEV_SLEEP(?SECS(1)), + + + %% *** Termination *** + #{desc => "order peer 1 to terminate", + cmd => fun(#{peer_1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await peer 1 termination", + cmd => fun(#{peer_1 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(peer_1, State), + {ok, State1} + end}, + #{desc => "order peer 2 to terminate", + cmd => fun(#{peer_2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await peer 2 termination", + cmd => fun(#{peer_2 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(peer_2, State), + {ok, State1} + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + Peer1 = ?SEV_START("peer 1", PeerSeq, InitState), + Peer2 = ?SEV_START("peer 2", PeerSeq, InitState), + Tester = ?SEV_START("tester", TesterSeq, #{peer_1 => Peer1#ev.pid, + peer_2 => Peer2#ev.pid}), + ok = ?SEV_AWAIT_FINISH([Peer1, Peer2, Tester]). + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %%