From b4d61414565e6c6aa34249bf5d6eb3d5e5952b76 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Fri, 19 Oct 2018 12:13:26 +0200 Subject: [socket-nif|test] Moved socket tests from kernel to erts/emulator OTP-14831 --- erts/emulator/test/Makefile | 11 + erts/emulator/test/socket_SUITE.erl | 4186 ++++++++++++++++++++++++++++++++++ erts/emulator/test/socket_client.erl | 538 +++++ erts/emulator/test/socket_lib.erl | 133 ++ erts/emulator/test/socket_server.erl | 954 ++++++++ lib/kernel/test/Makefile | 12 +- lib/kernel/test/socket_SUITE.erl | 4186 ---------------------------------- lib/kernel/test/socket_client.erl | 538 ----- lib/kernel/test/socket_lib.erl | 133 -- lib/kernel/test/socket_server.erl | 954 -------- 10 files changed, 5823 insertions(+), 5822 deletions(-) create mode 100644 erts/emulator/test/socket_SUITE.erl create mode 100644 erts/emulator/test/socket_client.erl create mode 100644 erts/emulator/test/socket_lib.erl create mode 100644 erts/emulator/test/socket_server.erl delete mode 100644 lib/kernel/test/socket_SUITE.erl delete mode 100644 lib/kernel/test/socket_client.erl delete mode 100644 lib/kernel/test/socket_lib.erl delete mode 100644 lib/kernel/test/socket_server.erl diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index bf00de2204..4f47ec47ef 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -28,6 +28,12 @@ EBIN = . # Target Specs # ---------------------------------------------------- +SOCKET_MODULES = \ + socket_lib \ + socket_server \ + socket_client \ + socket_SUITE + MODULES= \ a_SUITE \ after_SUITE \ @@ -103,6 +109,7 @@ MODULES= \ sensitive_SUITE \ signal_SUITE \ smoke_test_SUITE \ + $(SOCKET_MODULES) \ statistics_SUITE \ system_info_SUITE \ system_profile_SUITE \ @@ -151,6 +158,7 @@ NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +SOCKET_TARGET = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) EMAKEFILE=Emakefile @@ -197,6 +205,9 @@ clean: docs: +targets: $(TARGET_FILES) +socket_targets: $(SOCKET_TARGETS) + # ---------------------------------------------------- # Special targets # ---------------------------------------------------- diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl new file mode 100644 index 0000000000..022e83a944 --- /dev/null +++ b/erts/emulator/test/socket_SUITE.erl @@ -0,0 +1,4186 @@ +%% +%% %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% +%% + +-module(socket_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +%% Suite exports +-export([suite/0, all/0, groups/0]). +-export([init_per_suite/1, end_per_suite/1, + init_per_testcase/2, end_per_testcase/2]). + +%% Test cases +-export([ + %% API Basic + api_b_open_and_close_udp4/1, + api_b_open_and_close_tcp4/1, + api_b_sendto_and_recvfrom_udp4/1, + api_b_sendmsg_and_recvmsg_udp4/1, + api_b_send_and_recv_tcp4/1, + api_b_sendmsg_and_recvmsg_tcp4/1, + + %% API Options + api_opt_simple_otp_options/1, + api_opt_simple_otp_controlling_process/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 Closure + sc_cpe_socket_cleanup_tcp4/1, + sc_cpe_socket_cleanup_tcp6/1, + sc_cpe_socket_cleanup_udp4/1, + sc_cpe_socket_cleanup_udp6/1, + sc_lc_recv_response_tcp4/1, + sc_lc_recv_response_tcp6/1, + sc_lc_recvmsg_response_tcp4/1, + sc_lc_recvmsg_response_tcp6/1, + sc_lc_acceptor_response_tcp4/1, + sc_lc_acceptor_response_tcp6/1, + sc_rc_recv_response_tcp4/1, + sc_rc_recv_response_tcp6/1, + sc_rc_recvmsg_response_tcp4/1, + sc_rc_recvmsg_response_tcp6/1 + + %% Tickets + ]). + +%% Internal exports +%% -export([]). + + +-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(BASIC_REQ, <<"hejsan">>). +-define(BASIC_REP, <<"hoppsan">>). + +-define(FAIL(R), exit(R)). + +-define(SLEEP(T), receive after T -> ok end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + [ + {group, api}, + {group, socket_closure} + %% {group, tickets} + ]. + +groups() -> + [{api, [], api_cases()}, + {api_basic, [], api_basic_cases()}, + {api_options, [], api_options_cases()}, + {api_op_with_timeout, [], api_op_with_timeout_cases()}, + {socket_closure, [], socket_closure_cases()}, + {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, + {sc_local_close, [], sc_lc_cases()}, + {sc_remote_close, [], sc_rc_cases()} + %% {tickets, [], ticket_cases()} + ]. + +api_cases() -> + [ + {group, api_basic}, + {group, api_options}, + {group, api_op_with_timeout} + ]. + +api_basic_cases() -> + [ + api_b_open_and_close_udp4, + api_b_open_and_close_tcp4, + api_b_sendto_and_recvfrom_udp4, + api_b_sendmsg_and_recvmsg_udp4, + api_b_send_and_recv_tcp4, + api_b_sendmsg_and_recvmsg_tcp4 + ]. + +api_options_cases() -> + [ + api_opt_simple_otp_options, + api_opt_simple_otp_controlling_process + ]. + +api_op_with_timeout_cases() -> + [ + api_to_connect_tcp4, + api_to_connect_tcp6, + api_to_accept_tcp4, + api_to_accept_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 + ]. + +%% These cases tests what happens when the socket is closed, locally or +%% remotely. +socket_closure_cases() -> + [ + {group, sc_ctrl_proc_exit}, + {group, sc_local_close}, + {group, sc_remote_close} + ]. + +%% These cases are all about socket cleanup after the controlling process +%% exits *without* calling socket:close/1. +sc_cp_exit_cases() -> + [ + sc_cpe_socket_cleanup_tcp4, + sc_cpe_socket_cleanup_tcp6, + sc_cpe_socket_cleanup_udp4, + sc_cpe_socket_cleanup_udp6 + ]. + +%% 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_recvmsg_response_tcp4, + sc_lc_recvmsg_response_tcp6, + + sc_lc_acceptor_response_tcp4, + sc_lc_acceptor_response_tcp6 + ]. + +%% 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_recvmsg_response_tcp4, + sc_rc_recvmsg_response_tcp6 + ]. + + +%% ticket_cases() -> +%% []. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_suite(Config) -> + Config. + +end_per_suite(_) -> + ok. + +init_per_testcase(_TC, Config) -> + Config. + +end_per_testcase(_TC, Config) -> + Config. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API BASIC %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% 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) -> + 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 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) -> + tc_try(api_b_open_and_close_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + 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, _} = 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) -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}} + end}, + #{desc => "validate protocol", + cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> + {ok, State}; + ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> + {error, {unexpected_type, ExpProtocol, Protocol}}; + ({_, {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, normal}; + ({_, {error, _} = ERROR}) -> + ERROR + end}], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_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) -> + 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, + 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) -> + tc_try(api_b_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% CMsgHdr = #{level => ip, + %% type => tos, + %% data => reliability}, + %% CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + %% ctrl => CMsgHdrs, + iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + 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 := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "open src socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SASrc = sock_sockname(Sock), + {ok, State#{sock_src => Sock, sa_src => SASrc}} + end}, + #{desc => "bind src", + cmd => fun(#{sock_src := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname src socket", + cmd => fun(#{sock_src := Sock} = State) -> + SASrc = sock_sockname(Sock), + %% ei("src sockaddr: ~p", [SASrc]), + {ok, State#{sa_src => SASrc}} + end}, + #{desc => "open dst socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + {ok, State#{sock_dst => Sock}} + end}, + #{desc => "bind dst", + cmd => fun(#{sock_dst := Sock, lsa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "sockname dst socket", + cmd => fun(#{sock_dst := Sock} = State) -> + SADst = sock_sockname(Sock), + %% ei("dst sockaddr: ~p", [SADst]), + {ok, State#{sa_dst => SADst}} + end}, + #{desc => "send req (to dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> + ok = Send(Sock, ?BASIC_REQ, Dst) + end}, + #{desc => "recv req (from src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> + {ok, {Src, ?BASIC_REQ}} = Recv(Sock), + ok + end}, + #{desc => "send rep (to src)", + cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> + ok = Send(Sock, ?BASIC_REP, Src) + end}, + #{desc => "recv rep (from dst)", + cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> + {ok, {Dst, ?BASIC_REP}} = Recv(Sock), + ok + end}, + #{desc => "close src socket", + cmd => fun(#{sock_src := Sock}) -> + ok = socket:close(Sock) + end}, + #{desc => "close dst socket", + cmd => fun(#{sock_dst := Sock}) -> + ok = socket:close(Sock), + {ok, normal} + end} + ], + Evaluator = evaluator_start("tester", Seq, InitState), + ok = await_evaluator_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) -> + 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, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(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) -> + 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, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_b_send_and_recv_tcp(InitState) -> + process_flag(trap_exit, true), + ServerSeq = + [ + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {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 socket: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 server port", + cmd => fun(#{parent := Parent, lport := Port}) -> + ei("announcing port to parent (~p)", [Parent]), + Parent ! {server_port, self(), Port}, + ok + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("accepted: ~n ~p", [Sock]), + {ok, State#{tsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await request", + cmd => fun(#{tsock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "send reply", + cmd => fun(#{tsock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + #{desc => "close traffic socket", + cmd => fun(#{tsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => 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, lsa := LSA} = State) -> + case socket:bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "recv reply (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start server evaluator"), + Server = evaluator_start("server", ServerSeq, InitState), + p("await server (~p) port", [Server]), + SPort = receive + {server_port, Server, Port} -> + Port + end, + p("start client evaluator"), + Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Client]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% 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) -> + 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 => "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, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) -> + 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} + ], + + p("Run test for stream/tcp socket"), + InitState1 = #{domain => inet, type => stream, protocol => tcp}, + Tester1 = evaluator_start("tcp-tester", Seq, InitState1), + p("await evaluator 1"), + ok = await_evaluator_finish([Tester1]), + + p("Run test for dgram/udp socket"), + InitState2 = #{domain => inet, type => dgram, protocol => udp}, + Tester2 = evaluator_start("udp-tester", Seq, InitState2), + p("await evaluator 2"), + ok = await_evaluator_finish([Tester2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% 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) -> + 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) -> + receive + {start, Tester, Socket} -> + {ok, State#{tester => Tester, + sock => Socket}} + end + end}, + #{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 (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {continue, Tester} -> + ok + end + 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 (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await termination", + cmd => fun(#{tester := Tester} = State) -> + receive + {terminate, Tester} -> + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + 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 => "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) -> + Client ! {start, self(), Sock}, + ok + end}, + #{desc => "await (client) ready (1)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + 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", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await (client) ready (2)", + cmd => fun(#{client := Client} = _State) -> + receive + {ready, Client} -> + ok + end + 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 => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + #{desc => "order (client) terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await (client) down", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("Run test for stream/tcp socket"), + ClientInitState1 = #{}, + Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), + TesterInitState1 = #{domain => inet, + type => stream, + protocol => tcp, + client => Client1}, + Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), + p("await stream/tcp evaluator"), + ok = await_evaluator_finish([Tester1, Client1]), + + p("Run test for dgram/udp socket"), + ClientInitState2 = #{}, + Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), + TesterInitState2 = #{domain => inet, + type => dgram, + protocol => udp, + client => Client2}, + Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), + p("await dgram/udp evaluator"), + ok = await_evaluator_finish([Tester2, Client2]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% 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) -> + tc_try(api_to_connect_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_connect_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% 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) -> + tc_try(api_to_connect_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, timeout => 5000}, + 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) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {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 socket: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 => "monitor server", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + ei("announcing ready to tester (~p)", [Tester]), + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create socket 1", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock1 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 2", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock2 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create socket 3", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock3 => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 1 to local address", + cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 2 to local address", + cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind socket 3 to local address", + cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Synchronize with the server *** + #{desc => "order (server) start", + cmd => fun(#{server := Server}) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await ready (from server)", + cmd => fun(#{server := Server, lsa := LSA} = State) -> + receive + {ready, Server, Port} -> + {ok, State#{ssa => LSA#{port => Port}}} + end + end}, + + %% *** Connect sequence *** + #{desc => "order (server) start", + cmd => fun(#{sock1 := Sock1, + sock2 := Sock2, + sock3 := Sock3, + ssa := SSA, + timeout := To}) -> + Socks = [Sock1, Sock2, Sock3], + api_to_connect_tcp_await_timeout(Socks, To, SSA) + end}, + + %% *** Terminate server *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "order (server) terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await (server) down", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _} -> + State1 = maps:remove(server, State), + State2 = maps:remove(ssa, State1), + {ok, State2} + end + end}, + + %% *** Close our sockets *** + #{desc => "close socket 3", + cmd => fun(#{sock3 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock3, State)} + + end}, + #{desc => "close socket 2", + cmd => fun(#{sock2 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock2, State)} + + end}, + #{desc => "close socket 1", + cmd => fun(#{sock1 := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock1, State)} + + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("create tester evaluator"), + TesterInitState = InitState#{server => Server}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_finish([Server, Tester]). + + +api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). + +api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> + ?FAIL(unexpected_success); +api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> + ei("~w: try connect", [ID]), + Start = t(), + case socket:connect(Sock, ServerSA, To) of + {error, timeout} -> + ei("expected timeout (~w)", [ID]), + Stop = t(), + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end; + {error, econnreset = Reason} -> + ei("failed connecting: ~p - giving up", [Reason]), + ok; + {error, Reason} -> + ee("failed connecting: ~p", [Reason]), + ?FAIL({connect, Reason}); + ok -> + ei("unexpected success (~w) - try next", [ID]), + api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% 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) -> + 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) -> + tc_try(api_to_accept_tcp4, + fun() -> + not_yet_implemented(), + 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) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {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 socket: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 = tdiff(Start, Stop), + 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 *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_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) -> + 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) -> + tc_try(api_to_maccept_tcp4, + fun() -> + not_yet_implemented(), + 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) -> + receive + {start, Tester} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + tester_mref => MRef}} + end + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {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 socket: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", + cmd => fun(#{lsock := LSock, tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self(), LSock}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + 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 = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester}) -> + ei("announcing port to tester (~p)", [Tester]), + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + 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 *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + receive + {start, Tester, LSock} -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester => Tester, + lsock => LSock, + tester_mref => MRef}} + end + end}, + #{desc => "announce ready (1)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + 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 = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (2)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + ok + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + 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) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await prim-acceptor ready (1)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid, LSock} -> + {ok, State#{lsock => LSock}} + end + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 1 ready (1)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + Pid ! {start, self(), LSock}, + ok + end}, + #{desc => "await sec-acceptor 2 ready (1)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (2)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding prim-acceptor ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, prim_acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 1 ready (2)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_1}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await sec-acceptor 2 ready (2)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" + "~n ~p", [Reason]), + {error, {unexpected_exit, sec_acceptor_2}}; + {ready, Pid} -> + ok + end + end}, + + + %% Terminate the acceptor(s) + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ei("send terminate command to prim-acceptor (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), + Pid ! {terminate, self()}, + ok + end}, + + %% Await acceptor(s) termination + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1} + end + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = evaluator_start("prim-acceptor", + PrimAcceptorSeq, PrimAInitState), + + p("create prim-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = evaluator_start("sec-acceptor-1", + SecAcceptorSeq, SecAInitState1), + + p("create prim-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = evaluator_start("sec-acceptor-2", + SecAcceptorSeq, SecAInitState2), + + p("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor, + sec_acceptor1 => SecAcceptor1, + sec_acceptor2 => SecAcceptor2}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_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() -> + 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() -> + 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() -> + 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() -> + 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) -> + tc_try(api_to_recv_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + 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) -> + tc_try(api_to_recv_tcp6, + fun() -> + not_yet_implemented(), + case socket:supports(ipv6) of + true -> + Recv = fun(Sock, To) -> + socket:recv(Sock, 0, To) + end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + 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) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {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 socket: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 => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester, lport := Port}) -> + Tester ! {ready, self(), Port}, + ok + end}, + #{desc => "await continue", + cmd => fun(#{tester := Tester}) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "await accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + %% ok = socket:setopt(Sock, otp, debug, true), + {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 = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (recv timeout success)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + %% #{desc => "sleep some (before traffic close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "close (traffic) socket", + cmd => fun(#{sock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + %% #{desc => "sleep some (before listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + %% #{desc => "sleep some (after listen close)", + %% cmd => fun(_) -> + %% ?SLEEP(1000), + %% ok + %% end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester, Port} when is_pid(Tester) -> + {ok, State#{tester => Tester, + server_port => Port}} + end + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{lsa => LSA, ssa => 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, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = State) -> + MRef = erlang:monitor(process, Tester), + {ok, State#{tester_mref => MRef}} + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (with connect)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester} -> + ok + end + end}, + #{desc => "connect", + cmd => fun(#{sock := Sock, ssa := SSA}) -> + sock_connect(Sock, SSA), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + {error, {unexpected_exit, tester, Reason}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = State) -> + MRef = erlang:monitor(process, Server), + {ok, State#{server_mref => MRef}} + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = State) -> + MRef = erlang:monitor(process, Client), + {ok, State#{client_mref => MRef}} + end}, + + %% *** Activate server *** + #{desc => "start server", + cmd => fun(#{server := Server} = _State) -> + Server ! {start, self()}, + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server, Port} -> + {ok, State#{server_port => Port}} + end + end}, + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + Server ! {continue, self()}, + ok + end}, + + %% *** Activate client *** + #{desc => "start client", + cmd => fun(#{client := Client, server_port := Port} = _State) -> + Client ! {start, self(), Port}, + ok + end}, + #{desc => "await client ready", + cmd => fun(#{client := Client} = _State) -> + receive + {'DOWN', _, process, Client, Reason} -> + {error, {unexpected_exit, client, Reason}}; + {ready, Client} -> + ok + end + end}, + + %% *** The actual test *** + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + Client ! {continue, self()}, + ok + end}, + #{desc => "await server ready (accept/recv)", + cmd => fun(#{server := Server} = _State) -> + receive + {'DOWN', _, process, Server, Reason} -> + {error, {unexpected_exit, server, Reason}}; + {ready, Server} -> + ok + end + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + Client ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + receive + {'DOWN', _, process, Client, _Reason} -> + State1 = maps:remove(client, State), + State2 = maps:remove(client_mref, State1), + {ok, State2} + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + Server ! {terminate, self()}, + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + receive + {'DOWN', _, process, Server, _Reason} -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_mref, State1), + State3 = maps:remove(server_port, State2), + {ok, State3} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + + p("start server evaluator"), + ServerInitState = InitState, + Server = evaluator_start("server", ServerSeq, ServerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{server => Server, client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator(s)"), + ok = await_evaluator_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) -> + tc_try(api_to_recvfrom_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + 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) -> + tc_try(api_to_recvfrom_udp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +api_to_receive_udp(InitState) -> + TesterSeq = + [ + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {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 socket: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 = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + + %% *** Termination *** + #{desc => "close socket", + cmd => fun(#{sock := Sock} = _State) -> + sock_close(Sock), + ok + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start tester evaluator"), + Tester = evaluator_start("tester", TesterSeq, InitState), + + p("await evaluator"), + ok = await_evaluator_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) -> + tc_try(api_to_recvmsg_udp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + 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) -> + tc_try(api_to_recvmsg_udp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + 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) -> + tc_try(api_to_recvmsg_tcp4, + fun() -> + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv, + timeout => 5000}, + 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) -> + tc_try(api_to_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv, + timeout => 5000}, + ok = api_to_receive_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% 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) -> + tc_try(sc_cpe_socket_cleanup_tcp4, + fun() -> + %% not_yet_implemented(), + 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) -> + tc_try(sc_cpe_socket_cleanup_tcp6, + fun() -> + not_yet_implemented(), + 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 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) -> + 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) -> + tc_try(sc_cpe_socket_cleanup_udp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet6, + type => dgram, + protocol => udp}, + 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) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% *** Init part *** + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{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", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Tester ! {ready, self(), 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) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + %% #{desc => "enable (otp) debug", + %% cmd => fun(#{sock := Sock} = _State) -> + %% ok = socket:setopt(Sock, otp, debug, true) + %% end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + 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) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await (owner) ready", + cmd => fun(#{owner := Owner} = State) -> + receive + {'DOWN', _, process, Owner, Reason} -> + ee("Unexpected DOWN regarding owner ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, owner}}; + {ready, Owner, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "verify owner as controlling-process", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Owner} -> + ok; + {ok, Other} -> + {error, {unexpected_owner, Other}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (owner) terminate", + cmd => fun(#{owner := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await (owner) termination", + cmd => fun(#{owner := Owner} = _State) -> + receive + {'DOWN', _, process, Owner, _} -> + ok + end + end}, + #{desc => "verify no socket (closed)", + cmd => fun(#{owner := Owner, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Pid} -> + {error, {unexpected_success, Owner, Pid}}; + {error, closed} -> + ok; + {error, Reason} -> + ei("expected failure: ~p", [Reason]), + {error, {unexpected_failure, Reason}} + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start (socket) owner evaluator"), + Owner = evaluator_start("owner", OwnerSeq, InitState), + + p("start tester evaluator"), + TesterInitState = #{owner => Owner}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_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. +%% +%% +%% +%% 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. +%% +%% + +sc_lc_recv_response_tcp4(suite) -> + []; +sc_lc_recv_response_tcp4(doc) -> + []; +sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp4, + fun() -> + %% not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + 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) -> + tc_try(sc_lc_recv_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_lc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_tcp(InitState) -> + %% This is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure for the handler process. + AcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + 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) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{desc => "create (listen) socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, 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} -> + {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}) -> + Tester ! {ready, self(), Port}, + ok + end}, + + %% The actual test + #{desc => "await continue (connection)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester, Handler} -> + {ok, State#{handler => Handler}} + end + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ei("connection accepted"), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "transfer new connection to handler", + cmd => fun(#{handler := Handler, csock := Sock}) -> + ok = socket:setopt(Sock, + otp, controlling_process, + Handler), + Handler ! {connection, Sock}, + ok + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {continue, Tester} -> + ok + end + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{csock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + #{desc => "close (the connection) socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "socket cleanup", + cmd => fun(#{lsock := Sock} = State) -> + ok = socket:close(Sock), + State1 = maps:remove(csock, State), + State2 = maps:remove(lsock, State1), + State3 = maps:remove(lport, State2), + {ok, State3} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% 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) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + #{desc => "monitor server", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await connection socket", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {connection, Sock} -> + {ok, State#{sock => Sock}} + end + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% socket:setopt(Sock, otp, debug, true) + %% end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "attempt recv", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, _Data} -> + ee("Unexpected data received"), + {error, unexpected_data}; + {error, closed} -> + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ee("Unexpected read faulure: " + "~n ~p", [Reason]), + ERROR + end + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + Tester ! {ready, self()}, + ok + end}, + #{desc => "sleep some", + cmd => fun(_) -> + ?SLEEP(1000), + ok + end}, + %% #{desc => "monitored-by", + %% cmd => fun(_) -> + %% {_, Mons} = process_info(self(), monitored_by), + %% ei("Monitored By: ~p", [Mons]), + %% ok + %% end}, + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + ok + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + %% 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) -> + receive + {start, Tester} when is_pid(Tester) -> + {ok, State#{tester => Tester}} + end + end}, + + %% Init + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{lsa => LSA}} + end}, + #{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 => "bind socket to local address", + cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% The actual test + #{desc => "await continue", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester, Reason}}; + {continue, Tester, Port} -> + {ok, State#{lport => Port}} + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> + socket:connect(Sock, LSA#{port => LPort}) + end}, + #{desc => "announce ready (connection)", + cmd => fun(#{tester := Tester} = _State) -> + Tester ! {ready, self()}, + ok + end}, + + %% Cleaning up + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + receive + {'DOWN', _, process, Tester, Reason} -> + ee("Unexpected DOWN regarding tester ~p: " + "~n ~p", [Tester, Reason]), + {error, {unexpected_exit, tester}}; + {terminate, Tester} -> + {ok, maps:remove(tester, State)} + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := 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) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid, Port} -> + {ok, State#{lport => Port}} + end + end}, + + %% Start the handler + #{desc => "order handler start", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {start, self()}, + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding cient ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% The actual test + #{desc => "order acceptor to continue", + cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> + Pid ! {continue, self(), Handler}, + ok + end}, + #{desc => "order client to continue", + cmd => fun(#{client := Pid, lport := Port} = _State) -> + Pid ! {continue, self(), Port}, + ok + end}, + #{desc => "await acceptor ready (connection)", + cmd => fun(#{acceptor := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding acceptor ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await client ready (connection)", + cmd => fun(#{client := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding client ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "await handler ready (connection)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + #{desc => "sleep some", + cmd => fun(_State) -> + ?SLEEP(1000), + ok + end}, + #{desc => "order acceptor to continue (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {continue, self()}, + ok + end}, + #{desc => "await handler ready (close)", + cmd => fun(#{handler := Pid} = _State) -> + receive + {'DOWN', _, process, Pid, Reason} -> + ee("Unexpected DOWN regarding handler ~p: " + "~n ~p", [Pid, Reason]), + {error, {unexpected_exit, acceptor}}; + {ready, Pid} -> + ok + end + end}, + + %% Terminations + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(handler, State)} + end + end}, + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(client, State)} + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + Pid ! {terminate, self()}, + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + receive + {'DOWN', _, process, Pid, _} -> + {ok, maps:remove(acceptor, State)} + end + end}, + + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + p("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), + + p("start handler evaluator"), + HandlerInitState = #{recv => maps:get(recv, InitState)}, + Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), + + p("start client evaluator"), + ClientInitState = InitState, + Client = evaluator_start("client", ClientSeq, ClientInitState), + + p("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor, + handler => Handler, + client => Client}, + Tester = evaluator_start("tester", TesterSeq, TesterInitState), + + p("await evaluator"), + ok = await_evaluator_finish([Acceptor, Handler, Client, 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. + +sc_rc_recv_response_tcp4(suite) -> + []; +sc_rc_recv_response_tcp4(doc) -> + []; +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet, + type => stream, + 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) -> + tc_try(sc_rc_recv_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recv(Sock) end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv}, + ok = sc_rc_receive_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rc_receive_response_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% 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) -> + tc_try(sc_lc_recvmsg_response_tcp4, + fun() -> + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + 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) -> + tc_try(sc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + 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 +%% 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) -> + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet, + type => stream, + 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) -> + tc_try(sc_rc_recvmsg_response_tcp6, + fun() -> + not_yet_implemented(), + Recv = fun(Sock) -> socket:recvmsg(Sock) end, + InitState = #{domain => inet6, + type => stream, + 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 +%% 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) -> + tc_try(sc_lc_acceptor_response_tcp4, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + 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) -> + tc_try(sc_lc_acceptor_response_tcp6, + fun() -> + not_yet_implemented(), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_acceptor_response_tcp(_InitState) -> + ok. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% 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) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_addr(Domain, IFL); + {error, Reason} -> + ?FAIL({inet, getifaddrs, Reason}) + end. + +which_addr(_Domain, []) -> + ?FAIL(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_Domain, []) -> + ?FAIL(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% An evaluator is a process that executes a command sequence. +%% A test case will consist of atleast one evaluator (one for +%% each actor). +%% The evaluator process *always* run locally. Which means that +%% it will act as a "proxy" for remote nodes in necessary. +%% When the command sequence has been processed, the final state +%% will be used as exit reason. +%% A successful command shall evaluate to ok | {ok, NewState} + +-spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(), + Pid :: pid(), + MRef :: reference(). + +evaluator_start(Name, Seq, Init) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + Init2 = Init#{parent => self()}, + {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), + Pid. + +evaluator_init(Name, Seq, Init) -> + put(sname, Name), + evaluator_loop(1, Seq, Init). + +evaluator_loop(_ID, [], FinalState) -> + exit(FinalState); +evaluator_loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + ei("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + evaluator_loop(ID + 1, Cmds, State); + {ok, NewState} -> + evaluator_loop(ID + 1, Cmds, NewState); + {error, Reason} -> + ee("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + C:E:S -> + ee("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. + +await_evaluator_finish(Evs) -> + await_evaluator_finish(Evs, []). + +await_evaluator_finish([], []) -> + ok; +await_evaluator_finish([], Fails) -> + Fails; +await_evaluator_finish(Evs, Fails) -> + receive + {'DOWN', _MRef, process, Pid, normal} -> + case lists:delete(Pid, Evs) of + Evs -> + p("unknown process ~p died (normal)", [Pid]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("evaluator ~p success", [Pid]), + await_evaluator_finish(NewEvs, Fails) + end; + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:delete(Pid, Evs) of + Evs -> + p("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_evaluator_finish(Evs, Fails); + NewEvs -> + p("Evaluator ~p failed", [Pid]), + await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) + end + end. + + +ei(F) -> + ei(F, []). +ei(F, A) -> + eprint("", F, A). + +ee(F) -> + ee(F, []). +ee(F, A) -> + eprint(" ", F, A). + +eprint(Prefix, F, A) -> + io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", + [formated_timestamp(), get(sname), self(), Prefix | A]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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, SockAddr) -> + try socket:bind(Sock, SockAddr) of + {ok, Port} -> + Port; + {error, Reason} -> + p("sock_bind -> error: ~p", [Reason]), + ?FAIL({bind, Reason}) + catch + C:E:S -> + p("sock_bind -> failed: ~p, ~p, ~p", [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_listen(Sock) -> +%% sock_listen2(fun() -> socket:listen(Sock) end). + +%% sock_listen(Sock, BackLog) -> +%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). + +%% sock_listen2(Listen) -> +%% try Listen() of +%% ok -> +%% ok; +%% {error, Reason} -> +%% ?FAIL({listen, Reason}) +%% catch +%% C:E:S -> +%% ?FAIL({listen, C, E, S}) +%% end. + + +%% sock_accept(LSock) -> +%% try socket:accept(LSock) of +%% {ok, Sock} -> +%% Sock; +%% {error, Reason} -> +%% p("sock_accept -> error: ~p", [Reason]), +%% ?FAIL({accept, Reason}) +%% catch +%% C:E:S -> +%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), +%% ?FAIL({accept, C, E, S}) +%% end. + + +sock_close(Sock) -> + try socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + p("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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), + %% {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) -> + set_tc_name(TC), + p("begin ***"). + +tc_end(Result) when is_list(Result) -> + p("done: ~s", [Result]), + ok. + + +tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> + tc_begin(Case), + try + begin + Fun(), + tc_end("ok") + end + catch + throw:{skip, _} = SKIP -> + tc_end("skipping"), + SKIP; + Class:Error:Stack -> + tc_end("failed"), + erlang:raise(Class, Error, Stack) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). + +p(F) -> + p(F, []). + +p(F, A) -> + TcName = + 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, + i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). + + +%% i(F) -> +%% i(F, []). + +i(F, A) -> + io:format(user, F ++ "~n", A). diff --git a/erts/emulator/test/socket_client.erl b/erts/emulator/test/socket_client.erl new file mode 100644 index 0000000000..1c07e799b8 --- /dev/null +++ b/erts/emulator/test/socket_client.erl @@ -0,0 +1,538 @@ +%% +%% %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% +%% + +-module(socket_client). + +-export([ + start/1, start/2, start/5, start/6, + start_tcp/1, start_tcp/2, start_tcp/3, + start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2, + start_udp/1, start_udp/2, start_udp/3, + start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2 + ]). + +-define(LIB, socket_lib). + +-record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}). + +start(Port) -> + start(Port, 1). + +start(Port, Num) -> + start_tcp(Port, Num). + +start_tcp(Port) -> + start_tcp(Port, 1). + +start_tcp(Port, Num) -> + start_tcp4(Port, Num). + +start_tcp4(Port) -> + start_tcp4(Port, 1). + +start_tcp4(Port, Num) -> + start(inet, stream, tcp, Port, Num). + +start_tcp6(Port) -> + start_tcp6(Port, 1). + +start_tcp6(Port, Num) -> + start(inet6, stream, tcp, Port, Num). + +start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso + is_integer(Num) andalso + (Num > 0) -> + start(inet, stream, tcp, Addr, Port, Num); +start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso + is_integer(Num) andalso + (Num > 0) -> + start(inet6, stream, tcp, Addr, Port, Num). + + +start_udp(Port) -> + start_udp(Port, 1). + +start_udp(Port, Num) -> + start_udp4(Port, Num). + +start_udp4(Port) -> + start_udp4(Port, 1). + +start_udp4(Port, Num) -> + start(inet, dgram, udp, Port, Num). + +start_udp6(Port) -> + start_udp6(Port, 1). + +start_udp6(Port, Num) -> + start(inet6, dgram, udp, Port, Num). + +start_udp(Addr, Port, Num) when (size(Addr) =:= 4) -> + start(inet, dgram, udp, Addr, Port, Num); +start_udp(Addr, Port, Num) when (size(Addr) =:= 8) -> + start(inet6, dgram, udp, Addr, Port, Num). + + +start(Domain, Type, Proto, Port, Num) + when is_integer(Port) andalso is_integer(Num) -> + start(Domain, Type, Proto, which_addr(Domain), Port, Num); + +start(Domain, Type, Proto, Addr, Port) -> + start(Domain, Type, Proto, Addr, Port, 1). + +start(Domain, Type, Proto, Addr, Port, 1 = Num) -> + start(Domain, Type, Proto, Addr, Port, Num, true); +start(Domain, Type, Proto, Addr, Port, Num) + when is_integer(Num) andalso (Num > 1) -> + start(Domain, Type, Proto, Addr, Port, Num, false). + +start(Domain, Type, Proto, Addr, Port, Num, Verbose) -> + put(sname, "starter"), + Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose), + await_clients(Clients). + +start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) -> + start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []). + +start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc) + when (Num > 0) -> + StartClient = fun() -> + start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) + end, + {Pid, _} = spawn_monitor(StartClient), + ?LIB:sleep(500), + i("start client ~w", [ID]), + start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]); +start_clients(_, _, _, _, _, _, _, _, Acc) -> + i("all client(s) started"), + lists:reverse(Acc). + +await_clients([]) -> + i("all clients done"); +await_clients(Clients) -> + receive + {'DOWN', _MRef, process, Pid, _Reason} -> + case lists:delete(Pid, Clients) of + Clients2 when (Clients2 =/= Clients) -> + i("client ~p done", [Pid]), + await_clients(Clients2); + _ -> + await_clients(Clients) + end + end. + + +start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) -> + put(sname, ?LIB:f("client[~w]", [ID])), + SA = #{family => Domain, + addr => Addr, + port => Port}, + %% The way we use tos only works because we + %% send so few messages (a new value for every + %% message). + tos_init(), + do_start(Domain, Type, Proto, SA, Verbose). + +do_start(Domain, stream = Type, Proto, SA, Verbose) -> + try do_init(Domain, Type, Proto) of + Sock -> + connect(Sock, SA), + maybe_print_start_info(Verbose, Sock, Type), + %% Give the server some time... + ?LIB:sleep(5000), + %% ok = socket:close(Sock), + send_loop(#client{socket = Sock, + type = Type, + verbose = Verbose}) + catch + throw:E -> + e("Failed initiate: " + "~n Error: ~p", [E]) + end; +do_start(Domain, dgram = Type, Proto, SA, Verbose) -> + try do_init(Domain, Type, Proto) of + Sock -> + maybe_print_start_info(Verbose, Sock, Type), + %% Give the server some time... + ?LIB:sleep(5000), + %% ok = socket:close(Sock), + send_loop(#client{socket = Sock, + type = Type, + dest = SA, + verbose = Verbose}) + catch + throw:E -> + e("Failed initiate: " + "~n Error: ~p", [E]) + end. + +maybe_print_start_info(true = _Verbose, Sock, stream = _Type) -> + {ok, Name} = socket:sockname(Sock), + {ok, Peer} = socket:peername(Sock), + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MTU} = socket:getopt(Sock, ip, mtu), + {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + i("connected: " + "~n From: ~p" + "~n To: ~p" + "~nwhen" + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) MTU: ~p" + "~n (ip) MTU Discovery: ~p" + "~n (ip) Multicast ALL: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n => wait some", + [Name, Peer, + Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MTU, MTUDisc, MALL, MIF, MLoop, MTTL, + RecvTOS]); +maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) -> + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + {ok, OOBI} = socket:getopt(Sock, socket, oobinline), + {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), + {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), + {ok, Linger} = socket:getopt(Sock, socket, linger), + {ok, MALL} = socket:getopt(Sock, ip, multicast_all), + {ok, MIF} = socket:getopt(Sock, ip, multicast_if), + {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), + {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), + {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), + {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), + i("initiated when: " + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) OOBInline: ~p" + "~n (socket) SndBuf: ~p" + "~n (socket) RcvBuf: ~p" + "~n (socket) Linger: ~p" + "~n (ip) Multicast ALL: ~p" + "~n (ip) Multicast IF: ~p" + "~n (ip) Multicast Loop: ~p" + "~n (ip) Multicast TTL: ~p" + "~n (ip) RecvTOS: ~p" + "~n (ip) RecvTTL: ~p" + "~n => wait some", + [Domain, Type, Proto, + OOBI, SndBuf, RcvBuf, Linger, + MALL, MIF, MLoop, MTTL, + RecvTOS, RecvTTL]); +maybe_print_start_info(_Verbose, _Sock, _Type) -> + ok. + + +do_init(Domain, stream = Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + i("try (socket) bind"), + case socket:bind(Sock, any) of + {ok, _P} -> + ok = socket:setopt(Sock, socket, timestamp, true), + ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, ip, recvtos, true), + Sock; + {error, BReason} -> + throw({bind, BReason}) + end; +do_init(Domain, dgram = Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + case socket:bind(Sock, any) of + {ok, _} -> + ok = socket:setopt(Sock, socket, timestamp, true), + ok = socket:setopt(Sock, ip, tos, mincost), + ok = socket:setopt(Sock, ip, recvtos, true), + ok = socket:setopt(Sock, ip, recvttl, true), + Sock; + {error, BReason} -> + throw({bind, BReason}) + end. + + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + + +connect(Sock, SA) -> + i("try (socket) connect to:" + "~n ~p", [SA]), + case socket:connect(Sock, SA) of + ok -> + ok; + {error, Reason} -> + e("connect failure: " + "~n ~p", [Reason]), + exit({connect, Reason}) + end. + + +send_loop(#client{msg_id = N} = C) when (N =< 10) -> + i("try send request ~w", [N]), + Req = ?LIB:enc_req_msg(N, "hejsan"), + case send(C, Req) of + ok -> + i("request ~w sent - now try read answer", [N]), + case recv(C) of + {ok, {Source, Msg}} -> + if + (C#client.verbose =:= true) -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> ?LIB:f(" from:~n ~p", [Source]) + end]); + true -> + i("received ~w bytes", [size(Msg)]) + end, + case ?LIB:dec_msg(Msg) of + {reply, N, Reply} -> + if + (C#client.verbose =:= true) -> + i("received reply ~w: ~p", [N, Reply]); + true -> + i("received reply ~w", [N]) + end, + ?LIB:sleep(500), % Just to spread it out a bit + send_loop(C#client{msg_id = N+1}) + end; + {error, RReason} -> + e("Failed recv response for request ~w: " + "~n ~p", [N, RReason]), + exit({failed_recv, RReason}) + end; + {error, SReason} -> + e("Failed send request ~w: " + "~n ~p", [N, SReason]), + exit({failed_send, SReason}) + end; +send_loop(Client) -> + sock_close(Client). + +sock_close(#client{socket = Sock, verbose = true}) -> + i("we are done - close the socket when: " + "~n ~p", [socket:info()]), + ok = socket:close(Sock), + i("we are done - socket closed when: " + "~n ~p", [socket:info()]); +sock_close(#client{socket = Sock}) -> + i("we are done"), + ok = socket:close(Sock). + + + +send(#client{socket = Sock, type = stream}, Msg) -> + socket:send(Sock, Msg); +send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> + %% i("try send to: " + %% "~n ~p", [Dest]), + %% ok = socket:setopt(Sock, otp, debug, true), + TOS = tos_next(), + ok = socket:setopt(Sock, ip, tos, TOS), + case socket:sendto(Sock, Msg, Dest) of + ok = OK -> + OK; + {error, _} = ERROR -> + ERROR + end. + +recv(#client{socket = Sock, type = stream, msg = false}) -> + case socket:recv(Sock) of + {ok, Msg} -> + {ok, {undefined, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) -> + case socket:recvmsg(Sock) of + %% An iov of length 1 is an simplification... + {ok, #{addr := undefined = Source, + iov := [Msg], + ctrl := CMsgHdrs, + flags := Flags}} -> + if + (Verbose =:= true) -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]); + true -> + ok + end, + {ok, {Source, Msg}}; + {error, _} = ERROR -> + ERROR + end; +recv(#client{socket = Sock, type = dgram, msg = false}) -> + socket:recvfrom(Sock); +recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Msg], + ctrl := CMsgHdrs, + flags := Flags}} -> + if + (Verbose =:= true) -> + i("received message: " + "~n CMsgHdr: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]); + true -> + ok + end, + {ok, {Source, Msg}}; + {error, _} = ERROR -> + ERROR + end. + + + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +%% --- + +%% enc_req_msg(N, Data) -> +%% enc_msg(?REQ, N, Data). + +%% enc_rep_msg(N, Data) -> +%% enc_msg(?REP, N, Data). + +%% enc_msg(Type, N, Data) when is_list(Data) -> +%% enc_msg(Type, N, list_to_binary(Data)); +%% enc_msg(Type, N, Data) +%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> +%% <>. + +%% dec_msg(<>) -> +%% {request, N, Data}; +%% dec_msg(<>) -> +%% {reply, N, Data}. + + +%% --- + +%% sleep(T) -> +%% receive after T -> ok end. + + +%% --- + +%% 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). + + +%% --- + +tos_init() -> + put(tos, 1). + +tos_next() -> + case get(tos) of + TOS when (TOS < 100) -> + put(tos, TOS + 1), + TOS; + _ -> + put(tos, 1), + 1 + end. + + +%% --- + +e(F, A) -> + ?LIB:e(F, A). + +i(F) -> + ?LIB:i(F). + +i(F, A) -> + ?LIB:i(F, A). + diff --git a/erts/emulator/test/socket_lib.erl b/erts/emulator/test/socket_lib.erl new file mode 100644 index 0000000000..9d6524d467 --- /dev/null +++ b/erts/emulator/test/socket_lib.erl @@ -0,0 +1,133 @@ +%% +%% %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% +%% + +-module(socket_lib). + +-export([ + sleep/1, + req/0, rep/0, + enc_req_msg/2, enc_rep_msg/2, + enc_msg/3, dec_msg/1, + request/3, reply/4, + f/2, + i/1, i/2, + e/2 + ]). + + +-define(REQ, 0). +-define(REP, 1). + + +%% --- + +sleep(T) -> + receive after T -> ok end. + + +%% --- + +req() -> ?REQ. +rep() -> ?REP. + +enc_req_msg(N, Data) -> + enc_msg(?REQ, N, Data). + +enc_rep_msg(N, Data) -> + enc_msg(?REP, N, Data). + +enc_msg(Type, N, Data) when is_list(Data) -> + enc_msg(Type, N, list_to_binary(Data)); +enc_msg(Type, N, Data) + when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> + <>. + +dec_msg(<>) -> + {request, N, Data}; +dec_msg(<>) -> + {reply, N, Data}. + + +%% --- + +request(Tag, Pid, Request) -> + Ref = make_ref(), + Pid ! {Tag, self(), Ref, Request}, + receive + {Tag, Pid, Ref, Reply} -> + Reply + end. + +reply(Tag, Pid, Ref, Reply) -> + Pid ! {Tag, self(), Ref, Reply}. + + +%% --- + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%% --- + +e(F, A) -> + p(" " ++ F, A). + +i(F) -> + i(F, []). +i(F, A) -> + p("*** " ++ F, A). + +p(F, A) -> + p(get(sname), F, A). + +p(SName, F, A) -> + io:format("[~s,~p][~s] " ++ F ++ "~n", + [SName,self(),formated_timestamp()|A]). + + +%% --- + +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). + + diff --git a/erts/emulator/test/socket_server.erl b/erts/emulator/test/socket_server.erl new file mode 100644 index 0000000000..45adffc5e6 --- /dev/null +++ b/erts/emulator/test/socket_server.erl @@ -0,0 +1,954 @@ +%% +%% %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% +%% + +-module(socket_server). + +-export([ + start/0, start/5, + start_tcp/0, start_tcp/1, start_tcp/3, + start_tcp4/0, start_tcp4/1, start_tcp4/2, + start_tcp6/0, start_tcp6/1, start_tcp6/2, + start_udp/0, start_udp/1, start_udp/3, + start_udp4/0, start_udp4/1, start_udp4/2, + start_udp6/0, start_udp6/1, start_udp6/2, + start_sctp/0, start_sctp/1 + ]). + +-define(LIB, socket_lib). + +-record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). +-record(acceptor, {id, socket, manager, + atimeout = 5000}). +-record(handler, {socket, peek, msg, type, manager, + stimeout = 5000, rtimeout = 5000}). + +-define(NUM_ACCEPTORS, 5). + +start() -> + start_tcp(). + +start_tcp() -> + start_tcp4(). + +start_tcp(Peek) -> + start_tcp4(Peek). + +start_tcp4() -> + start_tcp4(false). + +start_tcp4(Peek) -> + start_tcp4(false, Peek). + +start_tcp4(UseMsg, Peek) -> + start_tcp(inet, UseMsg, Peek). + +start_tcp6() -> + start_tcp6(false). + +start_tcp6(Peek) -> + start_tcp6(false, Peek). + +start_tcp6(UseMsg, Peek) -> + start_tcp(inet6, UseMsg, Peek). + +start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, stream, tcp, UseMsg, Peek). + +start_udp() -> + start_udp4(). + +start_udp(Peek) -> + start_udp4(Peek). + +start_udp4() -> + start_udp4(false). + +start_udp4(Peek) -> + start_udp4(false, Peek). + +start_udp4(UseMsg, Peek) -> + start_udp(inet, UseMsg, Peek). + +start_udp6() -> + start_udp6(false, false). + +start_udp6(Peek) -> + start_udp6(false, Peek). + +start_udp6(UseMsg, Peek) -> + start_udp(inet6, UseMsg, Peek). + +start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> + start(Domain, dgram, udp, UseMsg, Peek). + + +start_sctp() -> + start_sctp(inet). + +start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> + start(Domain, seqpacket, sctp, true, false). + +start(Domain, Type, Proto, UseMsg, Peek) -> + put(sname, "starter"), + i("try start manager"), + {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek), + i("manager (~p) started", [Pid]), + loop(Pid, MRef). + +loop(Pid, MRef) -> + receive + {'DOWN', MRef, process, Pid, Reason} -> + i("manager process exited: " + "~n ~p", [Reason]), + ok + end. + + +%% ========================================================================= + +manager_start(Domain, Type, Proto, UseMsg, Peek) -> + spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end). + +manager_start_handler(Pid, Sock) -> + manager_request(Pid, {start_handler, Sock}). + +manager_stop(Pid, Reason) -> + manager_request(Pid, {stop, Reason}). + +manager_request(Pid, Request) -> + ?LIB:request(manager, Pid, Request). + +manager_reply(Pid, Ref, Reply) -> + ?LIB:reply(manager, Pid, Ref, Reply). + + +manager_init(Domain, Type, Proto, UseMsg, Peek) -> + put(sname, "manager"), + do_manager_init(Domain, Type, Proto, UseMsg, Peek). + +do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) -> + i("try start acceptor(s)"), + {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), + manager_loop(#manager{socket = Sock, + msg = UseMsg, + peek = Peek, + acceptors = Acceptors, + handler_id = 1, + handlers = []}); +do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) -> + i("try open socket"), + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + i("socket opened (~s,~s,~s): " + "~n broadcast: ~s" + "~n dontroute: ~s" + "~n keepalive: ~s" + "~n reuseaddr: ~s" + "~n linger: ~s" + "~n debug: ~s" + "~n prio: ~s" + "~n rcvbuf: ~s" + "~n rcvtimeo: ~s" + "~n sndbuf: ~s" + "~n sndtimeo: ~s" + "~n => try find (local) address", + [F(domain), F(type), F(protocol), + F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), + F(debug), F(priority), + F(rcvbuf), F(rcvtimeo), F(sndbuf), F(sndtimeo)]), + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + i("try bind to: " + "~n ~p", [Addr]), + case socket:bind(Sock, SA) of + {ok, _P} -> + ok; + {error, BReason} -> + throw({bind, BReason}) + end, + i("bound to: " + "~n ~s" + "~n => try start handler", + [case socket:sockname(Sock) of + {ok, Name} -> f("~p", [Name]); + {error, R} -> f("error: ~p", [R]) + end]), + case handler_start(1, Sock, UseMsg, Peek) of + {ok, {Pid, MRef}} -> + i("handler (~p) started", [Pid]), + handler_continue(Pid), + manager_loop(#manager{peek = Peek, + msg = UseMsg, + handler_id = 2, % Just in case + handlers = [{1, Pid, MRef}]}); + {error, SReason} -> + e("Failed starting handler: " + "~n ~p", [SReason]), + exit({failed_start_handler, SReason}) + end; + {error, OReason} -> + e("Failed open socket: " + "~n ~p", [OReason]), + exit({failed_open_socket, OReason}) + end; +do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) -> + %% This is as far as I have got with SCTP at the moment... + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + i("(sctp) socket opened: " + "~n ~p", [Sock]), + EXP = fun(_Desc, Expect, Expect) -> + Expect; + (Desc, Expect, Actual) -> + e("Unexpected result ~w: " + "~n Expect: ~p" + "~n Actual: ~p", [Desc, Expect, Actual]), + exit({Desc, Expect, Actual}) + end, + GO = fun(O) -> case socket:getopt(Sock, sctp, O) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + %% ok = socket:setopt(Sock, otp, debug, true), + + i("Miscellaneous options: " + "~n associnfo: ~s" + "~n autoclose: ~s" + "~n disable-fragments: ~s" + "~n initmsg: ~s" + "~n maxseg: ~s" + "~n nodelay: ~s" + "~n rtoinfo: ~s", + [GO(associnfo), + GO(autoclose), + GO(disable_fragments), + GO(initmsg), + GO(maxseg), + GO(nodelay), + GO(rtoinfo)]), + + Events = #{data_in => true, + association => true, + address => true, + send_failure => true, + peer_error => true, + shutdown => true, + partial_delivery => true, + adaptation_layer => true, + authentication => true, + sender_dry => true}, + EXP(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), + EXP(close_socket, ok, socket:close(Sock)); + {error, Reason} -> + exit({failed_open, Reason}) + end; +do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) -> + do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek); +do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + i("(sctp) socket opened: " + "~n ~p", [Sock]), + socket:close(Sock); + {error, Reason} -> + exit({failed_open, Reason}) + end. + + + +manager_stream_init(Domain, Type, Proto) -> + i("try (socket) open"), + Sock = case socket:open(Domain, Type, Proto) of + {ok, S} -> + S; + {error, OReason} -> + throw({open, OReason}) + end, + F = fun(X) -> case socket:getopt(Sock, socket, X) of + {ok, V} -> f("~p", [V]); + {error, R} -> f("error: ~p", [R]) + end + end, + i("(socket) open (~s,~s,~s): " + "~n debug: ~s" + "~n prio: ~s" + "~n => try find (local) address", + [F(domain), F(type), F(protocol), F(debug), F(priority)]), + Addr = which_addr(Domain), + SA = #{family => Domain, + addr => Addr}, + i("found: " + "~n ~p" + "~n => try (socket) bind", [Addr]), + %% ok = socket:setopt(Sock, otp, debug, true), + %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! + Port = case socket:bind(Sock, SA) of + {ok, P} -> + %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! + %% ok = socket:setopt(Sock, otp, debug, false), + P; + {error, BReason} -> + throw({bind, BReason}) + end, + i("bound to: " + "~n ~p" + "~n => try (socket) listen (acceptconn: ~s)", + [Port, F(acceptconn)]), + case socket:listen(Sock) of + ok -> + i("listening (acceptconn: ~s)", + [F(acceptconn)]), + manager_stream_init(Sock, 1, ?NUM_ACCEPTORS, []); + {error, LReason} -> + throw({listen, LReason}) + end. + +which_addr(Domain) -> + Iflist = case inet:getifaddrs() of + {ok, IFL} -> + IFL; + {error, Reason} -> + throw({inet,getifaddrs,Reason}) + end, + which_addr(Domain, Iflist). + +which_addr(_Domain, []) -> + throw(no_address); +which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> + which_addr2(Domain, IFO); +which_addr(Domain, [_|IFL]) -> + which_addr(Domain, IFL). + +which_addr2(_, []) -> + throw(no_address); +which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> + Addr; +which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> + Addr; +which_addr2(Domain, [_|IFO]) -> + which_addr2(Domain, IFO). + + +manager_stream_init(Sock, ID, NumAcceptors, Acc) + when (NumAcceptors > 0) -> + i("try start acceptor"), + case acceptor_start(Sock, ID) of + {ok, {Pid, MRef}} -> + i("acceptor ~w (~p) started", [ID, Pid]), + ?LIB:sleep(2000), + manager_stream_init(Sock, ID+1, NumAcceptors-1, + [{ID, Pid, MRef}|Acc]); + {error, Reason} -> + exit({failed_starting_acceptor, Reason}) + end; +manager_stream_init(Sock, _ID, 0, Acc) -> + %% Req = {kill_acceptor, length(Acc)}, % Last in the queue + %% Req = {kill_acceptor, 3}, % In the "middle" of the queue + %% Req = {kill_acceptor, 2}, % The first in the queue + %% Req = {kill_acceptor, 1}, % Current acceptor + %% Msg = {manager, self(), make_ref(), Req}, + %% erlang:send_after(timer:seconds(10), self(), Msg), + {Sock, lists:reverse(Acc)}. + + +manager_loop(M) -> + receive + {'DOWN', MRef, process, Pid, Reason} -> + M2 = manager_handle_down(M, MRef, Pid, Reason), + manager_loop(M2); + + {manager, Pid, Ref, Request} -> + M2 = manager_handle_request(M, Pid, Ref, Request), + manager_loop(M2) + end. + + +manager_handle_down(#manager{acceptors = Acceptors, + handlers = Handlers} = M, MRef, Pid, Reason) -> + case lists:keysearch(Pid, 2, Acceptors) of + {value, {ID, Pid, MRef}} when (Reason =:= normal) -> + i("acceptor ~w exited (normally)", [ID]), + case lists:keydelete(Pid, 2, Acceptors) of + [] -> + %% We are done + i("the last acceptor - we are done"), + exit(normal); + Acceptors2 -> + M#manager{acceptors = Acceptors2} + end; + {value, {ID, Pid, MRef}} -> + e("acceptor ~w crashed: " + "~n ~p", [ID, Reason]), + exit({acceptor_died, Reason}); + + false -> %% handler! + if + (Reason =/= normal) -> + e("handler ~p died: " + "~n ~p", [Pid, Reason]); + true -> + i("handler ~p terminated", [Pid]) + end, + Handlers2 = lists:keydelete(Pid, 2, Handlers), + M#manager{handlers = Handlers2} + end. + + +manager_handle_request(#manager{peek = Peek, + msg = UseMsg, + handler_id = HID, + handlers = Handlers} = M, Pid, Ref, + {start_handler, Sock}) -> + i("try start handler (~w)", [HID]), + case handler_start(HID, Sock, UseMsg, Peek) of + {ok, {HPid, HMRef}} -> + i("handler ~w started", [HID]), + manager_reply(Pid, Ref, {ok, HPid}), + M#manager{handler_id = HID+1, + handlers = [{HID, HPid, HMRef}|Handlers]}; + {error, Reason} = ERROR -> + e("Failed starting new handler: " + "~n Sock: ~p" + "~n Reason: ~p", [Sock, Reason]), + manager_reply(Pid, Ref, ERROR), + M + end; +manager_handle_request(#manager{socket = Sock, + acceptors = [{AID, APid, AMRef}]} = M, _Pid, _Ref, + {kill_acceptor, AID}) -> + i("try kill (only remeining) acceptor ~w", [AID]), + socket:setopt(Sock, otp, debug, true), + manager_stop_acceptor(APid, AMRef, AID, kill), + M#manager{acceptors = []}; +manager_handle_request(#manager{socket = Sock, + acceptors = Acceptors} = M, _Pid, _Ref, + {kill_acceptor, AID}) -> + i("try kill acceptor ~w", [AID]), + case lists:keysearch(AID, 1, Acceptors) of + {value, {AID, APid, AMRef}} -> + socket:setopt(Sock, otp, debug, true), + manager_stop_acceptor(APid, AMRef, AID, kill), + Acceptors2 = lists:keydelete(AID, 1, Acceptors), + M#manager{acceptors = Acceptors2}; + false -> + e("no such acceptor"), + M + end; +manager_handle_request(#manager{acceptors = Acceptors, + handlers = Handlers}, Pid, Ref, + {stop, Reason}) -> + i("stop"), + manager_reply(Pid, Ref, ok), + manager_stop_handlers(Handlers, Reason), + manager_stop_acceptors(Acceptors, Reason), + i("stopped", []), + exit(Reason). + +manager_stop_acceptors(Acceptors, Reason) -> + lists:foreach(fun({ID,P,M}) -> + manager_stop_acceptor(P, M, ID, Reason) + end, Acceptors). + +manager_stop_acceptor(Pid, MRef, ID, Reason) -> + i("try stop acceptor ~w (~p): ~p", [ID, Pid, Reason]), + erlang:demonitor(MRef, [flush]), + acceptor_stop(Pid, Reason), + ok. + +manager_stop_handlers(Handlers, Reason) -> + lists:foreach(fun({ID,P,M}) -> + manager_stop_handler(P, M, ID, Reason) + end, Handlers). + +manager_stop_handler(Pid, MRef, ID, Reason) -> + i("try stop handler ~w (~p): ~p", [ID, Pid, Reason]), + erlang:demonitor(MRef, [flush]), + handler_stop(Pid, Reason), + ok. + + + +%% ========================================================================= + +acceptor_start(Sock, ID) -> + Self = self(), + A = {Pid, _} = spawn_monitor(fun() -> + acceptor_init(Self, Sock, ID) + end), + receive + {acceptor, Pid, ok} -> + {ok, A}; + {acceptor, Pid, {error, _} = Error} -> + exit(Pid, kill), % Just in case + Error; + {'DOWN', _MRef, process, Pid, Reason} -> + {error, {crashed, Reason}} + end. + +acceptor_stop(Pid, _Reason) -> + %% acceptor_request(Pid, {stop, Reason}). + exit(Pid, kill). + +%% acceptor_request(Pid, Request) -> +%% request(acceptor, Pid, Request). + +%% acceptor_reply(Pid, Ref, Reply) -> +%% reply(acceptor, Pid, Ref, Reply). + + +acceptor_init(Manager, Sock, ID) -> + put(sname, f("acceptor[~w]", [ID])), + Manager ! {acceptor, self(), ok}, + %% ok = socket:setopt(Sock, otp, debug, true), + acceptor_loop(#acceptor{id = ID, + manager = Manager, + socket = Sock}). + +acceptor_loop(#acceptor{socket = LSock, atimeout = Timeout} = A) -> + i("try accept"), + case socket:accept(LSock, Timeout) of + {ok, Sock} -> + i("accepted: " + "~n ~p" + "~nwhen" + "~n ~p", [Sock, socket:info()]), + case acceptor_handle_accept_success(A, Sock) of + ok -> + acceptor_loop(A); + {error, Reason} -> + e("Failed starting handler: " + "~n ~p", [Reason]), + socket:close(Sock), + exit({failed_starting_handler, Reason}) + end; + {error, timeout} -> + i("timeout"), + acceptor_loop(A); + {error, Reason} -> + e("accept failure: " + "~n ~p", [Reason]), + exit({accept, Reason}) + end. + +acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> + i("try start handler for peer" + "~n ~p", [case socket:peername(Sock) of + {ok, Peer} -> Peer; + {error, _} = E -> E + end]), + case manager_start_handler(Manager, Sock) of + {ok, Pid} -> + i("handler (~p) started - now change 'ownership'", [Pid]), + case socket:setopt(Sock, otp, controlling_process, Pid) of + ok -> + %% Normally we should have a msgs collection here + %% (of messages we receive before the control was + %% handled over to Handler), but since we don't + %% have active implemented yet... + i("new handler (~p) now controlling process", [Pid]), + handler_continue(Pid), + ok; + {error, _} = ERROR -> + exit(Pid, kill), + ERROR + end; + {error, Reason2} -> + e("failed starting handler: " + "~n (new) Socket: ~p" + "~n Reason: ~p", [Sock, Reason2]), + exit({failed_starting_handler, Reason2}) + end. + + + +%% ========================================================================= + +handler_start(ID, Sock, UseMsg, Peek) -> + Self = self(), + H = {Pid, _} = spawn_monitor(fun() -> + handler_init(Self, ID, UseMsg, Peek, Sock) + end), + receive + {handler, Pid, ok} -> + {ok, H}; + {handler, Pid, {error, _} = ERROR} -> + exit(Pid, kill), % Just in case + ERROR + end. + +handler_stop(Pid, _Reason) -> + %% handler_request(Pid, {stop, Reason}). + exit(Pid, kill). + +handler_continue(Pid) -> + handler_request(Pid, continue). + +handler_request(Pid, Request) -> + ?LIB:request(handler, Pid, Request). + +handler_reply(Pid, Ref, Reply) -> + ?LIB:reply(handler, Pid, Ref, Reply). + + +handler_init(Manager, ID, Msg, Peek, Sock) -> + put(sname, f("handler:~w", [ID])), + i("starting"), + Manager ! {handler, self(), ok}, + receive + {handler, Pid, Ref, continue} -> + i("got continue"), + handler_reply(Pid, Ref, ok), + G = fun(L, O) -> case socket:getopt(Sock, L, O) of + {ok, Val} -> + f("~p", [Val]); + {error, R} when is_atom(R) -> + f("error: ~w", [R]); + {error, {T, R}} when is_atom(T) -> + f("error: ~w, ~p", [T, R]); + {error, R} -> + f("error: ~p", [R]) + end + end, + GSO = fun(O) -> G(socket, O) end, + GIP4 = fun(O) -> G(ip, O) end, + GIP6 = fun(O) -> G(ipv6, O) end, + {ok, Domain} = socket:getopt(Sock, socket, domain), + {ok, Type} = socket:getopt(Sock, socket, type), + {ok, Proto} = socket:getopt(Sock, socket, protocol), + B2D = GSO(bindtodevice), + RA = GSO(reuseaddr), + RP = GSO(reuseport), + OOBI = GSO(oobinline), + RcvBuf = GSO(rcvbuf), + RcvLW = GSO(rcvlowat), + RcvTO = GSO(rcvtimeo), + SndBuf = GSO(sndbuf), + SndLW = GSO(sndlowat), + SndTO = GSO(sndtimeo), + Linger = GSO(linger), + Timestamp = GSO(timestamp), + FreeBind = GIP4(freebind), + MTU = GIP4(mtu), + MTUDisc = GIP4(mtu_discover), + MALL = GIP4(multicast_all), + MIF4 = GIP4(multicast_if), + MLoop4 = GIP4(multicast_loop), + MTTL = GIP4(multicast_ttl), + NF = GIP4(nodefrag), % raw only + PktInfo = GIP4(pktinfo), % dgram only + RecvErr4 = GIP4(recverr), + RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) + RecvOPTS = GIP4(recvopts), % Not stream + RecvOrigDstAddr = GIP4(recvorigdstaddr), + RecvTOS = GIP4(recvtos), + RecvTTL = GIP4(recvttl), % not stream + RetOpts = GIP4(retopts), % not stream + SendSrcAddr = GIP4(sendsrcaddr), + TOS = GIP4(tos), + Transparent = GIP4(transparent), + TTL = GIP4(ttl), + MHops = GIP6(multicast_hops), + MIF6 = GIP6(multicast_if), % Only dgram and raw + MLoop6 = GIP6(multicast_loop), + RecvErr6 = GIP6(recverr), + RecvPktInfo = GIP6(recvpktinfo), + RtHdr = GIP6(rthdr), + AuthHdr = GIP6(authhdr), + HopLimit = GIP6(hoplimit), + HopOpts = GIP6(hopopts), + DstOpts = GIP6(dstopts), + FlowInfo = GIP6(flowinfo), + UHops = GIP6(unicast_hops), + i("got continue when: " + "~n (socket) Domain: ~p" + "~n (socket) Type: ~p" + "~n (socket) Protocol: ~p" + "~n (socket) Reuse Address: ~s" + "~n (socket) Reuse Port: ~s" + "~n (socket) Bind To Device: ~s" + "~n (socket) OOBInline: ~s" + "~n (socket) RcvBuf: ~s" + "~n (socket) RcvLW: ~s" + "~n (socket) RcvTO: ~s" + "~n (socket) SndBuf: ~s" + "~n (socket) SndLW: ~s" + "~n (socket) SndTO: ~s" + "~n (socket) Linger: ~s" + "~n (socket) Timestamp: ~s" + "~n (ip) FreeBind: ~s" + "~n (ip) MTU: ~s" + "~n (ip) MTU Discovery: ~s" + "~n (ip) Multicast ALL: ~s" + "~n (ip) Multicast IF: ~s" + "~n (ip) Multicast Loop: ~s" + "~n (ip) Multicast TTL: ~s" + "~n (ip) Node Frag: ~s" + "~n (ip) Pkt Info: ~s" + "~n (ip) Recv Err: ~s" + "~n (ip) Recv IF: ~s" + "~n (ip) Recv OPTS: ~s" + "~n (ip) Recv Orig Dst Addr: ~s" + "~n (ip) Recv TOS: ~s" + "~n (ip) Recv TTL: ~s" + "~n (ip) Ret Opts: ~s" + "~n (ip) Send Src Addr: ~s" + "~n (ip) TOS: ~s" + "~n (ip) Transparent: ~s" + "~n (ip) TTL: ~s" + "~n (ipv6) Multicast Hops: ~s" + "~n (ipv6) Multicast IF: ~s" + "~n (ipv6) Multicast Loop: ~s" + "~n (ipv6) Recv Err: ~s" + "~n (ipv6) Recv Pkt Info: ~s" + "~n (ipv6) RT Hdr: ~s" + "~n (ipv6) Auth Hdr: ~s" + "~n (ipv6) Hop Limit: ~s" + "~n (ipv6) Hop Opts: ~s" + "~n (ipv6) Dst Opts: ~s" + "~n (ipv6) Flow Info: ~s" + "~n (ipv6) Unicast Hops: ~s", + [Domain, Type, Proto, + RA, RP, B2D, OOBI, + RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, + Linger, Timestamp, + FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, + NF, PktInfo,RecvErr4, + RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts, + SendSrcAddr, TOS, Transparent, TTL, + MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, + RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, + UHops]), + + %% ok = socket:setopt(Sock, otp, debug, true), + %% case socket:getopt(Sock, 0, {13, int}) of + %% {ok, Val} -> + %% i("PktOpts ok: ~p", [Val]); + %% {error, Reason} -> + %% e("PktOpts err: ~p", [Reason]) + %% end, + %% ok = socket:setopt(Sock, otp, debug, false), + SSO = fun(O, V) -> soso(Sock, O, V) end, + SIP4 = + fun(O, V) -> + if + (Type =:= dgram) -> + ok = soip(Sock, O, V); + true -> + ok + end + end, + SSO(timestamp, true), + SIP4(pktinfo, true), + ok = soip(Sock, recvtos, true), + SIP4(recvttl, true), + ok = soip(Sock, recvorigdstaddr, true), + + handler_loop(#handler{msg = Msg, + peek = Peek, + manager = Manager, + type = Type, + socket = Sock}) + end. + +so(Sock, Lvl, Opt, Val) -> + ok = socket:setopt(Sock, Lvl, Opt, Val). + +soso(Sock, Opt, Val) -> + so(Sock, socket, Opt, Val). + +soip(Sock, Opt, Val) -> + so(Sock, ip, Opt, Val). + +%% soipv6(Sock, Opt, Val) -> +%% so(Sock, ipv6, Opt, Val). + +handler_loop(H) -> + i("try read message"), + case recv(H) of + {ok, {Source, Msg}} -> + i("received ~w bytes of data~s", + [size(Msg), case Source of + undefined -> ""; + _ -> f(" from:~n ~p", [Source]) + end]), + case ?LIB:dec_msg(Msg) of + {request, N, Req} -> + i("received request ~w: " + "~n ~p", [N, Req]), + Reply = ?LIB:enc_rep_msg(N, "hoppsan"), + case send(H, Reply, Source) of + ok -> + i("successfully sent reply ~w", [N]), + handler_loop(H); + {error, SReason} -> + e("failed sending reply ~w:" + "~n ~p", [N, SReason]), + exit({failed_sending_reply, SReason}) + end + end; + + {error, closed} -> + i("closed when" + "~n ~p", [socket:info()]), + exit(normal); + + {error, RReason} -> + e("failed reading request: " + "~n ~p", [RReason]), + exit({failed_reading_request, RReason}) + end. + + +recv(#handler{peek = true, socket = Sock, type = stream}) -> + peek_recv(Sock); +recv(#handler{socket = Sock, msg = true, type = stream}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined = Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + e("received *unexpected* message: " + "~n ~p", [X]), + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; +recv(#handler{socket = Sock, msg = true, type = dgram}) -> + case socket:recvmsg(Sock) of + {ok, #{addr := Source, + iov := [Data], + ctrl := CMsgHdrs, + flags := Flags}} -> + i("received message: " + "~n CMsgHdrs: ~p" + "~n Flags: ~p", [CMsgHdrs, Flags]), + {ok, {Source, Data}}; + {ok, X} -> + {error, {unexpected, X}}; + {error, _} = ERROR -> + ERROR + end; +recv(#handler{peek = false, socket = Sock, type = stream}) -> + do_recv(Sock); +recv(#handler{peek = Peek, socket = Sock, type = dgram}) + when (Peek =:= true) -> + %% ok = socket:setopt(Sock, otp, debug, true), + RES = peek_recvfrom(Sock, 5), + %% ok = socket:setopt(Sock, otp, debug, false), + RES; +recv(#handler{peek = Peek, socket = Sock, type = dgram}) + when (Peek =:= false) -> + %% ok = socket:setopt(Sock, otp, debug, true), + socket:recvfrom(Sock). + +do_recv(Sock) -> + case socket:recv(Sock) of + {ok, Msg} -> + {ok, {undefined, Msg}}; + {error, _} = ERROR -> + ERROR + end. + +peek_recv(Sock) -> + i("try peek on the message type (expect request)"), + Type = ?LIB:req(), + case socket:recv(Sock, 4, [peek]) of + {ok, <>} -> + i("was request - do proper recv"), + do_recv(Sock); + {error, _} = ERROR -> + ERROR + end. + +peek_recvfrom(Sock, BufSz) -> + i("try peek recvfrom with buffer size ~w", [BufSz]), + case socket:recvfrom(Sock, BufSz, [peek]) of + {ok, {_Source, Msg}} when (BufSz =:= size(Msg)) -> + %% i("we filled the buffer: " + %% "~n ~p", [Msg]), + %% It *may not* fit => try again with double size + peek_recvfrom(Sock, BufSz*2); + {ok, _} -> + %% It fits => read for real + i("we did *not* fill the buffer - do the 'real' read"), + socket:recvfrom(Sock); + {error, _} = ERROR -> + ERROR + end. + + +send(#handler{socket = Sock, msg = true, type = stream, stimeout = Timeout}, + Msg, _) -> + CMsgHdr = #{level => ip, type => tos, data => reliability}, + CMsgHdrs = [CMsgHdr], + MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs}, + %% socket:setopt(Sock, otp, debug, true), + Res = socket:sendmsg(Sock, MsgHdr, Timeout), + %% socket:setopt(Sock, otp, debug, false), + Res; +send(#handler{socket = Sock, type = stream, stimeout = Timeout}, Msg, _) -> + socket:send(Sock, Msg, Timeout); +send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout}, + Msg, Dest) -> + CMsgHdr = #{level => ip, type => tos, data => reliability}, + CMsgHdrs = [CMsgHdr], + MsgHdr = #{addr => Dest, + ctrl => CMsgHdrs, + iov => [Msg]}, + %% ok = socket:setopt(Sock, otp, debug, true), + Res = socket:sendmsg(Sock, MsgHdr, Timeout), + %% ok = socket:setopt(Sock, otp, debug, false), + Res; +send(#handler{socket = Sock, type = dgram, stimeout = Timeout}, Msg, Dest) -> + socket:sendto(Sock, Msg, Dest, Timeout). + +%% filler() -> +%% list_to_binary(lists:duplicate(2048, " FILLER ")). + + + +%% ========================================================================= + +f(F, A) -> + ?LIB:f(F, A). + +e(F) -> + e(F, []). +e(F, A) -> + ?LIB:e(F, A). + +i(F) -> + ?LIB:i(F). + +i(F, A) -> + ?LIB:i(F, A). + diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 28edf4889b..8b16e83707 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -24,11 +24,6 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # Target Specs # ---------------------------------------------------- -SOCKET_MODULES = \ - socket_lib \ - socket_server \ - socket_client - MODULES= \ rpc_SUITE \ pdict_SUITE \ @@ -95,9 +90,7 @@ MODULES= \ sendfile_SUITE \ standard_error_SUITE \ multi_load_SUITE \ - zzz_SUITE \ - socket_SUITE \ - $(SOCKET_MODULES) + zzz_SUITE APP_FILES = \ appinc.app \ @@ -134,7 +127,6 @@ ERL_COMPILE_FLAGS += EBIN = . -SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) TARGETS = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) @@ -160,8 +152,6 @@ docs: targets: $(TARGETS) -socket: $(SOCKET_TARGETS) - # ---------------------------------------------------- # Release Target diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl deleted file mode 100644 index 022e83a944..0000000000 --- a/lib/kernel/test/socket_SUITE.erl +++ /dev/null @@ -1,4186 +0,0 @@ -%% -%% %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% -%% - --module(socket_SUITE). - --include_lib("common_test/include/ct.hrl"). --include_lib("common_test/include/ct_event.hrl"). - -%% Suite exports --export([suite/0, all/0, groups/0]). --export([init_per_suite/1, end_per_suite/1, - init_per_testcase/2, end_per_testcase/2]). - -%% Test cases --export([ - %% API Basic - api_b_open_and_close_udp4/1, - api_b_open_and_close_tcp4/1, - api_b_sendto_and_recvfrom_udp4/1, - api_b_sendmsg_and_recvmsg_udp4/1, - api_b_send_and_recv_tcp4/1, - api_b_sendmsg_and_recvmsg_tcp4/1, - - %% API Options - api_opt_simple_otp_options/1, - api_opt_simple_otp_controlling_process/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 Closure - sc_cpe_socket_cleanup_tcp4/1, - sc_cpe_socket_cleanup_tcp6/1, - sc_cpe_socket_cleanup_udp4/1, - sc_cpe_socket_cleanup_udp6/1, - sc_lc_recv_response_tcp4/1, - sc_lc_recv_response_tcp6/1, - sc_lc_recvmsg_response_tcp4/1, - sc_lc_recvmsg_response_tcp6/1, - sc_lc_acceptor_response_tcp4/1, - sc_lc_acceptor_response_tcp6/1, - sc_rc_recv_response_tcp4/1, - sc_rc_recv_response_tcp6/1, - sc_rc_recvmsg_response_tcp4/1, - sc_rc_recvmsg_response_tcp6/1 - - %% Tickets - ]). - -%% Internal exports -%% -export([]). - - --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(BASIC_REQ, <<"hejsan">>). --define(BASIC_REP, <<"hoppsan">>). - --define(FAIL(R), exit(R)). - --define(SLEEP(T), receive after T -> ok end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -suite() -> - [{ct_hooks,[ts_install_cth]}, - {timetrap,{minutes,1}}]. - -all() -> - [ - {group, api}, - {group, socket_closure} - %% {group, tickets} - ]. - -groups() -> - [{api, [], api_cases()}, - {api_basic, [], api_basic_cases()}, - {api_options, [], api_options_cases()}, - {api_op_with_timeout, [], api_op_with_timeout_cases()}, - {socket_closure, [], socket_closure_cases()}, - {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, - {sc_local_close, [], sc_lc_cases()}, - {sc_remote_close, [], sc_rc_cases()} - %% {tickets, [], ticket_cases()} - ]. - -api_cases() -> - [ - {group, api_basic}, - {group, api_options}, - {group, api_op_with_timeout} - ]. - -api_basic_cases() -> - [ - api_b_open_and_close_udp4, - api_b_open_and_close_tcp4, - api_b_sendto_and_recvfrom_udp4, - api_b_sendmsg_and_recvmsg_udp4, - api_b_send_and_recv_tcp4, - api_b_sendmsg_and_recvmsg_tcp4 - ]. - -api_options_cases() -> - [ - api_opt_simple_otp_options, - api_opt_simple_otp_controlling_process - ]. - -api_op_with_timeout_cases() -> - [ - api_to_connect_tcp4, - api_to_connect_tcp6, - api_to_accept_tcp4, - api_to_accept_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 - ]. - -%% These cases tests what happens when the socket is closed, locally or -%% remotely. -socket_closure_cases() -> - [ - {group, sc_ctrl_proc_exit}, - {group, sc_local_close}, - {group, sc_remote_close} - ]. - -%% These cases are all about socket cleanup after the controlling process -%% exits *without* calling socket:close/1. -sc_cp_exit_cases() -> - [ - sc_cpe_socket_cleanup_tcp4, - sc_cpe_socket_cleanup_tcp6, - sc_cpe_socket_cleanup_udp4, - sc_cpe_socket_cleanup_udp6 - ]. - -%% 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_recvmsg_response_tcp4, - sc_lc_recvmsg_response_tcp6, - - sc_lc_acceptor_response_tcp4, - sc_lc_acceptor_response_tcp6 - ]. - -%% 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_recvmsg_response_tcp4, - sc_rc_recvmsg_response_tcp6 - ]. - - -%% ticket_cases() -> -%% []. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_per_suite(Config) -> - Config. - -end_per_suite(_) -> - ok. - -init_per_testcase(_TC, Config) -> - Config. - -end_per_testcase(_TC, Config) -> - Config. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% API BASIC %% -%% %% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% 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) -> - 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 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) -> - tc_try(api_b_open_and_close_tcp4, - fun() -> - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - 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, _} = 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) -> - Res = socket:getopt(Sock, socket, protocol), - {ok, {State, Res}} - end}, - #{desc => "validate protocol", - cmd => fun({#{protocol := Protocol} = State, {ok, Protocol}}) -> - {ok, State}; - ({#{protocol := ExpProtocol}, {ok, Protocol}}) -> - {error, {unexpected_type, ExpProtocol, Protocol}}; - ({_, {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, normal}; - ({_, {error, _} = ERROR}) -> - ERROR - end}], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_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) -> - 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, - 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) -> - tc_try(api_b_sendmsg_and_recvmsg_udp4, - fun() -> - Send = fun(Sock, Data, Dest) -> - %% CMsgHdr = #{level => ip, - %% type => tos, - %% data => reliability}, - %% CMsgHdrs = [CMsgHdr], - MsgHdr = #{addr => Dest, - %% ctrl => CMsgHdrs, - iov => [Data]}, - socket:sendmsg(Sock, MsgHdr) - end, - Recv = fun(Sock) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Data]}} -> - {ok, {Source, Data}}; - {error, _} = ERROR -> - ERROR - end - end, - InitState = #{domain => inet, - 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 := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "open src socket", - cmd => fun(#{domain := Domain} = State) -> - Sock = sock_open(Domain, dgram, udp), - SASrc = sock_sockname(Sock), - {ok, State#{sock_src => Sock, sa_src => SASrc}} - end}, - #{desc => "bind src", - cmd => fun(#{sock_src := Sock, lsa := LSA}) -> - sock_bind(Sock, LSA), - ok - end}, - #{desc => "sockname src socket", - cmd => fun(#{sock_src := Sock} = State) -> - SASrc = sock_sockname(Sock), - %% ei("src sockaddr: ~p", [SASrc]), - {ok, State#{sa_src => SASrc}} - end}, - #{desc => "open dst socket", - cmd => fun(#{domain := Domain} = State) -> - Sock = sock_open(Domain, dgram, udp), - {ok, State#{sock_dst => Sock}} - end}, - #{desc => "bind dst", - cmd => fun(#{sock_dst := Sock, lsa := LSA}) -> - sock_bind(Sock, LSA), - ok - end}, - #{desc => "sockname dst socket", - cmd => fun(#{sock_dst := Sock} = State) -> - SADst = sock_sockname(Sock), - %% ei("dst sockaddr: ~p", [SADst]), - {ok, State#{sa_dst => SADst}} - end}, - #{desc => "send req (to dst)", - cmd => fun(#{sock_src := Sock, sa_dst := Dst, send := Send}) -> - ok = Send(Sock, ?BASIC_REQ, Dst) - end}, - #{desc => "recv req (from src)", - cmd => fun(#{sock_dst := Sock, sa_src := Src, recv := Recv}) -> - {ok, {Src, ?BASIC_REQ}} = Recv(Sock), - ok - end}, - #{desc => "send rep (to src)", - cmd => fun(#{sock_dst := Sock, sa_src := Src, send := Send}) -> - ok = Send(Sock, ?BASIC_REP, Src) - end}, - #{desc => "recv rep (from dst)", - cmd => fun(#{sock_src := Sock, sa_dst := Dst, recv := Recv}) -> - {ok, {Dst, ?BASIC_REP}} = Recv(Sock), - ok - end}, - #{desc => "close src socket", - cmd => fun(#{sock_src := Sock}) -> - ok = socket:close(Sock) - end}, - #{desc => "close dst socket", - cmd => fun(#{sock_dst := Sock}) -> - ok = socket:close(Sock), - {ok, normal} - end} - ], - Evaluator = evaluator_start("tester", Seq, InitState), - ok = await_evaluator_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) -> - 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, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_tcp(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) -> - 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, - send => Send, - recv => Recv}, - ok = api_b_send_and_recv_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_b_send_and_recv_tcp(InitState) -> - process_flag(trap_exit, true), - ServerSeq = - [ - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {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 socket: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 server port", - cmd => fun(#{parent := Parent, lport := Port}) -> - ei("announcing port to parent (~p)", [Parent]), - Parent ! {server_port, self(), Port}, - ok - end}, - #{desc => "await connection", - cmd => fun(#{lsock := LSock} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - ei("accepted: ~n ~p", [Sock]), - {ok, State#{tsock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "await request", - cmd => fun(#{tsock := Sock, recv := Recv}) -> - case Recv(Sock) of - {ok, ?BASIC_REQ} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "send reply", - cmd => fun(#{tsock := Sock, send := Send}) -> - Send(Sock, ?BASIC_REP) - end}, - #{desc => "sleep some", - cmd => fun(_) -> - ?SLEEP(1000), - ok - end}, - #{desc => "close traffic socket", - cmd => fun(#{tsock := Sock}) -> - socket:close(Sock) - end}, - #{desc => "close listen socket", - cmd => fun(#{lsock := Sock}) -> - socket:close(Sock) - end}, - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - ClientSeq = - [ - #{desc => "which server (local) address", - cmd => fun(#{domain := Domain, server_port := Port} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, - addr => LAddr}, - SSA = LSA#{port => Port}, - {ok, State#{lsa => LSA, ssa => 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, lsa := LSA} = State) -> - case socket:bind(Sock, LSA) of - {ok, Port} -> - {ok, State#{port => Port}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "connect to server", - cmd => fun(#{sock := Sock, ssa := SSA}) -> - socket:connect(Sock, SSA) - end}, - #{desc => "send request (to server)", - cmd => fun(#{sock := Sock, send := Send}) -> - Send(Sock, ?BASIC_REQ) - end}, - #{desc => "recv reply (from server)", - cmd => fun(#{sock := Sock, recv := Recv}) -> - {ok, ?BASIC_REP} = Recv(Sock), - ok - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock}) -> - socket:close(Sock) - end}, - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start server evaluator"), - Server = evaluator_start("server", ServerSeq, InitState), - p("await server (~p) port", [Server]), - SPort = receive - {server_port, Server, Port} -> - Port - end, - p("start client evaluator"), - Client = evaluator_start("client", ClientSeq, InitState#{server_port => SPort}), - p("await evaluator(s)"), - ok = await_evaluator_finish([Server, Client]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% 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) -> - 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 => "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, InvalidRcvBuf} -> - {error, {invalid, InvalidRcvBuf}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "set (new) rcvbuf", - cmd => fun(#{sock := Sock, rcvbuf := OldRcvBuf} = State) -> - 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} - ], - - p("Run test for stream/tcp socket"), - InitState1 = #{domain => inet, type => stream, protocol => tcp}, - Tester1 = evaluator_start("tcp-tester", Seq, InitState1), - p("await evaluator 1"), - ok = await_evaluator_finish([Tester1]), - - p("Run test for dgram/udp socket"), - InitState2 = #{domain => inet, type => dgram, protocol => udp}, - Tester2 = evaluator_start("udp-tester", Seq, InitState2), - p("await evaluator 2"), - ok = await_evaluator_finish([Tester2]). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% 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) -> - 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) -> - receive - {start, Tester, Socket} -> - {ok, State#{tester => Tester, - sock => Socket}} - end - end}, - #{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 (1)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester} = _State) -> - receive - {continue, Tester} -> - ok - end - 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 (2)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await termination", - cmd => fun(#{tester := Tester} = State) -> - receive - {terminate, Tester} -> - State1 = maps:remove(tester, State), - State2 = maps:remove(sock, State1), - {ok, State2} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - 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 => "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) -> - Client ! {start, self(), Sock}, - ok - end}, - #{desc => "await (client) ready (1)", - cmd => fun(#{client := Client} = _State) -> - receive - {ready, Client} -> - ok - end - 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", - cmd => fun(#{client := Client} = _State) -> - Client ! {continue, self()}, - ok - end}, - #{desc => "await (client) ready (2)", - cmd => fun(#{client := Client} = _State) -> - receive - {ready, Client} -> - ok - end - 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 => "monitor client", - cmd => fun(#{client := Client} = State) -> - MRef = erlang:monitor(process, Client), - {ok, State#{client_mref => MRef}} - end}, - #{desc => "order (client) terminate", - cmd => fun(#{client := Client} = _State) -> - Client ! {terminate, self()}, - ok - end}, - #{desc => "await (client) down", - cmd => fun(#{client := Client} = State) -> - receive - {'DOWN', _, process, Client, _} -> - {ok, maps:remove(client, State)} - end - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("Run test for stream/tcp socket"), - ClientInitState1 = #{}, - Client1 = evaluator_start("tcp-client", ClientSeq, ClientInitState1), - TesterInitState1 = #{domain => inet, - type => stream, - protocol => tcp, - client => Client1}, - Tester1 = evaluator_start("tcp-tester", TesterSeq, TesterInitState1), - p("await stream/tcp evaluator"), - ok = await_evaluator_finish([Tester1, Client1]), - - p("Run test for dgram/udp socket"), - ClientInitState2 = #{}, - Client2 = evaluator_start("udp-client", ClientSeq, ClientInitState2), - TesterInitState2 = #{domain => inet, - type => dgram, - protocol => udp, - client => Client2}, - Tester2 = evaluator_start("udp-tester", TesterSeq, TesterInitState2), - p("await dgram/udp evaluator"), - ok = await_evaluator_finish([Tester2, Client2]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% 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) -> - tc_try(api_to_connect_tcp4, - fun() -> - InitState = #{domain => inet, timeout => 5000}, - ok = api_to_connect_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% 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) -> - tc_try(api_to_connect_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, timeout => 5000}, - 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) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {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 socket: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 => "monitor server", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester, lport := Port}) -> - ei("announcing ready to tester (~p)", [Tester]), - Tester ! {ready, self(), Port}, - ok - end}, - #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create socket 1", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock1 => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "create socket 2", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock2 => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "create socket 3", - cmd => fun(#{domain := Domain} = State) -> - case socket:open(Domain, stream, tcp) of - {ok, Sock} -> - {ok, State#{sock3 => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket 1 to local address", - cmd => fun(#{sock1 := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket 2 to local address", - cmd => fun(#{sock2 := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "bind socket 3 to local address", - cmd => fun(#{sock3 := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - - %% *** Synchronize with the server *** - #{desc => "order (server) start", - cmd => fun(#{server := Server}) -> - Server ! {start, self()}, - ok - end}, - #{desc => "await ready (from server)", - cmd => fun(#{server := Server, lsa := LSA} = State) -> - receive - {ready, Server, Port} -> - {ok, State#{ssa => LSA#{port => Port}}} - end - end}, - - %% *** Connect sequence *** - #{desc => "order (server) start", - cmd => fun(#{sock1 := Sock1, - sock2 := Sock2, - sock3 := Sock3, - ssa := SSA, - timeout := To}) -> - Socks = [Sock1, Sock2, Sock3], - api_to_connect_tcp_await_timeout(Socks, To, SSA) - end}, - - %% *** Terminate server *** - #{desc => "monitor server", - cmd => fun(#{server := Server} = State) -> - MRef = erlang:monitor(process, Server), - {ok, State#{server_mref => MRef}} - end}, - #{desc => "order (server) terminate", - cmd => fun(#{server := Server} = _State) -> - Server ! {terminate, self()}, - ok - end}, - #{desc => "await (server) down", - cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, _} -> - State1 = maps:remove(server, State), - State2 = maps:remove(ssa, State1), - {ok, State2} - end - end}, - - %% *** Close our sockets *** - #{desc => "close socket 3", - cmd => fun(#{sock3 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock3, State)} - - end}, - #{desc => "close socket 2", - cmd => fun(#{sock2 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock2, State)} - - end}, - #{desc => "close socket 1", - cmd => fun(#{sock1 := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock1, State)} - - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("create server evaluator"), - ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), - - p("create tester evaluator"), - TesterInitState = InitState#{server => Server}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator(s)"), - ok = await_evaluator_finish([Server, Tester]). - - -api_to_connect_tcp_await_timeout(Socks, To, ServerSA) -> - api_to_connect_tcp_await_timeout(Socks, To, ServerSA, 1). - -api_to_connect_tcp_await_timeout([], _To, _ServerSA, _ID) -> - ?FAIL(unexpected_success); -api_to_connect_tcp_await_timeout([Sock|Socks], To, ServerSA, ID) -> - ei("~w: try connect", [ID]), - Start = t(), - case socket:connect(Sock, ServerSA, To) of - {error, timeout} -> - ei("expected timeout (~w)", [ID]), - Stop = t(), - TDiff = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end; - {error, econnreset = Reason} -> - ei("failed connecting: ~p - giving up", [Reason]), - ok; - {error, Reason} -> - ee("failed connecting: ~p", [Reason]), - ?FAIL({connect, Reason}); - ok -> - ei("unexpected success (~w) - try next", [ID]), - api_to_connect_tcp_await_timeout(Socks, To, ServerSA, ID+1) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% 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) -> - 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) -> - tc_try(api_to_accept_tcp4, - fun() -> - not_yet_implemented(), - 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) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {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 socket: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 = tdiff(Start, Stop), - 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 *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("create tester evaluator"), - Tester = evaluator_start("tester", TesterSeq, InitState), - - p("await evaluator"), - ok = await_evaluator_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) -> - 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) -> - tc_try(api_to_maccept_tcp4, - fun() -> - not_yet_implemented(), - 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) -> - receive - {start, Tester} -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester => Tester, - tester_mref => MRef}} - end - end}, - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {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 socket: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", - cmd => fun(#{lsock := LSock, tester := Tester}) -> - ei("announcing port to tester (~p)", [Tester]), - Tester ! {ready, self(), LSock}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end - 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 = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester}) -> - ei("announcing port to tester (~p)", [Tester]), - Tester ! {ready, self()}, - ok - end}, - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - ok - end - 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 *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - - SecAcceptorSeq = - [ - %% *** Init part *** - #{desc => "await start", - cmd => fun(State) -> - receive - {start, Tester, LSock} -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester => Tester, - lsock => LSock, - tester_mref => MRef}} - end - end}, - #{desc => "announce ready (1)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end - 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 = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - #{desc => "announce ready (2)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - ok - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - - 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) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await prim-acceptor ready (1)", - cmd => fun(#{prim_acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, prim_acceptor}}; - {ready, Pid, LSock} -> - {ok, State#{lsock => LSock}} - end - end}, - - %% Start sec-acceptor-1 - #{desc => "start sec-acceptor 1", - cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> - Pid ! {start, self(), LSock}, - ok - end}, - #{desc => "await sec-acceptor 1 ready (1)", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_1}}; - {ready, Pid} -> - ok - end - end}, - - %% Start sec-acceptor-2 - #{desc => "start sec-acceptor 2", - cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> - Pid ! {start, self(), LSock}, - ok - end}, - #{desc => "await sec-acceptor 2 ready (1)", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_2}}; - {ready, Pid} -> - ok - end - end}, - - %% Activate the acceptor(s) - #{desc => "active prim-acceptor", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - #{desc => "active sec-acceptor 1", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - #{desc => "active sec-acceptor 2", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - - %% Await acceptor(s) completions - #{desc => "await prim-acceptor ready (2)", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding prim-acceptor ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, prim_acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await sec-acceptor 1 ready (2)", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 1 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_1}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await sec-acceptor 2 ready (2)", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding sec-acceptor 2 ~p:" - "~n ~p", [Reason]), - {error, {unexpected_exit, sec_acceptor_2}}; - {ready, Pid} -> - ok - end - end}, - - - %% Terminate the acceptor(s) - #{desc => "order prim-acceptor to terminate", - cmd => fun(#{prim_acceptor := Pid} = _State) -> - ei("send terminate command to prim-acceptor (~p)", [Pid]), - Pid ! {terminate, self()}, - ok - end}, - #{desc => "order sec-acceptor 1 to terminate", - cmd => fun(#{sec_acceptor1 := Pid} = _State) -> - ei("send terminate command to sec-acceptor-1 (~p)", [Pid]), - Pid ! {terminate, self()}, - ok - end}, - #{desc => "order sec-acceptor 2 to terminate", - cmd => fun(#{sec_acceptor2 := Pid} = _State) -> - ei("send terminate command to sec-acceptor-2 (~p)", [Pid]), - Pid ! {terminate, self()}, - ok - end}, - - %% Await acceptor(s) termination - #{desc => "await prim-acceptor termination", - cmd => fun(#{prim_acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(prim_acceptor, State), - {ok, State1} - end - end}, - #{desc => "await sec-acceptor 1 termination", - cmd => fun(#{sec_acceptor1 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(sec_acceptor1, State), - {ok, State1} - end - end}, - #{desc => "await sec-acceptor 2 termination", - cmd => fun(#{sec_acceptor2 := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - State1 = maps:remove(sec_acceptor2, State), - {ok, State1} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("create prim-acceptor evaluator"), - PrimAInitState = InitState, - PrimAcceptor = evaluator_start("prim-acceptor", - PrimAcceptorSeq, PrimAInitState), - - p("create prim-acceptor 1 evaluator"), - SecAInitState1 = maps:remove(domain, InitState), - SecAcceptor1 = evaluator_start("sec-acceptor-1", - SecAcceptorSeq, SecAInitState1), - - p("create prim-acceptor 2 evaluator"), - SecAInitState2 = SecAInitState1, - SecAcceptor2 = evaluator_start("sec-acceptor-2", - SecAcceptorSeq, SecAInitState2), - - p("create tester evaluator"), - TesterInitState = #{prim_acceptor => PrimAcceptor, - sec_acceptor1 => SecAcceptor1, - sec_acceptor2 => SecAcceptor2}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator(s)"), - ok = await_evaluator_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() -> - 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() -> - 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() -> - 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() -> - 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) -> - tc_try(api_to_recv_tcp4, - fun() -> - Recv = fun(Sock, To) -> socket:recv(Sock, 0, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - 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) -> - tc_try(api_to_recv_tcp6, - fun() -> - not_yet_implemented(), - case socket:supports(ipv6) of - true -> - Recv = fun(Sock, To) -> - socket:recv(Sock, 0, To) - end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - 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) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {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 socket: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 => "monitor tester", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester, lport := Port}) -> - Tester ! {ready, self(), Port}, - ok - end}, - #{desc => "await continue", - cmd => fun(#{tester := Tester}) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end - end}, - - %% *** The actual test *** - #{desc => "await accept", - cmd => fun(#{lsock := LSock} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - %% ok = socket:setopt(Sock, otp, debug, true), - {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 = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - #{desc => "announce ready (recv timeout success)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% *** Termination *** - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - %% #{desc => "sleep some (before traffic close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "close (traffic) socket", - cmd => fun(#{sock := Sock} = State) -> - %% ok = socket:setopt(Sock, otp, debug, true), - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - %% #{desc => "sleep some (before listen close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - #{desc => "close (listen) socket", - cmd => fun(#{lsock := LSock} = State) -> - sock_close(LSock), - {ok, maps:remove(lsock, State)} - end}, - %% #{desc => "sleep some (after listen close)", - %% cmd => fun(_) -> - %% ?SLEEP(1000), - %% ok - %% end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - ClientSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester, Port} when is_pid(Tester) -> - {ok, State#{tester => Tester, - server_port => Port}} - end - end}, - - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain, server_port := Port} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, - addr => LAddr}, - SSA = LSA#{port => Port}, - {ok, State#{lsa => LSA, ssa => 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, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = State) -> - MRef = erlang:monitor(process, Tester), - {ok, State#{tester_mref => MRef}} - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% *** The actual test *** - #{desc => "await continue (with connect)", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester} -> - ok - end - end}, - #{desc => "connect", - cmd => fun(#{sock := Sock, ssa := SSA}) -> - sock_connect(Sock, SSA), - ok - end}, - - %% *** Termination *** - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - {error, {unexpected_exit, tester, Reason}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "monitor server", - cmd => fun(#{server := Server} = State) -> - MRef = erlang:monitor(process, Server), - {ok, State#{server_mref => MRef}} - end}, - #{desc => "monitor client", - cmd => fun(#{client := Client} = State) -> - MRef = erlang:monitor(process, Client), - {ok, State#{client_mref => MRef}} - end}, - - %% *** Activate server *** - #{desc => "start server", - cmd => fun(#{server := Server} = _State) -> - Server ! {start, self()}, - ok - end}, - #{desc => "await server ready (init)", - cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, Reason} -> - {error, {unexpected_exit, server, Reason}}; - {ready, Server, Port} -> - {ok, State#{server_port => Port}} - end - end}, - #{desc => "order server to continue (with accept)", - cmd => fun(#{server := Server} = _State) -> - Server ! {continue, self()}, - ok - end}, - - %% *** Activate client *** - #{desc => "start client", - cmd => fun(#{client := Client, server_port := Port} = _State) -> - Client ! {start, self(), Port}, - ok - end}, - #{desc => "await client ready", - cmd => fun(#{client := Client} = _State) -> - receive - {'DOWN', _, process, Client, Reason} -> - {error, {unexpected_exit, client, Reason}}; - {ready, Client} -> - ok - end - end}, - - %% *** The actual test *** - #{desc => "order client to continue (with connect)", - cmd => fun(#{client := Client} = _State) -> - Client ! {continue, self()}, - ok - end}, - #{desc => "await server ready (accept/recv)", - cmd => fun(#{server := Server} = _State) -> - receive - {'DOWN', _, process, Server, Reason} -> - {error, {unexpected_exit, server, Reason}}; - {ready, Server} -> - ok - end - end}, - - %% *** Termination *** - #{desc => "order client to terminate", - cmd => fun(#{client := Client} = _State) -> - Client ! {terminate, self()}, - ok - end}, - #{desc => "await client termination", - cmd => fun(#{client := Client} = State) -> - receive - {'DOWN', _, process, Client, _Reason} -> - State1 = maps:remove(client, State), - State2 = maps:remove(client_mref, State1), - {ok, State2} - end - end}, - #{desc => "order server to terminate", - cmd => fun(#{server := Server} = _State) -> - Server ! {terminate, self()}, - ok - end}, - #{desc => "await server termination", - cmd => fun(#{server := Server} = State) -> - receive - {'DOWN', _, process, Server, _Reason} -> - State1 = maps:remove(server, State), - State2 = maps:remove(server_mref, State1), - State3 = maps:remove(server_port, State2), - {ok, State3} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - - p("start server evaluator"), - ServerInitState = InitState, - Server = evaluator_start("server", ServerSeq, ServerInitState), - - p("start client evaluator"), - ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), - - p("start tester evaluator"), - TesterInitState = #{server => Server, client => Client}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator(s)"), - ok = await_evaluator_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) -> - tc_try(api_to_recvfrom_udp4, - fun() -> - Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - 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) -> - tc_try(api_to_recvfrom_udp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock, To) -> socket:recvfrom(Sock, 0, To) end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_udp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -api_to_receive_udp(InitState) -> - TesterSeq = - [ - %% *** Init part *** - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {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 socket: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 = tdiff(Start, Stop), - if - (TDiff >= To) -> - ok; - true -> - {error, {unexpected_timeout, TDiff, To}} - end - end}, - - %% *** Termination *** - #{desc => "close socket", - cmd => fun(#{sock := Sock} = _State) -> - sock_close(Sock), - ok - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start tester evaluator"), - Tester = evaluator_start("tester", TesterSeq, InitState), - - p("await evaluator"), - ok = await_evaluator_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) -> - tc_try(api_to_recvmsg_udp4, - fun() -> - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - 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) -> - tc_try(api_to_recvmsg_udp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - 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) -> - tc_try(api_to_recvmsg_tcp4, - fun() -> - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet, - recv => Recv, - timeout => 5000}, - 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) -> - tc_try(api_to_recvmsg_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, - InitState = #{domain => inet6, - recv => Recv, - timeout => 5000}, - ok = api_to_receive_tcp(InitState) - end). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% %% -%% 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) -> - tc_try(sc_cpe_socket_cleanup_tcp4, - fun() -> - %% not_yet_implemented(), - 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) -> - tc_try(sc_cpe_socket_cleanup_tcp6, - fun() -> - not_yet_implemented(), - 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 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) -> - 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) -> - tc_try(sc_cpe_socket_cleanup_udp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet6, - type => dgram, - protocol => udp}, - 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) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% *** Init part *** - #{desc => "monitor tester", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok - end}, - #{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", - cmd => fun(#{tester := Tester, sock := Sock} = _State) -> - Tester ! {ready, self(), 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) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - %% #{desc => "enable (otp) debug", - %% cmd => fun(#{sock := Sock} = _State) -> - %% ok = socket:setopt(Sock, otp, debug, true) - %% end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - 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) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await (owner) ready", - cmd => fun(#{owner := Owner} = State) -> - receive - {'DOWN', _, process, Owner, Reason} -> - ee("Unexpected DOWN regarding owner ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, owner}}; - {ready, Owner, Sock} -> - {ok, State#{sock => Sock}} - end - end}, - #{desc => "verify owner as controlling-process", - cmd => fun(#{owner := Owner, sock := Sock} = _State) -> - case socket:getopt(Sock, otp, controlling_process) of - {ok, Owner} -> - ok; - {ok, Other} -> - {error, {unexpected_owner, Other}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "order (owner) terminate", - cmd => fun(#{owner := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await (owner) termination", - cmd => fun(#{owner := Owner} = _State) -> - receive - {'DOWN', _, process, Owner, _} -> - ok - end - end}, - #{desc => "verify no socket (closed)", - cmd => fun(#{owner := Owner, sock := Sock} = _State) -> - case socket:getopt(Sock, otp, controlling_process) of - {ok, Pid} -> - {error, {unexpected_success, Owner, Pid}}; - {error, closed} -> - ok; - {error, Reason} -> - ei("expected failure: ~p", [Reason]), - {error, {unexpected_failure, Reason}} - end - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start (socket) owner evaluator"), - Owner = evaluator_start("owner", OwnerSeq, InitState), - - p("start tester evaluator"), - TesterInitState = #{owner => Owner}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator"), - ok = await_evaluator_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. -%% -%% -%% -%% 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. -%% -%% - -sc_lc_recv_response_tcp4(suite) -> - []; -sc_lc_recv_response_tcp4(doc) -> - []; -sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_lc_recv_response_tcp4, - fun() -> - %% not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet, - type => stream, - 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) -> - tc_try(sc_lc_recv_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_lc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_lc_receive_response_tcp(InitState) -> - %% This is the server that accepts connections. - %% But it is also suppose to close the connection socket, - %% and trigger the read failure for the handler process. - AcceptorSeq = - [ - %% *** Wait for start order part *** - #{desc => "await start (from tester)", - cmd => fun(State) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - 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) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{desc => "create (listen) socket", - cmd => fun(#{domain := Domain, - type := Type, - protocol := Proto} = State) -> - case socket:open(Domain, Type, 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} -> - {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}) -> - Tester ! {ready, self(), Port}, - ok - end}, - - %% The actual test - #{desc => "await continue (connection)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester, Handler} -> - {ok, State#{handler => Handler}} - end - end}, - #{desc => "await connection", - cmd => fun(#{lsock := LSock} = State) -> - case socket:accept(LSock) of - {ok, Sock} -> - ei("connection accepted"), - {ok, State#{csock => Sock}}; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "transfer new connection to handler", - cmd => fun(#{handler := Handler, csock := Sock}) -> - ok = socket:setopt(Sock, - otp, controlling_process, - Handler), - Handler ! {connection, Sock}, - ok - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "await continue (close)", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {continue, Tester} -> - ok - end - end}, - %% #{desc => "enable debug", - %% cmd => fun(#{csock := Sock}) -> - %% socket:setopt(Sock, otp, debug, true) - %% end}, - #{desc => "close (the connection) socket", - cmd => fun(#{csock := Sock}) -> - socket:close(Sock) - end}, - - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - #{desc => "socket cleanup", - cmd => fun(#{lsock := Sock} = State) -> - ok = socket:close(Sock), - State1 = maps:remove(csock, State), - State2 = maps:remove(lsock, State1), - State3 = maps:remove(lport, State2), - {ok, State3} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - %% 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) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - #{desc => "monitor server", - cmd => fun(#{tester := Tester} = _State) -> - _MRef = erlang:monitor(process, Tester), - ok - end}, - #{desc => "announce ready (init)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - - %% The actual test - #{desc => "await connection socket", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {connection, Sock} -> - {ok, State#{sock => Sock}} - end - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - %% #{desc => "enable debug", - %% cmd => fun(#{sock := Sock}) -> - %% socket:setopt(Sock, otp, debug, true) - %% end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "attempt recv", - cmd => fun(#{sock := Sock, recv := Recv} = State) -> - case Recv(Sock) of - {ok, _Data} -> - ee("Unexpected data received"), - {error, unexpected_data}; - {error, closed} -> - State1 = maps:remove(sock, State), - {ok, State1}; - {error, Reason} = ERROR -> - ee("Unexpected read faulure: " - "~n ~p", [Reason]), - ERROR - end - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "announce ready (close)", - cmd => fun(#{tester := Tester}) -> - Tester ! {ready, self()}, - ok - end}, - #{desc => "sleep some", - cmd => fun(_) -> - ?SLEEP(1000), - ok - end}, - %% #{desc => "monitored-by", - %% cmd => fun(_) -> - %% {_, Mons} = process_info(self(), monitored_by), - %% ei("Monitored By: ~p", [Mons]), - %% ok - %% end}, - #{desc => "await terminate", - cmd => fun(#{tester := Tester} = _State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - ok - end - end}, - - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - %% 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) -> - receive - {start, Tester} when is_pid(Tester) -> - {ok, State#{tester => Tester}} - end - end}, - - %% Init - #{desc => "which local address", - cmd => fun(#{domain := Domain} = State) -> - LAddr = which_local_addr(Domain), - LSA = #{family => Domain, addr => LAddr}, - {ok, State#{lsa => LSA}} - end}, - #{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 => "bind socket to local address", - cmd => fun(#{sock := Sock, lsa := LSA} = _State) -> - case socket:bind(Sock, LSA) of - {ok, _} -> - ok; - {error, _} = ERROR -> - ERROR - end - end}, - #{desc => "announce ready", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% The actual test - #{desc => "await continue", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester, Reason}}; - {continue, Tester, Port} -> - {ok, State#{lport => Port}} - end - end}, - #{desc => "connect to server", - cmd => fun(#{sock := Sock, lsa := LSA, lport := LPort}) -> - socket:connect(Sock, LSA#{port => LPort}) - end}, - #{desc => "announce ready (connection)", - cmd => fun(#{tester := Tester} = _State) -> - Tester ! {ready, self()}, - ok - end}, - - %% Cleaning up - #{desc => "await terminate (from tester)", - cmd => fun(#{tester := Tester} = State) -> - receive - {'DOWN', _, process, Tester, Reason} -> - ee("Unexpected DOWN regarding tester ~p: " - "~n ~p", [Tester, Reason]), - {error, {unexpected_exit, tester}}; - {terminate, Tester} -> - {ok, maps:remove(tester, State)} - end - end}, - #{desc => "close socket", - cmd => fun(#{sock := Sock} = State) -> - sock_close(Sock), - {ok, maps:remove(sock, State)} - end}, - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - TesterSeq = - [ - %% *** Init part *** - #{desc => "monitor acceptor", - cmd => fun(#{acceptor := Pid} = _State) -> - _MRef = erlang:monitor(process, Pid), - ok - end}, - #{desc => "monitor handler", - cmd => fun(#{handler := 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) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await acceptor ready (init)", - cmd => fun(#{acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding acceptor ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid, Port} -> - {ok, State#{lport => Port}} - end - end}, - - %% Start the handler - #{desc => "order handler start", - cmd => fun(#{handler := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await handler ready (init)", - cmd => fun(#{handler := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - - %% Start the client - #{desc => "order client start", - cmd => fun(#{client := Pid} = _State) -> - Pid ! {start, self()}, - ok - end}, - #{desc => "await client ready (init)", - cmd => fun(#{client := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding cient ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - - %% The actual test - #{desc => "order acceptor to continue", - cmd => fun(#{acceptor := Pid, handler := Handler} = _State) -> - Pid ! {continue, self(), Handler}, - ok - end}, - #{desc => "order client to continue", - cmd => fun(#{client := Pid, lport := Port} = _State) -> - Pid ! {continue, self(), Port}, - ok - end}, - #{desc => "await acceptor ready (connection)", - cmd => fun(#{acceptor := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding acceptor ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await client ready (connection)", - cmd => fun(#{client := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding client ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "await handler ready (connection)", - cmd => fun(#{handler := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " - "~n ~p", [Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - #{desc => "sleep some", - cmd => fun(_State) -> - ?SLEEP(1000), - ok - end}, - #{desc => "order acceptor to continue (close)", - cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {continue, self()}, - ok - end}, - #{desc => "await handler ready (close)", - cmd => fun(#{handler := Pid} = _State) -> - receive - {'DOWN', _, process, Pid, Reason} -> - ee("Unexpected DOWN regarding handler ~p: " - "~n ~p", [Pid, Reason]), - {error, {unexpected_exit, acceptor}}; - {ready, Pid} -> - ok - end - end}, - - %% Terminations - #{desc => "order handler to terminate", - cmd => fun(#{handler := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await handler termination", - cmd => fun(#{handler := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(handler, State)} - end - end}, - #{desc => "order client to terminate", - cmd => fun(#{client := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await client termination", - cmd => fun(#{client := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(client, State)} - end - end}, - #{desc => "order acceptor to terminate", - cmd => fun(#{acceptor := Pid} = _State) -> - Pid ! {terminate, self()}, - ok - end}, - #{desc => "await acceptor termination", - cmd => fun(#{acceptor := Pid} = State) -> - receive - {'DOWN', _, process, Pid, _} -> - {ok, maps:remove(acceptor, State)} - end - end}, - - - %% *** We are done *** - #{desc => "finish", - cmd => fun(_) -> - {ok, normal} - end} - ], - - p("start acceptor evaluator"), - AccInitState = InitState, - Acceptor = evaluator_start("acceptor", AcceptorSeq, AccInitState), - - p("start handler evaluator"), - HandlerInitState = #{recv => maps:get(recv, InitState)}, - Handler = evaluator_start("handler", HandlerSeq, HandlerInitState), - - p("start client evaluator"), - ClientInitState = InitState, - Client = evaluator_start("client", ClientSeq, ClientInitState), - - p("start tester evaluator"), - TesterInitState = #{acceptor => Acceptor, - handler => Handler, - client => Client}, - Tester = evaluator_start("tester", TesterSeq, TesterInitState), - - p("await evaluator"), - ok = await_evaluator_finish([Acceptor, Handler, Client, 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. - -sc_rc_recv_response_tcp4(suite) -> - []; -sc_rc_recv_response_tcp4(doc) -> - []; -sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> - tc_try(sc_rc_recv_response_tcp4, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet, - type => stream, - 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) -> - tc_try(sc_rc_recv_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recv(Sock) end, - InitState = #{domain => inet6, - type => stream, - protocol => tcp, - recv => Recv}, - ok = sc_rc_receive_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_rc_receive_response_tcp(_InitState) -> - ok. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% 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) -> - tc_try(sc_lc_recvmsg_response_tcp4, - fun() -> - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet, - type => stream, - 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) -> - tc_try(sc_recvmsg_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet6, - type => stream, - 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 -%% 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) -> - tc_try(sc_rc_recvmsg_response_tcp4, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet, - type => stream, - 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) -> - tc_try(sc_rc_recvmsg_response_tcp6, - fun() -> - not_yet_implemented(), - Recv = fun(Sock) -> socket:recvmsg(Sock) end, - InitState = #{domain => inet6, - type => stream, - 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 -%% 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) -> - tc_try(sc_lc_acceptor_response_tcp4, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - 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) -> - tc_try(sc_lc_acceptor_response_tcp6, - fun() -> - not_yet_implemented(), - InitState = #{domain => inet, - type => stream, - protocol => tcp}, - ok = sc_lc_acceptor_response_tcp(InitState) - end). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -sc_lc_acceptor_response_tcp(_InitState) -> - ok. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% 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) -> - case inet:getifaddrs() of - {ok, IFL} -> - which_addr(Domain, IFL); - {error, Reason} -> - ?FAIL({inet, getifaddrs, Reason}) - end. - -which_addr(_Domain, []) -> - ?FAIL(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_Domain, []) -> - ?FAIL(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% An evaluator is a process that executes a command sequence. -%% A test case will consist of atleast one evaluator (one for -%% each actor). -%% The evaluator process *always* run locally. Which means that -%% it will act as a "proxy" for remote nodes in necessary. -%% When the command sequence has been processed, the final state -%% will be used as exit reason. -%% A successful command shall evaluate to ok | {ok, NewState} - --spec evaluator_start(Name, Seq, Init) -> {Pid, MRef} when - Name :: string(), - Seq :: [command()], - Init :: initial_evaluator_state(), - Pid :: pid(), - MRef :: reference(). - -evaluator_start(Name, Seq, Init) - when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> - Init2 = Init#{parent => self()}, - {Pid, _} = erlang:spawn_monitor(fun() -> evaluator_init(Name, Seq, Init2) end), - Pid. - -evaluator_init(Name, Seq, Init) -> - put(sname, Name), - evaluator_loop(1, Seq, Init). - -evaluator_loop(_ID, [], FinalState) -> - exit(FinalState); -evaluator_loop(ID, [#{desc := Desc, - cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> - ei("evaluate command ~2w: ~s", [ID, Desc]), - try Cmd(State) of - ok -> - evaluator_loop(ID + 1, Cmds, State); - {ok, NewState} -> - evaluator_loop(ID + 1, Cmds, NewState); - {error, Reason} -> - ee("command ~w failed: " - "~n Reason: ~p", [ID, Reason]), - exit({command_failed, ID, Reason, State}) - catch - C:E:S -> - ee("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. - -await_evaluator_finish(Evs) -> - await_evaluator_finish(Evs, []). - -await_evaluator_finish([], []) -> - ok; -await_evaluator_finish([], Fails) -> - Fails; -await_evaluator_finish(Evs, Fails) -> - receive - {'DOWN', _MRef, process, Pid, normal} -> - case lists:delete(Pid, Evs) of - Evs -> - p("unknown process ~p died (normal)", [Pid]), - await_evaluator_finish(Evs, Fails); - NewEvs -> - p("evaluator ~p success", [Pid]), - await_evaluator_finish(NewEvs, Fails) - end; - {'DOWN', _MRef, process, Pid, Reason} -> - case lists:delete(Pid, Evs) of - Evs -> - p("unknown process ~p died: " - "~n ~p", [Pid, Reason]), - await_evaluator_finish(Evs, Fails); - NewEvs -> - p("Evaluator ~p failed", [Pid]), - await_evaluator_finish(NewEvs, [{Pid, Reason}|Fails]) - end - end. - - -ei(F) -> - ei(F, []). -ei(F, A) -> - eprint("", F, A). - -ee(F) -> - ee(F, []). -ee(F, A) -> - eprint(" ", F, A). - -eprint(Prefix, F, A) -> - io:format(user, "[~s][~s][~p] ~s" ++ F ++ "~n", - [formated_timestamp(), get(sname), self(), Prefix | A]). - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -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, SockAddr) -> - try socket:bind(Sock, SockAddr) of - {ok, Port} -> - Port; - {error, Reason} -> - p("sock_bind -> error: ~p", [Reason]), - ?FAIL({bind, Reason}) - catch - C:E:S -> - p("sock_bind -> failed: ~p, ~p, ~p", [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_listen(Sock) -> -%% sock_listen2(fun() -> socket:listen(Sock) end). - -%% sock_listen(Sock, BackLog) -> -%% sock_listen2(fun() -> socket:listen(Sock, BackLog) end). - -%% sock_listen2(Listen) -> -%% try Listen() of -%% ok -> -%% ok; -%% {error, Reason} -> -%% ?FAIL({listen, Reason}) -%% catch -%% C:E:S -> -%% ?FAIL({listen, C, E, S}) -%% end. - - -%% sock_accept(LSock) -> -%% try socket:accept(LSock) of -%% {ok, Sock} -> -%% Sock; -%% {error, Reason} -> -%% p("sock_accept -> error: ~p", [Reason]), -%% ?FAIL({accept, Reason}) -%% catch -%% C:E:S -> -%% p("sock_accept -> failed: ~p, ~p, ~p", [C, E, S]), -%% ?FAIL({accept, C, E, S}) -%% end. - - -sock_close(Sock) -> - try socket:close(Sock) of - ok -> - ok; - {error, Reason} -> - p("sock_close -> error: ~p", [Reason]), - ?FAIL({close, Reason}) - catch - C:E:S -> - p("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), - ?FAIL({close, C, E, S}) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -not_yet_implemented() -> - skip("not yet implemented"). - -skip(Reason) -> - throw({skip, Reason}). - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -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), - %% {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) -> - set_tc_name(TC), - p("begin ***"). - -tc_end(Result) when is_list(Result) -> - p("done: ~s", [Result]), - ok. - - -tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> - tc_begin(Case), - try - begin - Fun(), - tc_end("ok") - end - catch - throw:{skip, _} = SKIP -> - tc_end("skipping"), - SKIP; - Class:Error:Stack -> - tc_end("failed"), - erlang:raise(Class, Error, Stack) - end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%% f(F, A) -> -%% lists:flatten(io_lib:format(F, A)). - -p(F) -> - p(F, []). - -p(F, A) -> - TcName = - 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, - i("*** [~s][~s][~p] " ++ F, [formated_timestamp(),TcName,self()|A]). - - -%% i(F) -> -%% i(F, []). - -i(F, A) -> - io:format(user, F ++ "~n", A). diff --git a/lib/kernel/test/socket_client.erl b/lib/kernel/test/socket_client.erl deleted file mode 100644 index 1c07e799b8..0000000000 --- a/lib/kernel/test/socket_client.erl +++ /dev/null @@ -1,538 +0,0 @@ -%% -%% %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% -%% - --module(socket_client). - --export([ - start/1, start/2, start/5, start/6, - start_tcp/1, start_tcp/2, start_tcp/3, - start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2, - start_udp/1, start_udp/2, start_udp/3, - start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2 - ]). - --define(LIB, socket_lib). - --record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}). - -start(Port) -> - start(Port, 1). - -start(Port, Num) -> - start_tcp(Port, Num). - -start_tcp(Port) -> - start_tcp(Port, 1). - -start_tcp(Port, Num) -> - start_tcp4(Port, Num). - -start_tcp4(Port) -> - start_tcp4(Port, 1). - -start_tcp4(Port, Num) -> - start(inet, stream, tcp, Port, Num). - -start_tcp6(Port) -> - start_tcp6(Port, 1). - -start_tcp6(Port, Num) -> - start(inet6, stream, tcp, Port, Num). - -start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso - is_integer(Num) andalso - (Num > 0) -> - start(inet, stream, tcp, Addr, Port, Num); -start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso - is_integer(Num) andalso - (Num > 0) -> - start(inet6, stream, tcp, Addr, Port, Num). - - -start_udp(Port) -> - start_udp(Port, 1). - -start_udp(Port, Num) -> - start_udp4(Port, Num). - -start_udp4(Port) -> - start_udp4(Port, 1). - -start_udp4(Port, Num) -> - start(inet, dgram, udp, Port, Num). - -start_udp6(Port) -> - start_udp6(Port, 1). - -start_udp6(Port, Num) -> - start(inet6, dgram, udp, Port, Num). - -start_udp(Addr, Port, Num) when (size(Addr) =:= 4) -> - start(inet, dgram, udp, Addr, Port, Num); -start_udp(Addr, Port, Num) when (size(Addr) =:= 8) -> - start(inet6, dgram, udp, Addr, Port, Num). - - -start(Domain, Type, Proto, Port, Num) - when is_integer(Port) andalso is_integer(Num) -> - start(Domain, Type, Proto, which_addr(Domain), Port, Num); - -start(Domain, Type, Proto, Addr, Port) -> - start(Domain, Type, Proto, Addr, Port, 1). - -start(Domain, Type, Proto, Addr, Port, 1 = Num) -> - start(Domain, Type, Proto, Addr, Port, Num, true); -start(Domain, Type, Proto, Addr, Port, Num) - when is_integer(Num) andalso (Num > 1) -> - start(Domain, Type, Proto, Addr, Port, Num, false). - -start(Domain, Type, Proto, Addr, Port, Num, Verbose) -> - put(sname, "starter"), - Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose), - await_clients(Clients). - -start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) -> - start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []). - -start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc) - when (Num > 0) -> - StartClient = fun() -> - start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) - end, - {Pid, _} = spawn_monitor(StartClient), - ?LIB:sleep(500), - i("start client ~w", [ID]), - start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]); -start_clients(_, _, _, _, _, _, _, _, Acc) -> - i("all client(s) started"), - lists:reverse(Acc). - -await_clients([]) -> - i("all clients done"); -await_clients(Clients) -> - receive - {'DOWN', _MRef, process, Pid, _Reason} -> - case lists:delete(Pid, Clients) of - Clients2 when (Clients2 =/= Clients) -> - i("client ~p done", [Pid]), - await_clients(Clients2); - _ -> - await_clients(Clients) - end - end. - - -start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) -> - put(sname, ?LIB:f("client[~w]", [ID])), - SA = #{family => Domain, - addr => Addr, - port => Port}, - %% The way we use tos only works because we - %% send so few messages (a new value for every - %% message). - tos_init(), - do_start(Domain, Type, Proto, SA, Verbose). - -do_start(Domain, stream = Type, Proto, SA, Verbose) -> - try do_init(Domain, Type, Proto) of - Sock -> - connect(Sock, SA), - maybe_print_start_info(Verbose, Sock, Type), - %% Give the server some time... - ?LIB:sleep(5000), - %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, - type = Type, - verbose = Verbose}) - catch - throw:E -> - e("Failed initiate: " - "~n Error: ~p", [E]) - end; -do_start(Domain, dgram = Type, Proto, SA, Verbose) -> - try do_init(Domain, Type, Proto) of - Sock -> - maybe_print_start_info(Verbose, Sock, Type), - %% Give the server some time... - ?LIB:sleep(5000), - %% ok = socket:close(Sock), - send_loop(#client{socket = Sock, - type = Type, - dest = SA, - verbose = Verbose}) - catch - throw:E -> - e("Failed initiate: " - "~n Error: ~p", [E]) - end. - -maybe_print_start_info(true = _Verbose, Sock, stream = _Type) -> - {ok, Name} = socket:sockname(Sock), - {ok, Peer} = socket:peername(Sock), - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MTU} = socket:getopt(Sock, ip, mtu), - {ok, MTUDisc} = socket:getopt(Sock, ip, mtu_discover), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), - i("connected: " - "~n From: ~p" - "~n To: ~p" - "~nwhen" - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" - "~n (ip) MTU: ~p" - "~n (ip) MTU Discovery: ~p" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) RecvTOS: ~p" - "~n => wait some", - [Name, Peer, - Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, - MTU, MTUDisc, MALL, MIF, MLoop, MTTL, - RecvTOS]); -maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) -> - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - {ok, OOBI} = socket:getopt(Sock, socket, oobinline), - {ok, SndBuf} = socket:getopt(Sock, socket, sndbuf), - {ok, RcvBuf} = socket:getopt(Sock, socket, rcvbuf), - {ok, Linger} = socket:getopt(Sock, socket, linger), - {ok, MALL} = socket:getopt(Sock, ip, multicast_all), - {ok, MIF} = socket:getopt(Sock, ip, multicast_if), - {ok, MLoop} = socket:getopt(Sock, ip, multicast_loop), - {ok, MTTL} = socket:getopt(Sock, ip, multicast_ttl), - {ok, RecvTOS} = socket:getopt(Sock, ip, recvtos), - {ok, RecvTTL} = socket:getopt(Sock, ip, recvttl), - i("initiated when: " - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) OOBInline: ~p" - "~n (socket) SndBuf: ~p" - "~n (socket) RcvBuf: ~p" - "~n (socket) Linger: ~p" - "~n (ip) Multicast ALL: ~p" - "~n (ip) Multicast IF: ~p" - "~n (ip) Multicast Loop: ~p" - "~n (ip) Multicast TTL: ~p" - "~n (ip) RecvTOS: ~p" - "~n (ip) RecvTTL: ~p" - "~n => wait some", - [Domain, Type, Proto, - OOBI, SndBuf, RcvBuf, Linger, - MALL, MIF, MLoop, MTTL, - RecvTOS, RecvTTL]); -maybe_print_start_info(_Verbose, _Sock, _Type) -> - ok. - - -do_init(Domain, stream = Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - i("try (socket) bind"), - case socket:bind(Sock, any) of - {ok, _P} -> - ok = socket:setopt(Sock, socket, timestamp, true), - ok = socket:setopt(Sock, ip, tos, mincost), - ok = socket:setopt(Sock, ip, recvtos, true), - Sock; - {error, BReason} -> - throw({bind, BReason}) - end; -do_init(Domain, dgram = Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - case socket:bind(Sock, any) of - {ok, _} -> - ok = socket:setopt(Sock, socket, timestamp, true), - ok = socket:setopt(Sock, ip, tos, mincost), - ok = socket:setopt(Sock, ip, recvtos, true), - ok = socket:setopt(Sock, ip, recvttl, true), - Sock; - {error, BReason} -> - throw({bind, BReason}) - end. - - -which_addr(Domain) -> - Iflist = case inet:getifaddrs() of - {ok, IFL} -> - IFL; - {error, Reason} -> - throw({inet,getifaddrs,Reason}) - end, - which_addr(Domain, Iflist). - - -connect(Sock, SA) -> - i("try (socket) connect to:" - "~n ~p", [SA]), - case socket:connect(Sock, SA) of - ok -> - ok; - {error, Reason} -> - e("connect failure: " - "~n ~p", [Reason]), - exit({connect, Reason}) - end. - - -send_loop(#client{msg_id = N} = C) when (N =< 10) -> - i("try send request ~w", [N]), - Req = ?LIB:enc_req_msg(N, "hejsan"), - case send(C, Req) of - ok -> - i("request ~w sent - now try read answer", [N]), - case recv(C) of - {ok, {Source, Msg}} -> - if - (C#client.verbose =:= true) -> - i("received ~w bytes of data~s", - [size(Msg), case Source of - undefined -> ""; - _ -> ?LIB:f(" from:~n ~p", [Source]) - end]); - true -> - i("received ~w bytes", [size(Msg)]) - end, - case ?LIB:dec_msg(Msg) of - {reply, N, Reply} -> - if - (C#client.verbose =:= true) -> - i("received reply ~w: ~p", [N, Reply]); - true -> - i("received reply ~w", [N]) - end, - ?LIB:sleep(500), % Just to spread it out a bit - send_loop(C#client{msg_id = N+1}) - end; - {error, RReason} -> - e("Failed recv response for request ~w: " - "~n ~p", [N, RReason]), - exit({failed_recv, RReason}) - end; - {error, SReason} -> - e("Failed send request ~w: " - "~n ~p", [N, SReason]), - exit({failed_send, SReason}) - end; -send_loop(Client) -> - sock_close(Client). - -sock_close(#client{socket = Sock, verbose = true}) -> - i("we are done - close the socket when: " - "~n ~p", [socket:info()]), - ok = socket:close(Sock), - i("we are done - socket closed when: " - "~n ~p", [socket:info()]); -sock_close(#client{socket = Sock}) -> - i("we are done"), - ok = socket:close(Sock). - - - -send(#client{socket = Sock, type = stream}, Msg) -> - socket:send(Sock, Msg); -send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) -> - %% i("try send to: " - %% "~n ~p", [Dest]), - %% ok = socket:setopt(Sock, otp, debug, true), - TOS = tos_next(), - ok = socket:setopt(Sock, ip, tos, TOS), - case socket:sendto(Sock, Msg, Dest) of - ok = OK -> - OK; - {error, _} = ERROR -> - ERROR - end. - -recv(#client{socket = Sock, type = stream, msg = false}) -> - case socket:recv(Sock) of - {ok, Msg} -> - {ok, {undefined, Msg}}; - {error, _} = ERROR -> - ERROR - end; -recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) -> - case socket:recvmsg(Sock) of - %% An iov of length 1 is an simplification... - {ok, #{addr := undefined = Source, - iov := [Msg], - ctrl := CMsgHdrs, - flags := Flags}} -> - if - (Verbose =:= true) -> - i("received message: " - "~n CMsgHdr: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]); - true -> - ok - end, - {ok, {Source, Msg}}; - {error, _} = ERROR -> - ERROR - end; -recv(#client{socket = Sock, type = dgram, msg = false}) -> - socket:recvfrom(Sock); -recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Msg], - ctrl := CMsgHdrs, - flags := Flags}} -> - if - (Verbose =:= true) -> - i("received message: " - "~n CMsgHdr: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]); - true -> - ok - end, - {ok, {Source, Msg}}; - {error, _} = ERROR -> - ERROR - end. - - - -which_addr(_Domain, []) -> - throw(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - -%% --- - -%% enc_req_msg(N, Data) -> -%% enc_msg(?REQ, N, Data). - -%% enc_rep_msg(N, Data) -> -%% enc_msg(?REP, N, Data). - -%% enc_msg(Type, N, Data) when is_list(Data) -> -%% enc_msg(Type, N, list_to_binary(Data)); -%% enc_msg(Type, N, Data) -%% when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> -%% <>. - -%% dec_msg(<>) -> -%% {request, N, Data}; -%% dec_msg(<>) -> -%% {reply, N, Data}. - - -%% --- - -%% sleep(T) -> -%% receive after T -> ok end. - - -%% --- - -%% 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). - - -%% --- - -tos_init() -> - put(tos, 1). - -tos_next() -> - case get(tos) of - TOS when (TOS < 100) -> - put(tos, TOS + 1), - TOS; - _ -> - put(tos, 1), - 1 - end. - - -%% --- - -e(F, A) -> - ?LIB:e(F, A). - -i(F) -> - ?LIB:i(F). - -i(F, A) -> - ?LIB:i(F, A). - diff --git a/lib/kernel/test/socket_lib.erl b/lib/kernel/test/socket_lib.erl deleted file mode 100644 index 9d6524d467..0000000000 --- a/lib/kernel/test/socket_lib.erl +++ /dev/null @@ -1,133 +0,0 @@ -%% -%% %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% -%% - --module(socket_lib). - --export([ - sleep/1, - req/0, rep/0, - enc_req_msg/2, enc_rep_msg/2, - enc_msg/3, dec_msg/1, - request/3, reply/4, - f/2, - i/1, i/2, - e/2 - ]). - - --define(REQ, 0). --define(REP, 1). - - -%% --- - -sleep(T) -> - receive after T -> ok end. - - -%% --- - -req() -> ?REQ. -rep() -> ?REP. - -enc_req_msg(N, Data) -> - enc_msg(?REQ, N, Data). - -enc_rep_msg(N, Data) -> - enc_msg(?REP, N, Data). - -enc_msg(Type, N, Data) when is_list(Data) -> - enc_msg(Type, N, list_to_binary(Data)); -enc_msg(Type, N, Data) - when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) -> - <>. - -dec_msg(<>) -> - {request, N, Data}; -dec_msg(<>) -> - {reply, N, Data}. - - -%% --- - -request(Tag, Pid, Request) -> - Ref = make_ref(), - Pid ! {Tag, self(), Ref, Request}, - receive - {Tag, Pid, Ref, Reply} -> - Reply - end. - -reply(Tag, Pid, Ref, Reply) -> - Pid ! {Tag, self(), Ref, Reply}. - - -%% --- - -f(F, A) -> - lists:flatten(io_lib:format(F, A)). - - -%% --- - -e(F, A) -> - p(" " ++ F, A). - -i(F) -> - i(F, []). -i(F, A) -> - p("*** " ++ F, A). - -p(F, A) -> - p(get(sname), F, A). - -p(SName, F, A) -> - io:format("[~s,~p][~s] " ++ F ++ "~n", - [SName,self(),formated_timestamp()|A]). - - -%% --- - -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). - - diff --git a/lib/kernel/test/socket_server.erl b/lib/kernel/test/socket_server.erl deleted file mode 100644 index 45adffc5e6..0000000000 --- a/lib/kernel/test/socket_server.erl +++ /dev/null @@ -1,954 +0,0 @@ -%% -%% %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% -%% - --module(socket_server). - --export([ - start/0, start/5, - start_tcp/0, start_tcp/1, start_tcp/3, - start_tcp4/0, start_tcp4/1, start_tcp4/2, - start_tcp6/0, start_tcp6/1, start_tcp6/2, - start_udp/0, start_udp/1, start_udp/3, - start_udp4/0, start_udp4/1, start_udp4/2, - start_udp6/0, start_udp6/1, start_udp6/2, - start_sctp/0, start_sctp/1 - ]). - --define(LIB, socket_lib). - --record(manager, {socket, msg, peek, acceptors, handler_id, handlers}). --record(acceptor, {id, socket, manager, - atimeout = 5000}). --record(handler, {socket, peek, msg, type, manager, - stimeout = 5000, rtimeout = 5000}). - --define(NUM_ACCEPTORS, 5). - -start() -> - start_tcp(). - -start_tcp() -> - start_tcp4(). - -start_tcp(Peek) -> - start_tcp4(Peek). - -start_tcp4() -> - start_tcp4(false). - -start_tcp4(Peek) -> - start_tcp4(false, Peek). - -start_tcp4(UseMsg, Peek) -> - start_tcp(inet, UseMsg, Peek). - -start_tcp6() -> - start_tcp6(false). - -start_tcp6(Peek) -> - start_tcp6(false, Peek). - -start_tcp6(UseMsg, Peek) -> - start_tcp(inet6, UseMsg, Peek). - -start_tcp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> - start(Domain, stream, tcp, UseMsg, Peek). - -start_udp() -> - start_udp4(). - -start_udp(Peek) -> - start_udp4(Peek). - -start_udp4() -> - start_udp4(false). - -start_udp4(Peek) -> - start_udp4(false, Peek). - -start_udp4(UseMsg, Peek) -> - start_udp(inet, UseMsg, Peek). - -start_udp6() -> - start_udp6(false, false). - -start_udp6(Peek) -> - start_udp6(false, Peek). - -start_udp6(UseMsg, Peek) -> - start_udp(inet6, UseMsg, Peek). - -start_udp(Domain, UseMsg, Peek) when is_boolean(UseMsg) andalso is_boolean(Peek) -> - start(Domain, dgram, udp, UseMsg, Peek). - - -start_sctp() -> - start_sctp(inet). - -start_sctp(Domain) when ((Domain =:= inet) orelse (Domain =:= inet6)) -> - start(Domain, seqpacket, sctp, true, false). - -start(Domain, Type, Proto, UseMsg, Peek) -> - put(sname, "starter"), - i("try start manager"), - {Pid, MRef} = manager_start(Domain, Type, Proto, UseMsg, Peek), - i("manager (~p) started", [Pid]), - loop(Pid, MRef). - -loop(Pid, MRef) -> - receive - {'DOWN', MRef, process, Pid, Reason} -> - i("manager process exited: " - "~n ~p", [Reason]), - ok - end. - - -%% ========================================================================= - -manager_start(Domain, Type, Proto, UseMsg, Peek) -> - spawn_monitor(fun() -> manager_init(Domain, Type, Proto, UseMsg, Peek) end). - -manager_start_handler(Pid, Sock) -> - manager_request(Pid, {start_handler, Sock}). - -manager_stop(Pid, Reason) -> - manager_request(Pid, {stop, Reason}). - -manager_request(Pid, Request) -> - ?LIB:request(manager, Pid, Request). - -manager_reply(Pid, Ref, Reply) -> - ?LIB:reply(manager, Pid, Ref, Reply). - - -manager_init(Domain, Type, Proto, UseMsg, Peek) -> - put(sname, "manager"), - do_manager_init(Domain, Type, Proto, UseMsg, Peek). - -do_manager_init(Domain, stream = Type, Proto, UseMsg, Peek) -> - i("try start acceptor(s)"), - {Sock, Acceptors} = manager_stream_init(Domain, Type, Proto), - manager_loop(#manager{socket = Sock, - msg = UseMsg, - peek = Peek, - acceptors = Acceptors, - handler_id = 1, - handlers = []}); -do_manager_init(Domain, dgram = Type, Proto, UseMsg, Peek) -> - i("try open socket"), - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - F = fun(X) -> case socket:getopt(Sock, socket, X) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - i("socket opened (~s,~s,~s): " - "~n broadcast: ~s" - "~n dontroute: ~s" - "~n keepalive: ~s" - "~n reuseaddr: ~s" - "~n linger: ~s" - "~n debug: ~s" - "~n prio: ~s" - "~n rcvbuf: ~s" - "~n rcvtimeo: ~s" - "~n sndbuf: ~s" - "~n sndtimeo: ~s" - "~n => try find (local) address", - [F(domain), F(type), F(protocol), - F(broadcast), F(dontroute), F(keepalive), F(reuseaddr), F(linger), - F(debug), F(priority), - F(rcvbuf), F(rcvtimeo), F(sndbuf), F(sndtimeo)]), - Addr = which_addr(Domain), - SA = #{family => Domain, - addr => Addr}, - i("try bind to: " - "~n ~p", [Addr]), - case socket:bind(Sock, SA) of - {ok, _P} -> - ok; - {error, BReason} -> - throw({bind, BReason}) - end, - i("bound to: " - "~n ~s" - "~n => try start handler", - [case socket:sockname(Sock) of - {ok, Name} -> f("~p", [Name]); - {error, R} -> f("error: ~p", [R]) - end]), - case handler_start(1, Sock, UseMsg, Peek) of - {ok, {Pid, MRef}} -> - i("handler (~p) started", [Pid]), - handler_continue(Pid), - manager_loop(#manager{peek = Peek, - msg = UseMsg, - handler_id = 2, % Just in case - handlers = [{1, Pid, MRef}]}); - {error, SReason} -> - e("Failed starting handler: " - "~n ~p", [SReason]), - exit({failed_start_handler, SReason}) - end; - {error, OReason} -> - e("Failed open socket: " - "~n ~p", [OReason]), - exit({failed_open_socket, OReason}) - end; -do_manager_init(Domain, seqpacket = Type, sctp = Proto, _UseMsg, _Peek) -> - %% This is as far as I have got with SCTP at the moment... - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - i("(sctp) socket opened: " - "~n ~p", [Sock]), - EXP = fun(_Desc, Expect, Expect) -> - Expect; - (Desc, Expect, Actual) -> - e("Unexpected result ~w: " - "~n Expect: ~p" - "~n Actual: ~p", [Desc, Expect, Actual]), - exit({Desc, Expect, Actual}) - end, - GO = fun(O) -> case socket:getopt(Sock, sctp, O) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - %% ok = socket:setopt(Sock, otp, debug, true), - - i("Miscellaneous options: " - "~n associnfo: ~s" - "~n autoclose: ~s" - "~n disable-fragments: ~s" - "~n initmsg: ~s" - "~n maxseg: ~s" - "~n nodelay: ~s" - "~n rtoinfo: ~s", - [GO(associnfo), - GO(autoclose), - GO(disable_fragments), - GO(initmsg), - GO(maxseg), - GO(nodelay), - GO(rtoinfo)]), - - Events = #{data_in => true, - association => true, - address => true, - send_failure => true, - peer_error => true, - shutdown => true, - partial_delivery => true, - adaptation_layer => true, - authentication => true, - sender_dry => true}, - EXP(set_sctp_events, ok, socket:setopt(Sock, sctp, events, Events)), - EXP(close_socket, ok, socket:close(Sock)); - {error, Reason} -> - exit({failed_open, Reason}) - end; -do_manager_init(Domain, raw = Type, Proto, UseMsg, Peek) when is_integer(Proto) -> - do_manager_init(Domain, Type, {raw, Proto}, UseMsg, Peek); -do_manager_init(Domain, raw = Type, Proto, _UseMsg, _Peek) -> - case socket:open(Domain, Type, Proto) of - {ok, Sock} -> - i("(sctp) socket opened: " - "~n ~p", [Sock]), - socket:close(Sock); - {error, Reason} -> - exit({failed_open, Reason}) - end. - - - -manager_stream_init(Domain, Type, Proto) -> - i("try (socket) open"), - Sock = case socket:open(Domain, Type, Proto) of - {ok, S} -> - S; - {error, OReason} -> - throw({open, OReason}) - end, - F = fun(X) -> case socket:getopt(Sock, socket, X) of - {ok, V} -> f("~p", [V]); - {error, R} -> f("error: ~p", [R]) - end - end, - i("(socket) open (~s,~s,~s): " - "~n debug: ~s" - "~n prio: ~s" - "~n => try find (local) address", - [F(domain), F(type), F(protocol), F(debug), F(priority)]), - Addr = which_addr(Domain), - SA = #{family => Domain, - addr => Addr}, - i("found: " - "~n ~p" - "~n => try (socket) bind", [Addr]), - %% ok = socket:setopt(Sock, otp, debug, true), - %% ok = socket:setopt(Sock, socket, debug, 1), %% must have rights!! - Port = case socket:bind(Sock, SA) of - {ok, P} -> - %% ok = socket:setopt(Sock, socket, debug, 0), %% must have rights!! - %% ok = socket:setopt(Sock, otp, debug, false), - P; - {error, BReason} -> - throw({bind, BReason}) - end, - i("bound to: " - "~n ~p" - "~n => try (socket) listen (acceptconn: ~s)", - [Port, F(acceptconn)]), - case socket:listen(Sock) of - ok -> - i("listening (acceptconn: ~s)", - [F(acceptconn)]), - manager_stream_init(Sock, 1, ?NUM_ACCEPTORS, []); - {error, LReason} -> - throw({listen, LReason}) - end. - -which_addr(Domain) -> - Iflist = case inet:getifaddrs() of - {ok, IFL} -> - IFL; - {error, Reason} -> - throw({inet,getifaddrs,Reason}) - end, - which_addr(Domain, Iflist). - -which_addr(_Domain, []) -> - throw(no_address); -which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") -> - which_addr2(Domain, IFO); -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). - -which_addr2(_, []) -> - throw(no_address); -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - Addr; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - Addr; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). - - -manager_stream_init(Sock, ID, NumAcceptors, Acc) - when (NumAcceptors > 0) -> - i("try start acceptor"), - case acceptor_start(Sock, ID) of - {ok, {Pid, MRef}} -> - i("acceptor ~w (~p) started", [ID, Pid]), - ?LIB:sleep(2000), - manager_stream_init(Sock, ID+1, NumAcceptors-1, - [{ID, Pid, MRef}|Acc]); - {error, Reason} -> - exit({failed_starting_acceptor, Reason}) - end; -manager_stream_init(Sock, _ID, 0, Acc) -> - %% Req = {kill_acceptor, length(Acc)}, % Last in the queue - %% Req = {kill_acceptor, 3}, % In the "middle" of the queue - %% Req = {kill_acceptor, 2}, % The first in the queue - %% Req = {kill_acceptor, 1}, % Current acceptor - %% Msg = {manager, self(), make_ref(), Req}, - %% erlang:send_after(timer:seconds(10), self(), Msg), - {Sock, lists:reverse(Acc)}. - - -manager_loop(M) -> - receive - {'DOWN', MRef, process, Pid, Reason} -> - M2 = manager_handle_down(M, MRef, Pid, Reason), - manager_loop(M2); - - {manager, Pid, Ref, Request} -> - M2 = manager_handle_request(M, Pid, Ref, Request), - manager_loop(M2) - end. - - -manager_handle_down(#manager{acceptors = Acceptors, - handlers = Handlers} = M, MRef, Pid, Reason) -> - case lists:keysearch(Pid, 2, Acceptors) of - {value, {ID, Pid, MRef}} when (Reason =:= normal) -> - i("acceptor ~w exited (normally)", [ID]), - case lists:keydelete(Pid, 2, Acceptors) of - [] -> - %% We are done - i("the last acceptor - we are done"), - exit(normal); - Acceptors2 -> - M#manager{acceptors = Acceptors2} - end; - {value, {ID, Pid, MRef}} -> - e("acceptor ~w crashed: " - "~n ~p", [ID, Reason]), - exit({acceptor_died, Reason}); - - false -> %% handler! - if - (Reason =/= normal) -> - e("handler ~p died: " - "~n ~p", [Pid, Reason]); - true -> - i("handler ~p terminated", [Pid]) - end, - Handlers2 = lists:keydelete(Pid, 2, Handlers), - M#manager{handlers = Handlers2} - end. - - -manager_handle_request(#manager{peek = Peek, - msg = UseMsg, - handler_id = HID, - handlers = Handlers} = M, Pid, Ref, - {start_handler, Sock}) -> - i("try start handler (~w)", [HID]), - case handler_start(HID, Sock, UseMsg, Peek) of - {ok, {HPid, HMRef}} -> - i("handler ~w started", [HID]), - manager_reply(Pid, Ref, {ok, HPid}), - M#manager{handler_id = HID+1, - handlers = [{HID, HPid, HMRef}|Handlers]}; - {error, Reason} = ERROR -> - e("Failed starting new handler: " - "~n Sock: ~p" - "~n Reason: ~p", [Sock, Reason]), - manager_reply(Pid, Ref, ERROR), - M - end; -manager_handle_request(#manager{socket = Sock, - acceptors = [{AID, APid, AMRef}]} = M, _Pid, _Ref, - {kill_acceptor, AID}) -> - i("try kill (only remeining) acceptor ~w", [AID]), - socket:setopt(Sock, otp, debug, true), - manager_stop_acceptor(APid, AMRef, AID, kill), - M#manager{acceptors = []}; -manager_handle_request(#manager{socket = Sock, - acceptors = Acceptors} = M, _Pid, _Ref, - {kill_acceptor, AID}) -> - i("try kill acceptor ~w", [AID]), - case lists:keysearch(AID, 1, Acceptors) of - {value, {AID, APid, AMRef}} -> - socket:setopt(Sock, otp, debug, true), - manager_stop_acceptor(APid, AMRef, AID, kill), - Acceptors2 = lists:keydelete(AID, 1, Acceptors), - M#manager{acceptors = Acceptors2}; - false -> - e("no such acceptor"), - M - end; -manager_handle_request(#manager{acceptors = Acceptors, - handlers = Handlers}, Pid, Ref, - {stop, Reason}) -> - i("stop"), - manager_reply(Pid, Ref, ok), - manager_stop_handlers(Handlers, Reason), - manager_stop_acceptors(Acceptors, Reason), - i("stopped", []), - exit(Reason). - -manager_stop_acceptors(Acceptors, Reason) -> - lists:foreach(fun({ID,P,M}) -> - manager_stop_acceptor(P, M, ID, Reason) - end, Acceptors). - -manager_stop_acceptor(Pid, MRef, ID, Reason) -> - i("try stop acceptor ~w (~p): ~p", [ID, Pid, Reason]), - erlang:demonitor(MRef, [flush]), - acceptor_stop(Pid, Reason), - ok. - -manager_stop_handlers(Handlers, Reason) -> - lists:foreach(fun({ID,P,M}) -> - manager_stop_handler(P, M, ID, Reason) - end, Handlers). - -manager_stop_handler(Pid, MRef, ID, Reason) -> - i("try stop handler ~w (~p): ~p", [ID, Pid, Reason]), - erlang:demonitor(MRef, [flush]), - handler_stop(Pid, Reason), - ok. - - - -%% ========================================================================= - -acceptor_start(Sock, ID) -> - Self = self(), - A = {Pid, _} = spawn_monitor(fun() -> - acceptor_init(Self, Sock, ID) - end), - receive - {acceptor, Pid, ok} -> - {ok, A}; - {acceptor, Pid, {error, _} = Error} -> - exit(Pid, kill), % Just in case - Error; - {'DOWN', _MRef, process, Pid, Reason} -> - {error, {crashed, Reason}} - end. - -acceptor_stop(Pid, _Reason) -> - %% acceptor_request(Pid, {stop, Reason}). - exit(Pid, kill). - -%% acceptor_request(Pid, Request) -> -%% request(acceptor, Pid, Request). - -%% acceptor_reply(Pid, Ref, Reply) -> -%% reply(acceptor, Pid, Ref, Reply). - - -acceptor_init(Manager, Sock, ID) -> - put(sname, f("acceptor[~w]", [ID])), - Manager ! {acceptor, self(), ok}, - %% ok = socket:setopt(Sock, otp, debug, true), - acceptor_loop(#acceptor{id = ID, - manager = Manager, - socket = Sock}). - -acceptor_loop(#acceptor{socket = LSock, atimeout = Timeout} = A) -> - i("try accept"), - case socket:accept(LSock, Timeout) of - {ok, Sock} -> - i("accepted: " - "~n ~p" - "~nwhen" - "~n ~p", [Sock, socket:info()]), - case acceptor_handle_accept_success(A, Sock) of - ok -> - acceptor_loop(A); - {error, Reason} -> - e("Failed starting handler: " - "~n ~p", [Reason]), - socket:close(Sock), - exit({failed_starting_handler, Reason}) - end; - {error, timeout} -> - i("timeout"), - acceptor_loop(A); - {error, Reason} -> - e("accept failure: " - "~n ~p", [Reason]), - exit({accept, Reason}) - end. - -acceptor_handle_accept_success(#acceptor{manager = Manager}, Sock) -> - i("try start handler for peer" - "~n ~p", [case socket:peername(Sock) of - {ok, Peer} -> Peer; - {error, _} = E -> E - end]), - case manager_start_handler(Manager, Sock) of - {ok, Pid} -> - i("handler (~p) started - now change 'ownership'", [Pid]), - case socket:setopt(Sock, otp, controlling_process, Pid) of - ok -> - %% Normally we should have a msgs collection here - %% (of messages we receive before the control was - %% handled over to Handler), but since we don't - %% have active implemented yet... - i("new handler (~p) now controlling process", [Pid]), - handler_continue(Pid), - ok; - {error, _} = ERROR -> - exit(Pid, kill), - ERROR - end; - {error, Reason2} -> - e("failed starting handler: " - "~n (new) Socket: ~p" - "~n Reason: ~p", [Sock, Reason2]), - exit({failed_starting_handler, Reason2}) - end. - - - -%% ========================================================================= - -handler_start(ID, Sock, UseMsg, Peek) -> - Self = self(), - H = {Pid, _} = spawn_monitor(fun() -> - handler_init(Self, ID, UseMsg, Peek, Sock) - end), - receive - {handler, Pid, ok} -> - {ok, H}; - {handler, Pid, {error, _} = ERROR} -> - exit(Pid, kill), % Just in case - ERROR - end. - -handler_stop(Pid, _Reason) -> - %% handler_request(Pid, {stop, Reason}). - exit(Pid, kill). - -handler_continue(Pid) -> - handler_request(Pid, continue). - -handler_request(Pid, Request) -> - ?LIB:request(handler, Pid, Request). - -handler_reply(Pid, Ref, Reply) -> - ?LIB:reply(handler, Pid, Ref, Reply). - - -handler_init(Manager, ID, Msg, Peek, Sock) -> - put(sname, f("handler:~w", [ID])), - i("starting"), - Manager ! {handler, self(), ok}, - receive - {handler, Pid, Ref, continue} -> - i("got continue"), - handler_reply(Pid, Ref, ok), - G = fun(L, O) -> case socket:getopt(Sock, L, O) of - {ok, Val} -> - f("~p", [Val]); - {error, R} when is_atom(R) -> - f("error: ~w", [R]); - {error, {T, R}} when is_atom(T) -> - f("error: ~w, ~p", [T, R]); - {error, R} -> - f("error: ~p", [R]) - end - end, - GSO = fun(O) -> G(socket, O) end, - GIP4 = fun(O) -> G(ip, O) end, - GIP6 = fun(O) -> G(ipv6, O) end, - {ok, Domain} = socket:getopt(Sock, socket, domain), - {ok, Type} = socket:getopt(Sock, socket, type), - {ok, Proto} = socket:getopt(Sock, socket, protocol), - B2D = GSO(bindtodevice), - RA = GSO(reuseaddr), - RP = GSO(reuseport), - OOBI = GSO(oobinline), - RcvBuf = GSO(rcvbuf), - RcvLW = GSO(rcvlowat), - RcvTO = GSO(rcvtimeo), - SndBuf = GSO(sndbuf), - SndLW = GSO(sndlowat), - SndTO = GSO(sndtimeo), - Linger = GSO(linger), - Timestamp = GSO(timestamp), - FreeBind = GIP4(freebind), - MTU = GIP4(mtu), - MTUDisc = GIP4(mtu_discover), - MALL = GIP4(multicast_all), - MIF4 = GIP4(multicast_if), - MLoop4 = GIP4(multicast_loop), - MTTL = GIP4(multicast_ttl), - NF = GIP4(nodefrag), % raw only - PktInfo = GIP4(pktinfo), % dgram only - RecvErr4 = GIP4(recverr), - RecvIF = GIP4(recvif), % Only dgram and raw (and FreeBSD) - RecvOPTS = GIP4(recvopts), % Not stream - RecvOrigDstAddr = GIP4(recvorigdstaddr), - RecvTOS = GIP4(recvtos), - RecvTTL = GIP4(recvttl), % not stream - RetOpts = GIP4(retopts), % not stream - SendSrcAddr = GIP4(sendsrcaddr), - TOS = GIP4(tos), - Transparent = GIP4(transparent), - TTL = GIP4(ttl), - MHops = GIP6(multicast_hops), - MIF6 = GIP6(multicast_if), % Only dgram and raw - MLoop6 = GIP6(multicast_loop), - RecvErr6 = GIP6(recverr), - RecvPktInfo = GIP6(recvpktinfo), - RtHdr = GIP6(rthdr), - AuthHdr = GIP6(authhdr), - HopLimit = GIP6(hoplimit), - HopOpts = GIP6(hopopts), - DstOpts = GIP6(dstopts), - FlowInfo = GIP6(flowinfo), - UHops = GIP6(unicast_hops), - i("got continue when: " - "~n (socket) Domain: ~p" - "~n (socket) Type: ~p" - "~n (socket) Protocol: ~p" - "~n (socket) Reuse Address: ~s" - "~n (socket) Reuse Port: ~s" - "~n (socket) Bind To Device: ~s" - "~n (socket) OOBInline: ~s" - "~n (socket) RcvBuf: ~s" - "~n (socket) RcvLW: ~s" - "~n (socket) RcvTO: ~s" - "~n (socket) SndBuf: ~s" - "~n (socket) SndLW: ~s" - "~n (socket) SndTO: ~s" - "~n (socket) Linger: ~s" - "~n (socket) Timestamp: ~s" - "~n (ip) FreeBind: ~s" - "~n (ip) MTU: ~s" - "~n (ip) MTU Discovery: ~s" - "~n (ip) Multicast ALL: ~s" - "~n (ip) Multicast IF: ~s" - "~n (ip) Multicast Loop: ~s" - "~n (ip) Multicast TTL: ~s" - "~n (ip) Node Frag: ~s" - "~n (ip) Pkt Info: ~s" - "~n (ip) Recv Err: ~s" - "~n (ip) Recv IF: ~s" - "~n (ip) Recv OPTS: ~s" - "~n (ip) Recv Orig Dst Addr: ~s" - "~n (ip) Recv TOS: ~s" - "~n (ip) Recv TTL: ~s" - "~n (ip) Ret Opts: ~s" - "~n (ip) Send Src Addr: ~s" - "~n (ip) TOS: ~s" - "~n (ip) Transparent: ~s" - "~n (ip) TTL: ~s" - "~n (ipv6) Multicast Hops: ~s" - "~n (ipv6) Multicast IF: ~s" - "~n (ipv6) Multicast Loop: ~s" - "~n (ipv6) Recv Err: ~s" - "~n (ipv6) Recv Pkt Info: ~s" - "~n (ipv6) RT Hdr: ~s" - "~n (ipv6) Auth Hdr: ~s" - "~n (ipv6) Hop Limit: ~s" - "~n (ipv6) Hop Opts: ~s" - "~n (ipv6) Dst Opts: ~s" - "~n (ipv6) Flow Info: ~s" - "~n (ipv6) Unicast Hops: ~s", - [Domain, Type, Proto, - RA, RP, B2D, OOBI, - RcvBuf, RcvLW, RcvTO, SndBuf, SndLW, SndTO, - Linger, Timestamp, - FreeBind, MTU, MTUDisc, MALL, MIF4, MLoop4, MTTL, - NF, PktInfo,RecvErr4, - RecvIF, RecvOPTS, RecvOrigDstAddr, RecvTOS, RecvTTL, RetOpts, - SendSrcAddr, TOS, Transparent, TTL, - MHops, MIF6, MLoop6, RecvErr6, RecvPktInfo, - RtHdr, AuthHdr, HopLimit, HopOpts, DstOpts, FlowInfo, - UHops]), - - %% ok = socket:setopt(Sock, otp, debug, true), - %% case socket:getopt(Sock, 0, {13, int}) of - %% {ok, Val} -> - %% i("PktOpts ok: ~p", [Val]); - %% {error, Reason} -> - %% e("PktOpts err: ~p", [Reason]) - %% end, - %% ok = socket:setopt(Sock, otp, debug, false), - SSO = fun(O, V) -> soso(Sock, O, V) end, - SIP4 = - fun(O, V) -> - if - (Type =:= dgram) -> - ok = soip(Sock, O, V); - true -> - ok - end - end, - SSO(timestamp, true), - SIP4(pktinfo, true), - ok = soip(Sock, recvtos, true), - SIP4(recvttl, true), - ok = soip(Sock, recvorigdstaddr, true), - - handler_loop(#handler{msg = Msg, - peek = Peek, - manager = Manager, - type = Type, - socket = Sock}) - end. - -so(Sock, Lvl, Opt, Val) -> - ok = socket:setopt(Sock, Lvl, Opt, Val). - -soso(Sock, Opt, Val) -> - so(Sock, socket, Opt, Val). - -soip(Sock, Opt, Val) -> - so(Sock, ip, Opt, Val). - -%% soipv6(Sock, Opt, Val) -> -%% so(Sock, ipv6, Opt, Val). - -handler_loop(H) -> - i("try read message"), - case recv(H) of - {ok, {Source, Msg}} -> - i("received ~w bytes of data~s", - [size(Msg), case Source of - undefined -> ""; - _ -> f(" from:~n ~p", [Source]) - end]), - case ?LIB:dec_msg(Msg) of - {request, N, Req} -> - i("received request ~w: " - "~n ~p", [N, Req]), - Reply = ?LIB:enc_rep_msg(N, "hoppsan"), - case send(H, Reply, Source) of - ok -> - i("successfully sent reply ~w", [N]), - handler_loop(H); - {error, SReason} -> - e("failed sending reply ~w:" - "~n ~p", [N, SReason]), - exit({failed_sending_reply, SReason}) - end - end; - - {error, closed} -> - i("closed when" - "~n ~p", [socket:info()]), - exit(normal); - - {error, RReason} -> - e("failed reading request: " - "~n ~p", [RReason]), - exit({failed_reading_request, RReason}) - end. - - -recv(#handler{peek = true, socket = Sock, type = stream}) -> - peek_recv(Sock); -recv(#handler{socket = Sock, msg = true, type = stream}) -> - case socket:recvmsg(Sock) of - {ok, #{addr := undefined = Source, - iov := [Data], - ctrl := CMsgHdrs, - flags := Flags}} -> - i("received message: " - "~n CMsgHdrs: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]), - {ok, {Source, Data}}; - {ok, X} -> - e("received *unexpected* message: " - "~n ~p", [X]), - {error, {unexpected, X}}; - {error, _} = ERROR -> - ERROR - end; -recv(#handler{socket = Sock, msg = true, type = dgram}) -> - case socket:recvmsg(Sock) of - {ok, #{addr := Source, - iov := [Data], - ctrl := CMsgHdrs, - flags := Flags}} -> - i("received message: " - "~n CMsgHdrs: ~p" - "~n Flags: ~p", [CMsgHdrs, Flags]), - {ok, {Source, Data}}; - {ok, X} -> - {error, {unexpected, X}}; - {error, _} = ERROR -> - ERROR - end; -recv(#handler{peek = false, socket = Sock, type = stream}) -> - do_recv(Sock); -recv(#handler{peek = Peek, socket = Sock, type = dgram}) - when (Peek =:= true) -> - %% ok = socket:setopt(Sock, otp, debug, true), - RES = peek_recvfrom(Sock, 5), - %% ok = socket:setopt(Sock, otp, debug, false), - RES; -recv(#handler{peek = Peek, socket = Sock, type = dgram}) - when (Peek =:= false) -> - %% ok = socket:setopt(Sock, otp, debug, true), - socket:recvfrom(Sock). - -do_recv(Sock) -> - case socket:recv(Sock) of - {ok, Msg} -> - {ok, {undefined, Msg}}; - {error, _} = ERROR -> - ERROR - end. - -peek_recv(Sock) -> - i("try peek on the message type (expect request)"), - Type = ?LIB:req(), - case socket:recv(Sock, 4, [peek]) of - {ok, <>} -> - i("was request - do proper recv"), - do_recv(Sock); - {error, _} = ERROR -> - ERROR - end. - -peek_recvfrom(Sock, BufSz) -> - i("try peek recvfrom with buffer size ~w", [BufSz]), - case socket:recvfrom(Sock, BufSz, [peek]) of - {ok, {_Source, Msg}} when (BufSz =:= size(Msg)) -> - %% i("we filled the buffer: " - %% "~n ~p", [Msg]), - %% It *may not* fit => try again with double size - peek_recvfrom(Sock, BufSz*2); - {ok, _} -> - %% It fits => read for real - i("we did *not* fill the buffer - do the 'real' read"), - socket:recvfrom(Sock); - {error, _} = ERROR -> - ERROR - end. - - -send(#handler{socket = Sock, msg = true, type = stream, stimeout = Timeout}, - Msg, _) -> - CMsgHdr = #{level => ip, type => tos, data => reliability}, - CMsgHdrs = [CMsgHdr], - MsgHdr = #{iov => [Msg], ctrl => CMsgHdrs}, - %% socket:setopt(Sock, otp, debug, true), - Res = socket:sendmsg(Sock, MsgHdr, Timeout), - %% socket:setopt(Sock, otp, debug, false), - Res; -send(#handler{socket = Sock, type = stream, stimeout = Timeout}, Msg, _) -> - socket:send(Sock, Msg, Timeout); -send(#handler{socket = Sock, msg = true, type = dgram, stimeout = Timeout}, - Msg, Dest) -> - CMsgHdr = #{level => ip, type => tos, data => reliability}, - CMsgHdrs = [CMsgHdr], - MsgHdr = #{addr => Dest, - ctrl => CMsgHdrs, - iov => [Msg]}, - %% ok = socket:setopt(Sock, otp, debug, true), - Res = socket:sendmsg(Sock, MsgHdr, Timeout), - %% ok = socket:setopt(Sock, otp, debug, false), - Res; -send(#handler{socket = Sock, type = dgram, stimeout = Timeout}, Msg, Dest) -> - socket:sendto(Sock, Msg, Dest, Timeout). - -%% filler() -> -%% list_to_binary(lists:duplicate(2048, " FILLER ")). - - - -%% ========================================================================= - -f(F, A) -> - ?LIB:f(F, A). - -e(F) -> - e(F, []). -e(F, A) -> - ?LIB:e(F, A). - -i(F) -> - ?LIB:i(F). - -i(F, A) -> - ?LIB:i(F, A). - -- cgit v1.2.3