diff options
author | Micael Karlberg <[email protected]> | 2019-02-04 14:47:48 +0100 |
---|---|---|
committer | Micael Karlberg <[email protected]> | 2019-02-04 14:47:48 +0100 |
commit | ad0beca8fb612fa33dabcfb3592796a92e858684 (patch) | |
tree | 97fdd079a2a8abfb3b9de8992ef6452dd2193acd /erts | |
parent | 4b3c3ae0bdcab6028faa5a5ea3e4076ce6af5de4 (diff) | |
parent | 875825874d4a8d52ec5cc593f5024afc696c29df (diff) | |
download | otp-ad0beca8fb612fa33dabcfb3592796a92e858684.tar.gz otp-ad0beca8fb612fa33dabcfb3592796a92e858684.tar.bz2 otp-ad0beca8fb612fa33dabcfb3592796a92e858684.zip |
Merge branch 'bmk/20180918/nififying_inet/OTP-14831' into bmk/20190204/socket_as_nif/OTP-14831
Diffstat (limited to 'erts')
47 files changed, 50845 insertions, 12 deletions
diff --git a/erts/configure.in b/erts/configure.in index 14c8b50680..a9bd1b117a 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -1655,7 +1655,9 @@ if test x"$ac_cv_header_netinet_sctp_h" = x"yes"; then struct sctp_paddrparams.spp_sackdelay, struct sctp_paddrparams.spp_flags, struct sctp_remote_error.sre_data, - struct sctp_send_failed.ssf_data], [], [], + struct sctp_send_failed.ssf_data, + struct sctp_event_subscribe.sctp_authentication_event, + struct sctp_event_subscribe.sctp_sender_dry_event], [], [], [#if HAVE_SYS_SOCKET_H #include <sys/socket.h> #endif diff --git a/erts/doc/src/Makefile b/erts/doc/src/Makefile index 40f74b78ff..fdfd81c2f3 100644 --- a/erts/doc/src/Makefile +++ b/erts/doc/src/Makefile @@ -55,7 +55,9 @@ XML_REF3_EFILES = \ persistent_term.xml \ atomics.xml \ counters.xml \ - zlib.xml + zlib.xml \ + socket.xml \ + net.xml XML_REF3_FILES = \ $(XML_REF3_EFILES) \ @@ -77,6 +79,7 @@ XML_CHAPTER_FILES = \ driver.xml \ absform.xml \ inet_cfg.xml \ + socket_usage.xml \ erl_ext_dist.xml \ erl_dist_protocol.xml \ communication.xml \ diff --git a/erts/doc/src/net.xml b/erts/doc/src/net.xml new file mode 100644 index 0000000000..e0c3b4ec73 --- /dev/null +++ b/erts/doc/src/net.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2018</year><year>2018</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>net</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>net.xml</file> + </header> + <module>net</module> + <modulesummary>Network interface.</modulesummary> + <description> + <p>This module provides an API for the network interface.</p> + </description> + + <datatypes> + <datatype> + <name name="address_info"/> + </datatype> + <datatype> + <name name="name_info"/> + </datatype> + <datatype> + <name name="name_info_flags"/> + </datatype> + <datatype> + <name name="name_info_flag"/> + </datatype> + <datatype> + <name name="name_info_flag_ext"/> + </datatype> + <datatype> + <name name="network_interface_name"/> + </datatype> + <datatype> + <name name="network_interface_index"/> + </datatype> + </datatypes> + + <funcs> + <func> + <name name="gethostname" arity="0"/> + <fsummary>Get hostname.</fsummary> + <desc> + <p>Returns the name of the current host.</p> + </desc> + </func> + + <func> + <name name="getnameinfo" arity="1"/> + <name name="getnameinfo" arity="2"/> + <fsummary>Address-to-name transaltion.</fsummary> + <desc> + <p>Address-to-name translation in a protocol-independant manner.</p> + <p>This function is the inverse of + <seealso marker="#getaddrinfo"><c>getaddrinfo</c></seealso>. + It converts a socket address to a corresponding host and service.</p> + </desc> + </func> + + <func> + <name name="getaddrinfo" arity="1"/> + <name name="getaddrinfo" arity="2" clause_i="1"/> + <name name="getaddrinfo" arity="2" clause_i="2"/> + <name name="getaddrinfo" arity="2" clause_i="3"/> + <fsummary>Network address and service transation.</fsummary> + <desc> + <p>Network address and service translation.</p> + <p>This function is the inverse of + <seealso marker="#getnameinfo"><c>getnameinfo</c></seealso>. + It converts host and service to a corresponding socket address.</p> + <p>One of the <c>Host</c> and <c>Service</c> may be <c>undefined</c> + but <em>not</em> both.</p> + </desc> + </func> + + <func> + <name name="if_name2index" arity="1"/> + <fsummary>Mappings between network interface names and indexes.</fsummary> + <desc> + <p>Mappings between network interface names and indexes.</p> + </desc> + </func> + + <func> + <name name="if_index2name" arity="1"/> + <fsummary>Mappings between network interface index and names.</fsummary> + <desc> + <p>Mappings between network interface index and names.</p> + </desc> + </func> + + <func> + <name name="if_names" arity="0"/> + <fsummary>Get network interface names and indexes.</fsummary> + <desc> + <p>Get network interface names and indexes.</p> + </desc> + </func> + + </funcs> + +</erlref> + diff --git a/erts/doc/src/part.xml b/erts/doc/src/part.xml index 05e9a24af8..f0b8a00b90 100644 --- a/erts/doc/src/part.xml +++ b/erts/doc/src/part.xml @@ -42,6 +42,7 @@ <xi:include href="tty.xml"/> <xi:include href="driver.xml"/> <xi:include href="inet_cfg.xml"/> + <xi:include href="socket_usage.xml"/> <xi:include href="erl_ext_dist.xml"/> <xi:include href="erl_dist_protocol.xml"/> </part> diff --git a/erts/doc/src/ref_man.xml b/erts/doc/src/ref_man.xml index 811d299f06..80cdcf9145 100644 --- a/erts/doc/src/ref_man.xml +++ b/erts/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1996</year><year>2016</year> + <year>1996</year><year>2018</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -46,8 +46,10 @@ <xi:include href="erts_alloc.xml"/> <xi:include href="escript.xml"/> <xi:include href="init.xml"/> + <xi:include href="net.xml"/> <xi:include href="persistent_term.xml"/> <xi:include href="run_erl.xml"/> + <xi:include href="socket.xml"/> <xi:include href="start.xml"/> <xi:include href="start_erl.xml"/> <xi:include href="werl.xml"/> diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml new file mode 100644 index 0000000000..ea2fde7dee --- /dev/null +++ b/erts/doc/src/socket.xml @@ -0,0 +1,580 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2018</year><year>2018</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>socket</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>socket.xml</file> + </header> + <module>socket</module> + <modulesummary>Socket interface.</modulesummary> + <description> + <p>This module provides an API for the socket interface. + It is used to create, delete and manipulate sockets.</p> + </description> + + <datatypes> + <datatype> + <name name="domain"/> + </datatype> + <datatype> + <name name="type"/> + </datatype> + <datatype> + <name name="protocol"/> + </datatype> + <datatype> + <name>socket()</name> + <desc><p>As returned by + <seealso marker="#open/1"><c>open/2,3,4</c></seealso> and + <seealso marker="#accept/1"><c>accept/1,2</c></seealso>.</p> + </desc> + </datatype> + <datatype> + <name name="ip4_address"/> + </datatype> + <datatype> + <name name="ip6_address"/> + </datatype> + <datatype> + <name name="ip_address"/> + </datatype> + <datatype> + <name name="sockaddr"/> + </datatype> + <datatype> + <name name="sockaddr_in4"/> + </datatype> + <datatype> + <name name="sockaddr_in6"/> + </datatype> + <datatype> + <name name="sockaddr_un"/> + </datatype> + <datatype> + <name name="port_number"/> + </datatype> + <datatype> + <name name="accept_flags"/> + </datatype> + <datatype> + <name name="accept_flag"/> + </datatype> + <datatype> + <name name="send_flags"/> + </datatype> + <datatype> + <name name="send_flag"/> + </datatype> + <datatype> + <name name="shutdown_how"/> + </datatype> + <datatype> + <name name="sockopt_level"/> + </datatype> + <datatype> + <name name="otp_socket_option"/> + </datatype> + <datatype> + <name name="socket_option"/> + </datatype> + <datatype> + <name name="ip_socket_option"/> + </datatype> + <datatype> + <name name="ipv6_socket_option"/> + </datatype> + <datatype> + <name name="tcp_socket_option"/> + </datatype> + <datatype> + <name name="udp_socket_option"/> + </datatype> + <datatype> + <name name="sctp_socket_option"/> + </datatype> + <datatype> + <name name="timeval"/> + </datatype> + <datatype> + <name name="ip_tos"/> + </datatype> + <datatype> + <name name="ip_mreq"/> + </datatype> + <datatype> + <name name="ip_mreq_source"/> + </datatype> + <datatype> + <name name="ip_pmtudisc"/> + </datatype> + <datatype> + <name name="ip_msfilter_mode"/> + </datatype> + <datatype> + <name name="ip_msfilter"/> + </datatype> + <datatype> + <name name="ip_pktinfo"/> + </datatype> + <datatype> + <name name="ipv6_mreq"/> + </datatype> + <datatype> + <name name="ipv6_pmtudisc"/> + </datatype> + <datatype> + <name name="sctp_assoc_id"/> + </datatype> + <datatype> + <name name="sctp_sndrcvinfo"/> + </datatype> + <datatype> + <name name="sctp_event_subscribe"/> + </datatype> + <datatype> + <name name="sctp_assocparams"/> + </datatype> + <datatype> + <name name="sctp_initmsg"/> + </datatype> + <datatype> + <name name="sctp_rtoinfo"/> + </datatype> + <datatype> + <name name="msghdr_flag"/> + </datatype> + <datatype> + <name name="msghdr_flags"/> + </datatype> + <datatype> + <name name="msghdr"/> + </datatype> + <datatype> + <name name="cmsghdr_level"/> + </datatype> + <datatype> + <name name="cmsghdr_type"/> + </datatype> + <!-- + <datatype> + <name name="cmsghdr_data"/> + </datatype> + --> + <datatype> + <name name="cmsghdr_recv"/> + </datatype> + <datatype> + <name name="cmsghdr_send"/> + </datatype> + <datatype> + <name name="uint8"/> + </datatype> + <datatype> + <name name="uint16"/> + </datatype> + <datatype> + <name name="uint20"/> + </datatype> + <datatype> + <name name="uint32"/> + </datatype> + <datatype> + <name name="int32"/> + </datatype> + <datatype> + <name name="supports_options_socket"/> + </datatype> + <datatype> + <name name="supports_options_ip"/> + </datatype> + <datatype> + <name name="supports_options_ipv6"/> + </datatype> + <datatype> + <name name="supports_options_tcp"/> + </datatype> + <datatype> + <name name="supports_options_udp"/> + </datatype> + <datatype> + <name name="supports_options_sctp"/> + </datatype> + <datatype> + <name name="supports_options"/> + </datatype> + </datatypes> + + <funcs> + <func> + <name name="accept" arity="1"/> + <name name="accept" arity="2"/> + <fsummary>Accept a connection on a socket.</fsummary> + <desc> + <p>Accept a connection on a socket.</p> + <p>This call is used with connection-based socket types + (<c>stream</c> or <c>seqpacket</c>). It extracs the first pending + connection request for the listen socket and returns the (newly) + connected socket.</p> + </desc> + </func> + + <func> + <name name="bind" arity="2"/> + <fsummary>Bind a name to a socket.</fsummary> + <desc> + <p>Bind a name to a socket.</p> + <p>When a socket is created + (with <seealso marker="#open/2"><c>open</c></seealso>), + it has no address assigned to it. <c>bind</c> assigns the + address specified by the <c>Addr</c> argument.</p> + <p>The rules used for name binding vary between domains.</p> + </desc> + </func> + + <func> + <name name="close" arity="1"/> + <fsummary>Close a socket.</fsummary> + <desc> + <p>Closes the socket.</p> + + <note> + <p>Note that for e.g. <c>protocol</c> = <c>tcp</c>, most implementations + doing a close does not guarantee that any data sent is delivered to + the recipient before the close is detected at the remote side. </p> + <p>One way to handle this is to use the + <seealso marker="#shutdown/2"><c>shutdown</c></seealso> + function + (<c>socket:shutdown(Socket, write)</c>) to signal that no more data is + to be sent and then wait for the read side of the socket to be closed.</p> + </note> + </desc> + </func> + + <func> + <name name="connect" arity="2"/> + <name name="connect" arity="3"/> + <fsummary>Initiate a connection on a socket.</fsummary> + <desc> + <p>This function connects the socket to the address + specied by the <c>SockAddr</c> argument.</p> + </desc> + </func> + + <func> + <name name="getopt" arity="3" clause_i="1"/> + <name name="getopt" arity="3" clause_i="2"/> + <name name="getopt" arity="3" clause_i="3"/> + <name name="getopt" arity="3" clause_i="4"/> + <name name="getopt" arity="3" clause_i="5"/> + <name name="getopt" arity="3" clause_i="6"/> + <name name="getopt" arity="3" clause_i="7"/> + <fsummary>Get an option on a socket.</fsummary> + <desc> + <p>Get an option on a socket.</p> + <p>What properties are valid depend both on <c>Level</c> and + on what kind of socket it is (<c>domain</c>, <c>type</c> and + <c>protocol</c>).</p> + + <note><p>Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.</p></note> + + </desc> + </func> + + <func> + <name name="getopt" arity="3" clause_i="8"/> + <fsummary>Get an option on a socket.</fsummary> + <desc> + <p>Get an option on a socket.</p> + <p>What properties are valid depend both on <c>Level</c> and + on what kind of socket it is (<c>domain</c>, <c>type</c> and + <c>protocol</c>).</p> + <p>When specifying <c>Level</c> as an integer, and therefor + using "native mode", it is *currently* up to the caller to + know how to interpret the result.</p> + + <note><p>Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.</p></note> + </desc> + </func> + + <func> + <name name="listen" arity="1"/> + <name name="listen" arity="2"/> + <fsummary>Listen for connections on a socket.</fsummary> + <desc> + <p>Listen for connections on a socket.</p> + </desc> + </func> + + <func> + <name name="open" arity="2"/> + <name name="open" arity="3"/> + <name name="open" arity="4"/> + <fsummary>Create an endpoint for communication.</fsummary> + <desc> + <p>Creates an endpoint (socket) for communication.</p> + <p>For some <c>types</c> there is a default protocol, which will + be used if no protocol is specified: </p> + + <list> + <item><p><c>stream</c>: <c>tcp</c></p></item> + <item><p><c>dgram</c>: <c>udp</c></p></item> + <item><p><c>seqpacket</c>: <c>sctp</c></p></item> + </list> + + <p>The <c>Extra</c> argument is intended for "obscure" options. + Currently the only supported option is <c>netns</c>, which + is only supported on the linux platform.</p> + </desc> + </func> + + <func> + <name name="peername" arity="1"/> + <fsummary>Get name of connected socket peer.</fsummary> + <desc> + <p>Returns the address of the peer connected to the socket.</p> + </desc> + </func> + + <func> + <name name="recv" arity="1"/> + <name name="recv" arity="2"/> + <name name="recv" arity="3" clause_i="1"/> + <name name="recv" arity="3" clause_i="2"/> + <name name="recv" arity="4"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>There is a special case for the argument <c>Length</c>. + If it is set to zero (0), it means "give me everything you + currently have".</p> + </desc> + </func> + + <func> + <name name="recvfrom" arity="1"/> + <name name="recvfrom" arity="2"/> + <name name="recvfrom" arity="3" clause_i="1"/> + <name name="recvfrom" arity="3" clause_i="2"/> + <name name="recvfrom" arity="3" clause_i="3"/> + <name name="recvfrom" arity="4"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message.</p> + <p>The <c>BufSz</c> argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with <c>Level</c> = <c>otp</c>) is used.</p> + <p>It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvfrom call + is needed, possibly with a then adjusted buffer size.</p> + </desc> + </func> + + <func> + <name name="recvmsg" arity="1"/> + <name name="recvmsg" arity="2" clause_i="1"/> + <name name="recvmsg" arity="2" clause_i="2"/> + <name name="recvmsg" arity="3" clause_i="1"/> + <name name="recvmsg" arity="3" clause_i="2"/> + <name name="recvmsg" arity="5"/> + <fsummary>Receive a message from a socket.</fsummary> + <desc> + <p>Receive a message from a socket.</p> + <p>This function reads "messages", which means that regardless of + how much we want to read, it returns when we get a message.</p> + <p>The message will be delivered in the form of a <c>msghdr()</c>, + which may contain the source address (if socket not connected), + a list of <c>cmsghdr_recv()</c> (depends on what socket options have + been set and what the protocol and platform supports) and + also a set of flags, providing further info about the read. </p> + + <p>The <c>BufSz</c> argument basically defines the size of the + receive buffer. By setting the value to zero (0), the configured + size (setopt with <c>Level</c> = <c>otp</c>) is used.</p> + + <p>The <c>CtrlSz</c> argument basically defines the size of the + receive buffer for the control messages. + By setting the value to zero (0), the configured size (setopt + with <c>Level</c> = <c>otp</c>) is used.</p> + + <p>It may be impossible to know what (buffer) size is appropriate + "in advance", and in those cases it may be convenient to use the + (recv) 'peek' flag. When this flag is provided, the message is *not* + "consumed" from the underlying buffers, so another recvmsg call + is needed, possibly with a then adjusted buffer size.</p> + </desc> + </func> + + <func> + <name name="send" arity="2"/> + <name name="send" arity="3" clause_i="1"/> + <name name="send" arity="3" clause_i="2"/> + <name name="send" arity="4"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a connected socket.</p> + </desc> + </func> + + <func> + <name name="sendmsg" arity="2"/> + <name name="sendmsg" arity="3" clause_i="1"/> + <name name="sendmsg" arity="3" clause_i="2"/> + <name name="sendmsg" arity="4"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a socket. The destination, if needed + (socket <em>not</em> connected) is provided in the <c>MsgHdr</c>, + which also contains the message to send, + The <c>MsgHdr</c> may also contain an list of optional <c>cmsghdr_send()</c> + (depends on what the protocol and platform supports).</p> + + <p>Unlike the <seealso marker="#send"><c>send</c></seealso> function, + this one sends <em>one message</em>. + This means that if, for whatever reason, its not possible to send the + message in one go, the function will instead return with the + <em>remaining</em> data (<c>{ok, Remaining}</c>). Thereby leaving it + up to the caller to decide what to do (retry with the remaining data + of give up). </p> + + </desc> + </func> + + <func> + <name name="sendto" arity="3"/> + <name name="sendto" arity="4" clause_i="1"/> + <name name="sendto" arity="4" clause_i="2"/> + <name name="sendto" arity="5"/> + <fsummary>Send a message on a socket.</fsummary> + <desc> + <p>Send a message on a socket, to the specified destination.</p> + </desc> + </func> + + <func> + <name name="setopt" arity="4" clause_i="1"/> + <name name="setopt" arity="4" clause_i="2"/> + <name name="setopt" arity="4" clause_i="3"/> + <name name="setopt" arity="4" clause_i="4"/> + <name name="setopt" arity="4" clause_i="5"/> + <name name="setopt" arity="4" clause_i="6"/> + <name name="setopt" arity="4" clause_i="7"/> + <fsummary>Set options on a socket.</fsummary> + <desc> + <p>Set options on a socket.</p> + <p>What properties are valid depend both on <c>Level</c> and on + what kind of socket it is (<c>domain</c>, <c>type</c> and + <c>protocol</c>).</p> + + <note><p>Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.</p></note> + + <note><p>Sockets are set 'non-blocking' when created, so this option + is *not* available (as it would adversely effect the Erlang VM + to set a socket 'blocking').</p></note> + </desc> + </func> + + <func> + <name name="setopt" arity="4" clause_i="8"/> + <fsummary>Set options on a socket.</fsummary> + <desc> + <p>Set options on a socket.</p> + <p>What properties are valid depend both on <c>Level</c> and on + what kind of socket it is (<c>domain</c>, <c>type</c> and + <c>protocol</c>).</p> + + <note><p>Not all options are valid on all platforms. That is, + even if "we" support an option, that does not mean that the + underlying OS does.</p></note> + + <note><p>Sockets are set 'non-blocking' when created, so this option + is *not* available (as it would adversely effect the Erlang VM + to set a socket 'blocking').</p></note> + </desc> + </func> + + <func> + <name name="shutdown" arity="2"/> + <fsummary>Shut down part of a full-duplex connection.</fsummary> + <desc> + <p>Shut down all or part of a full-duplex connection.</p> + </desc> + </func> + + <func> + <name name="sockname" arity="1"/> + <fsummary>Get socket name.</fsummary> + <desc> + <p>Returns the current address to which the socket is bound.</p> + </desc> + </func> + + <func> + <name name="supports" arity="0"/> + <name name="supports" arity="1" clause_i="1"/> + <name name="supports" arity="1" clause_i="2"/> + <name name="supports" arity="1" clause_i="3"/> + <name name="supports" arity="1" clause_i="4"/> + <name name="supports" arity="2" clause_i="1"/> + <name name="supports" arity="2" clause_i="2"/> + <name name="supports" arity="2" clause_i="3"/> + <name name="supports" arity="2" clause_i="4"/> + <name name="supports" arity="2" clause_i="5"/> + <name name="supports" arity="2" clause_i="6"/> + <name name="supports" arity="2" clause_i="7"/> + <name name="supports" arity="3" clause_i="1"/> + <name name="supports" arity="3" clause_i="2"/> + <name name="supports" arity="3" clause_i="3"/> + <name name="supports" arity="3" clause_i="4"/> + <name name="supports" arity="3" clause_i="5"/> + <name name="supports" arity="3" clause_i="6"/> + <name name="supports" arity="3" clause_i="7"/> + <fsummary>Report info about what the platform supports.</fsummary> + <desc> + <p>This function intends to retreive information about what the + platform supports. Such as if SCTP is supported. Or which socket + options are supported. </p> + </desc> + </func> + + </funcs> + <section> + <title>Examples</title> + <marker id="examples"></marker> + <p>TBD</p> + </section> +</erlref> + diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml new file mode 100644 index 0000000000..756f0dbd44 --- /dev/null +++ b/erts/doc/src/socket_usage.xml @@ -0,0 +1,765 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2018</year><year>2019</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>Socket Usage</title> + <prepared>Micael Karlberg</prepared> + <docno></docno> + <date>2018-07-17</date> + <rev>PA1</rev> + <file>socket_usage.xml</file> + </header> + + <section> + <title>Introduction</title> + <p>The socket interface (module) is basically an "thin" layer on top of + the OS socket interface. It is assumed that, unless you have special needs, + gen_[tcp|udp|sctp] should be sufficent. </p> + <p>Note that just because we have a documented and described option, + it does <em>not</em> mean that the OS supports it. So its recommended + that the user reads the platform specific documentation for the + option used. </p> + </section> + + <section> + <title>Socket Options</title> + + <p>Options for level <c>otp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>assoc_id</cell> + <cell>integer()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>type = seqpacket, protocol = sctp, is an association</cell> + </row> + <row> + <cell>debug</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>iow</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>controlling_process</cell> + <cell>pid()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvbuf</cell> + <cell>default | pos_integer() | {pos_integer(), pos_ineteger()}</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>'default' only valid for set. + The tuple form is only valid for type 'stream' and protocol 'tcp'.</cell> + </row> + <row> + <cell>rcvctrlbuf</cell> + <cell>default | pos_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>default only valid for set</cell> + </row> + <row> + <cell>sndctrlbuf</cell> + <cell>default | pos_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>default only valid for set</cell> + </row> + <row> + <cell>fd</cell> + <cell>integer()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + </table> + + <p>Options for level <c>socket</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>acceptcon</cell> + <cell>boolean()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>bindtodevice</cell> + <cell>string()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>broadcast</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram</cell> + </row> + <row> + <cell>debug</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>requires admin capability</cell> + </row> + <row> + <cell>domain</cell> + <cell>domain()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell><em>Not</em> on FreeBSD (for instance)</cell> + </row> + <row> + <cell>dontroute</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>keepalive</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>linger</cell> + <cell>abort | linger()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>oobinline</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>peek_off</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>domain = local (unix)</cell> + </row> + <row> + <cell>priority</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>protocol</cell> + <cell>protocol()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvbuf</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvlowat</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rcvtimeo</cell> + <cell>timeval()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>reuseaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>reuseport</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>domain = inet | inet6</cell> + </row> + <row> + <cell>sndbuf</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>sndlowat</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>not changeable on Linux</cell> + </row> + <row> + <cell>sndtimeo</cell> + <cell>timeval()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>timestamp</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>type</cell> + <cell>type()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + </table> + + <p>Options for level <c>ip</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>add_membership</cell> + <cell>ip_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>add_source_membership</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>block_source</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>drop_membership</cell> + <cell>ip_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>drop_source_membership</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>freebind</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>hdrincl</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>minttl</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>msfilter</cell> + <cell>null | ip_msfilter()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>mtu</cell> + <cell>integer()</cell> + <cell>no</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>mtu_discover</cell> + <cell>ip_pmtudisc()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_all</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_if</cell> + <cell>any | ip4_address()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_loop</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_ttl</cell> + <cell>uint8()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>nodefrag</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>pktinfo</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram</cell> + </row> + <row> + <cell>recvdstaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram</cell> + </row> + <row> + <cell>recverr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recvif</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw</cell> + </row> + <row> + <cell>recvopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type =/= stream</cell> + </row> + <row> + <cell>recvorigdstaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recvttl</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type =/= stream</cell> + </row> + <row> + <cell>retopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type =/= stream</cell> + </row> + <row> + <cell>router_alert</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>sendsrcaddr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>tos</cell> + <cell>ip_tos()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>some high-priority levels may require superuser capability</cell> + </row> + <row> + <cell>transparent</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>requires admin capability</cell> + </row> + <row> + <cell>ttl</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>unblock_source</cell> + <cell>ip_mreq_source()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + </table> + + <p>Options for level <c>ipv6</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>addrform</cell> + <cell>inet</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>allowed only for IPv6 sockets that are connected and bound to a + v4-mapped-on-v6 address</cell> + </row> + <row> + <cell>add_membership</cell> + <cell>ipv6_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>authhdr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, obsolete?</cell> + </row> + <row> + <cell>drop_membership</cell> + <cell>ipv6_mreq()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>dstopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>flowinfo</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>hoplimit</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>hopopts</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>mtu</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>Get: Only after the socket has been connected</cell> + </row> + <row> + <cell>mtu_discover</cell> + <cell>ipv6_pmtudisc()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_hops</cell> + <cell>default | uint8()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>multicast_if</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw</cell> + </row> + <row> + <cell>multicast_loop</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recverr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>recvpktinfo | pktinfo</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw</cell> + </row> + <row> + <cell>router_alert</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = raw</cell> + </row> + <row> + <cell>rthdr</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>type = dgram | raw, requires superuser privileges to update</cell> + </row> + <row> + <cell>unicast_hops</cell> + <cell>default | uint8()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>v6only</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + </table> + + <p>Options for level <c>tcp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>congestion</cell> + <cell>string()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>maxseg</cell> + <cell>integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>nodelay</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + </table> + + <p>Options for level <c>udp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>cork</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + </table> + + <p>Options for level <c>sctp</c>: </p> + <table> + <row> + <cell><em>Option Name</em></cell> + <cell><em>Value Type</em></cell> + <cell><em>Set</em></cell> + <cell><em>Get</em></cell> + <cell><em>Other Requirements and comments</em></cell> + </row> + <row> + <cell>associnfo</cell> + <cell>sctp_assocparams()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>autoclose</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>disable_fragments</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>events</cell> + <cell>sctp_event_subscribe()</cell> + <cell>yes</cell> + <cell>no</cell> + <cell>none</cell> + </row> + <row> + <cell>initmsg</cell> + <cell>sctp_initmsg()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>maxseg</cell> + <cell>non_neg_integer()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>nodelay</cell> + <cell>boolean()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + <row> + <cell>rtoinfo</cell> + <cell>sctp_rtoinfo()</cell> + <cell>yes</cell> + <cell>yes</cell> + <cell>none</cell> + </row> + </table> + + </section> +</chapter> + diff --git a/erts/doc/src/specs.xml b/erts/doc/src/specs.xml index 0b943e6295..68fab5edf1 100644 --- a/erts/doc/src/specs.xml +++ b/erts/doc/src/specs.xml @@ -5,6 +5,8 @@ <xi:include href="../specs/specs_erl_tracer.xml"/> <xi:include href="../specs/specs_init.xml"/> <xi:include href="../specs/specs_persistent_term.xml"/> + <xi:include href="../specs/specs_socket.xml"/> + <xi:include href="../specs/specs_net.xml"/> <xi:include href="../specs/specs_zlib.xml"/> <xi:include href="../specs/specs_atomics.xml"/> <xi:include href="../specs/specs_counters.xml"/> diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index b74f8371f5..64e4f2f4e4 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -1,7 +1,8 @@ + # # %CopyrightBegin% # -# Copyright Ericsson AB 1996-2018. All Rights Reserved. +# Copyright Ericsson AB 1996-2019. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -641,6 +642,8 @@ PRELOAD_BEAM = $(ERL_TOP)/erts/preloaded/ebin/erts_code_purger.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_inet.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_file.beam \ $(ERL_TOP)/erts/preloaded/ebin/zlib.beam \ + $(ERL_TOP)/erts/preloaded/ebin/socket.beam \ + $(ERL_TOP)/erts/preloaded/ebin/net.beam \ $(ERL_TOP)/erts/preloaded/ebin/prim_zip.beam \ $(ERL_TOP)/erts/preloaded/ebin/erl_prim_loader.beam \ $(ERL_TOP)/erts/preloaded/ebin/erlang.beam \ @@ -833,6 +836,18 @@ EMU_OBJS = \ $(OBJDIR)/beam_catches.o $(OBJDIR)/code_ix.o \ $(OBJDIR)/beam_ranges.o +ifneq ($(TARGET), win32) +# These are *currently* only needed for non-win32, +# since the nif-functions for socket and net are basically +# stubbed with nosup in the win32 case. +ESOCK_RUN_OBJS = \ + $(OBJDIR)/socket_dbg.o \ + $(OBJDIR)/socket_tarray.o \ + $(OBJDIR)/socket_util.o +else +ESOCK_RUN_OBJS = +endif + RUN_OBJS += \ $(OBJDIR)/erl_alloc.o $(OBJDIR)/erl_mtrace.o \ $(OBJDIR)/erl_alloc_util.o $(OBJDIR)/erl_goodfit_alloc.o \ @@ -865,7 +880,7 @@ RUN_OBJS += \ $(OBJDIR)/register.o $(OBJDIR)/break.o \ $(OBJDIR)/erl_async.o $(OBJDIR)/erl_lock_check.o \ $(OBJDIR)/erl_gc.o $(OBJDIR)/erl_lock_count.o \ - $(OBJDIR)/erl_posix_str.o \ + $(OBJDIR)/erl_posix_str.o \ $(OBJDIR)/erl_bits.o $(OBJDIR)/erl_math.o \ $(OBJDIR)/erl_fun.o $(OBJDIR)/erl_bif_port.o \ $(OBJDIR)/erl_term.o $(OBJDIR)/erl_node_tables.o \ @@ -879,14 +894,18 @@ RUN_OBJS += \ $(OBJDIR)/erl_thr_queue.o $(OBJDIR)/erl_sched_spec_pre_alloc.o \ $(OBJDIR)/erl_ptab.o $(OBJDIR)/erl_map.o \ $(OBJDIR)/erl_msacc.o $(OBJDIR)/erl_lock_flags.o \ - $(OBJDIR)/erl_io_queue.o $(OBJDIR)/erl_db_catree.o + $(OBJDIR)/erl_io_queue.o $(OBJDIR)/erl_db_catree.o \ + $(ESOCK_RUN_OBJS) LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o + NIF_OBJS = \ - $(OBJDIR)/erl_tracer_nif.o \ - $(OBJDIR)/prim_buffer_nif.o \ - $(OBJDIR)/prim_file_nif.o \ - $(OBJDIR)/zlib_nif.o + $(OBJDIR)/erl_tracer_nif.o \ + $(OBJDIR)/prim_buffer_nif.o \ + $(OBJDIR)/prim_file_nif.o \ + $(OBJDIR)/zlib_nif.o \ + $(OBJDIR)/socket_nif.o \ + $(OBJDIR)/net_nif.o ifeq ($(TARGET),win32) DRV_OBJS = \ @@ -1147,7 +1166,15 @@ endif BEAM_SRC=$(wildcard beam/*.c) DRV_COMMON_SRC=$(wildcard drivers/common/*.c) DRV_OSTYPE_SRC=$(wildcard drivers/$(ERLANG_OSTYPE)/*.c) +ifeq ($(TARGET), win32) +# These are *currently* only needed for non-win32, +# since the nif-functions for socket and net are basically +# stubbed with badarg in the win32 case. +NIF_SOCKET_UTILS_SRC=$(filter-out nifs/common/socket_nif.c, $(wildcard nifs/common/socket_*.c)) +NIF_COMMON_SRC=$(filter-out $(NIF_SOCKET_UTILS_SRC), $(wildcard nifs/common/*.c)) +else NIF_COMMON_SRC=$(wildcard nifs/common/*.c) +endif NIF_OSTYPE_SRC=$(wildcard nifs/$(ERLANG_OSTYPE)/*.c) ALL_SYS_SRC=$(wildcard sys/$(ERLANG_OSTYPE)/*.c) $(wildcard sys/common/*.c) # We use $(shell ls) here instead of wildcard as $(wildcard ) resolved at diff --git a/erts/emulator/nifs/common/net_nif.c b/erts/emulator/nifs/common/net_nif.c new file mode 100644 index 0000000000..d48112133f --- /dev/null +++ b/erts/emulator/nifs/common/net_nif.c @@ -0,0 +1,1683 @@ +/* + * %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% + * + * ---------------------------------------------------------------------- + * Purpose : The NIF (C) part of the net interface + * This is a module of miscellaneous functions. + * ---------------------------------------------------------------------- + * + */ + +#define STATIC_ERLANG_NIF 1 + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* If we HAVE_SCTP_H and Solaris, we need to define the following in + * order to get SCTP working: + */ +#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4)) +#define SOLARIS10 1 +/* WARNING: This is not quite correct, it may also be Solaris 11! */ +#define _XPG4_2 +#define __EXTENSIONS__ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <ctype.h> +#include <sys/types.h> +#include <errno.h> +#include <time.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif + +#ifdef HAVE_NET_IF_DL_H +#include <net/if_dl.h> +#endif + +#ifdef HAVE_IFADDRS_H +#include <ifaddrs.h> +#endif + +#ifdef HAVE_NETPACKET_PACKET_H +#include <netpacket/packet.h> +#endif + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +/* SENDFILE STUFF HERE IF WE NEED IT... */ + +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define __DARWIN__ 1 +#endif + + +#ifdef __WIN32__ +#define STRNCASECMP strncasecmp +#define INCL_WINSOCK_API_TYPEDEFS 1 + +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include <winsock2.h> +#endif +#include <windows.h> +#include <Ws2tcpip.h> /* NEED VC 6.0 or higher */ + +/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h + * to define the right structures. It needs to be set to WINXP (or LONGHORN) + * for IPV6 to work and it's set lower by default, so we need to change it. + */ +#ifdef HAVE_SDKDDKVER_H +# include <sdkddkver.h> +# ifdef NTDDI_VERSION +# undef NTDDI_VERSION +# endif +# define NTDDI_VERSION NTDDI_WINXP +#endif +#include <iphlpapi.h> + +#undef WANT_NONBLOCKING +#include "sys.h" + +#else /* !__WIN32__ */ + +#include <sys/time.h> +#ifdef NETDB_H_NEEDS_IN_H +#include <netinet/in.h> +#endif +#include <netdb.h> + +#include <sys/socket.h> +#include <netinet/in.h> + +#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H +#include <rpc/types.h> +#endif + +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> +#include <arpa/inet.h> + +#include <sys/param.h> +#ifdef HAVE_ARPA_NAMESER_H +#include <arpa/nameser.h> +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#include <net/if.h> + +#ifdef HAVE_SCHED_H +#include <sched.h> +#endif + +#ifdef HAVE_SETNS_H +#include <setns.h> +#endif + +#define HAVE_UDP + +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" + +#endif + +#include <erl_nif.h> + +#include "socket_dbg.h" +#include "socket_int.h" +#include "socket_util.h" + + +/* All platforms fail on malloc errors. */ +#define FATAL_MALLOC + + +#ifdef __WIN32__ +#define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__)) +#else +#define net_gethostname(__buf__, __bufSz__) gethostname((__buf__), (__bufSz__)) +#endif // __WIN32__ + + + +/* *** Misc macros and defines *** */ + +#ifdef __WIN32__ +#define get_errno() WSAGetLastError() +#else +#define get_errno() errno +#endif + + +#define HOSTNAME_LEN 256 +#define SERVICE_LEN 256 + + +/* MAXHOSTNAMELEN could be 64 or 255 depending + * on the platform. Instead, use INET_MAXHOSTNAMELEN + * which is always 255 across all platforms + */ +#define NET_MAXHOSTNAMELEN 255 + + +/* =================================================================== * + * * + * Various enif macros * + * * + * =================================================================== */ + + +#ifdef HAVE_SOCKLEN_T +# define SOCKLEN_T socklen_t +#else +# define SOCKLEN_T size_t +#endif + +/* Debug stuff... */ +#define NET_NIF_DEBUG_DEFAULT FALSE + +#define NDBG( proto ) ESOCK_DBG_PRINTF( data.debug , proto ) + + +typedef struct { + BOOLEAN_T debug; +} NetData; + + + +/* =================================================================== * + * * + * Static data * + * * + * =================================================================== */ + + +static NetData data; + + + +/* ---------------------------------------------------------------------- + * F o r w a r d s + * ---------------------------------------------------------------------- + */ + +/* THIS IS JUST TEMPORARY */ +extern char* erl_errno_id(int error); + + +static ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_command(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nif_gethostname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_if_names(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM ncommand(ErlNifEnv* env, + ERL_NIF_TERM cmd); +static ERL_NIF_TERM ngethostname(ErlNifEnv* env); +static ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, + const SocketAddress* saP, + SOCKLEN_T saLen, + int flags); +static ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, + char* host, + char* serv); +static ERL_NIF_TERM nif_name2index(ErlNifEnv* env, + char* ifn); +static ERL_NIF_TERM nif_index2name(ErlNifEnv* env, + unsigned int id); +static ERL_NIF_TERM nif_names(ErlNifEnv* env); +static unsigned int nif_names_length(struct if_nameindex* p); + +/* +static void net_dtor(ErlNifEnv* env, void* obj); +static void net_stop(ErlNifEnv* env, + void* obj, + int fd, + int is_direct_call); +static void net_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon); +*/ + +static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags); +static BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags); +static +BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP); +static ERL_NIF_TERM decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eBool, + BOOLEAN_T* bool); +static ERL_NIF_TERM encode_address_infos(ErlNifEnv* env, + struct addrinfo* addrInfo); +static ERL_NIF_TERM encode_address_info(ErlNifEnv* env, + struct addrinfo* addrInfoP); +static unsigned int address_info_length(struct addrinfo* addrInfoP); + +static ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env, + int family); +static ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, + int socktype); +static ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, + int proto); + +static char* make_address_info(ErlNifEnv* env, + ERL_NIF_TERM fam, + ERL_NIF_TERM sockType, + ERL_NIF_TERM proto, + ERL_NIF_TERM addr, + ERL_NIF_TERM* ai); + +static BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map); +static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); + + +#if HAVE_IN6 +# if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY +# if HAVE_DECL_IN6ADDR_ANY_INIT +static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } }; +# else +static const struct in6_addr in6addr_any = + { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }; +# endif /* HAVE_IN6ADDR_ANY_INIT */ +# endif /* ! HAVE_DECL_IN6ADDR_ANY */ + +# if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK +# if HAVE_DECL_IN6ADDR_LOOPBACK_INIT +static const struct in6_addr in6addr_loopback = + { { IN6ADDR_LOOPBACK_INIT } }; +# else +static const struct in6_addr in6addr_loopback = + { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }; +# endif /* HAVE_IN6ADDR_LOOPBACk_INIT */ +# endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */ +#endif /* HAVE_IN6 */ + + + +/* *** String constants *** */ +static char str_address_info[] = "address_info"; +static char str_debug[] = "debug"; +static char str_idn[] = "idn"; +static char str_idna_allow_unassigned[] = "idna_allow_unassigned"; +static char str_idna_use_std3_ascii_rules[] = "idna_use_std3_ascii_rules"; +static char str_namereqd[] = "namereqd"; +static char str_name_info[] = "name_info"; +static char str_nofqdn[] = "nofqdn"; +static char str_numerichost[] = "numerichost"; +static char str_numericserv[] = "numericserv"; + +/* (special) error string constants */ +static char str_eaddrfamily[] = "eaddrfamily"; +static char str_ebadflags[] = "ebadflags"; +static char str_efail[] = "efail"; +static char str_efamily[] = "efamily"; +static char str_efault[] = "efault"; +static char str_emem[] = "emem"; +static char str_enametoolong[] = "enametoolong"; +static char str_enodata[] = "enodata"; +static char str_enoname[] = "enoname"; +static char str_enxio[] = "enxio"; +static char str_eoverflow[] = "eoverflow"; +static char str_eservice[] = "eservice"; +static char str_esocktype[] = "esocktype"; +static char str_esystem[] = "esystem"; + + +/* *** Atoms *** */ + +static ERL_NIF_TERM atom_address_info; +static ERL_NIF_TERM atom_debug; +static ERL_NIF_TERM atom_idn; +static ERL_NIF_TERM atom_idna_allow_unassigned; +static ERL_NIF_TERM atom_idna_use_std3_ascii_rules; +static ERL_NIF_TERM atom_namereqd; +static ERL_NIF_TERM atom_name_info; +static ERL_NIF_TERM atom_nofqdn; +static ERL_NIF_TERM atom_numerichost; +static ERL_NIF_TERM atom_numericserv; + + +static ERL_NIF_TERM atom_eaddrfamily; +// static ERL_NIF_TERM atom_eagain; +static ERL_NIF_TERM atom_ebadflags; +static ERL_NIF_TERM atom_efail; +static ERL_NIF_TERM atom_efamily; +static ERL_NIF_TERM atom_efault; +static ERL_NIF_TERM atom_emem; +static ERL_NIF_TERM atom_enametoolong; +static ERL_NIF_TERM atom_enodata; +static ERL_NIF_TERM atom_enoname; +static ERL_NIF_TERM atom_enxio; +static ERL_NIF_TERM atom_eoverflow; +static ERL_NIF_TERM atom_eservice; +static ERL_NIF_TERM atom_esocktype; +static ERL_NIF_TERM atom_esystem; + + +/* *** net *** */ +static ErlNifResourceType* net; +static ErlNifResourceTypeInit netInit = { + NULL, // net_dtor, + NULL, // net_stop, + NULL // (ErlNifResourceDown*) net_down +}; + + + +/* ---------------------------------------------------------------------- + * N I F F u n c t i o n s + * ---------------------------------------------------------------------- + * + * Utility and admin functions: + * ---------------------------- + * nif_info/0 + * nif_command/1 + * + * The "proper" net functions: + * ------------------------------ + * nif_gethostname/0 + * nif_getnameinfo/2 + * nif_getaddrinfo/3 + * nif_if_name2index/1 + * nif_if_index2name/1 + * nif_if_names/0 + * + */ + + +/* ---------------------------------------------------------------------- + * nif_info + * + * Description: + * This is currently just a placeholder... + */ +static +ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM info, tmp; + + NDBG( ("NET", "info -> entry\r\n") ); + + tmp = enif_make_new_map(env); + if (!enif_make_map_put(env, tmp, atom_debug, BOOL2ATOM(data.debug), &info)) + info = tmp; + + NDBG( ("NET", "info -> done: %T\r\n", info) ); + + return info; +#endif +} + + + +/* ---------------------------------------------------------------------- + * nif_command + * + * Description: + * This is a general purpose utility function. + * + * Arguments: + * Command - This is a general purpose command, of any type. + * Currently, the only supported command is: + * + * {debug, boolean()} + */ +static +ERL_NIF_TERM nif_command(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM ecmd, result; + + NDBG( ("NET", "command -> entry (%d)\r\n", argc) ); + + if (argc != 1) + return enif_make_badarg(env); + + ecmd = argv[0]; + + NDBG( ("NET", "command -> ecmd: %T\r\n", ecmd) ); + + result = ncommand(env, ecmd); + + NDBG( ("NET", "command -> result: %T\r\n", result) ); + + return result; +#endif +} + + + +/* + * The command can, in principle, be anything, though currently we only + * support a debug command. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM ncommand(ErlNifEnv* env, + ERL_NIF_TERM cmd) +{ + const ERL_NIF_TERM* t; + int tsz; + + if (IS_TUPLE(env, cmd)) { + /* Could be the debug tuple */ + if (!GET_TUPLE(env, cmd, &tsz, &t)) + return esock_make_error(env, esock_atom_einval); + + if (tsz != 2) + return esock_make_error(env, esock_atom_einval); + + /* First element should be the atom 'debug' */ + if (COMPARE(t[0], atom_debug) != 0) + return esock_make_error(env, esock_atom_einval); + + return decode_bool(env, t[1], &data.debug); + + } else { + return esock_make_error(env, esock_atom_einval); + } + +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_gethostname + * + * Description: + * Access the hostname of the current processor. + * + */ +static +ERL_NIF_TERM nif_gethostname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM result; + + NDBG( ("NET", "nif_gethostname -> entry (%d)\r\n", argc) ); + + if (argc != 0) + return enif_make_badarg(env); + + result = ngethostname(env); + + NDBG( ("NET", "nif_gethostname -> done when result: %T\r\n", result) ); + + return result; +#endif +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM ngethostname(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + char buf[NET_MAXHOSTNAMELEN + 1]; + int res; + + res = net_gethostname(buf, sizeof(buf)); + + NDBG( ("NET", "ngethostname -> gethostname res: %d\r\n", res) ); + + switch (res) { + case 0: + result = esock_make_ok2(env, MKS(env, buf)); + break; + + case EFAULT: + result = esock_make_error(env, atom_efault); + break; + + case EINVAL: + result = esock_make_error(env, esock_atom_einval); + break; + + case ENAMETOOLONG: + result = esock_make_error(env, atom_enametoolong); + break; + + default: + result = esock_make_error(env, MKI(env, res)); + break; + } + + return result; +} +#endif + + + + +/* ---------------------------------------------------------------------- + * nif_getnameinfo + * + * Description: + * Address-to-name translation in protocol-independent manner. + * + * Arguments: + * SockAddr - Socket Address (address and port) + * Flags - The flags argument modifies the behavior of getnameinfo(). + */ + +static +ERL_NIF_TERM nif_getnameinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM result; + ERL_NIF_TERM eSockAddr, eFlags; + int flags = 0; // Just in case... + SocketAddress sa; + SOCKLEN_T saLen = 0; // Just in case... + + NDBG( ("NET", "nif_getnameinfo -> entry (%d)\r\n", argc) ); + + if (argc != 2) + return enif_make_badarg(env); + eSockAddr = argv[0]; + eFlags = argv[1]; + + NDBG( ("NET", + "nif_getnameinfo -> " + "\r\n SockAddr: %T" + "\r\n Flags: %T" + "\r\n", eSockAddr, eFlags) ); + + if (!esock_decode_sockaddr(env, eSockAddr, &sa, &saLen)) + return enif_make_badarg(env); + + if (!decode_nameinfo_flags(env, eFlags, &flags)) + return enif_make_badarg(env); + + result = ngetnameinfo(env, &sa, saLen, flags); + + NDBG( ("NET", + "nif_getnameinfo -> done when result: " + "\r\n %T\r\n", result) ); + + return result; +#endif +} + + + +/* Given the provided sock(et) address (and honts), retreive the host and + * service info. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM ngetnameinfo(ErlNifEnv* env, + const SocketAddress* saP, + SOCKLEN_T saLen, + int flags) +{ + ERL_NIF_TERM result; + char host[HOSTNAME_LEN]; + SOCKLEN_T hostLen = sizeof(host); + char serv[SERVICE_LEN]; + SOCKLEN_T servLen = sizeof(serv); + + int res = getnameinfo((struct sockaddr*) saP, saLen, + host, hostLen, + serv, servLen, + flags); + + NDBG( ("NET", "ngetnameinfo -> res: %d\r\n", res) ); + + switch (res) { + case 0: + { + ERL_NIF_TERM info = MKT3(env, + atom_name_info, + MKS(env, host), + MKS(env, serv)); + result = esock_make_ok2(env, info); + } + break; + +#if defined(EAI_AGAIN) + case EAI_AGAIN: + result = esock_make_error(env, esock_atom_eagain); + break; +#endif + +#if defined(EAI_BADFLAGS) + case EAI_BADFLAGS: + result = esock_make_error(env, atom_ebadflags); + break; +#endif + +#if defined(EAI_FAIL) + case EAI_FAIL: + result = esock_make_error(env, atom_efail); + break; +#endif + +#if defined(EAI_FAMILY) + case EAI_FAMILY: + result = esock_make_error(env, atom_efamily); + break; +#endif + +#if defined(EAI_MEMORY) + case EAI_MEMORY: + result = esock_make_error(env, atom_emem); + break; +#endif + +#if defined(EAI_NONAME) + case EAI_NONAME: + result = esock_make_error(env, atom_enoname); + break; +#endif + +#if defined(EAI_OVERFLOW) + case EAI_OVERFLOW: + result = esock_make_error(env, atom_eoverflow); + break; +#endif + +#if defined(EAI_SYSTEM) + case EAI_SYSTEM: + result = esock_make_error_errno(env, get_errno()); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_getaddrinfo + * + * Description: + * Network address and service translation. + * + * Arguments: + * Host - Host name (either a string or the atom undefined) + * Service - Service name (either a string or the atom undefined) + * Hints - Hints for the lookup (address info record) (currently *ignored*) + */ + +static +ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM result, eHostName, eServName; //, eHints; + char* hostName; + char* servName; + // struct addrinfo* hints; + + NDBG( ("NET", "nif_getaddrinfo -> entry (%d)\r\n", argc) ); + + if (argc != 3) { + return enif_make_badarg(env); + } + eHostName = argv[0]; + eServName = argv[1]; + // eHints = argv[2]; + + NDBG( ("NET", + "nif_getaddrinfo -> " + "\r\n ehost: %T" + "\r\n eservice: %T" + "\r\n ehints: %T" + "\r\n", argv[0], argv[1], argv[2]) ); + + if (!decode_addrinfo_string(env, eHostName, &hostName)) + return enif_make_badarg(env); + + if (!decode_addrinfo_string(env, eServName, &servName)) + return enif_make_badarg(env); + + /* + if (decode_addrinfo_hints(env, eHints, &hints)) + return enif_make_badarg(env); + */ + + if ((hostName == NULL) && (servName == NULL)) + return enif_make_badarg(env); + + result = ngetaddrinfo(env, hostName, servName); + + if (hostName != NULL) + FREE(hostName); + + if (servName != NULL) + FREE(servName); + + /* + if (hints != NULL) + FREE(hints); + */ + + NDBG( ("NET", + "nif_getaddrinfo -> done when result: " + "\r\n %T\r\n", result) ); + + return result; +#endif +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM ngetaddrinfo(ErlNifEnv* env, + char* host, + char* serv) +{ + ERL_NIF_TERM result; + struct addrinfo* addrInfoP; + int res; + + NDBG( ("NET", "ngetaddrinfo -> entry with" + "\r\n host: %s" + "\r\n serv: %s" + "\r\n", + ((host == NULL) ? "NULL" : host), + ((serv == NULL) ? "NULL" : serv)) ); + + res = getaddrinfo(host, serv, NULL, &addrInfoP); + + NDBG( ("NET", "ngetaddrinfo -> res: %d\r\n", res) ); + + switch (res) { + case 0: + { + ERL_NIF_TERM addrInfo = encode_address_infos(env, addrInfoP); + freeaddrinfo(addrInfoP); + result = esock_make_ok2(env, addrInfo); + } + break; + +#if defined(EAI_ADDRFAMILY) + case EAI_ADDRFAMILY: + result = esock_make_error(env, atom_eaddrfamily); + break; +#endif + +#if defined(EAI_AGAIN) + case EAI_AGAIN: + result = esock_make_error(env, esock_atom_eagain); + break; +#endif + +#if defined(EAI_BADFLAGS) + case EAI_BADFLAGS: + result = esock_make_error(env, atom_ebadflags); + break; +#endif + +#if defined(EAI_FAIL) + case EAI_FAIL: + result = esock_make_error(env, atom_efail); + break; +#endif + +#if defined(EAI_FAMILY) + case EAI_FAMILY: + result = esock_make_error(env, atom_efamily); + break; +#endif + +#if defined(EAI_MEMORY) + case EAI_MEMORY: + result = esock_make_error(env, atom_emem); + break; +#endif + +#if defined(EAI_NODATA) + case EAI_NODATA: + result = esock_make_error(env, atom_enodata); + break; +#endif + +#if defined(EAI_NONAME) + case EAI_NONAME: + result = esock_make_error(env, atom_enoname); + break; +#endif + +#if defined(EAI_SERVICE) + case EAI_SERVICE: + result = esock_make_error(env, atom_eservice); + break; +#endif + +#if defined(EAI_SOCKTYPE) + case EAI_SOCKTYPE: + result = esock_make_error(env, atom_esocktype); + break; +#endif + +#if defined(EAI_SYSTEM) + case EAI_SYSTEM: + result = esock_make_error(env, atom_esystem); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_if_name2index + * + * Description: + * Perform a Interface Name to Interface Index translation. + * + * Arguments: + * Ifn - Interface name to be translated. + */ + +static +ERL_NIF_TERM nif_if_name2index(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM eifn, result; + char ifn[IF_NAMESIZE+1]; + + NDBG( ("NET", "nif_if_name2index -> entry (%d)\r\n", argc) ); + + if (argc != 1) { + return enif_make_badarg(env); + } + eifn = argv[0]; + + NDBG( ("NET", + "nif_if_name2index -> " + "\r\n Ifn: %T" + "\r\n", argv[0]) ); + + if (0 >= GET_STR(env, eifn, ifn, sizeof(ifn))) + return esock_make_error(env, esock_atom_einval); + + result = nif_name2index(env, ifn); + + NDBG( ("NET", "nif_if_name2index -> done when result: %T\r\n", result) ); + + return result; +#endif +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nif_name2index(ErlNifEnv* env, + char* ifn) +{ + unsigned int idx; + + NDBG( ("NET", "nif_name2index -> entry with ifn: %s\r\n", ifn) ); + + idx = if_nametoindex(ifn); + + NDBG( ("NET", "nif_name2index -> idx: %d\r\n", idx) ); + + if (idx == 0) { + int save_errno = get_errno(); + NDBG( ("NET", "nif_name2index -> failed: %d\r\n", save_errno) ); + return esock_make_error_errno(env, save_errno); + } else { + return esock_make_ok2(env, MKI(env, idx)); + } + +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_if_index2name + * + * Description: + * Perform a Interface Index to Interface Name translation. + * + * Arguments: + * Idx - Interface index to be translated. + */ + +static +ERL_NIF_TERM nif_if_index2name(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM result; + unsigned int idx; + + NDBG( ("NET", "nif_if_index2name -> entry (%d)\r\n", argc) ); + + if ((argc != 1) || + !GET_UINT(env, argv[0], &idx)) { + return enif_make_badarg(env); + } + + NDBG( ("NET", "nif_index2name -> " + "\r\n Idx: %T" + "\r\n", argv[0]) ); + + result = nif_index2name(env, idx); + + NDBG( ("NET", "nif_if_index2name -> done when result: %T\r\n", result) ); + + return result; +#endif +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nif_index2name(ErlNifEnv* env, + unsigned int idx) +{ + ERL_NIF_TERM result; + char* ifn = MALLOC(IF_NAMESIZE+1); + + if (ifn == NULL) + return enif_make_badarg(env); // PLACEHOLDER + + if (NULL != if_indextoname(idx, ifn)) { + result = esock_make_ok2(env, MKS(env, ifn)); + } else { + result = esock_make_error(env, atom_enxio); + } + + FREE(ifn); + + return result; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_if_names + * + * Description: + * Get network interface names and indexes. + * + */ + +static +ERL_NIF_TERM nif_if_names(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM result; + + NDBG( ("NET", "nif_if_names -> entry (%d)\r\n", argc) ); + + if (argc != 0) { + return enif_make_badarg(env); + } + + result = nif_names(env); + + NDBG( ("NET", "nif_if_names -> done when result: %T\r\n", result) ); + + return result; +#endif +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nif_names(ErlNifEnv* env) +{ + ERL_NIF_TERM result; + struct if_nameindex* ifs = if_nameindex(); + + NDBG( ("NET", "nif_names -> ifs: 0x%lX\r\n", ifs) ); + + if (ifs == NULL) { + result = esock_make_error_errno(env, get_errno()); + } else { + /* + * We got some interfaces: + * 1) Calculate how many - the only way is to iterate through the list + * until its end (which is indicated by an entry with index = zero + * and if_name = NULL). + * 2) Allocate an ERL_NIF_TERM array of the calculated length. + * 3) Iterate through the array of interfaces and for each create + * a two tuple: {Idx, If} + * + * Or shall we instead build a list in reverse order and then when + * its done, reverse that? Check + */ + unsigned int len = nif_names_length(ifs); + + NDBG( ("NET", "nif_names -> len: %d\r\n", len) ); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i; + + for (i = 0; i < len; i++) { + array[i] = MKT2(env, + MKI(env, ifs[i].if_index), + MKS(env, ifs[i].if_name)); + } + + result = esock_make_ok2(env, MKLA(env, array, len)); + FREE(array); + } else { + result = esock_make_ok2(env, enif_make_list(env, 0)); + } + } + + if (ifs != NULL) + if_freenameindex(ifs); + + return result; +} + + +static +unsigned int nif_names_length(struct if_nameindex* p) +{ + unsigned int len = 0; + BOOLEAN_T done = FALSE; + + while (!done) { + + NDBG( ("NET", "nif_names_length -> %d: " + "\r\n if_index: %d" + "\r\n if_name: 0x%lX" + "\r\n", len, p[len].if_index, p[len].if_name) ); + + if ((p[len].if_index == 0) && (p[len].if_name == NULL)) + done = TRUE; + else + len++; + } + + return len; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * U t i l i t y F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* The erlang format for a set of flags is a list of atoms. + * A special case is when there is no flags, which is + * represented by the atom undefined. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags) +{ + BOOLEAN_T result; + + if (IS_ATOM(env, eflags)) { + if (COMPARE(eflags, esock_atom_undefined) == 0) { + *flags = 0; + result = TRUE; + } else { + result = FALSE; + } + } else if (IS_LIST(env, eflags)) { + NDBG( ("NET", "decode_nameinfo_flags -> is atom\r\n") ); + result = decode_nameinfo_flags_list(env, eflags, flags); + } else { + result = FALSE; + } + + return result; +} + + + +static +BOOLEAN_T decode_nameinfo_flags_list(ErlNifEnv* env, + const ERL_NIF_TERM eflags, + int* flags) +{ + ERL_NIF_TERM elem, tail, list = eflags; + int tmp = 0; + BOOLEAN_T done = FALSE; + + while (!done) { + if (GET_LIST_ELEM(env, list, &elem, &tail)) { + if (COMPARE(elem, atom_namereqd) == 0) { + tmp |= NI_NAMEREQD; + } else if (COMPARE(elem, esock_atom_dgram) == 0) { + tmp |= NI_DGRAM; + } else if (COMPARE(elem, atom_nofqdn) == 0) { + tmp |= NI_NOFQDN; + } else if (COMPARE(elem, atom_numerichost) == 0) { + tmp |= NI_NUMERICHOST; + } else if (COMPARE(elem, atom_numericserv) == 0) { + tmp |= NI_NUMERICSERV; + + /* Starting with glibc 2.3.4: */ + +#if defined(NI_IDN) + } else if (COMPARE(elem, atom_idn) == 0) { + tmp |= NI_IDN; +#endif + +#if defined(NI_IDN_ALLOW_UNASSIGNED) + } else if (COMPARE(elem, atom_idna_allow_unassigned) == 0) { + tmp |= NI_IDN_ALLOW_UNASSIGNED; +#endif + +#if defined(NI_IDN_USE_STD3_ASCII_RULES) + } else if (COMPARE(elem, atom_idna_use_std3_ascii_rules) == 0) { + tmp |= NI_IDN_USE_STD3_ASCII_RULES; +#endif + + } else { + return FALSE; + } + + list = tail; + + } else { + done = TRUE; + } + } + + *flags = tmp; + + return TRUE; +} + + + +/* Decode the address info string (hostname or service name) + * The string is either the atom undefined or an actual string. + */ +static +BOOLEAN_T decode_addrinfo_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP) +{ + BOOLEAN_T result; + + if (IS_ATOM(env, eString)) { + + if (COMPARE(eString, esock_atom_undefined) == 0) { + *stringP = NULL; + result = TRUE; + } else { + *stringP = NULL; + result = FALSE; + } + + } else { + + result = esock_decode_string(env, eString, stringP); + + } + + return result; + +} + + + +static +ERL_NIF_TERM decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eBool, + BOOLEAN_T* bool) +{ + if (COMPARE(eBool, esock_atom_true) == 0) { + *bool = TRUE; + return esock_atom_ok; + } else if (COMPARE(eBool, esock_atom_false) == 0) { + *bool = FALSE; + return esock_atom_ok; + } else { + return esock_make_error(env, esock_atom_einval); + } +} + + + +/* Encode the address info + * The address info is a linked list och address info, which + * will result in the result being a list of zero or more length. + */ +static +ERL_NIF_TERM encode_address_infos(ErlNifEnv* env, + struct addrinfo* addrInfo) +{ + ERL_NIF_TERM result; + unsigned int len = address_info_length(addrInfo); + + NDBG( ("NET", "encode_address_infos -> len: %d\r\n", len) ); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); // LEAK? + unsigned int i = 0; + struct addrinfo* p = addrInfo; + + while (i < len) { + array[i] = encode_address_info(env, p); + p = p->ai_next; + i++; + } + + result = MKLA(env, array, len); + } else { + result = MKEL(env); + } + + NDBG( ("NET", "encode_address_infos -> result: " + "\r\n %T\r\n", result) ); + + return result; +} + + + +/* Calculate the length of the adress info linked list + * The list is NULL-terminated, so the only way is to + * iterate through the list until we find next = NULL. + */ +static +unsigned int address_info_length(struct addrinfo* addrInfoP) +{ + unsigned int len = 1; + struct addrinfo* tmp; + BOOLEAN_T done = FALSE; + + tmp = addrInfoP; + + while (!done) { + if (tmp->ai_next != NULL) { + len++; + tmp = tmp->ai_next; + } else { + done = TRUE; + } + } + + return len; +} + + + +/* Create one (erlang) instance of the address info record + * Should we have address info as a record or as a map? + * + * {address_info, Fam, Type, Proto, Addr} + */ +static +ERL_NIF_TERM encode_address_info(ErlNifEnv* env, + struct addrinfo* addrInfoP) +{ + ERL_NIF_TERM fam, type, proto, addr, addrInfo; + + fam = encode_address_info_family(env, addrInfoP->ai_family); + type = encode_address_info_type(env, addrInfoP->ai_socktype); + proto = encode_address_info_proto(env, addrInfoP->ai_protocol); + esock_encode_sockaddr(env, + (SocketAddress*) addrInfoP->ai_addr, + addrInfoP->ai_addrlen, + &addr); + + if (make_address_info(env, fam, type, proto, addr, &addrInfo) == NULL) + return addrInfo; + else + return esock_atom_undefined; // We should to better... + +} + + +/* Convert an "native" family to an erlang family (=domain). + * Note that this is not currently exhaustive, but only supports + * inet and inet6. Other values will be returned as is, that is + * in the form of an integer. + */ +static +ERL_NIF_TERM encode_address_info_family(ErlNifEnv* env, + int family) +{ + ERL_NIF_TERM efam; + + if (NULL != esock_encode_domain(env, family, &efam)) + efam = MKI(env, family); + + return efam; +} + + + +/* Convert an "native" socket type to an erlang socket type. + * Note that this is not currently exhaustive, but only supports + * stream and dgram. Other values will be returned as is, that is + * in the form of an integer. + */ +static +ERL_NIF_TERM encode_address_info_type(ErlNifEnv* env, + int socktype) +{ + ERL_NIF_TERM etype; + + if (NULL != esock_encode_type(env, socktype, &etype)) + etype = MKI(env, socktype); + + return etype; +} + + + +/* Convert an "native" protocol to an erlang protocol. + * Note that this is not currently exhaustive, but only supports + * tcp and udp. Other values will be returned as is, that is + * in the form of an integer. + */ +static +ERL_NIF_TERM encode_address_info_proto(ErlNifEnv* env, + int proto) +{ + ERL_NIF_TERM eproto; + + if (NULL != esock_encode_protocol(env, proto, &eproto)) + eproto = MKI(env, proto); + + return eproto; +} + + + +static +char* make_address_info(ErlNifEnv* env, + ERL_NIF_TERM fam, + ERL_NIF_TERM sockType, + ERL_NIF_TERM proto, + ERL_NIF_TERM addr, + ERL_NIF_TERM* ai) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, + esock_atom_type, + esock_atom_protocol, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {fam, sockType, proto, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, ai)) { + *ai = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * C a l l b a c k F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* ========================================================================= + * net_dtor - Callback function for resource destructor + * + */ +/* +static +void net_dtor(ErlNifEnv* env, void* obj) +{ +} +*/ + + +/* ========================================================================= + * net_stop - Callback function for resource stop + * + */ +/* +static +void net_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) +{ +} +*/ + + + + +/* ========================================================================= + * net_down - Callback function for resource down (monitored processes) + * + */ +/* +static +void net_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon) +{ +} +*/ + + + +/* ---------------------------------------------------------------------- + * L o a d / u n l o a d / u p g r a d e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +ErlNifFunc net_funcs[] = +{ + // Some utility functions + {"nif_info", 0, nif_info, 0}, + {"nif_command", 1, nif_command, 0}, // Shall we let this be dirty? + + /* get/set hostname */ + {"nif_gethostname", 0, nif_gethostname, 0}, + + /* address and name translation in protocol-independent manner */ + {"nif_getnameinfo", 2, nif_getnameinfo, 0}, + {"nif_getaddrinfo", 3, nif_getaddrinfo, 0}, + + /* Network interface (name and/or index) functions */ + {"nif_if_name2index", 1, nif_if_name2index, 0}, + {"nif_if_index2name", 1, nif_if_index2name, 0}, + {"nif_if_names", 0, nif_if_names, 0} +}; + + +#if !defined(__WIN32__) +static +BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map) +{ + /* + * We need to do this here since the "proper" atom has not been + * created when this function is called. + */ + ERL_NIF_TERM debug = MKA(env, "debug"); + + return esock_extract_bool_from_map(env, map, debug, NET_NIF_DEBUG_DEFAULT); +} +#endif + + +/* ======================================================================= + * load_info - A map of misc info (e.g global debug) + */ + +static +int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ +#if !defined(__WIN32__) + // We should make it possible to use load_info to get default values + data.debug = extract_debug(env, load_info); + + NDBG( ("NET", "on_load -> entry\r\n") ); +#endif + + /* +++ Misc atoms +++ */ + atom_address_info = MKA(env, str_address_info); + atom_debug = MKA(env, str_debug); + atom_idn = MKA(env, str_idn); + atom_idna_allow_unassigned = MKA(env, str_idna_allow_unassigned); + atom_idna_use_std3_ascii_rules = MKA(env, str_idna_use_std3_ascii_rules); + atom_namereqd = MKA(env, str_namereqd); + atom_name_info = MKA(env, str_name_info); + atom_nofqdn = MKA(env, str_nofqdn); + atom_numerichost = MKA(env, str_numerichost); + atom_numericserv = MKA(env, str_numericserv); + + /* Error codes */ + atom_eaddrfamily = MKA(env, str_eaddrfamily); + atom_ebadflags = MKA(env, str_ebadflags); + atom_efail = MKA(env, str_efail); + atom_efamily = MKA(env, str_efamily); + atom_efault = MKA(env, str_efault); + atom_emem = MKA(env, str_emem); + atom_enametoolong = MKA(env, str_enametoolong); + atom_enodata = MKA(env, str_enodata); + atom_enoname = MKA(env, str_enoname); + atom_enxio = MKA(env, str_enxio); + atom_eoverflow = MKA(env, str_eoverflow); + atom_eservice = MKA(env, str_eservice); + atom_esocktype = MKA(env, str_esocktype); + atom_esystem = MKA(env, str_esystem); + + // For storing "global" things... + // data.env = enif_alloc_env(); // We should really check + // data.version = MKA(env, ERTS_VERSION); + // data.buildDate = MKA(env, ERTS_BUILD_DATE); + + net = enif_open_resource_type_x(env, + "net", + &netInit, + ERL_NIF_RT_CREATE, + NULL); + +#if !defined(__WIN32__) + NDBG( ("NET", "on_load -> done\r\n") ); +#endif + + return !net; +} + +ERL_NIF_INIT(net, net_funcs, on_load, NULL, NULL, NULL) diff --git a/erts/emulator/nifs/common/socket_dbg.c b/erts/emulator/nifs/common/socket_dbg.c new file mode 100644 index 0000000000..fe9135e5a0 --- /dev/null +++ b/erts/emulator/nifs/common/socket_dbg.c @@ -0,0 +1,138 @@ +/* + * %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% + * + * ---------------------------------------------------------------------- + * Purpose : Debug functions for the socket and net NIF(s). + * ---------------------------------------------------------------------- + * + */ + +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <time.h> + +#include <erl_nif.h> +#include "socket_dbg.h" + +#define TSELF() enif_thread_self() +#define TNAME(__T__) enif_thread_name( __T__ ) +#define TSNAME() TNAME(TSELF()) + +static FILE* dbgout = NULL; + +static int realtime(struct timespec* tsP); +static int timespec2str(char *buf, unsigned int len, struct timespec *ts); + + +extern +void esock_dbg_init(char* filename) +{ + if (filename != NULL) { + if (strcmp(filename, ESOCK_DBGOUT_DEFAULT) == 0) { + dbgout = stdout; + } else if (strcmp(filename, ESOCK_DBGOUT_UNIQUE) == 0) { + char template[] = "/tmp/esock-dbg-XXXXXX"; + dbgout = fdopen(mkstemp(template), "w+"); + } else { + dbgout = fopen(filename, "w+"); + } + } else { + char template[] = "/tmp/esock-dbg-XXXXXX"; + dbgout = fdopen(mkstemp(template), "w+"); + } +} + + + +/* + * Print a debug format string *with* both a timestamp and the + * the name of the *current* thread. + */ +extern +void esock_dbg_printf( const char* prefix, const char* format, ... ) +{ + va_list args; + char f[512 + sizeof(format)]; // This has to suffice... + char stamp[30]; + struct timespec ts; + int res; + + /* + * We should really include self in the printout, so we can se which process + * are executing the code. But then I must change the API.... + * ....something for later. + */ + + if (!realtime(&ts)) { + if (timespec2str(stamp, sizeof(stamp), &ts) != 0) { + res = enif_snprintf(f, sizeof(f), "%s [%s] %s", prefix, TSNAME(), format); + // res = enif_snprintf(f, sizeof(f), "%s [%s]", prefix, format); + } else { + res = enif_snprintf(f, sizeof(f), "%s [%s] [%s] %s", prefix, stamp, TSNAME(), format); + // res = enif_snprintf(f, sizeof(f), "%s [%s] %s", prefix, stamp, format); + } + + if (res > 0) { + va_start (args, format); + enif_vfprintf (dbgout, f, args); + va_end (args); + fflush(stdout); + } + } + + return; +} + + +static +int realtime(struct timespec* tsP) +{ + return clock_gettime(CLOCK_REALTIME, tsP); +} + + + + +/* + * Convert a timespec struct into a readable/printable string + */ +static +int timespec2str(char *buf, unsigned int len, struct timespec *ts) +{ + int ret, buflen; + struct tm t; + + tzset(); + if (localtime_r(&(ts->tv_sec), &t) == NULL) + return 1; + + ret = strftime(buf, len, "%F %T", &t); + if (ret == 0) + return 2; + len -= ret - 1; + buflen = strlen(buf); + + ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); + if (ret >= len) + return 3; + + return 0; +} diff --git a/erts/emulator/nifs/common/socket_dbg.h b/erts/emulator/nifs/common/socket_dbg.h new file mode 100644 index 0000000000..47739b46da --- /dev/null +++ b/erts/emulator/nifs/common/socket_dbg.h @@ -0,0 +1,55 @@ +/* + * %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% + * + * ---------------------------------------------------------------------- + * Purpose : Defines and macros for the debug util of the socket and + * net NIF(s). + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_DBG_H__ +#define SOCKET_DBG_H__ + +/* Used when calling the init function */ +#define ESOCK_DBGOUT_DEFAULT "stdout" +#define ESOCK_DBGOUT_UNIQUE "unique" + + +/* Used in debug printouts */ +#ifdef __WIN32__ +#define LLU "%I64u" +#else +#define LLU "%llu" +#endif +typedef unsigned long long llu_t; + + + +#define ESOCK_DBG_PRINTF( ___COND___ , proto ) \ + if ( ___COND___ ) { \ + esock_dbg_printf proto; \ + fflush(stdout); \ + } + + +extern void esock_dbg_init(char* filename); +extern void esock_dbg_printf( const char* prefix, const char* format, ... ); + +#endif // SOCKET_DBG_H__ diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h new file mode 100644 index 0000000000..0f973855ae --- /dev/null +++ b/erts/emulator/nifs/common/socket_int.h @@ -0,0 +1,387 @@ +/* + * %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% + * + * ---------------------------------------------------------------------- + * Purpose : Utility "stuff" for socket and net. + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_INT_H__ +#define SOCKET_INT_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef __WIN32__ + +/* All this just to replace sys/socket.h, netinet/in.h and sys/un.h??? */ +#define INCL_WINSOCK_API_TYPEDEFS 1 +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include <winsock2.h> +#endif +#include <windows.h> +#include <Ws2tcpip.h> /* NEED VC 6.0 or higher */ +/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h + * to define the right structures. It needs to be set to WINXP (or LONGHORN) + * for IPV6 to work and it's set lower by default, so we need to change it. + */ +#ifdef HAVE_SDKDDKVER_H +# include <sdkddkver.h> +# ifdef NTDDI_VERSION +# undef NTDDI_VERSION +# endif +# define NTDDI_VERSION NTDDI_WINXP +#endif +#include <iphlpapi.h> + +#else /* !__WIN32__ */ + +#include <sys/socket.h> +#include <netinet/in.h> +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +#endif + +#include <erl_nif.h> + +/* The general purpose sockaddr */ +typedef union { + /* General sockaddr */ + struct sockaddr sa; + + /* IPv4 sockaddr */ + struct sockaddr_in in4; + + /* IPv6 sockaddr */ +#if defined(HAVE_IN6) && defined(AF_INET6) + struct sockaddr_in6 in6; +#endif + + /* Unix Domain Socket sockaddr */ +#if defined(HAVE_SYS_UN_H) + struct sockaddr_un un; +#endif + +} SocketAddress; + + +/* *** Boolean *type* stuff... *** */ +typedef unsigned int BOOLEAN_T; +#define TRUE 1 +#define FALSE 0 + +#define BOOL2ATOM(__B__) ((__B__) ? esock_atom_true : esock_atom_false) + +#define B2S(__B__) ((__B__) ? "true" : "false") + +/* Misc error strings */ +#define ESOCK_STR_EAFNOSUPPORT "eafnosupport" +#define ESOCK_STR_EAGAIN "eagain" +#define ESOCK_STR_EINVAL "einval" + + +/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * "Global" atoms + */ +extern ERL_NIF_TERM esock_atom_abort; +extern ERL_NIF_TERM esock_atom_accept; +extern ERL_NIF_TERM esock_atom_acceptconn; +extern ERL_NIF_TERM esock_atom_acceptfilter; +extern ERL_NIF_TERM esock_atom_adaption_layer; +extern ERL_NIF_TERM esock_atom_addr; +extern ERL_NIF_TERM esock_atom_addrform; +extern ERL_NIF_TERM esock_atom_add_membership; +extern ERL_NIF_TERM esock_atom_add_source_membership; +extern ERL_NIF_TERM esock_atom_any; +extern ERL_NIF_TERM esock_atom_associnfo; +extern ERL_NIF_TERM esock_atom_authhdr; +extern ERL_NIF_TERM esock_atom_auth_active_key; +extern ERL_NIF_TERM esock_atom_auth_asconf; +extern ERL_NIF_TERM esock_atom_auth_chunk; +extern ERL_NIF_TERM esock_atom_auth_delete_key; +extern ERL_NIF_TERM esock_atom_auth_key; +extern ERL_NIF_TERM esock_atom_auth_level; +extern ERL_NIF_TERM esock_atom_autoclose; +extern ERL_NIF_TERM esock_atom_bindtodevice; +extern ERL_NIF_TERM esock_atom_block_source; +extern ERL_NIF_TERM esock_atom_broadcast; +extern ERL_NIF_TERM esock_atom_busy_poll; +extern ERL_NIF_TERM esock_atom_checksum; +extern ERL_NIF_TERM esock_atom_close; +extern ERL_NIF_TERM esock_atom_connect; +extern ERL_NIF_TERM esock_atom_congestion; +extern ERL_NIF_TERM esock_atom_context; +extern ERL_NIF_TERM esock_atom_cork; +extern ERL_NIF_TERM esock_atom_credentials; +extern ERL_NIF_TERM esock_atom_ctrl; +extern ERL_NIF_TERM esock_atom_ctrunc; +extern ERL_NIF_TERM esock_atom_data; +extern ERL_NIF_TERM esock_atom_debug; +extern ERL_NIF_TERM esock_atom_default_send_params; +extern ERL_NIF_TERM esock_atom_delayed_ack_time; +extern ERL_NIF_TERM esock_atom_dgram; +extern ERL_NIF_TERM esock_atom_disable_fragments; +extern ERL_NIF_TERM esock_atom_domain; +extern ERL_NIF_TERM esock_atom_dontfrag; +extern ERL_NIF_TERM esock_atom_dontroute; +extern ERL_NIF_TERM esock_atom_drop_membership; +extern ERL_NIF_TERM esock_atom_drop_source_membership; +extern ERL_NIF_TERM esock_atom_dstopts; +extern ERL_NIF_TERM esock_atom_eor; +extern ERL_NIF_TERM esock_atom_error; +extern ERL_NIF_TERM esock_atom_errqueue; +extern ERL_NIF_TERM esock_atom_esp_network_level; +extern ERL_NIF_TERM esock_atom_esp_trans_level; +extern ERL_NIF_TERM esock_atom_events; +extern ERL_NIF_TERM esock_atom_explicit_eor; +extern ERL_NIF_TERM esock_atom_faith; +extern ERL_NIF_TERM esock_atom_false; +extern ERL_NIF_TERM esock_atom_family; +extern ERL_NIF_TERM esock_atom_flags; +extern ERL_NIF_TERM esock_atom_flowinfo; +extern ERL_NIF_TERM esock_atom_fragment_interleave; +extern ERL_NIF_TERM esock_atom_freebind; +extern ERL_NIF_TERM esock_atom_get_peer_addr_info; +extern ERL_NIF_TERM esock_atom_hdrincl; +extern ERL_NIF_TERM esock_atom_hmac_ident; +extern ERL_NIF_TERM esock_atom_hoplimit; +extern ERL_NIF_TERM esock_atom_hopopts; +extern ERL_NIF_TERM esock_atom_ifindex; +extern ERL_NIF_TERM esock_atom_inet; +extern ERL_NIF_TERM esock_atom_inet6; +extern ERL_NIF_TERM esock_atom_info; +extern ERL_NIF_TERM esock_atom_initmsg; +extern ERL_NIF_TERM esock_atom_iov; +extern ERL_NIF_TERM esock_atom_ip; +extern ERL_NIF_TERM esock_atom_ipcomp_level; +extern ERL_NIF_TERM esock_atom_ipv6; +extern ERL_NIF_TERM esock_atom_i_want_mapped_v4_addr; +extern ERL_NIF_TERM esock_atom_join_group; +extern ERL_NIF_TERM esock_atom_keepalive; +extern ERL_NIF_TERM esock_atom_keepcnt; +extern ERL_NIF_TERM esock_atom_keepidle; +extern ERL_NIF_TERM esock_atom_keepintvl; +extern ERL_NIF_TERM esock_atom_leave_group; +extern ERL_NIF_TERM esock_atom_level; +extern ERL_NIF_TERM esock_atom_linger; +extern ERL_NIF_TERM esock_atom_local; +extern ERL_NIF_TERM esock_atom_local_auth_chunks; +extern ERL_NIF_TERM esock_atom_loopback; +extern ERL_NIF_TERM esock_atom_lowdelay; +extern ERL_NIF_TERM esock_atom_mark; +extern ERL_NIF_TERM esock_atom_maxburst; +extern ERL_NIF_TERM esock_atom_maxseg; +extern ERL_NIF_TERM esock_atom_md5sig; +extern ERL_NIF_TERM esock_atom_mincost; +extern ERL_NIF_TERM esock_atom_minttl; +extern ERL_NIF_TERM esock_atom_msfilter; +extern ERL_NIF_TERM esock_atom_mtu; +extern ERL_NIF_TERM esock_atom_mtu_discover; +extern ERL_NIF_TERM esock_atom_multicast_all; +extern ERL_NIF_TERM esock_atom_multicast_hops; +extern ERL_NIF_TERM esock_atom_multicast_if; +extern ERL_NIF_TERM esock_atom_multicast_loop; +extern ERL_NIF_TERM esock_atom_multicast_ttl; +extern ERL_NIF_TERM esock_atom_nodelay; +extern ERL_NIF_TERM esock_atom_nodefrag; +extern ERL_NIF_TERM esock_atom_noopt; +extern ERL_NIF_TERM esock_atom_nopush; +extern ERL_NIF_TERM esock_atom_not_found; +extern ERL_NIF_TERM esock_atom_not_owner; +extern ERL_NIF_TERM esock_atom_ok; +extern ERL_NIF_TERM esock_atom_oob; +extern ERL_NIF_TERM esock_atom_oobinline; +extern ERL_NIF_TERM esock_atom_options; +extern ERL_NIF_TERM esock_atom_origdstaddr; +extern ERL_NIF_TERM esock_atom_partial_delivery_point; +extern ERL_NIF_TERM esock_atom_passcred; +extern ERL_NIF_TERM esock_atom_path; +extern ERL_NIF_TERM esock_atom_peekcred; +extern ERL_NIF_TERM esock_atom_peek_off; +extern ERL_NIF_TERM esock_atom_peer_addr_params; +extern ERL_NIF_TERM esock_atom_peer_auth_chunks; +extern ERL_NIF_TERM esock_atom_pktinfo; +extern ERL_NIF_TERM esock_atom_pktoptions; +extern ERL_NIF_TERM esock_atom_port; +extern ERL_NIF_TERM esock_atom_portrange; +extern ERL_NIF_TERM esock_atom_primary_addr; +extern ERL_NIF_TERM esock_atom_priority; +extern ERL_NIF_TERM esock_atom_protocol; +extern ERL_NIF_TERM esock_atom_raw; +extern ERL_NIF_TERM esock_atom_rcvbuf; +extern ERL_NIF_TERM esock_atom_rcvbufforce; +extern ERL_NIF_TERM esock_atom_rcvlowat; +extern ERL_NIF_TERM esock_atom_rcvtimeo; +extern ERL_NIF_TERM esock_atom_rdm; +extern ERL_NIF_TERM esock_atom_recv; +extern ERL_NIF_TERM esock_atom_recvdstaddr; +extern ERL_NIF_TERM esock_atom_recverr; +extern ERL_NIF_TERM esock_atom_recvfrom; +extern ERL_NIF_TERM esock_atom_recvif; +extern ERL_NIF_TERM esock_atom_recvmsg; +extern ERL_NIF_TERM esock_atom_recvopts; +extern ERL_NIF_TERM esock_atom_recvorigdstaddr; +extern ERL_NIF_TERM esock_atom_recvpktinfo; +extern ERL_NIF_TERM esock_atom_recvtclass; +extern ERL_NIF_TERM esock_atom_recvtos; +extern ERL_NIF_TERM esock_atom_recvttl; +extern ERL_NIF_TERM esock_atom_reliability; +extern ERL_NIF_TERM esock_atom_reset_streams; +extern ERL_NIF_TERM esock_atom_retopts; +extern ERL_NIF_TERM esock_atom_reuseaddr; +extern ERL_NIF_TERM esock_atom_reuseport; +extern ERL_NIF_TERM esock_atom_rights; +extern ERL_NIF_TERM esock_atom_router_alert; +extern ERL_NIF_TERM esock_atom_rthdr; +extern ERL_NIF_TERM esock_atom_rtoinfo; +extern ERL_NIF_TERM esock_atom_rxq_ovfl; +extern ERL_NIF_TERM esock_atom_scope_id; +extern ERL_NIF_TERM esock_atom_sctp; +extern ERL_NIF_TERM esock_atom_sec; +extern ERL_NIF_TERM esock_atom_select_sent; +extern ERL_NIF_TERM esock_atom_send; +extern ERL_NIF_TERM esock_atom_sendmsg; +extern ERL_NIF_TERM esock_atom_sendsrcaddr; +extern ERL_NIF_TERM esock_atom_sendto; +extern ERL_NIF_TERM esock_atom_seqpacket; +extern ERL_NIF_TERM esock_atom_setfib; +extern ERL_NIF_TERM esock_atom_set_peer_primary_addr; +extern ERL_NIF_TERM esock_atom_sndbuf; +extern ERL_NIF_TERM esock_atom_sndbufforce; +extern ERL_NIF_TERM esock_atom_sndlowat; +extern ERL_NIF_TERM esock_atom_sndtimeo; +extern ERL_NIF_TERM esock_atom_socket; +extern ERL_NIF_TERM esock_atom_socket_tag; +extern ERL_NIF_TERM esock_atom_spec_dst; +extern ERL_NIF_TERM esock_atom_status; +extern ERL_NIF_TERM esock_atom_stream; +extern ERL_NIF_TERM esock_atom_syncnt; +extern ERL_NIF_TERM esock_atom_tclass; +extern ERL_NIF_TERM esock_atom_tcp; +extern ERL_NIF_TERM esock_atom_throughput; +extern ERL_NIF_TERM esock_atom_timestamp; +extern ERL_NIF_TERM esock_atom_tos; +extern ERL_NIF_TERM esock_atom_transparent; +extern ERL_NIF_TERM esock_atom_true; +extern ERL_NIF_TERM esock_atom_trunc; +extern ERL_NIF_TERM esock_atom_ttl; +extern ERL_NIF_TERM esock_atom_type; +extern ERL_NIF_TERM esock_atom_udp; +extern ERL_NIF_TERM esock_atom_unblock_source; +extern ERL_NIF_TERM esock_atom_undefined; +extern ERL_NIF_TERM esock_atom_unicast_hops; +extern ERL_NIF_TERM esock_atom_unknown; +extern ERL_NIF_TERM esock_atom_usec; +extern ERL_NIF_TERM esock_atom_user_timeout; +extern ERL_NIF_TERM esock_atom_use_ext_recvinfo; +extern ERL_NIF_TERM esock_atom_use_min_mtu; +extern ERL_NIF_TERM esock_atom_v6only; + + +/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * Error value (=reason) atoms + */ +extern ERL_NIF_TERM esock_atom_eafnosupport; +extern ERL_NIF_TERM esock_atom_eagain; +extern ERL_NIF_TERM esock_atom_einval; + + + +/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + * Various wrapper macros for enif functions + */ +#define MALLOC(SZ) enif_alloc((SZ)) +#define REALLOC(P, SZ) enif_realloc((P), (SZ)) +#define FREE(P) enif_free((P)) + +#define MKA(E,S) enif_make_atom((E), (S)) +#define MKBIN(E,B) enif_make_binary((E), (B)) +#define MKI(E,I) enif_make_int((E), (I)) +#define MKL(E,L) enif_make_long((E), (L)) +#define MKLA(E,A,L) enif_make_list_from_array((E), (A), (L)) +#define MKEL(E) enif_make_list((E), 0) +#define MKMA(E,KA,VA,L,M) enif_make_map_from_arrays((E), (KA), (VA), (L), (M)) +#define MKPID(E, P) enif_make_pid((E), (P)) +#define MKREF(E) enif_make_ref((E)) +#define MKS(E,S) enif_make_string((E), (S), ERL_NIF_LATIN1) +#define MKSL(E,S,L) enif_make_string_len((E), (S), (L), ERL_NIF_LATIN1) +#define MKSBIN(E,B,ST,SZ) enif_make_sub_binary((E), (B), (ST), (SZ)) +#define MKT2(E,E1,E2) enif_make_tuple2((E), (E1), (E2)) +#define MKT3(E,E1,E2,E3) enif_make_tuple3((E), (E1), (E2), (E3)) +#define MKT4(E,E1,E2,E3,E4) enif_make_tuple4((E), (E1), (E2), (E3), (E4)) +#define MKT5(E,E1,E2,E3,E4,E5) \ + enif_make_tuple5((E), (E1), (E2), (E3), (E4), (E5)) +#define MKT8(E,E1,E2,E3,E4,E5,E6,E7,E8) \ + enif_make_tuple8((E), (E1), (E2), (E3), (E4), (E5), (E6), (E7), (E8)) +#define MKTA(E, A, AL) enif_make_tuple_from_array((E), (A), (AL)) +#define MKUI(E,UI) enif_make_uint((E), (UI)) +#define MKUL(E,UL) enif_make_ulong((E), (UL)) + +#define MCREATE(N) enif_mutex_create((N)) +#define MDESTROY(M) enif_mutex_destroy((M)) +#define MLOCK(M) enif_mutex_lock((M)) +#define MUNLOCK(M) enif_mutex_unlock((M)) + +// #define MONP(S,E,D,P,M) enif_monitor_process((E), (D), (P), (M)) +// #define DEMONP(S,E,D,M) enif_demonitor_process((E), (D), (M)) +#define MONP(S,E,D,P,M) esock_monitor((S), (E), (D), (P), (M)) +#define DEMONP(S,E,D,M) esock_demonitor((S), (E), (D), (M)) +#define MON_INIT(M) esock_monitor_init((M)) +// #define MON_COMP(M1, M2) esock_monitor_compare((M1), (M2)) + +#define SELECT(E,FD,M,O,P,R) \ + if (enif_select((E), (FD), (M), (O), (P), (R)) < 0) \ + return enif_make_badarg((E)); + +#define COMPARE(A, B) enif_compare((A), (B)) + +#define IS_ATOM(E, TE) enif_is_atom((E), (TE)) +#define IS_BIN(E, TE) enif_is_binary((E), (TE)) +#define IS_LIST(E, TE) enif_is_list((E), (TE)) +#define IS_MAP(E, TE) enif_is_map((E), (TE)) +#define IS_NUM(E, TE) enif_is_number((E), (TE)) +#define IS_TUPLE(E, TE) enif_is_tuple((E), (TE)) + +#define GET_ATOM_LEN(E, TE, LP) \ + enif_get_atom_length((E), (TE), (LP), ERL_NIF_LATIN1) +#define GET_ATOM(E, TE, BP, MAX) \ + enif_get_atom((E), (TE), (BP), (MAX), ERL_NIF_LATIN1) +#define GET_BIN(E, TE, BP) enif_inspect_iolist_as_binary((E), (TE), (BP)) +#define GET_INT(E, TE, IP) enif_get_int((E), (TE), (IP)) +#define GET_LIST_ELEM(E, L, HP, TP) enif_get_list_cell((E), (L), (HP), (TP)) +#define GET_LIST_LEN(E, L, LP) enif_get_list_length((E), (L), (LP)) +#define GET_LONG(E, TE, LP) enif_get_long((E), (TE), (LP)) +#define GET_LPID(E, T, P) enif_get_local_pid((E), (T), (P)) +#define GET_STR(E, L, B, SZ) \ + enif_get_string((E), (L), (B), (SZ), ERL_NIF_LATIN1) +#define GET_UINT(E, TE, UIP) enif_get_uint((E), (TE), (UIP)) +#define GET_ULONG(E, TE, ULP) enif_get_long((E), (TE), (ULP)) +#define GET_TUPLE(E, TE, TSZ, TA) enif_get_tuple((E), (TE), (TSZ), (TA)) +#define GET_MAP_VAL(E, M, K, V) enif_get_map_value((E), (M), (K), (V)) + +#define ALLOC_BIN(SZ, BP) enif_alloc_binary((SZ), (BP)) +#define REALLOC_BIN(SZ, BP) enif_realloc_binary((SZ), (BP)) +#define FREE_BIN(BP) enif_release_binary((BP)) + + +#endif // SOCKET_INT_H__ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c new file mode 100644 index 0000000000..4b8d65ce6a --- /dev/null +++ b/erts/emulator/nifs/common/socket_nif.c @@ -0,0 +1,18421 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2019. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : The NIF (C) part of the socket interface + * ---------------------------------------------------------------------- + * + */ + +#define STATIC_ERLANG_NIF 1 + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* If we HAVE_SCTP_H and Solaris, we need to define the following in + * order to get SCTP working: + */ +#if (defined(HAVE_SCTP_H) && defined(__sun) && defined(__SVR4)) +#define SOLARIS10 1 +/* WARNING: This is not quite correct, it may also be Solaris 11! */ +#define _XPG4_2 +#define __EXTENSIONS__ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <ctype.h> +#include <sys/types.h> +#include <errno.h> +#include <time.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif + +#ifdef HAVE_NET_IF_DL_H +#include <net/if_dl.h> +#endif + +#ifdef HAVE_IFADDRS_H +#include <ifaddrs.h> +#endif + +#ifdef HAVE_NETPACKET_PACKET_H +#include <netpacket/packet.h> +#endif + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +/* SENDFILE STUFF HERE IF WE NEED IT... */ + +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define __DARWIN__ 1 +#endif + + +#ifdef __WIN32__ +#define STRNCASECMP strncasecmp +#define INCL_WINSOCK_API_TYPEDEFS 1 + +#ifndef WINDOWS_H_INCLUDES_WINSOCK2_H +#include <winsock2.h> +#endif +#include <windows.h> +#include <Ws2tcpip.h> /* NEED VC 6.0 or higher */ + +/* Visual studio 2008+: NTDDI_VERSION needs to be set for iphlpapi.h + * to define the right structures. It needs to be set to WINXP (or LONGHORN) + * for IPV6 to work and it's set lower by default, so we need to change it. + */ +#ifdef HAVE_SDKDDKVER_H +# include <sdkddkver.h> +# ifdef NTDDI_VERSION +# undef NTDDI_VERSION +# endif +# define NTDDI_VERSION NTDDI_WINXP +#endif +#include <iphlpapi.h> + +#undef WANT_NONBLOCKING +#include "sys.h" + + + + +/* AND HERE WE MAY HAVE A BUNCH OF DEFINES....SEE INET DRIVER.... */ + + + + +#else /* ifdef __WIN32__ */ + +#include <sys/time.h> +#ifdef NETDB_H_NEEDS_IN_H +#include <netinet/in.h> +#endif +#include <netdb.h> + +#include <sys/socket.h> +#include <netinet/in.h> + +#ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H +#include <rpc/types.h> +#endif + +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> +#include <arpa/inet.h> + +#include <sys/param.h> +#ifdef HAVE_ARPA_NAMESER_H +#include <arpa/nameser.h> +#endif + +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#include <net/if.h> + +#ifdef HAVE_SCHED_H +#include <sched.h> +#endif + +#ifdef HAVE_SETNS_H +#include <setns.h> +#endif + +#define HAVE_UDP + +/* SCTP support -- currently for UNIX platforms only: */ +#undef HAVE_SCTP +#if defined(HAVE_SCTP_H) + +#include <netinet/sctp.h> + +/* SCTP Socket API Draft from version 11 on specifies that netinet/sctp.h must + explicitly define HAVE_SCTP in case when SCTP is supported, but Solaris 10 + still apparently uses Draft 10, and does not define that symbol, so we have + to define it explicitly: +*/ +#ifndef HAVE_SCTP +# define HAVE_SCTP +#endif + +/* These changed in draft 11, so SOLARIS10 uses the old MSG_* */ +#if ! HAVE_DECL_SCTP_UNORDERED +# define SCTP_UNORDERED MSG_UNORDERED +#endif +#if ! HAVE_DECL_SCTP_ADDR_OVER +# define SCTP_ADDR_OVER MSG_ADDR_OVER +#endif +#if ! HAVE_DECL_SCTP_ABORT +# define SCTP_ABORT MSG_ABORT +#endif +#if ! HAVE_DECL_SCTP_EOF +# define SCTP_EOF MSG_EOF +#endif + +/* More Solaris 10 fixes: */ +#if ! HAVE_DECL_SCTP_CLOSED && HAVE_DECL_SCTPS_IDLE +# define SCTP_CLOSED SCTPS_IDLE +# undef HAVE_DECL_SCTP_CLOSED +# define HAVE_DECL_SCTP_CLOSED 1 +#endif +#if ! HAVE_DECL_SCTP_BOUND && HAVE_DECL_SCTPS_BOUND +# define SCTP_BOUND SCTPS_BOUND +# undef HAVE_DECL_SCTP_BOUND +# define HAVE_DECL_SCTP_BOUND 1 +#endif +#if ! HAVE_DECL_SCTP_LISTEN && HAVE_DECL_SCTPS_LISTEN +# define SCTP_LISTEN SCTPS_LISTEN +# undef HAVE_DECL_SCTP_LISTEN +# define HAVE_DECL_SCTP_LISTEN 1 +#endif +#if ! HAVE_DECL_SCTP_COOKIE_WAIT && HAVE_DECL_SCTPS_COOKIE_WAIT +# define SCTP_COOKIE_WAIT SCTPS_COOKIE_WAIT +# undef HAVE_DECL_SCTP_COOKIE_WAIT +# define HAVE_DECL_SCTP_COOKIE_WAIT 1 +#endif +#if ! HAVE_DECL_SCTP_COOKIE_ECHOED && HAVE_DECL_SCTPS_COOKIE_ECHOED +# define SCTP_COOKIE_ECHOED SCTPS_COOKIE_ECHOED +# undef HAVE_DECL_SCTP_COOKIE_ECHOED +# define HAVE_DECL_SCTP_COOKIE_ECHOED 1 +#endif +#if ! HAVE_DECL_SCTP_ESTABLISHED && HAVE_DECL_SCTPS_ESTABLISHED +# define SCTP_ESTABLISHED SCTPS_ESTABLISHED +# undef HAVE_DECL_SCTP_ESTABLISHED +# define HAVE_DECL_SCTP_ESTABLISHED 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_PENDING && HAVE_DECL_SCTPS_SHUTDOWN_PENDING +# define SCTP_SHUTDOWN_PENDING SCTPS_SHUTDOWN_PENDING +# undef HAVE_DECL_SCTP_SHUTDOWN_PENDING +# define HAVE_DECL_SCTP_SHUTDOWN_PENDING 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_SENT && HAVE_DECL_SCTPS_SHUTDOWN_SENT +# define SCTP_SHUTDOWN_SENT SCTPS_SHUTDOWN_SENT +# undef HAVE_DECL_SCTP_SHUTDOWN_SENT +# define HAVE_DECL_SCTP_SHUTDOWN_SENT 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_RECEIVED && HAVE_DECL_SCTPS_SHUTDOWN_RECEIVED +# define SCTP_SHUTDOWN_RECEIVED SCTPS_SHUTDOWN_RECEIVED +# undef HAVE_DECL_SCTP_SHUTDOWN_RECEIVED +# define HAVE_DECL_SCTP_SHUTDOWN_RECEIVED 1 +#endif +#if ! HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT && HAVE_DECL_SCTPS_SHUTDOWN_ACK_SENT +# define SCTP_SHUTDOWN_ACK_SENT SCTPS_SHUTDOWN_ACK_SENT +# undef HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT +# define HAVE_DECL_SCTP_SHUTDOWN_ACK_SENT 1 +#endif +/* New spelling in lksctp 2.6.22 or maybe even earlier: + * adaption -> adaptation + */ +#if !defined(SCTP_ADAPTATION_LAYER) && defined (SCTP_ADAPTION_LAYER) +# define SCTP_ADAPTATION_LAYER SCTP_ADAPTION_LAYER +# define SCTP_ADAPTATION_INDICATION SCTP_ADAPTION_INDICATION +# define sctp_adaptation_event sctp_adaption_event +# define sctp_setadaptation sctp_setadaption +# define sn_adaptation_event sn_adaption_event +# define sai_adaptation_ind sai_adaption_ind +# define ssb_adaptation_ind ssb_adaption_ind +# define sctp_adaptation_layer_event sctp_adaption_layer_event +#endif + +/* + * We *may* need this stuff later when we *fully* implement support for SCTP + * + +#if defined(__GNUC__) && defined(HAVE_SCTP_BINDX) +static typeof(sctp_bindx) *esock_sctp_bindx = NULL; +#else +static int (*esock_sctp_bindx) + (int sd, struct sockaddr *addrs, int addrcnt, int flags) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_PEELOFF) +static typeof(sctp_peeloff) *esock_sctp_peeloff = NULL; +#else +static int (*esock_sctp_peeloff) + (int sd, sctp_assoc_t assoc_id) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_GETLADDRS) +static typeof(sctp_getladdrs) *esock_sctp_getladdrs = NULL; +#else +static int (*esock_sctp_getladdrs) + (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_FREELADDRS) +static typeof(sctp_freeladdrs) *esock_sctp_freeladdrs = NULL; +#else +static void (*esock_sctp_freeladdrs)(struct sockaddr *addrs) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_GETPADDRS) +static typeof(sctp_getpaddrs) *esock_sctp_getpaddrs = NULL; +#else +static int (*esock_sctp_getpaddrs) + (int sd, sctp_assoc_t assoc_id, struct sockaddr **ss) = NULL; +#endif + +#if defined(__GNUC__) && defined(HAVE_SCTP_FREEPADDRS) +static typeof(sctp_freepaddrs) *esock_sctp_freepaddrs = NULL; +#else +static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; +#endif + +*/ + +#endif /* #if defined(HAVE_SCTP_H) */ + + +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" + +/* Socket stuff */ +#define INVALID_SOCKET -1 +// #define INVALID_EVENT -1 +#define SOCKET_ERROR -1 + +#endif /* ifdef __WIN32__ */ + +#include <erl_nif.h> + +#include "socket_dbg.h" +#include "socket_tarray.h" +#include "socket_int.h" +#include "socket_util.h" + +#if defined(SOL_IPV6) || defined(IPPROTO_IPV6) +#define HAVE_IPV6 +#endif + +/* All platforms fail on malloc errors. */ +#define FATAL_MALLOC + + +/* Debug stuff... */ +#define SOCKET_GLOBAL_DEBUG_DEFAULT FALSE +#define SOCKET_DEBUG_DEFAULT FALSE + +/* Counters and stuff (Don't know where to sent this stuff anyway) */ +#define SOCKET_NIF_IOW_DEFAULT FALSE + + + +/* Socket stuff */ +#define INVALID_EVENT -1 + +#define SOCKET int +#define HANDLE long int + + +/* ============================================================================== + * The IS_SOCKET_ERROR macro below is used for portability reasons. + * While POSIX specifies that errors from socket-related system calls + * should be indicated with a -1 return value, some users have experienced + * non-Windows OS kernels that return negative values other than -1. + * While one can argue that such kernels are technically broken, comparing + * against values less than 0 covers their out-of-spec return values without + * imposing incorrect semantics on systems that manage to correctly return -1 + * for errors, thus increasing Erlang's portability. + */ +#ifdef __WIN32__ +#define IS_SOCKET_ERROR(val) ((val) == SOCKET_ERROR) +#else +#define IS_SOCKET_ERROR(val) ((val) < 0) +#endif + + +/* *** Misc macros and defines *** */ + +/* This macro exist on some (linux) platforms */ +#if !defined(IPTOS_TOS_MASK) +#define IPTOS_TOS_MASK 0x1E +#endif +#if !defined(IPTOS_TOS) +#define IPTOS_TOS(tos) ((tos)&IPTOS_TOS_MASK) +#endif + + +#if defined(TCP_CA_NAME_MAX) +#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX +#else +/* This is really excessive, but just in case... */ +#define SOCKET_OPT_TCP_CONGESTION_NAME_MAX 256 +#endif + + +/* *** Socket state defs *** */ + +#define SOCKET_FLAG_OPEN 0x0001 +#define SOCKET_FLAG_ACTIVE 0x0004 +#define SOCKET_FLAG_LISTEN 0x0008 +#define SOCKET_FLAG_CON 0x0010 +#define SOCKET_FLAG_ACC 0x0020 +#define SOCKET_FLAG_BUSY 0x0040 +#define SOCKET_FLAG_CLOSE 0x0080 + +#define SOCKET_STATE_CLOSED (0) +#define SOCKET_STATE_OPEN (SOCKET_FLAG_OPEN) +#define SOCKET_STATE_CONNECTED (SOCKET_STATE_OPEN | SOCKET_FLAG_ACTIVE) +#define SOCKET_STATE_LISTENING (SOCKET_STATE_OPEN | SOCKET_FLAG_LISTEN) +#define SOCKET_STATE_CONNECTING (SOCKET_STATE_OPEN | SOCKET_FLAG_CON) +#define SOCKET_STATE_ACCEPTING (SOCKET_STATE_LISTENING | SOCKET_FLAG_ACC) +#define SOCKET_STATE_CLOSING (SOCKET_FLAG_CLOSE) + +#define IS_CLOSED(d) \ + ((d)->state == SOCKET_STATE_CLOSED) + +/* +#define IS_STATE(d, f) \ + (((d)->state & (f)) == (f)) +*/ + +#define IS_CLOSING(d) \ + (((d)->state & SOCKET_STATE_CLOSING) == SOCKET_STATE_CLOSING) + +#define IS_OPEN(d) \ + (((d)->state & SOCKET_FLAG_OPEN) == SOCKET_FLAG_OPEN) + +#define IS_CONNECTED(d) \ + (((d)->state & SOCKET_STATE_CONNECTED) == SOCKET_STATE_CONNECTED) + +#define IS_CONNECTING(d) \ + (((d)->state & SOCKET_FLAG_CON) == SOCKET_FLAG_CON) + +/* +#define IS_BUSY(d) \ + (((d)->state & SOCKET_FLAG_BUSY) == SOCKET_FLAG_BUSY) +*/ + +#define SOCKET_SEND_FLAG_CONFIRM 0 +#define SOCKET_SEND_FLAG_DONTROUTE 1 +#define SOCKET_SEND_FLAG_EOR 2 +#define SOCKET_SEND_FLAG_MORE 3 +#define SOCKET_SEND_FLAG_NOSIGNAL 4 +#define SOCKET_SEND_FLAG_OOB 5 +#define SOCKET_SEND_FLAG_LOW SOCKET_SEND_FLAG_CONFIRM +#define SOCKET_SEND_FLAG_HIGH SOCKET_SEND_FLAG_OOB + +#define SOCKET_RECV_FLAG_CMSG_CLOEXEC 0 +#define SOCKET_RECV_FLAG_ERRQUEUE 1 +#define SOCKET_RECV_FLAG_OOB 2 +#define SOCKET_RECV_FLAG_PEEK 3 +#define SOCKET_RECV_FLAG_TRUNC 4 +#define SOCKET_RECV_FLAG_LOW SOCKET_RECV_FLAG_CMSG_CLOEXEC +#define SOCKET_RECV_FLAG_HIGH SOCKET_RECV_FLAG_TRUNC + +#define SOCKET_RECV_BUFFER_SIZE_DEFAULT 8192 +#define SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024 +#define SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT 1024 + +#define VT2S(__VT__) (((__VT__) == SOCKET_OPT_VALUE_TYPE_UNSPEC) ? "unspec" : \ + (((__VT__) == SOCKET_OPT_VALUE_TYPE_INT) ? "int" : \ + ((__VT__) == SOCKET_OPT_VALUE_TYPE_BOOL) ? "bool" : \ + "undef")) + +#define SOCKET_OPT_VALUE_TYPE_UNSPEC 0 +#define SOCKET_OPT_VALUE_TYPE_INT 1 +#define SOCKET_OPT_VALUE_TYPE_BOOL 2 + +typedef union { + struct { + // 0 = not open, 1 = open + unsigned int open:1; + // 0 = not conn, 1 = connecting, 2 = connected + unsigned int connect:2; + // unsigned int connecting:1; + // unsigned int connected:1; + // 0 = not listen, 1 = listening, 2 = accepting + unsigned int listen:2; + // unsigned int listening:1; + // unsigned int accepting:1; + /* Room for more... */ + } flags; + unsigned int field; // Make it easy to reset all flags... +} SocketState; + +/* +#define IS_OPEN(d) ((d)->state.flags.open) +#define IS_CONNECTED(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTED) +#define IS_CONNECTING(d) ((d)->state.flags.connect == SOCKET_STATE_CONNECTING) +*/ + + +/*---------------------------------------------------------------------------- + * Interface constants. + * + * This section must be "identical" to the corresponding socket.hrl + */ + +/* domain */ +#define SOCKET_DOMAIN_LOCAL 1 +#define SOCKET_DOMAIN_INET 2 +#define SOCKET_DOMAIN_INET6 3 + +/* type */ +#define SOCKET_TYPE_STREAM 1 +#define SOCKET_TYPE_DGRAM 2 +#define SOCKET_TYPE_RAW 3 +// #define SOCKET_TYPE_RDM 4 +#define SOCKET_TYPE_SEQPACKET 5 + +/* protocol */ +#define SOCKET_PROTOCOL_IP 1 +#define SOCKET_PROTOCOL_TCP 2 +#define SOCKET_PROTOCOL_UDP 3 +#define SOCKET_PROTOCOL_SCTP 4 +#define SOCKET_PROTOCOL_ICMP 5 +#define SOCKET_PROTOCOL_IGMP 6 + +/* shutdown how */ +#define SOCKET_SHUTDOWN_HOW_RD 0 +#define SOCKET_SHUTDOWN_HOW_WR 1 +#define SOCKET_SHUTDOWN_HOW_RDWR 2 + + +#define SOCKET_OPT_LEVEL_OTP 0 +#define SOCKET_OPT_LEVEL_SOCKET 1 +#define SOCKET_OPT_LEVEL_IP 2 +#define SOCKET_OPT_LEVEL_IPV6 3 +#define SOCKET_OPT_LEVEL_TCP 4 +#define SOCKET_OPT_LEVEL_UDP 5 +#define SOCKET_OPT_LEVEL_SCTP 6 + +#define SOCKET_OPT_OTP_DEBUG 1 +#define SOCKET_OPT_OTP_IOW 2 +#define SOCKET_OPT_OTP_CTRL_PROC 3 +#define SOCKET_OPT_OTP_RCVBUF 4 +#define SOCKET_OPT_OTP_RCVCTRLBUF 6 +#define SOCKET_OPT_OTP_SNDCTRLBUF 7 +#define SOCKET_OPT_OTP_FD 8 +#define SOCKET_OPT_OTP_DOMAIN 0xFF01 // INTERNAL AND ONLY GET +#define SOCKET_OPT_OTP_TYPE 0xFF02 // INTERNAL AND ONLY GET +#define SOCKET_OPT_OTP_PROTOCOL 0xFF03 // INTERNAL AND ONLY GET + +#define SOCKET_OPT_SOCK_ACCEPTCONN 1 +#define SOCKET_OPT_SOCK_BINDTODEVICE 3 +#define SOCKET_OPT_SOCK_BROADCAST 4 +#define SOCKET_OPT_SOCK_DEBUG 6 +#define SOCKET_OPT_SOCK_DOMAIN 7 +#define SOCKET_OPT_SOCK_DONTROUTE 8 +#define SOCKET_OPT_SOCK_KEEPALIVE 10 +#define SOCKET_OPT_SOCK_LINGER 11 +#define SOCKET_OPT_SOCK_OOBINLINE 13 +#define SOCKET_OPT_SOCK_PEEK_OFF 15 +#define SOCKET_OPT_SOCK_PRIORITY 17 +#define SOCKET_OPT_SOCK_PROTOCOL 18 +#define SOCKET_OPT_SOCK_RCVBUF 19 +#define SOCKET_OPT_SOCK_RCVLOWAT 21 +#define SOCKET_OPT_SOCK_RCVTIMEO 22 +#define SOCKET_OPT_SOCK_REUSEADDR 23 +#define SOCKET_OPT_SOCK_REUSEPORT 24 +#define SOCKET_OPT_SOCK_SNDBUF 27 +#define SOCKET_OPT_SOCK_SNDLOWAT 29 +#define SOCKET_OPT_SOCK_SNDTIMEO 30 +#define SOCKET_OPT_SOCK_TIMESTAMP 31 +#define SOCKET_OPT_SOCK_TYPE 32 + +#define SOCKET_OPT_IP_ADD_MEMBERSHIP 1 +#define SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP 2 +#define SOCKET_OPT_IP_BLOCK_SOURCE 3 +#define SOCKET_OPT_IP_DROP_MEMBERSHIP 5 +#define SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP 6 +#define SOCKET_OPT_IP_FREEBIND 7 +#define SOCKET_OPT_IP_HDRINCL 8 +#define SOCKET_OPT_IP_MINTTL 9 +#define SOCKET_OPT_IP_MSFILTER 10 +#define SOCKET_OPT_IP_MTU 11 +#define SOCKET_OPT_IP_MTU_DISCOVER 12 +#define SOCKET_OPT_IP_MULTICAST_ALL 13 +#define SOCKET_OPT_IP_MULTICAST_IF 14 +#define SOCKET_OPT_IP_MULTICAST_LOOP 15 +#define SOCKET_OPT_IP_MULTICAST_TTL 16 +#define SOCKET_OPT_IP_NODEFRAG 17 +#define SOCKET_OPT_IP_PKTINFO 19 +#define SOCKET_OPT_IP_RECVDSTADDR 20 +#define SOCKET_OPT_IP_RECVERR 21 +#define SOCKET_OPT_IP_RECVIF 22 +#define SOCKET_OPT_IP_RECVOPTS 23 +#define SOCKET_OPT_IP_RECVORIGDSTADDR 24 +#define SOCKET_OPT_IP_RECVTOS 25 +#define SOCKET_OPT_IP_RECVTTL 26 +#define SOCKET_OPT_IP_RETOPTS 27 +#define SOCKET_OPT_IP_ROUTER_ALERT 28 +#define SOCKET_OPT_IP_SENDSRCADDR 29 // Same as IP_RECVDSTADDR? +#define SOCKET_OPT_IP_TOS 30 +#define SOCKET_OPT_IP_TRANSPARENT 31 +#define SOCKET_OPT_IP_TTL 32 +#define SOCKET_OPT_IP_UNBLOCK_SOURCE 33 + +#define SOCKET_OPT_IPV6_ADDRFORM 1 +#define SOCKET_OPT_IPV6_ADD_MEMBERSHIP 2 +#define SOCKET_OPT_IPV6_AUTHHDR 3 +#define SOCKET_OPT_IPV6_DROP_MEMBERSHIP 6 +#define SOCKET_OPT_IPV6_DSTOPTS 7 +#define SOCKET_OPT_IPV6_FLOWINFO 11 +#define SOCKET_OPT_IPV6_HOPLIMIT 12 +#define SOCKET_OPT_IPV6_HOPOPTS 13 +#define SOCKET_OPT_IPV6_MTU 17 +#define SOCKET_OPT_IPV6_MTU_DISCOVER 18 +#define SOCKET_OPT_IPV6_MULTICAST_HOPS 19 +#define SOCKET_OPT_IPV6_MULTICAST_IF 20 +#define SOCKET_OPT_IPV6_MULTICAST_LOOP 21 +#define SOCKET_OPT_IPV6_RECVERR 24 +#define SOCKET_OPT_IPV6_RECVPKTINFO 25 // PKTINFO on FreeBSD +#define SOCKET_OPT_IPV6_ROUTER_ALERT 27 +#define SOCKET_OPT_IPV6_RTHDR 28 +#define SOCKET_OPT_IPV6_UNICAST_HOPS 30 +#define SOCKET_OPT_IPV6_V6ONLY 32 + +#define SOCKET_OPT_TCP_CONGESTION 1 +#define SOCKET_OPT_TCP_CORK 2 +#define SOCKET_OPT_TCP_MAXSEG 7 +#define SOCKET_OPT_TCP_NODELAY 9 + +#define SOCKET_OPT_UDP_CORK 1 + +#define SOCKET_OPT_SCTP_ASSOCINFO 2 +#define SOCKET_OPT_SCTP_AUTOCLOSE 8 +#define SOCKET_OPT_SCTP_DISABLE_FRAGMENTS 12 +#define SOCKET_OPT_SCTP_EVENTS 14 +#define SOCKET_OPT_SCTP_INITMSG 18 +#define SOCKET_OPT_SCTP_MAXSEG 21 +#define SOCKET_OPT_SCTP_NODELAY 23 +#define SOCKET_OPT_SCTP_RTOINFO 29 + +/* We should *eventually* use this instead of hard-coding the size (to 1) */ +#define ESOCK_RECVMSG_IOVEC_SZ 1 + + +#define SOCKET_SUPPORTS_OPTIONS 0x0001 +#define SOCKET_SUPPORTS_SCTP 0x0002 +#define SOCKET_SUPPORTS_IPV6 0x0003 + + + +/* =================================================================== * + * * + * Various enif macros * + * * + * =================================================================== */ + +#define SGDBG( proto ) ESOCK_DBG_PRINTF( data.dbg , proto ) +#define SSDBG( __D__ , proto ) ESOCK_DBG_PRINTF( (__D__)->dbg , proto ) + + + +/* =================================================================== * + * * + * Basic socket operations * + * * + * =================================================================== */ + +#ifdef __WIN32__ + +/* *** Windows macros *** */ + +#define sock_accept(s, addr, len) \ + make_noninheritable_handle(accept((s), (addr), (len))) +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_close(s) closesocket((s)) +#define sock_close_event(e) WSACloseEvent(e) +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_create_event(s) WSACreateEvent() +#define sock_errno() WSAGetLastError() +#define sock_getopt(s,l,o,v,ln) getsockopt((s),(l),(o),(v),(ln)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) +#define sock_listen(s, b) listen((s), (b)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) +#define sock_open(domain, type, proto) \ + make_noninheritable_handle(socket((domain), (type), (proto))) +#define sock_peer(s, addr, len) getpeername((s), (addr), (len)) +#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) +#define sock_recvfrom(s,buf,blen,flag,addr,alen) \ + recvfrom((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_send(s,buf,len,flag) send((s),(buf),(len),(flag)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) +#define sock_shutdown(s, how) shutdown((s), (how)) + + +#define SET_BLOCKING(s) ioctlsocket(s, FIONBIO, &zero_value) +#define SET_NONBLOCKING(s) ioctlsocket(s, FIONBIO, &one_value) +static unsigned long zero_value = 0; +static unsigned long one_value = 1; + + +#else /* !__WIN32__ */ + + +#ifdef HAS_ACCEPT4 +// We have to figure out what the flags are... +#define sock_accept(s, addr, len) accept4((s), (addr), (len), (SOCK_CLOEXEC)) +#else +#define sock_accept(s, addr, len) accept((s), (addr), (len)) +#endif +#define sock_bind(s, addr, len) bind((s), (addr), (len)) +#define sock_close(s) close((s)) +#define sock_close_event(e) /* do nothing */ +#define sock_connect(s, addr, len) connect((s), (addr), (len)) +#define sock_create_event(s) (s) /* return file descriptor */ +#define sock_errno() errno +#define sock_getopt(s,t,n,v,l) getsockopt((s),(t),(n),(v),(l)) +#define sock_htons(x) htons((x)) +#define sock_htonl(x) htonl((x)) +#define sock_listen(s, b) listen((s), (b)) +#define sock_name(s, addr, len) getsockname((s), (addr), (len)) +#define sock_ntohs(x) ntohs((x)) +#define sock_open(domain, type, proto) socket((domain), (type), (proto)) +#define sock_peer(s, addr, len) getpeername((s), (addr), (len)) +#define sock_recv(s,buf,len,flag) recv((s),(buf),(len),(flag)) +#define sock_recvfrom(s,buf,blen,flag,addr,alen) \ + recvfrom((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_recvmsg(s,msghdr,flag) recvmsg((s),(msghdr),(flag)) +#define sock_send(s,buf,len,flag) send((s), (buf), (len), (flag)) +#define sock_sendmsg(s,msghdr,flag) sendmsg((s),(msghdr),(flag)) +#define sock_sendto(s,buf,blen,flag,addr,alen) \ + sendto((s),(buf),(blen),(flag),(addr),(alen)) +#define sock_setopt(s,l,o,v,ln) setsockopt((s),(l),(o),(v),(ln)) +#define sock_shutdown(s, how) shutdown((s), (how)) + +#endif /* !__WIN32__ */ + +#ifdef HAVE_SOCKLEN_T +# define SOCKLEN_T socklen_t +#else +# define SOCKLEN_T size_t +#endif + +#ifdef __WIN32__ +#define SOCKOPTLEN_T int +#else +#define SOCKOPTLEN_T SOCKLEN_T +#endif + +/* We can use the IPv4 def for this since the beginning + * is the same for INET and INET6 */ +#define which_address_port(sap) \ + ((((sap)->in4.sin_family == AF_INET) || \ + ((sap)->in4.sin_family == AF_INET6)) ? \ + ((sap)->in4.sin_port) : -1) + + +typedef union { + ErlNifMonitor mon; + Uint32 raw[4]; +} ESockMonitor; + + +typedef struct { + ErlNifPid pid; // PID of the requesting process + // ErlNifMonitor mon; Monitor to the requesting process + ESockMonitor mon; // Monitor to the requesting process + ERL_NIF_TERM ref; // The (unique) reference (ID) of the request +} SocketRequestor; + +typedef struct socket_request_queue_element { + struct socket_request_queue_element* nextP; + SocketRequestor data; +} SocketRequestQueueElement; + +typedef struct { + SocketRequestQueueElement* first; + SocketRequestQueueElement* last; +} SocketRequestQueue; + + +typedef struct { + /* +++ The actual socket +++ */ + SOCKET sock; + HANDLE event; + + /* +++ Stuff "about" the socket +++ */ + int domain; + int type; + int protocol; + + unsigned int state; + SocketAddress remote; + unsigned int addrLen; + + ErlNifEnv* env; + + /* +++ Controller (owner) process +++ */ + ErlNifPid ctrlPid; + // ErlNifMonitor ctrlMon; + ESockMonitor ctrlMon; + + /* +++ Write stuff +++ */ + ErlNifMutex* writeMtx; + SocketRequestor currentWriter; + SocketRequestor* currentWriterP; // NULL or points to currentWriter + SocketRequestQueue writersQ; + BOOLEAN_T isWritable; + Uint32 writePkgCnt; + Uint32 writeByteCnt; + Uint32 writeTries; + Uint32 writeWaits; + Uint32 writeFails; + + /* +++ Read stuff +++ */ + ErlNifMutex* readMtx; + SocketRequestor currentReader; + SocketRequestor* currentReaderP; // NULL or points to currentReader + SocketRequestQueue readersQ; + BOOLEAN_T isReadable; + ErlNifBinary rbuffer; // DO WE NEED THIS + Uint32 readCapacity; // DO WE NEED THIS + Uint32 readPkgCnt; + Uint32 readByteCnt; + Uint32 readTries; + Uint32 readWaits; + + /* +++ Accept stuff +++ */ + ErlNifMutex* accMtx; + SocketRequestor currentAcceptor; + SocketRequestor* currentAcceptorP; // NULL or points to currentAcceptor + SocketRequestQueue acceptorsQ; + + /* +++ Config & Misc stuff +++ */ + size_t rBufSz; // Read buffer size (when data length = 0) + /* rNum and rNumCnt are used (together with rBufSz) when calling the recv + * function with the Length argument set to 0 (zero). + * If rNum is 0 (zero), then rNumCnt is not used and only *one* read will + * be done. Also, when get'ing the value of the option (rcvbuf) with + * getopt, the value will be reported as an integer. If the rNum has a + * value greater then 0 (zero), then it will instead be reported as {N, BufSz}. + */ + unsigned int rNum; // recv: Number of reads using rBufSz + unsigned int rNumCnt; // recv: Current number of reads (so far) + size_t rCtrlSz; // Read control buffer size + size_t wCtrlSz; // Write control buffer size + BOOLEAN_T iow; // Inform On (counter) Wrap + BOOLEAN_T dbg; + + /* +++ Close stuff +++ */ + ErlNifMutex* closeMtx; + ErlNifPid closerPid; + // ErlNifMonitor closerMon; + ESockMonitor closerMon; + ERL_NIF_TERM closeRef; + BOOLEAN_T closeLocal; + +} SocketDescriptor; + + +#define SOCKET_OPT_VALUE_UNDEF 0 +#define SOCKET_OPT_VALUE_BOOL 1 +#define SOCKET_OPT_VALUE_INT 2 +#define SOCKET_OPT_VALUE_LINGER 3 +#define SOCKET_OPT_VALUE_BIN 4 +#define SOCKET_OPT_VALUE_STR 5 + +typedef struct { + unsigned int tag; + union { + BOOLEAN_T boolVal; + int intVal; + struct linger lingerVal; + ErlNifBinary binVal; + struct { + unsigned int len; + char* str; + } strVal; + } u; + /* + void* optValP; // Points to the actual data (above) + socklen_t optValLen; // The size of the option value + */ +} SocketOptValue; + + +/* Global stuff (do we really need to "collect" + * these things?) + */ +typedef struct { + /* These are for debugging, testing and the like */ + ERL_NIF_TERM version; + ERL_NIF_TERM buildDate; + BOOLEAN_T dbg; + + BOOLEAN_T iow; + ErlNifMutex* cntMtx; + Uint32 numSockets; + Uint32 numTypeStreams; + Uint32 numTypeDGrams; + Uint32 numTypeSeqPkgs; + Uint32 numDomainInet; + Uint32 numDomainInet6; + Uint32 numDomainLocal; + Uint32 numProtoIP; + Uint32 numProtoTCP; + Uint32 numProtoUDP; + Uint32 numProtoSCTP; +} SocketData; + + +/* ---------------------------------------------------------------------- + * F o r w a r d s + * ---------------------------------------------------------------------- + */ + + + +static ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_supports(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +/* +This is a *global* debug function (enable or disable for all +operations and all sockets. +static ERL_NIF_TERM nif_debug(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +*/ +static ERL_NIF_TERM nif_open(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_bind(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_connect(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_listen(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_accept(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_send(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_sendto(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_recv(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_setopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_getopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_sockname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_peername(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM nif_cancel(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]); + +static ERL_NIF_TERM nsupports(ErlNifEnv* env, int key); +static ERL_NIF_TERM nsupports_options(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_sctp(ErlNifEnv* env); +static ERL_NIF_TERM nsupports_ipv6(ErlNifEnv* env); + +static ERL_NIF_TERM nopen(ErlNifEnv* env, + int domain, + int type, + int protocol, + char* netns); +static ERL_NIF_TERM nbind(ErlNifEnv* env, + SocketDescriptor* descP, + SocketAddress* sockAddrP, + unsigned int addrLen); +static ERL_NIF_TERM nconnect(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM nlisten(ErlNifEnv* env, + SocketDescriptor* descP, + int backlog); +static ERL_NIF_TERM naccept_listening(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM naccept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM nsend(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags); +static ERL_NIF_TERM nsendto(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags, + SocketAddress* toAddrP, + unsigned int toAddrLen); +static ERL_NIF_TERM nsendmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM eMsgHdr, + int flags); +static ERL_NIF_TERM nrecv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM recvRef, + int len, + int flags); +static ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + Uint16 bufSz, + int flags); +static ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + Uint16 bufLen, + Uint16 ctrlLen, + int flags); +static ERL_NIF_TERM nclose(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM nshutdown(ErlNifEnv* env, + SocketDescriptor* descP, + int how); +static ERL_NIF_TERM nsetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); + + +/* *** Handling set of socket options for level = socket *** */ + +#if defined(SO_BINDTODEVICE) +static ERL_NIF_TERM nsetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_BROADCAST) +static ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_DEBUG) +static ERL_NIF_TERM nsetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_DONTROUTE) +static ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_KEEPALIVE) +static ERL_NIF_TERM nsetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_LINGER) +static ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_OOBINLINE) +static ERL_NIF_TERM nsetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_PEEK_OFF) +static ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_PRIORITY) +static ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_RCVBUF) +static ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_RCVLOWAT) +static ERL_NIF_TERM nsetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_RCVTIMEO) +static ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_REUSEADDR) +static ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_REUSEPORT) +static ERL_NIF_TERM nsetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_SNDBUF) +static ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_SNDLOWAT) +static ERL_NIF_TERM nsetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_SNDTIMEO) +static ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SO_TIMESTAMP) +static ERL_NIF_TERM nsetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +static ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); + +/* *** Handling set of socket options for level = ip *** */ +#if defined(IP_ADD_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_ADD_SOURCE_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_add_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_BLOCK_SOURCE) +static ERL_NIF_TERM nsetopt_lvl_ip_block_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_DROP_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_DROP_SOURCE_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_FREEBIND) +static ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_HDRINCL) +static ERL_NIF_TERM nsetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_MINTTL) +static ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) +static ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +static BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv* env, + ERL_NIF_TERM eVal, + Uint32* mode); +static ERL_NIF_TERM nsetopt_lvl_ip_msfilter_set(ErlNifEnv* env, + SOCKET sock, + struct ip_msfilter* msfP, + SOCKLEN_T optLen); +#endif +#if defined(IP_MTU_DISCOVER) +static ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_MULTICAST_ALL) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_MULTICAST_IF) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_MULTICAST_LOOP) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_MULTICAST_TTL) +static ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_NODEFRAG) +static ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_PKTINFO) +static ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVDSTADDR) +static ERL_NIF_TERM nsetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVERR) +static ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVIF) +static ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVOPTS) +static ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVORIGDSTADDR) +static ERL_NIF_TERM nsetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVTOS) +static ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RECVTTL) +static ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_RETOPTS) +static ERL_NIF_TERM nsetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_ROUTER_ALERT) +static ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_SENDSRCADDR) +static ERL_NIF_TERM nsetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_TOS) +static ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_TRANSPARENT) +static ERL_NIF_TERM nsetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_TTL) +static ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IP_UNBLOCK_SOURCE) +static ERL_NIF_TERM nsetopt_lvl_ip_unblock_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif + +#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif +#if defined(IP_ADD_SOURCE_MEMBERSHIP) || defined(IP_DROP_SOURCE_MEMBERSHIP) || defined(IP_BLOCK_SOURCE) || defined(IP_UNBLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif + + +/* *** Handling set of socket options for level = ipv6 *** */ +#if defined(HAVE_IPV6) +static ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(IPV6_ADDRFORM) +static ERL_NIF_TERM nsetopt_lvl_ipv6_addrform(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_ADD_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_AUTHHDR) +static ERL_NIF_TERM nsetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_DROP_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_DSTOPTS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_FLOWINFO) +static ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_HOPLIMIT) +static ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_HOPOPTS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_MTU) +static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_MTU_DISCOVER) +static ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_MULTICAST_HOPS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_MULTICAST_IF) +static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_MULTICAST_LOOP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_RECVERR) +static ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_ROUTER_ALERT) +static ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_RTHDR) +static ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_UNICAST_HOPS) +static ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(IPV6_V6ONLY) +static ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif + +#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP) +static ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt); +#endif + +#endif // defined(HAVE_IPV6) +static ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(TCP_CONGESTION) +static ERL_NIF_TERM nsetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(TCP_MAXSEG) +static ERL_NIF_TERM nsetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(TCP_NODELAY) +static ERL_NIF_TERM nsetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +static ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(UDP_CORK) +static ERL_NIF_TERM nsetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(HAVE_SCTP) +static ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal); +#if defined(SCTP_ASSOCINFO) +static ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_AUTOCLOSE) +static ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_DISABLE_FRAGMENTS) +static ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_EVENTS) +static ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_INITMSG) +static ERL_NIF_TERM nsetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_MAXSEG) +static ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_NODELAY) +static ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#if defined(SCTP_RTOINFO) +static ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal); +#endif +#endif // defined(HAVE_SCTP) + +static ERL_NIF_TERM ngetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + ERL_NIF_TERM eOpt); +static ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +static ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_fd(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + ERL_NIF_TERM eOpt); +static ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + SOCKOPTLEN_T valueSz); +static ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt); +static ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(SO_ACCEPTCONN) +static ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_BINDTODEVICE) +static ERL_NIF_TERM ngetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_BROADCAST) +static ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_DEBUG) +static ERL_NIF_TERM ngetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_DOMAIN) +static ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_DONTROUTE) +static ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_KEEPALIVE) +static ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_LINGER) +static ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_OOBINLINE) +static ERL_NIF_TERM ngetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_PEEK_OFF) +static ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_PRIORITY) +static ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_PROTOCOL) +static ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_RCVBUF) +static ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_RCVLOWAT) +static ERL_NIF_TERM ngetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_RCVTIMEO) +static ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_REUSEADDR) +static ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_REUSEPORT) +static ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_SNDBUF) +static ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_SNDLOWAT) +static ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_SNDTIMEO) +static ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_TIMESTAMP) +static ERL_NIF_TERM ngetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SO_TYPE) +static ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +static ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(IP_FREEBIND) +static ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_HDRINCL) +static ERL_NIF_TERM ngetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MINTTL) +static ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MTU) +static ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MTU_DISCOVER) +static ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MULTICAST_ALL) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MULTICAST_IF) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MULTICAST_LOOP) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_MULTICAST_TTL) +static ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_NODEFRAG) +static ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_PKTINFO) +static ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVDSTADDR) +static ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVERR) +static ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVIF) +static ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVOPTS) +static ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVORIGDSTADDR) +static ERL_NIF_TERM ngetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVTOS) +static ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RECVTTL) +static ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_RETOPTS) +static ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_ROUTER_ALERT) +static ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_SENDSRCADDR) +static ERL_NIF_TERM ngetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_TOS) +static ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_TRANSPARENT) +static ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IP_TTL) +static ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(HAVE_IPV6) +static ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(IPV6_AUTHHDR) +static ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_DSTOPTS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_FLOWINFO) +static ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_HOPLIMIT) +static ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_HOPOPTS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_MTU) +static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_MTU_DISCOVER) +static ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_MULTICAST_HOPS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_MULTICAST_IF) +static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_MULTICAST_LOOP) +static ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_RECVERR) +static ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_ROUTER_ALERT) +static ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_RTHDR) +static ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_UNICAST_HOPS) +static ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(IPV6_V6ONLY) +static ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP); +#endif + +#endif // defined(HAVE_IPV6) + +static ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(TCP_CONGESTION) +static ERL_NIF_TERM ngetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(TCP_MAXSEG) +static ERL_NIF_TERM ngetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(TCP_NODELAY) +static ERL_NIF_TERM ngetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +static ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(UDP_CORK) +static ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(HAVE_SCTP) +static ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt); +#if defined(SCTP_ASSOCINFO) +static ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_AUTOCLOSE) +static ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_DISABLE_FRAGMENTS) +static ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_MAXSEG) +static ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_INITMSG) +static ERL_NIF_TERM ngetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_NODELAY) +static ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#if defined(SCTP_RTOINFO) +static ERL_NIF_TERM ngetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP); +#endif +#endif // defined(HAVE_SCTP) +static ERL_NIF_TERM nsockname(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM npeername(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM op, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_connect(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_accept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_send(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ncancel_recv_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_write_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef); +static ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef, + int smode, + int rmode); + +static ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); +static ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal); + +static ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max); +static ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt); +static ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt); +static ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt); + +static BOOLEAN_T send_check_writer(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult); +static ERL_NIF_TERM send_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + ssize_t written, + ssize_t dataSize, + int saveErrno, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef); +static BOOLEAN_T recv_check_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult); +static char* recv_init_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref); +static ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, + SocketDescriptor* descP); +static void recv_error_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM reason); +static ERL_NIF_TERM recv_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int toRead, + int saveErrno, + ErlNifBinary* bufP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef); +static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int saveErrno, + ErlNifBinary* bufP, + SocketAddress* fromAddrP, + unsigned int fromAddrLen, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef); +static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int saveErrno, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef); + +static ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, + SocketDescriptor* descP); + +extern char* encode_msghdr(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eSockAddr); +extern char* encode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifBinary* cmsgBinP, + struct msghdr* msgHdrP, + ERL_NIF_TERM* eCMsgHdr); +extern char* decode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + char* cmsgHdrBufP, + size_t cmsgHdrBufLen, + size_t* cmsgHdrBufUsed); +extern char* decode_cmsghdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + char* bufP, + size_t rem, + size_t* used); +static char* encode_cmsghdr_level(ErlNifEnv* env, + int level, + ERL_NIF_TERM* eLevel); +static char* decode_cmsghdr_level(ErlNifEnv* env, + ERL_NIF_TERM eLevel, + int* level); +static char* encode_cmsghdr_type(ErlNifEnv* env, + int level, + int type, + ERL_NIF_TERM* eType); +static char* decode_cmsghdr_type(ErlNifEnv* env, + int level, + ERL_NIF_TERM eType, + int* type); +static char* encode_cmsghdr_data(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int level, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +static char* encode_cmsghdr_data_socket(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +static char* encode_cmsghdr_data_ip(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +#if defined(HAVE_IPV6) +static char* encode_cmsghdr_data_ipv6(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData); +#endif +extern char* encode_msghdr_flags(ErlNifEnv* env, + SocketDescriptor* descP, + int msgFlags, + ERL_NIF_TERM* flags); +static char* decode_cmsghdr_data(ErlNifEnv* env, + SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + ERL_NIF_TERM eData, + size_t* used); +static char* decode_cmsghdr_final(SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + char* data, + int sz, + size_t* used); +static BOOLEAN_T decode_sock_linger(ErlNifEnv* env, + ERL_NIF_TERM eVal, + struct linger* valP); +#if defined(IP_TOS) +static BOOLEAN_T decode_ip_tos(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* val); +#endif +#if defined(IP_MTU_DISCOVER) +static char* decode_ip_pmtudisc(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* val); +#endif +#if defined(IP_MTU_DISCOVER) +static void encode_ip_pmtudisc(ErlNifEnv* env, + int val, + ERL_NIF_TERM* eVal); +#endif +#if defined(IPV6_MTU_DISCOVER) +static char* decode_ipv6_pmtudisc(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* val); +#endif +#if defined(IPV6_MTU_DISCOVER) +static void encode_ipv6_pmtudisc(ErlNifEnv* env, + int val, + ERL_NIF_TERM* eVal); +#endif + +/* +static BOOLEAN_T decode_bool(ErlNifEnv* env, + ERL_NIF_TERM eVal, + BOOLEAN_T* val); +*/ +static BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, + ERL_NIF_TERM eVal, + int* opt, + Uint16* valueType, + int* valueSz); +// static void encode_bool(BOOLEAN_T val, ERL_NIF_TERM* eVal); +static ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val); + +static void inform_waiting_procs(ErlNifEnv* env, + SocketDescriptor* descP, + SocketRequestQueue* q, + BOOLEAN_T free, + ERL_NIF_TERM reason); + +static int socket_setopt(int sock, + int level, + int opt, + const void* optVal, + const socklen_t optLen); + +static BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err); + +static SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event); + +static int compare_pids(ErlNifEnv* env, + const ErlNifPid* pid1, + const ErlNifPid* pid2); + + + +static BOOLEAN_T edomain2domain(int edomain, int* domain); +static BOOLEAN_T etype2type(int etype, int* type); +static BOOLEAN_T eproto2proto(ErlNifEnv* env, + const ERL_NIF_TERM eproto, + int* proto); +static BOOLEAN_T ehow2how(unsigned int ehow, int* how); +static BOOLEAN_T esendflags2sendflags(unsigned int esendflags, int* sendflags); +static BOOLEAN_T erecvflags2recvflags(unsigned int erecvflags, int* recvflags); +static BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, + int eLevel, + BOOLEAN_T* isOTP, + int* level); +#ifdef HAVE_SETNS +static BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns); +static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); +static BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err); +#endif + +static BOOLEAN_T cnt_inc(Uint32* cnt, Uint32 inc); +static void cnt_dec(Uint32* cnt, Uint32 dec); + +static void inc_socket(int domain, int type, int protocol); +static void dec_socket(int domain, int type, int protocol); + + +static BOOLEAN_T acceptor_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid); +static ERL_NIF_TERM acceptor_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref); +static BOOLEAN_T acceptor_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + // ErlNifMonitor* mon, + ESockMonitor* mon, + ERL_NIF_TERM* ref); +static BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); + +static BOOLEAN_T writer_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid); +static ERL_NIF_TERM writer_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref); +static BOOLEAN_T writer_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + // ErlNifMonitor* mon, + ESockMonitor* mon, + ERL_NIF_TERM* ref); +static BOOLEAN_T writer_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); + +static BOOLEAN_T reader_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid); +static ERL_NIF_TERM reader_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref); +static BOOLEAN_T reader_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + // ErlNifMonitor* mon, + ESockMonitor* mon, + ERL_NIF_TERM* ref); +static BOOLEAN_T reader_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); + +static BOOLEAN_T qsearch4pid(ErlNifEnv* env, + SocketRequestQueue* q, + ErlNifPid* pid); +static void qpush(SocketRequestQueue* q, + SocketRequestQueueElement* e); +static SocketRequestQueueElement* qpop(SocketRequestQueue* q); +static BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const char* slogan, + SocketRequestQueue* q, + const ErlNifPid* pid); + +static int esock_monitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid, + ESockMonitor* mon); +static int esock_demonitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + ESockMonitor* monP); +static void esock_monitor_init(ESockMonitor* mon); +/* +static int esock_monitor_compare(const ErlNifMonitor* mon1, + const ESockMonitor* mon2); +*/ + + +/* +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +static size_t my_strnlen(const char *s, size_t maxlen); +#endif +*/ + +static void socket_dtor(ErlNifEnv* env, void* obj); +static void socket_stop(ErlNifEnv* env, + void* obj, + int fd, + int is_direct_call); +static void socket_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon); +static void socket_down_acceptor(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); +static void socket_down_writer(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); +static void socket_down_reader(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid); + +static char* esock_send_close_msg(ErlNifEnv* env, + ERL_NIF_TERM closeRef, + ErlNifPid* pid); +static char* esock_send_abort_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + ERL_NIF_TERM reason, + ErlNifPid* pid); +static char* esock_send_socket_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM tag, + ERL_NIF_TERM info, + ErlNifPid* pid); +static char* esock_send_msg(ErlNifEnv* env, + ERL_NIF_TERM msg, + ErlNifPid* pid); + +static BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map); +static BOOLEAN_T extract_iow(ErlNifEnv* env, + ERL_NIF_TERM map); + +static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); + + + +#if HAVE_IN6 +# if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY +# if HAVE_DECL_IN6ADDR_ANY_INIT +static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } }; +# else +static const struct in6_addr in6addr_any = + { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }; +# endif /* HAVE_IN6ADDR_ANY_INIT */ +# endif /* ! HAVE_DECL_IN6ADDR_ANY */ + +# if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK +# if HAVE_DECL_IN6ADDR_LOOPBACK_INIT +static const struct in6_addr in6addr_loopback = + { { IN6ADDR_LOOPBACK_INIT } }; +# else +static const struct in6_addr in6addr_loopback = + { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }; +# endif /* HAVE_IN6ADDR_LOOPBACk_INIT */ +# endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */ +#endif /* HAVE_IN6 */ + + + +/* *** String constants *** */ +static char str_adaptation_layer[] = "adaptation_layer"; +static char str_address[] = "address"; +static char str_association[] = "association"; +static char str_assoc_id[] = "assoc_id"; +static char str_authentication[] = "authentication"; +// static char str_any[] = "any"; +static char str_bool[] = "bool"; +static char str_close[] = "close"; +static char str_closed[] = "closed"; +static char str_closing[] = "closing"; +static char str_cookie_life[] = "cookie_life"; +static char str_data_in[] = "data_in"; +static char str_do[] = "do"; +static char str_dont[] = "dont"; +static char str_exclude[] = "exclude"; +static char str_false[] = "false"; +static char str_global_counters[] = "global_counters"; +static char str_in4_sockaddr[] = "in4_sockaddr"; +static char str_in6_sockaddr[] = "in6_sockaddr"; +static char str_include[] = "include"; +static char str_initial[] = "initial"; +static char str_int[] = "int"; +static char str_interface[] = "interface"; +static char str_iow[] = "iow"; +static char str_local_rwnd[] = "local_rwnd"; +// static char str_loopback[] = "loopback"; +static char str_max[] = "max"; +static char str_max_attempts[] = "max_attempts"; +static char str_max_init_timeo[] = "max_init_timeo"; +static char str_max_instreams[] = "max_instreams"; +static char str_max_rxt[] = "max_rxt"; +static char str_min[] = "min"; +static char str_mode[] = "mode"; +static char str_multiaddr[] = "multiaddr"; +// static char str_nif_abort[] = "nif_abort"; +static char str_null[] = "null"; +static char str_num_dlocal[] = "num_domain_local"; +static char str_num_dinet[] = "num_domain_inet"; +static char str_num_dinet6[] = "num_domain_inet6"; +static char str_num_outstreams[] = "num_outstreams"; +static char str_num_peer_dests[] = "num_peer_dests"; +static char str_num_pip[] = "num_proto_ip"; +static char str_num_psctp[] = "num_proto_sctp"; +static char str_num_ptcp[] = "num_proto_tcp"; +static char str_num_pudp[] = "num_proto_udp"; +static char str_num_sockets[] = "num_sockets"; +static char str_num_tdgrams[] = "num_type_dgram"; +static char str_num_tseqpkgs[] = "num_type_seqpacket"; +static char str_num_tstreams[] = "num_type_stream"; +static char str_partial_delivery[] = "partial_delivery"; +static char str_peer_error[] = "peer_error"; +static char str_peer_rwnd[] = "peer_rwnd"; +static char str_probe[] = "probe"; +static char str_select[] = "select"; +static char str_sender_dry[] = "sender_dry"; +static char str_send_failure[] = "send_failure"; +static char str_shutdown[] = "shutdown"; +static char str_slist[] = "slist"; +static char str_sourceaddr[] = "sourceaddr"; +static char str_timeout[] = "timeout"; +static char str_true[] = "true"; +static char str_want[] = "want"; + +/* (special) error string constants */ +static char str_eisconn[] = "eisconn"; +static char str_enotclosing[] = "enotclosing"; +static char str_enotconn[] = "enotconn"; +static char str_exalloc[] = "exalloc"; +static char str_exbadstate[] = "exbadstate"; +static char str_exbusy[] = "exbusy"; +static char str_exmon[] = "exmonitor"; // failed monitor +static char str_exself[] = "exself"; // failed self +static char str_exsend[] = "exsend"; // failed send + + +/* *** "Global" Atoms *** */ +ERL_NIF_TERM esock_atom_abort; +ERL_NIF_TERM esock_atom_accept; +ERL_NIF_TERM esock_atom_acceptconn; +ERL_NIF_TERM esock_atom_acceptfilter; +ERL_NIF_TERM esock_atom_adaption_layer; +ERL_NIF_TERM esock_atom_addr; +ERL_NIF_TERM esock_atom_addrform; +ERL_NIF_TERM esock_atom_add_membership; +ERL_NIF_TERM esock_atom_add_source_membership; +ERL_NIF_TERM esock_atom_any; +ERL_NIF_TERM esock_atom_associnfo; +ERL_NIF_TERM esock_atom_authhdr; +ERL_NIF_TERM esock_atom_auth_active_key; +ERL_NIF_TERM esock_atom_auth_asconf; +ERL_NIF_TERM esock_atom_auth_chunk; +ERL_NIF_TERM esock_atom_auth_delete_key; +ERL_NIF_TERM esock_atom_auth_key; +ERL_NIF_TERM esock_atom_auth_level; +ERL_NIF_TERM esock_atom_autoclose; +ERL_NIF_TERM esock_atom_bindtodevice; +ERL_NIF_TERM esock_atom_block_source; +ERL_NIF_TERM esock_atom_broadcast; +ERL_NIF_TERM esock_atom_busy_poll; +ERL_NIF_TERM esock_atom_checksum; +ERL_NIF_TERM esock_atom_close; +ERL_NIF_TERM esock_atom_connect; +ERL_NIF_TERM esock_atom_congestion; +ERL_NIF_TERM esock_atom_context; +ERL_NIF_TERM esock_atom_cork; +ERL_NIF_TERM esock_atom_credentials; +ERL_NIF_TERM esock_atom_ctrl; +ERL_NIF_TERM esock_atom_ctrunc; +ERL_NIF_TERM esock_atom_data; +ERL_NIF_TERM esock_atom_debug; +ERL_NIF_TERM esock_atom_default_send_params; +ERL_NIF_TERM esock_atom_delayed_ack_time; +ERL_NIF_TERM esock_atom_dgram; +ERL_NIF_TERM esock_atom_disable_fragments; +ERL_NIF_TERM esock_atom_domain; +ERL_NIF_TERM esock_atom_dontfrag; +ERL_NIF_TERM esock_atom_dontroute; +ERL_NIF_TERM esock_atom_drop_membership; +ERL_NIF_TERM esock_atom_drop_source_membership; +ERL_NIF_TERM esock_atom_dstopts; +ERL_NIF_TERM esock_atom_eor; +ERL_NIF_TERM esock_atom_error; +ERL_NIF_TERM esock_atom_errqueue; +ERL_NIF_TERM esock_atom_esp_network_level; +ERL_NIF_TERM esock_atom_esp_trans_level; +ERL_NIF_TERM esock_atom_events; +ERL_NIF_TERM esock_atom_explicit_eor; +ERL_NIF_TERM esock_atom_faith; +ERL_NIF_TERM esock_atom_false; +ERL_NIF_TERM esock_atom_family; +ERL_NIF_TERM esock_atom_flags; +ERL_NIF_TERM esock_atom_flowinfo; +ERL_NIF_TERM esock_atom_fragment_interleave; +ERL_NIF_TERM esock_atom_freebind; +ERL_NIF_TERM esock_atom_get_peer_addr_info; +ERL_NIF_TERM esock_atom_hdrincl; +ERL_NIF_TERM esock_atom_hmac_ident; +ERL_NIF_TERM esock_atom_hoplimit; +ERL_NIF_TERM esock_atom_hopopts; +ERL_NIF_TERM esock_atom_ifindex; +ERL_NIF_TERM esock_atom_inet; +ERL_NIF_TERM esock_atom_inet6; +ERL_NIF_TERM esock_atom_info; +ERL_NIF_TERM esock_atom_initmsg; +ERL_NIF_TERM esock_atom_iov; +ERL_NIF_TERM esock_atom_ip; +ERL_NIF_TERM esock_atom_ipcomp_level; +ERL_NIF_TERM esock_atom_ipv6; +ERL_NIF_TERM esock_atom_i_want_mapped_v4_addr; +ERL_NIF_TERM esock_atom_join_group; +ERL_NIF_TERM esock_atom_keepalive; +ERL_NIF_TERM esock_atom_keepcnt; +ERL_NIF_TERM esock_atom_keepidle; +ERL_NIF_TERM esock_atom_keepintvl; +ERL_NIF_TERM esock_atom_leave_group; +ERL_NIF_TERM esock_atom_level; +ERL_NIF_TERM esock_atom_linger; +ERL_NIF_TERM esock_atom_local; +ERL_NIF_TERM esock_atom_local_auth_chunks; +ERL_NIF_TERM esock_atom_loopback; +ERL_NIF_TERM esock_atom_lowdelay; +ERL_NIF_TERM esock_atom_mark; +ERL_NIF_TERM esock_atom_maxburst; +ERL_NIF_TERM esock_atom_maxseg; +ERL_NIF_TERM esock_atom_md5sig; +ERL_NIF_TERM esock_atom_mincost; +ERL_NIF_TERM esock_atom_minttl; +ERL_NIF_TERM esock_atom_msfilter; +ERL_NIF_TERM esock_atom_mtu; +ERL_NIF_TERM esock_atom_mtu_discover; +ERL_NIF_TERM esock_atom_multicast_all; +ERL_NIF_TERM esock_atom_multicast_hops; +ERL_NIF_TERM esock_atom_multicast_if; +ERL_NIF_TERM esock_atom_multicast_loop; +ERL_NIF_TERM esock_atom_multicast_ttl; +ERL_NIF_TERM esock_atom_nodelay; +ERL_NIF_TERM esock_atom_nodefrag; +ERL_NIF_TERM esock_atom_noopt; +ERL_NIF_TERM esock_atom_nopush; +ERL_NIF_TERM esock_atom_not_found; +ERL_NIF_TERM esock_atom_not_owner; +ERL_NIF_TERM esock_atom_ok; +ERL_NIF_TERM esock_atom_oob; +ERL_NIF_TERM esock_atom_oobinline; +ERL_NIF_TERM esock_atom_options; +ERL_NIF_TERM esock_atom_origdstaddr; +ERL_NIF_TERM esock_atom_partial_delivery_point; +ERL_NIF_TERM esock_atom_passcred; +ERL_NIF_TERM esock_atom_path; +ERL_NIF_TERM esock_atom_peekcred; +ERL_NIF_TERM esock_atom_peek_off; +ERL_NIF_TERM esock_atom_peer_addr_params; +ERL_NIF_TERM esock_atom_peer_auth_chunks; +ERL_NIF_TERM esock_atom_pktinfo; +ERL_NIF_TERM esock_atom_pktoptions; +ERL_NIF_TERM esock_atom_port; +ERL_NIF_TERM esock_atom_portrange; +ERL_NIF_TERM esock_atom_primary_addr; +ERL_NIF_TERM esock_atom_priority; +ERL_NIF_TERM esock_atom_protocol; +ERL_NIF_TERM esock_atom_raw; +ERL_NIF_TERM esock_atom_rcvbuf; +ERL_NIF_TERM esock_atom_rcvbufforce; +ERL_NIF_TERM esock_atom_rcvlowat; +ERL_NIF_TERM esock_atom_rcvtimeo; +ERL_NIF_TERM esock_atom_rdm; +ERL_NIF_TERM esock_atom_recv; +ERL_NIF_TERM esock_atom_recvdstaddr; +ERL_NIF_TERM esock_atom_recverr; +ERL_NIF_TERM esock_atom_recvfrom; +ERL_NIF_TERM esock_atom_recvif; +ERL_NIF_TERM esock_atom_recvmsg; +ERL_NIF_TERM esock_atom_recvopts; +ERL_NIF_TERM esock_atom_recvorigdstaddr; +ERL_NIF_TERM esock_atom_recvpktinfo; +ERL_NIF_TERM esock_atom_recvtclass; +ERL_NIF_TERM esock_atom_recvtos; +ERL_NIF_TERM esock_atom_recvttl; +ERL_NIF_TERM esock_atom_reliability; +ERL_NIF_TERM esock_atom_reset_streams; +ERL_NIF_TERM esock_atom_retopts; +ERL_NIF_TERM esock_atom_reuseaddr; +ERL_NIF_TERM esock_atom_reuseport; +ERL_NIF_TERM esock_atom_rights; +ERL_NIF_TERM esock_atom_router_alert; +ERL_NIF_TERM esock_atom_rthdr; +ERL_NIF_TERM esock_atom_rtoinfo; +ERL_NIF_TERM esock_atom_rxq_ovfl; +ERL_NIF_TERM esock_atom_scope_id; +ERL_NIF_TERM esock_atom_sctp; +ERL_NIF_TERM esock_atom_sec; +ERL_NIF_TERM esock_atom_select_sent; +ERL_NIF_TERM esock_atom_send; +ERL_NIF_TERM esock_atom_sendmsg; +ERL_NIF_TERM esock_atom_sendsrcaddr; +ERL_NIF_TERM esock_atom_sendto; +ERL_NIF_TERM esock_atom_seqpacket; +ERL_NIF_TERM esock_atom_setfib; +ERL_NIF_TERM esock_atom_set_peer_primary_addr; +ERL_NIF_TERM esock_atom_socket; +ERL_NIF_TERM esock_atom_socket_tag; +ERL_NIF_TERM esock_atom_sndbuf; +ERL_NIF_TERM esock_atom_sndbufforce; +ERL_NIF_TERM esock_atom_sndlowat; +ERL_NIF_TERM esock_atom_sndtimeo; +ERL_NIF_TERM esock_atom_spec_dst; +ERL_NIF_TERM esock_atom_status; +ERL_NIF_TERM esock_atom_stream; +ERL_NIF_TERM esock_atom_syncnt; +ERL_NIF_TERM esock_atom_tclass; +ERL_NIF_TERM esock_atom_tcp; +ERL_NIF_TERM esock_atom_throughput; +ERL_NIF_TERM esock_atom_timestamp; +ERL_NIF_TERM esock_atom_tos; +ERL_NIF_TERM esock_atom_transparent; +ERL_NIF_TERM esock_atom_true; +ERL_NIF_TERM esock_atom_trunc; +ERL_NIF_TERM esock_atom_ttl; +ERL_NIF_TERM esock_atom_type; +ERL_NIF_TERM esock_atom_udp; +ERL_NIF_TERM esock_atom_unblock_source; +ERL_NIF_TERM esock_atom_undefined; +ERL_NIF_TERM esock_atom_unicast_hops; +ERL_NIF_TERM esock_atom_unknown; +ERL_NIF_TERM esock_atom_usec; +ERL_NIF_TERM esock_atom_user_timeout; +ERL_NIF_TERM esock_atom_use_ext_recvinfo; +ERL_NIF_TERM esock_atom_use_min_mtu; +ERL_NIF_TERM esock_atom_v6only; + +/* *** "Global" error (=reason) atoms *** */ +ERL_NIF_TERM esock_atom_eagain; +ERL_NIF_TERM esock_atom_eafnosupport; +ERL_NIF_TERM esock_atom_einval; + +/* *** Atoms *** */ +static ERL_NIF_TERM atom_adaptation_layer; +static ERL_NIF_TERM atom_address; +static ERL_NIF_TERM atom_association; +static ERL_NIF_TERM atom_assoc_id; +static ERL_NIF_TERM atom_authentication; +static ERL_NIF_TERM atom_bool; +static ERL_NIF_TERM atom_close; +static ERL_NIF_TERM atom_closed; +static ERL_NIF_TERM atom_closing; +static ERL_NIF_TERM atom_cookie_life; +static ERL_NIF_TERM atom_data_in; +static ERL_NIF_TERM atom_do; +static ERL_NIF_TERM atom_dont; +static ERL_NIF_TERM atom_exclude; +static ERL_NIF_TERM atom_false; +static ERL_NIF_TERM atom_global_counters; +static ERL_NIF_TERM atom_in4_sockaddr; +static ERL_NIF_TERM atom_in6_sockaddr; +static ERL_NIF_TERM atom_include; +static ERL_NIF_TERM atom_initial; +static ERL_NIF_TERM atom_int; +static ERL_NIF_TERM atom_interface; +static ERL_NIF_TERM atom_iow; +static ERL_NIF_TERM atom_local_rwnd; +static ERL_NIF_TERM atom_max; +static ERL_NIF_TERM atom_max_attempts; +static ERL_NIF_TERM atom_max_init_timeo; +static ERL_NIF_TERM atom_max_instreams; +static ERL_NIF_TERM atom_max_rxt; +static ERL_NIF_TERM atom_min; +static ERL_NIF_TERM atom_mode; +static ERL_NIF_TERM atom_multiaddr; +// static ERL_NIF_TERM atom_nif_abort; +static ERL_NIF_TERM atom_null; +static ERL_NIF_TERM atom_num_dinet; +static ERL_NIF_TERM atom_num_dinet6; +static ERL_NIF_TERM atom_num_dlocal; +static ERL_NIF_TERM atom_num_outstreams; +static ERL_NIF_TERM atom_num_peer_dests; +static ERL_NIF_TERM atom_num_pip; +static ERL_NIF_TERM atom_num_psctp; +static ERL_NIF_TERM atom_num_ptcp; +static ERL_NIF_TERM atom_num_pudp; +static ERL_NIF_TERM atom_num_sockets; +static ERL_NIF_TERM atom_num_tdgrams; +static ERL_NIF_TERM atom_num_tseqpkgs; +static ERL_NIF_TERM atom_num_tstreams; +static ERL_NIF_TERM atom_partial_delivery; +static ERL_NIF_TERM atom_peer_error; +static ERL_NIF_TERM atom_peer_rwnd; +static ERL_NIF_TERM atom_probe; +static ERL_NIF_TERM atom_select; +static ERL_NIF_TERM atom_sender_dry; +static ERL_NIF_TERM atom_send_failure; +static ERL_NIF_TERM atom_shutdown; +static ERL_NIF_TERM atom_slist; +static ERL_NIF_TERM atom_sourceaddr; +static ERL_NIF_TERM atom_timeout; +static ERL_NIF_TERM atom_true; +static ERL_NIF_TERM atom_want; + +static ERL_NIF_TERM atom_eisconn; +static ERL_NIF_TERM atom_enotclosing; +static ERL_NIF_TERM atom_enotconn; +static ERL_NIF_TERM atom_exalloc; +static ERL_NIF_TERM atom_exbadstate; +static ERL_NIF_TERM atom_exbusy; +static ERL_NIF_TERM atom_exmon; +static ERL_NIF_TERM atom_exself; +static ERL_NIF_TERM atom_exsend; + + +/* *** Sockets *** */ +static ErlNifResourceType* sockets; +static ErlNifResourceTypeInit socketInit = { + socket_dtor, + socket_stop, + (ErlNifResourceDown*) socket_down +}; + +// Initiated when the nif is loaded +static SocketData data; + + +/* ---------------------------------------------------------------------- + * N I F F u n c t i o n s + * ---------------------------------------------------------------------- + * + * Utility and admin functions: + * ---------------------------- + * nif_info/0 + * nif_supports/1 + * (nif_debug/1) + * + * The "proper" socket functions: + * ------------------------------ + * nif_open(Domain, Type, Protocol, Extra) + * nif_bind(Sock, LocalAddr) + * nif_connect(Sock, SockAddr) + * nif_listen(Sock, Backlog) + * nif_accept(LSock, Ref) + * nif_send(Sock, SendRef, Data, Flags) + * nif_sendto(Sock, SendRef, Data, Dest, Flags) + * nif_sendmsg(Sock, SendRef, MsgHdr, Flags) + * nif_recv(Sock, RecvRef, Length, Flags) + * nif_recvfrom(Sock, RecvRef, BufSz, Flags) + * nif_recvmsg(Sock, RecvRef, BufSz, CtrlSz, Flags) + * nif_close(Sock) + * nif_shutdown(Sock, How) + * nif_sockname(Sock) + * nif_peername(Sock) + * + * And some functions to manipulate and retrieve socket options: + * ------------------------------------------------------------- + * nif_setopt/5 + * nif_getopt/4 + * + * And some utility functions: + * ------------------------------------------------------------- + * + * And some socket admin functions: + * ------------------------------------------------------------- + * nif_cancel(Sock, Ref) + */ + + +/* ---------------------------------------------------------------------- + * nif_info + * + * Description: + * This is currently just a placeholder... + */ +#define MKCT(E, T, C) MKT2((E), (T), MKI((E), (C))) + +static +ERL_NIF_TERM nif_info(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + if (argc != 0) { + return enif_make_badarg(env); + } else { + ERL_NIF_TERM numSockets = MKCT(env, atom_num_sockets, data.numSockets); + ERL_NIF_TERM numTypeDGrams = MKCT(env, atom_num_tdgrams, data.numTypeDGrams); + ERL_NIF_TERM numTypeStreams = MKCT(env, atom_num_tstreams, data.numTypeStreams); + ERL_NIF_TERM numTypeSeqPkgs = MKCT(env, atom_num_tseqpkgs, data.numTypeSeqPkgs); + ERL_NIF_TERM numDomLocal = MKCT(env, atom_num_dlocal, data.numDomainLocal); + ERL_NIF_TERM numDomInet = MKCT(env, atom_num_dinet, data.numDomainInet); + ERL_NIF_TERM numDomInet6 = MKCT(env, atom_num_dinet6, data.numDomainInet6); + ERL_NIF_TERM numProtoIP = MKCT(env, atom_num_pip, data.numProtoIP); + ERL_NIF_TERM numProtoTCP = MKCT(env, atom_num_ptcp, data.numProtoTCP); + ERL_NIF_TERM numProtoUDP = MKCT(env, atom_num_pudp, data.numProtoUDP); + ERL_NIF_TERM numProtoSCTP = MKCT(env, atom_num_psctp, data.numProtoSCTP); + ERL_NIF_TERM gcnt[] = {numSockets, + numTypeDGrams, numTypeStreams, numTypeSeqPkgs, + numDomLocal, numDomInet, numDomInet6, + numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP}; + unsigned int lenGCnt = sizeof(gcnt) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM lgcnt = MKLA(env, gcnt, lenGCnt); + ERL_NIF_TERM keys[] = {esock_atom_debug, atom_iow, atom_global_counters}; + ERL_NIF_TERM vals[] = {BOOL2ATOM(data.dbg), BOOL2ATOM(data.iow), lgcnt}; + ERL_NIF_TERM info; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &info)) + return enif_make_badarg(env); + + return info; + } +#endif +} + + + +/* ---------------------------------------------------------------------- + * nif_supports + * + * Description: + * This function is intended to answer the question: "Is X supported?" + * Currently only one key is "supported": options + * That results in a list of all *known options* (known by us) and if + * the platform supports (OS) it or not. + * + * Key + * --- + * options [{socket, [{Opt, boolean()}]}, + * {ip, [{Opt, boolean()}]}, + * {ipv6, [{Opt, boolean()}]}, + * {tcp, [{Opt, boolean()}]}, + * {udp, [{Opt, boolean()}]}, + * {sctp, [{Opt, boolean()}]}] + */ + +static +ERL_NIF_TERM nif_supports(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + int key; + + SGDBG( ("SOCKET", "nif_supports -> entry with %d args\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !GET_INT(env, argv[0], &key)) { + return enif_make_badarg(env); + } + + return nsupports(env, key); +#endif +} + + + +/* nopen - create an endpoint for communication + * + * Assumes the input has been validated. + * + * Normally we want debugging on (individual) sockets to be controlled + * by the sockets own debug flag. But since we don't even have a socket + * yet, we must use the global debug flag. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports(ErlNifEnv* env, int key) +{ + ERL_NIF_TERM result; + + SGDBG( ("SOCKET", "nsupports -> entry with 0x%lX\r\n", key) ); + + switch (key) { + case SOCKET_SUPPORTS_OPTIONS: + result = nsupports_options(env); + break; + + case SOCKET_SUPPORTS_SCTP: + result = nsupports_sctp(env); + break; + + case SOCKET_SUPPORTS_IPV6: + result = nsupports_ipv6(env); + break; + + default: + result = esock_atom_false; + break; + } + + return result; +} +#endif + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options(ErlNifEnv* env) +{ + ERL_NIF_TERM sockOpts = nsupports_options_socket(env); + ERL_NIF_TERM sockOptsT = MKT2(env, esock_atom_socket, sockOpts); + ERL_NIF_TERM ipOpts = nsupports_options_ip(env); + ERL_NIF_TERM ipOptsT = MKT2(env, esock_atom_ip, ipOpts); + ERL_NIF_TERM ipv6Opts = nsupports_options_ipv6(env); + ERL_NIF_TERM ipv6OptsT = MKT2(env, esock_atom_ipv6, ipv6Opts); + ERL_NIF_TERM tcpOpts = nsupports_options_tcp(env); + ERL_NIF_TERM tcpOptsT = MKT2(env, esock_atom_tcp, tcpOpts); + ERL_NIF_TERM udpOpts = nsupports_options_udp(env); + ERL_NIF_TERM udpOptsT = MKT2(env, esock_atom_udp, udpOpts); + ERL_NIF_TERM sctpOpts = nsupports_options_sctp(env); + ERL_NIF_TERM sctpOptsT = MKT2(env, esock_atom_sctp, sctpOpts); + ERL_NIF_TERM optsA[] = {sockOptsT, + ipOptsT, ipv6OptsT, + tcpOptsT, udpOptsT, sctpOptsT}; + unsigned int lenOptsA = sizeof(optsA) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM optsL = MKLA(env, optsA, lenOptsA); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options_socket(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(128); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_SOCK_ACCEPTCONN => SO_ACCEPTCONN *** */ +#if defined(SO_ACCEPTCONN) + tmp = MKT2(env, esock_atom_acceptconn, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_acceptconn, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_ACCEPTFILTER => SO_ACCEPTFILTER *** */ + tmp = MKT2(env, esock_atom_acceptfilter, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_BINDTODEVICE => SO_BINDTODEVICE *** */ +#if defined(SO_BINDTODEVICE) + tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_bindtodevice, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_BROADCAST => SO_BROADCAST *** */ +#if defined(SO_BROADCAST) + tmp = MKT2(env, esock_atom_broadcast, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_broadcast, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_BUSY_POLL => SO_BUSY_POLL *** */ + tmp = MKT2(env, esock_atom_busy_poll, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_DEBUG => SO_DEBUG *** */ +#if defined(SO_DEBUG) + tmp = MKT2(env, esock_atom_debug, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_debug, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_DOMAIN => SO_DOMAIN *** */ +#if defined(SO_DOMAIN) + tmp = MKT2(env, esock_atom_domain, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_domain, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_DONTROUTE => SO_DONTROUTE *** */ +#if defined(SO_DONTROUTE) + tmp = MKT2(env, esock_atom_dontroute, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_dontroute, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_ERROR => SO_ERROR *** */ + tmp = MKT2(env, esock_atom_error, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_KEEPALIVE => SO_KEEPALIVE *** */ +#if defined(SO_KEEPALIVE) + tmp = MKT2(env, esock_atom_keepalive, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_keepalive, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_LINGER => SO_LINGER *** */ +#if defined(SO_LINGER) + tmp = MKT2(env, esock_atom_linger, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_linger, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_MARK => SO_MARK *** */ + tmp = MKT2(env, esock_atom_mark, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_OOBINLINE => SO_OOBINLINE *** */ +#if defined(SO_OOBINLINE) + tmp = MKT2(env, esock_atom_oobinline, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_oobinline, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_PASSCRED => SO_PASSCRED *** */ + tmp = MKT2(env, esock_atom_passcred, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PEEK_OFF => SO_PEEK_OFF *** */ +#if defined(SO_PEEK_OFF) + tmp = MKT2(env, esock_atom_peek_off, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_peek_off, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PEEKCRED => SO_PEEKCRED *** */ + tmp = MKT2(env, esock_atom_peekcred, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PRIORITY => SO_PRIORITY *** */ +#if defined(SO_PRIORITY) + tmp = MKT2(env, esock_atom_priority, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_priority, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_PROTOCOL => SO_PROTOCOL *** */ +#if defined(SO_PROTOCOL) + tmp = MKT2(env, esock_atom_protocol, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_protocol, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVBUF => SO_RCVBUF *** */ +#if defined(SO_RCVBUF) + tmp = MKT2(env, esock_atom_rcvbuf, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rcvbuf, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVBUFFORCE => SO_RCVBUFFORCE *** */ + tmp = MKT2(env, esock_atom_rcvbufforce, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVLOWAT => SO_RCVLOWAT *** */ +#if defined(SO_RCVLOWAT) + tmp = MKT2(env, esock_atom_rcvlowat, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rcvlowat, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RCVTIMEO => SO_RCVTIMEO *** */ +#if defined(SO_RCVTIMEO) + tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_REUSEADDR => SO_REUSEADDR *** */ +#if defined(SO_REUSEADDR) + tmp = MKT2(env, esock_atom_reuseaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_reuseaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_REUSEPORT => SO_REUSEPORT *** */ +#if defined(SO_REUSEPORT) + tmp = MKT2(env, esock_atom_reuseport, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_reuseport, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_RXQ_OVFL => SO_RXQ_OVFL *** */ + tmp = MKT2(env, esock_atom_rxq_ovfl, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SETFIB => SO_SETFIB *** */ + tmp = MKT2(env, esock_atom_setfib, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDBUF => SO_SNDBUF *** */ +#if defined(SO_SNDBUF) + tmp = MKT2(env, esock_atom_sndbuf, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sndbuf, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDBUFFORCE => SO_SNDBUFFORCE *** */ + tmp = MKT2(env, esock_atom_sndbufforce, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDLOWAT => SO_SNDLOWAT *** */ +#if defined(SO_SNDLOWAT) + tmp = MKT2(env, esock_atom_sndlowat, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sndlowat, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_SNDTIMEO => SO_SNDTIMEO *** */ +#if defined(SO_SNDTIMEO) + tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_TIMESTAMP => SO_TIMESTAMP *** */ +#if defined(SO_TIMESTAMP) + tmp = MKT2(env, esock_atom_timestamp, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_timestamp, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SOCK_TYPE => SO_TYPE *** */ +#if defined(SO_TYPE) + tmp = MKT2(env, esock_atom_type, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_type, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options_ip(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(128); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_IP_ADD_MEMBERSHIP => IP_ADD_MEMBERSHIP *** */ +#if defined(IP_ADD_MEMBERSHIP) + tmp = MKT2(env, esock_atom_add_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_add_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP => IP_ADD_SOURCE_MEMBERSHIP *** */ +#if defined(IP_ADD_SOURCE_MEMBERSHIP) + tmp = MKT2(env, esock_atom_add_source_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_add_source_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_BLOCK_SOURCE => IP_BLOCK_SOURCE *** */ +#if defined(IP_BLOCK_SOURCE) + tmp = MKT2(env, esock_atom_block_source, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_block_source, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_DONTFRAG => IP_DONTFRAG *** */ + tmp = MKT2(env, esock_atom_dontfrag, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_DROP_MEMBERSHIP => IP_DROP_MEMBERSHIP *** */ +#if defined(IP_DROP_MEMBERSHIP) + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP => IP_DROP_SOURCE_MEMBERSHIP *** */ +#if defined(IP_DROP_SOURCE_MEMBERSHIP) + tmp = MKT2(env, esock_atom_drop_source_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_drop_source_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_FREEBIND => IP_FREEBIND *** */ +#if defined(IP_FREEBIND) + tmp = MKT2(env, esock_atom_freebind, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_freebind, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_HDRINCL => IP_HDRINCL *** */ +#if defined(IP_HDRINCL) + tmp = MKT2(env, esock_atom_hdrincl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_hdrincl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MINTTL => IP_MINTTL *** */ +#if defined(IP_MINTTL) + tmp = MKT2(env, esock_atom_minttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_minttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MSFILTER => IP_MSFILTER / IP_MSFILTER_SIZE *** */ +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) + tmp = MKT2(env, esock_atom_msfilter, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_msfilter, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MTU => IP_MTU *** */ + tmp = MKT2(env, esock_atom_mtu, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MTU_DISCOVER => IP_MTU_DISCOVER *** */ +#if defined(IP_MTU_DISCOVER) + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_ALL => IP_MULTICAST_ALL *** */ +#if defined(IP_MULTICAST_ALL) + tmp = MKT2(env, esock_atom_multicast_all, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_all, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_IF => IP_MULTICAST_IF *** */ +#if defined(IP_MULTICAST_IF) + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_LOOP => IP_MULTICAST_LOOP *** */ +#if defined(IP_MULTICAST_LOOP) + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_MULTICAST_TTL => IP_MULTICAST_TTL *** */ +#if defined(IP_MULTICAST_TTL) + tmp = MKT2(env, esock_atom_multicast_ttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_ttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_NODEFRAG => IP_NODEFRAG *** */ +#if defined(IP_NODEFRAG) + tmp = MKT2(env, esock_atom_nodefrag, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_nodefrag, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_OPTIONS => IP_OPTIONS *** */ + tmp = MKT2(env, esock_atom_options, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_PKTINFO => IP_PKTINFO *** */ +#if defined(IP_PKTINFO) + tmp = MKT2(env, esock_atom_pktinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_pktinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVDSTADDR => IP_RECVDSTADDR *** */ +#if defined(IP_RECVDSTADDR) + tmp = MKT2(env, esock_atom_recvdstaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvdstaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVERR => IP_RECVERR *** */ +#if defined(IP_RECVERR) + tmp = MKT2(env, esock_atom_recverr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recverr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVIF => IP_RECVIF *** */ +#if defined(IP_RECVIF) + tmp = MKT2(env, esock_atom_recvif, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvif, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVOPTS => IP_RECVOPTS *** */ +#if defined(IP_RECVOPTS) + tmp = MKT2(env, esock_atom_recvopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVORIGDSTADDR => IP_RECVORIGDSTADDR *** */ +#if defined(IP_RECVORIGDSTADDR) + tmp = MKT2(env, esock_atom_recvorigdstaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvorigdstaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVTOS => IP_RECVTOS *** */ +#if defined(IP_RECVTOS) + tmp = MKT2(env, esock_atom_recvtos, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvtos, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RECVTTL => IP_RECVTTL *** */ +#if defined(IP_RECVTTL) + tmp = MKT2(env, esock_atom_recvttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_RETOPTS => IP_RETOPTS *** */ +#if defined(IP_RETOPTS) + tmp = MKT2(env, esock_atom_retopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_retopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_ROUTER_ALERT => IP_ROUTER_ALERT *** */ +#if defined(IP_ROUTER_ALERT) + tmp = MKT2(env, esock_atom_router_alert, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_router_alert, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_SENDSRCADDR => IP_SENDSRCADDR *** */ +#if defined(IP_SENDSRCADDR) + tmp = MKT2(env, esock_atom_sendsrcaddr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_sendsrcaddr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_TOS => IP_TOS *** */ +#if defined(IP_TOS) + tmp = MKT2(env, esock_atom_tos, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_tos, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_TRANSPARENT => IP_TRANSPARENT *** */ +#if defined(IP_TRANSPARENT) + tmp = MKT2(env, esock_atom_transparent, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_transparent, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_TTL => IP_TTL *** */ +#if defined(IP_TTL) + tmp = MKT2(env, esock_atom_ttl, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_ttl, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IP_UNBLOCK_SOURCE => IP_UNBLOCK_SOURCE *** */ +#if defined(IP_UNBLOCK_SOURCE) + tmp = MKT2(env, esock_atom_unblock_source, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_unblock_source, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options_ipv6(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(128); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_IPV6_ADDRFORM => IPV6_ADDRFORM *** */ +#if defined(IPV6_ADDRFORM) + tmp = MKT2(env, esock_atom_addrform, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_addrform, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ADD_MEMBERSHIP => IPV6_ADD_MEMBERSHIP *** */ +#if defined(IPV6_ADD_MEMBERSHIP) + tmp = MKT2(env, esock_atom_add_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_add_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_AUTHHDR => IPV6_AUTHHDR *** */ +#if defined(IPV6_AUTHHDR) + tmp = MKT2(env, esock_atom_authhdr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_authhdr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_AUTH_LEVEL => IPV6_AUTH_LEVEL *** */ + tmp = MKT2(env, esock_atom_auth_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_CHECKSUM => IPV6_CHECKSUM *** */ + tmp = MKT2(env, esock_atom_checksum, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_DROP_MEMBERSHIP => IPV6_DROP_MEMBERSHIP *** */ +#if defined(IPV6_DROP_MEMBERSHIP) + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_drop_membership, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_DSTOPTS => IPV6_DSTOPTS *** */ +#if defined(IPV6_DSTOPTS) + tmp = MKT2(env, esock_atom_dstopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_dstopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL => IPV6_ESP_NETWORK_LEVEL *** */ + tmp = MKT2(env, esock_atom_esp_network_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ESP_TRANS_LEVEL => IPV6_ESP_TRANS_LEVEL *** */ + tmp = MKT2(env, esock_atom_esp_trans_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_FAITH => IPV6_FAITH *** */ + tmp = MKT2(env, esock_atom_faith, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_FLOWINFO => IPV6_FLOWINFO *** */ +#if defined(IPV6_FLOWINFO) + tmp = MKT2(env, esock_atom_flowinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_flowinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_HOPLIMIT => IPV6_HOPLIMIT *** */ +#if defined(IPV6_HOPLIMIT) + tmp = MKT2(env, esock_atom_hoplimit, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_hoplimit, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_HOPOPTS => IPV6_HOPOPTS *** */ +#if defined(IPV6_HOPOPTS) + tmp = MKT2(env, esock_atom_hopopts, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_hopopts, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_IPCOMP_LEVEL => IPV6_IPCOMP_LEVEL *** */ + tmp = MKT2(env, esock_atom_ipcomp_level, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_JOIN_GROUP => IPV6_JOIN_GROUP *** */ + tmp = MKT2(env, esock_atom_join_group, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_LEAVE_GROUP => IPV6_LEAVE_GROUP *** */ + tmp = MKT2(env, esock_atom_leave_group, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MTU => IPV6_MTU *** */ +#if defined(IPV6_MTU) + tmp = MKT2(env, esock_atom_mtu, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_mtu, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MTU_DISCOVER => IPV6_MTU_DISCOVER *** */ +#if defined(IPV6_MTU_DISCOVER) + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_mtu_discover, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MULTICAST_HOPS => IPV6_MULTICAST_HOPS *** */ +#if defined(IPV6_MULTICAST_HOPS) + tmp = MKT2(env, esock_atom_multicast_hops, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_hops, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MULTICAST_IF => IPV6_MULTICAST_IF *** */ +#if defined(IPV6_MULTICAST_IF) + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_if, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_MULTICAST_LOOP => IPV6_MULTICAST_LOOP *** */ +#if defined(IPV6_MULTICAST_LOOP) + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_multicast_loop, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_PORTRANGE => IPV6_PORTRANGE *** */ + tmp = MKT2(env, esock_atom_portrange, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_PKTOPTIONS => IPV6_PKTOPTIONS *** */ + tmp = MKT2(env, esock_atom_pktoptions, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RECVERR => IPV6_RECVERR *** */ +#if defined(IPV6_RECVERR) + tmp = MKT2(env, esock_atom_recverr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recverr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RECVPKTINFO => IPV6_RECVPKTINFO *** */ +#if defined(IPV6_RECVPKTINFO) + tmp = MKT2(env, esock_atom_recvpktinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_recvpktinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RECVTCLASS => IPV6_RECVTCLASS *** */ + tmp = MKT2(env, esock_atom_recvtclass, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_ROUTER_ALERT => IPV6_ROUTER_ALERT *** */ +#if defined(IPV6_ROUTER_ALERT) + tmp = MKT2(env, esock_atom_router_alert, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_router_alert, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_RTHDR => IPV6_RTHDR *** */ +#if defined(IPV6_RTHDR) + tmp = MKT2(env, esock_atom_rthdr, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rthdr, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_TCLASS => IPV6_TCLASS *** */ + tmp = MKT2(env, esock_atom_tclass, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_UNICAST_HOPS => IPV6_UNICAST_HOPS *** */ +#if defined(IPV6_UNICAST_HOPS) + tmp = MKT2(env, esock_atom_unicast_hops, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_unicast_hops, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_USE_MIN_MTU => IPV6_USE_MIN_MTU *** */ + tmp = MKT2(env, esock_atom_use_min_mtu, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_IPV6_V6ONLY => IPV6_V6ONLY *** */ +#if defined(IPV6_V6ONLY) + tmp = MKT2(env, esock_atom_v6only, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_v6only, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options_tcp(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(32); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_TCP_CONGESTION => TCP_CONGESTION *** */ +#if defined(TCP_CONGESTION) + tmp = MKT2(env, esock_atom_congestion, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_congestion, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_CORK => TCP_CORK *** */ +#if defined(TCP_CORK) + tmp = MKT2(env, esock_atom_cork, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_cork, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_INFO => TCP_INFO *** */ + tmp = MKT2(env, esock_atom_info, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_KEEPCNT => TCP_KEEPCNT *** */ + tmp = MKT2(env, esock_atom_keepcnt, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_KEEPIDLE => TCP_KEEPIDLE *** */ + tmp = MKT2(env, esock_atom_keepidle, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_KEEPINTVL => TCP_KEEPINTVL *** */ + tmp = MKT2(env, esock_atom_keepintvl, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_MAXSEG => TCP_MAXSEG *** */ +#if defined(TCP_) + tmp = MKT2(env, esock_atom_maxseg, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_maxseg, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_MD5SIG => TCP_MD5SIG *** */ + tmp = MKT2(env, esock_atom_md5sig, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_NODELAY => TCP_NODELAY *** */ +#if defined(TCP_) + tmp = MKT2(env, esock_atom_nodelay, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_nodelay, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_NOOPT => TCP_NOOPT *** */ + tmp = MKT2(env, esock_atom_noopt, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_NOPUSH => TCP_NOPUSH *** */ + tmp = MKT2(env, esock_atom_nopush, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_SYNCNT => TCP_SYNCNT *** */ + tmp = MKT2(env, esock_atom_syncnt, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_TCP_USER_TIMEOUT => TCP_USER_TIMEOUT *** */ + tmp = MKT2(env, esock_atom_user_timeout, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options_udp(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(8); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_UDP_CORK => UDP_CORK *** */ +#if defined(UDP_CORK) + tmp = MKT2(env, esock_atom_cork, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_cork, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_options_sctp(ErlNifEnv* env) +{ + SocketTArray opts = TARRAY_CREATE(64); + ERL_NIF_TERM tmp, optsL; + + + /* *** SOCKET_OPT_SCTP_ADAPTION_LAYER => SCTP_ADAPTION_LAYER *** */ + tmp = MKT2(env, esock_atom_adaption_layer, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_ASSOCINFO => SCTP_ASSOCINFO *** */ +#if defined(SCTP_ASSOCINFO) + tmp = MKT2(env, esock_atom_associnfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_associnfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY => SCTP_AUTH_ACTIVE_KEY *** */ + tmp = MKT2(env, esock_atom_auth_active_key, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_ASCONF => SCTP_AUTH_ASCONF *** */ + tmp = MKT2(env, esock_atom_auth_asconf, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_CHUNK => SCTP_AUTH_CHUNK *** */ + tmp = MKT2(env, esock_atom_auth_chunk, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_DELETE_KEY => SCTP_AUTH_DELETE_KEY *** */ + tmp = MKT2(env, esock_atom_auth_delete_key, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTH_KEY => SCTP_AUTH_KEY *** */ + tmp = MKT2(env, esock_atom_auth_key, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_AUTOCLOSE => SCTP_AUTOCLOSE *** */ +#if defined(SCTP_AUTOCLOSE) + tmp = MKT2(env, esock_atom_autoclose, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_autoclose, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_CONTEXT => SCTP_CONTEXT *** */ + tmp = MKT2(env, esock_atom_context, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS => SCTP_DEFAULT_SEND_PARAMS *** */ + tmp = MKT2(env, esock_atom_default_send_params, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_DELAYED_ACK_TIME => SCTP_DELAYED_ACK_TIME *** */ + tmp = MKT2(env, esock_atom_delayed_ack_time, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_DISABLE_FRAGMENTS => SCTP_DISABLE_FRAGMENTS *** */ +#if defined(SCTP_DISABLE_FRAGMENTS) + tmp = MKT2(env, esock_atom_disable_fragments, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_disable_fragments, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_HMAC_IDENT => SCTP_HMAC_IDENT *** */ + tmp = MKT2(env, esock_atom_hmac_ident, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_EVENTS => SCTP_EVENTS *** */ +#if defined(SCTP_EVENTS) + tmp = MKT2(env, esock_atom_events, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_events, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_EXPLICIT_EOR => SCTP_EXPLICIT_EOR *** */ + tmp = MKT2(env, esock_atom_explicit_eor, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE => SCTP_FRAGMENT_INTERLEAVE *** */ + tmp = MKT2(env, esock_atom_fragment_interleave, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO => SCTP_GET_PEER_ADDR_INFO *** */ + tmp = MKT2(env, esock_atom_get_peer_addr_info, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_INITMSG => SCTP_INITMSG *** */ +#if defined(SCTP_INITMSG) + tmp = MKT2(env, esock_atom_initmsg, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_initmsg, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR => SCTP_I_WANT_MAPPED_V4_ADDR *** */ + tmp = MKT2(env, esock_atom_i_want_mapped_v4_addr, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS => SCTP_LOCAL_AUTH_CHUNKS *** */ + tmp = MKT2(env, esock_atom_local_auth_chunks, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_MAXSEG => SCTP_MAXSEG *** */ +#if defined(SCTP_MAXSEG) + tmp = MKT2(env, esock_atom_maxseg, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_maxseg, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_MAXBURST => SCTP_MAXBURST *** */ + tmp = MKT2(env, esock_atom_maxburst, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_NODELAY => SCTP_NODELAY *** */ +#if defined(SCTP_NODELAY) + tmp = MKT2(env, esock_atom_nodelay, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_nodelay, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT => SCTP_PARTIAL_DELIVERY_POINT *** */ + tmp = MKT2(env, esock_atom_partial_delivery_point, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PEER_ADDR_PARAMS => SCTP_PEER_ADDR_PARAMS *** */ + tmp = MKT2(env, esock_atom_peer_addr_params, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS => SCTP_PEER_AUTH_CHUNKS *** */ + tmp = MKT2(env, esock_atom_peer_auth_chunks, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_PRIMARY_ADDR => SCTP_PRIMARY_ADDR *** */ + tmp = MKT2(env, esock_atom_primary_addr, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_RESET_STREAMS => SCTP_RESET_STREAMS *** */ + tmp = MKT2(env, esock_atom_reset_streams, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_RTOINFO => SCTP_RTOINFO *** */ +#if defined(SCTP_RTOINFO) + tmp = MKT2(env, esock_atom_rtoinfo, esock_atom_true); +#else + tmp = MKT2(env, esock_atom_rtoinfo, esock_atom_false); +#endif + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR => SCTP_SET_PEER_PRIMARY_ADDR *** */ + tmp = MKT2(env, esock_atom_set_peer_primary_addr, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_STATUS => SCTP_STATUS *** */ + tmp = MKT2(env, esock_atom_status, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + /* *** SOCKET_OPT_SCTP_USE_EXT_RECVINFO => SCTP_USE_EXT_RECVINFO *** */ + tmp = MKT2(env, esock_atom_use_ext_recvinfo, esock_atom_false); + TARRAY_ADD(opts, tmp); + + + TARRAY_TOLIST(opts, env, &optsL); + + return optsL; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_sctp(ErlNifEnv* env) +{ + ERL_NIF_TERM supports; + +#if defined(HAVE_SCTP) + supports = esock_atom_true; +#else + supports = esock_atom_false; +#endif + + return supports; +} +#endif + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsupports_ipv6(ErlNifEnv* env) +{ + ERL_NIF_TERM supports; + + /* Is this (test) really sufficient for testing if we support IPv6? */ +#if defined(HAVE_IPV6) + supports = esock_atom_true; +#else + supports = esock_atom_false; +#endif + + return supports; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_open + * + * Description: + * Create an endpoint for communication. + * + * Arguments: + * Domain - The domain, for example 'inet' + * Type - Type of socket, for example 'stream' + * Protocol - The protocol, for example 'tcp' + * Extra - A map with "obscure" options. + * Currently the only allowed option is netns (network namespace). + * This is *only* allowed on linux! + */ +static +ERL_NIF_TERM nif_open(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + int edomain, etype, eproto; + int domain, type, proto; + char* netns; + ERL_NIF_TERM emap; + ERL_NIF_TERM result; + + SGDBG( ("SOCKET", "nif_open -> entry with %d args\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !GET_INT(env, argv[0], &edomain) || + !GET_INT(env, argv[1], &etype) || + !IS_MAP(env, argv[3])) { + return enif_make_badarg(env); + } + eproto = argv[2]; + emap = argv[3]; + + SGDBG( ("SOCKET", "nif_open -> " + "\r\n edomain: %T" + "\r\n etype: %T" + "\r\n eproto: %T" + "\r\n extra: %T" + "\r\n", argv[0], argv[1], eproto, emap) ); + + if (!edomain2domain(edomain, &domain)) { + SGDBG( ("SOCKET", "nif_open -> domain: %d\r\n", domain) ); + return esock_make_error(env, esock_atom_einval); + } + + if (!etype2type(etype, &type)) { + SGDBG( ("SOCKET", "nif_open -> type: %d\r\n", type) ); + return esock_make_error(env, esock_atom_einval); + } + + if (!eproto2proto(env, eproto, &proto)) { + SGDBG( ("SOCKET", "nif_open -> protocol: %d\r\n", proto) ); + return esock_make_error(env, esock_atom_einval); + } + +#ifdef HAVE_SETNS + /* We *currently* only support one extra option: netns */ + if (!emap2netns(env, emap, &netns)) { + SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) ); + return enif_make_badarg(env); + } +#else + netns = NULL; +#endif + + result = nopen(env, domain, type, proto, netns); + + SGDBG( ("SOCKET", "nif_open -> done with result: " + "\r\n %T" + "\r\n", result) ); + + return result; +#endif // if defined(__WIN32__) +} + + +/* nopen - create an endpoint for communication + * + * Assumes the input has been validated. + * + * Normally we want debugging on (individual) sockets to be controlled + * by the sockets own debug flag. But since we don't even have a socket + * yet, we must use the global debug flag. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nopen(ErlNifEnv* env, + int domain, int type, int protocol, + char* netns) +{ + SocketDescriptor* descP; + ERL_NIF_TERM res; + int save_errno = 0; + SOCKET sock; + HANDLE event; +#ifdef HAVE_SETNS + int current_ns = 0; +#endif + + SGDBG( ("SOCKET", "nopen -> entry with" + "\r\n domain: %d" + "\r\n type: %d" + "\r\n protocol: %d" + "\r\n netns: %s" + "\r\n", domain, type, protocol, ((netns == NULL) ? "NULL" : netns)) ); + +#ifdef HAVE_SETNS + if ((netns != NULL) && + !change_network_namespace(netns, ¤t_ns, &save_errno)) + return esock_make_error_errno(env, save_errno); +#endif + + if ((sock = sock_open(domain, type, protocol)) == INVALID_SOCKET) + return esock_make_error_errno(env, sock_errno()); + + SGDBG( ("SOCKET", "nopen -> open success: %d\r\n", sock) ); + +#ifdef HAVE_SETNS + if ((netns != NULL) && + !restore_network_namespace(current_ns, sock, &save_errno)) + return esock_make_error_errno(env, save_errno); + + if (netns != NULL) + FREE(netns); +#endif + + + if ((event = sock_create_event(sock)) == INVALID_EVENT) { + save_errno = sock_errno(); + while ((sock_close(sock) == INVALID_SOCKET) && (sock_errno() == EINTR)); + return esock_make_error_errno(env, save_errno); + } + + SGDBG( ("SOCKET", "nopen -> event success: %d\r\n", event) ); + + SET_NONBLOCKING(sock); + + + /* Create and initiate the socket "descriptor" */ + if ((descP = alloc_descriptor(sock, event)) == NULL) { + sock_close(sock); + // Not sure if this is really the proper error, but... + return enif_make_badarg(env); + } + + descP->state = SOCKET_STATE_OPEN; + descP->domain = domain; + descP->type = type; + descP->protocol = protocol; + + /* Does this apply to other types? Such as RAW? */ + if (type == SOCK_DGRAM) { + descP->isReadable = TRUE; + descP->isWritable = TRUE; + } + + /* + * Should we keep track of sockets (resources) in some way? + * Doing it here will require mutex to ensure data integrity, + * which will be costly. Send it somewhere? + */ + res = enif_make_resource(env, descP); + enif_release_resource(descP); + + /* Keep track of the creator + * This should not be a problem but just in case + * the *open* function is used with the wrong kind + * of environment... + */ + if (enif_self(env, &descP->ctrlPid) == NULL) + return esock_make_error(env, atom_exself); + + if (MONP("nopen -> ctrl", + env, descP, + &descP->ctrlPid, + &descP->ctrlMon) != 0) + return esock_make_error(env, atom_exmon); + + + inc_socket(domain, type, protocol); + + return esock_make_ok2(env, res); +} +#endif // if !defined(__WIN32__) + + + +#ifdef HAVE_SETNS +/* We should really have another API, so that we can return errno... */ + +/* *** change network namespace *** + * Retreive the current namespace and set the new. + * Return result and previous namespace if successfull. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err) +{ + int save_errno; + int current_ns = 0; + int new_ns = 0; + + SGDBG( ("SOCKET", "change_network_namespace -> entry with" + "\r\n new ns: %s", netns) ); + + if (netns != NULL) { + current_ns = open("/proc/self/ns/net", O_RDONLY); + if (current_ns == INVALID_SOCKET) { + *cns = current_ns; + *err = sock_errno(); + return FALSE; + } + new_ns = open(netns, O_RDONLY); + if (new_ns == INVALID_SOCKET) { + save_errno = sock_errno(); + while (close(current_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + *cns = -1; + *err = save_errno; + return FALSE; + } + if (setns(new_ns, CLONE_NEWNET) != 0) { + save_errno = sock_errno(); + while ((close(new_ns) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + while ((close(current_ns) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + *cns = -1; + *err = save_errno; + return FALSE; + } else { + while ((close(new_ns) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + *cns = current_ns; + *err = 0; + return TRUE; + } + } else { + *cns = INVALID_SOCKET; + *err = 0; + return TRUE; + } +} +#endif // if !defined(__WIN32__) + + +/* *** restore network namespace *** + * Restore the previous namespace (see above). + */ +#if !defined(__WIN32__) +static +BOOLEAN_T restore_network_namespace(int ns, SOCKET sock, int* err) +{ + int save_errno; + + SGDBG( ("SOCKET", "restore_network_namespace -> entry with" + "\r\n ns: %d", ns) ); + + if (ns != INVALID_SOCKET) { + if (setns(ns, CLONE_NEWNET) != 0) { + /* XXX Failed to restore network namespace. + * What to do? Tidy up and return an error... + * Note that the thread now might still be in the namespace. + * Can this even happen? Should the emulator be aborted? + */ + if (sock != INVALID_SOCKET) + save_errno = sock_errno(); + while (close(sock) == INVALID_SOCKET && + sock_errno() == EINTR); + sock = INVALID_SOCKET; + while (close(ns) == INVALID_SOCKET && + sock_errno() == EINTR); + *err = save_errno; + return FALSE; + } else { + while (close(ns) == INVALID_SOCKET && + sock_errno() == EINTR); + *err = 0; + return TRUE; + } + } + + *err = 0; + return TRUE; +} +#endif // if !defined(__WIN32__) +#endif // ifdef HAVE_SETNS + + + +/* ---------------------------------------------------------------------- + * nif_bind + * + * Description: + * Bind a name to a socket. + * + * Arguments: + * [0] Socket (ref) - Points to the socket descriptor. + * [1] LocalAddr - Local address is a sockaddr map ( socket:sockaddr() ). + */ +static +ERL_NIF_TERM nif_bind(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM eSockAddr; + SocketAddress sockAddr; + unsigned int addrLen; + char* xres; + + SGDBG( ("SOCKET", "nif_bind -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + eSockAddr = argv[1]; + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_bind -> args when sock = %d (0x%lX)" + "\r\n Socket: %T" + "\r\n SockAddr: %T" + "\r\n", descP->sock, descP->state, argv[0], eSockAddr) ); + + /* Make sure we are ready + * Not sure how this would even happen, but... + */ + if (descP->state != SOCKET_STATE_OPEN) + return esock_make_error(env, atom_exbadstate); + + if ((xres = esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) != NULL) + return esock_make_error_str(env, xres); + + return nbind(env, descP, &sockAddr, addrLen); +#endif // if defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nbind(ErlNifEnv* env, + SocketDescriptor* descP, + SocketAddress* sockAddrP, + unsigned int addrLen) +{ + int port, ntohs_port; + + SSDBG( descP, ("SOCKET", "nbind -> try bind\r\n") ); + + if (IS_SOCKET_ERROR(sock_bind(descP->sock, + (struct sockaddr*) sockAddrP, addrLen))) { + return esock_make_error_errno(env, sock_errno()); + } + + SSDBG( descP, ("SOCKET", "nbind -> bound - get port\r\n") ); + + port = which_address_port(sockAddrP); + SSDBG( descP, ("SOCKET", "nbind -> port: %d\r\n", port) ); + if (port == 0) { + SOCKLEN_T len = sizeof(SocketAddress); + sys_memzero((char *) sockAddrP, len); + sock_name(descP->sock, &sockAddrP->sa, &len); + port = which_address_port(sockAddrP); + } else if (port == -1) { + port = 0; + } + + ntohs_port = sock_ntohs(port); + + SSDBG( descP, ("SOCKET", "nbind -> done with port = %d\r\n", ntohs_port) ); + + return esock_make_ok2(env, MKI(env, ntohs_port)); + +} +#endif // if !defined(__WIN32__) + + + + +/* ---------------------------------------------------------------------- + * nif_connect + * + * Description: + * Initiate a connection on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SockAddr - Socket Address of "remote" host. + * This is sockaddr(), which is either + * sockaddr_in4 or sockaddr_in6. + */ +static +ERL_NIF_TERM nif_connect(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM eSockAddr; + char* xres; + + SGDBG( ("SOCKET", "nif_connect -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + eSockAddr = argv[1]; + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_connect -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n SockAddr: %T" + "\r\n", descP->sock, argv[0], eSockAddr) ); + + if ((xres = esock_decode_sockaddr(env, eSockAddr, + &descP->remote, &descP->addrLen)) != NULL) { + return esock_make_error_str(env, xres); + } + + return nconnect(env, descP); +#endif // if !defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nconnect(ErlNifEnv* env, + SocketDescriptor* descP) +{ + int code, save_errno = 0; + + /* + * <KOLLA> + * + * We should look both the read and write mutex:es... + * + * </KOLLA> + * + * Verify that we are where in the proper state */ + + if (!IS_OPEN(descP)) { + SSDBG( descP, ("SOCKET", "nif_connect -> not open\r\n") ); + return esock_make_error(env, atom_exbadstate); + } + + if (IS_CONNECTED(descP)) { + SSDBG( descP, ("SOCKET", "nif_connect -> already connected\r\n") ); + return esock_make_error(env, atom_eisconn); + } + + if (IS_CONNECTING(descP)) { + SSDBG( descP, ("SOCKET", "nif_connect -> already connecting\r\n") ); + return esock_make_error(env, esock_atom_einval); + } + + code = sock_connect(descP->sock, + (struct sockaddr*) &descP->remote, + descP->addrLen); + save_errno = sock_errno(); + + SSDBG( descP, ("SOCKET", "nif_connect -> connect result: %d, %d\r\n", + code, save_errno) ); + + if (IS_SOCKET_ERROR(code) && + ((save_errno == ERRNO_BLOCK) || /* Winsock2 */ + (save_errno == EINPROGRESS))) { /* Unix & OSE!! */ + ERL_NIF_TERM ref = MKREF(env); + descP->state = SOCKET_STATE_CONNECTING; + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, NULL, ref); + return esock_make_ok2(env, ref); + } else if (code == 0) { /* ok we are connected */ + descP->state = SOCKET_STATE_CONNECTED; + descP->isReadable = TRUE; + descP->isWritable = TRUE; + /* Do we need to do somthing for "active" mode? + * Is there even such a thing *here*? + */ + return esock_atom_ok; + } else { + return esock_make_error_errno(env, save_errno); + } + +} +#endif // if !defined(__WIN32__) + + +/* ---------------------------------------------------------------------- + * nif_finalize_connection + * + * Description: + * Make socket ready for input and output. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ +static +ERL_NIF_TERM nif_finalize_connection(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + return nfinalize_connection(env, descP); + +#endif +} + + +/* *** nfinalize_connection *** + * Perform the final check to verify a connection. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nfinalize_connection(ErlNifEnv* env, + SocketDescriptor* descP) +{ + int error; + + if (descP->state != SOCKET_STATE_CONNECTING) + return esock_make_error(env, atom_enotconn); + + if (!verify_is_connected(descP, &error)) { + descP->state = SOCKET_STATE_OPEN; /* restore state */ + return esock_make_error_errno(env, error); + } + + descP->state = SOCKET_STATE_CONNECTED; + descP->isReadable = TRUE; + descP->isWritable = TRUE; + + return esock_atom_ok; +} +#endif + + +/* *** verify_is_connected *** + * Check if a connection has been established. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T verify_is_connected(SocketDescriptor* descP, int* err) +{ + /* + * *** This is strange *** + * + * This *should* work on Windows NT too, but doesn't. + * An bug in Winsock 2.0 for Windows NT? + * + * See "Unix Netwok Programming", W.R.Stevens, p 412 for a + * discussion about Unix portability and non blocking connect. + */ + +#ifndef SO_ERROR + + int sz, code; + + sz = sizeof(descP->remote); + sys_memzero((char *) &descP->remote, sz); + code = sock_peer(desc->sock, + (struct sockaddr*) &descP->remote, &sz); + + if (IS_SOCKET_ERROR(code)) { + *err = sock_errno(); + return FALSE; + } + +#else + + int error = 0; /* Has to be initiated, we check it */ + unsigned int sz = sizeof(error); /* even if we get -1 */ + int code = sock_getopt(descP->sock, + SOL_SOCKET, SO_ERROR, + (void *)&error, &sz); + + if ((code < 0) || error) { + *err = error; + return FALSE; + } + +#endif /* SO_ERROR */ + + *err = 0; + + return TRUE; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_listen + * + * Description: + * Listen for connections on a socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Backlog - The maximum length to which the queue of pending + * connections for socket may grow. + */ +static +ERL_NIF_TERM nif_listen(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + int backlog; + + SGDBG( ("SOCKET", "nif_listen -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_INT(env, argv[1], &backlog)) { + return enif_make_badarg(env); + } + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_listen -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n backlog: %d" + "\r\n", descP->sock, argv[0], backlog) ); + + return nlisten(env, descP, backlog); +#endif // if defined(__WIN32__) +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nlisten(ErlNifEnv* env, + SocketDescriptor* descP, + int backlog) +{ + if (descP->state == SOCKET_STATE_CLOSED) + return esock_make_error(env, atom_exbadstate); + + if (!IS_OPEN(descP)) + return esock_make_error(env, atom_exbadstate); + + if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog))) + return esock_make_error_errno(env, sock_errno()); + + descP->state = SOCKET_STATE_LISTENING; + + return esock_atom_ok; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_accept + * + * Description: + * Accept a connection on a socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Request ref - Unique "id" of this request + * (used for the select, if none is in queue). + */ +static +ERL_NIF_TERM nif_accept(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM ref; + + SGDBG( ("SOCKET", "nif_accept -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + ref = argv[1]; + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_accept -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n ReqRef: %T" + "\r\n", descP->sock, argv[0], ref) ); + + return naccept(env, descP, ref); +#endif // if defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM naccept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref) +{ + ERL_NIF_TERM res; + + switch (descP->state) { + case SOCKET_STATE_LISTENING: + MLOCK(descP->accMtx); + res = naccept_listening(env, descP, ref); + MUNLOCK(descP->accMtx); + break; + + case SOCKET_STATE_ACCEPTING: + MLOCK(descP->accMtx); + res = naccept_accepting(env, descP, ref); + MUNLOCK(descP->accMtx); + break; + + default: + res = esock_make_error(env, esock_atom_einval); + break; + } + + return res; +} +#endif // if !defined(__WIN32__) + + +/* *** naccept_listening *** + * We have no active acceptor and no acceptors in queue. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM naccept_listening(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref) +{ + SocketAddress remote; + unsigned int n; + SOCKET accSock; + HANDLE accEvent; + int save_errno; + ErlNifPid caller; + + SSDBG( descP, ("SOCKET", "naccept_listening -> get caller\r\n") ); + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + n = sizeof(remote); + sys_memzero((char *) &remote, n); + SSDBG( descP, ("SOCKET", "naccept_listening -> try accept\r\n") ); + accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); + if (accSock == INVALID_SOCKET) { + + save_errno = sock_errno(); + + SSDBG( descP, + ("SOCKET", + "naccept_listening -> accept failed (%d)\r\n", save_errno) ); + + if (save_errno == ERRNO_BLOCK) { + + /* *** Try again later *** */ + SSDBG( descP, ("SOCKET", "naccept_listening -> would block\r\n") ); + + descP->currentAcceptor.pid = caller; + if (MONP("naccept_listening -> current acceptor", + env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon) != 0) + return esock_make_error(env, atom_exmon); + + descP->currentAcceptor.ref = enif_make_copy(descP->env, ref); + descP->currentAcceptorP = &descP->currentAcceptor; + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, ref); + + /* Shall we really change state? + * The ready event is sent directly to the calling + * process, which simply calls this function again. + * Basically, state accepting means that we have + * an "outstanding" accept. + * Shall we store the pid of the calling process? + * And if someone else calls accept, return with ebusy? + * Can any process call accept or just the controlling + * process? + * We also need a monitor it case the calling process is + * called before we are done! + * + * Change state (to accepting) and store pid of the acceptor + * (current process). Only accept calls from the acceptor + * process (ebusy) and once we have a successful accept, + * change state back to listening. If cancel is called instead + * (only accepted from the acceptor process), we reset + * state to listening and also resets the pid to "null" + * (is there such a value?). + * Need a mutex to secure that we don't test and change the + * pid at the same time. + */ + + descP->state = SOCKET_STATE_ACCEPTING; + + return esock_make_error(env, esock_atom_eagain); + + } else { + SSDBG( descP, + ("SOCKET", + "naccept_listening -> errno: %d\r\n", save_errno) ); + return esock_make_error_errno(env, save_errno); + } + + } else { + SocketDescriptor* accDescP; + ERL_NIF_TERM accRef; + + /* + * We got one + */ + + SSDBG( descP, ("SOCKET", "naccept_listening -> accept success\r\n") ); + + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { + save_errno = sock_errno(); + while ((sock_close(accSock) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + return esock_make_error_errno(env, save_errno); + } + + if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) { + sock_close(accSock); + return enif_make_badarg(env); + } + + accDescP->domain = descP->domain; + accDescP->type = descP->type; + accDescP->protocol = descP->protocol; + accDescP->rBufSz = descP->rBufSz; // Inherit buffer size + accDescP->rNum = descP->rNum; // Inherit buffer uses + accDescP->rNumCnt = 0; + accDescP->rCtrlSz = descP->rCtrlSz; // Inherit buffer siez + accDescP->wCtrlSz = descP->wCtrlSz; // Inherit buffer size + + accRef = enif_make_resource(env, accDescP); + enif_release_resource(accDescP); + + accDescP->ctrlPid = caller; + if (MONP("naccept_listening -> ctrl", + env, accDescP, + &accDescP->ctrlPid, + &accDescP->ctrlMon) != 0) { + sock_close(accSock); + return esock_make_error(env, atom_exmon); + } + + accDescP->remote = remote; + SET_NONBLOCKING(accDescP->sock); + + accDescP->state = SOCKET_STATE_CONNECTED; + accDescP->isReadable = TRUE; + accDescP->isWritable = TRUE; + + return esock_make_ok2(env, accRef); + } +} +#endif // if !defined(__WIN32__) + + +/* *** naccept_accepting *** + * We have an active acceptor and possibly acceptors waiting in queue. + * If the pid of the calling process is not the pid of the "current process", + * push the requester onto the queue. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM naccept_accepting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref) +{ + SocketAddress remote; + unsigned int n; + SOCKET accSock; + HANDLE accEvent; + ErlNifPid caller; + int save_errno; + ERL_NIF_TERM result; + + SSDBG( descP, ("SOCKET", "naccept_accepting -> get caller\r\n") ); + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + SSDBG( descP, ("SOCKET", "naccept_accepting -> check: " + "are caller current acceptor:" + "\r\n Caller: %T" + "\r\n Current: %T" + "\r\n", caller, descP->currentAcceptor.pid) ); + + if (!compare_pids(env, &descP->currentAcceptor.pid, &caller)) { + + /* Not the "current acceptor", so (maybe) push onto queue */ + + SSDBG( descP, ("SOCKET", "naccept_accepting -> not (active) acceptor\r\n") ); + + if (!acceptor_search4pid(env, descP, &caller)) + result = acceptor_push(env, descP, caller, ref); + else + result = esock_make_error(env, esock_atom_eagain); + + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> queue (push) result: %T\r\n", result) ); + + return result; + } + + n = sizeof(descP->remote); + sys_memzero((char *) &remote, n); + SSDBG( descP, ("SOCKET", "naccept_accepting -> try accept\r\n") ); + accSock = sock_accept(descP->sock, (struct sockaddr*) &remote, &n); + if (accSock == INVALID_SOCKET) { + + save_errno = sock_errno(); + + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> accept failed (%d)\r\n", save_errno) ); + + if (save_errno == ERRNO_BLOCK) { + + /* + * Just try again, no real error, just a ghost trigger from poll, + */ + + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> would block: try again\r\n") ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, NULL, ref); + + return esock_make_error(env, esock_atom_eagain); + } else { + SSDBG( descP, + ("SOCKET", + "naccept_accepting -> errno: %d\r\n", save_errno) ); + return esock_make_error_errno(env, save_errno); + } + } else { + SocketDescriptor* accDescP; + ERL_NIF_TERM accRef; + + /* + * We got one + */ + + SSDBG( descP, ("SOCKET", "naccept_accepting -> accept success\r\n") ); + + DEMONP("naccept_accepting -> current acceptor", + env, descP, &descP->currentAcceptor.mon); + + if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) { + save_errno = sock_errno(); + while ((sock_close(accSock) == INVALID_SOCKET) && + (sock_errno() == EINTR)); + return esock_make_error_errno(env, save_errno); + } + + if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) { + sock_close(accSock); + return enif_make_badarg(env); + } + + accDescP->domain = descP->domain; + accDescP->type = descP->type; + accDescP->protocol = descP->protocol; + + accRef = enif_make_resource(env, accDescP); + enif_release_resource(accDescP); // We should really store a reference ... + + accDescP->ctrlPid = caller; + if (MONP("naccept_accepting -> ctrl", + env, accDescP, + &accDescP->ctrlPid, + &accDescP->ctrlMon) != 0) { + sock_close(accSock); + return esock_make_error(env, atom_exmon); + } + + accDescP->remote = remote; + SET_NONBLOCKING(accDescP->sock); + + accDescP->state = SOCKET_STATE_CONNECTED; + accDescP->isReadable = TRUE; + accDescP->isWritable = TRUE; + + /* Check if there are waiting acceptors (popping the acceptor queue) */ + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "naccept_accepting -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, &descP->currentAcceptor.pid, descP->currentAcceptor.ref); + } else { + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + + return esock_make_ok2(env, accRef); + } +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_send + * + * Description: + * Send a message on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * Data - The data to send in the form of a IOVec. + * Flags - Send flags. + */ + +static +ERL_NIF_TERM nif_send(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM sockRef, sendRef; + ErlNifBinary sndData; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_send -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !GET_BIN(env, argv[2], &sndData) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + sockRef = argv[0]; // We need this in case we send in case we send abort + sendRef = argv[1]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_send -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n SendRef: %T" + "\r\n Size of data: %d" + "\r\n eFlags: %d" + "\r\n", descP->sock, sockRef, sendRef, sndData.size, eflags) ); + + if (!esendflags2sendflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->writeMtx); + + /* We need to handle the case when another process tries + * to write at the same time. + * If the current write could not write its entire package + * this time (resulting in an select). The write of the + * other process must be made to wait until current + * is done! + * Basically, we need a write queue! + * + * A 'writing' field (boolean), which is set if we did + * not manage to write the entire message and reset every + * time we do. + */ + + res = nsend(env, descP, sockRef, sendRef, &sndData, flags); + + MUNLOCK(descP->writeMtx); + + return res; +#endif // if defined(__WIN32__) +} + + + +/* *** nsend *** + * + * Do the actual send. + * Do some initial writer checks, do the actual send and then + * analyze the result. If we are done, another writer may be + * scheduled (if there is one in the writer queue). + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsend(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef, + ErlNifBinary* sndDataP, + int flags) +{ + int save_errno; + ssize_t written; + ERL_NIF_TERM writerCheck; + + if (!descP->isWritable) + return enif_make_badarg(env); + + /* Check if there is already a current writer and if its us */ + if (!send_check_writer(env, descP, sendRef, &writerCheck)) + return writerCheck; + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->writeTries, 1); + + written = sock_send(descP->sock, sndDataP->data, sndDataP->size, flags); + if (IS_SOCKET_ERROR(written)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + return send_check_result(env, descP, + written, sndDataP->size, save_errno, + sockRef, sendRef); + +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_sendto + * + * Description: + * Send a message on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * Data - The data to send in the form of a IOVec. + * Dest - Destination (socket) address. + * Flags - Send flags. + */ + +static +ERL_NIF_TERM nif_sendto(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM sockRef, sendRef; + ErlNifBinary sndData; + unsigned int eflags; + int flags; + ERL_NIF_TERM eSockAddr; + SocketAddress remoteAddr; + unsigned int remoteAddrLen; + char* xres; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_sendto -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 5) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_BIN(env, argv[2], &sndData) || + !GET_UINT(env, argv[4], &eflags)) { + return enif_make_badarg(env); + } + sockRef = argv[0]; // We need this in case we send in case we send abort + sendRef = argv[1]; + eSockAddr = argv[3]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_sendto -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n sendRef: %T" + "\r\n size of data: %d" + "\r\n eSockAddr: %T" + "\r\n eflags: %d" + "\r\n", + descP->sock, sockRef, sendRef, sndData.size, eSockAddr, eflags) ); + + if (!esendflags2sendflags(eflags, &flags)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> sendflags decode failed\r\n") ); + return esock_make_error(env, esock_atom_einval); + } + + if ((xres = esock_decode_sockaddr(env, eSockAddr, + &remoteAddr, + &remoteAddrLen)) != NULL) { + SSDBG( descP, ("SOCKET", "nif_sendto -> sockaddr decode: %s\r\n", xres) ); + return esock_make_error_str(env, xres); + } + + MLOCK(descP->writeMtx); + + res = nsendto(env, descP, sockRef, sendRef, &sndData, flags, + &remoteAddr, remoteAddrLen); + + MUNLOCK(descP->writeMtx); + + SGDBG( ("SOCKET", "nif_sendto -> done with result: " + "\r\n %T" + "\r\n", res) ); + + return res; +#endif // if defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsendto(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef, + ErlNifBinary* dataP, + int flags, + SocketAddress* toAddrP, + unsigned int toAddrLen) +{ + int save_errno; + ssize_t written; + ERL_NIF_TERM writerCheck; + + if (!descP->isWritable) + return enif_make_badarg(env); + + /* Check if there is already a current writer and if its us */ + if (!send_check_writer(env, descP, sendRef, &writerCheck)) + return writerCheck; + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->writeTries, 1); + + if (toAddrP != NULL) { + written = sock_sendto(descP->sock, + dataP->data, dataP->size, flags, + &toAddrP->sa, toAddrLen); + } else { + written = sock_sendto(descP->sock, + dataP->data, dataP->size, flags, + NULL, 0); + } + if (IS_SOCKET_ERROR(written)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + return send_check_result(env, descP, written, dataP->size, save_errno, + sockRef, sendRef); +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_sendmsg + * + * Description: + * Send a message on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * MsgHdr - Message Header - data and (maybe) control and dest + * Flags - Send flags. + */ + +static +ERL_NIF_TERM nif_sendmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + ERL_NIF_TERM res, sockRef, sendRef, eMsgHdr; + SocketDescriptor* descP; + unsigned int eflags; + int flags; + + SGDBG( ("SOCKET", "nif_sendmsg -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !IS_MAP(env, argv[2]) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + sockRef = argv[0]; // We need this in case we send in case we send abort + sendRef = argv[1]; + eMsgHdr = argv[2]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_sendmsg -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n sendRef: %T" + "\r\n eflags: %d" + "\r\n", + descP->sock, argv[0], sendRef, eflags) ); + + if (!esendflags2sendflags(eflags, &flags)) + return esock_make_error(env, esock_atom_einval); + + MLOCK(descP->writeMtx); + + res = nsendmsg(env, descP, sockRef, sendRef, eMsgHdr, flags); + + MUNLOCK(descP->writeMtx); + + SSDBG( descP, + ("SOCKET", "nif_sendmsg -> done with result: " + "\r\n %T" + "\r\n", res) ); + + return res; +#endif // if defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsendmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM eMsgHdr, + int flags) +{ + ERL_NIF_TERM res, eAddr, eIOV, eCtrl; + SocketAddress addr; + struct msghdr msgHdr; + ErlNifBinary* iovBins; + struct iovec* iov; + unsigned int iovLen; + char* ctrlBuf; + size_t ctrlBufLen, ctrlBufUsed; + int save_errno; + ssize_t written, dataSize; + ERL_NIF_TERM writerCheck; + char* xres; + + if (!descP->isWritable) + return enif_make_badarg(env); + + /* Check if there is already a current writer and if its us */ + if (!send_check_writer(env, descP, sendRef, &writerCheck)) + return writerCheck; + + /* Depending on if we are *connected* or not, we require + * different things in the msghdr map. + */ + if (IS_CONNECTED(descP)) { + + /* We don't need the address */ + + SSDBG( descP, ("SOCKET", "nsendmsg -> connected: no address\r\n") ); + + msgHdr.msg_name = NULL; + msgHdr.msg_namelen = 0; + + } else { + + /* We need the address */ + + msgHdr.msg_name = (void*) &addr; + msgHdr.msg_namelen = sizeof(addr); + sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen); + if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, ("SOCKET", "nsendmsg -> not connected: " + "\r\n address: %T" + "\r\n", eAddr) ); + + if ((xres = esock_decode_sockaddr(env, eAddr, + msgHdr.msg_name, + &msgHdr.msg_namelen)) != NULL) + return esock_make_error_str(env, xres); + } + + + /* Extract the (other) attributes of the msghdr map: iov and maybe ctrl */ + + /* The *mandatory* iov, which must be a list */ + if (!GET_MAP_VAL(env, eMsgHdr, esock_atom_iov, &eIOV)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_LIST_LEN(env, eIOV, &iovLen) && (iovLen > 0)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, ("SOCKET", "nsendmsg -> iov length: %d\r\n", iovLen) ); + + iovBins = MALLOC(iovLen * sizeof(ErlNifBinary)); + ESOCK_ASSERT( (iovBins != NULL) ); + + iov = MALLOC(iovLen * sizeof(struct iovec)); + ESOCK_ASSERT( (iov != NULL) ); + + /* The *opional* ctrl */ + if (GET_MAP_VAL(env, eMsgHdr, esock_atom_ctrl, &eCtrl)) { + ctrlBufLen = descP->wCtrlSz; + ctrlBuf = (char*) MALLOC(ctrlBufLen); + ESOCK_ASSERT( (ctrlBuf != NULL) ); + } else { + eCtrl = esock_atom_undefined; + ctrlBufLen = 0; + ctrlBuf = NULL; + } + SSDBG( descP, ("SOCKET", "nsendmsg -> optional ctrl: " + "\r\n ctrlBuf: 0x%lX" + "\r\n ctrlBufLen: %d" + "\r\n eCtrl: %T\r\n", ctrlBuf, ctrlBufLen, eCtrl) ); + + /* Decode the iov and initiate that part of the msghdr */ + if ((xres = esock_decode_iov(env, eIOV, + iovBins, iov, iovLen, &dataSize)) != NULL) { + FREE(iovBins); + FREE(iov); + if (ctrlBuf != NULL) FREE(ctrlBuf); + return esock_make_error_str(env, xres); + } + msgHdr.msg_iov = iov; + msgHdr.msg_iovlen = iovLen; + + + SSDBG( descP, ("SOCKET", + "nsendmsg -> total (iov) data size: %d\r\n", dataSize) ); + + + /* Decode the ctrl and initiate that part of the msghdr. + */ + if (ctrlBuf != NULL) { + if ((xres = decode_cmsghdrs(env, descP, + eCtrl, + ctrlBuf, ctrlBufLen, &ctrlBufUsed)) != NULL) { + FREE(iovBins); + FREE(iov); + if (ctrlBuf != NULL) FREE(ctrlBuf); + return esock_make_error_str(env, xres); + } + } else { + ctrlBufUsed = 0; + } + msgHdr.msg_control = ctrlBuf; + msgHdr.msg_controllen = ctrlBufUsed; + + + /* The msg-flags field is not used when sending, but zero it just in case */ + msgHdr.msg_flags = 0; + + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->writeTries, 1); + + /* And now, finally, try to send the message */ + written = sock_sendmsg(descP->sock, &msgHdr, flags); + + if (IS_SOCKET_ERROR(written)) + save_errno = sock_errno(); + else + save_errno = -1; // OK or not complete: this value should not matter in this case + + res = send_check_result(env, descP, written, dataSize, save_errno, + sockRef, sendRef); + + FREE(iovBins); + FREE(iov); + if (ctrlBuf != NULL) FREE(ctrlBuf); + + return res; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_writev / nif_sendv + * + * Description: + * Send a message (vector) on a socket + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * SendRef - A unique id for this (send) request. + * Data - A vector of binaries + * Flags - Send flags. + */ + +#ifdef FOBAR +static +ERL_NIF_TERM nwritev(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sendRef, + ERL_NIF_TERM data) +{ + ERL_NIF_TERM tail; + ErlNifIOVec vec; + ErlNifIOVec* iovec = &vec; + SysIOVec* sysiovec; + int save_errno; + int iovcnt, n; + + if (!enif_inspect_iovec(env, MAX_VSZ, data, &tail, &iovec)) + return enif_make_badarg(env); + + if (enif_ioq_size(descP->outQ) > 0) { + /* If the I/O queue contains data we enqueue the iovec + * and then peek the data to write out of the queue. + */ + if (!enif_ioq_enqv(q, iovec, 0)) + return -3; + + sysiovec = enif_ioq_peek(descP->outQ, &iovcnt); + + } else { + /* If the I/O queue is empty we skip the trip through it. */ + iovcnt = iovec->iovcnt; + sysiovec = iovec->iov; + } + + /* Attempt to write the data */ + n = writev(fd, sysiovec, iovcnt); + saved_errno = errno; + + if (enif_ioq_size(descP->outQ) == 0) { + /* If the I/O queue was initially empty we enqueue any + remaining data into the queue for writing later. */ + if (n >= 0 && !enif_ioq_enqv(descP->outQ, iovec, n)) + return -3; + } else { + /* Dequeue any data that was written from the queue. */ + if (n > 0 && !enif_ioq_deq(descP->outQ, n, NULL)) + return -4; + } + /* return n, which is either number of bytes written or -1 if + some error happened */ + errno = saved_errno; + return n; +} +#endif + + + +/* ---------------------------------------------------------------------- + * nif_recv + * + * Description: + * Receive a message on a socket. + * Normally used only on a connected socket! + * If we are trying to read > 0 bytes, then that is what we do. + * But if we have specified 0 bytes, then we want to read + * whatever is in the buffers (everything it got). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * RecvRef - A unique id for this (send) request. + * Length - The number of bytes to receive. + * Flags - Receive flags. + */ + +static +ERL_NIF_TERM nif_recv(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM sockRef, recvRef; + int len; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + if ((argc != 4) || + !GET_INT(env, argv[2], &len) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + sockRef = argv[0]; // We need this in case we case we send abort + recvRef = argv[1]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + if (!erecvflags2recvflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->readMtx); + + /* We need to handle the case when another process tries + * to receive at the same time. + * If the current recv could not read its entire package + * this time (resulting in an select). The read of the + * other process must be made to wait until current + * is done! + * Basically, we need a read queue! + * + * A 'reading' field (boolean), which is set if we did + * not manage to read the entire message and reset every + * time we do. + */ + + res = nrecv(env, descP, sockRef, recvRef, len, flags); + + MUNLOCK(descP->readMtx); + + return res; +#endif // if defined(__WIN32__) +} + + +/* The (read) buffer handling *must* be optimized! + * But for now we make it easy for ourselves by + * allocating a binary (of the specified or default + * size) and then throwing it away... + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nrecv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + int len, + int flags) +{ + ssize_t read; + ErlNifBinary buf; + ERL_NIF_TERM readerCheck; + int save_errno; + int bufSz = (len ? len : descP->rBufSz); + + SSDBG( descP, ("SOCKET", "nrecv -> entry with" + "\r\n len: %d (%d:%d)" + "\r\n flags: %d" + "\r\n", len, descP->rNumCnt, bufSz, flags) ); + + if (!descP->isReadable) + return enif_make_badarg(env); + + /* Check if there is already a current reader and if its us */ + if (!recv_check_reader(env, descP, recvRef, &readerCheck)) + return readerCheck; + + /* Allocate a buffer: + * Either as much as we want to read or (if zero (0)) use the "default" + * size (what has been configured). + */ + if (!ALLOC_BIN(bufSz, &buf)) + return esock_make_error(env, atom_exalloc); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->readTries, 1); + + // If it fails (read = -1), we need errno... + SSDBG( descP, ("SOCKET", "nrecv -> try read (%d)\r\n", buf.size) ); + read = sock_recv(descP->sock, buf.data, buf.size, flags); + if (IS_SOCKET_ERROR(read)) { + save_errno = sock_errno(); + } else { + save_errno = -1; // The value does not actually matter in this case + } + + SSDBG( descP, ("SOCKET", "nrecv -> read: %d (%d)\r\n", read, save_errno) ); + + return recv_check_result(env, descP, + read, len, + save_errno, + &buf, + sockRef, + recvRef); +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_recvfrom + * + * Description: + * Receive a message on a socket. + * Normally used only on a (un-) connected socket! + * If a buffer size = 0 is specified, then the we will use the default + * buffer size for this socket (whatever has been configured). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * RecvRef - A unique id for this (send) request. + * BufSz - Size of the buffer into which we put the received message. + * Flags - Receive flags. + * + * <KOLLA> + * + * How do we handle if the peek flag is set? We need to basically keep + * track of if we expect any data from the read. Regardless of the + * number of bytes we try to read. + * + * </KOLLA> + */ + +static +ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM sockRef, recvRef; + unsigned int bufSz; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_recvfrom -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 4) || + !GET_UINT(env, argv[2], &bufSz) || + !GET_UINT(env, argv[3], &eflags)) { + return enif_make_badarg(env); + } + sockRef = argv[0]; // We need this in case we send in case we send abort + recvRef = argv[1]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_recvfrom -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n recvRef: %T" + "\r\n bufSz: %d" + "\r\n eflags: %d" + "\r\n", descP->sock, argv[0], recvRef, bufSz, eflags) ); + + /* if (IS_OPEN(descP)) */ + /* return esock_make_error(env, atom_enotconn); */ + + if (!erecvflags2recvflags(eflags, &flags)) { + SSDBG( descP, ("SOCKET", "nif_recvfrom -> recvflags decode failed\r\n") ); + return enif_make_badarg(env); + } + + MLOCK(descP->readMtx); + + /* <KOLLA> + * We need to handle the case when another process tries + * to receive at the same time. + * If the current recv could not read its entire package + * this time (resulting in an select). The read of the + * other process must be made to wait until current + * is done! + * Basically, we need a read queue! + * + * A 'reading' field (boolean), which is set if we did + * not manage to read the entire message and reset every + * time we do. + * </KOLLA> + */ + + res = nrecvfrom(env, descP, sockRef, recvRef, bufSz, flags); + + MUNLOCK(descP->readMtx); + + return res; +#endif // if defined(__WIN32__) +} + + +/* The (read) buffer handling *must* be optimized! + * But for now we make it easy for ourselves by + * allocating a binary (of the specified or default + * size) and then throwing it away... + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nrecvfrom(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + Uint16 len, + int flags) +{ + SocketAddress fromAddr; + unsigned int addrLen; + ssize_t read; + int save_errno; + ErlNifBinary buf; + ERL_NIF_TERM readerCheck; + int bufSz = (len ? len : descP->rBufSz); + + SSDBG( descP, ("SOCKET", "nrecvfrom -> entry with" + "\r\n len: %d (%d)" + "\r\n flags: %d" + "\r\n", len, bufSz, flags) ); + + if (!descP->isReadable) + return enif_make_badarg(env); + + /* Check if there is already a current reader and if its us */ + if (!recv_check_reader(env, descP, recvRef, &readerCheck)) + return readerCheck; + + /* Allocate a buffer: + * Either as much as we want to read or (if zero (0)) use the "default" + * size (what has been configured). + */ + if (!ALLOC_BIN(bufSz, &buf)) + return esock_make_error(env, atom_exalloc); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->readTries, 1); + + addrLen = sizeof(fromAddr); + sys_memzero((char*) &fromAddr, addrLen); + + read = sock_recvfrom(descP->sock, buf.data, buf.size, flags, + &fromAddr.sa, &addrLen); + if (IS_SOCKET_ERROR(read)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + return recvfrom_check_result(env, descP, + read, + save_errno, + &buf, + &fromAddr, addrLen, + sockRef, + recvRef); +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_recvmsg + * + * Description: + * Receive a message on a socket. + * Normally used only on a (un-) connected socket! + * If a buffer size = 0 is specified, then we will use the default + * buffer size for this socket (whatever has been configured). + * If ctrl (buffer) size = 0 is specified, then the default ctrl + * (buffer) size is used (1024). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * RecvRef - A unique id for this (send) request. + * BufSz - Size of the buffer into which we put the received message. + * CtrlSz - Size of the ctrl (buffer) into which we put the received + * ancillary data. + * Flags - Receive flags. + * + * <KOLLA> + * + * How do we handle if the peek flag is set? We need to basically keep + * track of if we expect any data from the read. Regardless of the + * number of bytes we try to read. + * + * </KOLLA> + */ + +static +ERL_NIF_TERM nif_recvmsg(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM sockRef, recvRef; + unsigned int bufSz; + unsigned int ctrlSz; + unsigned int eflags; + int flags; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_recvmsg -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 5) || + !GET_UINT(env, argv[2], &bufSz) || + !GET_UINT(env, argv[3], &ctrlSz) || + !GET_UINT(env, argv[4], &eflags)) { + return enif_make_badarg(env); + } + sockRef = argv[0]; // We need this in case we send in case we send abort + recvRef = argv[1]; + + if (!enif_get_resource(env, sockRef, sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + SSDBG( descP, + ("SOCKET", "nif_recvmsg -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n recvRef: %T" + "\r\n bufSz: %d" + "\r\n ctrlSz: %d" + "\r\n eflags: %d" + "\r\n", descP->sock, argv[0], recvRef, bufSz, ctrlSz, eflags) ); + + /* if (IS_OPEN(descP)) */ + /* return esock_make_error(env, atom_enotconn); */ + + if (!erecvflags2recvflags(eflags, &flags)) + return enif_make_badarg(env); + + MLOCK(descP->readMtx); + + /* <KOLLA> + * + * We need to handle the case when another process tries + * to receive at the same time. + * If the current recv could not read its entire package + * this time (resulting in an select). The read of the + * other process must be made to wait until current + * is done! + * Basically, we need a read queue! + * + * A 'reading' field (boolean), which is set if we did + * not manage to read the entire message and reset every + * time we do. + * + * </KOLLA> + */ + + res = nrecvmsg(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags); + + MUNLOCK(descP->readMtx); + + return res; +#endif // if defined(__WIN32__) +} + + +/* The (read) buffer handling *must* be optimized! + * But for now we make it easy for ourselves by + * allocating a binary (of the specified or default + * size) and then throwing it away... + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nrecvmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + Uint16 bufLen, + Uint16 ctrlLen, + int flags) +{ + unsigned int addrLen; + ssize_t read; + int save_errno; + int bufSz = (bufLen ? bufLen : descP->rBufSz); + int ctrlSz = (ctrlLen ? ctrlLen : descP->rCtrlSz); + struct msghdr msgHdr; + struct iovec iov[1]; // Shall we always use 1? + ErlNifBinary data[1]; // Shall we always use 1? + ErlNifBinary ctrl; + ERL_NIF_TERM readerCheck; + SocketAddress addr; + + SSDBG( descP, ("SOCKET", "nrecvmsg -> entry with" + "\r\n bufSz: %d (%d)" + "\r\n ctrlSz: %d (%d)" + "\r\n flags: %d" + "\r\n", bufSz, bufLen, ctrlSz, ctrlLen, flags) ); + + if (!descP->isReadable) + return enif_make_badarg(env); + + /* Check if there is already a current reader and if its us */ + if (!recv_check_reader(env, descP, recvRef, &readerCheck)) + return readerCheck; + + /* + for (i = 0; i < sizeof(buf); i++) { + if (!ALLOC_BIN(bifSz, &buf[i])) + return esock_make_error(env, atom_exalloc); + iov[i].iov_base = buf[i].data; + iov[i].iov_len = buf[i].size; + } + */ + + /* Allocate the (msg) data buffer: + */ + if (!ALLOC_BIN(bufSz, &data[0])) + return esock_make_error(env, atom_exalloc); + + /* Allocate the ctrl (buffer): + */ + if (!ALLOC_BIN(ctrlSz, &ctrl)) + return esock_make_error(env, atom_exalloc); + + /* We ignore the wrap for the moment. + * Maybe we should issue a wrap-message to controlling process... + */ + cnt_inc(&descP->readTries, 1); + + addrLen = sizeof(addr); + sys_memzero((char*) &addr, addrLen); + sys_memzero((char*) &msgHdr, sizeof(msgHdr)); + + iov[0].iov_base = data[0].data; + iov[0].iov_len = data[0].size; + + msgHdr.msg_name = &addr; + msgHdr.msg_namelen = addrLen; + msgHdr.msg_iov = iov; + msgHdr.msg_iovlen = 1; // Should use a constant or calculate... + msgHdr.msg_control = ctrl.data; + msgHdr.msg_controllen = ctrl.size; + + read = sock_recvmsg(descP->sock, &msgHdr, flags); + if (IS_SOCKET_ERROR(read)) + save_errno = sock_errno(); + else + save_errno = -1; // The value does not actually matter in this case + + return recvmsg_check_result(env, descP, + read, + save_errno, + &msgHdr, + data, // Needed for iov encode + &ctrl, // Needed for ctrl header encode + sockRef, + recvRef); +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_close + * + * Description: + * Close a (socket) file descriptor. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + return nclose(env, descP); +#endif // if defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nclose(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM reply, reason; + BOOLEAN_T doClose; + int selectRes; + int domain = descP->domain; + int type = descP->type; + int protocol = descP->protocol; + + SSDBG( descP, ("SOCKET", + "nclose -> [%d] entry (0x%lX, 0x%lX, 0x%lX, 0x%lX)\r\n", + descP->sock, + descP->state, + descP->currentWriterP, + descP->currentReaderP, + descP->currentAcceptorP) ); + + MLOCK(descP->closeMtx); + + if (descP->state == SOCKET_STATE_CLOSED) { + reason = atom_closed; + doClose = FALSE; + } else if (descP->state == SOCKET_STATE_CLOSING) { + reason = atom_closing; + doClose = FALSE; + } else { + + /* Store the PID of the caller, + * since we need to inform it when we + * (that is, the stop callback function) + * completes. + */ + + if (enif_self(env, &descP->closerPid) == NULL) { + MUNLOCK(descP->closeMtx); + return esock_make_error(env, atom_exself); + } + + /* Monitor the caller, since we should complete this operation even if + * the caller dies (for whatever reason). + * + * <KOLLA> + * + * Can we actiually use this for anything? + * + * </KOLLA> + */ + + if (MONP("nclose -> closer", + env, descP, + &descP->closerPid, + &descP->closerMon) != 0) { + MUNLOCK(descP->closeMtx); + return esock_make_error(env, atom_exmon); + } + + descP->closeLocal = TRUE; + descP->state = SOCKET_STATE_CLOSING; + descP->isReadable = FALSE; + descP->isWritable = FALSE; + doClose = TRUE; + } + + MUNLOCK(descP->closeMtx); + + if (doClose) { + descP->closeRef = MKREF(env); + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); + if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { + /* Prep done - inform the caller it can finalize (close) directly */ + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop was called\r\n", descP->sock) ); + dec_socket(domain, type, protocol); + reply = esock_atom_ok; + } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { + /* The stop callback function has been *scheduled* which means that we + * have to wait for it to complete. */ + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop was scheduled\r\n", + descP->sock) ); + dec_socket(domain, type, protocol); // SHALL WE DO THIS AT finalize? + reply = esock_make_ok2(env, descP->closeRef); + } else { + + SSDBG( descP, + ("SOCKET", "nclose -> [%d] stop failed: %d\r\n", + descP->sock, selectRes) ); + + /* <KOLLA> + * + * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, + * SO WE DON'T LET STUFF LEAK. + * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH + * THE CLOSE, WHAT TO DO? ABORT? + * + * </KOLLA> + */ + + // No point in having this? + DEMONP("nclose -> closer", env, descP, &descP->closerMon); + + reason = MKT2(env, atom_select, MKI(env, selectRes)); + reply = esock_make_error(env, reason); + } + } else { + reply = esock_make_error(env, reason); + } + + SSDBG( descP, + ("SOCKET", "nclose -> [%d] done when: " + "\r\n state: 0x%lX" + "\r\n reply: %T" + "\r\n", descP->sock, descP->state, reply) ); + + return reply; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_finalize_close + * + * Description: + * Perform the actual socket close! + * Note that this function is executed in a dirty scheduler. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ +static +ERL_NIF_TERM nif_finalize_close(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + return nfinalize_close(env, descP); +#endif // if defined(__WIN32__) +} + + +/* *** nfinalize_close *** + * Perform the final step in the socket close. + */ +#if !defined(__WIN32__) +static +ERL_NIF_TERM nfinalize_close(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM reply; + + if (IS_CLOSED(descP)) + return esock_atom_ok; + + if (!IS_CLOSING(descP)) + return esock_make_error(env, atom_enotclosing); + + /* This nif is executed in a dirty scheduler just so that + * it can "hang" (whith minumum effect on the VM) while the + * kernel writes our buffers. IF we have set the linger option + * for this ({true, integer() > 0}). For this to work we must + * be blocking... + */ + SET_BLOCKING(descP->sock); + + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + if (save_errno != ERRNO_BLOCK) { + /* Not all data in the buffers where sent, + * make sure the caller gets this. + */ + reply = esock_make_error(env, atom_timeout); + } else { + reply = esock_make_error_errno(env, save_errno); + } + } else { + reply = esock_atom_ok; + } + sock_close_event(descP->event); + + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + + descP->state = SOCKET_STATE_CLOSED; + + return reply; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_shutdown + * + * Description: + * Disable sends and/or receives on a socket. + * + * Arguments: + * [0] Socket (ref) - Points to the socket descriptor. + * [1] How - What will be shutdown. + */ + +static +ERL_NIF_TERM nif_shutdown(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + unsigned int ehow; + int how; + + if ((argc != 2) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_UINT(env, argv[1], &ehow)) { + return enif_make_badarg(env); + } + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + if (!ehow2how(ehow, &how)) + return enif_make_badarg(env); + + return nshutdown(env, descP, how); +#endif // if defined(__WIN32__) +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nshutdown(ErlNifEnv* env, + SocketDescriptor* descP, + int how) +{ + ERL_NIF_TERM reply; + + if (sock_shutdown(descP->sock, how) == 0) { + switch (how) { + case SHUT_RD: + descP->isReadable = FALSE; + break; + case SHUT_WR: + descP->isWritable = FALSE; + break; + case SHUT_RDWR: + descP->isReadable = FALSE; + descP->isWritable = FALSE; + break; + } + reply = esock_atom_ok; + } else { + reply = esock_make_error_errno(env, sock_errno()); + } + + return reply; +} +#endif // if !defined(__WIN32__) + + + + +/* ---------------------------------------------------------------------- + * nif_setopt + * + * Description: + * Set socket option. + * Its possible to use a "raw" mode (not encoded). That is, we do not + * interpret level, opt and value. They are passed "as is" to the + * setsockopt function call (the value arguments is assumed to be a + * binary, already encoded). + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Encoded - Are the "arguments" encoded or not. + * Level - Level of the socket option. + * Opt - The socket option. + * Value - Value of the socket option (type depend on the option). + */ + +static +ERL_NIF_TERM nif_setopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP = NULL; + int eLevel, level = -1; + int eOpt; + ERL_NIF_TERM eIsEncoded; + ERL_NIF_TERM eVal; + BOOLEAN_T isEncoded, isOTP; + ERL_NIF_TERM result; + + SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 5) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_INT(env, argv[2], &eLevel) || + !GET_INT(env, argv[3], &eOpt)) { + SGDBG( ("SOCKET", "nif_setopt -> failed initial arg check\r\n") ); + return enif_make_badarg(env); + } + eIsEncoded = argv[1]; + eVal = argv[4]; + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + isEncoded = esock_decode_bool(eIsEncoded); + + /* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */ + /* eIsEncoded, isEncoded) ); */ + + if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) { + SSDBG( descP, ("SOCKET", "nif_seopt -> failed decode level\r\n") ); + return esock_make_error(env, esock_atom_einval); + } + + SSDBG( descP, + ("SOCKET", "nif_setopt -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n Encoded: %d (%T)" + "\r\n Level: %d (%d)" + "\r\n Opt: %d" + "\r\n Value: %T" + "\r\n", + descP->sock, argv[0], + isEncoded, eIsEncoded, + level, eLevel, + eOpt, eVal) ); + + result = nsetopt(env, descP, isEncoded, isOTP, level, eOpt, eVal); + + SSDBG( descP, + ("SOCKET", "nif_setopt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +#endif // if defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + if (isOTP) { + /* These are not actual socket options, + * but options for our implementation. + */ + result = nsetopt_otp(env, descP, eOpt, eVal); + } else if (!isEncoded) { + result = nsetopt_native(env, descP, level, eOpt, eVal); + } else { + result = nsetopt_level(env, descP, level, eOpt, eVal); + } + + return result; +} + + + +/* nsetopt_otp - Handle OTP (level) options + */ +static +ERL_NIF_TERM nsetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_otp -> entry with" + "\r\n eOpt: %d" + "\r\n eVal: %T" + "\r\n", eOpt, eVal) ); + + switch (eOpt) { + case SOCKET_OPT_OTP_DEBUG: + result = nsetopt_otp_debug(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_IOW: + result = nsetopt_otp_iow(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_CTRL_PROC: + result = nsetopt_otp_ctrl_proc(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_RCVBUF: + result = nsetopt_otp_rcvbuf(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_RCVCTRLBUF: + result = nsetopt_otp_rcvctrlbuf(env, descP, eVal); + break; + + case SOCKET_OPT_OTP_SNDCTRLBUF: + result = nsetopt_otp_sndctrlbuf(env, descP, eVal); + break; + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} + + +/* nsetopt_otp_debug - Handle the OTP (level) debug options + */ +static +ERL_NIF_TERM nsetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + descP->dbg = esock_decode_bool(eVal); + + return esock_atom_ok; +} + + +/* nsetopt_otp_iow - Handle the OTP (level) iow options + */ +static +ERL_NIF_TERM nsetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + descP->iow = esock_decode_bool(eVal); + + return esock_atom_ok; +} + + + +/* nsetopt_otp_ctrl_proc - Handle the OTP (level) controlling_process options + */ +static +ERL_NIF_TERM nsetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ErlNifPid caller, newCtrlPid; + // ErlNifMonitor newCtrlMon; + ESockMonitor newCtrlMon; + int xres; + + SSDBG( descP, + ("SOCKET", "nsetopt_otp_ctrl_proc -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + /* Before we begin, ensure that caller is (current) controlling-process */ + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + if (!compare_pids(env, &descP->ctrlPid, &caller)) { + SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> not owner (%T)\r\n", + descP->ctrlPid) ); + return esock_make_error(env, esock_atom_not_owner); + } + + if (!GET_LPID(env, eVal, &newCtrlPid)) { + esock_warning_msg("Failed get pid of new controlling process\r\n"); + return esock_make_error(env, esock_atom_einval); + } + + if ((xres = MONP("nsetopt_otp_ctrl_proc -> (new) ctrl", + env, descP, &newCtrlPid, &newCtrlMon)) != 0) { + esock_warning_msg("Failed monitor %d) (new) controlling process\r\n", xres); + return esock_make_error(env, esock_atom_einval); + } + + if ((xres = DEMONP("nsetopt_otp_ctrl_proc -> (old) ctrl", + env, descP, &descP->ctrlMon)) != 0) { + esock_warning_msg("Failed demonitor (%d) " + "old controlling process %T (%T)\r\n", + xres, descP->ctrlPid, descP->ctrlMon); + } + + descP->ctrlPid = newCtrlPid; + descP->ctrlMon = newCtrlMon; + + SSDBG( descP, ("SOCKET", "nsetopt_otp_ctrl_proc -> done\r\n") ); + + return esock_atom_ok; +} + + + +/* nsetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option + * The (otp) rcvbuf option is provided as: + * + * BufSz :: integer() | {N :: pos_integer(), BufSz :: pod_integer()} + * + * Where N is the max number of reads. + */ +static +ERL_NIF_TERM nsetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + const ERL_NIF_TERM* t; // The array of the elements of the tuple + int tsz; // The size of the tuple - should be 2 + unsigned int n; + size_t bufSz; + char* xres; + + if (IS_NUM(env, eVal)) { + + /* This will have the effect that the buffer size will be + * reported as an integer (getopt). + */ + n = 0; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_RECV_BUFFER_SIZE_DEFAULT, + &bufSz)) != NULL) + return esock_make_error_str(env, xres); + + } else if (IS_TUPLE(env, eVal)) { + + if (!GET_TUPLE(env, eVal, &tsz, &t)) + return enif_make_badarg(env); // We should use a "proper" error value... + + if (tsz != 2) + return enif_make_badarg(env); // We should use a "proper" error value... + + if (!GET_UINT(env, t[0], &n)) + return enif_make_badarg(env); // We should use a "proper" error value... + + if ((xres = esock_decode_bufsz(env, + t[1], + SOCKET_RECV_BUFFER_SIZE_DEFAULT, + &bufSz)) != NULL) + return esock_make_error_str(env, xres); + + } else { + return enif_make_badarg(env); // We should use a "proper" error value... + } + + descP->rNum = n; + descP->rBufSz = bufSz; + + return esock_atom_ok; +} + + + +/* nsetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT, + &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->rCtrlSz = val; + + return esock_atom_ok; +} + + + +/* nsetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option + */ +static +ERL_NIF_TERM nsetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + size_t val; + char* xres; + + if ((xres = esock_decode_bufsz(env, + eVal, + SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT, + &val)) != NULL) + return esock_make_error_str(env, xres); + + descP->wCtrlSz = val; + + return esock_atom_ok; +} + + + +/* The option has *not* been encoded. Instead it has been provided + * in "native mode" (option is provided as is and value as a binary). + */ +static +ERL_NIF_TERM nsetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ErlNifBinary val; + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_native -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n eVal: %T" + "\r\n", level, opt, eVal) ); + + if (GET_BIN(env, eVal, &val)) { + int res = socket_setopt(descP->sock, level, opt, + val.data, val.size); + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + } else { + result = esock_make_error(env, esock_atom_einval); + } + + SSDBG( descP, + ("SOCKET", "nsetopt_native -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + + +/* nsetopt_level - A "proper" level (option) has been specified + */ +static +ERL_NIF_TERM nsetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_level -> entry with" + "\r\n level: %d" + "\r\n", level) ); + + switch (level) { + case SOL_SOCKET: + result = nsetopt_lvl_socket(env, descP, eOpt, eVal); + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + result = nsetopt_lvl_ip(env, descP, eOpt, eVal); + break; + +#if defined(HAVE_IPV6) +#if defined(SOL_IPV6) + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif + result = nsetopt_lvl_ipv6(env, descP, eOpt, eVal); + break; +#endif + + case IPPROTO_TCP: + result = nsetopt_lvl_tcp(env, descP, eOpt, eVal); + break; + + case IPPROTO_UDP: + result = nsetopt_lvl_udp(env, descP, eOpt, eVal); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = nsetopt_lvl_sctp(env, descP, eOpt, eVal); + break; +#endif + + default: + SSDBG( descP, + ("SOCKET", "nsetopt_level -> unknown level (%d)\r\n", level) ); + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "nsetopt_level -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + + +/* nsetopt_lvl_socket - Level *SOCKET* option + */ +static +ERL_NIF_TERM nsetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_socket -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(SO_BINDTODEVICE) + case SOCKET_OPT_SOCK_BINDTODEVICE: + result = nsetopt_lvl_sock_bindtodevice(env, descP, eVal); + break; +#endif + +#if defined(SO_BROADCAST) + case SOCKET_OPT_SOCK_BROADCAST: + result = nsetopt_lvl_sock_broadcast(env, descP, eVal); + break; +#endif + +#if defined(SO_DEBUG) + case SOCKET_OPT_SOCK_DEBUG: + result = nsetopt_lvl_sock_debug(env, descP, eVal); + break; +#endif + +#if defined(SO_DONTROUTE) + case SOCKET_OPT_SOCK_DONTROUTE: + result = nsetopt_lvl_sock_dontroute(env, descP, eVal); + break; +#endif + +#if defined(SO_KEEPALIVE) + case SOCKET_OPT_SOCK_KEEPALIVE: + result = nsetopt_lvl_sock_keepalive(env, descP, eVal); + break; +#endif + +#if defined(SO_LINGER) + case SOCKET_OPT_SOCK_LINGER: + result = nsetopt_lvl_sock_linger(env, descP, eVal); + break; +#endif + +#if defined(SO_PEEK_OFF) + case SOCKET_OPT_SOCK_PEEK_OFF: + result = nsetopt_lvl_sock_peek_off(env, descP, eVal); + break; +#endif + +#if defined(SO_OOBINLINE) + case SOCKET_OPT_SOCK_OOBINLINE: + result = nsetopt_lvl_sock_oobinline(env, descP, eVal); + break; +#endif + +#if defined(SO_PRIORITY) + case SOCKET_OPT_SOCK_PRIORITY: + result = nsetopt_lvl_sock_priority(env, descP, eVal); + break; +#endif + +#if defined(SO_RCVBUF) + case SOCKET_OPT_SOCK_RCVBUF: + result = nsetopt_lvl_sock_rcvbuf(env, descP, eVal); + break; +#endif + +#if defined(SO_RCVLOWAT) + case SOCKET_OPT_SOCK_RCVLOWAT: + result = nsetopt_lvl_sock_rcvlowat(env, descP, eVal); + break; +#endif + +#if defined(SO_RCVTIMEO) + case SOCKET_OPT_SOCK_RCVTIMEO: + result = nsetopt_lvl_sock_rcvtimeo(env, descP, eVal); + break; +#endif + +#if defined(SO_REUSEADDR) + case SOCKET_OPT_SOCK_REUSEADDR: + result = nsetopt_lvl_sock_reuseaddr(env, descP, eVal); + break; +#endif + +#if defined(SO_REUSEPORT) + case SOCKET_OPT_SOCK_REUSEPORT: + result = nsetopt_lvl_sock_reuseport(env, descP, eVal); + break; +#endif + +#if defined(SO_SNDBUF) + case SOCKET_OPT_SOCK_SNDBUF: + result = nsetopt_lvl_sock_sndbuf(env, descP, eVal); + break; +#endif + +#if defined(SO_SNDLOWAT) + case SOCKET_OPT_SOCK_SNDLOWAT: + result = nsetopt_lvl_sock_sndlowat(env, descP, eVal); + break; +#endif + +#if defined(SO_SNDTIMEO) + case SOCKET_OPT_SOCK_SNDTIMEO: + result = nsetopt_lvl_sock_sndtimeo(env, descP, eVal); + break; +#endif + +#if defined(SO_TIMESTAMP) + case SOCKET_OPT_SOCK_TIMESTAMP: + result = nsetopt_lvl_sock_timestamp(env, descP, eVal); + break; +#endif + + default: + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_socket -> unknown opt (%d)\r\n", eOpt) ); + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_socket -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +#if defined(SO_BINDTODEVICE) +static +ERL_NIF_TERM nsetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_str_opt(env, descP, + SOL_SOCKET, SO_BROADCAST, + IFNAMSIZ, eVal); +} +#endif + + +#if defined(SO_BROADCAST) +static +ERL_NIF_TERM nsetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST, eVal); +} +#endif + + +#if defined(SO_DEBUG) +static +ERL_NIF_TERM nsetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG, eVal); +} +#endif + + +#if defined(SO_DONTROUTE) +static +ERL_NIF_TERM nsetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE, eVal); +} +#endif + + +#if defined(SO_KEEPALIVE) +static +ERL_NIF_TERM nsetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE, eVal); +} +#endif + + +#if defined(SO_LINGER) +static +ERL_NIF_TERM nsetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + struct linger val; + + if (decode_sock_linger(env, eVal, &val)) { + int optLen = sizeof(val); + int res = socket_setopt(descP->sock, SOL_SOCKET, SO_LINGER, + (void*) &val, optLen); + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + } else { + result = esock_make_error(env, esock_atom_einval); + } + + return result; +} +#endif + + +#if defined(SO_OOBINLINE) +static +ERL_NIF_TERM nsetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE, eVal); +} +#endif + + +#if defined(SO_PEEK_OFF) +static +ERL_NIF_TERM nsetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF, eVal); +} +#endif + + +#if defined(SO_PRIORITY) +static +ERL_NIF_TERM nsetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY, eVal); +} +#endif + + +#if defined(SO_RCVBUF) +static +ERL_NIF_TERM nsetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF, eVal); +} +#endif + + +#if defined(SO_RCVLOWAT) +static +ERL_NIF_TERM nsetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT, eVal); +} +#endif + + +#if defined(SO_RCVTIMEO) +static +ERL_NIF_TERM nsetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO, eVal); +} +#endif + + +#if defined(SO_REUSEADDR) +static +ERL_NIF_TERM nsetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR, eVal); +} +#endif + + +#if defined(SO_REUSEPORT) +static +ERL_NIF_TERM nsetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT, eVal); +} +#endif + + +#if defined(SO_SNDBUF) +static +ERL_NIF_TERM nsetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF, eVal); +} +#endif + + +#if defined(SO_SNDLOWAT) +static +ERL_NIF_TERM nsetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT, eVal); +} +#endif + + +#if defined(SO_SNDTIMEO) +static +ERL_NIF_TERM nsetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sock_sndtimeo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + return nsetopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO, eVal); +} +#endif + + +#if defined(SO_TIMESTAMP) +static +ERL_NIF_TERM nsetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP, eVal); +} +#endif + + + +/* nsetopt_lvl_ip - Level *IP* option(s) + */ +static +ERL_NIF_TERM nsetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(IP_ADD_MEMBERSHIP) + case SOCKET_OPT_IP_ADD_MEMBERSHIP: + result = nsetopt_lvl_ip_add_membership(env, descP, eVal); + break; +#endif + +#if defined(IP_ADD_SOURCE_MEMBERSHIP) + case SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP: + result = nsetopt_lvl_ip_add_source_membership(env, descP, eVal); + break; +#endif + +#if defined(IP_BLOCK_SOURCE) + case SOCKET_OPT_IP_BLOCK_SOURCE: + result = nsetopt_lvl_ip_block_source(env, descP, eVal); + break; +#endif + +#if defined(IP_DROP_MEMBERSHIP) + case SOCKET_OPT_IP_DROP_MEMBERSHIP: + result = nsetopt_lvl_ip_drop_membership(env, descP, eVal); + break; +#endif + +#if defined(IP_DROP_SOURCE_MEMBERSHIP) + case SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP: + result = nsetopt_lvl_ip_drop_source_membership(env, descP, eVal); + break; +#endif + +#if defined(IP_FREEBIND) + case SOCKET_OPT_IP_FREEBIND: + result = nsetopt_lvl_ip_freebind(env, descP, eVal); + break; +#endif + +#if defined(IP_HDRINCL) + case SOCKET_OPT_IP_HDRINCL: + result = nsetopt_lvl_ip_hdrincl(env, descP, eVal); + break; +#endif + +#if defined(IP_MINTTL) + case SOCKET_OPT_IP_MINTTL: + result = nsetopt_lvl_ip_minttl(env, descP, eVal); + break; +#endif + +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) + case SOCKET_OPT_IP_MSFILTER: + result = nsetopt_lvl_ip_msfilter(env, descP, eVal); + break; +#endif + +#if defined(IP_MTU_DISCOVER) + case SOCKET_OPT_IP_MTU_DISCOVER: + result = nsetopt_lvl_ip_mtu_discover(env, descP, eVal); + break; +#endif + +#if defined(IP_MULTICAST_ALL) + case SOCKET_OPT_IP_MULTICAST_ALL: + result = nsetopt_lvl_ip_multicast_all(env, descP, eVal); + break; +#endif + +#if defined(IP_MULTICAST_IF) + case SOCKET_OPT_IP_MULTICAST_IF: + result = nsetopt_lvl_ip_multicast_if(env, descP, eVal); + break; +#endif + +#if defined(IP_MULTICAST_LOOP) + case SOCKET_OPT_IP_MULTICAST_LOOP: + result = nsetopt_lvl_ip_multicast_loop(env, descP, eVal); + break; +#endif + +#if defined(IP_MULTICAST_TTL) + case SOCKET_OPT_IP_MULTICAST_TTL: + result = nsetopt_lvl_ip_multicast_ttl(env, descP, eVal); + break; +#endif + +#if defined(IP_NODEFRAG) + case SOCKET_OPT_IP_NODEFRAG: + result = nsetopt_lvl_ip_nodefrag(env, descP, eVal); + break; +#endif + +#if defined(IP_PKTINFO) + case SOCKET_OPT_IP_PKTINFO: + result = nsetopt_lvl_ip_pktinfo(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVDSTADDR) + case SOCKET_OPT_IP_RECVDSTADDR: + result = nsetopt_lvl_ip_recvdstaddr(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVERR) + case SOCKET_OPT_IP_RECVERR: + result = nsetopt_lvl_ip_recverr(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVIF) + case SOCKET_OPT_IP_RECVIF: + result = nsetopt_lvl_ip_recvif(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVOPTS) + case SOCKET_OPT_IP_RECVOPTS: + result = nsetopt_lvl_ip_recvopts(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVORIGDSTADDR) + case SOCKET_OPT_IP_RECVORIGDSTADDR: + result = nsetopt_lvl_ip_recvorigdstaddr(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVTOS) + case SOCKET_OPT_IP_RECVTOS: + result = nsetopt_lvl_ip_recvtos(env, descP, eVal); + break; +#endif + +#if defined(IP_RECVTTL) + case SOCKET_OPT_IP_RECVTTL: + result = nsetopt_lvl_ip_recvttl(env, descP, eVal); + break; +#endif + +#if defined(IP_RETOPTS) + case SOCKET_OPT_IP_RETOPTS: + result = nsetopt_lvl_ip_retopts(env, descP, eVal); + break; +#endif + +#if defined(IP_ROUTER_ALERT) + case SOCKET_OPT_IP_ROUTER_ALERT: + result = nsetopt_lvl_ip_router_alert(env, descP, eVal); + break; +#endif + +#if defined(IP_SENDSRCADDR) + case SOCKET_OPT_IP_SENDSRCADDR: + result = nsetopt_lvl_ip_sendsrcaddr(env, descP, eVal); + break; +#endif + +#if defined(IP_TOS) + case SOCKET_OPT_IP_TOS: + result = nsetopt_lvl_ip_tos(env, descP, eVal); + break; +#endif + +#if defined(IP_TRANSPARENT) + case SOCKET_OPT_IP_TRANSPARENT: + result = nsetopt_lvl_ip_transparent(env, descP, eVal); + break; +#endif + +#if defined(IP_TTL) + case SOCKET_OPT_IP_TTL: + result = nsetopt_lvl_ip_ttl(env, descP, eVal); + break; +#endif + +#if defined(IP_UNBLOCK_SOURCE) + case SOCKET_OPT_IP_UNBLOCK_SOURCE: + result = nsetopt_lvl_ip_unblock_source(env, descP, eVal); + break; +#endif + + default: + SSDBG( descP, ("SOCKET", "nsetopt_lvl_ip -> unknown opt (%d)\r\n", eOpt) ); + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +/* nsetopt_lvl_ip_add_membership - Level IP ADD_MEMBERSHIP option + * + * The value is a map with two attributes: multiaddr and interface. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is either the atom 'any' or a 4-tuple + * (IPv4 address). + */ +#if defined(IP_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_membership(env, descP, eVal, IP_ADD_MEMBERSHIP); +} +#endif + + +/* nsetopt_lvl_ip_add_source_membership - Level IP ADD_SOURCE_MEMBERSHIP option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_ADD_SOURCE_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_add_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, + IP_ADD_SOURCE_MEMBERSHIP); +} +#endif + + +/* nsetopt_lvl_ip_block_source - Level IP BLOCK_SOURCE option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_BLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_block_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, IP_BLOCK_SOURCE); +} +#endif + + +/* nsetopt_lvl_ip_drop_membership - Level IP DROP_MEMBERSHIP option + * + * The value is a map with two attributes: multiaddr and interface. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is either the atom 'any' or a 4-tuple + * (IPv4 address). + * + * We should really have a common function with add_membership, + * since the code is virtually identical (except for the option + * value). + */ +#if defined(IP_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_membership(env, descP, eVal, + IP_DROP_MEMBERSHIP); +} +#endif + + + +/* nsetopt_lvl_ip_drop_source_membership - Level IP DROP_SOURCE_MEMBERSHIP option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_DROP_SOURCE_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_drop_source_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, + IP_DROP_SOURCE_MEMBERSHIP); +} +#endif + + + +/* nsetopt_lvl_ip_freebind - Level IP FREEBIND option + */ +#if defined(IP_FREEBIND) +static +ERL_NIF_TERM nsetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_FREEBIND, eVal); +} +#endif + + + +/* nsetopt_lvl_ip_hdrincl - Level IP HDRINCL option + */ +#if defined(IP_HDRINCL) +static +ERL_NIF_TERM nsetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_HDRINCL, eVal); +} +#endif + + + +/* nsetopt_lvl_ip_minttl - Level IP MINTTL option + */ +#if defined(IP_MINTTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_MINTTL, eVal); +} +#endif + + + +/* nsetopt_lvl_ip_msfilter - Level IP MSFILTER option + * + * The value can be *either* the atom 'null' or a map of type ip_msfilter(). + */ +#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE) +static +ERL_NIF_TERM nsetopt_lvl_ip_msfilter(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + if (COMPARE(eVal, atom_null) == 0) { + return nsetopt_lvl_ip_msfilter_set(env, descP->sock, NULL, 0); + } else { + struct ip_msfilter* msfP; + Uint32 msfSz; + ERL_NIF_TERM eMultiAddr, eInterface, eFMode, eSList, elem, tail; + size_t sz; + unsigned int slistLen, idx; + + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast four attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 4)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_mode, &eFMode)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_slist, &eSList)) + return esock_make_error(env, esock_atom_einval); + + /* We start (decoding) with the slist, since without it we don't + * really know how much (memory) to allocate. + */ + if (!GET_LIST_LEN(env, eSList, &slistLen)) + return esock_make_error(env, esock_atom_einval); + + msfSz = IP_MSFILTER_SIZE(slistLen); + msfP = MALLOC(msfSz); + + if (!esock_decode_ip4_address(env, eMultiAddr, &msfP->imsf_multiaddr)) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } + + if (!esock_decode_ip4_address(env, eInterface, &msfP->imsf_interface)) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } + + if (!decode_ip_msfilter_mode(env, eFMode, &msfP->imsf_fmode)) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } + + /* And finally, extract the source addresses */ + msfP->imsf_numsrc = slistLen; + for (idx = 0; idx < slistLen; idx++) { + if (GET_LIST_ELEM(env, eSList, &elem, &tail)) { + if (!esock_decode_ip4_address(env, elem, &msfP->imsf_slist[idx])) { + FREE(msfP); + return esock_make_error(env, esock_atom_einval); + } else { + eSList = tail; + } + } + } + + /* And now, finally, set the option */ + result = nsetopt_lvl_ip_msfilter_set(env, descP->sock, msfP, msfSz); + FREE(msfP); + return result; + } + +} + + +static +BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv* env, + ERL_NIF_TERM eVal, + Uint32* mode) +{ + BOOLEAN_T result; + + if (COMPARE(eVal, atom_include) == 0) { + *mode = MCAST_INCLUDE; + result = TRUE; + } else if (COMPARE(eVal, atom_exclude) == 0) { + *mode = MCAST_EXCLUDE; + result = TRUE; + } else { + result = FALSE; + } + + return result; +} + + +static +ERL_NIF_TERM nsetopt_lvl_ip_msfilter_set(ErlNifEnv* env, + SOCKET sock, + struct ip_msfilter* msfP, + SOCKLEN_T optLen) +{ + ERL_NIF_TERM result; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + res = socket_setopt(sock, level, IP_MSFILTER, (void*) msfP, optLen); + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif // IP_MSFILTER + + + +/* nsetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option + * + * The value is an atom of the type ip_pmtudisc(). + */ +#if defined(IP_MTU_DISCOVER) +static +ERL_NIF_TERM nsetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int val; + char* xres; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + if ((xres = decode_ip_pmtudisc(env, eVal, &val)) != NULL) { + + result = esock_make_error_str(env, xres); + + } else { + + res = socket_setopt(descP->sock, level, IP_MTU_DISCOVER, + &val, sizeof(val)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } + + return result; +} +#endif + + +/* nsetopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option + */ +#if defined(IP_MULTICAST_ALL) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_MULTICAST_ALL, eVal); +} +#endif + + +/* nsetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option + * + * The value is either the atom 'any' or a 4-tuple. + */ +#if defined(IP_MULTICAST_IF) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + struct in_addr ifAddr; + char* xres; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + if ((xres = esock_decode_ip4_address(env, eVal, &ifAddr)) != NULL) { + result = esock_make_error_str(env, xres); + } else { + + res = socket_setopt(descP->sock, level, IP_MULTICAST_LOOP, + &ifAddr, sizeof(ifAddr)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } + + return result; +} +#endif + + +/* nsetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option + */ +#if defined(IP_MULTICAST_LOOP) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP, eVal); +} +#endif + + +/* nsetopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option + */ +#if defined(IP_MULTICAST_TTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_MULTICAST_TTL, eVal); +} +#endif + + +/* nsetopt_lvl_ip_nodefrag - Level IP NODEFRAG option + */ +#if defined(IP_NODEFRAG) +static +ERL_NIF_TERM nsetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_NODEFRAG, eVal); +} +#endif + + +/* nsetopt_lvl_ip_pktinfo - Level IP PKTINFO option + */ +#if defined(IP_PKTINFO) +static +ERL_NIF_TERM nsetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_PKTINFO, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option + */ +#if defined(IP_RECVDSTADDR) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVDSTADDR, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recverr - Level IP RECVERR option + */ +#if defined(IP_RECVERR) +static +ERL_NIF_TERM nsetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVERR, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recvif - Level IP RECVIF option + */ +#if defined(IP_RECVIF) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVIF, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recvopts - Level IP RECVOPTS option + */ +#if defined(IP_RECVOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVOPTS, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option + */ +#if defined(IP_RECVORIGDSTADDR) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recvtos - Level IP RECVTOS option + */ +#if defined(IP_RECVTOS) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVTOS, eVal); +} +#endif + + +/* nsetopt_lvl_ip_recvttl - Level IP RECVTTL option + */ +#if defined(IP_RECVTTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RECVTTL, eVal); +} +#endif + + +/* nsetopt_lvl_ip_retopts - Level IP RETOPTS option + */ +#if defined(IP_RETOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_RETOPTS, eVal); +} +#endif + + +/* nsetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option + */ +#if defined(IP_ROUTER_ALERT) +static +ERL_NIF_TERM nsetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_ROUTER_ALERT, eVal); +} +#endif + + +/* nsetopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option + */ +#if defined(IP_SENDSRCADDR) +static +ERL_NIF_TERM nsetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_SENDSRCADDR, eVal); +} +#endif + + +/* nsetopt_lvl_ip_tos - Level IP TOS option + */ +#if defined(IP_TOS) +static +ERL_NIF_TERM nsetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + ERL_NIF_TERM result; + int val; + + if (decode_ip_tos(env, eVal, &val)) { + int res = socket_setopt(descP->sock, level, IP_TOS, &val, sizeof(val)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } else { + result = esock_make_error(env, esock_atom_einval); + } + + return result; +} +#endif + + +/* nsetopt_lvl_ip_transparent - Level IP TRANSPARENT option + */ +#if defined(IP_TRANSPARENT) +static +ERL_NIF_TERM nsetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_bool_opt(env, descP, level, IP_TRANSPARENT, eVal); +} +#endif + + + +/* nsetopt_lvl_ip_ttl - Level IP TTL option + */ +#if defined(IP_TTL) +static +ERL_NIF_TERM nsetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return nsetopt_int_opt(env, descP, level, IP_TTL, eVal); +} +#endif + + + +/* nsetopt_lvl_ip_unblock_source - Level IP UNBLOCK_SOURCE option + * + * The value is a map with three attributes: multiaddr, interface and + * sourceaddr. + * The attribute 'multiaddr' is always a 4-tuple (IPv4 address). + * The attribute 'interface' is always a 4-tuple (IPv4 address). + * The attribute 'sourceaddr' is always a 4-tuple (IPv4 address). + * (IPv4 address). + */ +#if defined(IP_UNBLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_unblock_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ip_update_source(env, descP, eVal, IP_UNBLOCK_SOURCE); +} +#endif + + + +#if defined(IP_ADD_MEMBERSHIP) || defined(IP_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface; + struct ip_mreq mreq; + char* xres; + int res; + size_t sz; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have atleast two attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip4_address(env, + eMultiAddr, + &mreq.imr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eInterface, + &mreq.imr_interface)) != NULL) + return esock_make_error_str(env, xres); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + +#if defined(IP_ADD_SOURCE_MEMBERSHIP) || defined(IP_DROP_SOURCE_MEMBERSHIP) || defined(IP_BLOCK_SOURCE) || defined(IP_UNBLOCK_SOURCE) +static +ERL_NIF_TERM nsetopt_lvl_ip_update_source(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface, eSourceAddr; + struct ip_mreq_source mreq; + char* xres; + int res; + size_t sz; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have atleast three attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 3)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_sourceaddr, &eSourceAddr)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip4_address(env, + eMultiAddr, + &mreq.imr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eInterface, + &mreq.imr_interface)) != NULL) + return esock_make_error_str(env, xres); + + if ((xres = esock_decode_ip4_address(env, + eSourceAddr, + &mreq.imr_sourceaddr)) != NULL) + return esock_make_error_str(env, xres); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + + +/* *** Handling set of socket options for level = ipv6 *** */ + +/* nsetopt_lvl_ipv6 - Level *IPv6* option(s) + */ +#if defined(HAVE_IPV6) +static +ERL_NIF_TERM nsetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6 -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(IPV6_ADDRFORM) + case SOCKET_OPT_IPV6_ADDRFORM: + result = nsetopt_lvl_ipv6_addrform(env, descP, eVal); + break; +#endif + +#if defined(IPV6_ADD_MEMBERSHIP) + case SOCKET_OPT_IPV6_ADD_MEMBERSHIP: + result = nsetopt_lvl_ipv6_add_membership(env, descP, eVal); + break; +#endif + +#if defined(IPV6_AUTHHDR) + case SOCKET_OPT_IPV6_AUTHHDR: + result = nsetopt_lvl_ipv6_authhdr(env, descP, eVal); + break; +#endif + +#if defined(IPV6_DROP_MEMBERSHIP) + case SOCKET_OPT_IPV6_DROP_MEMBERSHIP: + result = nsetopt_lvl_ipv6_drop_membership(env, descP, eVal); + break; +#endif + +#if defined(IPV6_DSTOPTS) + case SOCKET_OPT_IPV6_DSTOPTS: + result = nsetopt_lvl_ipv6_dstopts(env, descP, eVal); + break; +#endif + +#if defined(IPV6_FLOWINFO) + case SOCKET_OPT_IPV6_FLOWINFO: + result = nsetopt_lvl_ipv6_flowinfo(env, descP, eVal); + break; +#endif + +#if defined(IPV6_HOPLIMIT) + case SOCKET_OPT_IPV6_HOPLIMIT: + result = nsetopt_lvl_ipv6_hoplimit(env, descP, eVal); + break; +#endif + +#if defined(IPV6_HOPOPTS) + case SOCKET_OPT_IPV6_HOPOPTS: + result = nsetopt_lvl_ipv6_hopopts(env, descP, eVal); + break; +#endif + +#if defined(IPV6_MTU) + case SOCKET_OPT_IPV6_MTU: + result = nsetopt_lvl_ipv6_mtu(env, descP, eVal); + break; +#endif + +#if defined(IPV6_MTU_DISCOVER) + case SOCKET_OPT_IPV6_MTU_DISCOVER: + result = nsetopt_lvl_ipv6_mtu_discover(env, descP, eVal); + break; +#endif + +#if defined(IPV6_MULTICAST_HOPS) + case SOCKET_OPT_IPV6_MULTICAST_HOPS: + result = nsetopt_lvl_ipv6_multicast_hops(env, descP, eVal); + break; +#endif + +#if defined(IPV6_MULTICAST_IF) + case SOCKET_OPT_IPV6_MULTICAST_IF: + result = nsetopt_lvl_ipv6_multicast_if(env, descP, eVal); + break; +#endif + +#if defined(IPV6_MULTICAST_LOOP) + case SOCKET_OPT_IPV6_MULTICAST_LOOP: + result = nsetopt_lvl_ipv6_multicast_loop(env, descP, eVal); + break; +#endif + +#if defined(IPV6_RECVERR) + case SOCKET_OPT_IPV6_RECVERR: + result = nsetopt_lvl_ipv6_recverr(env, descP, eVal); + break; +#endif + +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) + case SOCKET_OPT_IPV6_RECVPKTINFO: + result = nsetopt_lvl_ipv6_recvpktinfo(env, descP, eVal); + break; +#endif + +#if defined(IPV6_ROUTER_ALERT) + case SOCKET_OPT_IPV6_ROUTER_ALERT: + result = nsetopt_lvl_ipv6_router_alert(env, descP, eVal); + break; +#endif + +#if defined(IPV6_RTHDR) + case SOCKET_OPT_IPV6_RTHDR: + result = nsetopt_lvl_ipv6_rthdr(env, descP, eVal); + break; +#endif + +#if defined(IPV6_UNICAST_HOPS) + case SOCKET_OPT_IPV6_UNICAST_HOPS: + result = nsetopt_lvl_ipv6_unicast_hops(env, descP, eVal); + break; +#endif + +#if defined(IPV6_V6ONLY) + case SOCKET_OPT_IPV6_V6ONLY: + result = nsetopt_lvl_ipv6_v6only(env, descP, eVal); + break; +#endif + + default: + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6 -> unknown opt (%d)\r\n", eOpt) ); + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6 -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +#if defined(IPV6_ADDRFORM) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_addrform(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int res, edomain, domain; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_addrform -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + if (!GET_INT(env, eVal, &edomain)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_addrform -> decode" + "\r\n edomain: %d" + "\r\n", edomain) ); + + if (!edomain2domain(edomain, &domain)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, ("SOCKET", "nsetopt_lvl_ipv6_addrform -> try set opt to %d\r\n", + domain) ); + + res = socket_setopt(descP->sock, + SOL_IPV6, IPV6_ADDRFORM, + &domain, sizeof(domain)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + +#if defined(IPV6_ADD_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_add_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ipv6_update_membership(env, descP, eVal, + IPV6_ADD_MEMBERSHIP); +} +#endif + + +#if defined(IPV6_AUTHHDR) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR, eVal); +} +#endif + + +#if defined(IPV6_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_drop_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_lvl_ipv6_update_membership(env, descP, eVal, + IPV6_DROP_MEMBERSHIP); +} +#endif + + +#if defined(IPV6_DSTOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_DSTOPTS, eVal); +} +#endif + + +#if defined(IPV6_FLOWINFO) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_FLOWINFO, eVal); +} +#endif + + +#if defined(IPV6_HOPLIMIT) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_HOPLIMIT, eVal); +} +#endif + + +#if defined(IPV6_HOPOPTS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_HOPOPTS, eVal); +} +#endif + + +#if defined(IPV6_MTU) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_MTU, eVal); +} +#endif + + +/* nsetopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option + * + * The value is an atom of the type ipv6_pmtudisc(). + */ +#if defined(IPV6_MTU_DISCOVER) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int val; + char* xres; + int res; + + if ((xres = decode_ipv6_pmtudisc(env, eVal, &val)) != NULL) { + + result = esock_make_error_str(env, xres); + + } else { +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + + res = socket_setopt(descP->sock, level, IPV6_MTU_DISCOVER, + &val, sizeof(val)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } + + return result; +} +#endif + + +#if defined(IPV6_MULTICAST_HOPS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS, eVal); +} +#endif + + + +#if defined(IPV6_MULTICAST_IF) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_MULTICAST_IF, eVal); +} +#endif + + + +#if defined(IPV6_MULTICAST_LOOP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP, eVal); +} +#endif + + +#if defined(IPV6_RECVERR) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_RECVERR, eVal); +} +#endif + + +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif +#if defined(IPV6_RECVPKTINFO) + int opt = IPV6_RECVPKTINFO; +#else + int opt = IPV6_PKTINFO; +#endif + + return nsetopt_bool_opt(env, descP, level, opt, eVal); +} +#endif + + +#if defined(IPV6_ROUTER_ALERT) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT, eVal); +} +#endif + + + +#if defined(IPV6_RTHDR) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_RTHDR, eVal); +} +#endif + + +#if defined(IPV6_UNICAST_HOPS) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS, eVal); +} +#endif + + + +#if defined(IPV6_V6ONLY) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return nsetopt_bool_opt(env, descP, level, IPV6_V6ONLY, eVal); +} +#endif + + +#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP) +static +ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal, + int opt) +{ + ERL_NIF_TERM result, eMultiAddr, eInterface; + struct ipv6_mreq mreq; + char* xres; + int res; + size_t sz; +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + // It must be a map + if (!IS_MAP(env, eVal)) + return enif_make_badarg(env); + + // It must have atleast two attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + return enif_make_badarg(env); + + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + return enif_make_badarg(env); + + if ((xres = esock_decode_ip6_address(env, + eMultiAddr, + &mreq.ipv6mr_multiaddr)) != NULL) + return esock_make_error_str(env, xres); + + if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface)) + return esock_make_error(env, esock_atom_einval); + + res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} +#endif + + + +#endif // defined(HAVE_IPV6) + + + +/* nsetopt_lvl_tcp - Level *TCP* option(s) + */ +static +ERL_NIF_TERM nsetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_tcp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(TCP_CONGESTION) + case SOCKET_OPT_TCP_CONGESTION: + result = nsetopt_lvl_tcp_congestion(env, descP, eVal); + break; +#endif + +#if defined(TCP_MAXSEG) + case SOCKET_OPT_TCP_MAXSEG: + result = nsetopt_lvl_tcp_maxseg(env, descP, eVal); + break; +#endif + +#if defined(TCP_NODELAY) + case SOCKET_OPT_TCP_NODELAY: + result = nsetopt_lvl_tcp_nodelay(env, descP, eVal); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} + + +/* nsetopt_lvl_tcp_congestion - Level TCP CONGESTION option + */ +#if defined(TCP_CONGESTION) +static +ERL_NIF_TERM nsetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + int max = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1; + + return nsetopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max, eVal); +} +#endif + + +/* nsetopt_lvl_tcp_maxseg - Level TCP MAXSEG option + */ +#if defined(TCP_MAXSEG) +static +ERL_NIF_TERM nsetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG, eVal); +} +#endif + + +/* nsetopt_lvl_tcp_nodelay - Level TCP NODELAY option + */ +#if defined(TCP_NODELAY) +static +ERL_NIF_TERM nsetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY, eVal); +} +#endif + + + +/* nsetopt_lvl_udp - Level *UDP* option(s) + */ +static +ERL_NIF_TERM nsetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_udp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(UDP_CORK) + case SOCKET_OPT_UDP_CORK: + result = nsetopt_lvl_udp_cork(env, descP, eVal); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} + + +/* nsetopt_lvl_udp_cork - Level UDP CORK option + */ +#if defined(UDP_CORK) +static +ERL_NIF_TERM nsetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK, eVal); +} +#endif + + + + +/* nsetopt_lvl_sctp - Level *SCTP* option(s) + */ +#if defined(HAVE_SCTP) +static +ERL_NIF_TERM nsetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(SCTP_ASSOCINFO) + case SOCKET_OPT_SCTP_ASSOCINFO: + result = nsetopt_lvl_sctp_associnfo(env, descP, eVal); + break; +#endif + +#if defined(SCTP_AUTOCLOSE) + case SOCKET_OPT_SCTP_AUTOCLOSE: + result = nsetopt_lvl_sctp_autoclose(env, descP, eVal); + break; +#endif + +#if defined(SCTP_DISABLE_FRAGMENTS) + case SOCKET_OPT_SCTP_DISABLE_FRAGMENTS: + result = nsetopt_lvl_sctp_disable_fragments(env, descP, eVal); + break; +#endif + +#if defined(SCTP_EVENTS) + case SOCKET_OPT_SCTP_EVENTS: + result = nsetopt_lvl_sctp_events(env, descP, eVal); + break; +#endif + +#if defined(SCTP_INITMSG) + case SOCKET_OPT_SCTP_INITMSG: + result = nsetopt_lvl_sctp_initmsg(env, descP, eVal); + break; +#endif + +#if defined(SCTP_MAXSEG) + case SOCKET_OPT_SCTP_MAXSEG: + result = nsetopt_lvl_sctp_maxseg(env, descP, eVal); + break; +#endif + +#if defined(SCTP_NODELAY) + case SOCKET_OPT_SCTP_NODELAY: + result = nsetopt_lvl_sctp_nodelay(env, descP, eVal); + break; +#endif + +#if defined(SCTP_RTOINFO) + case SOCKET_OPT_SCTP_RTOINFO: + result = nsetopt_lvl_sctp_rtoinfo(env, descP, eVal); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} + + +/* nsetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option + */ +#if defined(SCTP_ASSOCINFO) +static +ERL_NIF_TERM nsetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAssocId, eMaxRxt, eNumPeerDests; + ERL_NIF_TERM ePeerRWND, eLocalRWND, eCookieLife; + struct sctp_assocparams assocParams; + int res; + size_t sz; + unsigned int tmp; + Sint32 tmpAssocId; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 6)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_rxt, &eMaxRxt)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_num_peer_dests, &eNumPeerDests)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_peer_rwnd, &ePeerRWND)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_local_rwnd, &eLocalRWND)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_cookie_life, &eCookieLife)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> decode attributes\r\n") ); + + /* On some platforms the assoc id is typed as an unsigned integer (uint32) + * So, to avoid warnings there, we always make an explicit cast... + */ + if (!GET_INT(env, eAssocId, &tmpAssocId)) + return esock_make_error(env, esock_atom_einval); + assocParams.sasoc_assoc_id = (typeof(assocParams.sasoc_assoc_id)) tmpAssocId; + + /* + * We should really make sure this is ok in erlang (to ensure that + * the values (max-rxt and num-peer-dests) fits in 16-bits). + * The value should be a 16-bit unsigned int... + * Both sasoc_asocmaxrxt and sasoc_number_peer_destinations. + */ + + if (!GET_UINT(env, eMaxRxt, &tmp)) + return esock_make_error(env, esock_atom_einval); + assocParams.sasoc_asocmaxrxt = (Uint16) tmp; + + if (!GET_UINT(env, eNumPeerDests, &tmp)) + return esock_make_error(env, esock_atom_einval); + assocParams.sasoc_number_peer_destinations = (Uint16) tmp; + + if (!GET_UINT(env, ePeerRWND, &assocParams.sasoc_peer_rwnd)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eLocalRWND, &assocParams.sasoc_local_rwnd)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eCookieLife, &assocParams.sasoc_cookie_life)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> set associnfo option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, + &assocParams, sizeof(assocParams)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_associnfo -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + +/* nsetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option + */ +#if defined(SCTP_AUTOCLOSE) +static +ERL_NIF_TERM nsetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE, eVal); +} +#endif + + +/* nsetopt_lvl_sctp_disable_fragments - Level SCTP DISABLE_FRAGMENTS option + */ +#if defined(SCTP_DISABLE_FRAGMENTS) +static +ERL_NIF_TERM nsetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, eVal); +} +#endif + + +/* nsetopt_lvl_sctp_events - Level SCTP EVENTS option + */ +#if defined(SCTP_EVENTS) +static +ERL_NIF_TERM nsetopt_lvl_sctp_events(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eDataIn, eAssoc, eAddr, eSndFailure; + ERL_NIF_TERM ePeerError, eShutdown, ePartialDelivery; + ERL_NIF_TERM eAdaptLayer, eAuth; +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) + ERL_NIF_TERM eSndDry; +#endif + struct sctp_event_subscribe events; + int res; + size_t sz; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 10)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_data_in, &eDataIn)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_association, &eAssoc)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_address, &eAddr)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_send_failure, &eSndFailure)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_peer_error, &ePeerError)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_shutdown, &eShutdown)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_partial_delivery, &ePartialDelivery)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_adaptation_layer, &eAdaptLayer)) + return esock_make_error(env, esock_atom_einval); + +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT) + if (!GET_MAP_VAL(env, eVal, atom_authentication, &eAuth)) + return esock_make_error(env, esock_atom_einval); +#endif + +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) + if (!GET_MAP_VAL(env, eVal, atom_sender_dry, &eSndDry)) + return esock_make_error(env, esock_atom_einval); +#endif + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> decode attributes\r\n") ); + + events.sctp_data_io_event = esock_decode_bool(eDataIn); + events.sctp_association_event = esock_decode_bool(eAssoc); + events.sctp_address_event = esock_decode_bool(eAddr); + events.sctp_send_failure_event = esock_decode_bool(eSndFailure); + events.sctp_peer_error_event = esock_decode_bool(ePeerError); + events.sctp_shutdown_event = esock_decode_bool(eShutdown); + events.sctp_partial_delivery_event = esock_decode_bool(ePartialDelivery); + events.sctp_adaptation_layer_event = esock_decode_bool(eAdaptLayer); +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT) + events.sctp_authentication_event = esock_decode_bool(eAuth); +#endif +#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_SENDER_DRY_EVENT) + events.sctp_sender_dry_event = esock_decode_bool(eSndDry); +#endif + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> set events option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_EVENTS, + &events, sizeof(events)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_events -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + +/* nsetopt_lvl_sctp_initmsg - Level SCTP INITMSG option + */ +#if defined(SCTP_INITMSG) +static +ERL_NIF_TERM nsetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eNumOut, eMaxIn, eMaxAttempts, eMaxInitTO; + struct sctp_initmsg initMsg; + int res; + size_t sz; + unsigned int tmp; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 4)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_num_outstreams, &eNumOut)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_instreams, &eMaxIn)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_attempts, &eMaxAttempts)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max_init_timeo, &eMaxInitTO)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> decode attributes\r\n") ); + + if (!GET_UINT(env, eNumOut, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_num_ostreams = (Uint16) tmp; + + if (!GET_UINT(env, eMaxIn, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_max_instreams = (Uint16) tmp; + + if (!GET_UINT(env, eMaxAttempts, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_max_attempts = (Uint16) tmp; + + if (!GET_UINT(env, eMaxInitTO, &tmp)) + return esock_make_error(env, esock_atom_einval); + initMsg.sinit_max_init_timeo = (Uint16) tmp; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> set initmsg option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG, + &initMsg, sizeof(initMsg)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_initmsg -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + +/* nsetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option + */ +#if defined(SCTP_MAXSEG) +static +ERL_NIF_TERM nsetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG, eVal); +} +#endif + + +/* nsetopt_lvl_sctp_nodelay - Level SCTP NODELAY option + */ +#if defined(SCTP_NODELAY) +static +ERL_NIF_TERM nsetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + return nsetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY, eVal); +} +#endif + + +/* nsetopt_lvl_sctp_rtoinfo - Level SCTP RTOINFO option + */ +#if defined(SCTP_RTOINFO) +static +ERL_NIF_TERM nsetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAssocId, eInitial, eMax, eMin; + struct sctp_rtoinfo rtoInfo; + int res; + size_t sz; + Sint32 tmpAssocId; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + // It must be a map + if (!IS_MAP(env, eVal)) + return esock_make_error(env, esock_atom_einval); + + // It must have atleast ten attributes + if (!enif_get_map_size(env, eVal, &sz) || (sz < 4)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> extract attributes\r\n") ); + + if (!GET_MAP_VAL(env, eVal, atom_assoc_id, &eAssocId)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_initial, &eInitial)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_max, &eMax)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_MAP_VAL(env, eVal, atom_min, &eMin)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> decode attributes\r\n") ); + + /* On some platforms the assoc id is typed as an unsigned integer (uint32) + * So, to avoid warnings there, we always make an explicit cast... + */ + if (!GET_INT(env, eAssocId, &tmpAssocId)) + return esock_make_error(env, esock_atom_einval); + rtoInfo.srto_assoc_id = (typeof(rtoInfo.srto_assoc_id)) tmpAssocId; + + if (!GET_UINT(env, eInitial, &rtoInfo.srto_initial)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eMax, &rtoInfo.srto_max)) + return esock_make_error(env, esock_atom_einval); + + if (!GET_UINT(env, eMin, &rtoInfo.srto_min)) + return esock_make_error(env, esock_atom_einval); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> set associnfo option\r\n") ); + + res = socket_setopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO, + &rtoInfo, sizeof(rtoInfo)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_sctp_rtoinfo -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} +#endif + + + +#endif // defined(HAVE_SCTP) + + + + +/* nsetopt_bool_opt - set an option that has an (integer) bool value + */ +static +ERL_NIF_TERM nsetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + BOOLEAN_T val; + int ival, res; + + val = esock_decode_bool(eVal); + + ival = (val) ? 1 : 0; + res = socket_setopt(descP->sock, level, opt, &ival, sizeof(ival)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + return result; +} + + +/* nsetopt_int_opt - set an option that has an integer value + */ +static +ERL_NIF_TERM nsetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + int val; + + if (GET_INT(env, eVal, &val)) { + int res; + + /* + SSDBG( descP, + ("SOCKET", "nsetopt_int_opt -> set option" + "\r\n opt: %d" + "\r\n val: %d" + "\r\n", opt, val) ); + */ + + res = socket_setopt(descP->sock, level, opt, &val, sizeof(val)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } else { + result = esock_make_error(env, esock_atom_einval); + } + + return result; +} + + +/* nsetopt_str_opt - set an option that has an string value + */ +static +ERL_NIF_TERM nsetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + char* val = MALLOC(max); + + if (GET_STR(env, eVal, val, max) > 0) { + int optLen = strlen(val); + int res = socket_setopt(descP->sock, level, opt, &val, optLen); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + } else { + result = esock_make_error(env, esock_atom_einval); + } + + FREE(val); + + return result; +} + + +/* nsetopt_timeval_opt - set an option that has an (timeval) bool value + */ +static +ERL_NIF_TERM nsetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + ERL_NIF_TERM eVal) +{ + ERL_NIF_TERM result; + struct timeval timeVal; + int res; + char* xres; + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> entry with" + "\r\n eVal: %T" + "\r\n", eVal) ); + + if ((xres = esock_decode_timeval(env, eVal, &timeVal)) != NULL) + return esock_make_error_str(env, xres); + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> set timeval option\r\n") ); + + res = socket_setopt(descP->sock, level, opt, &timeVal, sizeof(timeVal)); + + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + + SSDBG( descP, + ("SOCKET", "nsetopt_timeval_opt -> done with" + "\r\n result: %T" + "\r\n", result) ); + + return result; + +} + + + +static +BOOLEAN_T elevel2level(BOOLEAN_T isEncoded, + int eLevel, + BOOLEAN_T* isOTP, + int* level) +{ + BOOLEAN_T result; + + if (isEncoded) { + switch (eLevel) { + case SOCKET_OPT_LEVEL_OTP: + *isOTP = TRUE; + *level = -1; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_SOCKET: + *isOTP = FALSE; + *level = SOL_SOCKET; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_IP: + *isOTP = FALSE; +#if defined(SOL_IP) + *level = SOL_IP; +#else + *level = IPPROTO_IP; +#endif + result = TRUE; + break; + +#if defined(HAVE_IPV6) + case SOCKET_OPT_LEVEL_IPV6: + *isOTP = FALSE; +#if defined(SOL_IPV6) + *level = SOL_IPV6; +#else + *level = IPPROTO_IPV6; +#endif + result = TRUE; + break; +#endif + + case SOCKET_OPT_LEVEL_TCP: + *isOTP = FALSE; + *level = IPPROTO_TCP; + result = TRUE; + break; + + case SOCKET_OPT_LEVEL_UDP: + *isOTP = FALSE; + *level = IPPROTO_UDP; + result = TRUE; + break; + +#ifdef HAVE_SCTP + case SOCKET_OPT_LEVEL_SCTP: + *isOTP = FALSE; + *level = IPPROTO_SCTP; + result = TRUE; + break; +#endif + + default: + *isOTP = FALSE; + *level = -1; + result = FALSE; + break; + } + } else { + *isOTP = FALSE; + *level = eLevel; + result = TRUE; + } + + return result; +} + + + +/* +++ socket_setopt +++ + * + * <Per H @ Tail-f> + * The original code here had problems that possibly + * only occur if you abuse it for non-INET sockets, but anyway: + * a) If the getsockopt for SO_PRIORITY or IP_TOS failed, the actual + * requested setsockopt was never even attempted. + * b) If {get,set}sockopt for one of IP_TOS and SO_PRIORITY failed, + * but ditto for the other worked and that was actually the requested + * option, failure was still reported to erlang. + * </Per H @ Tail-f> + * + * <PaN> + * The relations between SO_PRIORITY, TOS and other options + * is not what you (or at least I) would expect...: + * If TOS is set after priority, priority is zeroed. + * If any other option is set after tos, tos might be zeroed. + * Therefore, save tos and priority. If something else is set, + * restore both after setting, if tos is set, restore only + * prio and if prio is set restore none... All to keep the + * user feeling socket options are independent. + * </PaN> + */ +static +int socket_setopt(int sock, int level, int opt, + const void* optVal, const socklen_t optLen) +{ + int res; + +#if defined(IP_TOS) && defined(SOL_IP) && defined(SO_PRIORITY) + int tmpIValPRIO; + int tmpIValTOS; + int resPRIO; + int resTOS; + SOCKOPTLEN_T tmpArgSzPRIO = sizeof(tmpIValPRIO); + SOCKOPTLEN_T tmpArgSzTOS = sizeof(tmpIValTOS); + + resPRIO = sock_getopt(sock, SOL_SOCKET, SO_PRIORITY, + &tmpIValPRIO, &tmpArgSzPRIO); + resTOS = sock_getopt(sock, SOL_IP, IP_TOS, + &tmpIValTOS, &tmpArgSzTOS); + + res = sock_setopt(sock, level, opt, optVal, optLen); + if (res == 0) { + + /* Ok, now we *maybe* need to "maybe" restore PRIO and TOS... + * maybe, possibly, ... + */ + + if (opt != SO_PRIORITY) { + if ((opt != IP_TOS) && (resTOS == 0)) { + resTOS = sock_setopt(sock, SOL_IP, IP_TOS, + (void *) &tmpIValTOS, + tmpArgSzTOS); + res = resTOS; + } + if ((res == 0) && (resPRIO == 0)) { + resPRIO = sock_setopt(sock, SOL_SOCKET, SO_PRIORITY, + &tmpIValPRIO, + tmpArgSzPRIO); + + /* Some kernels set a SO_PRIORITY by default + * that you are not permitted to reset, + * silently ignore this error condition. + */ + + if ((resPRIO != 0) && (sock_errno() == EPERM)) { + res = 0; + } else { + res = resPRIO; + } + } + } + } + +#else + + res = sock_setopt(sock, level, opt, optVal, optLen); + +#endif + + return res; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_getopt + * + * Description: + * Get socket option. + * Its possible to use a "raw" mode (not encoded). That is, we do not + * interpret level and opt. They are passed "as is" to the + * getsockopt function call. The value in this case will "copied" as + * is and provided to the user in the form of a binary. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * IsEncoded - Are the "arguments" encoded or not. + * Level - Level of the socket option. + * Opt - The socket option. + */ + +static +ERL_NIF_TERM nif_getopt(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + int eLevel, level = -1; + ERL_NIF_TERM eIsEncoded, eOpt; + BOOLEAN_T isEncoded, isOTP; + + SGDBG( ("SOCKET", "nif_getopt -> entry with argc: %d\r\n", argc) ); + + if ((argc != 4) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP) || + !GET_INT(env, argv[2], &eLevel)) { + SGDBG( ("SOCKET", "nif_getopt -> failed processing args\r\n") ); + return enif_make_badarg(env); + } + eIsEncoded = argv[1]; + eOpt = argv[3]; // Is "normally" an int, but if raw mode: {Int, ValueSz} + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_getopt -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n eIsEncoded: %T" + "\r\n eLevel: %d" + "\r\n eOpt: %T" + "\r\n", descP->sock, argv[0], eIsEncoded, eLevel, eOpt) ); + + isEncoded = esock_decode_bool(eIsEncoded); + + if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) + return esock_make_error(env, esock_atom_einval); + + return ngetopt(env, descP, isEncoded, isOTP, level, eOpt); +#endif // if defined(__WIN32__) +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM ngetopt(ErlNifEnv* env, + SocketDescriptor* descP, + BOOLEAN_T isEncoded, + BOOLEAN_T isOTP, + int level, + ERL_NIF_TERM eOpt) +{ + ERL_NIF_TERM result; + int opt; + + SSDBG( descP, + ("SOCKET", "ngetopt -> entry with" + "\r\n isEncoded: %s" + "\r\n isOTP: %s" + "\r\n level: %d" + "\r\n eOpt: %T" + "\r\n", B2S(isEncoded), B2S(isOTP), level, eOpt) ); + + if (isOTP) { + /* These are not actual socket options, + * but options for our implementation. + */ + if (GET_INT(env, eOpt, &opt)) + result = ngetopt_otp(env, descP, opt); + else + result = esock_make_error(env, esock_atom_einval); + } else if (!isEncoded) { + result = ngetopt_native(env, descP, level, eOpt); + } else { + if (GET_INT(env, eOpt, &opt)) + result = ngetopt_level(env, descP, level, opt); + else + result = esock_make_error(env, esock_atom_einval); + } + + SSDBG( descP, + ("SOCKET", "ngetopt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + + +/* ngetopt_otp - Handle OTP (level) options + */ +static +ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "ngetopt_otp -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { + case SOCKET_OPT_OTP_DEBUG: + result = ngetopt_otp_debug(env, descP); + break; + + case SOCKET_OPT_OTP_IOW: + result = ngetopt_otp_iow(env, descP); + break; + + case SOCKET_OPT_OTP_CTRL_PROC: + result = ngetopt_otp_ctrl_proc(env, descP); + break; + + case SOCKET_OPT_OTP_RCVBUF: + result = ngetopt_otp_rcvbuf(env, descP); + break; + + case SOCKET_OPT_OTP_RCVCTRLBUF: + result = ngetopt_otp_rcvctrlbuf(env, descP); + break; + + case SOCKET_OPT_OTP_SNDCTRLBUF: + result = ngetopt_otp_sndctrlbuf(env, descP); + break; + + case SOCKET_OPT_OTP_FD: + result = ngetopt_otp_fd(env, descP); + break; + + /* *** INTERNAL *** */ + case SOCKET_OPT_OTP_DOMAIN: + result = ngetopt_otp_domain(env, descP); + break; + + case SOCKET_OPT_OTP_TYPE: + result = ngetopt_otp_type(env, descP); + break; + + case SOCKET_OPT_OTP_PROTOCOL: + result = ngetopt_otp_protocol(env, descP); + break; + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "ngetopt_otp -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +/* ngetopt_otp_debug - Handle the OTP (level) debug option + */ +static +ERL_NIF_TERM ngetopt_otp_debug(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = esock_encode_bool(descP->dbg); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_iow - Handle the OTP (level) iow option + */ +static +ERL_NIF_TERM ngetopt_otp_iow(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = esock_encode_bool(descP->iow); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_ctrl_proc - Handle the OTP (level) controlling_process option + */ +static +ERL_NIF_TERM ngetopt_otp_ctrl_proc(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKPID(env, &descP->ctrlPid); + + return esock_make_ok2(env, eVal); +} + + + +/* ngetopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option + */ +static +ERL_NIF_TERM ngetopt_otp_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal; + + if (descP->rNum == 0) { + eVal = MKI(env, descP->rBufSz); + } else { + eVal = MKT2(env, MKI(env, descP->rNum), MKI(env, descP->rBufSz)); + } + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option + */ +static +ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->rCtrlSz); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option + */ +static +ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->wCtrlSz); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_fd - Handle the OTP (level) fd option + */ +static +ERL_NIF_TERM ngetopt_otp_fd(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM eVal = MKI(env, descP->sock); + + return esock_make_ok2(env, eVal); +} + + +/* ngetopt_otp_domain - Handle the OTP (level) domain option + */ +static +ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->domain; + + switch (val) { + case AF_INET: + result = esock_make_ok2(env, esock_atom_inet); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + result = esock_make_ok2(env, esock_atom_inet6); + break; +#endif + +#if defined(HAVE_SYS_UN_H) + case AF_UNIX: + result = esock_make_ok2(env, esock_atom_local); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, + esock_atom_unknown, + MKI(env, val))); + break; + } + + return result; +} + + +/* ngetopt_otp_type - Handle the OTP (level) type options. + */ +static +ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->type; + + switch (val) { + case SOCK_STREAM: + result = esock_make_ok2(env, esock_atom_stream); + break; + + case SOCK_DGRAM: + result = esock_make_ok2(env, esock_atom_dgram); + break; + +#ifdef HAVE_SCTP + case SOCK_SEQPACKET: + result = esock_make_ok2(env, esock_atom_seqpacket); + break; +#endif + case SOCK_RAW: + result = esock_make_ok2(env, esock_atom_raw); + break; + + case SOCK_RDM: + result = esock_make_ok2(env, esock_atom_rdm); + break; + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + + return result; +} + + +/* ngetopt_otp_protocol - Handle the OTP (level) protocol options. + */ +static +ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->protocol; + + switch (val) { + case IPPROTO_IP: + result = esock_make_ok2(env, esock_atom_ip); + break; + + case IPPROTO_TCP: + result = esock_make_ok2(env, esock_atom_tcp); + break; + + case IPPROTO_UDP: + result = esock_make_ok2(env, esock_atom_udp); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = esock_make_ok2(env, esock_atom_sctp); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + + return result; +} + + + +/* The option has *not* been encoded. Instead it has been provided + * in "native mode" (option is provided as is). In this case it will have the + * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()} + */ +static +ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + ERL_NIF_TERM eOpt) +{ + ERL_NIF_TERM result = enif_make_badarg(env); + int opt; + Uint16 valueType; + SOCKOPTLEN_T valueSz; + + SSDBG( descP, + ("SOCKET", "ngetopt_native -> entry with" + "\r\n level: %d" + "\r\n eOpt: %T" + "\r\n", level, eOpt) ); + + /* <KOLLA> + * We should really make it possible to specify more common specific types, + * such as integer or boolean (instead of the size)... + * </KOLLA> + */ + + if (decode_native_get_opt(env, eOpt, &opt, &valueType, (int*) &valueSz)) { + + SSDBG( descP, + ("SOCKET", "ngetopt_native -> decoded opt" + "\r\n valueType: %d (%s)" + "\r\n ValueSize: %d" + "\r\n", valueType, VT2S(valueType), valueSz) ); + + switch (valueType) { + case SOCKET_OPT_VALUE_TYPE_UNSPEC: + result = ngetopt_native_unspec(env, descP, level, opt, valueSz); + break; + case SOCKET_OPT_VALUE_TYPE_INT: + result = ngetopt_int_opt(env, descP, level, opt); + break; + case SOCKET_OPT_VALUE_TYPE_BOOL: + result = ngetopt_bool_opt(env, descP, level, opt); + break; + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + } else { + result = esock_make_error(env, esock_atom_einval); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_native -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +static +ERL_NIF_TERM ngetopt_native_unspec(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + SOCKOPTLEN_T valueSz) +{ + ERL_NIF_TERM result = esock_make_error(env, esock_atom_einval); + int res; + + SSDBG( descP, + ("SOCKET", "ngetopt_native_unspec -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n valueSz: %d" + "\r\n", level, opt, valueSz) ); + + if (valueSz == 0) { + res = sock_getopt(descP->sock, level, opt, NULL, NULL); + if (res != 0) + result = esock_make_error_errno(env, sock_errno()); + else + result = esock_atom_ok; + } else { + SOCKOPTLEN_T vsz = valueSz; + ErlNifBinary val; + + SSDBG( descP, ("SOCKET", "ngetopt_native_unspec -> try alloc buffer\r\n") ); + + if (ALLOC_BIN(vsz, &val)) { + int saveErrno; + res = sock_getopt(descP->sock, level, opt, val.data, &vsz); + if (res != 0) { + saveErrno = sock_errno(); + + result = esock_make_error_errno(env, saveErrno); + } else { + + /* Did we use all of the buffer? */ + if (vsz == val.size) { + + result = esock_make_ok2(env, MKBIN(env, &val)); + + } else { + + ERL_NIF_TERM tmp; + + tmp = MKBIN(env, &val); + tmp = MKSBIN(env, tmp, 0, vsz); + + result = esock_make_ok2(env, tmp); + } + } + } else { + result = enif_make_badarg(env); + } + } + + SSDBG( descP, + ("SOCKET", "ngetopt_native_unspec -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + + +/* ngetopt_level - A "proper" level (option) has been specified + */ +static +ERL_NIF_TERM ngetopt_level(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int eOpt) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "ngetopt_level -> entry with" + "\r\n level: %d" + "\r\n eOpt: %d" + "\r\n", level, eOpt) ); + + switch (level) { + case SOL_SOCKET: + result = ngetopt_lvl_socket(env, descP, eOpt); + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + result = ngetopt_lvl_ip(env, descP, eOpt); + break; + +#if defined(HAVE_IPV6) +#if defined(SOL_IPV6) + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif + result = ngetopt_lvl_ipv6(env, descP, eOpt); + break; +#endif + + case IPPROTO_TCP: + result = ngetopt_lvl_tcp(env, descP, eOpt); + break; + + case IPPROTO_UDP: + result = ngetopt_lvl_udp(env, descP, eOpt); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = ngetopt_lvl_sctp(env, descP, eOpt); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "ngetopt_level -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +/* ngetopt_lvl_socket - Level *SOCKET* option + */ +static +ERL_NIF_TERM ngetopt_lvl_socket(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_socket -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(SO_ACCEPTCONN) + case SOCKET_OPT_SOCK_ACCEPTCONN: + result = ngetopt_lvl_sock_acceptconn(env, descP); + break; +#endif + +#if defined(SO_BINDTODEVICE) + case SOCKET_OPT_SOCK_BINDTODEVICE: + result = ngetopt_lvl_sock_bindtodevice(env, descP); + break; +#endif + +#if defined(SO_BROADCAST) + case SOCKET_OPT_SOCK_BROADCAST: + result = ngetopt_lvl_sock_broadcast(env, descP); + break; +#endif + +#if defined(SO_DEBUG) + case SOCKET_OPT_SOCK_DEBUG: + result = ngetopt_lvl_sock_debug(env, descP); + break; +#endif + +#if defined(SO_DOMAIN) + case SOCKET_OPT_SOCK_DOMAIN: + result = ngetopt_lvl_sock_domain(env, descP); + break; +#endif + +#if defined(SO_DONTROUTE) + case SOCKET_OPT_SOCK_DONTROUTE: + result = ngetopt_lvl_sock_dontroute(env, descP); + break; +#endif + +#if defined(SO_KEEPALIVE) + case SOCKET_OPT_SOCK_KEEPALIVE: + result = ngetopt_lvl_sock_keepalive(env, descP); + break; +#endif + +#if defined(SO_LINGER) + case SOCKET_OPT_SOCK_LINGER: + result = ngetopt_lvl_sock_linger(env, descP); + break; +#endif + +#if defined(SO_OOBINLINE) + case SOCKET_OPT_SOCK_OOBINLINE: + result = ngetopt_lvl_sock_oobinline(env, descP); + break; +#endif + +#if defined(SO_PEEK_OFF) + case SOCKET_OPT_SOCK_PEEK_OFF: + result = ngetopt_lvl_sock_peek_off(env, descP); + break; +#endif + +#if defined(SO_PRIORITY) + case SOCKET_OPT_SOCK_PRIORITY: + result = ngetopt_lvl_sock_priority(env, descP); + break; +#endif + +#if defined(SO_PROTOCOL) + case SOCKET_OPT_SOCK_PROTOCOL: + result = ngetopt_lvl_sock_protocol(env, descP); + break; +#endif + +#if defined(SO_RCVBUF) + case SOCKET_OPT_SOCK_RCVBUF: + result = ngetopt_lvl_sock_rcvbuf(env, descP); + break; +#endif + +#if defined(SO_RCVLOWAT) + case SOCKET_OPT_SOCK_RCVLOWAT: + result = ngetopt_lvl_sock_rcvlowat(env, descP); + break; +#endif + +#if defined(SO_RCVTIMEO) + case SOCKET_OPT_SOCK_RCVTIMEO: + result = ngetopt_lvl_sock_rcvtimeo(env, descP); + break; +#endif + +#if defined(SO_REUSEADDR) + case SOCKET_OPT_SOCK_REUSEADDR: + result = ngetopt_lvl_sock_reuseaddr(env, descP); + break; +#endif + +#if defined(SO_REUSEPORT) + case SOCKET_OPT_SOCK_REUSEPORT: + result = ngetopt_lvl_sock_reuseport(env, descP); + break; +#endif + +#if defined(SO_SNDBUF) + case SOCKET_OPT_SOCK_SNDBUF: + result = ngetopt_lvl_sock_sndbuf(env, descP); + break; +#endif + +#if defined(SO_SNDLOWAT) + case SOCKET_OPT_SOCK_SNDLOWAT: + result = ngetopt_lvl_sock_sndlowat(env, descP); + break; +#endif + +#if defined(SO_SNDTIMEO) + case SOCKET_OPT_SOCK_SNDTIMEO: + result = ngetopt_lvl_sock_sndtimeo(env, descP); + break; +#endif + +#if defined(SO_TIMESTAMP) + case SOCKET_OPT_SOCK_TIMESTAMP: + result = ngetopt_lvl_sock_timestamp(env, descP); + break; +#endif + +#if defined(SO_TYPE) + case SOCKET_OPT_SOCK_TYPE: + result = ngetopt_lvl_sock_type(env, descP); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_socket -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +#if defined(SO_ACCEPTCONN) +static +ERL_NIF_TERM ngetopt_lvl_sock_acceptconn(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_ACCEPTCONN); +} +#endif + + +#if defined(SO_BINDTODEVICE) +static +ERL_NIF_TERM ngetopt_lvl_sock_bindtodevice(ErlNifEnv* env, + SocketDescriptor* descP) +{ + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sock_bindtodevice -> entry with\r\n") ); + + return ngetopt_str_opt(env, descP, SOL_SOCKET, SO_BROADCAST, IFNAMSIZ+1); +} +#endif + + +#if defined(SO_BROADCAST) +static +ERL_NIF_TERM ngetopt_lvl_sock_broadcast(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST); +} +#endif + + +#if defined(SO_DEBUG) +static +ERL_NIF_TERM ngetopt_lvl_sock_debug(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG); +} +#endif + + +#if defined(SO_DOMAIN) +static +ERL_NIF_TERM ngetopt_lvl_sock_domain(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_DOMAIN, + &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + switch (val) { + case AF_INET: + result = esock_make_ok2(env, esock_atom_inet); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + result = esock_make_ok2(env, esock_atom_inet6); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + result = esock_make_ok2(env, esock_atom_local); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, + esock_atom_unknown, + MKI(env, val))); + break; + } + } + + return result; +} +#endif + + +#if defined(SO_DONTROUTE) +static +ERL_NIF_TERM ngetopt_lvl_sock_dontroute(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE); +} +#endif + + +#if defined(SO_KEEPALIVE) +static +ERL_NIF_TERM ngetopt_lvl_sock_keepalive(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE); +} +#endif + + +#if defined(SO_LINGER) +static +ERL_NIF_TERM ngetopt_lvl_sock_linger(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct linger val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + sys_memzero((void *) &val, sizeof(val)); + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_LINGER, + &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM lOnOff = ((val.l_onoff) ? atom_true : atom_false); + ERL_NIF_TERM lSecs = MKI(env, val.l_linger); + ERL_NIF_TERM linger = MKT2(env, lOnOff, lSecs); + + result = esock_make_ok2(env, linger); + } + + return result; +} +#endif + + +#if defined(SO_OOBINLINE) +static +ERL_NIF_TERM ngetopt_lvl_sock_oobinline(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE); +} +#endif + + +#if defined(SO_PEEK_OFF) +static +ERL_NIF_TERM ngetopt_lvl_sock_peek_off(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF); +} +#endif + + +#if defined(SO_PRIORITY) +static +ERL_NIF_TERM ngetopt_lvl_sock_priority(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY); +} +#endif + + +#if defined(SO_PROTOCOL) +static +ERL_NIF_TERM ngetopt_lvl_sock_protocol(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_PROTOCOL, + &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + switch (val) { + case IPPROTO_IP: + result = esock_make_ok2(env, esock_atom_ip); + break; + + case IPPROTO_TCP: + result = esock_make_ok2(env, esock_atom_tcp); + break; + + case IPPROTO_UDP: + result = esock_make_ok2(env, esock_atom_udp); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = esock_make_ok2(env, esock_atom_sctp); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + } + + return result; +} +#endif + + +#if defined(SO_RCVBUF) +static +ERL_NIF_TERM ngetopt_lvl_sock_rcvbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF); +} +#endif + + +#if defined(SO_RCVLOWAT) +static +ERL_NIF_TERM ngetopt_lvl_sock_rcvlowat(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT); +} +#endif + + +#if defined(SO_RCVTIMEO) +static +ERL_NIF_TERM ngetopt_lvl_sock_rcvtimeo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO); +} +#endif + + +#if defined(SO_REUSEADDR) +static +ERL_NIF_TERM ngetopt_lvl_sock_reuseaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR); +} +#endif + + +#if defined(SO_REUSEPORT) +static +ERL_NIF_TERM ngetopt_lvl_sock_reuseport(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT); +} +#endif + + +#if defined(SO_SNDBUF) +static +ERL_NIF_TERM ngetopt_lvl_sock_sndbuf(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF); +} +#endif + + +#if defined(SO_SNDLOWAT) +static +ERL_NIF_TERM ngetopt_lvl_sock_sndlowat(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT); +} +#endif + + +#if defined(SO_SNDTIMEO) +static +ERL_NIF_TERM ngetopt_lvl_sock_sndtimeo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO); +} +#endif + + +#if defined(SO_TIMESTAMP) +static +ERL_NIF_TERM ngetopt_lvl_sock_timestamp(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP); +} +#endif + + +#if defined(SO_TYPE) +static +ERL_NIF_TERM ngetopt_lvl_sock_type(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, SOL_SOCKET, SO_TYPE, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + switch (val) { + case SOCK_STREAM: + result = esock_make_ok2(env, esock_atom_stream); + break; + case SOCK_DGRAM: + result = esock_make_ok2(env, esock_atom_dgram); + break; +#ifdef HAVE_SCTP + case SOCK_SEQPACKET: + result = esock_make_ok2(env, esock_atom_seqpacket); + break; +#endif + case SOCK_RAW: + result = esock_make_ok2(env, esock_atom_raw); + break; + case SOCK_RDM: + result = esock_make_ok2(env, esock_atom_rdm); + break; + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + } + + return result; +} +#endif + + +/* ngetopt_lvl_ip - Level *IP* option(s) + */ +static +ERL_NIF_TERM ngetopt_lvl_ip(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ip -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(IP_FREEBIND) + case SOCKET_OPT_IP_FREEBIND: + result = ngetopt_lvl_ip_freebind(env, descP); + break; +#endif + +#if defined(IP_HDRINCL) + case SOCKET_OPT_IP_HDRINCL: + result = ngetopt_lvl_ip_hdrincl(env, descP); + break; +#endif + +#if defined(IP_MINTTL) + case SOCKET_OPT_IP_MINTTL: + result = ngetopt_lvl_ip_minttl(env, descP); + break; +#endif + +#if defined(IP_MTU) + case SOCKET_OPT_IP_MTU: + result = ngetopt_lvl_ip_mtu(env, descP); + break; +#endif + +#if defined(IP_MTU_DISCOVER) + case SOCKET_OPT_IP_MTU_DISCOVER: + result = ngetopt_lvl_ip_mtu_discover(env, descP); + break; +#endif + +#if defined(IP_MULTICAST_ALL) + case SOCKET_OPT_IP_MULTICAST_ALL: + result = ngetopt_lvl_ip_multicast_all(env, descP); + break; +#endif + +#if defined(IP_MULTICAST_IF) + case SOCKET_OPT_IP_MULTICAST_IF: + result = ngetopt_lvl_ip_multicast_if(env, descP); + break; +#endif + +#if defined(IP_MULTICAST_LOOP) + case SOCKET_OPT_IP_MULTICAST_LOOP: + result = ngetopt_lvl_ip_multicast_loop(env, descP); + break; +#endif + +#if defined(IP_MULTICAST_TTL) + case SOCKET_OPT_IP_MULTICAST_TTL: + result = ngetopt_lvl_ip_multicast_ttl(env, descP); + break; +#endif + +#if defined(IP_NODEFRAG) + case SOCKET_OPT_IP_NODEFRAG: + result = ngetopt_lvl_ip_nodefrag(env, descP); + break; +#endif + +#if defined(IP_PKTINFO) + case SOCKET_OPT_IP_PKTINFO: + result = ngetopt_lvl_ip_pktinfo(env, descP); + break; +#endif + +#if defined(IP_RECVDSTADDR) + case SOCKET_OPT_IP_RECVDSTADDR: + result = ngetopt_lvl_ip_recvdstaddr(env, descP); + break; +#endif + +#if defined(IP_RECVERR) + case SOCKET_OPT_IP_RECVERR: + result = ngetopt_lvl_ip_recverr(env, descP); + break; +#endif + +#if defined(IP_RECVIF) + case SOCKET_OPT_IP_RECVIF: + result = ngetopt_lvl_ip_recvif(env, descP); + break; +#endif + +#if defined(IP_RECVOPTS) + case SOCKET_OPT_IP_RECVOPTS: + result = ngetopt_lvl_ip_recvopts(env, descP); + break; +#endif + +#if defined(IP_RECVORIGDSTADDR) + case SOCKET_OPT_IP_RECVORIGDSTADDR: + result = ngetopt_lvl_ip_recvorigdstaddr(env, descP); + break; +#endif + +#if defined(IP_RECVTOS) + case SOCKET_OPT_IP_RECVTOS: + result = ngetopt_lvl_ip_recvtos(env, descP); + break; +#endif + +#if defined(IP_RECVTTL) + case SOCKET_OPT_IP_RECVTTL: + result = ngetopt_lvl_ip_recvttl(env, descP); + break; +#endif + +#if defined(IP_RETOPTS) + case SOCKET_OPT_IP_RETOPTS: + result = ngetopt_lvl_ip_retopts(env, descP); + break; +#endif + +#if defined(IP_ROUTER_ALERT) + case SOCKET_OPT_IP_ROUTER_ALERT: + result = ngetopt_lvl_ip_router_alert(env, descP); + break; +#endif + +#if defined(IP_SENDSRCADDR) + case SOCKET_OPT_IP_SENDSRCADDR: + result = ngetopt_lvl_ip_sendsrcaddr(env, descP); + break; +#endif + +#if defined(IP_TOS) + case SOCKET_OPT_IP_TOS: + result = ngetopt_lvl_ip_tos(env, descP); + break; +#endif + +#if defined(IP_TRANSPARENT) + case SOCKET_OPT_IP_TRANSPARENT: + result = ngetopt_lvl_ip_transparent(env, descP); + break; +#endif + +#if defined(IP_TTL) + case SOCKET_OPT_IP_TTL: + result = ngetopt_lvl_ip_ttl(env, descP); + break; +#endif + + default: + SSDBG( descP, ("SOCKET", "ngetopt_lvl_ip -> unknown opt %d\r\n", eOpt) ); + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ip -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +/* ngetopt_lvl_ip_minttl - Level IP MINTTL option + */ +#if defined(IP_MINTTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_minttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_MINTTL); +} +#endif + + +/* ngetopt_lvl_ip_freebind - Level IP FREEBIND option + */ +#if defined(IP_FREEBIND) +static +ERL_NIF_TERM ngetopt_lvl_ip_freebind(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_FREEBIND); +} +#endif + + +/* ngetopt_lvl_ip_hdrincl - Level IP HDRINCL option + */ +#if defined(IP_HDRINCL) +static +ERL_NIF_TERM ngetopt_lvl_ip_hdrincl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_HDRINCL); +} +#endif + + +/* ngetopt_lvl_ip_mtu - Level IP MTU option + */ +#if defined(IP_MTU) +static +ERL_NIF_TERM ngetopt_lvl_ip_mtu(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_MTU); +} +#endif + + +/* ngetopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option + */ +#if defined(IP_MTU_DISCOVER) +static +ERL_NIF_TERM ngetopt_lvl_ip_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eMtuDisc; + int mtuDisc; + SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc); + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + res = sock_getopt(descP->sock, level, IP_MTU_DISCOVER, + &mtuDisc, &mtuDiscSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + encode_ip_pmtudisc(env, mtuDisc, &eMtuDisc); + result = esock_make_ok2(env, eMtuDisc); + } + + return result; + +} +#endif + + +/* ngetopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option + */ +#if defined(IP_MULTICAST_ALL) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_all(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_MULTICAST_ALL); +} +#endif + + +/* ngetopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option + */ +#if defined(IP_MULTICAST_IF) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eAddr; + struct in_addr ifAddr; + SOCKOPTLEN_T ifAddrSz = sizeof(ifAddr); + char* xres; + int res; +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + res = sock_getopt(descP->sock, level, IP_MULTICAST_IF, &ifAddr, &ifAddrSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + if ((xres = esock_encode_ip4_address(env, &ifAddr, &eAddr)) != NULL) { + result = esock_make_error_str(env, xres); + } else { + result = esock_make_ok2(env, eAddr); + } + } + + return result; + +} +#endif + + +/* ngetopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option + */ +#if defined(IP_MULTICAST_LOOP) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP); +} +#endif + + +/* ngetopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option + */ +#if defined(IP_MULTICAST_TTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_multicast_ttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_MULTICAST_TTL); +} +#endif + + +/* ngetopt_lvl_ip_nodefrag - Level IP NODEFRAG option + */ +#if defined(IP_NODEFRAG) +static +ERL_NIF_TERM ngetopt_lvl_ip_nodefrag(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_NODEFRAG); +} +#endif + + +/* ngetopt_lvl_ip_pktinfo - Level IP PKTINFO option + */ +#if defined(IP_PKTINFO) +static +ERL_NIF_TERM ngetopt_lvl_ip_pktinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_PKTINFO); +} +#endif + + +/* ngetopt_lvl_ip_recvtos - Level IP RECVTOS option + */ +#if defined(IP_RECVTOS) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvtos(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVTOS); +} +#endif + + +/* ngetopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option + */ +#if defined(IP_RECVDSTADDR) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvdstaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVDSTADDR); +} +#endif + + +/* ngetopt_lvl_ip_recverr - Level IP RECVERR option + */ +#if defined(IP_RECVERR) +static +ERL_NIF_TERM ngetopt_lvl_ip_recverr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVERR); +} +#endif + + +/* ngetopt_lvl_ip_recvif - Level IP RECVIF option + */ +#if defined(IP_RECVIF) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvif(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVIF); +} +#endif + + +/* ngetopt_lvl_ip_recvopt - Level IP RECVOPTS option + */ +#if defined(IP_RECVOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVOPTS); +} +#endif + + +/* ngetopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option + */ +#if defined(IP_RECVORIGDSTADDR) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvorigdstaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR); +} +#endif + + +/* ngetopt_lvl_ip_recvttl - Level IP RECVTTL option + */ +#if defined(IP_RECVTTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_recvttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RECVTTL); +} +#endif + + +/* ngetopt_lvl_ip_retopts - Level IP RETOPTS option + */ +#if defined(IP_RETOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ip_retopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_RETOPTS); +} +#endif + + +/* ngetopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option + */ +#if defined(IP_ROUTER_ALERT) +static +ERL_NIF_TERM ngetopt_lvl_ip_router_alert(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_ROUTER_ALERT); +} +#endif + + +/* ngetopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option + */ +#if defined(IP_SENDSRCADDR) +static +ERL_NIF_TERM ngetopt_lvl_ip_sendsrcaddr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_SENDSRCADDR); +} +#endif + + +/* ngetopt_lvl_ip_tos - Level IP TOS option + */ +#if defined(IP_TOS) +static +ERL_NIF_TERM ngetopt_lvl_ip_tos(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, level, IP_TOS, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + result = encode_ip_tos(env, val); + } + + return result; +} +#endif + + +/* ngetopt_lvl_ip_transparent - Level IP TRANSPARENT option + */ +#if defined(IP_TRANSPARENT) +static +ERL_NIF_TERM ngetopt_lvl_ip_transparent(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_bool_opt(env, descP, level, IP_TRANSPARENT); +} +#endif + + + +/* ngetopt_lvl_ip_ttl - Level IP TTL option + */ +#if defined(IP_TTL) +static +ERL_NIF_TERM ngetopt_lvl_ip_ttl(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IP) + int level = SOL_IP; +#else + int level = IPPROTO_IP; +#endif + + return ngetopt_int_opt(env, descP, level, IP_TTL); +} +#endif + + + +/* ngetopt_lvl_ipv6 - Level *IPv6* option(s) + */ +#if defined(HAVE_IPV6) +static +ERL_NIF_TERM ngetopt_lvl_ipv6(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ipv6 -> entry with" + "\r\n eOpt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(IPV6_AUTHHDR) + case SOCKET_OPT_IPV6_AUTHHDR: + result = ngetopt_lvl_ipv6_authhdr(env, descP); + break; +#endif + +#if defined(IPV6_DSTOPTS) + case SOCKET_OPT_IPV6_DSTOPTS: + result = ngetopt_lvl_ipv6_dstopts(env, descP); + break; +#endif + +#if defined(IPV6_FLOWINFO) + case SOCKET_OPT_IPV6_FLOWINFO: + result = ngetopt_lvl_ipv6_flowinfo(env, descP); + break; +#endif + +#if defined(IPV6_HOPLIMIT) + case SOCKET_OPT_IPV6_HOPLIMIT: + result = ngetopt_lvl_ipv6_hoplimit(env, descP); + break; +#endif + +#if defined(IPV6_HOPOPTS) + case SOCKET_OPT_IPV6_HOPOPTS: + result = ngetopt_lvl_ipv6_hopopts(env, descP); + break; +#endif + +#if defined(IPV6_MTU) + case SOCKET_OPT_IPV6_MTU: + result = ngetopt_lvl_ipv6_mtu(env, descP); + break; +#endif + +#if defined(IPV6_MTU_DISCOVER) + case SOCKET_OPT_IPV6_MTU_DISCOVER: + result = ngetopt_lvl_ipv6_mtu_discover(env, descP); + break; +#endif + +#if defined(IPV6_MULTICAST_HOPS) + case SOCKET_OPT_IPV6_MULTICAST_HOPS: + result = ngetopt_lvl_ipv6_multicast_hops(env, descP); + break; +#endif + +#if defined(IPV6_MULTICAST_IF) + case SOCKET_OPT_IPV6_MULTICAST_IF: + result = ngetopt_lvl_ipv6_multicast_if(env, descP); + break; +#endif + +#if defined(IPV6_MULTICAST_LOOP) + case SOCKET_OPT_IPV6_MULTICAST_LOOP: + result = ngetopt_lvl_ipv6_multicast_loop(env, descP); + break; +#endif + +#if defined(IPV6_RECVERR) + case SOCKET_OPT_IPV6_RECVERR: + result = ngetopt_lvl_ipv6_recverr(env, descP); + break; +#endif + +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) + case SOCKET_OPT_IPV6_RECVPKTINFO: + result = ngetopt_lvl_ipv6_recvpktinfo(env, descP); + break; +#endif + +#if defined(IPV6_ROUTER_ALERT) + case SOCKET_OPT_IPV6_ROUTER_ALERT: + result = ngetopt_lvl_ipv6_router_alert(env, descP); + break; +#endif + +#if defined(IPV6_RTHDR) + case SOCKET_OPT_IPV6_RTHDR: + result = ngetopt_lvl_ipv6_rthdr(env, descP); + break; +#endif + +#if defined(IPV6_UNICAST_HOPS) + case SOCKET_OPT_IPV6_UNICAST_HOPS: + result = ngetopt_lvl_ipv6_unicast_hops(env, descP); + break; +#endif + +#if defined(IPV6_V6ONLY) + case SOCKET_OPT_IPV6_V6ONLY: + result = ngetopt_lvl_ipv6_v6only(env, descP); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_ipv6 -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +#if defined(IPV6_AUTHHDR) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_authhdr(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR); +} +#endif + + +#if defined(IPV6_DSTOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_dstopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + return ngetopt_bool_opt(env, descP, level, IPV6_DSTOPTS); +} +#endif + + +#if defined(IPV6_FLOWINFO) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_flowinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_FLOWINFO); +} +#endif + + +#if defined(IPV6_HOPLIMIT) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_hoplimit(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_HOPLIMIT); +} +#endif + + +#if defined(IPV6_HOPOPTS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_hopopts(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_HOPOPTS); +} +#endif + + +#if defined(IPV6_MTU) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_mtu(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_MTU); +} +#endif + + +/* ngetopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option + */ +#if defined(IPV6_MTU_DISCOVER) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_mtu_discover(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + ERL_NIF_TERM eMtuDisc; + int mtuDisc; + SOCKOPTLEN_T mtuDiscSz = sizeof(mtuDisc); + int res; +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + res = sock_getopt(descP->sock, level, IPV6_MTU_DISCOVER, + &mtuDisc, &mtuDiscSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + encode_ipv6_pmtudisc(env, mtuDisc, &eMtuDisc); + result = esock_make_ok2(env, eMtuDisc); + } + + return result; + +} +#endif + + +#if defined(IPV6_MULTICAST_HOPS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_hops(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS); +} +#endif + + +#if defined(IPV6_MULTICAST_IF) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_if(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_MULTICAST_IF); +} +#endif + + +#if defined(IPV6_MULTICAST_LOOP) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_multicast_loop(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP); +} +#endif + + +#if defined(IPV6_RECVERR) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_recverr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_RECVERR); +} +#endif + + +#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_recvpktinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif +#if defined(IPV6_RECVPKTINFO) + int opt = IPV6_RECVPKTINFO; +#else + int opt = IPV6_PKTINFO; +#endif + + return ngetopt_bool_opt(env, descP, level, opt); +} +#endif + + +#if defined(IPV6_ROUTER_ALERT) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_router_alert(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT); +} +#endif + + +#if defined(IPV6_RTHDR) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_rthdr(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_RTHDR); +} +#endif + + +#if defined(IPV6_UNICAST_HOPS) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_unicast_hops(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS); +} +#endif + + +#if defined(IPV6_V6ONLY) +static +ERL_NIF_TERM ngetopt_lvl_ipv6_v6only(ErlNifEnv* env, + SocketDescriptor* descP) +{ +#if defined(SOL_IPV6) + int level = SOL_IPV6; +#else + int level = IPPROTO_IPV6; +#endif + + return ngetopt_bool_opt(env, descP, level, IPV6_V6ONLY); +} +#endif + + +#endif // defined(HAVE_IPV6) + + + +/* ngetopt_lvl_tcp - Level *TCP* option(s) + */ +static +ERL_NIF_TERM ngetopt_lvl_tcp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(TCP_CONGESTION) + case SOCKET_OPT_TCP_CONGESTION: + result = ngetopt_lvl_tcp_congestion(env, descP); + break; +#endif + +#if defined(TCP_MAXSEG) + case SOCKET_OPT_TCP_MAXSEG: + result = ngetopt_lvl_tcp_maxseg(env, descP); + break; +#endif + +#if defined(TCP_NODELAY) + case SOCKET_OPT_TCP_NODELAY: + result = ngetopt_lvl_tcp_nodelay(env, descP); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_tcp_congestion - Level TCP CONGESTION option + */ +#if defined(TCP_CONGESTION) +static +ERL_NIF_TERM ngetopt_lvl_tcp_congestion(ErlNifEnv* env, + SocketDescriptor* descP) +{ + int max = SOCKET_OPT_TCP_CONGESTION_NAME_MAX+1; + + return ngetopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max); +} +#endif + + +/* ngetopt_lvl_tcp_maxseg - Level TCP MAXSEG option + */ +#if defined(TCP_MAXSEG) +static +ERL_NIF_TERM ngetopt_lvl_tcp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG); +} +#endif + + +/* ngetopt_lvl_tcp_nodelay - Level TCP NODELAY option + */ +#if defined(TCP_NODELAY) +static +ERL_NIF_TERM ngetopt_lvl_tcp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY); +} +#endif + + + +/* ngetopt_lvl_udp - Level *UDP* option(s) + */ +static +ERL_NIF_TERM ngetopt_lvl_udp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + switch (eOpt) { +#if defined(UDP_CORK) + case SOCKET_OPT_UDP_CORK: + result = ngetopt_lvl_udp_cork(env, descP); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + return result; +} + + +/* ngetopt_lvl_udp_cork - Level UDP CORK option + */ +#if defined(UDP_CORK) +static +ERL_NIF_TERM ngetopt_lvl_udp_cork(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK); +} +#endif + + + +/* ngetopt_lvl_sctp - Level *SCTP* option(s) + */ +#if defined(HAVE_SCTP) +static +ERL_NIF_TERM ngetopt_lvl_sctp(ErlNifEnv* env, + SocketDescriptor* descP, + int eOpt) +{ + ERL_NIF_TERM result; + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp -> entry with" + "\r\n opt: %d" + "\r\n", eOpt) ); + + switch (eOpt) { +#if defined(SCTP_ASSOCINFO) + case SOCKET_OPT_SCTP_ASSOCINFO: + result = ngetopt_lvl_sctp_associnfo(env, descP); + break; +#endif + +#if defined(SCTP_AUTOCLOSE) + case SOCKET_OPT_SCTP_AUTOCLOSE: + result = ngetopt_lvl_sctp_autoclose(env, descP); + break; +#endif + +#if defined(SCTP_DISABLE_FRAGMENTS) + case SOCKET_OPT_SCTP_DISABLE_FRAGMENTS: + result = ngetopt_lvl_sctp_disable_fragments(env, descP); + break; +#endif + +#if defined(SCTP_INITMSG) + case SOCKET_OPT_SCTP_INITMSG: + result = ngetopt_lvl_sctp_initmsg(env, descP); + break; +#endif + +#if defined(SCTP_MAXSEG) + case SOCKET_OPT_SCTP_MAXSEG: + result = ngetopt_lvl_sctp_maxseg(env, descP); + break; +#endif + +#if defined(SCTP_NODELAY) + case SOCKET_OPT_SCTP_NODELAY: + result = ngetopt_lvl_sctp_nodelay(env, descP); + break; +#endif + +#if defined(SCTP_RTOINFO) + case SOCKET_OPT_SCTP_RTOINFO: + result = ngetopt_lvl_sctp_rtoinfo(env, descP); + break; +#endif + + default: + result = esock_make_error(env, esock_atom_einval); + break; + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +/* ngetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option + * + * <KOLLA> + * + * We should really specify which association this relates to, + * as it is now we get assoc-id = 0. If this socket is an + * association (and not an endpoint) then it will have an + * assoc id. But since the sctp support at present is "limited", + * we leave it for now. + * What do we do if this is an endpoint? Invalid op? + * + * </KOLLA> + */ +#if defined(SCTP_ASSOCINFO) +static +ERL_NIF_TERM ngetopt_lvl_sctp_associnfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct sctp_assocparams val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_associnfo -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_ASSOCINFO, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eAssocParams; + ERL_NIF_TERM keys[] = {atom_assoc_id, atom_max_rxt, atom_num_peer_dests, + atom_peer_rwnd, atom_local_rwnd, atom_cookie_life}; + ERL_NIF_TERM vals[] = {MKUI(env, val.sasoc_assoc_id), + MKUI(env, val.sasoc_asocmaxrxt), + MKUI(env, val.sasoc_number_peer_destinations), + MKUI(env, val.sasoc_peer_rwnd), + MKUI(env, val.sasoc_local_rwnd), + MKUI(env, val.sasoc_cookie_life)}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eAssocParams)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eAssocParams); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp_associnfo -> done with" + "\r\n res: %d" + "\r\n result: %T" + "\r\n", res, result) ); + + return result; +} +#endif + + +/* ngetopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option + */ +#if defined(SCTP_AUTOCLOSE) +static +ERL_NIF_TERM ngetopt_lvl_sctp_autoclose(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE); +} +#endif + + +/* ngetopt_lvl_sctp_disable_fragments - Level SCTP DISABLE:FRAGMENTS option + */ +#if defined(SCTP_DISABLE_FRAGMENTS) +static +ERL_NIF_TERM ngetopt_lvl_sctp_disable_fragments(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS); +} +#endif + + +/* ngetopt_lvl_sctp_initmsg - Level SCTP INITMSG option + * + */ +#if defined(SCTP_INITMSG) +static +ERL_NIF_TERM ngetopt_lvl_sctp_initmsg(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct sctp_initmsg val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_initmsg -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_INITMSG, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eInitMsg; + ERL_NIF_TERM keys[] = {atom_num_outstreams, atom_max_instreams, + atom_max_attempts, atom_max_init_timeo}; + ERL_NIF_TERM vals[] = {MKUI(env, val.sinit_num_ostreams), + MKUI(env, val.sinit_max_instreams), + MKUI(env, val.sinit_max_attempts), + MKUI(env, val.sinit_max_init_timeo)}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eInitMsg)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eInitMsg); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp_initmsg -> done with" + "\r\n res: %d" + "\r\n result: %T" + "\r\n", res, result) ); + + return result; +} +#endif + + +/* ngetopt_lvl_sctp_maxseg - Level SCTP MAXSEG option + */ +#if defined(SCTP_MAXSEG) +static +ERL_NIF_TERM ngetopt_lvl_sctp_maxseg(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG); +} +#endif + + +/* ngetopt_lvl_sctp_nodelay - Level SCTP NODELAY option + */ +#if defined(SCTP_NODELAY) +static +ERL_NIF_TERM ngetopt_lvl_sctp_nodelay(ErlNifEnv* env, + SocketDescriptor* descP) +{ + return ngetopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY); +} +#endif + + +/* ngetopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option + * + * <KOLLA> + * + * We should really specify which association this relates to, + * as it is now we get assoc-id = 0. If this socket is an + * association (and not an endpoint) then it will have an + * assoc id (we can assume). But since the sctp support at + * present is "limited", we leave it for now. + * What do we do if this is an endpoint? Invalid op? + * + * </KOLLA> + */ +#if defined(SCTP_RTOINFO) +static +ERL_NIF_TERM ngetopt_lvl_sctp_rtoinfo(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + struct sctp_rtoinfo val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, ("SOCKET", "ngetopt_lvl_sctp_rtoinfo -> entry\r\n") ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, IPPROTO_SCTP, SCTP_RTOINFO, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eRTOInfo; + ERL_NIF_TERM keys[] = {atom_assoc_id, atom_initial, atom_max, atom_min}; + ERL_NIF_TERM vals[] = {MKUI(env, val.srto_assoc_id), + MKUI(env, val.srto_initial), + MKUI(env, val.srto_max), + MKUI(env, val.srto_min)}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &eRTOInfo)) + return esock_make_error(env, esock_atom_einval);; + + result = esock_make_ok2(env, eRTOInfo); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_lvl_sctp_rtoinfo -> done with" + "\r\n res: %d" + "\r\n result: %T" + "\r\n", res, result) ); + + return result; +} +#endif + + + +#endif // defined(HAVE_SCTP) + + + +/* ngetopt_bool_opt - get an (integer) bool option + */ +static +ERL_NIF_TERM ngetopt_bool_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + /* + SSDBG( descP, ("SOCKET", "ngetopt_bool_opt -> entry with" + "\r\n: level: %d" + "\r\n: opt: %d" + "\r\n", level, opt) ); + */ + + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM bval = ((val) ? atom_true : atom_false); + + result = esock_make_ok2(env, bval); + } + + /* + SSDBG( descP, ("SOCKET", "ngetopt_bool_opt -> done when" + "\r\n: res: %d" + "\r\n: result: %T" + "\r\n", res, result) ); + */ + + return result; +} + + +/* ngetopt_int_opt - get an integer option + */ +static +ERL_NIF_TERM ngetopt_int_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + int val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + result = esock_make_ok2(env, MKI(env, val)); + } + + return result; +} + + + +/* ngetopt_timeval_opt - get an timeval option + */ +static +ERL_NIF_TERM ngetopt_timeval_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt) +{ + ERL_NIF_TERM result; + struct timeval val; + SOCKOPTLEN_T valSz = sizeof(val); + int res; + + SSDBG( descP, + ("SOCKET", "ngetopt_timeval_opt -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n", level, opt) ); + + sys_memzero((char*) &val, valSz); + res = sock_getopt(descP->sock, level, opt, &val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM eTimeVal; + char* xres; + + if ((xres = esock_encode_timeval(env, &val, &eTimeVal)) != NULL) + result = esock_make_error_str(env, xres); + else + result = esock_make_ok2(env, eTimeVal); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_timeval_opt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + + +/* ngetopt_str_opt - get an string option + * + * We provide the max size of the string. This is the + * size of the buffer we allocate for the value. + * The actual size of the (read) value will be communicated + * in the optSz variable. + */ +static +ERL_NIF_TERM ngetopt_str_opt(ErlNifEnv* env, + SocketDescriptor* descP, + int level, + int opt, + int max) +{ + ERL_NIF_TERM result; + char* val = MALLOC(max); + SOCKOPTLEN_T valSz = max; + int res; + + SSDBG( descP, + ("SOCKET", "ngetopt_str_opt -> entry with" + "\r\n level: %d" + "\r\n opt: %d" + "\r\n max: %d" + "\r\n", level, opt, max) ); + + res = sock_getopt(descP->sock, level, opt, val, &valSz); + + if (res != 0) { + result = esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM sval = MKSL(env, val, valSz); + + result = esock_make_ok2(env, sval); + } + + SSDBG( descP, + ("SOCKET", "ngetopt_str_opt -> done when" + "\r\n result: %T" + "\r\n", result) ); + + FREE(val); + + return result; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_sockname - get socket name + * + * Description: + * Returns the current address to which the socket is bound. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_sockname(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_sockname -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_sockname -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n", descP->sock, argv[0]) ); + + res = nsockname(env, descP); + + SSDBG( descP, ("SOCKET", "nif_sockname -> done with res = %T\r\n", res) ); + + return res; +#endif // if defined(__WIN32__) +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM nsockname(ErlNifEnv* env, + SocketDescriptor* descP) +{ + SocketAddress sa; + SocketAddress* saP = &sa; + unsigned int sz = sizeof(SocketAddress); + + sys_memzero((char*) saP, sz); + if (IS_SOCKET_ERROR(sock_name(descP->sock, (struct sockaddr*) saP, &sz))) { + return esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM esa; + char* xres; + + if ((xres = esock_encode_sockaddr(env, saP, sz, &esa)) != NULL) + return esock_make_error_str(env, xres); + else + return esock_make_ok2(env, esa); + } +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_peername - get name of the connected peer socket + * + * Description: + * Returns the address of the peer connected to the socket. + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + */ + +static +ERL_NIF_TERM nif_peername(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM res; + + SGDBG( ("SOCKET", "nif_peername -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 1) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_peername -> args when sock = %d:" + "\r\n Socket: %T" + "\r\n", descP->sock, argv[0]) ); + + res = npeername(env, descP); + + SSDBG( descP, ("SOCKET", "nif_peername -> done with res = %T\r\n", res) ); + + return res; +#endif // if defined(__WIN32__) +} + + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM npeername(ErlNifEnv* env, + SocketDescriptor* descP) +{ + SocketAddress sa; + SocketAddress* saP = &sa; + unsigned int sz = sizeof(SocketAddress); + + sys_memzero((char*) saP, sz); + if (IS_SOCKET_ERROR(sock_peer(descP->sock, (struct sockaddr*) saP, &sz))) { + return esock_make_error_errno(env, sock_errno()); + } else { + ERL_NIF_TERM esa; + char* xres; + + if ((xres = esock_encode_sockaddr(env, saP, sz, &esa)) != NULL) + return esock_make_error_str(env, xres); + else + return esock_make_ok2(env, esa); + } +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * nif_cancel + * + * Description: + * Cancel a previous select! + * + * Arguments: + * Socket (ref) - Points to the socket descriptor. + * Operation (atom) - What kind of operation (accept, send, ...) is to be cancelled + * Ref (ref) - Unique id for the operation + */ +static +ERL_NIF_TERM nif_cancel(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "nosup")); +#else + SocketDescriptor* descP; + ERL_NIF_TERM op, opRef, result; + + SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) ); + + /* Extract arguments and perform preliminary validation */ + + if ((argc != 3) || + !enif_get_resource(env, argv[0], sockets, (void**) &descP)) { + return enif_make_badarg(env); + } + op = argv[1]; + opRef = argv[2]; + + if (IS_CLOSED(descP) || IS_CLOSING(descP)) + return esock_make_error(env, atom_closed); + + SSDBG( descP, + ("SOCKET", "nif_cancel -> args when sock = %d:" + "\r\n op: %T" + "\r\n opRef: %T" + "\r\n", descP->sock, op, opRef) ); + + result = ncancel(env, descP, op, opRef); + + SSDBG( descP, + ("SOCKET", "nif_cancel -> done with result: " + "\r\n %T" + "\r\n", result) ); + + return result; +#endif // if !defined(__WIN32__) +} + + +#if !defined(__WIN32__) +static +ERL_NIF_TERM ncancel(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM op, + ERL_NIF_TERM opRef) +{ + /* <KOLLA> + * + * Do we really need all these variants? Should it not be enough with: + * + * connect | accept | send | recv + * + * </KOLLA> + */ + if (COMPARE(op, esock_atom_connect) == 0) { + return ncancel_connect(env, descP, opRef); + } else if (COMPARE(op, esock_atom_accept) == 0) { + return ncancel_accept(env, descP, opRef); + } else if (COMPARE(op, esock_atom_send) == 0) { + return ncancel_send(env, descP, opRef); + } else if (COMPARE(op, esock_atom_sendto) == 0) { + return ncancel_send(env, descP, opRef); + } else if (COMPARE(op, esock_atom_sendmsg) == 0) { + return ncancel_send(env, descP, opRef); + } else if (COMPARE(op, esock_atom_recv) == 0) { + return ncancel_recv(env, descP, opRef); + } else if (COMPARE(op, esock_atom_recvfrom) == 0) { + return ncancel_recv(env, descP, opRef); + } else if (COMPARE(op, esock_atom_recvmsg) == 0) { + return ncancel_recv(env, descP, opRef); + } else { + return esock_make_error(env, esock_atom_einval); + } +} + + + +/* *** ncancel_connect *** + * + * + */ +static +ERL_NIF_TERM ncancel_connect(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return ncancel_write_select(env, descP, opRef); +} + + +/* *** ncancel_accept *** + * + * We have two different cases: + * *) Its the current acceptor + * Cancel the select! + * We need to activate one of the waiting acceptors. + * *) Its one of the acceptors ("waiting") in the queue + * Simply remove the acceptor from the queue. + * + */ +static +ERL_NIF_TERM ncancel_accept(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ERL_NIF_TERM res; + + SSDBG( descP, + ("SOCKET", "ncancel_accept -> entry with" + "\r\n opRef: %T" + "\r\n %s" + "\r\n", opRef, + ((descP->currentAcceptorP == NULL) ? "without acceptor" : "with acceptor")) ); + + MLOCK(descP->accMtx); + + if (descP->currentAcceptorP != NULL) { + if (COMPARE(opRef, descP->currentAcceptor.ref) == 0) { + res = ncancel_accept_current(env, descP); + } else { + res = ncancel_accept_waiting(env, descP, opRef); + } + } else { + /* Or badarg? */ + res = esock_make_error(env, esock_atom_einval); + } + + MUNLOCK(descP->accMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_accept -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* The current acceptor process has an ongoing select we first must + * cancel. Then we must re-activate the "first" (the first + * in the acceptor queue). + */ +static +ERL_NIF_TERM ncancel_accept_current(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM res; + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> entry\r\n") ); + + DEMONP("ncancel_accept_current -> current acceptor", + env, descP, &descP->currentAcceptor.mon); + res = ncancel_read_select(env, descP, descP->currentAcceptor.ref); + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> cancel res: %T\r\n", res) ); + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, &descP->currentAcceptor.pid, descP->currentAcceptor.ref); + + } else { + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> no more acceptors\r\n") ); + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + + SSDBG( descP, ("SOCKET", "ncancel_accept_current -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* These processes have not performed a select, so we can simply + * remove them from the acceptor queue. + */ +static +ERL_NIF_TERM ncancel_accept_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + /* unqueue request from (acceptor) queue */ + + if (acceptor_unqueue(env, descP, &caller)) { + return esock_atom_ok; + } else { + /* Race? */ + return esock_make_error(env, esock_atom_not_found); + } +} + + + +/* *** ncancel_send *** + * + * Cancel a send operation. + * Its either the current writer or one of the waiting writers. + */ +static +ERL_NIF_TERM ncancel_send(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ERL_NIF_TERM res; + + MLOCK(descP->writeMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_send -> entry with" + "\r\n opRef: %T" + "\r\n %s" + "\r\n", opRef, + ((descP->currentWriterP == NULL) ? "without writer" : "with writer")) ); + + if (descP->currentWriterP != NULL) { + if (COMPARE(opRef, descP->currentWriter.ref) == 0) { + res = ncancel_send_current(env, descP); + } else { + res = ncancel_send_waiting(env, descP, opRef); + } + } else { + /* Or badarg? */ + res = esock_make_error(env, esock_atom_einval); + } + + MUNLOCK(descP->writeMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_send -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + + +/* The current writer process has an ongoing select we first must + * cancel. Then we must re-activate the "first" (the first + * in the writer queue). + */ +static +ERL_NIF_TERM ncancel_send_current(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM res; + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> entry\r\n") ); + + DEMONP("ncancel_recv_current -> current writer", + env, descP, &descP->currentWriter.mon); + res = ncancel_write_select(env, descP, descP->currentWriter.ref); + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> cancel res: %T\r\n", res) ); + + if (writer_pop(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon, + &descP->currentWriter.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> new (active) writer: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentWriter.pid, + descP->currentWriter.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, &descP->currentWriter.pid, descP->currentWriter.ref); + + } else { + SSDBG( descP, ("SOCKET", "ncancel_send_current -> no more writers\r\n") ); + descP->currentWriterP = NULL; + } + + SSDBG( descP, ("SOCKET", "ncancel_send_current -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* These processes have not performed a select, so we can simply + * remove them from the writer queue. + */ +static +ERL_NIF_TERM ncancel_send_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + /* unqueue request from (writer) queue */ + + if (writer_unqueue(env, descP, &caller)) { + return esock_atom_ok; + } else { + /* Race? */ + return esock_make_error(env, esock_atom_not_found); + } +} + + + +/* *** ncancel_recv *** + * + * Cancel a read operation. + * Its either the current reader or one of the waiting readers. + */ +static +ERL_NIF_TERM ncancel_recv(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ERL_NIF_TERM res; + + MLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_recv -> entry with" + "\r\n opRef: %T" + "\r\n %s" + "\r\n", opRef, + ((descP->currentReaderP == NULL) ? "without reader" : "with reader")) ); + + if (descP->currentReaderP != NULL) { + if (COMPARE(opRef, descP->currentReader.ref) == 0) { + res = ncancel_recv_current(env, descP); + } else { + res = ncancel_recv_waiting(env, descP, opRef); + } + } else { + /* Or badarg? */ + res = esock_make_error(env, esock_atom_einval); + } + + MUNLOCK(descP->readMtx); + + SSDBG( descP, + ("SOCKET", "ncancel_recv -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* The current reader process has an ongoing select we first must + * cancel. Then we must re-activate the "first" (the first + * in the reader queue). + */ +static +ERL_NIF_TERM ncancel_recv_current(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM res; + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> entry\r\n") ); + + DEMONP("ncancel_recv_current -> current reader", + env, descP, &descP->currentReader.mon); + res = ncancel_read_select(env, descP, descP->currentReader.ref); + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> cancel res: %T\r\n", res) ); + + if (reader_pop(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon, + &descP->currentReader.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> new (active) reader: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentReader.pid, + descP->currentReader.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, &descP->currentReader.pid, descP->currentReader.ref); + + } else { + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> no more readers\r\n") ); + descP->currentReaderP = NULL; + } + + SSDBG( descP, ("SOCKET", "ncancel_recv_current -> done with result:" + "\r\n %T" + "\r\n", res) ); + + return res; +} + + +/* These processes have not performed a select, so we can simply + * remove them from the reader queue. + */ +static +ERL_NIF_TERM ncancel_recv_waiting(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + + /* unqueue request from (reader) queue */ + + if (reader_unqueue(env, descP, &caller)) { + return esock_atom_ok; + } else { + /* Race? */ + return esock_make_error(env, esock_atom_not_found); + } +} + + + +static +ERL_NIF_TERM ncancel_read_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return ncancel_mode_select(env, descP, opRef, + ERL_NIF_SELECT_READ, + ERL_NIF_SELECT_READ_CANCELLED); +} + + +static +ERL_NIF_TERM ncancel_write_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef) +{ + return ncancel_mode_select(env, descP, opRef, + ERL_NIF_SELECT_WRITE, + ERL_NIF_SELECT_WRITE_CANCELLED); +} + + +static +ERL_NIF_TERM ncancel_mode_select(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM opRef, + int smode, + int rmode) +{ + int selectRes = enif_select(env, descP->sock, + (ERL_NIF_SELECT_CANCEL | smode), + descP, NULL, opRef); + + if (selectRes & rmode) { + /* Was cancelled */ + return esock_atom_ok; + } else if (selectRes > 0) { + /* Has already sent the message */ + return esock_make_error(env, esock_atom_select_sent); + } else { + /* Stopped? */ + SSDBG( descP, ("SOCKET", "ncancel_mode_select -> failed: %d (0x%lX)" + "\r\n", selectRes, selectRes) ); + return esock_make_error(env, esock_atom_einval); + } + +} +#endif // if !defined(__WIN32__) + + + + +/* ---------------------------------------------------------------------- + * U t i l i t y F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* *** send_check_writer *** + * + * Checks if we have a current writer and if that is us. If not, then we must + * be made to wait for our turn. This is done by pushing us unto the writer queue. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T send_check_writer(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult) +{ + if (descP->currentWriterP != NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) { + *checkResult = esock_make_error(env, atom_exself); + return FALSE; + } + + if (!compare_pids(env, &descP->currentWriter.pid, &caller)) { + /* Not the "current writer", so (maybe) push onto queue */ + + SSDBG( descP, + ("SOCKET", "send_check_writer -> not (current) writer\r\n") ); + + if (!writer_search4pid(env, descP, &caller)) + *checkResult = writer_push(env, descP, caller, ref); + else + *checkResult = esock_make_error(env, esock_atom_eagain); + + SSDBG( descP, + ("SOCKET", + "send_check_writer -> queue (push) result: %T\r\n", + checkResult) ); + + return FALSE; + + } + + } + + *checkResult = esock_atom_ok; // Does not actually matter in this case, but ... + + return TRUE; +} + + + +/* *** send_check_result *** + * + * Check the result of a socket send (send, sendto and sendmsg) call. + * If a "complete" send has been made, the next (waiting) writer will be + * scheduled (if there is one). + * If we did not manage to send the entire package, make another select, + * so that we can be informed when we can make another try (to send the rest), + * and return with the amount we actually managed to send (its up to the caller + * (that is the erlang code) to figure out hust much is left to send). + * If the write fail, we give up and return with the appropriate error code. + * + * What about the remaining writers!! + */ +static +ERL_NIF_TERM send_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + ssize_t written, + ssize_t dataSize, + int saveErrno, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM sendRef) +{ + SSDBG( descP, + ("SOCKET", "send_check_result -> entry with" + "\r\n written: %d" + "\r\n dataSize: %d" + "\r\n saveErrno: %d" + "\r\n", written, dataSize, saveErrno) ); + + if (written >= dataSize) { + + cnt_inc(&descP->writePkgCnt, 1); + cnt_inc(&descP->writeByteCnt, written); + if (descP->currentWriterP != NULL) + DEMONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.mon); + + SSDBG( descP, + ("SOCKET", "send_check_result -> " + "everything written (%d,%d) - done\r\n", dataSize, written) ); + + /* Ok, this write is done maybe activate the next (if any) */ + + if (writer_pop(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon, + &descP->currentWriter.ref)) { + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "send_check_result -> new (active) writer: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentWriter.pid, + descP->currentWriter.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, &descP->currentWriter.pid, descP->currentWriter.ref); + + } else { + descP->currentWriterP = NULL; + } + + return esock_atom_ok; + + } else if (written < 0) { + + /* Some kind of send failure - check what kind */ + + if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) { + ErlNifPid pid; + // ErlNifMonitor mon; + ESockMonitor mon; + ERL_NIF_TERM ref, res; + + /* + * An actual failure - we (and everyone waiting) give up + */ + + cnt_inc(&descP->writeFails, 1); + + SSDBG( descP, + ("SOCKET", "send_check_result -> error: %d\r\n", saveErrno) ); + + res = esock_make_error_errno(env, saveErrno); + + if (descP->currentWriterP != NULL) { + DEMONP("send_check_result -> current writer", + env, descP, &descP->currentWriter.mon); + + while (writer_pop(env, descP, &pid, &mon, &ref)) { + SSDBG( descP, + ("SOCKET", "send_check_result -> abort %T\r\n", pid) ); + esock_send_abort_msg(env, sockRef, ref, res, &pid); + DEMONP("send_check_result -> pop'ed writer", + env, descP, &mon); + } + } + + return res; + + } else { + + /* Ok, try again later */ + + SSDBG( descP, ("SOCKET", "send_check_result -> try again\r\n") ); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); + + return esock_make_error(env, esock_atom_eagain); + + } + + } + + /* We failed to write the *entire* packet (anything less then size + * of the packet, which is 0 <= written < sizeof packet), + * so schedule the rest for later. + */ + + if (descP->currentWriterP == NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return esock_make_error(env, atom_exself); + descP->currentWriter.pid = caller; + if (MONP("send_check_result -> current writer", + env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon) != 0) + return esock_make_error(env, atom_exmon); + descP->currentWriter.ref = enif_make_copy(descP->env, sendRef); + descP->currentWriterP = &descP->currentWriter; + } + + cnt_inc(&descP->writeWaits, 1); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_WRITE), descP, NULL, sendRef); + + SSDBG( descP, + ("SOCKET", "send_check_result -> " + "not entire package written (%d of %d)\r\n", written, dataSize) ); + + return esock_make_ok2(env, MKI(env, written)); + +} + + + +/* *** recv_check_reader *** + * + * Checks if we have a current reader and if that is us. If not, then we must + * be made to wait for our turn. This is done by pushing us unto the reader queue. + */ +static +BOOLEAN_T recv_check_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM ref, + ERL_NIF_TERM* checkResult) +{ + if (descP->currentReaderP != NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) { + *checkResult = esock_make_error(env, atom_exself); + return FALSE; + } + + if (!compare_pids(env, &descP->currentReader.pid, &caller)) { + ERL_NIF_TERM tmp; + + /* Not the "current reader", so (maybe) push onto queue */ + + SSDBG( descP, + ("SOCKET", "recv_check_reader -> not (current) reader\r\n") ); + + if (!reader_search4pid(env, descP, &caller)) + tmp = reader_push(env, descP, caller, ref); + else + tmp = esock_make_error(env, esock_atom_eagain); + + SSDBG( descP, + ("SOCKET", + "recv_check_reader -> queue (push) result: %T\r\n", tmp) ); + + *checkResult = tmp; + + return FALSE; + + } + + } + + *checkResult = esock_atom_ok; // Does not actually matter in this case, but ... + + return TRUE; +} + + + +/* *** recv_init_current_reader *** + * + * Initiate (maybe) the currentReader structure of the descriptor. + * Including monitoring the calling process. + */ +static +char* recv_init_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM recvRef) +{ + if (descP->currentReaderP == NULL) { + ErlNifPid caller; + + if (enif_self(env, &caller) == NULL) + return str_exself; + + descP->currentReader.pid = caller; + if (MONP("recv_init_current_reader -> current reader", + env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon) != 0) { + return str_exmon; + } + descP->currentReader.ref = enif_make_copy(descP->env, recvRef); + descP->currentReaderP = &descP->currentReader; + } + + return NULL; +} + + + +/* *** recv_update_current_reader *** + * + * Demonitors the current reader process and pop's the reader queue. + * If there is a waiting (reader) process, then it will be assigned + * as the new current reader and a new (read) select will be done. + */ + +static +ERL_NIF_TERM recv_update_current_reader(ErlNifEnv* env, + SocketDescriptor* descP) +{ + if (descP->currentReaderP != NULL) { + + DEMONP("recv_update_current_reader -> current reader", + env, descP, &descP->currentReader.mon); + + if (reader_pop(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon, + &descP->currentReader.ref)) { + + /* There was another one */ + + SSDBG( descP, + ("SOCKET", "recv_update_current_reader -> new (active) reader: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentReader.pid, + descP->currentReader.ref) ); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentReader.pid, + descP->currentReader.ref); + + } else { + descP->currentReaderP = NULL; + } + } + + return esock_atom_ok; +} + + + +/* *** recv_error_current_reader *** + * + * Process the current reader and any waiting readers + * when a read (fatal) error has occured. + * All waiting readers will be "aborted", that is a + * nif_abort message will be sent (with reaf and reason). + */ +static +void recv_error_current_reader(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM reason) +{ + if (descP->currentReaderP != NULL) { + ErlNifPid pid; + // ErlNifMonitor mon; + ESockMonitor mon; + ERL_NIF_TERM ref; + + DEMONP("recv_error_current_reader -> current reader", + env, descP, &descP->currentReader.mon); + + while (reader_pop(env, descP, &pid, &mon, &ref)) { + SSDBG( descP, + ("SOCKET", "recv_error_current_reader -> abort %T\r\n", pid) ); + esock_send_abort_msg(env, sockRef, ref, reason, &pid); + DEMONP("recv_error_current_reader -> pop'ed reader", + env, descP, &mon); + } + } +} + + + +/* *** recv_check_result *** + * + * Process the result of a call to recv. + */ +static +ERL_NIF_TERM recv_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int toRead, + int saveErrno, + ErlNifBinary* bufP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef) +{ + char* xres; + ERL_NIF_TERM data; + + SSDBG( descP, + ("SOCKET", "recv_check_result -> entry with" + "\r\n read: %d" + "\r\n toRead: %d" + "\r\n saveErrno: %d" + "\r\n recvRef: %T" + "\r\n", read, toRead, saveErrno, recvRef) ); + + + /* <KOLLA> + * + * We need to handle read = 0 for other type(s) (DGRAM) when + * its actually valid to read 0 bytes. + * + * </KOLLA> + */ + + if ((read == 0) && (descP->type == SOCK_STREAM)) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); + + /* + * When a stream socket peer has performed an orderly shutdown, the return + * value will be 0 (the traditional "end-of-file" return). + * + * *We* do never actually try to read 0 bytes from a stream socket! + * + * We must also notify any waiting readers! + */ + + recv_error_current_reader(env, descP, sockRef, res); + + FREE_BIN(bufP); + + return res; + + } + + /* There is a special case: If the provided 'to read' value is + * zero (0) (only for type =/= stream). + * That means that we reads as much as we can, using the default + * read buffer size. + */ + + if (bufP->size == read) { + + /* +++ We filled the buffer +++ */ + + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] filled the buffer\r\n", toRead) ); + + if (toRead == 0) { + + /* +++ Give us everything you have got => * + * (maybe) needs to continue +++ */ + + /* How do we do this? + * Either: + * 1) Send up each chunk of data for each of the read + * and let the erlang code assemble it: {ok, false, Bin} + * (when complete it should return {ok, true, Bin}). + * We need to read atleast one more time to be sure if its + * done... + * 2) Or put it in a buffer here, and then let the erlang code + * know that it should call again (special return value) + * (continuous binary realloc "here"). + * + * => We choose alt 1 for now. + * + * Also, we need to check if the rNumCnt has reached its max (rNum), + * in which case we will assume the read to be done! + */ + + cnt_inc(&descP->readByteCnt, read); + + SSDBG( descP, + ("SOCKET", "recv_check_result -> shall we continue reading" + "\r\n read: %d" + "\r\n rNum: %d" + "\r\n rNumCnt: %d" + "\r\n", read, descP->rNum, descP->rNumCnt) ); + + if (descP->rNum > 0) { + + descP->rNumCnt++; + if (descP->rNumCnt >= descP->rNum) { + + descP->rNumCnt = 0; + + cnt_inc(&descP->readPkgCnt, 1); + + recv_update_current_reader(env, descP); + + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ + data = MKBIN(env, bufP); + + return esock_make_ok3(env, atom_true, data); + + } else { + + /* Yes, we *do* need to continue reading */ + + if ((xres = recv_init_current_reader(env, + descP, recvRef)) != NULL) { + descP->rNumCnt = 0; + return esock_make_error_str(env, xres); + } + + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ + data = MKBIN(env, bufP); + + return esock_make_ok3(env, atom_false, data); + + } + + } else { + + /* Yes, we *do* need to continue reading */ + + if ((xres = recv_init_current_reader(env, + descP, recvRef)) != NULL) { + descP->rNumCnt = 0; + return esock_make_error_str(env, xres); + } + + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ + data = MKBIN(env, bufP); + + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] " + "we are done for now - read more\r\n", toRead) ); + + return esock_make_ok3(env, atom_false, data); + } + + } else { + + /* +++ We got exactly as much as we requested => We are done +++ */ + + cnt_inc(&descP->readPkgCnt, 1); + cnt_inc(&descP->readByteCnt, read); + + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] " + "we got exactly what we could fit\r\n", toRead) ); + + recv_update_current_reader(env, descP); + + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ + data = MKBIN(env, bufP); + + return esock_make_ok3(env, atom_true, data); + + } + + } else if (read < 0) { + + /* +++ Error handling +++ */ + + if (saveErrno == ECONNRESET) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); + + /* +++ Oups - closed +++ */ + + SSDBG( descP, ("SOCKET", + "recv_check_result -> [%d] closed\r\n", toRead) ); + + /* <KOLLA> + * + * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING + * PROCESS, WE NEED TO INFORM IT!!! + * + * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * HANDLED BY THE STOP (CALLBACK) FUNCTION? + * + * SINCE THIS IS A REMOTE CLOSE, WE DON'T NEED TO WAIT + * FOR OUTPUT TO BE WRITTEN (NO ONE WILL READ), JUST + * ABORT THE SOCKET REGARDLESS OF LINGER??? + * + * </KOLLA> + */ + + descP->closeLocal = FALSE; + descP->state = SOCKET_STATE_CLOSING; + + recv_error_current_reader(env, descP, sockRef, res); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_STOP), + descP, NULL, recvRef); + + FREE_BIN(bufP); + + return res; + + } else if ((saveErrno == ERRNO_BLOCK) || + (saveErrno == EAGAIN)) { + int sres; + + SSDBG( descP, ("SOCKET", + "recv_check_result -> [%d] eagain\r\n", toRead) ); + + descP->rNumCnt = 0; + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + + SSDBG( descP, ("SOCKET", "recv_check_result -> SELECT for more\r\n") ); + + sres = enif_select(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + + SSDBG( descP, + ("SOCKET", "recv_check_result -> SELECT res: %d\r\n", sres) ); + + FREE_BIN(bufP); + + return esock_make_error(env, esock_atom_eagain); + } else { + ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); + + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] errno: %d\r\n", + toRead, saveErrno) ); + + recv_error_current_reader(env, descP, sockRef, res); + + FREE_BIN(bufP); + + return res; + } + + } else { + + /* +++ We did not fill the buffer +++ */ + + SSDBG( descP, + ("SOCKET", + "recv_check_result -> [%d] " + "did not fill the buffer (%d of %d)\r\n", + toRead, read, bufP->size) ); + + if (toRead == 0) { + + /* +++ We got it all, but since we +++ + * +++ did not fill the buffer, we +++ + * +++ must split it into a sub-binary. +++ + */ + + SSDBG( descP, ("SOCKET", + "recv_check_result -> [%d] split buffer\r\n", toRead) ); + + descP->rNumCnt = 0; + cnt_inc(&descP->readPkgCnt, 1); + cnt_inc(&descP->readByteCnt, read); + + recv_update_current_reader(env, descP); + + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ + data = MKBIN(env, bufP); + data = MKSBIN(env, data, 0, read); + + SSDBG( descP, + ("SOCKET", "recv_check_result -> [%d] done\r\n", toRead) ); + + return esock_make_ok3(env, atom_true, data); + + } else { + + /* +++ We got only a part of what was expected +++ + * +++ => select for more more later and +++ + * +++ deliver what we got. +++ */ + + SSDBG( descP, ("SOCKET", "recv_check_result -> [%d] " + "only part of message - expect more\r\n", toRead) ); + + /* SELECT for more data */ + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + + cnt_inc(&descP->readByteCnt, read); + + /* This transfers "ownership" of the *allocated* binary to an + * erlang term (no need for an explicit free). + */ + data = MKBIN(env, bufP); + data = MKSBIN(env, data, 0, read); + + return esock_make_ok3(env, atom_false, data); + } + } +} + + +/* The recvfrom function delivers one (1) message. If our buffer + * is to small, the message will be truncated. So, regardless + * if we filled the buffer or not, we have got what we are going + * to get regarding this message. + */ +static +ERL_NIF_TERM recvfrom_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int saveErrno, + ErlNifBinary* bufP, + SocketAddress* fromAddrP, + unsigned int fromAddrLen, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef) +{ + char* xres; + ERL_NIF_TERM data; + + SSDBG( descP, + ("SOCKET", "recvfrom_check_result -> entry with" + "\r\n read: %d" + "\r\n saveErrno: %d" + "\r\n recvRef: %T" + "\r\n", read, saveErrno, recvRef) ); + + + /* There is a special case: If the provided 'to read' value is + * zero (0). That means that we reads as much as we can, using + * the default read buffer size. + */ + + if (read < 0) { + + /* +++ Error handling +++ */ + + if (saveErrno == ECONNRESET) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); + + /* +++ Oups - closed +++ */ + + SSDBG( descP, ("SOCKET", "recvfrom_check_result -> closed\r\n") ); + + /* <KOLLA> + * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING + * PROCESS, WE NEED TO INFORM IT!!! + * + * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * + * </KOLLA> + */ + + descP->closeLocal = FALSE; + descP->state = SOCKET_STATE_CLOSING; + + recv_error_current_reader(env, descP, sockRef, res); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_STOP), + descP, NULL, recvRef); + + FREE_BIN(bufP); + + return res; + + } else if ((saveErrno == ERRNO_BLOCK) || + (saveErrno == EAGAIN)) { + + SSDBG( descP, ("SOCKET", "recvfrom_check_result -> eagain\r\n") ); + + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + + FREE_BIN(bufP); + + return esock_make_error(env, esock_atom_eagain); + + } else { + ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); + + SSDBG( descP, + ("SOCKET", + "recvfrom_check_result -> errno: %d\r\n", saveErrno) ); + + recv_error_current_reader(env, descP, sockRef, res); + + FREE_BIN(bufP); + + return res; + } + + } else { + + /* +++ We sucessfully got a message - time to encode the address +++ */ + + ERL_NIF_TERM eSockAddr; + + esock_encode_sockaddr(env, + fromAddrP, fromAddrLen, + &eSockAddr); + + if (read == bufP->size) { + data = MKBIN(env, bufP); + } else { + + /* +++ We got a chunk of data but +++ + * +++ since we did not fill the +++ + * +++ buffer, we must split it +++ + * +++ into a sub-binary. +++ + */ + + data = MKBIN(env, bufP); + data = MKSBIN(env, data, 0, read); + } + + recv_update_current_reader(env, descP); + + return esock_make_ok2(env, MKT2(env, eSockAddr, data)); + + } +} + + + +/* The recvmsg function delivers one (1) message. If our buffer + * is to small, the message will be truncated. So, regardless + * if we filled the buffer or not, we have got what we are going + * to get regarding this message. + */ +static +ERL_NIF_TERM recvmsg_check_result(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + int saveErrno, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef) +{ + + SSDBG( descP, + ("SOCKET", "recvmsg_check_result -> entry with" + "\r\n read: %d" + "\r\n saveErrno: %d" + "\r\n recvRef: %T" + "\r\n", read, saveErrno, recvRef) ); + + + /* <KOLLA> + * + * We need to handle read = 0 for other type(s) (DGRAM) when + * its actually valid to read 0 bytes. + * + * </KOLLA> + */ + + if ((read == 0) && (descP->type == SOCK_STREAM)) { + + /* + * When a stream socket peer has performed an orderly shutdown, the return + * value will be 0 (the traditional "end-of-file" return). + * + * *We* do never actually try to read 0 bytes from a stream socket! + */ + + + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + + return esock_make_error(env, atom_closed); + + } + + + /* There is a special case: If the provided 'to read' value is + * zero (0). That means that we reads as much as we can, using + * the default read buffer size. + */ + + if (read < 0) { + + /* +++ Error handling +++ */ + + if (saveErrno == ECONNRESET) { + ERL_NIF_TERM res = esock_make_error(env, atom_closed); + + /* +++ Oups - closed +++ */ + + SSDBG( descP, ("SOCKET", "recvmsg_check_result -> closed\r\n") ); + + /* <KOLLA> + * IF THE CURRENT PROCESS IS *NOT* THE CONTROLLING + * PROCESS, WE NEED TO INFORM IT!!! + * + * ALL WAITING PROCESSES MUST ALSO GET THE ERROR!! + * + * </KOLLA> + */ + + descP->closeLocal = FALSE; + descP->state = SOCKET_STATE_CLOSING; + + recv_error_current_reader(env, descP, sockRef, res); + + SELECT(env, + descP->sock, + (ERL_NIF_SELECT_STOP), + descP, NULL, recvRef); + + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + + return res; + + } else if ((saveErrno == ERRNO_BLOCK) || + (saveErrno == EAGAIN)) { + char* xres; + + SSDBG( descP, ("SOCKET", "recvmsg_check_result -> eagain\r\n") ); + + if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) + return esock_make_error_str(env, xres); + + SELECT(env, descP->sock, (ERL_NIF_SELECT_READ), + descP, NULL, recvRef); + + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + + return esock_make_error(env, esock_atom_eagain); + + } else { + ERL_NIF_TERM res = esock_make_error_errno(env, saveErrno); + + SSDBG( descP, + ("SOCKET", + "recvmsg_check_result -> errno: %d\r\n", saveErrno) ); + + recv_error_current_reader(env, descP, sockRef, res); + + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + + return res; + } + + } else { + + /* +++ We sucessfully got a message - time to encode it +++ */ + + ERL_NIF_TERM eMsgHdr; + char* xres; + + /* + * <KOLLA> + * + * The return value of recvmsg is the *total* number of bytes + * that where successfully read. This data has been put into + * the *IO vector*. + * + * </KOLLA> + */ + + if ((xres = encode_msghdr(env, descP, + read, msgHdrP, dataBufP, ctrlBufP, + &eMsgHdr)) != NULL) { + + SSDBG( descP, + ("SOCKET", + "recvmsg_check_result -> " + "(msghdr) encode failed: %s\r\n", xres) ); + + recv_update_current_reader(env, descP); + + FREE_BIN(dataBufP); FREE_BIN(ctrlBufP); + + return esock_make_error_str(env, xres); + } else { + + SSDBG( descP, + ("SOCKET", + "recvmsg_check_result -> " + "(msghdr) encode ok: %T\r\n", eMsgHdr) ); + + recv_update_current_reader(env, descP); + + return esock_make_ok2(env, eMsgHdr); + } + + } +} + + + + +/* +++ encode_msghdr +++ + * + * Encode a msghdr (recvmsg). In erlang its represented as + * a map, which has a specific set of attributes: + * + * addr (source address) - sockaddr() + * iov - [binary()] + * ctrl - [cmsghdr()] + * flags - msghdr_flags() + */ + +extern +char* encode_msghdr(ErlNifEnv* env, + SocketDescriptor* descP, + int read, + struct msghdr* msgHdrP, + ErlNifBinary* dataBufP, + ErlNifBinary* ctrlBufP, + ERL_NIF_TERM* eSockAddr) +{ + char* xres; + ERL_NIF_TERM addr, iov, ctrl, flags; + + SSDBG( descP, + ("SOCKET", "encode_msghdr -> entry with" + "\r\n read: %d" + "\r\n", read) ); + + /* The address is not used if we are connected, + * so check (length = 0) before we try to encodel + */ + if (msgHdrP->msg_namelen != 0) { + if ((xres = esock_encode_sockaddr(env, + (SocketAddress*) msgHdrP->msg_name, + msgHdrP->msg_namelen, + &addr)) != NULL) + return xres; + } else { + addr = esock_atom_undefined; + } + + SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode iov\r\n") ); + if ((xres = esock_encode_iov(env, + read, + msgHdrP->msg_iov, + msgHdrP->msg_iovlen, + dataBufP, + &iov)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode cmsghdrs\r\n") ); + if ((xres = encode_cmsghdrs(env, descP, ctrlBufP, msgHdrP, &ctrl)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> try encode flags\r\n") ); + if ((xres = encode_msghdr_flags(env, descP, msgHdrP->msg_flags, &flags)) != NULL) + return xres; + + SSDBG( descP, + ("SOCKET", "encode_msghdr -> components encoded:" + "\r\n addr: %T" + "\r\n iov: %T" + "\r\n ctrl: %T" + "\r\n flags: %T" + "\r\n", addr, iov, ctrl, flags) ); + { + ERL_NIF_TERM keys[] = {esock_atom_addr, + esock_atom_iov, + esock_atom_ctrl, + esock_atom_flags}; + ERL_NIF_TERM vals[] = {addr, iov, ctrl, flags}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM tmp; + + ESOCK_ASSERT( (numKeys == numVals) ); + + SSDBG( descP, ("SOCKET", "encode_msghdr -> create msghdr map\r\n") ); + if (!MKMA(env, keys, vals, numKeys, &tmp)) + return ESOCK_STR_EINVAL; + + SSDBG( descP, ("SOCKET", "encode_msghdr -> msghdr: " + "\r\n %T" + "\r\n", tmp) ); + + *eSockAddr = tmp; + } + + SSDBG( descP, ("SOCKET", "encode_msghdr -> done\r\n") ); + + return NULL; +} + + + + +/* +++ encode_cmsghdrs +++ + * + * Encode a list of cmsghdr(). There can be 0 or more cmsghdr "blocks". + * + * Our "problem" is that we have no idea how many control messages + * we have. + * + * The cmsgHdrP arguments points to the start of the control data buffer, + * an actual binary. Its the only way to create sub-binaries. So, what we + * need to continue processing this is to turn that into an binary erlang + * term (which can then in turn be turned into sub-binaries). + * + * We need the cmsgBufP (even though cmsgHdrP points to it) to be able + * to create sub-binaries (one for each cmsg hdr). + * + * The TArray (term array) is created with the size of 128, which should + * be enough. But if its not, then it will be automatically realloc'ed during + * add. Once we are done adding hdr's to it, we convert the tarray to a list. + */ + +extern +char* encode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifBinary* cmsgBinP, + struct msghdr* msgHdrP, + ERL_NIF_TERM* eCMsgHdr) +{ + ERL_NIF_TERM ctrlBuf = MKBIN(env, cmsgBinP); // The *entire* binary + SocketTArray cmsghdrs = TARRAY_CREATE(128); + struct cmsghdr* firstP = CMSG_FIRSTHDR(msgHdrP); + struct cmsghdr* currentP; + + SSDBG( descP, ("SOCKET", "encode_cmsghdrs -> entry\r\n") ); + + for (currentP = firstP; + currentP != NULL; + currentP = CMSG_NXTHDR(msgHdrP, currentP)) { + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> process cmsg header when" + "\r\n TArray Size: %d" + "\r\n", TARRAY_SZ(cmsghdrs)) ); + + /* MUST check this since on Linux the returned "cmsg" may actually + * go too far! + */ + if (((CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP)) > + msgHdrP->msg_controllen) { + /* Ouch, fatal error - give up + * We assume we cannot trust any data if this is wrong. + */ + TARRAY_DELETE(cmsghdrs); + return ESOCK_STR_EINVAL; + } else { + ERL_NIF_TERM level, type, data; + unsigned char* dataP = (unsigned char*) CMSG_DATA(currentP); + size_t dataPos = dataP - cmsgBinP->data; + size_t dataLen = currentP->cmsg_len - (CHARP(currentP)-CHARP(dataP)); + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> cmsg header data: " + "\r\n dataPos: %d" + "\r\n dataLen: %d" + "\r\n", dataPos, dataLen) ); + + /* We can't give up just because its an unknown protocol, + * so if its a protocol we don't know, we return its integer + * value and leave it to the user. + */ + if (encode_cmsghdr_level(env, currentP->cmsg_level, &level) != NULL) + level = MKI(env, currentP->cmsg_level); + + if (encode_cmsghdr_type(env, + currentP->cmsg_level, currentP->cmsg_type, + &type) != NULL) + type = MKI(env, currentP->cmsg_type); + + if (encode_cmsghdr_data(env, ctrlBuf, + currentP->cmsg_level, + currentP->cmsg_type, + dataP, dataPos, dataLen, + &data) != NULL) + data = MKSBIN(env, ctrlBuf, dataPos, dataLen); + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> " + "\r\n level: %T" + "\r\n type: %T" + "\r\n data: %T" + "\r\n", level, type, data) ); + + /* And finally create the 'cmsghdr' map - + * and if successfull add it to the tarray. + */ + { + ERL_NIF_TERM keys[] = {esock_atom_level, + esock_atom_type, + esock_atom_data}; + ERL_NIF_TERM vals[] = {level, type, data}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + ERL_NIF_TERM cmsgHdr; + + /* Guard agains cut-and-paste errors */ + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, &cmsgHdr)) { + TARRAY_DELETE(cmsghdrs); + return ESOCK_STR_EINVAL; + } + + /* And finally add it to the list... */ + TARRAY_ADD(cmsghdrs, cmsgHdr); + } + } + } + + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs -> cmsg headers processed when" + "\r\n TArray Size: %d" + "\r\n", TARRAY_SZ(cmsghdrs)) ); + + /* The tarray is populated - convert it to a list */ + TARRAY_TOLIST(cmsghdrs, env, eCMsgHdr); + + return NULL; +} + + + +/* +++ decode_cmsghdrs +++ + * + * Decode a list of cmsghdr(). There can be 0 or more cmsghdr "blocks". + * + * Each element can either be a (erlang) map that needs to be decoded, + * or a (erlang) binary that just needs to be appended to the control + * buffer. + * + * Our "problem" is that we have no idea much memory we actually need. + * + */ + +extern +char* decode_cmsghdrs(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + char* cmsgHdrBufP, + size_t cmsgHdrBufLen, + size_t* cmsgHdrBufUsed) +{ + ERL_NIF_TERM elem, tail, list; + char* bufP; + size_t rem, used, totUsed = 0; + unsigned int len; + int i; + char* xres; + + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> entry with" + "\r\n cmsgHdrBufP: 0x%lX" + "\r\n cmsgHdrBufLen: %d" + "\r\n", cmsgHdrBufP, cmsgHdrBufLen) ); + + if (IS_LIST(env, eCMsgHdr) && GET_LIST_LEN(env, eCMsgHdr, &len)) { + + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> list length: %d\r\n", len) ); + + for (i = 0, list = eCMsgHdr, rem = cmsgHdrBufLen, bufP = cmsgHdrBufP; + i < len; i++) { + + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> process elem %d:" + "\r\n (buffer) rem: %u" + "\r\n (buffer) totUsed: %u" + "\r\n", i, rem, totUsed) ); + + /* Extract the (current) head of the (cmsg hdr) list */ + if (!GET_LIST_ELEM(env, list, &elem, &tail)) + return ESOCK_STR_EINVAL; + + used = 0; // Just in case... + if ((xres = decode_cmsghdr(env, descP, elem, bufP, rem, &used)) != NULL) + return xres; + + bufP = CHARP( ULONG(bufP) + used ); + rem = SZT( rem - used ); + list = tail; + totUsed += used; + + } + + SSDBG( descP, ("SOCKET", + "decode_cmsghdrs -> all %d ctrl headers processed\r\n", + len) ); + + xres = NULL; + } else { + xres = ESOCK_STR_EINVAL; + } + + *cmsgHdrBufUsed = totUsed; + + SSDBG( descP, ("SOCKET", "decode_cmsghdrs -> done with %s when" + "\r\n totUsed = %u\r\n", + ((xres != NULL) ? xres : "NULL"), totUsed) ); + + return xres; +} + + +/* +++ decode_cmsghdr +++ + * + * Decode one cmsghdr(). Put the "result" into the buffer and advance the + * pointer (of the buffer) afterwards. Also update 'rem' accordingly. + * But before the actual decode, make sure that there is enough room in + * the buffer for the cmsg header (sizeof(*hdr) < rem). + * + * The eCMsgHdr should be a map with three fields: + * + * level :: cmsghdr_level() (socket | protocol() | integer()) + * type :: cmsghdr_type() (atom() | integer()) + * What values are valid depend on the level + * data :: cmsghdr_data() (term() | binary()) + * The type of the data depends on + * level and type, but can be a binary, + * which means that the data is already coded. + */ +extern +char* decode_cmsghdr(ErlNifEnv* env, + SocketDescriptor* descP, + ERL_NIF_TERM eCMsgHdr, + char* bufP, + size_t rem, + size_t* used) +{ + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> entry with" + "\r\n eCMsgHdr: %T" + "\r\n", eCMsgHdr) ); + + if (IS_MAP(env, eCMsgHdr)) { + ERL_NIF_TERM eLevel, eType, eData; + int level, type; + char* xres; + + /* First extract all three attributes (as terms) */ + + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_level, &eLevel)) + return ESOCK_STR_EINVAL; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eLevel: %T" + "\r\n", eLevel) ); + + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_type, &eType)) + return ESOCK_STR_EINVAL; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eType: %T" + "\r\n", eType) ); + + if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_data, &eData)) + return ESOCK_STR_EINVAL; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> eData: %T" + "\r\n", eData) ); + + /* Second, decode level */ + if ((xres = decode_cmsghdr_level(env, eLevel, &level)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> level: %d\r\n", level) ); + + /* third, decode type */ + if ((xres = decode_cmsghdr_type(env, level, eType, &type)) != NULL) + return xres; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr -> type: %d\r\n", type) ); + + /* And finally data + * If its a binary, we are done. Otherwise, we need to check + * level and type to know what kind of data to expect. + */ + + return decode_cmsghdr_data(env, descP, bufP, rem, level, type, eData, used); + + } else { + *used = 0; + return ESOCK_STR_EINVAL; + } + + return NULL; +} + + +/* *** decode_cmsghdr_data *** + * + * For all combinations of level and type we accept a binary as data, + * so we begin by testing for that. If its not a binary, then we check + * level (ip) and type (tos or ttl), in which case the data *must* be + * an integer and ip_tos() respectively. + */ +static +char* decode_cmsghdr_data(ErlNifEnv* env, + SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + ERL_NIF_TERM eData, + size_t* used) +{ + char* xres; + + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry with" + "\r\n eData: %T" + "\r\n", eData) ); + + if (IS_BIN(env, eData)) { + ErlNifBinary bin; + + if (GET_BIN(env, eData, &bin)) { + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> " + "do final decode with binary\r\n") ); + return decode_cmsghdr_final(descP, bufP, rem, level, type, + (char*) bin.data, bin.size, + used); + } else { + *used = 0; + xres = ESOCK_STR_EINVAL; + } + } else { + + /* Its *not* a binary so we need to look at what level and type + * we have and treat them individually. + */ + + switch (level) { +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + switch (type) { +#if defined(IP_TOS) + case IP_TOS: + { + int data; + if (decode_ip_tos(env, eData, &data)) { + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> " + "do final decode with tos\r\n") ); + return decode_cmsghdr_final(descP, bufP, rem, level, type, + (char*) &data, + sizeof(data), + used); + } else { + *used = 0; + xres = ESOCK_STR_EINVAL; + } + } + break; +#endif + +#if defined(IP_TTL) + case IP_TTL: + { + int data; + if (GET_INT(env, eData, &data)) { + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> " + "do final decode with ttl\r\n") ); + return decode_cmsghdr_final(descP, bufP, rem, level, type, + (char*) &data, + sizeof(data), + used); + } else { + *used = 0; + xres = ESOCK_STR_EINVAL; + } + } + break; +#endif + + } + break; + + default: + *used = 0; + xres = ESOCK_STR_EINVAL; + break; + } + + } + + return xres; +} + + +/* *** decode_cmsghdr_final *** + * + * This does the final create of the cmsghdr (including the data copy). + */ +static +char* decode_cmsghdr_final(SocketDescriptor* descP, + char* bufP, + size_t rem, + int level, + int type, + char* data, + int sz, + size_t* used) +{ + int len = CMSG_LEN(sz); + int space = CMSG_SPACE(sz); + + SSDBG( descP, ("SOCKET", "decode_cmsghdr_data -> entry when" + "\r\n level: %d" + "\r\n type: %d" + "\r\n sz: %d => %d, %d" + "\r\n", level, type, sz, len, space) ); + + if (rem >= space) { + struct cmsghdr* cmsgP = (struct cmsghdr*) bufP; + + /* The header */ + cmsgP->cmsg_len = len; + cmsgP->cmsg_level = level; + cmsgP->cmsg_type = type; + + sys_memcpy(CMSG_DATA(cmsgP), data, sz); + *used = space; + } else { + *used = 0; + return ESOCK_STR_EINVAL; + } + + SSDBG( descP, ("SOCKET", "decode_cmsghdr_final -> done\r\n") ); + + return NULL; +} + + +/* +++ encode_cmsghdr_level +++ + * + * Encode the level part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_level(ErlNifEnv* env, + int level, + ERL_NIF_TERM* eLevel) +{ + char* xres; + + switch (level) { + case SOL_SOCKET: + *eLevel = esock_atom_socket; + xres = NULL; + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + *eLevel = esock_atom_ip; + xres = NULL; + break; + +#if defined(HAVE_IPV6) +#if defined(SOL_IPV6) + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif + *eLevel = esock_atom_ip; + xres = NULL; + break; +#endif + + case IPPROTO_UDP: + *eLevel = esock_atom_udp; + xres = NULL; + break; + + default: + *eLevel = MKI(env, level); + xres = NULL; + break; + } + + return xres; +} + + + +/* +++ decode_cmsghdr_level +++ + * + * Decode the level part of the cmsghdr(). + * + */ + +static +char* decode_cmsghdr_level(ErlNifEnv* env, + ERL_NIF_TERM eLevel, + int* level) +{ + char* xres = NULL; + + if (IS_ATOM(env, eLevel)) { + + if (COMPARE(eLevel, esock_atom_socket) == 0) { + *level = SOL_SOCKET; + xres = NULL; + } else if (COMPARE(eLevel, esock_atom_ip) == 0) { +#if defined(SOL_IP) + *level = SOL_IP; +#else + *level = IPPROTO_IP; +#endif + xres = NULL; +#if defined(HAVE_IPV6) + } else if (COMPARE(eLevel, esock_atom_ipv6) == 0) { +#if defined(SOL_IPV6) + *level = SOL_IPV6; +#else + *level = IPPROTO_IPV6; +#endif + xres = NULL; +#endif + } else if (COMPARE(eLevel, esock_atom_udp) == 0) { + *level = IPPROTO_UDP; + xres = NULL; + } else { + *level = -1; + xres = ESOCK_STR_EINVAL; + } + } else if (IS_NUM(env, eLevel)) { + if (!GET_INT(env, eLevel, level)) + xres = ESOCK_STR_EINVAL; + } else { + *level = -1; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} + + + +/* +++ encode_cmsghdr_type +++ + * + * Encode the type part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_type(ErlNifEnv* env, + int level, + int type, + ERL_NIF_TERM* eType) +{ + char* xres = NULL; + + switch (level) { + case SOL_SOCKET: + switch (type) { +#if defined(SO_TIMESTAMP) + case SO_TIMESTAMP: + *eType = esock_atom_timestamp; + break; +#endif + +#if defined(SCM_RIGHTS) + case SCM_RIGHTS: + *eType = esock_atom_rights; + break; +#endif + +#if defined(SCM_CREDENTIALS) + case SCM_CREDENTIALS: + *eType = esock_atom_credentials; + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + switch (type) { +#if defined(IP_TOS) + case IP_TOS: + *eType = esock_atom_tos; + break; +#endif + +#if defined(IP_TTL) + case IP_TTL: + *eType = esock_atom_ttl; + break; +#endif + +#if defined(IP_PKTINFO) + case IP_PKTINFO: + *eType = esock_atom_pktinfo; + break; +#endif + +#if defined(IP_ORIGDSTADDR) + case IP_ORIGDSTADDR: + *eType = esock_atom_origdstaddr; + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + +#if defined(HAVE_IPV6) +#if defined(SOL_IPV6) + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif + switch (type) { +#if defined(IPV6_PKTINFO) + case IPV6_PKTINFO: + *eType = esock_atom_pktinfo; + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; +#endif + + case IPPROTO_TCP: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + + case IPPROTO_UDP: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + switch (type) { + default: + xres = ESOCK_STR_EINVAL; + break; + } + break; +#endif + + default: + xres = ESOCK_STR_EINVAL; + break; + } + + return xres; +} + + + +/* +++ decode_cmsghdr_type +++ + * + * Decode the type part of the cmsghdr(). + * + */ + +static +char* decode_cmsghdr_type(ErlNifEnv* env, + int level, + ERL_NIF_TERM eType, + int* type) +{ + char* xres = NULL; + + switch (level) { + case SOL_SOCKET: + if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + } else { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + break; + + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + if (IS_ATOM(env, eType)) { + if (COMPARE(eType, esock_atom_tos) == 0) { +#if defined(IP_TOS) + *type = IP_TOS; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else if (COMPARE(eType, esock_atom_ttl) == 0) { +#if defined(IP_TTL) + *type = IP_TTL; +#else + xres = ESOCK_STR_EINVAL; +#endif + } else { + xres = ESOCK_STR_EINVAL; + } + } else if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + } else { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + break; + +#if defined(HAVE_IPV6) +#if defined(SOL_IPV6) + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif + if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + } else { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + break; +#endif + + case IPPROTO_UDP: + if (IS_NUM(env, eType)) { + if (!GET_INT(env, eType, type)) { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + } else { + *type = -1; + xres = ESOCK_STR_EINVAL; + } + break; + + default: + *type = -1; + xres = ESOCK_STR_EINVAL; + break; + } + + return xres; +} + + + +/* +++ encode_cmsghdr_data +++ + * + * Encode the data part of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int level, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + char* xres; + + switch (level) { +#if defined(SOL_SOCKET) + case SOL_SOCKET: + xres = encode_cmsghdr_data_socket(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; +#endif + +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + xres = encode_cmsghdr_data_ip(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; + +#if defined(HAVE_IPV6) +#if defined(SOL_IPV6) + case SOL_IPV6: +#else + case IPPROTO_IPV6: +#endif + xres = encode_cmsghdr_data_ipv6(env, ctrlBuf, type, + dataP, dataPos, dataLen, + eCMsgHdrData); + break; +#endif + + /* + case IPPROTO_TCP: + xres = encode_cmsghdr_data_tcp(env, type, dataP, eCMsgHdrData); + break; + */ + + /* + case IPPROTO_UDP: + xres = encode_cmsghdr_data_udp(env, type, dataP, eCMsgHdrData); + break; + */ + + /* + #if defined(HAVE_SCTP) + case IPPROTO_SCTP: + xres = encode_cmsghdr_data_sctp(env, type, dataP, eCMsgHdrData); + break; + #endif + */ + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + xres = NULL; + break; + } + + return xres; +} + + + +/* +++ encode_cmsghdr_data_socket +++ + * + * Encode the data part when "protocol" = socket of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data_socket(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + // char* xres; + + switch (type) { +#if defined(SO_TIMESTAMP) + case SO_TIMESTAMP: + { + struct timeval* timeP = (struct timeval*) dataP; + + if (esock_encode_timeval(env, timeP, eCMsgHdrData) != NULL) + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return NULL; +} + + + +/* +++ encode_cmsghdr_data_ip +++ + * + * Encode the data part when protocol = IP of the cmsghdr(). + * + */ + +static +char* encode_cmsghdr_data_ip(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + char* xres = NULL; + + switch (type) { +#if defined(IP_TOS) + case IP_TOS: + { + unsigned char tos = *dataP; + switch (IPTOS_TOS(tos)) { + case IPTOS_LOWDELAY: + *eCMsgHdrData = esock_atom_lowdelay; + break; + case IPTOS_THROUGHPUT: + *eCMsgHdrData = esock_atom_throughput; + break; + case IPTOS_RELIABILITY: + *eCMsgHdrData = esock_atom_reliability; + break; +#if defined(IPTOS_MINCOST) + case IPTOS_MINCOST: + *eCMsgHdrData = esock_atom_mincost; + break; +#endif + default: + *eCMsgHdrData = MKUI(env, tos); + break; + } + } + break; +#endif + +#if defined(IP_TTL) + case IP_TTL: + { + int ttl = *((int*) dataP); + *eCMsgHdrData = MKI(env, ttl); + } + break; +#endif + +#if defined(IP_PKTINFO) + case IP_PKTINFO: + { + struct in_pktinfo* pktInfoP = (struct in_pktinfo*) dataP; + ERL_NIF_TERM ifIndex = MKUI(env, pktInfoP->ipi_ifindex); + ERL_NIF_TERM specDst, addr; + + if ((xres = esock_encode_ip4_address(env, + &pktInfoP->ipi_spec_dst, + &specDst)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + + if ((xres = esock_encode_ip4_address(env, + &pktInfoP->ipi_addr, + &addr)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + + + { + ERL_NIF_TERM keys[] = {esock_atom_ifindex, + esock_atom_spec_dst, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {ifIndex, specDst, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eCMsgHdrData)) { + *eCMsgHdrData = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } + } + } + break; +#endif + +#if defined(IP_ORIGDSTADDR) + case IP_ORIGDSTADDR: + if ((xres = esock_encode_sockaddr_in4(env, + (struct sockaddr_in*) dataP, + dataLen, + eCMsgHdrData)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return xres; +} + + + +/* +++ encode_cmsghdr_data_ipv6 +++ + * + * Encode the data part when protocol = IPv6 of the cmsghdr(). + * + */ +#if defined(HAVE_IPV6) +static +char* encode_cmsghdr_data_ipv6(ErlNifEnv* env, + ERL_NIF_TERM ctrlBuf, + int type, + unsigned char* dataP, + size_t dataPos, + size_t dataLen, + ERL_NIF_TERM* eCMsgHdrData) +{ + char* xres; + + switch (type) { +#if defined(IPV6_PKTINFO) + case IPV6_PKTINFO: + { + struct in6_pktinfo* pktInfoP = (struct in6_pktinfo*) dataP; + ERL_NIF_TERM ifIndex = MKI(env, pktInfoP->ipi6_ifindex); + ERL_NIF_TERM addr; + + if ((xres = esock_encode_ip6_address(env, + &pktInfoP->ipi6_addr, + &addr)) != NULL) { + *eCMsgHdrData = esock_atom_undefined; + return xres; + } + + { + ERL_NIF_TERM keys[] = {esock_atom_addr, esock_atom_ifindex}; + ERL_NIF_TERM vals[] = {addr, ifIndex}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eCMsgHdrData)) { + *eCMsgHdrData = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } + } + } + break; +#endif + + default: + *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen); + break; + } + + return NULL; +} +#endif + + + +/* +++ encode_msghdr_flags +++ + * + * Encode a list of msghdr_flag(). + * + * The following flags are handled: eor | trunc | ctrunc | oob | errqueue. + */ + +extern +char* encode_msghdr_flags(ErlNifEnv* env, + SocketDescriptor* descP, + int msgFlags, + ERL_NIF_TERM* flags) +{ + SSDBG( descP, + ("SOCKET", "encode_cmsghdrs_flags -> entry with" + "\r\n msgFlags: %d (0x%lX)" + "\r\n", msgFlags, msgFlags) ); + + if (msgFlags == 0) { + *flags = MKEL(env); + return NULL; + } else { + SocketTArray ta = TARRAY_CREATE(10); // Just to be on the safe side + +#if defined(MSG_EOR) + if ((msgFlags & MSG_EOR) == MSG_EOR) + TARRAY_ADD(ta, esock_atom_eor); +#endif + +#if defined(MSG_TRUNC) + if ((msgFlags & MSG_TRUNC) == MSG_TRUNC) + TARRAY_ADD(ta, esock_atom_trunc); +#endif + +#if defined(MSG_CTRUNC) + if ((msgFlags & MSG_CTRUNC) == MSG_CTRUNC) + TARRAY_ADD(ta, esock_atom_ctrunc); +#endif + +#if defined(MSG_OOB) + if ((msgFlags & MSG_OOB) == MSG_OOB) + TARRAY_ADD(ta, esock_atom_oob); +#endif + +#if defined(MSG_ERRQUEUE) + if ((msgFlags & MSG_ERRQUEUE) == MSG_ERRQUEUE) + TARRAY_ADD(ta, esock_atom_errqueue); +#endif + + SSDBG( descP, + ("SOCKET", "esock_encode_cmsghdrs -> flags processed when" + "\r\n TArray size: %d" + "\r\n", TARRAY_SZ(ta)) ); + + TARRAY_TOLIST(ta, env, flags); + + return NULL; + } +} + + + + +/* +++ decode the linger value +++ + * The (socket) linger option is provided as a two tuple: + * + * {OnOff :: boolean(), Time :: integer()} + * + */ +static +BOOLEAN_T decode_sock_linger(ErlNifEnv* env, ERL_NIF_TERM eVal, struct linger* valP) +{ + const ERL_NIF_TERM* lt; // The array of the elements of the tuple + int sz; // The size of the tuple - should be 2 + BOOLEAN_T onOff; + int secs; + + if (!GET_TUPLE(env, eVal, &sz, <)) + return FALSE; + + if (sz != 2) + return FALSE; + + + /* So fas so good - now check the two elements of the tuple. */ + + onOff = esock_decode_bool(lt[0]); + + if (!GET_INT(env, lt[1], &secs)) + return FALSE; + + valP->l_onoff = (onOff) ? 1 : 0; + valP->l_linger = secs; + + return TRUE; +} + + + +/* +++ decode the ip socket option TOS +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * When its an atom it can have the values: + * + * lowdelay | throughput | reliability | mincost + * + */ +#if defined(IP_TOS) +static +BOOLEAN_T decode_ip_tos(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +{ + BOOLEAN_T result = FALSE; + + if (IS_ATOM(env, eVal)) { + + if (COMPARE(eVal, esock_atom_lowdelay) == 0) { + *val = IPTOS_LOWDELAY; + result = TRUE; + } else if (COMPARE(eVal, esock_atom_throughput) == 0) { + *val = IPTOS_THROUGHPUT; + result = TRUE; + } else if (COMPARE(eVal, esock_atom_reliability) == 0) { + *val = IPTOS_RELIABILITY; + result = TRUE; +#if defined(IPTOS_MINCOST) + } else if (COMPARE(eVal, esock_atom_mincost) == 0) { + *val = IPTOS_MINCOST; + result = TRUE; +#endif + } else { + *val = -1; + result = FALSE; + } + + } else if (IS_NUM(env, eVal)) { + + if (GET_INT(env, eVal, val)) { + result = TRUE; + } else { + *val = -1; + result = FALSE; + } + + } else { + *val = -1; + result = FALSE; + } + + return result; +} +#endif + + + +/* +++ decode the ip socket option MTU_DISCOVER +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * When its an atom it can have the values: + * + * want | dont | do | probe + * + */ +#if defined(IP_MTU_DISCOVER) +static +char* decode_ip_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +{ + char* res = NULL; + + if (IS_ATOM(env, eVal)) { + + if (COMPARE(eVal, atom_want) == 0) { + *val = IP_PMTUDISC_WANT; + } else if (COMPARE(eVal, atom_dont) == 0) { + *val = IP_PMTUDISC_DONT; + } else if (COMPARE(eVal, atom_do) == 0) { + *val = IP_PMTUDISC_DO; +#if defined(IP_PMTUDISC_PROBE) + } else if (COMPARE(eVal, atom_probe) == 0) { + *val = IP_PMTUDISC_PROBE; +#endif + } else { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else if (IS_NUM(env, eVal)) { + + if (!GET_INT(env, eVal, val)) { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else { + + *val = -1; + res = ESOCK_STR_EINVAL; + + } + + return res; +} +#endif + + + +/* +++ decode the ipv6 socket option MTU_DISCOVER +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * When its an atom it can have the values: + * + * want | dont | do | probe + * + */ +#if defined(IPV6_MTU_DISCOVER) +static +char* decode_ipv6_pmtudisc(ErlNifEnv* env, ERL_NIF_TERM eVal, int* val) +{ + char* res = NULL; + + if (IS_ATOM(env, eVal)) { + + if (COMPARE(eVal, atom_want) == 0) { + *val = IPV6_PMTUDISC_WANT; + } else if (COMPARE(eVal, atom_dont) == 0) { + *val = IPV6_PMTUDISC_DONT; + } else if (COMPARE(eVal, atom_do) == 0) { + *val = IPV6_PMTUDISC_DO; +#if defined(IPV6_PMTUDISC_PROBE) + } else if (COMPARE(eVal, atom_probe) == 0) { + *val = IPV6_PMTUDISC_PROBE; +#endif + } else { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else if (IS_NUM(env, eVal)) { + + if (!GET_INT(env, eVal, val)) { + *val = -1; + res = ESOCK_STR_EINVAL; + } + + } else { + + *val = -1; + res = ESOCK_STR_EINVAL; + + } + + return res; +} +#endif + + + +/* +++ encode the ip socket option MTU_DISCOVER +++ + * The (ip) option can be provide in two ways: + * + * atom() | integer() + * + * If its one of the "known" values, it will be an atom: + * + * want | dont | do | probe + * + */ +#if defined(IP_MTU_DISCOVER) +static +void encode_ip_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) +{ + switch (val) { + case IP_PMTUDISC_WANT: + *eVal = atom_want; + break; + + case IP_PMTUDISC_DONT: + *eVal = atom_dont; + break; + + case IP_PMTUDISC_DO: + *eVal = atom_do; + break; + +#if defined(IP_PMTUDISC_PROBE) + case IP_PMTUDISC_PROBE: + *eVal = atom_probe; + break; +#endif + + default: + *eVal = MKI(env, val); + break; + } + + return; +} +#endif + + + +/* +++ encode the ipv6 socket option MTU_DISCOVER +++ + * The (ipv6) option can be provide in two ways: + * + * atom() | integer() + * + * If its one of the "known" values, it will be an atom: + * + * want | dont | do | probe + * + */ +#if defined(IPV6_MTU_DISCOVER) +static +void encode_ipv6_pmtudisc(ErlNifEnv* env, int val, ERL_NIF_TERM* eVal) +{ + switch (val) { + case IPV6_PMTUDISC_WANT: + *eVal = atom_want; + break; + + case IPV6_PMTUDISC_DONT: + *eVal = atom_dont; + break; + + case IPV6_PMTUDISC_DO: + *eVal = atom_do; + break; + +#if defined(IPV6_PMTUDISC_PROBE) + case IPV6_PMTUDISC_PROBE: + *eVal = atom_probe; + break; +#endif + + default: + *eVal = MKI(env, val); + break; + } + + return; +} +#endif + + + +/* +++ decocde the native getopt option +++ + * The option is in this case provide in the form of a two tuple: + * + * {NativeOpt, ValueSize} + * + * NativeOpt :: integer() + * ValueSize :: int | bool | non_neg_integer() + * + */ +static +BOOLEAN_T decode_native_get_opt(ErlNifEnv* env, ERL_NIF_TERM eVal, + int* opt, Uint16* valueType, int* valueSz) +{ + const ERL_NIF_TERM* nativeOptT; + int nativeOptTSz; + + /* First, get the tuple and verify its size (2) */ + + if (!GET_TUPLE(env, eVal, &nativeOptTSz, &nativeOptT)) + return FALSE; + + if (nativeOptTSz != 2) + return FALSE; + + /* So far so good. + * First element is an integer. + * Second element is an atom or an integer. + * The only "types" that we support at the moment are: + * + * bool - Which is actually a integer + * (but will be *returned* as a boolean()) + * int - Just short for integer + */ + + if (!GET_INT(env, nativeOptT[0], opt)) + return FALSE; + + if (IS_ATOM(env, nativeOptT[1])) { + + if (COMPARE(nativeOptT[1], atom_int) == 0) { + SGDBG( ("SOCKET", "decode_native_get_opt -> int\r\n") ); + *valueType = SOCKET_OPT_VALUE_TYPE_INT; + *valueSz = sizeof(int); // Just to be sure + } else if (COMPARE(nativeOptT[1], atom_bool) == 0) { + SGDBG( ("SOCKET", "decode_native_get_opt -> bool\r\n") ); + *valueType = SOCKET_OPT_VALUE_TYPE_BOOL; + *valueSz = sizeof(int); // Just to be sure + } else { + return FALSE; + } + } else if (IS_NUM(env, nativeOptT[1])) { + if (GET_INT(env, nativeOptT[1], valueSz)) { + SGDBG( ("SOCKET", "decode_native_get_opt -> unspec\r\n") ); + *valueType = SOCKET_OPT_VALUE_TYPE_UNSPEC; + } else { + return FALSE; + } + } else { + return FALSE; + } + + SGDBG( ("SOCKET", "decode_native_get_opt -> done\r\n") ); + + return TRUE; +} + + + +/* +++ encode the ip socket option tos +++ + * The (ip) option can be provide as: + * + * lowdelay | throughput | reliability | mincost | integer() + * + */ +static +ERL_NIF_TERM encode_ip_tos(ErlNifEnv* env, int val) +{ + ERL_NIF_TERM result; + + switch (IPTOS_TOS(val)) { + case IPTOS_LOWDELAY: + result = esock_make_ok2(env, esock_atom_lowdelay); + break; + + case IPTOS_THROUGHPUT: + result = esock_make_ok2(env, esock_atom_throughput); + break; + + case IPTOS_RELIABILITY: + result = esock_make_ok2(env, esock_atom_reliability); + break; + +#if defined(IPTOS_MINCOST) + case IPTOS_MINCOST: + result = esock_make_ok2(env, esock_atom_mincost); + break; +#endif + + default: + result = esock_make_ok2(env, MKI(env, val)); + break; + } + + return result; +} + + + + + +/* *** alloc_descriptor *** + * Allocate and perform basic initialization of a socket descriptor. + * + */ +static +SocketDescriptor* alloc_descriptor(SOCKET sock, HANDLE event) +{ + SocketDescriptor* descP; + + if ((descP = enif_alloc_resource(sockets, sizeof(SocketDescriptor))) != NULL) { + char buf[64]; /* Buffer used for building the mutex name */ + + // This needs to be released when the socket is closed! + descP->env = enif_alloc_env(); + + sprintf(buf, "socket[w,%d]", sock); + descP->writeMtx = MCREATE(buf); + descP->currentWriterP = NULL; // currentWriter not used + descP->writersQ.first = NULL; + descP->writersQ.last = NULL; + descP->isWritable = FALSE; // TRUE; + descP->writePkgCnt = 0; + descP->writeByteCnt = 0; + descP->writeTries = 0; + descP->writeWaits = 0; + descP->writeFails = 0; + + sprintf(buf, "socket[r,%d]", sock); + descP->readMtx = MCREATE(buf); + descP->currentReaderP = NULL; // currentReader not used + descP->readersQ.first = NULL; + descP->readersQ.last = NULL; + descP->isReadable = FALSE; // TRUE; + descP->readPkgCnt = 0; + descP->readByteCnt = 0; + descP->readTries = 0; + descP->readWaits = 0; + + sprintf(buf, "socket[acc,%d]", sock); + descP->accMtx = MCREATE(buf); + descP->currentAcceptorP = NULL; // currentAcceptor not used + descP->acceptorsQ.first = NULL; + descP->acceptorsQ.last = NULL; + + sprintf(buf, "socket[close,%d]", sock); + descP->closeMtx = MCREATE(buf); + + descP->rBufSz = SOCKET_RECV_BUFFER_SIZE_DEFAULT; + descP->rNum = 0; + descP->rNumCnt = 0; + descP->rCtrlSz = SOCKET_RECV_CTRL_BUFFER_SIZE_DEFAULT; + descP->wCtrlSz = SOCKET_SEND_CTRL_BUFFER_SIZE_DEFAULT; + descP->iow = FALSE; + descP->dbg = SOCKET_DEBUG_DEFAULT; + + descP->sock = sock; + descP->event = event; + + MON_INIT(&descP->currentWriter.mon); + MON_INIT(&descP->currentReader.mon); + MON_INIT(&descP->currentAcceptor.mon); + MON_INIT(&descP->ctrlMon); + MON_INIT(&descP->closerMon); + } + + return descP; +} + + + +/* decrement counters for when a socket is closed */ +static +void dec_socket(int domain, int type, int protocol) +{ + MLOCK(data.cntMtx); + + cnt_dec(&data.numSockets, 1); + + if (domain == AF_INET) + cnt_dec(&data.numDomainInet, 1); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (domain == AF_INET6) + cnt_dec(&data.numDomainInet6, 1); +#endif +#if defined(HAVE_SYS_UN_H) + else if (domain == AF_UNIX) + cnt_dec(&data.numDomainInet6, 1); +#endif + + if (type == SOCK_STREAM) + cnt_dec(&data.numTypeStreams, 1); + else if (type == SOCK_DGRAM) + cnt_dec(&data.numTypeDGrams, 1); +#ifdef HAVE_SCTP + else if (type == SOCK_SEQPACKET) + cnt_dec(&data.numTypeSeqPkgs, 1); +#endif + + if (protocol == IPPROTO_IP) + cnt_dec(&data.numProtoIP, 1); + else if (protocol == IPPROTO_TCP) + cnt_dec(&data.numProtoTCP, 1); + else if (protocol == IPPROTO_UDP) + cnt_dec(&data.numProtoUDP, 1); +#if defined(HAVE_SCTP) + else if (protocol == IPPROTO_SCTP) + cnt_dec(&data.numProtoSCTP, 1); +#endif + + MUNLOCK(data.cntMtx); +} + + +/* increment counters for when a socket is opened */ +static +void inc_socket(int domain, int type, int protocol) +{ + MLOCK(data.cntMtx); + + cnt_inc(&data.numSockets, 1); + + if (domain == AF_INET) + cnt_inc(&data.numDomainInet, 1); +#if defined(HAVE_IN6) && defined(AF_INET6) + else if (domain == AF_INET6) + cnt_inc(&data.numDomainInet6, 1); +#endif +#if defined(HAVE_SYS_UN_H) + else if (domain == AF_UNIX) + cnt_inc(&data.numDomainInet6, 1); +#endif + + if (type == SOCK_STREAM) + cnt_inc(&data.numTypeStreams, 1); + else if (type == SOCK_DGRAM) + cnt_inc(&data.numTypeDGrams, 1); +#ifdef HAVE_SCTP + else if (type == SOCK_SEQPACKET) + cnt_inc(&data.numTypeSeqPkgs, 1); +#endif + + if (protocol == IPPROTO_IP) + cnt_inc(&data.numProtoIP, 1); + else if (protocol == IPPROTO_TCP) + cnt_inc(&data.numProtoTCP, 1); + else if (protocol == IPPROTO_UDP) + cnt_inc(&data.numProtoUDP, 1); +#if defined(HAVE_SCTP) + else if (protocol == IPPROTO_SCTP) + cnt_inc(&data.numProtoSCTP, 1); +#endif + + MUNLOCK(data.cntMtx); +} + + + +/* compare_pids - Test if two pids are equal + * + */ +static +int compare_pids(ErlNifEnv* env, + const ErlNifPid* pid1, + const ErlNifPid* pid2) +{ + ERL_NIF_TERM p1 = enif_make_pid(env, pid1); + ERL_NIF_TERM p2 = enif_make_pid(env, pid2); + + return enif_is_identical(p1, p2); +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * D e c o d e / E n c o d e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* edomain2domain - convert internal (erlang) domain to (proper) domain + * + * Note that only a subset is supported. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T edomain2domain(int edomain, int* domain) +{ + switch (edomain) { + case SOCKET_DOMAIN_INET: + *domain = AF_INET; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case SOCKET_DOMAIN_INET6: + *domain = AF_INET6; + break; +#endif +#ifdef HAVE_SYS_UN_H + case SOCKET_DOMAIN_LOCAL: + *domain = AF_UNIX; + break; +#endif + + default: + *domain = -1; + return FALSE; + } + + return TRUE; +} + + +/* etype2type - convert internal (erlang) type to (proper) type + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T etype2type(int etype, int* type) +{ + switch (etype) { + case SOCKET_TYPE_STREAM: + *type = SOCK_STREAM; + break; + + case SOCKET_TYPE_DGRAM: + *type = SOCK_DGRAM; + break; + + case SOCKET_TYPE_RAW: + *type = SOCK_RAW; + break; + +#ifdef HAVE_SCTP + case SOCKET_TYPE_SEQPACKET: + *type = SOCK_SEQPACKET; + break; +#endif + + default: + *type = -1; + return FALSE; + } + + return TRUE; +} + + +/* eproto2proto - convert internal (erlang) protocol to (proper) protocol + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T eproto2proto(ErlNifEnv* env, + ERL_NIF_TERM eproto, + int* proto) +{ + if (IS_NUM(env, eproto)) { + int ep; + + if (!GET_INT(env, eproto, &ep)) { + *proto = -1; + return FALSE; + } + + switch (ep) { + case SOCKET_PROTOCOL_IP: + *proto = IPPROTO_IP; + break; + + case SOCKET_PROTOCOL_TCP: + *proto = IPPROTO_TCP; + break; + + case SOCKET_PROTOCOL_UDP: + *proto = IPPROTO_UDP; + break; + +#if defined(HAVE_SCTP) + case SOCKET_PROTOCOL_SCTP: + *proto = IPPROTO_SCTP; + break; +#endif + + case SOCKET_PROTOCOL_ICMP: + *proto = IPPROTO_ICMP; + break; + + case SOCKET_PROTOCOL_IGMP: + *proto = IPPROTO_IGMP; + break; + + default: + *proto = -2; + return FALSE; + } + } else { + const ERL_NIF_TERM* a; + int sz; + + if (!GET_TUPLE(env, eproto, &sz, &a)) { + *proto = -3; + return FALSE; + } + + if (sz != 2) { + *proto = -4; + return FALSE; + } + + if (COMPARE(a[0], esock_atom_raw) != 0) { + *proto = -5; + return FALSE; + } + + if (!GET_INT(env, a[1], proto)) { + *proto = -6; + return FALSE; + } + } + + return TRUE; +} + + +#ifdef HAVE_SETNS + /* emap2netns - extract the netns field from the extra map + * + * Note that currently we only support one extra option, the netns. + */ +static +BOOLEAN_T emap2netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns) +{ + size_t sz; + ERL_NIF_TERM key; + ERL_NIF_TERM value; + unsigned int len; + char* buf; + int written; + + /* Note that its acceptable that the extra map is empty */ + if (!enif_get_map_size(env, map, &sz) || + (sz != 1)) { + *netns = NULL; + return TRUE; + } + + /* The currently only supported extra option is: netns */ + key = enif_make_atom(env, "netns"); + if (!GET_MAP_VAL(env, map, key, &value)) { + *netns = NULL; // Just in case... + return FALSE; + } + + /* So far so good. The value should be a string, check. */ + if (!enif_is_list(env, value)) { + *netns = NULL; // Just in case... + return FALSE; + } + + if (!enif_get_list_length(env, value, &len)) { + *netns = NULL; // Just in case... + return FALSE; + } + + if ((buf = MALLOC(len+1)) == NULL) { + *netns = NULL; // Just in case... + return FALSE; + } + + written = enif_get_string(env, value, buf, len+1, ERL_NIF_LATIN1); + if (written == (len+1)) { + *netns = buf; + return TRUE; + } else { + *netns = NULL; // Just in case... + return FALSE; + } +} +#endif + + +/* esendflags2sendflags - convert internal (erlang) send flags to (proper) + * send flags. + */ +static +BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags) +{ + unsigned int ef; + int tmp = 0; + + /* First, check if we have any flags at all */ + if (eflags == 0) { + *flags = 0; + return TRUE; + } + + for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) { + + switch (ef) { +#if defined(MSG_CONFIRM) + case SOCKET_SEND_FLAG_CONFIRM: + if ((1 << SOCKET_SEND_FLAG_CONFIRM) & eflags) + tmp |= MSG_CONFIRM; + break; +#endif + +#if defined(MSG_DONTROUTE) + case SOCKET_SEND_FLAG_DONTROUTE: + if ((1 << SOCKET_SEND_FLAG_DONTROUTE) & eflags) + tmp |= MSG_DONTROUTE; + break; +#endif + +#if defined(MSG_EOR) + case SOCKET_SEND_FLAG_EOR: + if ((1 << SOCKET_SEND_FLAG_EOR) & eflags) + tmp |= MSG_EOR; + break; +#endif + +#if defined(MSG_MORE) + case SOCKET_SEND_FLAG_MORE: + if ((1 << SOCKET_SEND_FLAG_MORE) & eflags) + tmp |= MSG_MORE; + break; +#endif + +#if defined(MSG_NOSIGNAL) + case SOCKET_SEND_FLAG_NOSIGNAL: + if ((1 << SOCKET_SEND_FLAG_NOSIGNAL) & eflags) + tmp |= MSG_NOSIGNAL; + break; +#endif + +#if defined(MSG_OOB) + case SOCKET_SEND_FLAG_OOB: + if ((1 << SOCKET_SEND_FLAG_OOB) & eflags) + tmp |= MSG_OOB; + break; +#endif + + default: + return FALSE; + } + + } + + *flags = tmp; + + return TRUE; +} + + + +/* erecvflags2recvflags - convert internal (erlang) send flags to (proper) + * send flags. + */ +static +BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags) +{ + unsigned int ef; + int tmp = 0; + + SGDBG( ("SOCKET", "erecvflags2recvflags -> entry with" + "\r\n eflags: %d" + "\r\n", eflags) ); + + if (eflags == 0) { + *flags = 0; + return TRUE; + } + + for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) { + + SGDBG( ("SOCKET", "erecvflags2recvflags -> iteration" + "\r\n ef: %d" + "\r\n tmp: %d" + "\r\n", ef, tmp) ); + + switch (ef) { +#if defined(MSG_CMSG_CLOEXEC) + case SOCKET_RECV_FLAG_CMSG_CLOEXEC: + if ((1 << SOCKET_RECV_FLAG_CMSG_CLOEXEC) & eflags) + tmp |= MSG_CMSG_CLOEXEC; + break; +#endif + +#if defined(MSG_ERRQUEUE) + case SOCKET_RECV_FLAG_ERRQUEUE: + if ((1 << SOCKET_RECV_FLAG_ERRQUEUE) & eflags) + tmp |= MSG_ERRQUEUE; + break; +#endif + +#if defined(MSG_OOB) + case SOCKET_RECV_FLAG_OOB: + if ((1 << SOCKET_RECV_FLAG_OOB) & eflags) + tmp |= MSG_OOB; + break; +#endif + + /* + * <KOLLA> + * + * We need to handle this, because it may effect the read algorithm + * + * </KOLLA> + */ +#if defined(MSG_PEEK) + case SOCKET_RECV_FLAG_PEEK: + if ((1 << SOCKET_RECV_FLAG_PEEK) & eflags) + tmp |= MSG_PEEK; + break; +#endif + +#if defined(MSG_TRUNC) + case SOCKET_RECV_FLAG_TRUNC: + if ((1 << SOCKET_RECV_FLAG_TRUNC) & eflags) + tmp |= MSG_TRUNC; + break; +#endif + + default: + return FALSE; + } + + } + + *flags = tmp; + + return TRUE; +} + + + +/* eproto2proto - convert internal (erlang) protocol to (proper) protocol + * + * Note that only a subset is supported. + */ +static +BOOLEAN_T ehow2how(unsigned int ehow, int* how) +{ + switch (ehow) { + case SOCKET_SHUTDOWN_HOW_RD: + *how = SHUT_RD; + break; + + case SOCKET_SHUTDOWN_HOW_WR: + *how = SHUT_WR; + break; + + case SOCKET_SHUTDOWN_HOW_RDWR: + *how = SHUT_RDWR; + break; + + default: + return FALSE; + } + + return TRUE; +} + + + +#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE) +/* strnlen doesn't exist everywhere */ +/* +static +size_t my_strnlen(const char *s, size_t maxlen) +{ + size_t i = 0; + while (i < maxlen && s[i] != '\0') + i++; + return i; +} +*/ +#endif + + +/* Send an error closed message to the specified process: + * + * This message is for processes that are waiting in the + * erlang API functions for a select message. + */ +/* +static +char* send_msg_error_closed(ErlNifEnv* env, + ErlNifPid* pid) +{ + return send_msg_error(env, atom_closed, pid); +} +*/ + +/* Send an error message to the specified process: + * A message in the form: + * + * {error, Reason} + * + * This message is for processes that are waiting in the + * erlang API functions for a select message. + */ +/* +static +char* send_msg_error(ErlNifEnv* env, + ERL_NIF_TERM reason, + ErlNifPid* pid) +{ + ERL_NIF_TERM msg = enif_make_tuple2(env, atom_error, reason); + + return send_msg(env, msg, pid); +} +*/ + + +/* Send an close message to the specified process: + * A message in the form: + * + * {'$socket', SockRef, close, CloseRef} + * + * This message is for processes that is waiting in the + * erlang API (close-) function for the socket to be "closed" + * (actually that the 'stop' callback function has been called). + */ +static +char* esock_send_close_msg(ErlNifEnv* env, + ERL_NIF_TERM closeRef, + ErlNifPid* pid) +{ + return esock_send_socket_msg(env, + esock_atom_undefined, + esock_atom_close, closeRef, pid); +} + + +/* Send an abort message to the specified process: + * A message in the form: + * + * {'$socket', SockRef, abort, {RecvRef, Reason}} + * + * This message is for processes that is waiting in the + * erlang API functions for a select message. + */ +static +char* esock_send_abort_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM recvRef, + ERL_NIF_TERM reason, + ErlNifPid* pid) +{ + ERL_NIF_TERM info = MKT2(env, recvRef, reason); + + /* + esock_dbg_printf("SEND MSG", + "try send abort message to %T:\r\n", + "\r\n sockRef: %T" + "\r\n recvRef: %T" + "\r\n reason: %T" + "\r\n", *pid, sockRef, recvRef, reason); + */ + + return esock_send_socket_msg(env, sockRef, esock_atom_abort, info, pid); +} + + +/* *** esock_send_socket_msg *** + * + * This function sends a general purpose socket message to an erlang + * process. A general 'socket' message has the form: + * + * {'$socket', SockRef, Tag, Info} + * + */ + +static +char* esock_send_socket_msg(ErlNifEnv* env, + ERL_NIF_TERM sockRef, + ERL_NIF_TERM tag, + ERL_NIF_TERM info, + ErlNifPid* pid) +{ + ERL_NIF_TERM msg = MKT4(env, esock_atom_socket_tag, sockRef, tag, info); + + return esock_send_msg(env, msg, pid); +} + + +/* Send a message to the specified process. + */ +static +char* esock_send_msg(ErlNifEnv* env, + ERL_NIF_TERM msg, + ErlNifPid* pid) +{ + if (!enif_send(env, pid, NULL, msg)) + return str_exsend; + else + return NULL; +} +#endif // #if defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * R e q u e s t Q u e u e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* *** acceptor search for pid *** + * + * Search for a pid in the acceptor queue. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T acceptor_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid) +{ + return qsearch4pid(env, &descP->acceptorsQ, pid); +} + + +/* *** acceptor push *** + * + * Push an acceptor onto the acceptor queue. + * This happens when we already have atleast one current acceptor. + */ +static +ERL_NIF_TERM acceptor_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref) +{ + SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement)); + SocketRequestor* reqP = &e->data; + + reqP->pid = pid; + reqP->ref = enif_make_copy(descP->env, ref); + + if (MONP("acceptor_push -> acceptor request", + env, descP, &pid, &reqP->mon) != 0) { + FREE(reqP); + return esock_make_error(env, atom_exmon); + } + + qpush(&descP->acceptorsQ, e); + + // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN + return esock_make_error(env, esock_atom_eagain); +} + + +/* *** acceptor pop *** + * + * Pop an acceptor from the acceptor queue. + */ +static +BOOLEAN_T acceptor_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + // ErlNifMonitor* mon, + ESockMonitor* mon, + ERL_NIF_TERM* ref) +{ + SocketRequestQueueElement* e = qpop(&descP->acceptorsQ); + + if (e != NULL) { + *pid = e->data.pid; + *mon = e->data.mon; + *ref = e->data.ref; + FREE(e); + return TRUE; + } else { + /* (acceptors) Queue was empty */ + // *pid = NULL; we have no null value for pids + // *mon = NULL; we have no null value for monitors + *ref = esock_atom_undefined; // Just in case + return FALSE; + } + +} + + +/* *** acceptor unqueue *** + * + * Remove an acceptor from the acceptor queue. + */ +static +BOOLEAN_T acceptor_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + return qunqueue(env, descP, "qunqueue -> waiting acceptor", + &descP->acceptorsQ, pid); +} + + + +/* *** writer search for pid *** + * + * Search for a pid in the writer queue. + */ +static +BOOLEAN_T writer_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid) +{ + return qsearch4pid(env, &descP->writersQ, pid); +} + + +/* *** writer push *** + * + * Push an writer onto the writer queue. + * This happens when we already have atleast one current writer. + */ +static +ERL_NIF_TERM writer_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref) +{ + SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement)); + SocketRequestor* reqP = &e->data; + + reqP->pid = pid; + reqP->ref = enif_make_copy(descP->env, ref); + + if (MONP("writer_push -> writer request", + env, descP, &pid, &reqP->mon) != 0) { + FREE(reqP); + return esock_make_error(env, atom_exmon); + } + + qpush(&descP->writersQ, e); + + // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN + return esock_make_error(env, esock_atom_eagain); +} + + +/* *** writer pop *** + * + * Pop an writer from the writer queue. + */ +static +BOOLEAN_T writer_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + // ErlNifMonitor* mon, + ESockMonitor* mon, + ERL_NIF_TERM* ref) +{ + SocketRequestQueueElement* e = qpop(&descP->writersQ); + + if (e != NULL) { + *pid = e->data.pid; + *mon = e->data.mon; + *ref = e->data.ref; // At this point the ref has already been copied (env) + FREE(e); + return TRUE; + } else { + /* (writers) Queue was empty */ + // *pid = NULL; we have no null value for pids + // *mon = NULL; we have no null value for monitors + *ref = esock_atom_undefined; // Just in case + return FALSE; + } + +} + + +/* *** writer unqueue *** + * + * Remove an writer from the writer queue. + */ +static +BOOLEAN_T writer_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + return qunqueue(env, descP, "qunqueue -> waiting writer", + &descP->writersQ, pid); +} + + + +/* *** reader search for pid *** + * + * Search for a pid in the reader queue. + */ +static +BOOLEAN_T reader_search4pid(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid) +{ + return qsearch4pid(env, &descP->readersQ, pid); +} + + +/* *** reader push *** + * + * Push an reader onto the raeder queue. + * This happens when we already have atleast one current reader. + */ +static +ERL_NIF_TERM reader_push(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid pid, + ERL_NIF_TERM ref) +{ + SocketRequestQueueElement* e = MALLOC(sizeof(SocketRequestQueueElement)); + SocketRequestor* reqP = &e->data; + + reqP->pid = pid; + reqP->ref = enif_make_copy(descP->env, ref); + + if (MONP("reader_push -> reader request", + env, descP, &pid, &reqP->mon) != 0) { + FREE(reqP); + return esock_make_error(env, atom_exmon); + } + + qpush(&descP->readersQ, e); + + // THIS IS OK => MAKES THE CALLER WAIT FOR ITS TURN + return esock_make_error(env, esock_atom_eagain); +} + + +/* *** reader pop *** + * + * Pop an writer from the reader queue. + */ +static +BOOLEAN_T reader_pop(ErlNifEnv* env, + SocketDescriptor* descP, + ErlNifPid* pid, + // ErlNifMonitor* mon, + ESockMonitor* mon, + ERL_NIF_TERM* ref) +{ + SocketRequestQueueElement* e = qpop(&descP->readersQ); + + if (e != NULL) { + *pid = e->data.pid; + *mon = e->data.mon; + *ref = e->data.ref; // At this point the ref has already been copied (env) + FREE(e); + return TRUE; + } else { + /* (readers) Queue was empty */ + // *pid = NULL; we have no null value for pids + // *mon = NULL; we have no null value for monitors + *ref = esock_atom_undefined; // Just in case + return FALSE; + } + +} + + +/* *** reader unqueue *** + * + * Remove an reader from the reader queue. + */ +static +BOOLEAN_T reader_unqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + return qunqueue(env, descP, "qunqueue -> waiting reader", + &descP->readersQ, pid); +} + + + + + +static +BOOLEAN_T qsearch4pid(ErlNifEnv* env, + SocketRequestQueue* q, + ErlNifPid* pid) +{ + SocketRequestQueueElement* tmp = q->first; + + while (tmp != NULL) { + if (compare_pids(env, &tmp->data.pid, pid)) + return TRUE; + else + tmp = tmp->nextP; + } + + return FALSE; +} + + +static +void qpush(SocketRequestQueue* q, + SocketRequestQueueElement* e) +{ + if (q->first != NULL) { + q->last->nextP = e; + q->last = e; + e->nextP = NULL; + } else { + q->first = e; + q->last = e; + e->nextP = NULL; + } +} + + +static +SocketRequestQueueElement* qpop(SocketRequestQueue* q) +{ + SocketRequestQueueElement* e = q->first; + + if (e != NULL) { + /* Atleast one element in the queue */ + if (e == q->last) { + /* Only one element in the queue */ + q->first = q->last = NULL; + } else { + /* More than one element in the queue */ + q->first = e->nextP; + } + } + + return e; +} + + + +static +BOOLEAN_T qunqueue(ErlNifEnv* env, + SocketDescriptor* descP, + const char* slogan, + SocketRequestQueue* q, + const ErlNifPid* pid) +{ + SocketRequestQueueElement* e = q->first; + SocketRequestQueueElement* p = NULL; + + /* Check if it was one of the waiting acceptor processes */ + while (e != NULL) { + if (compare_pids(env, &e->data.pid, pid)) { + + /* We have a match */ + + DEMONP(slogan, env, descP, &e->data.mon); + + if (p != NULL) { + /* Not the first, but could be the last */ + if (q->last == e) { + q->last = p; + p->nextP = NULL; + } else { + p->nextP = e->nextP; + } + + } else { + /* The first and could also be the last */ + if (q->last == e) { + q->last = NULL; + q->first = NULL; + } else { + q->first = e->nextP; + } + } + + FREE(e); + + return TRUE; + } + + /* Try next */ + p = e; + e = e->nextP; + } + + return FALSE; +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * C o u n t e r F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +#if !defined(__WIN32__) +static +BOOLEAN_T cnt_inc(Uint32* cnt, Uint32 inc) +{ + BOOLEAN_T wrap; + Uint32 max = 0xFFFFFFFF; + Uint32 current = *cnt; + + if ((max - inc) >= current) { + *cnt += inc; + wrap = FALSE; + } else { + *cnt = inc - (max - current) - 1; + wrap = TRUE; + } + + return (wrap); +} + + +static +void cnt_dec(Uint32* cnt, Uint32 dec) +{ + Uint32 current = *cnt; + + if (dec > current) + *cnt = 0; // The counter cannot be < 0 so this is the best we can do... + else + *cnt -= dec; + + return; +} +#endif // if !defined(__WIN32__) + + + + +/* ---------------------------------------------------------------------- + * M o n i t o r W r a p p e r F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +#if !defined(__WIN32__) +static +int esock_monitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid, + ESockMonitor* monP) +{ + int res; + + SSDBG( descP, ("SOCKET", "[%d] %s: try monitor\r\n", descP->sock, slogan) ); + /* esock_dbg_printf("MONP", "[%d] %s\r\n", descP->sock, slogan); */ + res = enif_monitor_process(env, descP, pid, &monP->mon); + + if (res != 0) { + SSDBG( descP, ("SOCKET", "[%d] monitor failed: %d\r\n", descP->sock, res) ); + // esock_dbg_printf("MONP", "[%d] failed: %d\r\n", descP->sock, res); + } /* else { + esock_dbg_printf("MONP", + "[%d] success: " + "%u,%u,%u,%u\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + } */ + + return res; +} + + +static +int esock_demonitor(const char* slogan, + ErlNifEnv* env, + SocketDescriptor* descP, + ESockMonitor* monP) +{ + int res; + + SSDBG( descP, ("SOCKET", "[%d] %s: try demonitor\r\n", descP->sock, slogan) ); + + /* + esock_dbg_printf("DEMONP", "[%d] %s: %u,%u,%u,%u\r\n", + descP->sock, slogan, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + res = enif_demonitor_process(env, descP, &monP->mon); + + if (res == 0) { + esock_monitor_init(monP); + } else { + SSDBG( descP, + ("SOCKET", "[%d] demonitor failed: %d\r\n", descP->sock, res) ); + /* + esock_dbg_printf("DEMONP", "[%d] failed: %d\r\n", descP->sock, res); + */ + } + + return res; +} + + +static +void esock_monitor_init(ESockMonitor* monP) +{ + int i; + + /* + * UGLY, + * but since we don't have a ERL_NIF_MONITOR_NULL, + * this will have to do for now... + */ + for (i = 0; i < 4; i++) + monP->raw[i] = 0; + +} +#endif // if !defined(__WIN32__) + + +/* +static +int esock_monitor_compare(const ErlNifMonitor* mon1, + const ESockMonitor* mon2) +{ + return enif_compare_monitors(mon1, &mon2->mon); +} +*/ + + +/* ---------------------------------------------------------------------- + * C a l l b a c k F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +/* ========================================================================= + * socket_dtor - Callback function for resource destructor + * + */ +static +void socket_dtor(ErlNifEnv* env, void* obj) +{ +#if !defined(__WIN32__) + SocketDescriptor* descP = (SocketDescriptor*) obj; + + enif_clear_env(descP->env); + enif_free_env(descP->env); + descP->env = NULL; + + MDESTROY(descP->writeMtx); + MDESTROY(descP->readMtx); + MDESTROY(descP->accMtx); + MDESTROY(descP->closeMtx); +#endif +} + + +/* ========================================================================= + * socket_stop - Callback function for resource stop + * + * When the socket is stopped, we need to inform: + * + * * the controlling process + * * the current writer and any waiting writers + * * the current reader and any waiting readers + * * the current acceptor and any waiting acceptor + * + * Also, make sure no process gets the message twice + * (in case it is, for instance, both controlling process + * and a writer). + * + * <KOLLA> + * + * We do not handle linger-issues yet! So anything in the out + * buffers will be left for the OS to solve... + * Do we need a special "close"-thread? Dirty scheduler? + * + * What happens if we are "stopped" for another reason then 'close'? + * For instance, down? + * + * </KOLLA> + */ +static +void socket_stop(ErlNifEnv* env, void* obj, int fd, int is_direct_call) +{ +#if !defined(__WIN32__) + SocketDescriptor* descP = (SocketDescriptor*) obj; + + /* + esock_dbg_printf("STOP", "[%d] begin\r\n", descP->sock); + */ + + SSDBG( descP, + ("SOCKET", "socket_stop -> entry when %s" + "\r\n sock: %d (%d)" + "\r\n", ((is_direct_call) ? "called" : "scheduled"), descP->sock, fd) ); + + MLOCK(descP->writeMtx); + MLOCK(descP->readMtx); + MLOCK(descP->accMtx); + MLOCK(descP->closeMtx); + + SSDBG( descP, ("SOCKET", "socket_stop -> " + "[%d, %T] all mutex(s) locked when counters:" + "\r\n writePkgCnt: %u" + "\r\n writeByteCnt: %u" + "\r\n writeTries: %u" + "\r\n writeWaits: %u" + "\r\n writeFails: %u" + "\r\n readPkgCnt: %u" + "\r\n readByteCnt: %u" + "\r\n readTries: %u" + "\r\n readWaits: %u" + "\r\n", + descP->sock, descP->ctrlPid, + descP->writePkgCnt, + descP->writeByteCnt, + descP->writeTries, + descP->writeWaits, + descP->writeFails, + descP->readPkgCnt, + descP->readByteCnt, + descP->readTries, + descP->readWaits) ); + + descP->state = SOCKET_STATE_CLOSING; // Just in case...??? + descP->isReadable = FALSE; + descP->isWritable = FALSE; + + /* We should check that we actually have a monitor. + * This *should* be done with a "NULL" monitor value, + * which there currently is none... + * If we got here because the controlling process died, + * its no point in demonitor. Also, we not actually have + * a monitor in that case... + */ + DEMONP("socket_stop -> ctrl", env, descP, &descP->ctrlMon); + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current writer (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ + if (descP->currentWriterP != NULL) { + /* We have a (current) writer and *may* therefor also have + * writers waiting. + */ + + DEMONP("socket_stop -> current writer", + env, descP, &descP->currentWriter.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current writer\r\n") ); + if (!compare_pids(env, + &descP->closerPid, + &descP->currentWriter.pid)) { + SSDBG( descP, ("SOCKET", "socket_stop -> " + "send abort message to current writer %T\r\n", + descP->currentWriter.pid) ); + if (esock_send_abort_msg(env, + esock_atom_undefined, + descP->currentWriter.ref, + atom_closed, + &descP->currentWriter.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current writer %T\r\n", + descP->currentWriter.ref, + descP->currentWriter.pid); + } + } + + /* And also deal with the waiting writers (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting writer(s)\r\n") ); + inform_waiting_procs(env, descP, &descP->writersQ, TRUE, atom_closed); + } + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current reader (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ + if (descP->currentReaderP != NULL) { + + /* We have a (current) reader and *may* therefor also have + * readers waiting. + */ + + DEMONP("socket_stop -> current reader", + env, descP, &descP->currentReader.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current reader\r\n") ); + if (!compare_pids(env, + &descP->closerPid, + &descP->currentReader.pid)) { + SSDBG( descP, ("SOCKET", "socket_stop -> " + "send abort message to current reader %T\r\n", + descP->currentReader.pid) ); + if (esock_send_abort_msg(env, + esock_atom_undefined, + descP->currentReader.ref, + atom_closed, + &descP->currentReader.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current reader %T\r\n", + descP->currentReader.ref, + descP->currentReader.pid); + } + } + + /* And also deal with the waiting readers (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting reader(s)\r\n") ); + inform_waiting_procs(env, descP, &descP->readersQ, TRUE, atom_closed); + } + + /* + esock_dbg_printf("STOP", "[%d] maybe handle current acceptor (0x%lX)\r\n", + descP->sock, descP->currentReaderP); + */ + if (descP->currentAcceptorP != NULL) { + /* We have a (current) acceptor and *may* therefor also have + * acceptors waiting. + */ + + DEMONP("socket_stop -> current acceptor", + env, descP, &descP->currentAcceptor.mon); + + SSDBG( descP, ("SOCKET", "socket_stop -> handle current acceptor\r\n") ); + if (!compare_pids(env, + &descP->closerPid, + &descP->currentAcceptor.pid)) { + SSDBG( descP, ("SOCKET", "socket_stop -> " + "send abort message to current acceptor %T\r\n", + descP->currentWriter.pid) ); + if (esock_send_abort_msg(env, + esock_atom_undefined, + descP->currentAcceptor.ref, + atom_closed, + &descP->currentAcceptor.pid) != NULL) { + /* Shall we really do this? + * This happens if the controlling process has been killed! + */ + esock_warning_msg("Failed sending abort (%T) message to " + "current acceptor %T\r\n", + descP->currentAcceptor.ref, + descP->currentAcceptor.pid); + } + } + + /* And also deal with the waiting acceptors (in the same way) */ + SSDBG( descP, ("SOCKET", "socket_stop -> handle waiting acceptor(s)\r\n") ); + inform_waiting_procs(env, descP, &descP->acceptorsQ, TRUE, atom_closed); + } + + + if (descP->sock != INVALID_SOCKET) { + + /* + * <KOLLA> + * + * WE NEED TO CHECK IF THIS OPERATION IS TRIGGERED + * LOCALLY (VIA A CALL TO CLOSE) OR REMOTELLY + * (VIA I.E. ECONSRESET). + * + * </KOLLA> + */ + + if (descP->closeLocal && + !is_direct_call) { + + /* +++ send close message to the waiting process +++ + * + * {close, CloseRef} + * + * <KOLLA> + * + * WHAT HAPPENS IF THE RECEIVER HAS DIED IN THE MEANTIME???? + * + * Also, we should really *always* use a tag unique to this + * (nif-) module. Some like (in this case): + * + * {'$socket', undefined, close, CloseRef} + * + * </KOLLA> + */ + + esock_send_close_msg(env, descP->closeRef, &descP->closerPid); + + DEMONP("socket_stop -> closer", env, descP, &descP->closerMon); + + } else { + + /* + * <KOLLA> + * + * ABORT? + * + * </KOLLA> + */ + } + } + + + if (!is_direct_call) { + if (descP->closeLocal) { + DEMONP("socket_stop -> closer", + env, descP, &descP->closerMon); + } + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + descP->state = SOCKET_STATE_CLOSED; + } + + SSDBG( descP, ("SOCKET", "socket_stop -> unlock all mutex(s)\r\n") ); + + MUNLOCK(descP->closeMtx); + MUNLOCK(descP->accMtx); + MUNLOCK(descP->readMtx); + MUNLOCK(descP->writeMtx); + + /* + esock_dbg_printf("STOP", "[%d] end\r\n", descP->sock); + */ + + SSDBG( descP, + ("SOCKET", "socket_stop -> done (%d, %d)\r\n", descP->sock, fd) ); +#endif // if !defined(__WIN32__) +} + + +/* This function traverse the queue and sends the specified + * nif_abort message with the specified reason to each member, + * and if the 'free' argument is TRUE, the queue will be emptied. + */ +#if !defined(__WIN32__) +static +void inform_waiting_procs(ErlNifEnv* env, + SocketDescriptor* descP, + SocketRequestQueue* q, + BOOLEAN_T free, + ERL_NIF_TERM reason) +{ + SocketRequestQueueElement* currentP = q->first; + SocketRequestQueueElement* nextP; + + while (currentP != NULL) { + + /* <KOLLA> + * + * Should we inform anyone if we fail to demonitor? + * NOT SURE WHAT THAT WOULD REPRESENT AND IT IS NOT + * IMPORTANT IN *THIS* CASE, BUT ITS A FUNDAMENTAL OP... + * + * </KOLLA> + */ + + SSDBG( descP, + ("SOCKET", "inform_waiting_procs -> abort request %T (from %T)\r\n", + currentP->data.ref, currentP->data.pid) ); + + ESOCK_ASSERT( (NULL == esock_send_abort_msg(env, + esock_atom_undefined, + currentP->data.ref, + reason, + ¤tP->data.pid)) ); + + DEMONP("inform_waiting_procs -> current 'request'", + env, descP, ¤tP->data.mon); + nextP = currentP->nextP; + if (free) FREE(currentP); + currentP = nextP; + } + + if (free) { + q->first = NULL; + q->last = NULL; + } +} +#endif // if !defined(__WIN32__) + + +/* ========================================================================= + * socket_down - Callback function for resource down (monitored processes) + * + */ +static +void socket_down(ErlNifEnv* env, + void* obj, + const ErlNifPid* pid, + const ErlNifMonitor* mon) +{ +#if !defined(__WIN32__) + SocketDescriptor* descP = (SocketDescriptor*) obj; + ESockMonitor* monP = (ESockMonitor*) mon; + + SSDBG( descP, ("SOCKET", "socket_down -> entry with" + "\r\n sock: %d" + "\r\n pid: %T" + "\r\n Close: %s (%s)" + "\r\n", + descP->sock, *pid, + B2S(IS_CLOSED(descP)), + B2S(IS_CLOSING(descP))) ); + + /* + esock_dbg_printf("DOWN", + "[%d] begin %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + /* + if (MON_COMP(mon, &descP->ctrlMon) == 0) { + SSDBG( descP, ("SOCKET", "socket_down -> controlling process mon\r\n") ); + } else if (MON_COMP(mon, &descP->closerMon) == 0) { + SSDBG( descP, ("SOCKET", "socket_down -> closer mon\r\n") ); + } else if ((descP->currentWriterP != NULL) && + (MON_COMP(mon, &descP->currentWriter.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current writer mon\r\n") ); + } else if ((descP->currentReaderP != NULL) && + (MON_COMP(mon, &descP->currentReader.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current reader mon\r\n") ); + } else if ((descP->currentAcceptorP != NULL) && + (MON_COMP(mon, &descP->currentAcceptor.mon) == 0)) { + SSDBG( descP, ("SOCKET", "socket_down -> current acceptor mon\r\n") ); + } else { + SSDBG( descP, ("SOCKET", "socket_down -> OTHER mon\r\n") ); + } + */ + + if (!IS_CLOSED(descP)) { + if (compare_pids(env, &descP->ctrlPid, pid)) { + int selectRes; + + /* We don't bother with the queue cleanup here - + * we leave it to the stop callback function. + */ + + SSDBG( descP, + ("SOCKET", "socket_down -> controlling process exit\r\n") ); + + descP->state = SOCKET_STATE_CLOSING; + descP->closeLocal = TRUE; + descP->closerPid = *pid; + descP->closerMon = (ESockMonitor) *mon; + descP->closeRef = MKREF(env); // Do we really need this in this case? + + /* + esock_dbg_printf("DOWN", + "[%d] select stop %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + selectRes = enif_select(env, descP->sock, (ERL_NIF_SELECT_STOP), + descP, NULL, descP->closeRef); + + if (selectRes & ERL_NIF_SELECT_STOP_CALLED) { + /* We are done - we can finalize (socket close) directly */ + SSDBG( descP, + ("SOCKET", + "socket_down -> [%d] stop called\r\n", descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + descP->state = SOCKET_STATE_CLOSED; + + /* And finally close the socket. + * Since we close the socket because of an exiting owner, + * we do not need to wait for buffers to sync (linger). + * If the owner wish to ensure the buffer are written, + * it should have closed the socket explicitly... + */ + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + descP->sock = INVALID_SOCKET; + descP->event = INVALID_EVENT; + + descP->state = SOCKET_STATE_CLOSED; + + } else if (selectRes & ERL_NIF_SELECT_STOP_SCHEDULED) { + /* The stop callback function has been *scheduled* which means + * that "should" wait for it to complete. But since we are in + * a callback (down) function, we cannot... + * So, we must close the socket + */ + SSDBG( descP, + ("SOCKET", + "socket_down -> [%d] stop scheduled\r\n", + descP->sock) ); + dec_socket(descP->domain, descP->type, descP->protocol); + + /* And now what? We can't wait for the stop function here... + * So, we simply close it here and leave the rest of the "close" + * for later (when the stop function actually gets called... + */ + + if (sock_close(descP->sock) != 0) { + int save_errno = sock_errno(); + + esock_warning_msg("Failed closing socket for terminating " + "controlling process: " + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Errno: %d" + "\r\n", pid, descP->sock, save_errno); + } + sock_close_event(descP->event); + + } else { + + /* + * <KOLLA> + * + * WE SHOULD REALLY HAVE A WAY TO CLOBBER THE SOCKET, + * SO WE DON'T LET STUFF LEAK. + * NOW, BECAUSE WE FAILED TO SELECT, WE CANNOT FINISH + * THE CLOSE, WHAT TO DO? ABORT? + * + * </KOLLA> + */ + esock_warning_msg("Failed selecting stop when handling down " + "of controlling process: " + "\r\n Select Res: %d" + "\r\n Controlling Process: %T" + "\r\n Descriptor: %d" + "\r\n Monitor: %u.%u.%u.%u" + "\r\n", selectRes, pid, descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + } + + } else { + + /* check all operation queue(s): acceptor, writer and reader. + * + * Is it really any point in doing this if the socket is closed? + * + */ + + SSDBG( descP, ("SOCKET", "socket_down -> other process term\r\n") ); + + MLOCK(descP->accMtx); + if (descP->currentAcceptorP != NULL) + socket_down_acceptor(env, descP, pid); + MUNLOCK(descP->accMtx); + + MLOCK(descP->writeMtx); + if (descP->currentWriterP != NULL) + socket_down_writer(env, descP, pid); + MUNLOCK(descP->writeMtx); + + MLOCK(descP->readMtx); + if (descP->currentReaderP != NULL) + socket_down_reader(env, descP, pid); + MUNLOCK(descP->readMtx); + + } + } + + /* + esock_dbg_printf("DOWN", + "[%d] end %u,%u,%u,%d\r\n", + descP->sock, + monP->raw[0], monP->raw[1], + monP->raw[2], monP->raw[3]); + */ + + SSDBG( descP, ("SOCKET", "socket_down -> done\r\n") ); + +#endif // if !defined(__WIN32__) +} + + + +/* *** socket_down_acceptor *** + * + * Check and then handle a downed acceptor process. + * + */ +#if !defined(__WIN32__) +static +void socket_down_acceptor(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + if (compare_pids(env, &descP->currentAcceptor.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> " + "current acceptor - try pop the queue\r\n") ); + + if (acceptor_pop(env, descP, + &descP->currentAcceptor.pid, + &descP->currentAcceptor.mon, + &descP->currentAcceptor.ref)) { + int res; + + /* There was another one, so we will still be in accepting state */ + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> new (active) acceptor: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentAcceptor.pid, + descP->currentAcceptor.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentAcceptor.pid, + descP->currentAcceptor.ref) < 0)) { + + esock_warning_msg("Failed select (%d) for new acceptor " + "after current (%T) died\r\n", + res, *pid); + + } + + } else { + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> no active acceptor\r\n") ); + + descP->currentAcceptorP = NULL; + descP->state = SOCKET_STATE_LISTENING; + } + + } else { + + /* Maybe unqueue one of the waiting acceptors */ + + SSDBG( descP, ("SOCKET", + "socket_down_acceptor -> " + "not current acceptor - maybe a waiting acceptor\r\n") ); + + acceptor_unqueue(env, descP, pid); + } +} + + + + +/* *** socket_down_writer *** + * + * Check and then handle a downed writer process. + * + */ +static +void socket_down_writer(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + if (compare_pids(env, &descP->currentWriter.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down_writer -> " + "current writer - try pop the queue\r\n") ); + + if (writer_pop(env, descP, + &descP->currentWriter.pid, + &descP->currentWriter.mon, + &descP->currentWriter.ref)) { + int res; + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "socket_down_writer -> new (current) writer: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentWriter.pid, + descP->currentWriter.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_WRITE), + descP, + &descP->currentWriter.pid, + descP->currentWriter.ref) < 0)) { + + esock_warning_msg("Failed select (%d) for new writer " + "after current (%T) died\r\n", + res, *pid); + + } + + } else { + + SSDBG( descP, ("SOCKET", + "socket_down_writer -> no active writer\r\n") ); + + descP->currentWriterP = NULL; + } + + } else { + + /* Maybe unqueue one of the waiting writer(s) */ + + SSDBG( descP, ("SOCKET", + "socket_down_writer -> " + "not current writer - maybe a waiting writer\r\n") ); + + writer_unqueue(env, descP, pid); + } +} + + + + +/* *** socket_down_reader *** + * + * Check and then handle a downed reader process. + * + */ +static +void socket_down_reader(ErlNifEnv* env, + SocketDescriptor* descP, + const ErlNifPid* pid) +{ + if (compare_pids(env, &descP->currentReader.pid, pid)) { + + SSDBG( descP, ("SOCKET", + "socket_down_reader -> " + "current reader - try pop the queue\r\n") ); + + if (reader_pop(env, descP, + &descP->currentReader.pid, + &descP->currentReader.mon, + &descP->currentReader.ref)) { + int res; + + /* There was another one */ + + SSDBG( descP, ("SOCKET", "socket_down_reader -> new (current) reader: " + "\r\n pid: %T" + "\r\n ref: %T" + "\r\n", + descP->currentReader.pid, + descP->currentReader.ref) ); + + if ((res = enif_select(env, + descP->sock, + (ERL_NIF_SELECT_READ), + descP, + &descP->currentReader.pid, + descP->currentReader.ref) < 0)) { + + esock_warning_msg("Failed select (%d) for new reader " + "after current (%T) died\r\n", + res, *pid); + + } + + } else { + + SSDBG( descP, ("SOCKET", + "socket_down_reader -> no active reader\r\n") ); + + descP->currentReaderP = NULL; + } + + } else { + + /* Maybe unqueue one of the waiting reader(s) */ + + SSDBG( descP, ("SOCKET", + "socket_down_reader -> " + "not current reader - maybe a waiting reader\r\n") ); + + reader_unqueue(env, descP, pid); + } +} +#endif // if !defined(__WIN32__) + + + +/* ---------------------------------------------------------------------- + * L o a d / u n l o a d / u p g r a d e F u n c t i o n s + * ---------------------------------------------------------------------- + */ + +static +ErlNifFunc socket_funcs[] = +{ + // Some utility and support functions + {"nif_info", 0, nif_info, 0}, + {"nif_supports", 1, nif_supports, 0}, + // {"nif_debug", 1, nif_debug, 0}, + // {"nif_command", 1, nif_command, 0}, + + // The proper "socket" interface + // nif_open/1 is used when we already have a file descriptor + // {"nif_open", 1, nif_open, 0}, + {"nif_open", 4, nif_open, 0}, + {"nif_bind", 2, nif_bind, 0}, + {"nif_connect", 2, nif_connect, 0}, + {"nif_listen", 2, nif_listen, 0}, + {"nif_accept", 2, nif_accept, 0}, + {"nif_send", 4, nif_send, 0}, + {"nif_sendto", 5, nif_sendto, 0}, + {"nif_sendmsg", 4, nif_sendmsg, 0}, + {"nif_recv", 4, nif_recv, 0}, + {"nif_recvfrom", 4, nif_recvfrom, 0}, + {"nif_recvmsg", 5, nif_recvmsg, 0}, + {"nif_close", 1, nif_close, 0}, + {"nif_shutdown", 2, nif_shutdown, 0}, + {"nif_setopt", 5, nif_setopt, 0}, + {"nif_getopt", 4, nif_getopt, 0}, + {"nif_sockname", 1, nif_sockname, 0}, + {"nif_peername", 1, nif_peername, 0}, + + /* Misc utility functions */ + + /* "Extra" functions to "complete" the socket interface. + * For instance, the function nif_finalize_connection + * is called after the connect *select* has "completed". + */ + {"nif_finalize_connection", 1, nif_finalize_connection, 0}, + {"nif_cancel", 3, nif_cancel, 0}, + {"nif_finalize_close", 1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND} +}; + + +#if !defined(__WIN32__) +static +BOOLEAN_T extract_debug(ErlNifEnv* env, + ERL_NIF_TERM map) +{ + /* + * We need to do this here since the "proper" atom has not been + * created when this function is called. + */ + ERL_NIF_TERM debug = MKA(env, "debug"); + + return esock_extract_bool_from_map(env, map, debug, SOCKET_GLOBAL_DEBUG_DEFAULT); +} + +static +BOOLEAN_T extract_iow(ErlNifEnv* env, + ERL_NIF_TERM map) +{ + /* + * We need to do this here since the "proper" atom has not been + * created when this function is called. + */ + ERL_NIF_TERM iow = MKA(env, "iow"); + + return esock_extract_bool_from_map(env, map, iow, SOCKET_NIF_IOW_DEFAULT); +} +#endif // if !defined(__WIN32__) + + + +/* ======================================================================= + * load_info - A map of misc info (e.g global debug) + */ + +static +int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ +#if !defined(__WIN32__) + esock_dbg_init(ESOCK_DBGOUT_DEFAULT); + // esock_dbg_init(ESOCK_DBGOUT_UNIQUE); + + data.dbg = extract_debug(env, load_info); + data.iow = extract_iow(env, load_info); + + /* +++ Global Counters +++ */ + data.cntMtx = MCREATE("socket[gcnt]"); + data.numSockets = 0; + data.numTypeDGrams = 0; + data.numTypeStreams = 0; + data.numTypeSeqPkgs = 0; + data.numDomainLocal = 0; + data.numDomainInet = 0; + data.numDomainInet6 = 0; + data.numProtoIP = 0; + data.numProtoTCP = 0; + data.numProtoUDP = 0; + data.numProtoSCTP = 0; +#endif + + /* +++ Misc atoms +++ */ + atom_adaptation_layer = MKA(env, str_adaptation_layer); + atom_address = MKA(env, str_address); + atom_association = MKA(env, str_association); + atom_assoc_id = MKA(env, str_assoc_id); + atom_authentication = MKA(env, str_authentication); + atom_bool = MKA(env, str_bool); + atom_close = MKA(env, str_close); + atom_closed = MKA(env, str_closed); + atom_closing = MKA(env, str_closing); + atom_cookie_life = MKA(env, str_cookie_life); + atom_data_in = MKA(env, str_data_in); + atom_do = MKA(env, str_do); + atom_dont = MKA(env, str_dont); + atom_exclude = MKA(env, str_exclude); + atom_false = MKA(env, str_false); + atom_global_counters = MKA(env, str_global_counters); + atom_in4_sockaddr = MKA(env, str_in4_sockaddr); + atom_in6_sockaddr = MKA(env, str_in6_sockaddr); + atom_include = MKA(env, str_include); + atom_initial = MKA(env, str_initial); + atom_int = MKA(env, str_int); + atom_interface = MKA(env, str_interface); + atom_iow = MKA(env, str_iow); + atom_local_rwnd = MKA(env, str_local_rwnd); + atom_max = MKA(env, str_max); + atom_max_attempts = MKA(env, str_max_attempts); + atom_max_init_timeo = MKA(env, str_max_init_timeo); + atom_max_instreams = MKA(env, str_max_instreams); + atom_max_rxt = MKA(env, str_max_rxt); + atom_min = MKA(env, str_min); + atom_mode = MKA(env, str_mode); + atom_multiaddr = MKA(env, str_multiaddr); + // atom_nif_abort = MKA(env, str_nif_abort); + atom_null = MKA(env, str_null); + atom_num_dinet = MKA(env, str_num_dinet); + atom_num_dinet6 = MKA(env, str_num_dinet6); + atom_num_dlocal = MKA(env, str_num_dlocal); + atom_num_outstreams = MKA(env, str_num_outstreams); + atom_num_peer_dests = MKA(env, str_num_peer_dests); + atom_num_pip = MKA(env, str_num_pip); + atom_num_psctp = MKA(env, str_num_psctp); + atom_num_ptcp = MKA(env, str_num_ptcp); + atom_num_pudp = MKA(env, str_num_pudp); + atom_num_sockets = MKA(env, str_num_sockets); + atom_num_tdgrams = MKA(env, str_num_tdgrams); + atom_num_tseqpkgs = MKA(env, str_num_tseqpkgs); + atom_num_tstreams = MKA(env, str_num_tstreams); + atom_partial_delivery = MKA(env, str_partial_delivery); + atom_peer_rwnd = MKA(env, str_peer_rwnd); + atom_peer_error = MKA(env, str_peer_error); + atom_probe = MKA(env, str_probe); + atom_select = MKA(env, str_select); + atom_sender_dry = MKA(env, str_sender_dry); + atom_send_failure = MKA(env, str_send_failure); + atom_shutdown = MKA(env, str_shutdown); + atom_slist = MKA(env, str_slist); + atom_sourceaddr = MKA(env, str_sourceaddr); + atom_timeout = MKA(env, str_timeout); + atom_true = MKA(env, str_true); + atom_want = MKA(env, str_want); + + /* Global atom(s) */ + esock_atom_abort = MKA(env, "abort"); + esock_atom_accept = MKA(env, "accept"); + esock_atom_acceptconn = MKA(env, "acceptconn"); + esock_atom_acceptfilter = MKA(env, "acceptfilter"); + esock_atom_adaption_layer = MKA(env, "adaption_layer"); + esock_atom_addr = MKA(env, "addr"); + esock_atom_addrform = MKA(env, "addrform"); + esock_atom_add_membership = MKA(env, "add_membership"); + esock_atom_add_source_membership = MKA(env, "add_source_membership"); + esock_atom_any = MKA(env, "any"); + esock_atom_associnfo = MKA(env, "associnfo"); + esock_atom_authhdr = MKA(env, "authhdr"); + esock_atom_auth_active_key = MKA(env, "auth_active_key"); + esock_atom_auth_asconf = MKA(env, "auth_asconf"); + esock_atom_auth_chunk = MKA(env, "auth_chunk"); + esock_atom_auth_delete_key = MKA(env, "auth_delete_key"); + esock_atom_auth_key = MKA(env, "auth_key"); + esock_atom_auth_level = MKA(env, "auth_level"); + esock_atom_autoclose = MKA(env, "autoclose"); + esock_atom_bindtodevice = MKA(env, "bindtodevice"); + esock_atom_block_source = MKA(env, "block_source"); + esock_atom_broadcast = MKA(env, "broadcast"); + esock_atom_busy_poll = MKA(env, "busy_poll"); + esock_atom_checksum = MKA(env, "checksum"); + esock_atom_close = MKA(env, "close"); + esock_atom_connect = MKA(env, "connect"); + esock_atom_congestion = MKA(env, "congestion"); + esock_atom_context = MKA(env, "context"); + esock_atom_cork = MKA(env, "cork"); + esock_atom_credentials = MKA(env, "credentials"); + esock_atom_ctrl = MKA(env, "ctrl"); + esock_atom_ctrunc = MKA(env, "ctrunc"); + esock_atom_data = MKA(env, "data"); + esock_atom_debug = MKA(env, "debug"); + esock_atom_default_send_params = MKA(env, "default_send_params"); + esock_atom_delayed_ack_time = MKA(env, "delayed_ack_time"); + esock_atom_dgram = MKA(env, "dgram"); + esock_atom_disable_fragments = MKA(env, "disable_fragments"); + esock_atom_domain = MKA(env, "domain"); + esock_atom_dontfrag = MKA(env, "dontfrag"); + esock_atom_dontroute = MKA(env, "dontroute"); + esock_atom_drop_membership = MKA(env, "drop_membership"); + esock_atom_drop_source_membership = MKA(env, "drop_source_membership"); + esock_atom_dstopts = MKA(env, "dstpopts"); + esock_atom_eor = MKA(env, "eor"); + esock_atom_error = MKA(env, "error"); + esock_atom_errqueue = MKA(env, "errqueue"); + esock_atom_esp_network_level = MKA(env, "esp_network_level"); + esock_atom_esp_trans_level = MKA(env, "esp_trans_level"); + esock_atom_events = MKA(env, "events"); + esock_atom_explicit_eor = MKA(env, "explicit_eor"); + esock_atom_faith = MKA(env, "faith"); + esock_atom_false = MKA(env, "false"); + esock_atom_family = MKA(env, "family"); + esock_atom_flags = MKA(env, "flags"); + esock_atom_flowinfo = MKA(env, "flowinfo"); + esock_atom_fragment_interleave = MKA(env, "fragment_interleave"); + esock_atom_freebind = MKA(env, "freebind"); + esock_atom_get_peer_addr_info = MKA(env, "get_peer_addr_info"); + esock_atom_hdrincl = MKA(env, "hdrincl"); + esock_atom_hmac_ident = MKA(env, "hmac_ident"); + esock_atom_hoplimit = MKA(env, "hoplimit"); + esock_atom_hopopts = MKA(env, "hopopts"); + esock_atom_ifindex = MKA(env, "ifindex"); + esock_atom_inet = MKA(env, "inet"); + esock_atom_inet6 = MKA(env, "inet6"); + esock_atom_info = MKA(env, "info"); + esock_atom_initmsg = MKA(env, "initmsg"); + esock_atom_iov = MKA(env, "iov"); + esock_atom_ip = MKA(env, "ip"); + esock_atom_ipcomp_level = MKA(env, "ipcomp_level"); + esock_atom_ipv6 = MKA(env, "ipv6"); + esock_atom_i_want_mapped_v4_addr = MKA(env, "i_want_mapped_v4_addr"); + esock_atom_join_group = MKA(env, "join_group"); + esock_atom_keepalive = MKA(env, "keepalive"); + esock_atom_keepcnt = MKA(env, "keepcnt"); + esock_atom_keepidle = MKA(env, "keepidle"); + esock_atom_keepintvl = MKA(env, "keepintvl"); + esock_atom_leave_group = MKA(env, "leave_group"); + esock_atom_level = MKA(env, "level"); + esock_atom_linger = MKA(env, "linger"); + esock_atom_local = MKA(env, "local"); + esock_atom_local_auth_chunks = MKA(env, "local_auth_chunks"); + esock_atom_loopback = MKA(env, "loopback"); + esock_atom_lowdelay = MKA(env, "lowdelay"); + esock_atom_mark = MKA(env, "mark"); + esock_atom_maxburst = MKA(env, "maxburst"); + esock_atom_maxseg = MKA(env, "maxseg"); + esock_atom_md5sig = MKA(env, "md5sig"); + esock_atom_mincost = MKA(env, "mincost"); + esock_atom_minttl = MKA(env, "minttl"); + esock_atom_msfilter = MKA(env, "msfilter"); + esock_atom_mtu = MKA(env, "mtu"); + esock_atom_mtu_discover = MKA(env, "mtu_discover"); + esock_atom_multicast_all = MKA(env, "multicast_all"); + esock_atom_multicast_hops = MKA(env, "multicast_hops"); + esock_atom_multicast_if = MKA(env, "multicast_if"); + esock_atom_multicast_loop = MKA(env, "multicast_loop"); + esock_atom_multicast_ttl = MKA(env, "multicast_ttl"); + esock_atom_nodefrag = MKA(env, "nodefrag"); + esock_atom_nodelay = MKA(env, "nodelay"); + esock_atom_noopt = MKA(env, "noopt"); + esock_atom_nopush = MKA(env, "nopush"); + esock_atom_not_found = MKA(env, "not_found"); + esock_atom_not_owner = MKA(env, "not_owner"); + esock_atom_ok = MKA(env, "ok"); + esock_atom_oob = MKA(env, "oob"); + esock_atom_oobinline = MKA(env, "oobinline"); + esock_atom_options = MKA(env, "options"); + esock_atom_origdstaddr = MKA(env, "origdstaddr"); + esock_atom_partial_delivery_point = MKA(env, "partial_delivery_point"); + esock_atom_passcred = MKA(env, "passcred"); + esock_atom_path = MKA(env, "path"); + esock_atom_peekcred = MKA(env, "peekcred"); + esock_atom_peek_off = MKA(env, "peek_off"); + esock_atom_peer_addr_params = MKA(env, "peer_addr_params"); + esock_atom_peer_auth_chunks = MKA(env, "peer_auth_chunks"); + esock_atom_pktinfo = MKA(env, "pktinfo"); + esock_atom_pktoptions = MKA(env, "pktoptions"); + esock_atom_port = MKA(env, "port"); + esock_atom_portrange = MKA(env, "portrange"); + esock_atom_primary_addr = MKA(env, "primary_addr"); + esock_atom_priority = MKA(env, "priority"); + esock_atom_protocol = MKA(env, "protocol"); + esock_atom_raw = MKA(env, "raw"); + esock_atom_rcvbuf = MKA(env, "rcvbuf"); + esock_atom_rcvbufforce = MKA(env, "rcvbufforce"); + esock_atom_rcvlowat = MKA(env, "rcvlowat"); + esock_atom_rcvtimeo = MKA(env, "rcvtimeo"); + esock_atom_rdm = MKA(env, "rdm"); + esock_atom_recv = MKA(env, "recv"); + esock_atom_recvdstaddr = MKA(env, "recvdstaddr"); + esock_atom_recverr = MKA(env, "recverr"); + esock_atom_recvfrom = MKA(env, "recvfrom"); + esock_atom_recvif = MKA(env, "recvif"); + esock_atom_recvmsg = MKA(env, "recvmsg"); + esock_atom_recvopts = MKA(env, "recvopts"); + esock_atom_recvorigdstaddr = MKA(env, "recvorigdstaddr"); + esock_atom_recvpktinfo = MKA(env, "recvpktinfo"); + esock_atom_recvtclass = MKA(env, "recvtclass"); + esock_atom_recvtos = MKA(env, "recvtos"); + esock_atom_recvttl = MKA(env, "recvttl"); + esock_atom_reliability = MKA(env, "reliability"); + esock_atom_reset_streams = MKA(env, "reset_streams"); + esock_atom_retopts = MKA(env, "retopts"); + esock_atom_reuseaddr = MKA(env, "reuseaddr"); + esock_atom_reuseport = MKA(env, "reuseport"); + esock_atom_rights = MKA(env, "rights"); + esock_atom_router_alert = MKA(env, "router_alert"); + esock_atom_rthdr = MKA(env, "rthdr"); + esock_atom_rtoinfo = MKA(env, "rtoinfo"); + esock_atom_rxq_ovfl = MKA(env, "rxq_ovfl"); + esock_atom_scope_id = MKA(env, "scope_id"); + esock_atom_sctp = MKA(env, "sctp"); + esock_atom_sec = MKA(env, "sec"); + esock_atom_select_sent = MKA(env, "select_sent"); + esock_atom_send = MKA(env, "send"); + esock_atom_sendmsg = MKA(env, "sendmsg"); + esock_atom_sendsrcaddr = MKA(env, "sendsrcaddr"); + esock_atom_sendto = MKA(env, "sendto"); + esock_atom_seqpacket = MKA(env, "seqpacket"); + esock_atom_setfib = MKA(env, "setfib"); + esock_atom_set_peer_primary_addr = MKA(env, "set_peer_primary_addr"); + esock_atom_sndbuf = MKA(env, "sndbuf"); + esock_atom_sndbufforce = MKA(env, "sndbufforce"); + esock_atom_sndlowat = MKA(env, "sndlowat"); + esock_atom_sndtimeo = MKA(env, "sndtimeo"); + esock_atom_socket = MKA(env, "socket"); + esock_atom_socket_tag = MKA(env, "$socket"); + esock_atom_spec_dst = MKA(env, "spec_dst"); + esock_atom_status = MKA(env, "status"); + esock_atom_stream = MKA(env, "stream"); + esock_atom_syncnt = MKA(env, "syncnt"); + esock_atom_tclass = MKA(env, "tclass"); + esock_atom_tcp = MKA(env, "tcp"); + esock_atom_throughput = MKA(env, "throughput"); + esock_atom_timestamp = MKA(env, "timestamp"); + esock_atom_tos = MKA(env, "tos"); + esock_atom_transparent = MKA(env, "transparent"); + esock_atom_true = MKA(env, "true"); + esock_atom_trunc = MKA(env, "trunc"); + esock_atom_ttl = MKA(env, "ttl"); + esock_atom_type = MKA(env, "type"); + esock_atom_udp = MKA(env, "udp"); + esock_atom_unblock_source = MKA(env, "unblock_source"); + esock_atom_undefined = MKA(env, "undefined"); + esock_atom_unicast_hops = MKA(env, "unicast_hops"); + esock_atom_unknown = MKA(env, "unknown"); + esock_atom_usec = MKA(env, "usec"); + esock_atom_user_timeout = MKA(env, "user_timeout"); + esock_atom_use_ext_recvinfo = MKA(env, "use_ext_recvinfo"); + esock_atom_use_min_mtu = MKA(env, "use_min_mtu"); + esock_atom_v6only = MKA(env, "v6only"); + + /* Global error codes */ + esock_atom_eafnosupport = MKA(env, ESOCK_STR_EAFNOSUPPORT); + esock_atom_eagain = MKA(env, ESOCK_STR_EAGAIN); + esock_atom_einval = MKA(env, ESOCK_STR_EINVAL); + + /* Error codes */ + atom_eisconn = MKA(env, str_eisconn); + atom_enotclosing = MKA(env, str_enotclosing); + atom_enotconn = MKA(env, str_enotconn); + atom_exalloc = MKA(env, str_exalloc); + atom_exbadstate = MKA(env, str_exbadstate); + atom_exbusy = MKA(env, str_exbusy); + atom_exmon = MKA(env, str_exmon); + atom_exself = MKA(env, str_exself); + atom_exsend = MKA(env, str_exsend); + + // For storing "global" things... + // data.env = enif_alloc_env(); // We should really check + // data.version = MKA(env, ERTS_VERSION); + // data.buildDate = MKA(env, ERTS_BUILD_DATE); + + sockets = enif_open_resource_type_x(env, + "sockets", + &socketInit, + ERL_NIF_RT_CREATE, + NULL); + + return !sockets; +} + +ERL_NIF_INIT(socket, socket_funcs, on_load, NULL, NULL, NULL) diff --git a/erts/emulator/nifs/common/socket_tarray.c b/erts/emulator/nifs/common/socket_tarray.c new file mode 100644 index 0000000000..def22c4919 --- /dev/null +++ b/erts/emulator/nifs/common/socket_tarray.c @@ -0,0 +1,143 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2019. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Build and "maintain" a (erlang) term array of + * variable length. + * ---------------------------------------------------------------------- + * + */ + +/* #ifdef HAVE_CONFIG_H */ +/* #include "config.h" */ +/* #endif */ + +#include <stdio.h> + +#include <erl_nif.h> + +#include "socket_int.h" +#include <sys.h> +#include "socket_util.h" +#include "socket_tarray.h" + + + +/* ---------------------------------------------------------------------- + * Types + */ + +typedef struct { + Uint32 sz; + Uint32 idx; + ERL_NIF_TERM* array; +} SocketTArrayInt; + + +/* ---------------------------------------------------------------------- + * Forward for internal functions + */ + +static void esock_tarray_add1(SocketTArrayInt* taP, ERL_NIF_TERM t); +static void esock_tarray_ensure_fits(SocketTArrayInt* taP, Uint32 needs); + + +/* ---------------------------------------------------------------------- + * API + */ + +extern +void* esock_tarray_create(Uint32 sz) +{ + SocketTArrayInt* tarrayP; + + ESOCK_ASSERT( (sz > 0) ); + + tarrayP = MALLOC(sizeof(SocketTArrayInt)); + ESOCK_ASSERT( (tarrayP != NULL) ); + + tarrayP->array = MALLOC(sz * sizeof(ERL_NIF_TERM)); + ESOCK_ASSERT( (tarrayP->array != NULL) ); + tarrayP->sz = sz; + tarrayP->idx = 0; + + return ((SocketTArray) tarrayP); +} + +extern +void esock_tarray_delete(SocketTArray ta) +{ + SocketTArrayInt* taP = (SocketTArrayInt*) ta; + + FREE(taP->array); + FREE(taP); +} + + +extern +Uint32 esock_tarray_sz(SocketTArray a) +{ + return ( ((SocketTArrayInt*) a)->idx ); +} + +extern +void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t) +{ + esock_tarray_add1((SocketTArrayInt*) ta, t); +} + +extern +void esock_tarray_tolist(SocketTArray ta, + ErlNifEnv* env, + ERL_NIF_TERM* list) +{ + SocketTArrayInt* taP = (SocketTArrayInt*) ta; + + *list = MKLA(env, taP->array, taP->idx); + + esock_tarray_delete(taP); +} + + + +/* ---------------------------------------------------------------------- + * "Internal" functions + */ + +static +void esock_tarray_add1(SocketTArrayInt* taP, ERL_NIF_TERM t) +{ + esock_tarray_ensure_fits(taP, 1); + + taP->array[taP->idx++] = t; +} + +static +void esock_tarray_ensure_fits(SocketTArrayInt* taP, Uint32 needs) +{ + if (taP->sz < (taP->idx + needs)) { + Uint32 newSz = (needs < taP->sz) ? 2*taP->sz : 2*needs; + void* mem = REALLOC(taP->array, newSz * sizeof(ERL_NIF_TERM)); + + ESOCK_ASSERT( (mem != NULL) ); + + taP->sz = newSz; + taP->array = (ERL_NIF_TERM*) mem; + } +} diff --git a/erts/emulator/nifs/common/socket_tarray.h b/erts/emulator/nifs/common/socket_tarray.h new file mode 100644 index 0000000000..4f1152fb9e --- /dev/null +++ b/erts/emulator/nifs/common/socket_tarray.h @@ -0,0 +1,47 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2019. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Build and "maintain" a (erlang) term array of + * variable length. + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_TARRAY_H__ +#define SOCKET_TARRAY_H__ + +typedef void* SocketTArray; + +extern SocketTArray esock_tarray_create(Uint32 sz); +extern void esock_tarray_delete(SocketTArray ta); +extern Uint32 esock_tarray_sz(SocketTArray ta); +extern void esock_tarray_add(SocketTArray ta, ERL_NIF_TERM t); +extern void esock_tarray_tolist(SocketTArray ta, + ErlNifEnv* env, + ERL_NIF_TERM* list); + +#define TARRAY_CREATE(SZ) esock_tarray_create((SZ)) +#define TARRAY_DELETE(TA) esock_tarray_delete((TA)) +#define TARRAY_SZ(TA) esock_tarray_sz((TA)) +#define TARRAY_ADD(TA, T) esock_tarray_add((TA), (T)) +#define TARRAY_TOLIST(TA, E, L) esock_tarray_tolist((TA), (E), (L)) + + +#endif // SOCKET_TARRAY_H__ diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c new file mode 100644 index 0000000000..f6e4781977 --- /dev/null +++ b/erts/emulator/nifs/common/socket_util.c @@ -0,0 +1,1658 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2018-2019. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + * + * ---------------------------------------------------------------------- + * Purpose : Utility functions for the socket and net NIF(s). + * ---------------------------------------------------------------------- + * + */ + +#include <stdarg.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <time.h> +#include <stddef.h> + +#include "socket_int.h" +#include "sys.h" +#include "socket_util.h" +#include "socket_dbg.h" + +/* We don't have a "debug flag" to check here, so we + * should use the compile debug flag, whatever that is... + */ + +// #define COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK 1 +#if defined(COMPILE_DEBUG_FLAG_WE_NEED_TO_CHECK) +#define UTIL_DEBUG TRUE +#else +#define UTIL_DEBUG FALSE +#endif + +#define UDBG( proto ) ESOCK_DBG_PRINTF( UTIL_DEBUG , proto ) + + +extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */ + +static int realtime(struct timespec* tsP); +static int timespec2str(char *buf, unsigned int len, struct timespec *ts); + +static char* make_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM* sa); +static char* make_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM flowInfo, + ERL_NIF_TERM scopeId, + ERL_NIF_TERM* sa); +static char* make_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM path, + ERL_NIF_TERM* sa); + + +/* +++ esock_encode_iov +++ + * + * Encode an IO Vector. In erlang we represented this as a list of binaries. + * + * We iterate through the IO vector, and as long as the remaining (rem) + * number of bytes is greater than the size of the current buffer, we + * contunue. When we have a buffer that is greater than rem, we have found + * the last buffer (it may be empty, and then the previous was last). + * We may need to split this (if 0 < rem < bufferSz). + */ + +extern +char* esock_encode_iov(ErlNifEnv* env, + int read, + struct iovec* iov, + size_t len, + ErlNifBinary* data, + ERL_NIF_TERM* eIOV) +{ + int rem = read; + Uint16 i; + BOOLEAN_T done = FALSE; + ERL_NIF_TERM a[len]; // At most this length + + UDBG( ("SUTIL", "esock_encode_iov -> entry with" + "\r\n read: %d" + "\r\n (IOV) len: %d" + "\r\n", read, len) ); + + if (len == 0) { + *eIOV = MKEL(env); + return NULL; + } + + for (i = 0; (!done) && (i < len); i++) { + UDBG( ("SUTIL", "esock_encode_iov -> process iov:" + "\r\n iov[%d].iov_len: %d" + "\r\n rem: %d" + "\r\n", i, iov[i].iov_len, rem) ); + if (iov[i].iov_len == rem) { + /* We have the exact amount - we are done */ + UDBG( ("SUTIL", "esock_encode_iov -> exact => done\r\n") ); + a[i] = MKBIN(env, &data[i]); + rem = 0; // Besserwisser + done = TRUE; + } else if (iov[i].iov_len < rem) { + /* Filled another buffer - continue */ + UDBG( ("SUTIL", "esock_encode_iov -> filled => continue\r\n") ); + a[i] = MKBIN(env, &data[i]); + rem -= iov[i].iov_len; + } else if (iov[i].iov_len > rem) { + /* Partly filled buffer (=> split) - we are done */ + ERL_NIF_TERM tmp; + UDBG( ("SUTIL", "esock_encode_iov -> split => done\r\n") ); + tmp = MKBIN(env, &data[i]); + a[i] = MKSBIN(env, tmp, 0, rem); + rem = 0; // Besserwisser + done = TRUE; + } + } + + UDBG( ("SUTIL", "esock_encode_iov -> create the IOV list (%d)\r\n", i) ); + + *eIOV = MKLA(env, a, i); + + UDBG( ("SUTIL", "esock_encode_msghdr -> done\r\n") ); + + return NULL; +} + + + +/* +++ esock_decode_iov +++ + * + * Decode an IO Vector. In erlang we represented this as a list of binaries. + * + * We assume that we have already figured out how long the iov (actually + * eIOV) is (len), and therefor allocated an array of bins and iov to be + * used. + */ + +extern +char* esock_decode_iov(ErlNifEnv* env, + ERL_NIF_TERM eIOV, + ErlNifBinary* bufs, + struct iovec* iov, + size_t len, + ssize_t* totSize) +{ + Uint16 i; + ssize_t sz; + ERL_NIF_TERM elem, tail, list; + + UDBG( ("SUTIL", "esock_decode_iov -> entry with" + "\r\n (IOV) len: %d" + "\r\n", read, len) ); + + for (i = 0, list = eIOV, sz = 0; (i < len); i++) { + + UDBG( ("SUTIL", "esock_decode_iov -> " + "\r\n iov[%d].iov_len: %d" + "\r\n rem: %d" + "\r\n", i) ); + + if (!GET_LIST_ELEM(env, list, &elem, &tail)) + return ESOCK_STR_EINVAL; + + if (IS_BIN(env, elem) && GET_BIN(env, elem, &bufs[i])) { + iov[i].iov_base = bufs[i].data; + iov[i].iov_len = bufs[i].size; + sz += bufs[i].size; + } else { + return ESOCK_STR_EINVAL; + } + + list = tail; + } + + *totSize = sz; + + UDBG( ("SUTIL", "esock_decode_msghdr -> done (%d)\r\n", sz) ); + + return NULL; +} + + + +/* +++ esock_decode_sockaddr +++ + * + * Decode a socket address - sockaddr. In erlang its represented as + * a map, which has a specific set of attributes, depending on one + * mandatory attribute; family. So depending on the value of the family + * attribute: + * + * local - sockaddr_un: path + * inet - sockaddr_in4: port, addr + * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id + */ + +extern +char* esock_decode_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLen) +{ + ERL_NIF_TERM efam; + int fam; + char* xres; + + UDBG( ("SUTIL", "esock_decode_sockaddr -> entry\r\n") ); + + if (!IS_MAP(env, eSockAddr)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_family, &efam)) + return ESOCK_STR_EINVAL; + + UDBG( ("SUTIL", "esock_decode_sockaddr -> try decode domain (%T)\r\n", efam) ); + if ((xres = esock_decode_domain(env, efam, &fam)) != NULL) + return xres; + + UDBG( ("SUTIL", "esock_decode_sockaddr -> fam: %d\r\n", fam) ); + switch (fam) { + case AF_INET: + xres = esock_decode_sockaddr_in4(env, eSockAddr, + &sockAddrP->in4, addrLen); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + xres = esock_decode_sockaddr_in6(env, eSockAddr, + &sockAddrP->in6, addrLen); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + xres = esock_decode_sockaddr_un(env, eSockAddr, + &sockAddrP->un, addrLen); + break; +#endif + + default: + xres = ESOCK_STR_EAFNOSUPPORT; + break; + + } + + return xres; +} + + + +/* +++ esock_encode_sockaddr +++ + * + * Encode a socket address - sockaddr. In erlang its represented as + * a map, which has a specific set of attributes, depending on one + * mandatory attribute; family. So depending on the value of the family + * attribute: + * + * local - sockaddr_un: path + * inet - sockaddr_in4: port, addr + * inet6 - sockaddr_in6: port, addr, flowinfo, scope_id + */ + +extern +char* esock_encode_sockaddr(ErlNifEnv* env, + SocketAddress* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + char* xres; + + UDBG( ("SUTIL", "esock_encode_sockaddr -> entry with" + "\r\n family: %d" + "\r\n addrLen: %d" + "\r\n", sockAddrP->sa.sa_family, addrLen) ); + + switch (sockAddrP->sa.sa_family) { + case AF_INET: + xres = esock_encode_sockaddr_in4(env, &sockAddrP->in4, addrLen, eSockAddr); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + xres = esock_encode_sockaddr_in6(env, &sockAddrP->in6, addrLen, eSockAddr); + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + xres = esock_encode_sockaddr_un(env, &sockAddrP->un, addrLen, eSockAddr); + break; +#endif + + default: + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EAFNOSUPPORT; + break; + + } + + return xres; +} + + + +/* +++ esock_decode_sockaddr_in4 +++ + * + * Decode a IPv4 socket address - sockaddr_in4. In erlang its represented as + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): + * + * port :: port_numbber() + * addr :: ip4_address() + * + * The erlang module ensures that both of these has values exist, so there + * is no need for any elaborate error handling. + */ + +extern +char* esock_decode_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen) +{ + ERL_NIF_TERM eport, eaddr; + int port; + char* xres; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> entry\r\n") ); + + /* Basic init */ + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in)); + +#ifndef NO_SA_LEN + sockAddrP->sin_len = sizeof(struct sockaddr_in); +#endif + + sockAddrP->sin_family = AF_INET; + + /* Extract (e) port number from map */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try get port number\r\n") ); + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) + return ESOCK_STR_EINVAL; + + /* Decode port number */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode port number\r\n") ); + if (!GET_INT(env, eport, &port)) + return ESOCK_STR_EINVAL; + + sockAddrP->sin_port = htons(port); + + /* Extract (e) address from map */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try get (ip) address\r\n") ); + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) + return ESOCK_STR_EINVAL; + + /* Decode address */ + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> try decode (ip) address\r\n") ); + if ((xres = esock_decode_ip4_address(env, + eaddr, + &sockAddrP->sin_addr)) != NULL) + return xres; + + *addrLen = sizeof(struct sockaddr_in); + + UDBG( ("SUTIL", "esock_decode_sockaddr_in4 -> done\r\n") ); + + return NULL; +} + + + +/* +++ esock_encode_sockaddr_in4 +++ + * + * Encode a IPv4 socket address - sockaddr_in4. In erlang its represented as + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): + * + * port :: port_numbber() + * addr :: ip4_address() + * + */ + +extern +char* esock_encode_sockaddr_in4(ErlNifEnv* env, + struct sockaddr_in* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM ePort, eAddr; + int port; + char* xres = NULL; + + UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> entry\r\n") ); + + if (addrLen >= sizeof(struct sockaddr_in)) { + /* The port */ + port = ntohs(sockAddrP->sin_port); + ePort = MKI(env, port); + + /* The address */ + if ((xres = esock_encode_ip4_address(env, &sockAddrP->sin_addr, + &eAddr)) == NULL) { + /* And finally construct the in4_sockaddr record */ + xres = make_sockaddr_in4(env, ePort, eAddr, eSockAddr); + } else { + UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> " + "failed encoding (ip) address: " + "\r\n xres: %s" + "\r\n", xres) ); + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + } else { + UDBG( ("SUTIL", "esock_encode_sockaddr_in4 -> wrong size: " + "\r\n addrLen: %d" + "\r\n addr size: %d" + "\r\n", addrLen, sizeof(struct sockaddr_in)) ); + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} + + + +/* +++ esock_decode_sockaddr_in6 +++ + * + * Decode a IPv6 socket address - sockaddr_in6. In erlang its represented as + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): + * + * port :: port_numbber() (integer) + * addr :: ip6_address() (tuple) + * flowinfo :: in6_flow_info() (integer) + * scope_id :: in6_scope_id() (integer) + * + * The erlang module ensures that all of these has values exist, so there + * is no need for any elaborate error handling here. + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen) +{ + ERL_NIF_TERM eport, eaddr, eflowInfo, escopeId; + int port; + unsigned int flowInfo, scopeId; + char* xres; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> entry\r\n") ); + + /* Basic init */ + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + sockAddrP->sin6_len = sizeof(struct sockaddr_in); +#endif + + sockAddrP->sin6_family = AF_INET6; + + /* *** Extract (e) port number from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_port, &eport)) + return ESOCK_STR_EINVAL; + + /* Decode port number */ + if (!GET_INT(env, eport, &port)) + return ESOCK_STR_EINVAL; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> port: %d\r\n", port) ); + + sockAddrP->sin6_port = htons(port); + + /* *** Extract (e) flowinfo from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_flowinfo, &eflowInfo)) + return ESOCK_STR_EINVAL; + + /* 4: Get the flowinfo */ + if (!GET_UINT(env, eflowInfo, &flowInfo)) + return ESOCK_STR_EINVAL; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> flowinfo: %d\r\n", flowInfo) ); + + sockAddrP->sin6_flowinfo = flowInfo; + + /* *** Extract (e) scope_id from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_scope_id, &escopeId)) + return ESOCK_STR_EINVAL; + + /* *** Get the scope_id *** */ + if (!GET_UINT(env, escopeId, &scopeId)) + return ESOCK_STR_EINVAL; + + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> scopeId: %d\r\n", scopeId) ); + + sockAddrP->sin6_scope_id = scopeId; + + /* *** Extract (e) address from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eaddr)) + return ESOCK_STR_EINVAL; + + /* Decode address */ + if ((xres = esock_decode_ip6_address(env, + eaddr, + &sockAddrP->sin6_addr)) != NULL) + return xres; + + *addrLen = sizeof(struct sockaddr_in6); + + UDBG( ("SUTIL", "esock_decode_sockaddr_in6 -> done\r\n") ); + + return NULL; +} +#endif + + + +/* +++ esock_encode_sockaddr_in6 +++ + * + * Encode a IPv6 socket address - sockaddr_in6. In erlang its represented as + * a map, which has a specific set of attributes (beside the mandatory family + * attribute, which is "inherited" from the "sockaddr" type): + * + * port :: port_numbber() (integer) + * addr :: ip6_address() (tuple) + * flowinfo :: in6_flow_info() (integer) + * scope_id :: in6_scope_id() (integer) + * + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_encode_sockaddr_in6(ErlNifEnv* env, + struct sockaddr_in6* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM ePort, eAddr, eFlowInfo, eScopeId; + char* xres; + + if (addrLen >= sizeof(struct sockaddr_in6)) { + /* The port */ + ePort = MKI(env, ntohs(sockAddrP->sin6_port)); + + /* The flowInfo */ + eFlowInfo = MKI(env, sockAddrP->sin6_flowinfo); + + /* The scopeId */ + eScopeId = MKI(env, sockAddrP->sin6_scope_id); + + /* The address */ + if ((xres = esock_encode_ip6_address(env, &sockAddrP->sin6_addr, + &eAddr)) == NULL) { + /* And finally construct the in6_sockaddr record */ + xres = make_sockaddr_in6(env, + ePort, eAddr, eFlowInfo, eScopeId, eSockAddr); + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} +#endif + + + +/* +++ esock_decode_sockaddr_un +++ + * + * Decode a Unix Domain socket address - sockaddr_un. In erlang its + * represented as a map, which has a specific set of attributes + * (beside the mandatory family attribute, which is "inherited" from + * the "sockaddr" type): + * + * path :: binary() + * + * The erlang module ensures that this value exist, so there + * is no need for any elaborate error handling here. + */ + +#ifdef HAVE_SYS_UN_H +extern +char* esock_decode_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_un* sockAddrP, + unsigned int* addrLen) +{ + ErlNifBinary bin; + ERL_NIF_TERM epath; + unsigned int len; + + /* *** Extract (e) path (a binary) from map *** */ + if (!GET_MAP_VAL(env, eSockAddr, esock_atom_path, &epath)) + return ESOCK_STR_EINVAL; + + /* Get the path */ + if (!GET_BIN(env, epath, &bin)) + return ESOCK_STR_EINVAL; + + if ((bin.size + +#ifdef __linux__ + /* Make sure the address gets zero terminated + * except when the first byte is \0 because then it is + * sort of zero terminated although the zero termination + * comes before the address... + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + (bin.data[0] == '\0' ? 0 : 1) +#else + 1 +#endif + ) > sizeof(sockAddrP->sun_path)) + return ESOCK_STR_EINVAL; + + + sys_memzero((char*) sockAddrP, sizeof(struct sockaddr_un)); + sockAddrP->sun_family = AF_UNIX; + + sys_memcpy(sockAddrP->sun_path, bin.data, bin.size); + len = offsetof(struct sockaddr_un, sun_path) + bin.size; + +#ifndef NO_SA_LEN + sockAddrP->sun_len = len; +#endif + *addrLen = len; + + return NULL; +} +#endif + + + +/* +++ esock_encode_sockaddr_un +++ + * + * Encode a Unix Domain socket address - sockaddr_un. In erlang its + * represented as a map, which has a specific set of attributes + * (beside the mandatory family attribute, which is "inherited" from + * the "sockaddr" type): + * + * path :: binary() + * + */ + +#ifdef HAVE_SYS_UN_H +extern +char* esock_encode_sockaddr_un(ErlNifEnv* env, + struct sockaddr_un* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM ePath; + size_t n, m; + char* xres; + + if (addrLen >= offsetof(struct sockaddr_un, sun_path)) { + n = addrLen - offsetof(struct sockaddr_un, sun_path); + if (255 < n) { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } else { + m = esock_strnlen(sockAddrP->sun_path, n); +#ifdef __linux__ + /* Assume that the address is a zero terminated string, + * except when the first byte is \0 i.e the string length is 0, + * then use the reported length instead. + * This fix handles Linux's nonportable + * abstract socket address extension. + */ + if (m == 0) { + m = n; + } +#endif + + /* And finally build the 'path' attribute */ + ePath = MKSL(env, sockAddrP->sun_path, m); + + /* And the socket address */ + xres = make_sockaddr_un(env, ePath, eSockAddr); + } + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} +#endif + + + +/* +++ esock_decode_ip4_address +++ + * + * Decode a IPv4 address. This can be three things: + * + * + Then atom 'any' + * + Then atom 'loopback' + * + An ip4_address() (4 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv4) socket address. + */ + +extern +char* esock_decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in_addr* inAddrP) +{ + struct in_addr addr; + + UDBG( ("SUTIL", "esock_decode_ip4_address -> entry with" + "\r\n eAddr: %T" + "\r\n", eAddr) ); + + if (IS_ATOM(env, eAddr)) { + /* This is either 'any' or 'loopback' */ + + if (COMPARE(esock_atom_loopback, eAddr) == 0) { + UDBG( ("SUTIL", "esock_decode_ip4_address -> address: lookback\r\n") ); + addr.s_addr = htonl(INADDR_LOOPBACK); + } else if (COMPARE(esock_atom_any, eAddr) == 0) { + UDBG( ("SUTIL", "esock_decode_ip4_address -> address: any\r\n") ); + addr.s_addr = htonl(INADDR_ANY); + } else { + UDBG( ("SUTIL", "esock_decode_ip4_address -> address: unknown\r\n") ); + return ESOCK_STR_EINVAL; + } + + inAddrP->s_addr = addr.s_addr; + + } else { + /* This is a 4-tuple */ + + const ERL_NIF_TERM* addrt; + int addrtSz; + int a, v; + char addr[4]; + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return ESOCK_STR_EINVAL; + + if (addrtSz != 4) + return ESOCK_STR_EINVAL; + + for (a = 0; a < 4; a++) { + if (!GET_INT(env, addrt[a], &v)) + return ESOCK_STR_EINVAL; + addr[a] = v; + } + + sys_memcpy(inAddrP, &addr, sizeof(addr)); + + } + + return NULL; +} + + + +/* +++ esock_encode_ip4_address +++ + * + * Encode a IPv4 address: + * + * + An ip4_address() (4 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv4) socket address. There are several other things (port). + */ + +extern +char* esock_encode_ip4_address(ErlNifEnv* env, + struct in_addr* addrP, + ERL_NIF_TERM* eAddr) +{ + unsigned int i; + ERL_NIF_TERM at[4]; + unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); + unsigned char* a = (unsigned char*) addrP; + ERL_NIF_TERM addr; + + /* The address */ + for (i = 0; i < atLen; i++) { + at[i] = MKI(env, a[i]); + } + + addr = MKTA(env, at, atLen); + UDBG( ("SUTIL", "esock_encode_ip4_address -> addr: %T\r\n", addr) ); + // *eAddr = MKTA(env, at, atLen); + *eAddr = addr; + + return NULL; +} + + + +/* +++ esock_decode_ip6_address +++ + * + * Decode a IPv6 address. This can be three things: + * + * + Then atom 'any' + * + Then atom 'loopback' + * + An ip6_address() (8 tuple) + * + * Note that this *only* decodes the "address" part of a + * (IPv6) socket address. There are several other things + * (port, flowinfo and scope_id) that are handled elsewhere). + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in6_addr* inAddrP) +{ + UDBG( ("SUTIL", "esock_decode_ip6_address -> entry with" + "\r\n eAddr: %T" + "\r\n", eAddr) ); + + if (IS_ATOM(env, eAddr)) { + /* This is either 'any' or 'loopback' */ + const struct in6_addr* addr; + + if (COMPARE(esock_atom_loopback, eAddr) == 0) { + addr = &in6addr_loopback; + } else if (COMPARE(esock_atom_any, eAddr) == 0) { + addr = &in6addr_any; + } else { + return ESOCK_STR_EINVAL; + } + + *inAddrP = *addr; + + } else { + /* This is a 8-tuple */ + + const ERL_NIF_TERM* addrt; + int addrtSz; + int ai, v; + unsigned char addr[16]; + unsigned char* a = addr; + unsigned int addrLen = sizeof(addr) / sizeof(unsigned char); + + if (!GET_TUPLE(env, eAddr, &addrtSz, &addrt)) + return ESOCK_STR_EINVAL; + + if (addrtSz != 8) + return ESOCK_STR_EINVAL; + + for (ai = 0; ai < 8; ai++) { + if (!GET_INT(env, addrt[ai], &v)) + return ESOCK_STR_EINVAL; + put_int16(v, a); + a += 2; + } + + sys_memcpy(inAddrP, &addr, addrLen); + + } + + return NULL; +} +#endif + + + +/* +++ esock_encode_ip6_address +++ + * + * Encode a IPv6 address: + * + * + An ip6_address() (8 tuple) + * + * Note that this *only* encodes the "address" part of a + * (IPv6) socket address. There are several other things + * (port, flowinfo and scope_id) that are handled elsewhere). + */ + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_encode_ip6_address(ErlNifEnv* env, + struct in6_addr* addrP, + ERL_NIF_TERM* eAddr) +{ + unsigned int i; + ERL_NIF_TERM at[8]; + unsigned int atLen = sizeof(at) / sizeof(ERL_NIF_TERM); + unsigned char* a = (unsigned char*) addrP; + + /* The address */ + for (i = 0; i < atLen; i++) { + at[i] = MKI(env, get_int16(a + i*2)); + } + + *eAddr = MKTA(env, at, atLen); + + return NULL; +} +#endif + + + +/* +++ esock_encode_timeval +++ + * + * Encode a timeval struct into its erlang form, a map with two fields: + * + * sec + * usec + * + */ +extern +char* esock_encode_timeval(ErlNifEnv* env, + struct timeval* timeP, + ERL_NIF_TERM* eTime) +{ + ERL_NIF_TERM keys[] = {esock_atom_sec, esock_atom_usec}; + ERL_NIF_TERM vals[] = {MKL(env, timeP->tv_sec), MKL(env, timeP->tv_usec)}; + + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, eTime)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + + +/* +++ esock_decode_timeval +++ + * + * Decode a timeval in its erlang form (a map) into its native form, + * a timeval struct. + * + */ +extern +char* esock_decode_timeval(ErlNifEnv* env, + ERL_NIF_TERM eTime, + struct timeval* timeP) +{ + ERL_NIF_TERM eSec, eUSec; + size_t sz; + + // It must be a map + if (!IS_MAP(env, eTime)) + return ESOCK_STR_EINVAL; + + // It must have atleast two attributes + if (!enif_get_map_size(env, eTime, &sz) || (sz < 2)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eTime, esock_atom_sec, &eSec)) + return ESOCK_STR_EINVAL; + + if (!GET_MAP_VAL(env, eTime, esock_atom_usec, &eUSec)) + return ESOCK_STR_EINVAL; + + if (!GET_LONG(env, eSec, &timeP->tv_sec)) + return ESOCK_STR_EINVAL; + + if (!GET_LONG(env, eUSec, &timeP->tv_usec)) + return ESOCK_STR_EINVAL; + + return NULL; +} + + + +/* +++ esock_decode_domain +++ + * + * Decode the Erlang form of the 'domain' type, that is: + * + * inet => AF_INET + * inet6 => AF_INET6 + * local => AF_UNIX + * + */ +extern +char* esock_decode_domain(ErlNifEnv* env, + ERL_NIF_TERM eDomain, + int* domain) +{ + char* xres = NULL; + + if (COMPARE(esock_atom_inet, eDomain) == 0) { + *domain = AF_INET; + +#if defined(HAVE_IN6) && defined(AF_INET6) + } else if (COMPARE(esock_atom_inet6, eDomain) == 0) { + *domain = AF_INET6; +#endif + +#ifdef HAVE_SYS_UN_H + } else if (COMPARE(esock_atom_local, eDomain) == 0) { + *domain = AF_UNIX; +#endif + + } else { + *domain = -1; + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_encode_domain +++ + * + * Encode the native domain to the Erlang form, that is: + * + * AF_INET => inet + * AF_INET6 => inet6 + * AF_UNIX => local + * + */ +extern +char* esock_encode_domain(ErlNifEnv* env, + int domain, + ERL_NIF_TERM* eDomain) +{ + char* xres = NULL; + + switch (domain) { + case AF_INET: + *eDomain = esock_atom_inet; + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + *eDomain = esock_atom_inet6; + break; +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + *eDomain = esock_atom_local; + break; +#endif + + default: + *eDomain = esock_atom_undefined; // Just in case + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_decode_type +++ + * + * Decode the Erlang form of the 'type' type, that is: + * + * stream => SOCK_STREAM + * dgram => SOCK_DGRAM + * raw => SOCK_RAW + * seqpacket => SOCK_SEQPACKET + * + */ +extern +char* esock_decode_type(ErlNifEnv* env, + ERL_NIF_TERM eType, + int* type) +{ + char* xres = NULL; + + if (COMPARE(esock_atom_stream, eType) == 0) { + *type = SOCK_STREAM; + } else if (COMPARE(esock_atom_dgram, eType) == 0) { + *type = SOCK_DGRAM; + } else if (COMPARE(esock_atom_raw, eType) == 0) { + *type = SOCK_RAW; + +#if defined(HAVE_SCTP) + } else if (COMPARE(esock_atom_seqpacket, eType) == 0) { + *type = SOCK_SEQPACKET; +#endif + + } else { + *type = -1; + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_decode_type +++ + * + * Encode the native type to the Erlang form, that is: + * + * SOCK_STREAM => stream + * SOCK_DGRAM => dgram + * SOCK_RAW => raw + * SOCK_SEQPACKET => seqpacket + * + */ +extern +char* esock_encode_type(ErlNifEnv* env, + int type, + ERL_NIF_TERM* eType) +{ + char* xres = NULL; + + switch (type) { + case SOCK_STREAM: + *eType = esock_atom_stream; + break; + + case SOCK_DGRAM: + *eType = esock_atom_dgram; + break; + + case SOCK_RAW: + *eType = esock_atom_raw; + break; + +#if defined(HAVE_SCTP) + case SOCK_SEQPACKET: + *eType = esock_atom_seqpacket; + break; +#endif + + default: + *eType = esock_atom_undefined; // Just in case + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_encode_protocol +++ + * + * Encode the native protocol to the Erlang form, that is: + * + * SOL_IP | IPPROTO_IP => ip + * SOL_IPV6 => ipv6 + * SOL_TCP => tcp + * SOL_UDP => udp + * SOL_SCTP => sctp + * + */ +extern +char* esock_encode_protocol(ErlNifEnv* env, + int proto, + ERL_NIF_TERM* eProto) +{ + char* xres = NULL; + + switch (proto) { +#if defined(SOL_IP) + case SOL_IP: +#else + case IPPROTO_IP: +#endif + *eProto = esock_atom_ip; + break; + +#if defined(SOL_IPV6) + case SOL_IPV6: + *eProto = esock_atom_ipv6; + break; +#endif + + case IPPROTO_TCP: + *eProto = esock_atom_tcp; + break; + + case IPPROTO_UDP: + *eProto = esock_atom_udp; + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + *eProto = esock_atom_sctp; + break; +#endif + + default: + *eProto = esock_atom_undefined; + xres = ESOCK_STR_EAFNOSUPPORT; + break; + } + + return xres; +} + + + +/* +++ esock_decode_protocol +++ + * + * Decode the Erlang form of the 'protocol' type, that is: + * + * ip => SOL_IP | IPPROTO_IP + * ipv6 => SOL_IPV6 + * tcp => SOL_TCP + * udp => SOL_UDP + * sctp => SOL_SCTP + * + */ +extern +char* esock_decode_protocol(ErlNifEnv* env, + ERL_NIF_TERM eProto, + int* proto) +{ + char* xres = NULL; + + if (COMPARE(esock_atom_ip, eProto) == 0) { +#if defined(SOL_IP) + *proto = SOL_IP; +#else + *proto = IPPROTO_IP; +#endif + } else if (COMPARE(esock_atom_ipv6, eProto) == 0) { +#if defined(SOL_IPV6) + *proto = SOL_IPV6; +#else + *proto = IPPROTO_IPV6; +#endif + } else if (COMPARE(esock_atom_tcp, eProto) == 0) { + *proto = IPPROTO_TCP; + } else if (COMPARE(esock_atom_udp, eProto) == 0) { + *proto = IPPROTO_UDP; +#if defined(HAVE_SCTP) + } else if (COMPARE(esock_atom_sctp, eProto) == 0) { + *proto = IPPROTO_SCTP; +#endif + } else { + *proto = -1; + xres = ESOCK_STR_EAFNOSUPPORT; + } + + return xres; +} + + + +/* +++ esock_decode_bufsz +++ + * + * Decode an buffer size. The size of a buffer is: + * + * Sz > 0 => Use provided value + * Sz => Use provided default + * + */ +extern +char* esock_decode_bufsz(ErlNifEnv* env, + ERL_NIF_TERM eVal, + size_t defSz, + size_t* sz) +{ + int val; + + if (!GET_INT(env, eVal, &val)) + return ESOCK_STR_EINVAL; + + if (val > 0) + *sz = (size_t) val; + else + *sz = defSz; + + return NULL; +} + + + +/* *** esock_decode_string *** + * + * Decode a string value. A successful decode results in an + * allocation of the string, which the caller has to free + * once the string has been used. + */ +extern +BOOLEAN_T esock_decode_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP) +{ + BOOLEAN_T result; + unsigned int len; + char* bufP; + + if (!GET_LIST_LEN(env, eString, &len) && (len != 0)) { + *stringP = NULL; + result = FALSE; + } else { + + UDBG( ("SUTIL", "esock_decode_string -> len: %d\r\n", len) ); + + bufP = MALLOC(len + 1); // We shall NULL-terminate + + if (GET_STR(env, eString, bufP, len+1)) { + UDBG( ("SUTIL", "esock_decode_string -> buf: %s\r\n", bufP) ); + // bufP[len] = '\0'; + *stringP = bufP; + result = TRUE; + } else { + *stringP = NULL; + result = FALSE; + FREE(bufP); + } + } + + return result; +} + + + +/* *** esock_extract_bool_from_map *** + * + * Extract an boolean item from a map. + * + */ +extern +BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM key, + BOOLEAN_T def) +{ + ERL_NIF_TERM val; + + if (!GET_MAP_VAL(env, map, key, &val)) + return def; + + if (!IS_ATOM(env, val)) + return def; + + if (COMPARE(val, esock_atom_true) == 0) + return TRUE; + else + return FALSE; +} + + + +/* *** esock_decode_bool *** + * + * Decode a boolean value. + * + */ +extern +BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val) +{ + if (COMPARE(esock_atom_true, val) == 0) + return TRUE; + else + return FALSE; +} + + +/* *** esock_encode_bool *** + * + * Encode a boolean value. + * + */ +extern +ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val) +{ + if (val) + return esock_atom_true; + else + return esock_atom_false; +} + + +/* Create an ok two (2) tuple in the form: + * + * {ok, Any} + * + * The second element (Any) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +extern +ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any) +{ + return MKT2(env, esock_atom_ok, any); +} + + +/* Create an ok three (3) tuple in the form: + * + * {ok, Val1, Val2} + * + * The second (Val1) and third (Val2) elements are already in + * the form of an ERL_NIF_TERM so all we have to do is create + * the tuple. + */ +extern +ERL_NIF_TERM esock_make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2) +{ + return MKT3(env, esock_atom_ok, val1, val2); +} + + + +/* Create an error two (2) tuple in the form: + * + * {error, Reason} + * + * The second element (Reason) is already in the form of an + * ERL_NIF_TERM so all we have to do is create the tuple. + */ +extern +ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason) +{ + return MKT2(env, esock_atom_error, reason); +} + + + +/* Create an error two (2) tuple in the form: {error, Reason}. + * + * {error, Reason} + * + * The second element, Reason, is the reason string that has + * converted into an atom. + */ +extern +ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason) +{ + return esock_make_error(env, MKA(env, reason)); +} + + +/* Create an error two (2) tuple in the form: + * + * {error, Reason} + * + * The second element, Reason, is the errno value in its + * basic form (integer) which has been converted into an atom. + */ +extern +ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err) +{ + return esock_make_error_str(env, erl_errno_id(err)); +} + + + +/* strnlen doesn't exist everywhere */ +extern +size_t esock_strnlen(const char *s, size_t maxlen) +{ + size_t i = 0; + while (i < maxlen && s[i] != '\0') + i++; + return i; +} + + + +/* *** esock_abort *** + * + * Generate an abort with "extra" info. This should be called + * via the ESOCK_ABORT macro. + * Basically it prints the extra info onto stderr before aborting. + * + */ +extern +void esock_abort(const char* expr, + const char* func, + const char* file, + int line) +{ + fflush(stdout); + fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n", + file, line, func, expr); + fflush(stderr); + abort(); +} + + + +/* *** esock_warning_msg *** + * + * Temporary function for issuing warning messages. + * + */ +extern +void esock_warning_msg( const char* format, ... ) +{ + va_list args; + char f[512 + sizeof(format)]; // This has to suffice... + char stamp[64]; // Just in case... + struct timespec ts; + int res; + + /* + * We should really include self in the printout, so we can se which process + * are executing the code. But then I must change the API.... + * ....something for later. + */ + + // 2018-06-29 12:13:21.232089 + // 29-Jun-2018::13:47:25.097097 + + if (!realtime(&ts)) { + if (timespec2str(stamp, sizeof(stamp), &ts) != 0) { + res = enif_snprintf(f, sizeof(f), "=WARNING MSG==== %s", format); + } else { + res = enif_snprintf(f, sizeof(f), + "=WARNING MSG==== %s ===\r\n%s" , stamp, format); + } + + if (res > 0) { + va_start (args, format); + enif_vfprintf (stdout, f, args); + va_end (args); + fflush(stdout); + } + } + + return; +} + + +static +int realtime(struct timespec* tsP) +{ + return clock_gettime(CLOCK_REALTIME, tsP); +} + + +/* + * Convert a timespec struct into a readable/printable string. + * + * "%F::%T" => 2018-06-29 12:13:21[.232089] + * "%d-%b-%Y::%T" => 29-Jun-2018::13:47:25.097097 + */ +static +int timespec2str(char *buf, unsigned int len, struct timespec *ts) +{ + int ret, buflen; + struct tm t; + + tzset(); + if (localtime_r(&(ts->tv_sec), &t) == NULL) + return 1; + + ret = strftime(buf, len, "%d-%B-%Y::%T", &t); + if (ret == 0) + return 2; + len -= ret - 1; + buflen = strlen(buf); + + ret = snprintf(&buf[buflen], len, ".%06ld", ts->tv_nsec/1000); + if (ret >= len) + return 3; + + return 0; +} + + +/* =================================================================== * + * * + * Various (internal) utility functions * + * * + * =================================================================== */ + +/* Construct the IPv4 socket address */ +static +char* make_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, esock_atom_port, esock_atom_addr}; + ERL_NIF_TERM vals[] = {esock_atom_inet, port, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + + +/* Construct the IPv6 socket address */ +static +char* make_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM port, + ERL_NIF_TERM addr, + ERL_NIF_TERM flowInfo, + ERL_NIF_TERM scopeId, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, + esock_atom_port, + esock_atom_addr, + esock_atom_flowinfo, + esock_atom_scope_id}; + ERL_NIF_TERM vals[] = {esock_atom_inet6, port, addr, flowInfo, scopeId}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + + +/* Construct the Unix Domain socket address */ +static +char* make_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM path, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, esock_atom_path}; + ERL_NIF_TERM vals[] = {esock_atom_inet, path}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + + diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h new file mode 100644 index 0000000000..1b5d003155 --- /dev/null +++ b/erts/emulator/nifs/common/socket_util.h @@ -0,0 +1,205 @@ +/* + * %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% + * + * ---------------------------------------------------------------------- + * Purpose : Utility "stuff" for socket and net. + * ---------------------------------------------------------------------- + * + */ + +#ifndef SOCKET_UTIL_H__ +#define SOCKET_UTIL_H__ + +#include <erl_nif.h> +#include "socket_int.h" + +#define CHAR(C) ((char) (C)) +#define UCHAR(C) ((unsigned char) (C)) +#define INT(I) ((int) (I)) +#define UINT(U) ((unsigned int) (U)) +#define LONG(L) ((long) (L)) +#define ULONG(L) ((unsigned long) (L)) +#define SZT(I) ((size_t) (I)) +#define VOIDP(P) ((void*) (P)) +#define CHARP(P) ((char*) (P)) + +#define ESOCK_ABORT(E) esock_abort(E, __func__, __FILE__, __LINE__) +#define ESOCK_ASSERT(e) ((void) ((e) ? 1 : (ESOCK_ABORT(#e), 0))) + +extern +char* esock_encode_iov(ErlNifEnv* env, + int read, + struct iovec* iov, + size_t len, + ErlNifBinary* data, + ERL_NIF_TERM* eIOV); +extern +char* esock_decode_iov(ErlNifEnv* env, + ERL_NIF_TERM eIOV, + ErlNifBinary* bufs, + struct iovec* iov, + size_t len, + ssize_t* totSize); +extern +char* esock_decode_sockaddr(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + SocketAddress* sockAddrP, + unsigned int* addrLen); +extern +char* esock_encode_sockaddr(ErlNifEnv* env, + SocketAddress* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); + +extern +char* esock_decode_sockaddr_in4(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in* sockAddrP, + unsigned int* addrLen); +extern +char* esock_encode_sockaddr_in4(ErlNifEnv* env, + struct sockaddr_in* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_sockaddr_in6(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_in6* sockAddrP, + unsigned int* addrLen); +extern +char* esock_encode_sockaddr_in6(ErlNifEnv* env, + struct sockaddr_in6* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); +#endif + +#ifdef HAVE_SYS_UN_H +extern +char* esock_decode_sockaddr_un(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + struct sockaddr_un* sockAddrP, + unsigned int* addrLen); +extern +char* esock_encode_sockaddr_un(ErlNifEnv* env, + struct sockaddr_un* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); +#endif + +extern +char* esock_decode_ip4_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in_addr* inAddrP); +extern +char* esock_encode_ip4_address(ErlNifEnv* env, + struct in_addr* addrP, + ERL_NIF_TERM* eAddr); + +#if defined(HAVE_IN6) && defined(AF_INET6) +extern +char* esock_decode_ip6_address(ErlNifEnv* env, + ERL_NIF_TERM eAddr, + struct in6_addr* inAddrP); +extern +char* esock_encode_ip6_address(ErlNifEnv* env, + struct in6_addr* addrP, + ERL_NIF_TERM* eAddr); +#endif + +extern char* esock_encode_timeval(ErlNifEnv* env, + struct timeval* timeP, + ERL_NIF_TERM* eTime); +extern char* esock_decode_timeval(ErlNifEnv* env, + ERL_NIF_TERM eTime, + struct timeval* timeP); +extern +char* esock_decode_domain(ErlNifEnv* env, + ERL_NIF_TERM eDomain, + int* domain); +extern +char* esock_encode_domain(ErlNifEnv* env, + int domain, + ERL_NIF_TERM* eDomain); + +extern +char* esock_decode_type(ErlNifEnv* env, + ERL_NIF_TERM eType, + int* type); +extern +char* esock_encode_type(ErlNifEnv* env, + int type, + ERL_NIF_TERM* eType); + +extern +char* esock_decode_protocol(ErlNifEnv* env, + ERL_NIF_TERM eProtocol, + int* protocol); +extern +char* esock_encode_protocol(ErlNifEnv* env, + int type, + ERL_NIF_TERM* eProtocol); + +extern +char* esock_decode_bufsz(ErlNifEnv* env, + ERL_NIF_TERM eVal, + size_t defSz, + size_t* sz); + +extern +BOOLEAN_T esock_decode_string(ErlNifEnv* env, + const ERL_NIF_TERM eString, + char** stringP); + +extern +BOOLEAN_T esock_extract_bool_from_map(ErlNifEnv* env, + ERL_NIF_TERM map, + ERL_NIF_TERM key, + BOOLEAN_T def); +extern +BOOLEAN_T esock_decode_bool(ERL_NIF_TERM val); +extern +ERL_NIF_TERM esock_encode_bool(BOOLEAN_T val); + +extern +size_t esock_strnlen(const char *s, size_t maxlen); +extern +void esock_abort(const char* expr, + const char* func, + const char* file, + int line); + +extern +ERL_NIF_TERM esock_make_ok2(ErlNifEnv* env, ERL_NIF_TERM any); +extern +ERL_NIF_TERM esock_make_ok3(ErlNifEnv* env, ERL_NIF_TERM val1, ERL_NIF_TERM val2); + +extern +ERL_NIF_TERM esock_make_error(ErlNifEnv* env, ERL_NIF_TERM reason); +extern +ERL_NIF_TERM esock_make_error_str(ErlNifEnv* env, char* reason); +extern +ERL_NIF_TERM esock_make_error_errno(ErlNifEnv* env, int err); + +extern +void esock_warning_msg(const char* format, ... ); + + +#endif // SOCKET_UTIL_H__ diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index b66cc1b2a3..c2a01b9e71 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -28,6 +28,24 @@ EBIN = . # Target Specs # ---------------------------------------------------- +SOCKET_MODULES = \ + socket_lib \ + socket_server \ + socket_client \ + socket_test_lib \ + socket_test_logger \ + socket_test_evaluator \ + socket_test_ttest_lib \ + socket_test_ttest_tcp_gen \ + socket_test_ttest_tcp_socket \ + socket_test_ttest_tcp_client \ + socket_test_ttest_tcp_client_gen \ + socket_test_ttest_tcp_client_socket \ + socket_test_ttest_tcp_server \ + socket_test_ttest_tcp_server_gen \ + socket_test_ttest_tcp_server_socket \ + socket_SUITE + MODULES= \ a_SUITE \ after_SUITE \ @@ -106,6 +124,7 @@ MODULES= \ sensitive_SUITE \ signal_SUITE \ smoke_test_SUITE \ + $(SOCKET_MODULES) \ statistics_SUITE \ system_info_SUITE \ system_profile_SUITE \ @@ -153,8 +172,13 @@ NATIVE_MODULES= $(NATIVE:%=%_native_SUITE) NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) +HRL_FILES= \ + socket_test_evaluator.hrl \ + socket_test_ttest.hrl \ + socket_test_ttest_client.hrl TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) +SOCKET_TARGETS = $(SOCKET_MODULES:%=$(EBIN)/%.$(EMULATOR)) EMAKEFILE=Emakefile @@ -201,6 +225,9 @@ clean: docs: +targets: $(TARGET_FILES) +socket_targets: $(SOCKET_TARGETS) + # ---------------------------------------------------- # Special targets # ---------------------------------------------------- @@ -221,7 +248,7 @@ release_spec: release_tests_spec: make_emakefile $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(EMAKEFILE) $(TEST_SPEC_FILES) \ - $(ERL_FILES) "$(RELSYSDIR)" + $(ERL_FILES) $(HRL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" diff --git a/erts/emulator/test/esock-ttest b/erts/emulator/test/esock-ttest new file mode 100755 index 0000000000..418b19eec1 --- /dev/null +++ b/erts/emulator/test/esock-ttest @@ -0,0 +1,350 @@ +#!/usr/bin/env escript + +%% +%% %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% +%% + +%% ========================================================================== +%% +%% This is a simple wrapper escript on top of the socket ttest program(s). +%% The idea is to make it simple to run in a normal shell (bash). +%% +%% ========================================================================== + +-define(SECS(I), timer:seconds(I)). + +-define(CLIENT_MSG_1_MAX_OUTSTANDING, 100). +-define(CLIENT_MSG_2_MAX_OUTSTANDING, 10). +-define(CLIENT_MSG_3_MAX_OUTSTANDING, 1). + +main(Args) -> + State = process_args(Args), + exec(State), + ok. + +usage(ErrorString) when is_list(ErrorString) -> + eprint(ErrorString), + usage(), + erlang:halt(0). + +usage() -> + io:format("usage: ~s [options]" + "~n" + "~n This erlang script is used to start the (e)socket ttest " + "~n units (server or client)." + "~n" + "~n options: " + "~n --help Display this info and exit. " + "~n --server [server-options] Start a server. " + "~n There are no mandatory server options." + "~n --client client-options Start a client" + "~n Some client options are mandatory and" + "~n others optional." + "~n --active <active> boolean() | once." + "~n Valid for both client and server." + "~n Defaults to: false" + "~n --transport <transport> Which transport to use: gen|sock[:plain|msg]" + "~n gen: gen_tcp" + "~n sock: socket" + "~n plain: recv/send (default)" + "~n msg: recvmsg/sendmsg" + "~n Defaults to: sock:plain" + "~n --scon <addr>:<port> Address and port of the server." + "~n The address part is in the standard form:" + "~n \"a.b.c.d\"." + "~n Only valid for client." + "~n Mandatory." + "~n --msg-id <1|2|3> Choose which message to use during the test." + "~n Basically: " + "~n 1: small" + "~n 2: medium" + "~n 3: large" + "~n Defaults to: 1" + "~n --max-outstanding <Num> How many messages to send before waiting for" + "~n a reply." + "~n Valid only for client." + "~n Defaults to: " + "~n MsgID 1: 100" + "~n MsgID 2: 10" + "~n MsgID 3: 1" + "~n --runtime <Time> Time of the test in seconds." + "~n Only valid for client." + "~n Mandatory." + "~n Defaults to: 60 (seconds)" + "~n" + "~n" + "~n", + [scriptname()]), + ok. + +process_args(["--help"|_]) -> + usage(); +process_args(["--server"|ServerArgs]) -> + process_server_args(ServerArgs); +process_args(["--client"|ClientArgs]) -> + process_client_args(ClientArgs); +process_args(Args) -> + usage(f("Invalid Args: " + "~n ~p", [Args])). + + +process_server_args(Args) -> + Defaults = #{role => server, + active => false, + transport => {sock, plain}}, + process_server_args(Args, Defaults). + +process_server_args([], State) -> + State; + +process_server_args(["--active", Active|Args], State) + when ((Active =:= "false") orelse + (Active =:= "once") orelse + (Active =:= "true")) -> + process_server_args(Args, State#{active => list_to_atom(Active)}); + +process_server_args(["--transport", "gen" | Args], State) -> + process_server_args(Args, State#{transport => gen}); +process_server_args(["--transport", "sock" | Args], State) -> + process_server_args(Args, State#{transport => {sock, plain}}); +process_server_args(["--transport", "sock:plain" | Args], State) -> + process_server_args(Args, State#{transport => {sock, plain}}); +process_server_args(["--transport", "sock:msg" | Args], State) -> + process_server_args(Args, State#{transport => {sock, msg}}); + +process_server_args([Arg|_], _State) -> + usage(f("Invalid Server arg: ~s", [Arg])). + + +process_client_args(Args) -> + Defaults = #{role => client, + active => false, + transport => {sock, plain}, + %% Will cause error if not provided + %% Should be "addr:port" + server => undefined, + msg_id => 1, + %% Will be filled in based on msg_id if not provided + max_outstanding => undefined, + runtime => ?SECS(60)}, + process_client_args(Args, Defaults). + +process_client_args([], State) -> + process_client_args_ensure_max_outstanding(State); + +process_client_args(["--active", Active|Args], State) + when ((Active =:= "false") orelse + (Active =:= "once") orelse + (Active =:= "true")) -> + process_client_args(Args, State#{active => list_to_atom(Active)}); + +process_client_args(["--transport", "gen" | Args], State) -> + process_client_args(Args, State#{transport => gen}); +process_client_args(["--transport", "sock" | Args], State) -> + process_client_args(Args, State#{transport => {sock, plain}}); +process_client_args(["--transport", "sock:plain" | Args], State) -> + process_client_args(Args, State#{transport => {sock, plain}}); +process_client_args(["--transport", "sock:msg" | Args], State) -> + process_client_args(Args, State#{transport => {sock, msg}}); + +process_client_args(["--msg-id", MsgID|Args], State) + when ((MsgID =:= "1") orelse + (MsgID =:= "2") orelse + (MsgID =:= "3")) -> + process_client_args(Args, State#{msg_id => list_to_integer(MsgID)}); + +process_client_args(["--max-outstanding", Max|Args], State) -> + try list_to_integer(Max) of + I when (I > 0) -> + process_client_args(Args, State#{max_outstanding => I}); + _ -> + usage(f("Invalid Max Outstanding: ~s", [Max])) + catch + _:_:_ -> + usage(f("Invalid Max Outstanding: ~s", [Max])) + end; + +process_client_args(["--scon", Server|Args], State) -> + case string:tokens(Server, [$:]) of + [AddrStr,PortStr] -> + Addr = case inet:parse_address(AddrStr) of + {ok, A} -> + A; + {error, _} -> + usage(f("Invalid Server Address: ~s", [AddrStr])) + end, + Port = try list_to_integer(PortStr) of + I when (I > 0) -> + I; + _ -> + usage(f("Invalid Server Port: ~s", [PortStr])) + catch + _:_:_ -> + usage(f("Invalid Server Port: ~s", [PortStr])) + end, + process_client_args(Args, State#{server => {Addr, Port}}); + _ -> + usage(f("Invalid Server: ~s", [Server])) + end; + +process_client_args(["--runtime", T|Args], State) -> + try list_to_integer(T) of + I when (I > 0) -> + process_client_args(Args, State#{runtime => ?SECS(I)}); + _ -> + usage(f("Invalid Run Time: ~s", [T])) + catch + _:_:_ -> + usage(f("Invalid Run Time: ~s", [T])) + end; + +process_client_args([Arg|_], _State) -> + usage(f("Invalid Client arg: ~s", [Arg])). + + +process_client_args_ensure_max_outstanding( + #{msg_id := 1, + max_outstanding := undefined} = State) -> + State#{max_outstanding => ?CLIENT_MSG_1_MAX_OUTSTANDING}; +process_client_args_ensure_max_outstanding( + #{msg_id := 2, + max_outstanding := undefined} = State) -> + State#{max_outstanding => ?CLIENT_MSG_2_MAX_OUTSTANDING}; +process_client_args_ensure_max_outstanding( + #{msg_id := 3, + max_outstanding := undefined} = State) -> + State#{max_outstanding => ?CLIENT_MSG_3_MAX_OUTSTANDING}; +process_client_args_ensure_max_outstanding( + #{msg_id := MsgID, + max_outstanding := MaxOutstanding} = State) + when ((MsgID =:= 1) orelse + (MsgID =:= 2) orelse + (MsgID =:= 3)) andalso + (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) -> + State; +process_client_args_ensure_max_outstanding( + #{msg_id := MsgID, + max_outstanding := MaxOutstanding}) -> + usage(f("Invalid Msg ID (~w) and Max Outstanding (~w)", + [MsgID, MaxOutstanding])). + + + +%% ========================================================================== + +exec(#{role := server, + active := Active, + transport := gen}) -> + case socket_test_ttest_tcp_server_gen:start(Active) of + {ok, {Pid, _}} -> + MRef = erlang:monitor(process, Pid), + receive + {'DOWN', MRef, process, Pid, Info} -> + Info + end; + {error, Reason} -> + eprint(f("Failed starting server: " + "~n ~p", [Reason])), + error + end; +exec(#{role := server, + active := Active, + transport := {sock, Method}}) -> + case socket_test_ttest_tcp_server_socket:start(Method, Active) of + {ok, {Pid, _}} -> + MRef = erlang:monitor(process, Pid), + receive + {'DOWN', MRef, process, Pid, Info} -> + Info + end; + {error, Reason} -> + eprint(f("Failed starting server: " + "~n ~p", [Reason])), + error + end; + +exec(#{role := client, + server := undefined}) -> + usage("Mandatory option 'server' not provided"); +exec(#{role := client, + server := {Addr, Port}, + active := Active, + transport := gen, + msg_id := MsgID, + max_outstanding := MaxOutstanding, + runtime := RunTime}) -> + case socket_test_ttest_tcp_client_gen:start(Active, + Addr, Port, + MsgID, MaxOutstanding, + RunTime) of + {ok, Pid} -> + MRef = erlang:monitor(process, Pid), + receive + {'DOWN', MRef, process, Pid, Info} -> + Info + end; + {error, Reason} -> + eprint(f("Failed starting server: " + "~n ~p", [Reason])), + error + end; +exec(#{role := client, + server := {Addr, Port}, + active := Active, + transport := {sock, Method}, + msg_id := MsgID, + max_outstanding := MaxOutstanding, + runtime := RunTime}) -> + case socket_test_ttest_tcp_client_socket:start(Method, + Active, + Addr, Port, + MsgID, MaxOutstanding, + RunTime) of + {ok, Pid} -> + MRef = erlang:monitor(process, Pid), + receive + {'DOWN', MRef, process, Pid, Info} -> + Info + end; + {error, Reason} -> + eprint(f("Failed starting server: " + "~n ~p", [Reason])), + error + end; +exec(_) -> + usage("Unexpected option combo"), + ok. + + + +%% ========================================================================== + +f(F, A) -> + socket_test_ttest_lib:format(F, A). + +eprint(ErrorString) when is_list(ErrorString) -> + print("<ERROR> " ++ ErrorString ++ "~n", []). + +print(F, A) -> + io:format(F ++ "~n", A). + +scriptname() -> + FullName = escript:script_name(), + filename:basename(FullName). + diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl new file mode 100644 index 0000000000..012bff1a7b --- /dev/null +++ b/erts/emulator/test/socket_SUITE.erl @@ -0,0 +1,17429 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% Run the entire test suite: +%% ts:run(emulator, socket_SUITE, [batch]). +%% +%% Run a specific group: +%% ts:run(emulator, socket_SUITE, {group, foo}, [batch]). +%% +%% Run a specific test case: +%% ts:run(emulator, socket_SUITE, foo, [batch]). + +-module(socket_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +%% 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_rcvbuf_option/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_recvfrom_response_udp4/1, + sc_lc_recvfrom_response_udp6/1, + sc_lc_recvmsg_response_tcp4/1, + sc_lc_recvmsg_response_tcp6/1, + sc_lc_recvmsg_response_udp4/1, + sc_lc_recvmsg_response_udp6/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, + + sc_rs_recv_send_shutdown_receive_tcp4/1, + sc_rs_recv_send_shutdown_receive_tcp6/1, + sc_rs_recvmsg_send_shutdown_receive_tcp4/1, + sc_rs_recvmsg_send_shutdown_receive_tcp6/1, + + %% *** Traffic *** + traffic_send_and_recv_chunks_tcp4/1, + traffic_send_and_recv_chunks_tcp6/1, + + traffic_ping_pong_small_send_and_recv_tcp4/1, + traffic_ping_pong_small_send_and_recv_tcp6/1, + traffic_ping_pong_medium_send_and_recv_tcp4/1, + traffic_ping_pong_medium_send_and_recv_tcp6/1, + traffic_ping_pong_large_send_and_recv_tcp4/1, + traffic_ping_pong_large_send_and_recv_tcp6/1, + + traffic_ping_pong_small_sendto_and_recvfrom_udp4/1, + traffic_ping_pong_small_sendto_and_recvfrom_udp6/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udp4/1, + traffic_ping_pong_medium_sendto_and_recvfrom_udp6/1, + + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4/1, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6/1, + + traffic_ping_pong_small_sendmsg_and_recvmsg_udp4/1, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp6/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4/1, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6/1, + + %% *** Time Test *** + %% Server: transport = gen_tcp, active = false + %% Client: transport = gen_tcp + ttest_sgenf_cgenf_small_tcp4/1, + ttest_sgenf_cgenf_small_tcp6/1, + ttest_sgenf_cgenf_medium_tcp4/1, + ttest_sgenf_cgenf_medium_tcp6/1, + ttest_sgenf_cgenf_large_tcp4/1, + ttest_sgenf_cgenf_large_tcp6/1, + + ttest_sgenf_cgeno_small_tcp4/1, + ttest_sgenf_cgeno_small_tcp6/1, + ttest_sgenf_cgeno_medium_tcp4/1, + ttest_sgenf_cgeno_medium_tcp6/1, + ttest_sgenf_cgeno_large_tcp4/1, + ttest_sgenf_cgeno_large_tcp6/1, + + ttest_sgenf_cgent_small_tcp4/1, + ttest_sgenf_cgent_small_tcp6/1, + ttest_sgenf_cgent_medium_tcp4/1, + ttest_sgenf_cgent_medium_tcp6/1, + ttest_sgenf_cgent_large_tcp4/1, + ttest_sgenf_cgent_large_tcp6/1, + + %% Server: transport = gen_tcp, active = false + %% Client: transport = socket(tcp) + ttest_sgenf_csockf_small_tcp4/1, + ttest_sgenf_csockf_small_tcp6/1, + ttest_sgenf_csockf_medium_tcp4/1, + ttest_sgenf_csockf_medium_tcp6/1, + ttest_sgenf_csockf_large_tcp4/1, + ttest_sgenf_csockf_large_tcp6/1, + + ttest_sgenf_csocko_small_tcp4/1, + ttest_sgenf_csocko_small_tcp6/1, + ttest_sgenf_csocko_medium_tcp4/1, + ttest_sgenf_csocko_medium_tcp6/1, + ttest_sgenf_csocko_large_tcp4/1, + ttest_sgenf_csocko_large_tcp6/1, + + ttest_sgenf_csockt_small_tcp4/1, + ttest_sgenf_csockt_small_tcp6/1, + ttest_sgenf_csockt_medium_tcp4/1, + ttest_sgenf_csockt_medium_tcp6/1, + ttest_sgenf_csockt_large_tcp4/1, + ttest_sgenf_csockt_large_tcp6/1, + + %% Server: transport = gen_tcp, active = once + %% Client: transport = gen_tcp + ttest_sgeno_cgenf_small_tcp4/1, + ttest_sgeno_cgenf_small_tcp6/1, + ttest_sgeno_cgenf_medium_tcp4/1, + ttest_sgeno_cgenf_medium_tcp6/1, + ttest_sgeno_cgenf_large_tcp4/1, + ttest_sgeno_cgenf_large_tcp6/1, + + ttest_sgeno_cgeno_small_tcp4/1, + ttest_sgeno_cgeno_small_tcp6/1, + ttest_sgeno_cgeno_medium_tcp4/1, + ttest_sgeno_cgeno_medium_tcp6/1, + ttest_sgeno_cgeno_large_tcp4/1, + ttest_sgeno_cgeno_large_tcp6/1, + + ttest_sgeno_cgent_small_tcp4/1, + ttest_sgeno_cgent_small_tcp6/1, + ttest_sgeno_cgent_medium_tcp4/1, + ttest_sgeno_cgent_medium_tcp6/1, + ttest_sgeno_cgent_large_tcp4/1, + ttest_sgeno_cgent_large_tcp6/1, + + %% Server: transport = gen_tcp, active = once + %% Client: transport = socket(tcp) + ttest_sgeno_csockf_small_tcp4/1, + ttest_sgeno_csockf_small_tcp6/1, + ttest_sgeno_csockf_medium_tcp4/1, + ttest_sgeno_csockf_medium_tcp6/1, + ttest_sgeno_csockf_large_tcp4/1, + ttest_sgeno_csockf_large_tcp6/1, + + ttest_sgeno_csocko_small_tcp4/1, + ttest_sgeno_csocko_small_tcp6/1, + ttest_sgeno_csocko_medium_tcp4/1, + ttest_sgeno_csocko_medium_tcp6/1, + ttest_sgeno_csocko_large_tcp4/1, + ttest_sgeno_csocko_large_tcp6/1, + + ttest_sgeno_csockt_small_tcp4/1, + ttest_sgeno_csockt_small_tcp6/1, + ttest_sgeno_csockt_medium_tcp4/1, + ttest_sgeno_csockt_medium_tcp6/1, + ttest_sgeno_csockt_large_tcp4/1, + ttest_sgeno_csockt_large_tcp6/1, + + %% Server: transport = gen_tcp, active = true + %% Client: transport = gen_tcp + ttest_sgent_cgenf_small_tcp4/1, + ttest_sgent_cgenf_small_tcp6/1, + ttest_sgent_cgenf_medium_tcp4/1, + ttest_sgent_cgenf_medium_tcp6/1, + ttest_sgent_cgenf_large_tcp4/1, + ttest_sgent_cgenf_large_tcp6/1, + + ttest_sgent_cgeno_small_tcp4/1, + ttest_sgent_cgeno_small_tcp6/1, + ttest_sgent_cgeno_medium_tcp4/1, + ttest_sgent_cgeno_medium_tcp6/1, + ttest_sgent_cgeno_large_tcp4/1, + ttest_sgent_cgeno_large_tcp6/1, + + ttest_sgent_cgent_small_tcp4/1, + ttest_sgent_cgent_small_tcp6/1, + ttest_sgent_cgent_medium_tcp4/1, + ttest_sgent_cgent_medium_tcp6/1, + ttest_sgent_cgent_large_tcp4/1, + ttest_sgent_cgent_large_tcp6/1, + + %% Server: transport = gen_tcp, active = true + %% Client: transport = socket(tcp) + ttest_sgent_csockf_small_tcp4/1, + ttest_sgent_csockf_small_tcp6/1, + ttest_sgent_csockf_medium_tcp4/1, + ttest_sgent_csockf_medium_tcp6/1, + ttest_sgent_csockf_large_tcp4/1, + ttest_sgent_csockf_large_tcp6/1, + + ttest_sgent_csocko_small_tcp4/1, + ttest_sgent_csocko_small_tcp6/1, + ttest_sgent_csocko_medium_tcp4/1, + ttest_sgent_csocko_medium_tcp6/1, + ttest_sgent_csocko_large_tcp4/1, + ttest_sgent_csocko_large_tcp6/1, + + ttest_sgent_csockt_small_tcp4/1, + ttest_sgent_csockt_small_tcp6/1, + ttest_sgent_csockt_medium_tcp4/1, + ttest_sgent_csockt_medium_tcp6/1, + ttest_sgent_csockt_large_tcp4/1, + ttest_sgent_csockt_large_tcp6/1, + + %% Server: transport = socket(tcp), active = false + %% Client: transport = gen_tcp + ttest_ssockf_cgenf_small_tcp4/1, + ttest_ssockf_cgenf_small_tcp6/1, + ttest_ssockf_cgenf_medium_tcp4/1, + ttest_ssockf_cgenf_medium_tcp6/1, + ttest_ssockf_cgenf_large_tcp4/1, + ttest_ssockf_cgenf_large_tcp6/1, + + ttest_ssockf_cgeno_small_tcp4/1, + ttest_ssockf_cgeno_small_tcp6/1, + ttest_ssockf_cgeno_medium_tcp4/1, + ttest_ssockf_cgeno_medium_tcp6/1, + ttest_ssockf_cgeno_large_tcp4/1, + ttest_ssockf_cgeno_large_tcp6/1, + + ttest_ssockf_cgent_small_tcp4/1, + ttest_ssockf_cgent_small_tcp6/1, + ttest_ssockf_cgent_medium_tcp4/1, + ttest_ssockf_cgent_medium_tcp6/1, + ttest_ssockf_cgent_large_tcp4/1, + ttest_ssockf_cgent_large_tcp6/1, + + %% Server: transport = socket(tcp), active = false + %% Client: transport = socket(tcp) + ttest_ssockf_csockf_small_tcp4/1, + ttest_ssockf_csockf_small_tcp6/1, + ttest_ssockf_csockf_medium_tcp4/1, + ttest_ssockf_csockf_medium_tcp6/1, + ttest_ssockf_csockf_large_tcp4/1, + ttest_ssockf_csockf_large_tcp6/1, + + ttest_ssockf_csocko_small_tcp4/1, + ttest_ssockf_csocko_small_tcp6/1, + ttest_ssockf_csocko_medium_tcp4/1, + ttest_ssockf_csocko_medium_tcp6/1, + ttest_ssockf_csocko_large_tcp4/1, + ttest_ssockf_csocko_large_tcp6/1, + + ttest_ssockf_csockt_small_tcp4/1, + ttest_ssockf_csockt_small_tcp6/1, + ttest_ssockf_csockt_medium_tcp4/1, + ttest_ssockf_csockt_medium_tcp6/1, + ttest_ssockf_csockt_large_tcp4/1, + ttest_ssockf_csockt_large_tcp6/1, + + %% Server: transport = socket(tcp), active = once + %% Client: transport = gen_tcp + ttest_ssocko_cgenf_small_tcp4/1, + ttest_ssocko_cgenf_small_tcp6/1, + ttest_ssocko_cgenf_medium_tcp4/1, + ttest_ssocko_cgenf_medium_tcp6/1, + ttest_ssocko_cgenf_large_tcp4/1, + ttest_ssocko_cgenf_large_tcp6/1, + + ttest_ssocko_cgeno_small_tcp4/1, + ttest_ssocko_cgeno_small_tcp6/1, + ttest_ssocko_cgeno_medium_tcp4/1, + ttest_ssocko_cgeno_medium_tcp6/1, + ttest_ssocko_cgeno_large_tcp4/1, + ttest_ssocko_cgeno_large_tcp6/1, + + ttest_ssocko_cgent_small_tcp4/1, + ttest_ssocko_cgent_small_tcp6/1, + ttest_ssocko_cgent_medium_tcp4/1, + ttest_ssocko_cgent_medium_tcp6/1, + ttest_ssocko_cgent_large_tcp4/1, + ttest_ssocko_cgent_large_tcp6/1, + + %% Server: transport = socket(tcp), active = once + %% Client: transport = socket(tcp) + ttest_ssocko_csockf_small_tcp4/1, + ttest_ssocko_csockf_small_tcp6/1, + ttest_ssocko_csockf_medium_tcp4/1, + ttest_ssocko_csockf_medium_tcp6/1, + ttest_ssocko_csockf_large_tcp4/1, + ttest_ssocko_csockf_large_tcp6/1, + + ttest_ssocko_csocko_small_tcp4/1, + ttest_ssocko_csocko_small_tcp6/1, + ttest_ssocko_csocko_medium_tcp4/1, + ttest_ssocko_csocko_medium_tcp6/1, + ttest_ssocko_csocko_large_tcp4/1, + ttest_ssocko_csocko_large_tcp6/1, + + ttest_ssocko_csockt_small_tcp4/1, + ttest_ssocko_csockt_small_tcp6/1, + ttest_ssocko_csockt_medium_tcp4/1, + ttest_ssocko_csockt_medium_tcp6/1, + ttest_ssocko_csockt_large_tcp4/1, + ttest_ssocko_csockt_large_tcp6/1, + + %% Server: transport = socket(tcp), active = true + %% Client: transport = gen_tcp + ttest_ssockt_cgenf_small_tcp4/1, + ttest_ssockt_cgenf_small_tcp6/1, + ttest_ssockt_cgenf_medium_tcp4/1, + ttest_ssockt_cgenf_medium_tcp6/1, + ttest_ssockt_cgenf_large_tcp4/1, + ttest_ssockt_cgenf_large_tcp6/1, + + ttest_ssockt_cgeno_small_tcp4/1, + ttest_ssockt_cgeno_small_tcp6/1, + ttest_ssockt_cgeno_medium_tcp4/1, + ttest_ssockt_cgeno_medium_tcp6/1, + ttest_ssockt_cgeno_large_tcp4/1, + ttest_ssockt_cgeno_large_tcp6/1, + + ttest_ssockt_cgent_small_tcp4/1, + ttest_ssockt_cgent_small_tcp6/1, + ttest_ssockt_cgent_medium_tcp4/1, + ttest_ssockt_cgent_medium_tcp6/1, + ttest_ssockt_cgent_large_tcp4/1, + ttest_ssockt_cgent_large_tcp6/1, + + %% Server: transport = socket(tcp), active = true + %% Client: transport = socket(tcp) + ttest_ssockt_csockf_small_tcp4/1, + ttest_ssockt_csockf_small_tcp6/1, + ttest_ssockt_csockf_medium_tcp4/1, + ttest_ssockt_csockf_medium_tcp6/1, + ttest_ssockt_csockf_large_tcp4/1, + ttest_ssockt_csockf_large_tcp6/1, + + ttest_ssockt_csocko_small_tcp4/1, + ttest_ssockt_csocko_small_tcp6/1, + ttest_ssockt_csocko_medium_tcp4/1, + ttest_ssockt_csocko_medium_tcp6/1, + ttest_ssockt_csocko_large_tcp4/1, + ttest_ssockt_csocko_large_tcp6/1, + + ttest_ssockt_csockt_small_tcp4/1, + ttest_ssockt_csockt_small_tcp6/1, + ttest_ssockt_csockt_medium_tcp4/1, + ttest_ssockt_csockt_medium_tcp6/1, + ttest_ssockt_csockt_large_tcp4/1, + ttest_ssockt_csockt_large_tcp6/1 + + %% Tickets + ]). + + +-include("socket_test_evaluator.hrl"). + +%% Internal exports +%% -export([]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(BASIC_REQ, <<"hejsan">>). +-define(BASIC_REP, <<"hoppsan">>). + +-define(DATA, <<"HOPPSAN">>). % Temporary +-define(FAIL(R), exit(R)). + +-define(SLEEP(T), receive after T -> ok end). + +-define(MINS(M), timer:minutes(M)). +-define(SECS(S), timer:seconds(S)). + +-define(TT(T), ct:timetrap(T)). + +-define(LIB, socket_test_lib). +-define(TTEST_LIB, socket_test_ttest_lib). +-define(LOGGER, socket_test_logger). + +-define(TPP_SMALL, lists:seq(1, 8)). +-define(TPP_MEDIUM, lists:flatten(lists:duplicate(1024, ?TPP_SMALL))). +-define(TPP_LARGE, lists:flatten(lists:duplicate(1024, ?TPP_MEDIUM))). + +-define(TPP_SMALL_NUM, 10000). +-define(TPP_MEDIUM_NUM, 1000). +-define(TPP_LARGE_NUM, 100). + +-define(TTEST_RUNTIME, ?SECS(10)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + Groups = [{api, "ESOCK_TEST_API", include}, + {socket_closure, "ESOCK_TEST_SOCK_CLOSE", include}, + {traffic, "ESOCK_TEST_TRAFFIC", include}, + {ttest, "ESOCK_TEST_TTEST", exclude}], + [use_group(Group, Env, Default) || {Group, Env, Default} <- Groups]. + +use_group(Group, Env, Default) -> + case os:getenv(Env) of + false when (Default =:= include) -> + [{group, Group}]; + false -> + []; + Val -> + case list_to_atom(string:to_lower(Val)) of + Use when (Use =:= include) orelse + (Use =:= enable) orelse + (Use =:= true) -> + [{group, Group}]; + _ -> + [] + end + end. + + +groups() -> + [{api, [], api_cases()}, + {api_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()}, + {sc_remote_shutdown, [], sc_rs_cases()}, + {traffic, [], traffic_cases()}, + {ttest, [], ttest_cases()}, + {ttest_sgenf, [], ttest_sgenf_cases()}, + {ttest_sgenf_cgen, [], ttest_sgenf_cgen_cases()}, + {ttest_sgenf_cgenf, [], ttest_sgenf_cgenf_cases()}, + {ttest_sgenf_cgeno, [], ttest_sgenf_cgeno_cases()}, + {ttest_sgenf_cgent, [], ttest_sgenf_cgent_cases()}, + {ttest_sgenf_csock, [], ttest_sgenf_csock_cases()}, + {ttest_sgenf_csockf, [], ttest_sgenf_csockf_cases()}, + {ttest_sgenf_csocko, [], ttest_sgenf_csocko_cases()}, + {ttest_sgenf_csockt, [], ttest_sgenf_csockt_cases()}, + {ttest_sgeno, [], ttest_sgeno_cases()}, + {ttest_sgeno_cgen, [], ttest_sgeno_cgen_cases()}, + {ttest_sgeno_cgenf, [], ttest_sgeno_cgenf_cases()}, + {ttest_sgeno_cgeno, [], ttest_sgeno_cgeno_cases()}, + {ttest_sgeno_cgent, [], ttest_sgeno_cgent_cases()}, + {ttest_sgeno_csock, [], ttest_sgeno_csock_cases()}, + {ttest_sgeno_csockf, [], ttest_sgeno_csockf_cases()}, + {ttest_sgeno_csocko, [], ttest_sgeno_csocko_cases()}, + {ttest_sgeno_csockt, [], ttest_sgeno_csockt_cases()}, + {ttest_sgent, [], ttest_sgent_cases()}, + {ttest_sgent_cgen, [], ttest_sgent_cgen_cases()}, + {ttest_sgent_cgenf, [], ttest_sgent_cgenf_cases()}, + {ttest_sgent_cgeno, [], ttest_sgent_cgeno_cases()}, + {ttest_sgent_cgent, [], ttest_sgent_cgent_cases()}, + {ttest_sgent_csock, [], ttest_sgent_csock_cases()}, + {ttest_sgent_csockf, [], ttest_sgent_csockf_cases()}, + {ttest_sgent_csocko, [], ttest_sgent_csocko_cases()}, + {ttest_sgent_csockt, [], ttest_sgent_csockt_cases()}, + {ttest_ssockf, [], ttest_ssockf_cases()}, + {ttest_ssockf_cgen, [], ttest_ssockf_cgen_cases()}, + {ttest_ssockf_cgenf, [], ttest_ssockf_cgenf_cases()}, + {ttest_ssockf_cgeno, [], ttest_ssockf_cgeno_cases()}, + {ttest_ssockf_cgent, [], ttest_ssockf_cgent_cases()}, + {ttest_ssockf_csock, [], ttest_ssockf_csock_cases()}, + {ttest_ssockf_csockf, [], ttest_ssockf_csockf_cases()}, + {ttest_ssockf_csocko, [], ttest_ssockf_csocko_cases()}, + {ttest_ssockf_csockt, [], ttest_ssockf_csockt_cases()}, + {ttest_ssocko, [], ttest_ssocko_cases()}, + {ttest_ssocko_cgen, [], ttest_ssocko_cgen_cases()}, + {ttest_ssocko_cgenf, [], ttest_ssocko_cgenf_cases()}, + {ttest_ssocko_cgeno, [], ttest_ssocko_cgeno_cases()}, + {ttest_ssocko_cgent, [], ttest_ssocko_cgent_cases()}, + {ttest_ssocko_csock, [], ttest_ssocko_csock_cases()}, + {ttest_ssocko_csockf, [], ttest_ssocko_csockf_cases()}, + {ttest_ssocko_csocko, [], ttest_ssocko_csocko_cases()}, + {ttest_ssocko_csockt, [], ttest_ssocko_csockt_cases()}, + {ttest_ssockt, [], ttest_ssockt_cases()}, + {ttest_ssockt_cgen, [], ttest_ssockt_cgen_cases()}, + {ttest_ssockt_cgenf, [], ttest_ssockt_cgenf_cases()}, + {ttest_ssockt_cgeno, [], ttest_ssockt_cgeno_cases()}, + {ttest_ssockt_cgent, [], ttest_ssockt_cgent_cases()}, + {ttest_ssockt_csock, [], ttest_ssockt_csock_cases()}, + {ttest_ssockt_csockf, [], ttest_ssockt_csockf_cases()}, + {ttest_ssockt_csocko, [], ttest_ssockt_csocko_cases()}, + {ttest_ssockt_csockt, [], ttest_ssockt_csockt_cases()} + + %% {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_rcvbuf_option, + 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_maccept_tcp4, + api_to_maccept_tcp6, + api_to_send_tcp4, + api_to_send_tcp6, + api_to_sendto_udp4, + api_to_sendto_udp6, + api_to_sendmsg_tcp4, + api_to_sendmsg_tcp6, + api_to_recv_udp4, + api_to_recv_udp6, + api_to_recv_tcp4, + api_to_recv_tcp6, + api_to_recvfrom_udp4, + api_to_recvfrom_udp6, + api_to_recvmsg_udp4, + api_to_recvmsg_udp6, + api_to_recvmsg_tcp4, + api_to_recvmsg_tcp6 + ]. + +%% These cases tests what happens when the socket is closed/shutdown, +%% locally or remotely. +socket_closure_cases() -> + [ + {group, sc_ctrl_proc_exit}, + {group, sc_local_close}, + {group, sc_remote_close}, + {group, sc_remote_shutdown} + ]. + +%% These cases are all about socket cleanup after the controlling process +%% exits *without* 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_recvfrom_response_udp4, + sc_lc_recvfrom_response_udp6, + + sc_lc_recvmsg_response_tcp4, + sc_lc_recvmsg_response_tcp6, + sc_lc_recvmsg_response_udp4, + sc_lc_recvmsg_response_udp6, + + 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 + ]. + +%% These cases tests what happens when the socket is shutdown/closed remotely +%% after writing and reading is ongoing. +sc_rs_cases() -> + [ + sc_rs_recv_send_shutdown_receive_tcp4, + sc_rs_recv_send_shutdown_receive_tcp6, + + sc_rs_recvmsg_send_shutdown_receive_tcp4, + sc_rs_recvmsg_send_shutdown_receive_tcp6 + ]. + + +traffic_cases() -> + [ + traffic_send_and_recv_chunks_tcp4, + traffic_send_and_recv_chunks_tcp6, + + traffic_ping_pong_small_send_and_recv_tcp4, + traffic_ping_pong_small_send_and_recv_tcp6, + traffic_ping_pong_medium_send_and_recv_tcp4, + traffic_ping_pong_medium_send_and_recv_tcp6, + traffic_ping_pong_large_send_and_recv_tcp4, + traffic_ping_pong_large_send_and_recv_tcp6, + + traffic_ping_pong_small_sendto_and_recvfrom_udp4, + traffic_ping_pong_small_sendto_and_recvfrom_udp6, + traffic_ping_pong_medium_sendto_and_recvfrom_udp4, + traffic_ping_pong_medium_sendto_and_recvfrom_udp6, + + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, + + traffic_ping_pong_small_sendmsg_and_recvmsg_udp4, + traffic_ping_pong_small_sendmsg_and_recvmsg_udp6, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4, + traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6 + ]. + + +ttest_cases() -> + [ + %% Server: transport = gen_tcp, active = false + {group, ttest_sgenf}, + + %% Server: transport = gen_tcp, active = once + {group, ttest_sgeno}, + + %% Server: transport = gen_tcp, active = true + {group, ttest_sgent}, + + %% Server: transport = socket(tcp), active = false + {group, ttest_ssockf}, + + %% Server: transport = socket(tcp), active = once + {group, ttest_ssocko}, + + %% Server: transport = socket(tcp), active = true + {group, ttest_ssockt} + + ]. + + +%% Server: transport = gen_tcp, active = false +ttest_sgenf_cases() -> + [ + {group, ttest_sgenf_cgen}, + {group, ttest_sgenf_csock} + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp +ttest_sgenf_cgen_cases() -> + [ + {group, ttest_sgenf_cgenf}, + {group, ttest_sgenf_cgeno}, + {group, ttest_sgenf_cgent} + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = false +ttest_sgenf_cgenf_cases() -> + [ + ttest_sgenf_cgenf_small_tcp4, + ttest_sgenf_cgenf_small_tcp6, + + ttest_sgenf_cgenf_medium_tcp4, + ttest_sgenf_cgenf_medium_tcp6, + + ttest_sgenf_cgenf_large_tcp4, + ttest_sgenf_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = once +ttest_sgenf_cgeno_cases() -> + [ + ttest_sgenf_cgeno_small_tcp4, + ttest_sgenf_cgeno_small_tcp6, + + ttest_sgenf_cgeno_medium_tcp4, + ttest_sgenf_cgeno_medium_tcp6, + + ttest_sgenf_cgeno_large_tcp4, + ttest_sgenf_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = gen_tcp, active = true +ttest_sgenf_cgent_cases() -> + [ + ttest_sgenf_cgent_small_tcp4, + ttest_sgenf_cgent_small_tcp6, + + ttest_sgenf_cgent_medium_tcp4, + ttest_sgenf_cgent_medium_tcp6, + + ttest_sgenf_cgent_large_tcp4, + ttest_sgenf_cgent_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = false +%% Client: transport = socket(tcp) +ttest_sgenf_csock_cases() -> + [ + {group, ttest_sgenf_csockf}, + {group, ttest_sgenf_csocko}, + {group, ttest_sgenf_csockt} + ]. + +ttest_sgenf_csockf_cases() -> + [ + ttest_sgenf_csockf_small_tcp4, + ttest_sgenf_csockf_small_tcp6, + + ttest_sgenf_csockf_medium_tcp4, + ttest_sgenf_csockf_medium_tcp6, + + ttest_sgenf_csockf_large_tcp4, + ttest_sgenf_csockf_large_tcp6 + ]. + +ttest_sgenf_csocko_cases() -> + [ + ttest_sgenf_csocko_small_tcp4, + ttest_sgenf_csocko_small_tcp6, + + ttest_sgenf_csocko_medium_tcp4, + ttest_sgenf_csocko_medium_tcp6, + + ttest_sgenf_csocko_large_tcp4, + ttest_sgenf_csocko_large_tcp6 + ]. + +ttest_sgenf_csockt_cases() -> + [ + ttest_sgenf_csockt_small_tcp4, + ttest_sgenf_csockt_small_tcp6, + + ttest_sgenf_csockt_medium_tcp4, + ttest_sgenf_csockt_medium_tcp6, + + ttest_sgenf_csockt_large_tcp4, + ttest_sgenf_csockt_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +ttest_sgeno_cases() -> + [ + {group, ttest_sgeno_cgen}, + {group, ttest_sgeno_csock} + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp +ttest_sgeno_cgen_cases() -> + [ + {group, ttest_sgeno_cgenf}, + {group, ttest_sgeno_cgeno}, + {group, ttest_sgeno_cgent} + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = false +ttest_sgeno_cgenf_cases() -> + [ + ttest_sgeno_cgenf_small_tcp4, + ttest_sgeno_cgenf_small_tcp6, + + ttest_sgeno_cgenf_medium_tcp4, + ttest_sgeno_cgenf_medium_tcp6, + + ttest_sgeno_cgenf_large_tcp4, + ttest_sgeno_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = once +ttest_sgeno_cgeno_cases() -> + [ + ttest_sgeno_cgeno_small_tcp4, + ttest_sgeno_cgeno_small_tcp6, + + ttest_sgeno_cgeno_medium_tcp4, + ttest_sgeno_cgeno_medium_tcp6, + + ttest_sgeno_cgeno_large_tcp4, + ttest_sgeno_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = gen_tcp, active = true +ttest_sgeno_cgent_cases() -> + [ + ttest_sgeno_cgent_small_tcp4, + ttest_sgeno_cgent_small_tcp6, + + ttest_sgeno_cgent_medium_tcp4, + ttest_sgeno_cgent_medium_tcp6, + + ttest_sgeno_cgent_large_tcp4, + ttest_sgeno_cgent_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = once +%% Client: transport = socket(tcp) +ttest_sgeno_csock_cases() -> + [ + {group, ttest_sgeno_csockf}, + {group, ttest_sgeno_csocko}, + {group, ttest_sgeno_csockt} + ]. + +ttest_sgeno_csockf_cases() -> + [ + ttest_sgeno_csockf_small_tcp4, + ttest_sgeno_csockf_small_tcp6, + + ttest_sgeno_csockf_medium_tcp4, + ttest_sgeno_csockf_medium_tcp6, + + ttest_sgeno_csockf_large_tcp4, + ttest_sgeno_csockf_large_tcp6 + ]. + +ttest_sgeno_csocko_cases() -> + [ + ttest_sgeno_csocko_small_tcp4, + ttest_sgeno_csocko_small_tcp6, + + ttest_sgeno_csocko_medium_tcp4, + ttest_sgeno_csocko_medium_tcp6, + + ttest_sgeno_csocko_large_tcp4, + ttest_sgeno_csocko_large_tcp6 + ]. + +ttest_sgeno_csockt_cases() -> + [ + ttest_sgeno_csockt_small_tcp4, + ttest_sgeno_csockt_small_tcp6, + + ttest_sgeno_csockt_medium_tcp4, + ttest_sgeno_csockt_medium_tcp6, + + ttest_sgeno_csockt_large_tcp4, + ttest_sgeno_csockt_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +ttest_sgent_cases() -> + [ + {group, ttest_sgent_cgen}, + {group, ttest_sgent_csock} + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp +ttest_sgent_cgen_cases() -> + [ + {group, ttest_sgent_cgenf}, + {group, ttest_sgent_cgeno}, + {group, ttest_sgent_cgent} + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = false +ttest_sgent_cgenf_cases() -> + [ + ttest_sgent_cgenf_small_tcp4, + ttest_sgent_cgenf_small_tcp6, + + ttest_sgent_cgenf_medium_tcp4, + ttest_sgent_cgenf_medium_tcp6, + + ttest_sgent_cgenf_large_tcp4, + ttest_sgent_cgenf_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = once +ttest_sgent_cgeno_cases() -> + [ + ttest_sgent_cgeno_small_tcp4, + ttest_sgent_cgeno_small_tcp6, + + ttest_sgent_cgeno_medium_tcp4, + ttest_sgent_cgeno_medium_tcp6, + + ttest_sgent_cgeno_large_tcp4, + ttest_sgent_cgeno_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = gen_tcp, active = true +ttest_sgent_cgent_cases() -> + [ + ttest_sgent_cgent_small_tcp4, + ttest_sgent_cgent_small_tcp6, + + ttest_sgent_cgent_medium_tcp4, + ttest_sgent_cgent_medium_tcp6, + + ttest_sgent_cgent_large_tcp4, + ttest_sgent_cgent_large_tcp6 + ]. + +%% Server: transport = gen_tcp, active = true +%% Client: transport = socket(tcp) +ttest_sgent_csock_cases() -> + [ + {group, ttest_sgent_csockf}, + {group, ttest_sgent_csocko}, + {group, ttest_sgent_csockt} + ]. + +ttest_sgent_csockf_cases() -> + [ + ttest_sgent_csockf_small_tcp4, + ttest_sgent_csockf_small_tcp6, + + ttest_sgent_csockf_medium_tcp4, + ttest_sgent_csockf_medium_tcp6, + + ttest_sgent_csockf_large_tcp4, + ttest_sgent_csockf_large_tcp6 + ]. + +ttest_sgent_csocko_cases() -> + [ + ttest_sgent_csocko_small_tcp4, + ttest_sgent_csocko_small_tcp6, + + ttest_sgent_csocko_medium_tcp4, + ttest_sgent_csocko_medium_tcp6, + + ttest_sgent_csocko_large_tcp4, + ttest_sgent_csocko_large_tcp6 + ]. + +ttest_sgent_csockt_cases() -> + [ + ttest_sgent_csockt_small_tcp4, + ttest_sgent_csockt_small_tcp6, + + ttest_sgent_csockt_medium_tcp4, + ttest_sgent_csockt_medium_tcp6, + + ttest_sgent_csockt_large_tcp4, + ttest_sgent_csockt_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +ttest_ssockf_cases() -> + [ + {group, ttest_ssockf_cgen}, + {group, ttest_ssockf_csock} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp +ttest_ssockf_cgen_cases() -> + [ + {group, ttest_ssockf_cgenf}, + {group, ttest_ssockf_cgeno}, + {group, ttest_ssockf_cgent} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = false +ttest_ssockf_cgenf_cases() -> + [ + ttest_ssockf_cgenf_small_tcp4, + ttest_ssockf_cgenf_small_tcp6, + + ttest_ssockf_cgenf_medium_tcp4, + ttest_ssockf_cgenf_medium_tcp6, + + ttest_ssockf_cgenf_large_tcp4, + ttest_ssockf_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = once +ttest_ssockf_cgeno_cases() -> + [ + ttest_ssockf_cgeno_small_tcp4, + ttest_ssockf_cgeno_small_tcp6, + + ttest_ssockf_cgeno_medium_tcp4, + ttest_ssockf_cgeno_medium_tcp6, + + ttest_ssockf_cgeno_large_tcp4, + ttest_ssockf_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = gen_tcp, active = true +ttest_ssockf_cgent_cases() -> + [ + ttest_ssockf_cgent_small_tcp4, + ttest_ssockf_cgent_small_tcp6, + + ttest_ssockf_cgent_medium_tcp4, + ttest_ssockf_cgent_medium_tcp6, + + ttest_ssockf_cgent_large_tcp4, + ttest_ssockf_cgent_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp) +ttest_ssockf_csock_cases() -> + [ + {group, ttest_ssockf_csockf}, + {group, ttest_ssockf_csocko}, + {group, ttest_ssockf_csockt} + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = false +ttest_ssockf_csockf_cases() -> + [ + ttest_ssockf_csockf_small_tcp4, + ttest_ssockf_csockf_small_tcp6, + + ttest_ssockf_csockf_medium_tcp4, + ttest_ssockf_csockf_medium_tcp6, + + ttest_ssockf_csockf_large_tcp4, + ttest_ssockf_csockf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = once +ttest_ssockf_csocko_cases() -> + [ + ttest_ssockf_csocko_small_tcp4, + ttest_ssockf_csocko_small_tcp6, + + ttest_ssockf_csocko_medium_tcp4, + ttest_ssockf_csocko_medium_tcp6, + + ttest_ssockf_csocko_large_tcp4, + ttest_ssockf_csocko_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = false +%% Client: transport = socket(tcp), active = true +ttest_ssockf_csockt_cases() -> + [ + ttest_ssockf_csockt_small_tcp4, + ttest_ssockf_csockt_small_tcp6, + + ttest_ssockf_csockt_medium_tcp4, + ttest_ssockf_csockt_medium_tcp6, + + ttest_ssockf_csockt_large_tcp4, + ttest_ssockf_csockt_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +ttest_ssocko_cases() -> + [ + {group, ttest_ssocko_cgen}, + {group, ttest_ssocko_csock} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp +ttest_ssocko_cgen_cases() -> + [ + {group, ttest_ssocko_cgenf}, + {group, ttest_ssocko_cgeno}, + {group, ttest_ssocko_cgent} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = false +ttest_ssocko_cgenf_cases() -> + [ + ttest_ssocko_cgenf_small_tcp4, + ttest_ssocko_cgenf_small_tcp6, + + ttest_ssocko_cgenf_medium_tcp4, + ttest_ssocko_cgenf_medium_tcp6, + + ttest_ssocko_cgenf_large_tcp4, + ttest_ssocko_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = once +ttest_ssocko_cgeno_cases() -> + [ + ttest_ssocko_cgeno_small_tcp4, + ttest_ssocko_cgeno_small_tcp6, + + ttest_ssocko_cgeno_medium_tcp4, + ttest_ssocko_cgeno_medium_tcp6, + + ttest_ssocko_cgeno_large_tcp4, + ttest_ssocko_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = gen_tcp, active = true +ttest_ssocko_cgent_cases() -> + [ + ttest_ssocko_cgent_small_tcp4, + ttest_ssocko_cgent_small_tcp6, + + ttest_ssocko_cgent_medium_tcp4, + ttest_ssocko_cgent_medium_tcp6, + + ttest_ssocko_cgent_large_tcp4, + ttest_ssocko_cgent_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp) +ttest_ssocko_csock_cases() -> + [ + {group, ttest_ssocko_csockf}, + {group, ttest_ssocko_csocko}, + {group, ttest_ssocko_csockt} + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = false +ttest_ssocko_csockf_cases() -> + [ + ttest_ssocko_csockf_small_tcp4, + ttest_ssocko_csockf_small_tcp6, + + ttest_ssocko_csockf_medium_tcp4, + ttest_ssocko_csockf_medium_tcp6, + + ttest_ssocko_csockf_large_tcp4, + ttest_ssocko_csockf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = once +ttest_ssocko_csocko_cases() -> + [ + ttest_ssocko_csocko_small_tcp4, + ttest_ssocko_csocko_small_tcp6, + + ttest_ssocko_csocko_medium_tcp4, + ttest_ssocko_csocko_medium_tcp6, + + ttest_ssocko_csocko_large_tcp4, + ttest_ssocko_csocko_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = once +%% Client: transport = socket(tcp), active = true +ttest_ssocko_csockt_cases() -> + [ + ttest_ssocko_csockt_small_tcp4, + ttest_ssocko_csockt_small_tcp6, + + ttest_ssocko_csockt_medium_tcp4, + ttest_ssocko_csockt_medium_tcp6, + + ttest_ssocko_csockt_large_tcp4, + ttest_ssocko_csockt_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +ttest_ssockt_cases() -> + [ + {group, ttest_ssockt_cgen}, + {group, ttest_ssockt_csock} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp +ttest_ssockt_cgen_cases() -> + [ + {group, ttest_ssockt_cgenf}, + {group, ttest_ssockt_cgeno}, + {group, ttest_ssockt_cgent} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = false +ttest_ssockt_cgenf_cases() -> + [ + ttest_ssockt_cgenf_small_tcp4, + ttest_ssockt_cgenf_small_tcp6, + + ttest_ssockt_cgenf_medium_tcp4, + ttest_ssockt_cgenf_medium_tcp6, + + ttest_ssockt_cgenf_large_tcp4, + ttest_ssockt_cgenf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = once +ttest_ssockt_cgeno_cases() -> + [ + ttest_ssockt_cgeno_small_tcp4, + ttest_ssockt_cgeno_small_tcp6, + + ttest_ssockt_cgeno_medium_tcp4, + ttest_ssockt_cgeno_medium_tcp6, + + ttest_ssockt_cgeno_large_tcp4, + ttest_ssockt_cgeno_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = gen_tcp, active = true +ttest_ssockt_cgent_cases() -> + [ + ttest_ssockt_cgent_small_tcp4, + ttest_ssockt_cgent_small_tcp6, + + ttest_ssockt_cgent_medium_tcp4, + ttest_ssockt_cgent_medium_tcp6, + + ttest_ssockt_cgent_large_tcp4, + ttest_ssockt_cgent_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp) +ttest_ssockt_csock_cases() -> + [ + {group, ttest_ssockt_csockf}, + {group, ttest_ssockt_csocko}, + {group, ttest_ssockt_csockt} + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = false +ttest_ssockt_csockf_cases() -> + [ + ttest_ssockt_csockf_small_tcp4, + ttest_ssockt_csockf_small_tcp6, + + ttest_ssockt_csockf_medium_tcp4, + ttest_ssockt_csockf_medium_tcp6, + + ttest_ssockt_csockf_large_tcp4, + ttest_ssockt_csockf_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = once +ttest_ssockt_csocko_cases() -> + [ + ttest_ssockt_csocko_small_tcp4, + ttest_ssockt_csocko_small_tcp6, + + ttest_ssockt_csocko_medium_tcp4, + ttest_ssockt_csocko_medium_tcp6, + + ttest_ssockt_csocko_large_tcp4, + ttest_ssockt_csocko_large_tcp6 + ]. + +%% Server: transport = socket(tcp), active = true +%% Client: transport = socket(tcp), active = true +ttest_ssockt_csockt_cases() -> + [ + ttest_ssockt_csockt_small_tcp4, + ttest_ssockt_csockt_small_tcp6, + + ttest_ssockt_csockt_medium_tcp4, + ttest_ssockt_csockt_medium_tcp6, + + ttest_ssockt_csockt_large_tcp4, + ttest_ssockt_csockt_large_tcp6 + ]. + +%% ticket_cases() -> +%% []. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init_per_suite(Config) -> + case os:type() of + {win32, _} -> + not_yet_implemented(); + _ -> + ?LOGGER:start(), + Config + end. + +end_per_suite(_) -> + ?LOGGER:stop(), + ok. + +init_per_testcase(_TC, Config) -> + ?LOGGER:start(), + Config. + +end_per_testcase(_TC, Config) -> + ?LOGGER:stop(), + 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) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_udp4, + fun() -> + InitState = #{domain => inet, + type => dgram, + protocol => udp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically open (create) and close an IPv4 TCP (stream) socket. +%% With some extra checks... +api_b_open_and_close_tcp4(suite) -> + []; +api_b_open_and_close_tcp4(doc) -> + []; +api_b_open_and_close_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_open_and_close_tcp4, + fun() -> + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = api_b_open_and_close(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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) -> + case socket:supports(options, socket, protocol) of + true -> + Res = socket:getopt(Sock, socket, protocol), + {ok, {State, Res}}; + false -> + {ok, {State, not_supported}} + end + end}, + #{desc => "validate protocol", + cmd => fun({State, not_supported}) -> + ?SEV_IPRINT("socket option 'protocol' " + "not supported"), + {ok, State}; + ({#{protocol := Protocol} = State, {ok, Protocol}}) -> + {ok, State}; + ({#{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; + ({_, {error, _} = ERROR}) -> + ERROR + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket using +%% sendto and recvfrom.. +api_b_sendto_and_recvfrom_udp4(suite) -> + []; +api_b_sendto_and_recvfrom_udp4(doc) -> + []; +api_b_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendto_and_recvfrom_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock) -> + socket:recvfrom(Sock) + end, + InitState = #{domain => inet, + send => Send, + recv => Recv}, + ok = api_b_send_and_recv_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive on an IPv4 UDP (dgram) socket +%% using sendmsg and recvmsg. +api_b_sendmsg_and_recvmsg_udp4(suite) -> + []; +api_b_sendmsg_and_recvmsg_udp4(doc) -> + []; +api_b_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_b_sendmsg_and_recvmsg_udp4, + fun() -> + Send = fun(Sock, Data, Dest) -> + %% 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) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + Evaluator = ?SEV_START("tester", Seq, InitState), + ok = ?SEV_AWAIT_FINISH([Evaluator]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Basically send and receive using the "common" functions (send and recv) +%% on an IPv4 TCP (stream) socket. +api_b_send_and_recv_tcp4(suite) -> + []; +api_b_send_and_recv_tcp4(doc) -> + []; +api_b_send_and_recv_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(10)), + tc_try(api_b_send_and_recv_tcp4, + fun() -> + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + InitState = #{domain => inet, + 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) -> + ?TT(?SECS(10)), + tc_try(api_b_sendmsg_and_recvmsg_tcp4, + fun() -> + Send = fun(Sock, Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState = #{domain => inet, + 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 = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + 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 ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "await connection", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: ~n ~p", [Sock]), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await (recv) request", + cmd => fun(#{csock := Sock, recv := Recv}) -> + case Recv(Sock) of + {ok, ?BASIC_REQ} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_req), + ok + end}, + #{desc => "await continue (with send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_reply) + end}, + #{desc => "send reply", + cmd => fun(#{csock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REP) + end}, + #{desc => "announce ready (send reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket", + cmd => fun(#{csock := Sock}) -> + socket:close(Sock) + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The init part *** + #{desc => "which server (local) address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (send request)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send_req) + end}, + #{desc => "send request (to server)", + cmd => fun(#{sock := Sock, send := Send}) -> + Send(Sock, ?BASIC_REQ) + end}, + #{desc => "announce ready (send request)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_req), + ok + end}, + #{desc => "await recv reply (from server)", + cmd => fun(#{sock := Sock, recv := Recv}) -> + {ok, ?BASIC_REP} = Recv(Sock), + ok + end}, + #{desc => "announce ready (recv reply)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_reply), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_port => Port}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% *** The actual test *** + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(?SECS(1)), + ok + end}, + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + #{desc => "order client to continue (with send request)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send_req), + ok + end}, + #{desc => "await client ready (with send request)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send_req) + end}, + #{desc => "await server ready (request recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_req) + end}, + #{desc => "order server to continue (with send reply)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, send_reply), + ok + end}, + #{desc => "await server ready (with reply sent)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, send_reply) + end}, + #{desc => "await client ready (reply recv)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, recv_reply) + end}, + + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + Server = ?SEV_START("server", ServerSeq, InitState), + + i("start client evaluator"), + Client = ?SEV_START("client", ClientSeq, InitState), + i("await evaluator(s)"), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% API OPTIONS %% +%% %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_options(suite) -> + []; +api_opt_simple_otp_options(doc) -> + []; +api_opt_simple_otp_options(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_simple_otp_options, + fun() -> api_opt_simple_otp_options() end). + +api_opt_simple_otp_options() -> + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + Seq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "create dummy process", + cmd => fun(State) -> + Pid = spawn_link(fun() -> + put(sname, "dummy"), + receive + die -> + exit(normal) + end + end), + {ok, State#{dummy => Pid}} + end}, + + %% *** Check iow part *** + #{desc => "get iow", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, iow) of + {ok, IOW} when is_boolean(IOW) -> + {ok, State#{iow => IOW}}; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% #{desc => "enable debug", + %% cmd => fun(#{sock := Sock}) -> + %% ok = socket:setopt(Sock, otp, debug, true) + %% end}, + + #{desc => "set (new) iow", + cmd => fun(#{sock := Sock, iow := OldIOW} = State) -> + NewIOW = not OldIOW, + case Set(Sock, iow, NewIOW) of + ok -> + {ok, State#{iow => NewIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) iow", + cmd => fun(#{sock := Sock, iow := IOW}) -> + case Get(Sock, iow) of + {ok, IOW} -> + ok; + {ok, InvalidIOW} -> + {error, {invalid, InvalidIOW}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvbuf part *** + #{desc => "get rcvbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} when is_integer(RcvBuf) -> + {ok, State#{rcvbuf => RcvBuf}}; + {ok, {N, RcvBuf} = V} when is_integer(N) andalso + is_integer(RcvBuf) -> + {ok, State#{rcvbuf => V}}; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := {OldN, OldRcvBuf}} = State) -> + NewRcvBuf = {OldN+2, OldRcvBuf + 1024}, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, rcvbuf := OldRcvBuf} = State) when is_integer(OldRcvBuf) -> + NewRcvBuf = 2 * OldRcvBuf, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end; + (#{sock := Sock, rcvbuf := OldRcvBuf, + type := stream, + protocol := tcp} = State) when is_integer(OldRcvBuf) -> + NewRcvBuf = {2, OldRcvBuf}, + case Set(Sock, rcvbuf, NewRcvBuf) of + ok -> + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := RcvBuf}) -> + case Get(Sock, rcvbuf) of + {ok, RcvBuf} -> + ok; + {ok, InvalidRcvBuf} -> + {error, {invalid, InvalidRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + %% *** Check rcvctrlbuf part *** + #{desc => "get rcvctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} when is_integer(RcvCtrlBuf) -> + {ok, State#{rcvctrlbuf => RcvCtrlBuf}}; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := OldRcvCtrlBuf} = State) -> + NewRcvCtrlBuf = 2 * OldRcvCtrlBuf, + case Set(Sock, rcvctrlbuf, NewRcvCtrlBuf) of + ok -> + {ok, State#{rcvctrlbuf => NewRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) rcvctrlbuf", + cmd => fun(#{sock := Sock, rcvctrlbuf := RcvCtrlBuf}) -> + case Get(Sock, rcvctrlbuf) of + {ok, RcvCtrlBuf} -> + ok; + {ok, InvalidRcvCtrlBuf} -> + {error, {invalid, InvalidRcvCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Check sndctrlbuf part *** + #{desc => "get sndctrlbuf", + cmd => fun(#{sock := Sock} = State) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} when is_integer(SndCtrlBuf) -> + {ok, State#{sndctrlbuf => SndCtrlBuf}}; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := OldSndCtrlBuf} = State) -> + NewSndCtrlBuf = 2 * OldSndCtrlBuf, + case Set(Sock, sndctrlbuf, NewSndCtrlBuf) of + ok -> + {ok, State#{sndctrlbuf => NewSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "get (new) sndctrlbuf", + cmd => fun(#{sock := Sock, sndctrlbuf := SndCtrlBuf}) -> + case Get(Sock, sndctrlbuf) of + {ok, SndCtrlBuf} -> + ok; + {ok, InvalidSndCtrlBuf} -> + {error, {invalid, InvalidSndCtrlBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Check controlling-process part *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock}) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "set dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + Set(Sock, controlling_process, Dummy) + end}, + #{desc => "verify dummy as controlling-process", + cmd => fun(#{sock := Sock, dummy := Dummy}) -> + case Get(Sock, controlling_process) of + {ok, Dummy} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end} + ], + + i("start tcp (stream) evaluator"), + InitState1 = #{domain => inet, type => stream, protocol => tcp}, + Tester1 = ?SEV_START("tcp-tester", Seq, InitState1), + i("await tcp evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester1]), + + i("start udp (dgram) socket"), + InitState2 = #{domain => inet, type => dgram, protocol => udp}, + Tester2 = ?SEV_START("udp-tester", Seq, InitState2), + i("await udp evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester2]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple operations with the rcvbuf otp option +%% The operations we test here are only for type = stream and +%% protocol = tcp. +api_opt_simple_otp_rcvbuf_option(suite) -> + []; +api_opt_simple_otp_rcvbuf_option(doc) -> + []; +api_opt_simple_otp_rcvbuf_option(_Config) when is_list(_Config) -> + ?TT(?SECS(15)), + tc_try(api_opt_simple_otp_rcvbuf_option, + fun() -> api_opt_simple_otp_rcvbuf_option() end). + +api_opt_simple_otp_rcvbuf_option() -> + Get = fun(S) -> + socket:getopt(S, otp, rcvbuf) + end, + Set = fun(S, Val) -> + socket:setopt(S, otp, rcvbuf, Val) + end, + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case 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, + local_sa := LocalSA, + lport := Port}) -> + ServerSA = LocalSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + + %% *** The actual test part *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% Recv with default size for (otp) rcvbuf + #{desc => "await continue (recv initial)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, MsgSz} -> + ?SEV_IPRINT("MsgSz: ~p", [MsgSz]), + {ok, State#{msg_sz => MsgSz}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + ?SEV_IPRINT("try recv ~w bytes when rcvbuf is ~s", + [MsgSz, + case Get(Sock) of + {ok, RcvBuf} -> f("~w", [RcvBuf]); + {error, _} -> "-" + end]), + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv initial)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Recv with new size (1) for (otp) rcvbuf + #{desc => "await continue (recv 1)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, NewRcvBuf} -> + ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]), + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to setopt rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) -> + case Set(Sock, NewRcvBuf) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Recv with new size (2) for (otp) rcvbuf + #{desc => "await continue (recv 2)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, NewRcvBuf} -> + ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]), + {ok, State#{rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to setopt rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) -> + case Set(Sock, NewRcvBuf) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Recv with new size (3) for (otp) rcvbuf + #{desc => "await continue (recv 3, truncated)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, {ExpSz, NewRcvBuf}} -> + {ok, State#{msg_sz => ExpSz, + rcvbuf => NewRcvBuf}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to setopt rcvbuf", + cmd => fun(#{sock := Sock, rcvbuf := NewRcvBuf} = _State) -> + case Set(Sock, NewRcvBuf) of + ok -> + ?SEV_IPRINT("set new rcvbuf: ~p", [NewRcvBuf]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv", + cmd => fun(#{sock := Sock, msg_sz := MsgSz} = _State) -> + ?SEV_IPRINT("try recv ~w bytes of data", [MsgSz]), + case socket:recv(Sock) of + {ok, Data} when (size(Data) =:= MsgSz) -> + ok; + {ok, Data} -> + {error, {invalid_msg_sz, MsgSz, size(Data)}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + + %% Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket(s)", + cmd => fun(#{lsock := LSock, sock := Sock} = State) -> + sock_close(Sock), + sock_close(LSock), + State1 = maps:remove(sock, State), + State2 = maps:remove(lport, State1), + State3 = maps:remove(lsock, State2), + {ok, State3} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _Port} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + socket:connect(Sock, SSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send initial)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, send) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "send (initial) data to server", + cmd => fun(#{sock := Sock, data := Data} = _State) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send initial)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (send 1)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (1) data to server", + cmd => fun(#{sock := Sock, data := Data}) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send 1)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (send 2)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (2) data to server", + cmd => fun(#{sock := Sock, data := Data}) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send 2)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + #{desc => "await continue (send 3)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, send) + end}, + #{desc => "send (3) data to server", + cmd => fun(#{sock := Sock, data := Data}) -> + ?SEV_IPRINT("try send ~w bytes", [size(Data)]), + socket:send(Sock, Data) + end}, + #{desc => "announce ready (send 3)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + + %% Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock}) -> + socket:close(Sock) + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "order server start", + cmd => fun(#{server := Server}) -> + ?SEV_ANNOUNCE_START(Server) + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Server, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + #{desc => "order client start", + cmd => fun(#{client := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init) + end}, + + + %% The actual test (connecting) + #{desc => "order server accept (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect) + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept) + end}, + + %% The actual test (initial part) + #{desc => "order client continue (send initial)", + cmd => fun(#{client := Client, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv initial)", + cmd => fun(#{server := Server, data := Data} = _State) -> + ExpMsgSz = size(Data), + ?SEV_ANNOUNCE_CONTINUE(Server, recv, ExpMsgSz), + ok + end}, + #{desc => "await client ready (send initial)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv initial)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% The actual test (part 1) + #{desc => "order client continue (send 1)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv 1)", + cmd => fun(#{server := Server, data := Data} = _State) -> + MsgSz = size(Data), + NewRcvBuf = {2 + (MsgSz div 1024), 1024}, + ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf), + ok + end}, + #{desc => "await client ready (send 1)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv 1)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + %% The actual test (part 2) + #{desc => "order client continue (send 2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv 2)", + cmd => fun(#{server := Server, data := Data} = _State) -> + MsgSz = size(Data), + NewRcvBuf = {2 + (MsgSz div 2048), 2048}, + ?SEV_ANNOUNCE_CONTINUE(Server, recv, NewRcvBuf), + ok + end}, + #{desc => "await client ready (send 2)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv 2)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% The actual test (part 3) + #{desc => "order client continue (send 3)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order server continue (recv 3)", + cmd => fun(#{server := Server, data := Data} = _State) -> + MsgSz = size(Data), + BufSz = 2048, + N = MsgSz div BufSz - 1, + NewRcvBuf = {N, BufSz}, + ?SEV_ANNOUNCE_CONTINUE(Server, recv, + {N*BufSz, NewRcvBuf}) + end}, + #{desc => "await client ready (send 3)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv 3)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, client, recv, + [{client, Client}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + ?SEV_SLEEP(?SECS(1)), + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + State2 = maps:remove(server_sa, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% Create a data binary of 6*1024 bytes + Data = list_to_binary(lists:duplicate(6*4, lists:seq(0, 255))), + InitState = #{domain => inet, + data => Data}, + + i("create server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("create client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("create tester evaluator"), + TesterInitState = InitState#{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Perform some simple getopt and setopt with the level = otp options +api_opt_simple_otp_controlling_process(suite) -> + []; +api_opt_simple_otp_controlling_process(doc) -> + []; +api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> + ?TT(?SECS(5)), + tc_try(api_opt_simple_otp_controlling_process, + fun() -> api_opt_simple_otp_controlling_process() end). + +api_opt_simple_otp_controlling_process() -> + Get = fun(S, Key) -> + socket:getopt(S, otp, Key) + end, + Set = fun(S, Key, Val) -> + socket:setopt(S, otp, Key, Val) + end, + + ClientSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** The actual test *** + #{desc => "verify tester as controlling-process", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Tester} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (not owner)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, not_owner), + ok + end}, + #{desc => "await continue (owner)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, owner) + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt controlling-process transfer to tester", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + Set(Sock, controlling_process, Tester) + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (owner)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, owner), + ok + + end}, + + %% *** Termination *** + #{desc => "await termination", + cmd => fun(#{tester := Tester} = State) -> + ?SEV_AWAIT_TERMINATE(Tester, tester), + State1 = maps:remove(tester, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Protocol} = State) -> + Sock = sock_open(Domain, Type, Protocol), + {ok, State#{sock => Sock}} + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + + %% *** The actual test *** + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) start", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Client, Sock), + ok + end}, + #{desc => "await (client) ready (not owner)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, not_owner) + end}, + #{desc => "attempt controlling-process transfer to client", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + Set(Sock, controlling_process, Client) + end}, + #{desc => "verify client as controlling-process", + cmd => fun(#{client := Client, sock := Sock} = _State) -> + case Get(Sock, controlling_process) of + {ok, Client} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt invalid controlling-process transfer (to self)", + cmd => fun(#{sock := Sock} = _State) -> + case Set(Sock, controlling_process, self()) of + {error, not_owner} -> + ok; + ok -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (client) continue (owner)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, owner), + ok + end}, + #{desc => "await (client) ready (2)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, owner), + ok + end}, + #{desc => "verify self as controlling-process", + cmd => fun(#{sock := Sock} = _State) -> + Self = self(), + case Get(Sock, controlling_process) of + {ok, Self} -> + ok; + {ok, InvalidPid} -> + {error, {invalid, InvalidPid}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** Termination *** + #{desc => "order (client) terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + {ok, maps:remove(client, State)} + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start tcp (stream) client evaluator"), + ClientInitState1 = #{}, + Client1 = ?SEV_START("tcp-client", ClientSeq, ClientInitState1), + + i("start tcp (stream) tester evaluator"), + TesterInitState1 = #{domain => inet, + type => stream, + protocol => tcp, + client => Client1#ev.pid}, + Tester1 = ?SEV_START("tcp-tester", TesterSeq, TesterInitState1), + + i("await tcp evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester1, Client1]), + + i("start udp (dgram) client evaluator"), + ClientInitState2 = #{}, + Client2 = ?SEV_START("udp-client", ClientSeq, ClientInitState2), + + i("start udp (dgram) tester evaluator"), + TesterInitState2 = #{domain => inet, + type => dgram, + protocol => udp, + client => Client2#ev.pid}, + Tester2 = ?SEV_START("udp-tester", TesterSeq, TesterInitState2), + + i("await udp evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester2, Client2]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% +%% 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() -> + ?TT(?SECS(10)), + InitState = #{domain => inet, + backlog => 1, + timeout => 5000, + connect_limit => 3}, + 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(), + ?TT(?SECS(10)), + InitState = #{domain => inet6, + backlog => 1, + timeout => 5000, + connect_limit => 3}, + ok = api_to_connect_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% We use the backlog (listen) argument to test this. +%% Note that the behaviour of the TCP "server side" can vary when +%% a client connect to a "busy" server (full backlog). +%% For instance, on FreeBSD (11.2) the reponse when the backlog is full +%% is a econreset. + +api_to_connect_tcp(InitState) -> + process_flag(trap_exit, true), + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Backlog} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + backlog => Backlog}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case 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, backlog := Backlog}) -> + socket:listen(LSock, Backlog) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{lsock := Sock} = State) -> + sock_close(Sock), + State1 = maps:remove(lport, State), + State2 = maps:remove(sock, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + ?SEV_IPRINT("try create node on ~p", [Host]), + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node} = State) -> + Pid = api_toc_tcp_client_start(Node), + ?SEV_IPRINT("remote client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]) of + {ok, {ConTimeout, ConLimit}} -> + {ok, State#{connect_timeout => ConTimeout, + connect_limit => ConLimit}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := RClient, + connect_timeout := ConTimeout, + connect_limit := ConLimit}) -> + ?SEV_ANNOUNCE_CONTINUE(RClient, connect, + {ConTimeout, ConLimit}), + ok + end}, + #{desc => "await remote client ready (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, connect, + [{tester, Tester}]) of + {ok, ok = _Result} -> + {ok, maps:remove(connect_limit, State)}; + {ok, {error, {connect_limit_reached,R,L}}} -> + {skip, + ?LIB:f("Connect limit reached ~w: ~w", + [L, R])}; + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, RClient}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "order server start", + cmd => fun(#{server := Server, + backlog := Backlog}) -> + ?SEV_ANNOUNCE_START(Server, Backlog), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server, local_sa := LSA} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}} + end}, + #{desc => "order client start", + cmd => fun(#{client := Client, + server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init), + ok + end}, + + %% The actual test + %% The server does nothing (this is the point), no accept, + %% the client tries to connect. + #{desc => "order client continue (connect)", + cmd => fun(#{client := Client, + timeout := Timeout, + connect_limit := ConLimit} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect, + {Timeout, ConLimit}), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + State1 = maps:remove(server, State), + State2 = maps:remove(server_sa, State1), + {ok, State2} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("create server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("create client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("create tester evaluator"), + TesterInitState = InitState#{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +api_toc_tcp_client_start(Node) -> + Self = self(), + Fun = fun() -> api_toc_tcp_client(Self) end, + erlang:spawn(Node, Fun). + +api_toc_tcp_client(Parent) -> + api_toc_tcp_client_init(Parent), + ServerSA = api_toc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + api_toc_tcp_client_announce_ready(Parent, init), + {To, ConLimit} = api_toc_tcp_client_await_continue(Parent, connect), + Result = api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit), + ?SEV_IPRINT("result: ~p", [Result]), + api_toc_tcp_client_announce_ready(Parent, connect, Result), + Reason = api_toc_tcp_client_await_terminate(Parent), + exit(Reason). + +api_toc_tcp_client_init(Parent) -> + put(sname, "rclient"), + %% i("api_toc_tcp_client_init -> entry"), + _MRef = erlang:monitor(process, Parent), + ok. + +api_toc_tcp_client_await_start(Parent) -> + %% i("api_toc_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +api_toc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). +api_toc_tcp_client_announce_ready(Parent, Slogan, Result) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result). + +api_toc_tcp_client_await_continue(Parent, Slogan) -> + %% i("api_toc_tcp_client_await_continue -> entry"), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ok; + {ok, Extra} -> + Extra; + {error, Reason} -> + exit({await_continue, Slogan, Reason}) + end. + +api_toc_tcp_client_await_terminate(Parent) -> + %% i("api_toc_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +api_to_connect_tcp_await_timeout(To, ServerSA, Domain, ConLimit) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + NewSock = fun() -> + S = case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, OReason} -> + ?FAIL({open, OReason}) + end, + case socket:bind(S, LSA) of + {ok, _} -> + S; + {error, BReason} -> + ?FAIL({bind, BReason}) + end + end, + api_to_connect_tcp_await_timeout(1, ConLimit, To, ServerSA, NewSock, []). + +api_to_connect_tcp_await_timeout(ID, ConLimit, _To, _ServerSA, _NewSock, Acc) + when (ID > ConLimit) -> + api_to_connect_tcp_await_timeout3(Acc), + {error, {connect_limit_reached, ID, ConLimit}}; +api_to_connect_tcp_await_timeout(ID, ConLimit, To, ServerSA, NewSock, Acc) -> + case api_to_connect_tcp_await_timeout2(ID, To, ServerSA, NewSock) of + ok -> + %% ?SEV_IPRINT("success when number of socks: ~w", [length(Acc)]), + api_to_connect_tcp_await_timeout3(Acc), + ok; + {ok, Sock} -> + %% ?SEV_IPRINT("~w: unexpected success (connect)", [ID]), + api_to_connect_tcp_await_timeout(ID+1, ConLimit, + To, ServerSA, NewSock, + [Sock|Acc]); + {error, _} = ERROR -> + ERROR + end. + +api_to_connect_tcp_await_timeout2(_ID, To, ServerSA, NewSock) -> + Sock = NewSock(), + %% ?SEV_IPRINT("~w: try connect", [ID]), + Start = t(), + case socket:connect(Sock, ServerSA, To) of + {error, timeout} -> + Stop = t(), + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + (catch socket:close(Sock)), + ok; + true -> + (catch socket:close(Sock)), + ?FAIL({unexpected_timeout, TDiff, To}) + end; + {error, econnreset = _Reason} -> + (catch socket:close(Sock)), + ok; + {error, Reason} -> + (catch socket:close(Sock)), + ?FAIL({connect, Reason}); + ok -> + {ok, Sock} + end. + +api_to_connect_tcp_await_timeout3([]) -> + ok; +api_to_connect_tcp_await_timeout3([Sock|Socka]) -> + (catch socket:close(Sock)), + api_to_connect_tcp_await_timeout3(Socka). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv4 TCP (stream) socket. +api_to_accept_tcp4(suite) -> + []; +api_to_accept_tcp4(doc) -> + []; +api_to_accept_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_accept_tcp4, + fun() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + 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 *** + ?SEV_FINISH_NORMAL + ], + + i("create tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the multi accept timeout option +%% on an IPv4 TCP (stream) socket with multiple acceptor processes +%% (three in this case). +api_to_maccept_tcp4(suite) -> + []; +api_to_maccept_tcp4(doc) -> + []; +api_to_maccept_tcp4(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_to_maccept_tcp4, + fun() -> + InitState = #{domain => inet, timeout => 5000}, + ok = api_to_maccept_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the accept timeout option +%% on an IPv6 TCP (stream) socket. +api_to_maccept_tcp6(suite) -> + []; +api_to_maccept_tcp6(doc) -> + []; +api_to_maccept_tcp6(_Config) when is_list(_Config) -> + ?TT(?SECS(20)), + tc_try(api_to_maccept_tcp4, + fun() -> + 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) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + 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 (init)", + cmd => fun(#{lsock := LSock, tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init, LSock), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + ?SEV_EPRINT("Unexpected accept success: " + "~n ~p", [Sock]), + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = _State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + ok; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_TERMINATE(Tester, tester), + ok + end}, + %% *** Close (listen) socket *** + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, LSock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + lsock => LSock}} + + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test part *** + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "attempt to accept (without success)", + cmd => fun(#{lsock := LSock, timeout := To} = State) -> + Start = t(), + case socket:accept(LSock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, Sock} -> + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + State1 = maps:remove(start, State), + State2 = maps:remove(stop, State1), + {ok, State2}; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% Init part + #{desc => "monitor prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + + %% Start the prim-acceptor + #{desc => "start prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await prim-acceptor ready (init)", + cmd => fun(#{prim_acceptor := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acceptor, init), + {ok, State#{lsock => Sock}} + end}, + + %% Start sec-acceptor-1 + #{desc => "start sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid, lsock := LSock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LSock), + ok + end}, + #{desc => "await sec-acceptor 1 ready (init)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor1, init) + end}, + + %% Start sec-acceptor-2 + #{desc => "start sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid, lsock := LSock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, LSock), + ok + end}, + #{desc => "await sec-acceptor 2 ready (init)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor2, init) + end}, + + %% Activate the acceptor(s) + #{desc => "active prim-acceptor", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "active sec-acceptor 1", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + #{desc => "active sec-acceptor 2", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + + %% Await acceptor(s) completions + #{desc => "await prim-acceptor ready (accept)", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_acceptor, accept) + end}, + #{desc => "await sec-acceptor 1 ready (accept)", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor1, accept) + end}, + #{desc => "await sec-acceptor 2 ready (accept)", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_acceptor2, accept) + end}, + + %% Terminate + #{desc => "order prim-acceptor to terminate", + cmd => fun(#{prim_acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await prim-acceptor termination", + cmd => fun(#{prim_acceptor := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(prim_acceptor, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order sec-acceptor 1 to terminate", + cmd => fun(#{sec_acceptor1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await sec-acceptor 1 termination", + cmd => fun(#{sec_acceptor1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order sec-acceptor 2 to terminate", + cmd => fun(#{sec_acceptor2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await sec-acceptor 2 termination", + cmd => fun(#{sec_acceptor2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(sec_acceptor2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("create prim-acceptor evaluator"), + PrimAInitState = InitState, + PrimAcceptor = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAInitState), + + i("create sec-acceptor 1 evaluator"), + SecAInitState1 = maps:remove(domain, InitState), + SecAcceptor1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAInitState1), + + i("create sec-acceptor 2 evaluator"), + SecAInitState2 = SecAInitState1, + SecAcceptor2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAInitState2), + + i("create tester evaluator"), + TesterInitState = #{prim_acceptor => PrimAcceptor#ev.pid, + sec_acceptor1 => SecAcceptor1#ev.pid, + sec_acceptor2 => SecAcceptor2#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([PrimAcceptor, SecAcceptor1, SecAcceptor2, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv4 TCP (stream) socket. +api_to_send_tcp4(suite) -> + []; +api_to_send_tcp4(doc) -> + []; +api_to_send_tcp4(_Config) when is_list(_Config) -> + tc_try(api_to_send_tcp4, + fun() -> + not_yet_implemented()%% , + %% ok = api_to_send_tcp(inet) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the send timeout option +%% on an IPv6 TCP (stream) socket. +api_to_send_tcp6(suite) -> + []; +api_to_send_tcp6(doc) -> + []; +api_to_send_tcp6(_Config) when is_list(_Config) -> + tc_try(api_to_send_tcp6, + fun() -> + 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() -> + ?TT(?SECS(10)), + 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 -> + ?TT(?SECS(10)), + 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) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester}) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case 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 => "announce ready (init)", + cmd => fun(#{tester := Tester, lport := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (accept and recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept_recv) + end}, + #{desc => "attempt accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "attempt to recv (without success)", + cmd => fun(#{sock := Sock, recv := Recv, timeout := To} = State) -> + Start = t(), + case Recv(Sock, To) of + {error, timeout} -> + {ok, State#{start => Start, stop => t()}}; + {ok, _Data} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "validate timeout time", + cmd => fun(#{start := Start, stop := Stop, timeout := To} = State) -> + TDiff = tdiff(Start, Stop), + if + (TDiff >= To) -> + State1 = maps:remove(start, State), + State2 = maps:remove(stop, State1), + {ok, State2}; + true -> + {error, {unexpected_timeout, TDiff, To}} + end + end}, + #{desc => "announce ready (recv timeout success)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, accept_recv), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close (traffic) socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + #{desc => "close (listen) socket", + cmd => fun(#{lsock := LSock} = State) -> + sock_close(LSock), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Port} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_port => Port}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain, server_port := Port} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + SSA = LSA#{port => Port}, + {ok, State#{local_sa => LSA, server_sa => SSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{sock := Sock, local_sa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% *** The actual test *** + #{desc => "await continue (with connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect) + end}, + #{desc => "connect", + cmd => fun(#{sock := Sock, server_sa := SSA}) -> + sock_connect(Sock, SSA), + ok + end}, + + %% *** Termination *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Client} = _State) -> + _MRef = erlang:monitor(process, Client), + ok + end}, + + %% *** Activate server *** + #{desc => "start server", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_START(Server), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Server} = State) -> + {ok, Port} = ?SEV_AWAIT_READY(Server, server, init), + {ok, State#{server_port => Port}} + end}, + #{desc => "order server to continue (with accept)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, accept_recv), + ok + end}, + + %% *** Activate client *** + #{desc => "start client", + cmd => fun(#{client := Client, server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Client, Port), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, init) + end}, + + %% *** The actual test *** + #{desc => "order client to continue (with connect)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await server ready (accept/recv)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept_recv) + end}, + + %% *** Termination *** + #{desc => "order client to terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Client} = State) -> + case ?SEV_AWAIT_TERMINATION(Client) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + case ?SEV_AWAIT_TERMINATION(Server) of + ok -> + State1 = maps:remove(server, State), + State2 = maps:remove(server_port, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = InitState, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start tester evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvfrom timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_recvfrom_udp4(suite) -> + []; +api_to_recvfrom_udp4(doc) -> + []; +api_to_recvfrom_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_recvfrom_udp4, + fun() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + 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 *** + ?SEV_FINISH_NORMAL + ], + + i("start tester evaluator"), + Tester = ?SEV_START("tester", TesterSeq, InitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% This test case is intended to test the recvmsg timeout option +%% on an IPv4 UDP (dgram) socket. +api_to_recvmsg_udp4(suite) -> + []; +api_to_recvmsg_udp4(doc) -> + []; +api_to_recvmsg_udp4(_Config) when is_list(_Config) -> + tc_try(api_to_recvmsg_udp4, + fun() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + 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() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(5)), + 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(), + ?TT(?SECS(5)), + 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() -> + ?TT(?SECS(5)), + 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(), + ?TT(?SECS(5)), + 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) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create socket", + cmd => fun(#{domain := Domain, + type := Type, + protocol := Proto} = State) -> + case socket:open(Domain, Type, Proto) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% *** The actual test *** + %% We *intentially* leave the socket "as is", no explicit close + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor owner", + cmd => fun(#{owner := Owner} = _State) -> + _MRef = erlang:monitor(process, Owner), + ok + end}, + #{desc => "order (owner) start", + cmd => fun(#{owner := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await (owner) ready", + cmd => fun(#{owner := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, owner, init), + {ok, State#{sock => Sock}} + end}, + #{desc => "verify owner as controlling-process", + cmd => fun(#{owner := Pid, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, Pid} -> + ok; + {ok, Other} -> + {error, {unexpected_owner, Other}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order (owner) terminate", + cmd => fun(#{owner := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await (owner) termination", + cmd => fun(#{owner := Pid} = _State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + %% The reason we get closed, is that as long as there is a ref to + %% the resource (socket), then it will not be garbage collected. + #{desc => "verify no socket (closed)", + cmd => fun(#{owner := Pid, sock := Sock} = _State) -> + case socket:getopt(Sock, otp, controlling_process) of + {ok, OtherPid} -> + {error, {unexpected_success, Pid, OtherPid}}; + {error, closed} -> + ok; + {error, Reason} -> + ?SEV_IPRINT("expected failure: ~p", [Reason]), + {error, {unexpected_failure, Reason}} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start (socket) owner evaluator"), + Owner = ?SEV_START("owner", OwnerSeq, InitState), + + i("start tester evaluator"), + TesterInitState = #{owner => Owner#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Owner, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recv function. +%% Socket is IPv4. +%% +%% <KOLLA> +%% +%% We should really have a similar test cases for when the controlling +%% process exits and there are other processes in recv, accept, and +%% all the other functions. +%% +%% </KOLLA> + +sc_lc_recv_response_tcp4(suite) -> + []; +sc_lc_recv_response_tcp4(doc) -> + []; +sc_lc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recv_response_tcp4, + fun() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + 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 (acceptor) is the server that accepts connections. + %% But it is also suppose to close the connection socket, + %% and trigger the read failure (=closed) for the handler process. + AcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => 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, local_sa := 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}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Port), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, {H1, H2, H3}} -> + {ok, State#{handler1 => H1, + handler2 => H2, + handler3 => H3}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("connection accepted"), + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "transfer connection to handler 1", + cmd => fun(#{handler1 := Handler, csock := Sock}) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), + ok + end}, + #{desc => "transfer connection to handler 2", + cmd => fun(#{handler2 := Handler, csock := Sock}) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), + ok + end}, + #{desc => "transfer connection to handler 3", + cmd => fun(#{handler3 := Handler, csock := Sock}) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, transfer, Sock), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close), + ok + end}, + #{desc => "close the connection socket", + cmd => fun(#{csock := Sock} = State) -> + %% ok = socket:setopt(Sock, otp, debug, true), + case socket:close(Sock) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{lsock := Sock} = State) -> + case socket:close(Sock) of + ok -> + State1 = maps:remove(lsock, State), + State2 = maps:remove(lport, State1), + {ok, State2}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% The point of this is to perform the recv for which + %% we are testing the reponse. + HandlerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + {Tester, Acceptor} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + acceptor => Acceptor}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Acceptor} = _State) -> + _MRef = erlang:monitor(process, Acceptor), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (transfer)", + cmd => fun(#{acceptor := Pid} = State) -> + case ?SEV_AWAIT_CONTINUE(Pid, acceptor, transfer) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (transfer)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, transfer), + ok + end}, + #{desc => "attempt recv (=> closed)", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock) of + {ok, _Data} -> + ?SEV_EPRINT("Unexpected data received"), + {error, unexpected_success}; + {error, closed} -> + ?SEV_IPRINT("received expected 'closed' " + "result"), + State1 = maps:remove(sock, State), + {ok, State1}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Unexpected read faulure: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "announce ready (recv closed)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + %% The point of this is basically just to create the connection. + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => 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, local_sa := LSA} = _State) -> + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, local_sa := LSA} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, connect) of + {ok, Port} -> + ServerSA = LSA#{port => Port}, + {ok, State#{server_sa => ServerSA}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "connect to server", + cmd => fun(#{sock := Sock, server_sa := ServerSA}) -> + socket:connect(Sock, ServerSA) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + %% *** Terminate *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + sock_close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor acceptor", + cmd => fun(#{acceptor := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 1", + cmd => fun(#{handler1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 2", + cmd => fun(#{handler2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor handler 3", + cmd => fun(#{handler3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the acceptor + #{desc => "order acceptor start", + cmd => fun(#{acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await acceptor ready (init)", + cmd => fun(#{acceptor := Pid} = State) -> + case ?SEV_AWAIT_READY(Pid, acceptor, init) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% Start the handler(s) + #{desc => "order handler 1 start", + cmd => fun(#{acceptor := Acceptor, handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Acceptor), + ok + end}, + #{desc => "await handler 1 ready (init)", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, handler1, init) + end}, + #{desc => "order handler 2 start", + cmd => fun(#{acceptor := Acceptor, handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Acceptor), + ok + end}, + #{desc => "await handler 2 ready (init)", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, handler2, init) + end}, + #{desc => "order handler 3 start", + cmd => fun(#{acceptor := Acceptor, handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Acceptor), + ok + end}, + #{desc => "await handler 3 ready (init)", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, handler3, init) + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order acceptor to continue (accept)", + cmd => fun(#{acceptor := Pid, + handler1 := H1, + handler2 := H2, + handler3 := H3} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, {H1, H2, H3}), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client to continue (connect)", + cmd => fun(#{client := Pid, lport := Port} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect, Port), + ok + end}, + #{desc => "await acceptor ready (accept)", + cmd => fun(#{acceptor := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, acceptor, accept) + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, connect) + end}, + #{desc => "await handler 1 ready (transfer)", + cmd => fun(#{handler1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler1, transfer) + end}, + #{desc => "await handler 2 ready (transfer)", + cmd => fun(#{handler2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler2, transfer) + end}, + #{desc => "await handler 3 ready (transfer)", + cmd => fun(#{handler3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler3, transfer) + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order acceptor to continue (close connection socket)", + cmd => fun(#{acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await acceptor ready (close)", + cmd => fun(#{acceptor := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, acceptor, close) + end}, + #{desc => "await handler 1 ready (recv closed)", + cmd => fun(#{handler1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler1, recv_closed) + end}, + #{desc => "await handler 2 ready (recv closed)", + cmd => fun(#{handler2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler2, recv_closed) + end}, + #{desc => "await handler 3 ready (recv closed)", + cmd => fun(#{handler3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, handler3, recv_closed) + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(client, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler1, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler2, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(handler3, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order acceptor to terminate", + cmd => fun(#{acceptor := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await acceptor termination", + cmd => fun(#{acceptor := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(acceptor, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start acceptor evaluator"), + AccInitState = InitState, + Acceptor = ?SEV_START("acceptor", AcceptorSeq, AccInitState), + + i("start handler 1 evaluator"), + HandlerInitState = #{recv => maps:get(recv, InitState)}, + Handler1 = ?SEV_START("handler-1", HandlerSeq, HandlerInitState), + + i("start handler 2 evaluator"), + Handler2 = ?SEV_START("handler-2", HandlerSeq, HandlerInitState), + + i("start handler 3 evaluator"), + Handler3 = ?SEV_START("handler-3", HandlerSeq, HandlerInitState), + + i("start client evaluator"), + ClientInitState = InitState, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start tester evaluator"), + TesterInitState = #{acceptor => Acceptor#ev.pid, + handler1 => Handler1#ev.pid, + handler2 => Handler2#ev.pid, + handler3 => Handler3#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Acceptor, + Handler1, Handler2, Handler3, + Client, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while a process is calling the recvfrom function. +%% Socket is IPv4. +%% + +sc_lc_recvfrom_response_udp4(suite) -> + []; +sc_lc_recvfrom_response_udp4(doc) -> + []; +sc_lc_recvfrom_response_udp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvfrom_response_udp4, + fun() -> + ?TT(?SECS(30)), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end, + InitState = #{domain => inet, + type => dgram, + protocol => udp, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recv function. +%% Socket is IPv6. + +sc_lc_recvfrom_response_udp6(suite) -> + []; +sc_lc_recvfrom_response_udp6(doc) -> + []; +sc_lc_recvfrom_response_udp6(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvfrom_response_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + Recv = fun(Sock, To) -> socket:recvfrom(Sock, [], To) end, + InitState = #{domain => inet6, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_receive_response_udp(InitState) -> + PrimServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "open socket", + cmd => fun(#{domain := Domain} = State) -> + Sock = sock_open(Domain, dgram, udp), + SA = sock_sockname(Sock), + {ok, State#{sock => Sock, sa => SA}} + end}, + #{desc => "bind socket", + cmd => fun(#{sock := Sock, local_sa := LSA}) -> + sock_bind(Sock, LSA), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock}) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% The actual test + #{desc => "await continue (recv, with timeout)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "receive, with timeout", + cmd => fun(#{sock := Sock, recv := Recv, timeout := Timeout}) -> + case Recv(Sock, Timeout) of + {error, timeout} -> + ok; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv, with timeout)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, terminate) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SecServerSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + + end}, + #{desc => "receive", + cmd => fun(#{sock := Sock, recv := Recv} = State) -> + case Recv(Sock, infinity) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv closed)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor primary server", + cmd => fun(#{prim_server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 1", + cmd => fun(#{sec_server1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 2", + cmd => fun(#{sec_server2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary server 3", + cmd => fun(#{sec_server3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the primary server + #{desc => "order 'primary server' start", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await 'primary server' ready (init)", + cmd => fun(#{prim_server := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_server, init), + {ok, State#{sock => Sock}} + end}, + + %% Start the secondary server 1 + #{desc => "order 'secondary server 1' start", + cmd => fun(#{sec_server1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary server 1' ready (init)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_server1, init) + end}, + + %% Start the secondary server 2 + #{desc => "order 'secondary server 2' start", + cmd => fun(#{sec_server2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary server 2' ready (init)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_server2, init) + end}, + + %% Start the secondary server 3 + #{desc => "order 'secondary server 3' start", + cmd => fun(#{sec_server3 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary server 3' ready (init)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_server3, init) + end}, + + + %% The actual test + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary server 1' to continue (recv)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary server 2' to continue (recv)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary server 3' to continue (recv)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'primary server' to continue (recv, with timeout)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv, ?SECS(5)), + ok + end}, + #{desc => "await 'primary server' ready (recv, with timeout)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_server, recv) + end}, + #{desc => "order 'primary server' to continue (close)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await 'primary server' ready (close)", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, prim_server, close) + end}, + #{desc => "await 'secondary server 1' ready (closed)", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_server1, recv_closed) + end}, + #{desc => "await 'secondary server 2' ready (closed)", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_server2, recv_closed) + end}, + #{desc => "await 'secondary server 3' ready (closed)", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ?SEV_AWAIT_READY(Pid, sec_server3, recv_closed) + end}, + + %% Terminations + #{desc => "order 'secondary server 3' to terminate", + cmd => fun(#{sec_server3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary server 3' termination", + cmd => fun(#{sec_server3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server3, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary server 2' to terminate", + cmd => fun(#{sec_server2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary server 2' termination", + cmd => fun(#{sec_server2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server2, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary server 1' to terminate", + cmd => fun(#{sec_server1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary server 1' termination", + cmd => fun(#{sec_server1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_server1, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'primary server' to terminate", + cmd => fun(#{prim_server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'primary server' termination", + cmd => fun(#{prim_server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(prim_server, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start 'primary server' evaluator"), + PrimSrvInitState = InitState, + PrimServer = ?SEV_START("prim-server", PrimServerSeq, PrimSrvInitState), + + i("start 'secondary server 1' evaluator"), + SecSrvInitState = #{recv => maps:get(recv, InitState)}, + SecServer1 = ?SEV_START("sec-server-1", SecServerSeq, SecSrvInitState), + + i("start 'secondary server 2' evaluator"), + SecServer2 = ?SEV_START("sec-server-2", SecServerSeq, SecSrvInitState), + + i("start 'secondary server 3' evaluator"), + SecServer3 = ?SEV_START("sec-server-3", SecServerSeq, SecSrvInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{prim_server => PrimServer#ev.pid, + sec_server1 => SecServer1#ev.pid, + sec_server2 => SecServer2#ev.pid, + sec_server3 => SecServer3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([PrimServer, + SecServer1, SecServer2, SecServer3, + Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_tcp4(suite) -> + []; +sc_lc_recvmsg_response_tcp4(doc) -> + []; +sc_lc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_tcp4, + fun() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + 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 +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_lc_recvmsg_response_udp4(suite) -> + []; +sc_lc_recvmsg_response_udp4(doc) -> + []; +sc_lc_recvmsg_response_udp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_recvmsg_response_udp4, + fun() -> + ?TT(?SECS(10)), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the recvmsg function. +%% Socket is IPv6. + +sc_lc_recvmsg_response_udp6(suite) -> + []; +sc_lc_recvmsg_response_udp6(doc) -> + []; +sc_lc_recvmsg_response_udp6(_Config) when is_list(_Config) -> + tc_try(sc_recvmsg_response_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + Recv = fun(Sock, To) -> socket:recvmsg(Sock, To) end, + InitState = #{domain => inet6, + recv => Recv}, + ok = sc_lc_receive_response_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% locally closed while the process is calling the accept function. +%% We test what happens with a non-controlling_process also, since we +%% git the setup anyway. +%% Socket is IPv4. + +sc_lc_acceptor_response_tcp4(suite) -> + []; +sc_lc_acceptor_response_tcp4(doc) -> + []; +sc_lc_acceptor_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_lc_acceptor_response_tcp4, + fun() -> + ?TT(?SECS(10)), + 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(), + ?TT(?SECS(10)), + InitState = #{domain => inet, + type => stream, + protocol => tcp}, + ok = sc_lc_acceptor_response_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_lc_acceptor_response_tcp(InitState) -> + PrimAcceptorSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start (from tester)", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + 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#{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}, + #{desc => "make listen socket", + cmd => fun(#{sock := Sock}) -> + socket:listen(Sock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, init, Sock), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, accept) of + {ok, Timeout} -> + {ok, State#{timeout => Timeout}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await connection", + cmd => fun(#{sock := Sock, timeout := Timeout} = _State) -> + case socket:accept(Sock, Timeout) of + {error, timeout} -> + ok; + {ok, Sock} -> + ?SEV_EPRINT("unexpected success"), + (catch socket:close(Sock)), + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept timeout)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_timeout), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, close) + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + % Termination + #{desc => "await terminate", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + SecAcceptorSeq = + [ + %% *** Init part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, Sock} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, sock => Sock}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init) + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ok = ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{sock := Sock} = State) -> + case socket:accept(Sock) of + {error, closed} -> + {ok, maps:remove(sock, State)}; + {ok, _} -> + {error, unexpected_success}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept closed)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept_closed) + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor 'primary acceptor'", + cmd => fun(#{prim_acc := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor 'secondary acceptor 1'", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary acceptor 2", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor secondary acceptor 3", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the primary server + #{desc => "order 'primary acceptor' start", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await 'primary acceptor' ready (init)", + cmd => fun(#{prim_acc := Pid} = State) -> + {ok, Sock} = ?SEV_AWAIT_READY(Pid, prim_acc, init), + {ok, State#{sock => Sock}} + end}, + + %% Start the secondary acceptor 1 + #{desc => "order 'secondary acceptor 1' start", + cmd => fun(#{sec_acc1 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary acceptor 1' ready (init)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, init) + end}, + + %% Start the secondary acceptor 2 + #{desc => "order 'secondary acceptor 2' start", + cmd => fun(#{sec_acc2 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary acceptor 2' ready (init)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, init) + end}, + + %% Start the secondary acceptor 3 + #{desc => "order 'secondary acceptor 3' start", + cmd => fun(#{sec_acc3 := Pid, sock := Sock} = _State) -> + ?SEV_ANNOUNCE_START(Pid, Sock), + ok + end}, + #{desc => "await 'secondary acceptor 3' ready (init)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, init) + end}, + + + %% The actual test + %% Make all the seondary servers continue, with an infinit recvfrom + %% and then the prim-server with a timed recvfrom. + %% After the prim server notifies us (about the timeout) we order it + %% to close the socket, which should cause the all the secondary + %% server to return with error-closed. + + #{desc => "order 'secondary acceptor 1' to continue (accept)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary acceptor 2' to continue (accept)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'secondary acceptor 3' to continue (accept)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order 'primary acceptor' to continue", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept, ?SECS(5)), + ok + end}, + #{desc => "await 'primary acceptor' ready (accept timeout)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, prim_acc, accept_timeout) + end}, + #{desc => "order 'primary acceptor' to continue (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await 'primary acceptor' ready (close)", + cmd => fun(#{prim_acc := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, prim_acc, close) + end}, + #{desc => "await 'secondary acceptor 1' ready (accept closed)", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc1, accept_closed) + end}, + #{desc => "await 'secondary acceptor 2' ready (accept closed)", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc2, accept_closed) + end}, + #{desc => "await 'secondary acceptor 3' ready (accept closed)", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, sec_acc3, accept_closed) + end}, + + + %% Terminations + #{desc => "order 'secondary acceptor 3' to terminate", + cmd => fun(#{sec_acc3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary acceptor 3' termination", + cmd => fun(#{sec_acc3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc3, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary acceptor 2' to terminate", + cmd => fun(#{sec_acc2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary acceptor 2' termination", + cmd => fun(#{sec_acc2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc2, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'secondary acceptor 1' to terminate", + cmd => fun(#{sec_acc1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'secondary acceptor 1' termination", + cmd => fun(#{sec_acc1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(sec_acc1, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order 'primary acceptor' to terminate", + cmd => fun(#{prim_acc := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await 'primary acceptor' termination", + cmd => fun(#{prim_acc := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + {ok, maps:remove(prim_acc, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start 'primary acceptor' evaluator"), + PrimAccInitState = InitState, + PrimAcc = ?SEV_START("prim-acceptor", PrimAcceptorSeq, PrimAccInitState), + + i("start 'secondary acceptor 1' evaluator"), + SecAccInitState = #{}, + SecAcc1 = ?SEV_START("sec-acceptor-1", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 2' evaluator"), + SecAcc2 = ?SEV_START("sec-acceptor-2", SecAcceptorSeq, SecAccInitState), + + i("start 'secondary acceptor 3' evaluator"), + SecAcc3 = ?SEV_START("sec-acceptor-3", SecAcceptorSeq, SecAccInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{prim_acc => PrimAcc#ev.pid, + sec_acc1 => SecAcc1#ev.pid, + sec_acc2 => SecAcc2#ev.pid, + sec_acc3 => SecAcc3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([PrimAcc, SecAcc1, SecAcc2, SecAcc3, Tester]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% + +sc_rc_recv_response_tcp4(suite) -> + []; +sc_rc_recv_response_tcp4(doc) -> + []; +sc_rc_recv_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recv_response_tcp4, + fun() -> + ?TT(?SECS(30)), + 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(), + ?TT(?SECS(10)), + 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) -> + %% Each connection are handled by handler processes. + %% These are created (on the fly) and handled internally + %% by the server! + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case 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, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept all three connections)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept 1", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(1, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock1 => Sock, + handler1 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler 1 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1} = _State) -> + ?SEV_AWAIT_READY(Handler1, handler1, init, + [{tester, Tester}]) + end}, + #{desc => "accept 2", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(2, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock2 => Sock, + handler2 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler 2 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2} = _State) -> + ?SEV_AWAIT_READY(Handler2, handler2, init, + [{tester, Tester}, + {handler1, Handler1}]) + end}, + #{desc => "accept 3", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = sc_rc_tcp_handler_start(3, Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock3 => Sock, + handler3 => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler 3 ready (init)", + cmd => fun(#{tester := Tester, + handler1 := Handler1, + handler2 := Handler2, + handler3 := Handler3} = _State) -> + ?SEV_AWAIT_READY(Handler3, handler3, init, + [{tester, Tester}, + {handler1, Handler1}, + {handler2, Handler2}]) + end}, + #{desc => "announce ready (accept all three connections)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler 1 to receive", + cmd => fun(#{handler1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "order handler 2 to receive", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "order handler 3 to receive", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler 1 (recv)", + cmd => fun(#{tester := Tester, handler1 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler1, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ready from handler 2 (recv)", + cmd => fun(#{tester := Tester, handler2 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler2, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ready from handler 3 (recv)", + cmd => fun(#{tester := Tester, handler3 := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler3, recv, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv closed from all handlers)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv_closed), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler 1 to terminate", + cmd => fun(#{handler1 := Pid} = _State) -> + %% Pid ! {terminate, self(), ok}, + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 1 termination", + cmd => fun(#{handler1 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock1, State), + State2 = maps:remove(handler1, State1), + {ok, State2} + end}, + #{desc => "order handler 2 to terminate", + cmd => fun(#{handler2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 2 termination", + cmd => fun(#{handler2 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock2, State), + State2 = maps:remove(handler2, State1), + {ok, State2} + end}, + #{desc => "order handler 3 to terminate", + cmd => fun(#{handler3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler 3 termination", + cmd => fun(#{handler3 := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock3, State), + State2 = maps:remove(handler3, State1), + {ok, State2} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, {NodeID, ServerSA}} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + node_id => NodeID, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host, node_id := NodeID} = State) -> + case start_node(Host, l2a(f("client_~w", [NodeID]))) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node 1", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node} = State) -> + Pid = sc_rc_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connected)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to close", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 1", + cmd => fun(#{client1 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 2", + cmd => fun(#{client2 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client 3", + cmd => fun(#{client3 := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client(s) + #{desc => "order client 1 start", + cmd => fun(#{client1 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {1, ServerSA}), + ok + end}, + #{desc => "await client 1 ready (init)", + cmd => fun(#{client1 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client1, init) + end}, + #{desc => "order client 2 start", + cmd => fun(#{client2 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {2, ServerSA}), + ok + end}, + #{desc => "await client 2 ready (init)", + cmd => fun(#{client2 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client2, init) + end}, + #{desc => "order client 3 start", + cmd => fun(#{client3 := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {3, ServerSA}), + ok + end}, + #{desc => "await client 3 ready (init)", + cmd => fun(#{client3 := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client3, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 continue (connect)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 1 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client1, client1, connect, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 2 continue (connect)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 2 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client2, client2, connect, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (connect)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client 3 ready (connect)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client3, client3, connect, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (accept from all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order server continue (recv for all connections)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client 1 continue (close)", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 1 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client1, client1, close, + [{server, Server}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 2 continue (close)", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 2 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client2, client2, close, + [{server, Server}, + {client1, Client1}, + {client3, Client3}]), + ok + end}, + #{desc => "order client 3 continue (close)", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client 3 ready (close)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Client3, client1, close, + [{server, Server}, + {client1, Client1}, + {client2, Client2}]), + ok + end}, + #{desc => "await server ready (close for all connections)", + cmd => fun(#{server := Server, + client1 := Client1, + client2 := Client2, + client3 := Client3} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv_closed, + [{client1, Client1}, + {client2, Client2}, + {client3, Client3}]), + ok + end}, + + %% Terminations + #{desc => "order client 1 to terminate", + cmd => fun(#{client1 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 1 termination", + cmd => fun(#{client1 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client1, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 2 to terminate", + cmd => fun(#{client2 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 2 termination", + cmd => fun(#{client2 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client2, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order client 3 to terminate", + cmd => fun(#{client3 := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client 3 termination", + cmd => fun(#{client3 := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client3, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client1 = ?SEV_START("client-1", ClientSeq, ClientInitState), + Client2 = ?SEV_START("client-2", ClientSeq, ClientInitState), + Client3 = ?SEV_START("client-3", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client1 => Client1#ev.pid, + client2 => Client2#ev.pid, + client3 => Client3#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, + Client1, Client2, Client3, + Tester]). + + +sc_rc_tcp_client_start(Node) -> + Self = self(), + Fun = fun() -> sc_rc_tcp_client(Self) end, + erlang:spawn(Node, Fun). + + +sc_rc_tcp_client(Parent) -> + sc_rc_tcp_client_init(Parent), + ServerSA = sc_rc_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rc_tcp_client_create(Domain), + sc_rc_tcp_client_bind(Sock, Domain), + sc_rc_tcp_client_announce_ready(Parent, init), + sc_rc_tcp_client_await_continue(Parent, connect), + sc_rc_tcp_client_connect(Sock, ServerSA), + sc_rc_tcp_client_announce_ready(Parent, connect), + sc_rc_tcp_client_await_continue(Parent, close), + sc_rc_tcp_client_close(Sock), + sc_rc_tcp_client_announce_ready(Parent, close), + Reason = sc_rc_tcp_client_await_terminate(Parent), + ?SEV_IPRINT("terminate"), + exit(Reason). + +sc_rc_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +sc_rc_tcp_client_await_start(Parent) -> + i("sc_rc_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +sc_rc_tcp_client_create(Domain) -> + i("sc_rc_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + case socket:getopt(Sock, otp, fd) of + {ok, FD} -> + put(sname, f("rclient-~w", [FD])); % Update SName + _ -> + ok + end, + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rc_tcp_client_bind(Sock, Domain) -> + i("sc_rc_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rc_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("ready ~w", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +sc_rc_tcp_client_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await ~w continue", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +sc_rc_tcp_client_connect(Sock, ServerSA) -> + i("sc_rc_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rc_tcp_client_close(Sock) -> + i("sc_rc_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +sc_rc_tcp_client_await_terminate(Parent) -> + i("sc_rc_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +%% The handlers run on the same node as the server (the local node). + +sc_rc_tcp_handler_start(ID, Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rc_tcp_handler(ID, Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + Pid. + +sc_rc_tcp_handler(ID, Parent, Recv, Sock) -> + sc_rc_tcp_handler_init(ID, socket:getopt(Sock, otp, fd), Parent), + sc_rc_tcp_handler_await(Parent, recv), + RecvRes = sc_rc_tcp_handler_recv(Recv, Sock), + sc_rc_tcp_handler_announce_ready(Parent, recv, RecvRes), + Reason = sc_rc_tcp_handler_await(Parent, terminate), + exit(Reason). + +sc_rc_tcp_handler_init(ID, {ok, FD}, Parent) -> + put(sname, f("handler-~w:~w", [ID, FD])), + _MRef = erlang:monitor(process, Parent), + ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), + ok. + +sc_rc_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rc_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +sc_rc_tcp_handler_recv(Recv, Sock) -> + ?SEV_IPRINT("recv"), + try Recv(Sock) of + {error, closed} -> + ok; + {ok, _} -> + ?SEV_IPRINT("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_IPRINT("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ?SEV_IPRINT("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rc_tcp_handler_announce_ready(Parent, Slogan, Result) -> + ?SEV_IPRINT("announce ready"), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% Socket is IPv4. + +sc_rc_recvmsg_response_tcp4(suite) -> + []; +sc_rc_recvmsg_response_tcp4(doc) -> + []; +sc_rc_recvmsg_response_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rc_recvmsg_response_tcp4, + fun() -> + ?TT(?SECS(30)), + 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(), + ?TT(?SECS(10)), + 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 +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv4. +%% +%% To minimize the chance of "weirdness", we should really have test cases +%% where the two sides of the connection is on different machines. But for +%% now, we will make do with different VMs on the same host. +%% + +sc_rs_recv_send_shutdown_receive_tcp4(suite) -> + []; +sc_rs_recv_send_shutdown_receive_tcp4(doc) -> + []; +sc_rs_recv_send_shutdown_receive_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rs_recv_send_shutdown_receive_tcp4, + fun() -> + ?TT(?SECS(30)), + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => inet, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recv function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv6. + +sc_rs_recv_send_shutdown_receive_tcp6(suite) -> + []; +sc_rs_recv_send_shutdown_receive_tcp6(doc) -> + []; +sc_rs_recv_send_shutdown_receive_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rs_recv_send_shutdown_receive_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + MsgData = ?DATA, + Recv = fun(Sock) -> + socket:recv(Sock) + end, + Send = fun(Sock, Data) -> + socket:send(Sock, Data) + end, + InitState = #{domain => inet6, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sc_rs_send_shutdown_receive_tcp(InitState) -> + %% The connection is handled by a handler processes. + %% This are created (on the fly) and handled internally + %% by the server! + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + i("get local address for ~p", [Domain]), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case 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, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock, recv := Recv} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + ?SEV_IPRINT("accepted: try start handler"), + Handler = + sc_rs_tcp_handler_start(Recv, Sock), + ?SEV_IPRINT("handler started"), + {ok, State#{csock => Sock, + handler => Handler}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (first recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler to receive (first)", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler (first recv)", + cmd => fun(#{tester := Tester, handler := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + ?SEV_IPRINT("first recv: ~p", [Result]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (first recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + #{desc => "await continue (second recv)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv) + end}, + #{desc => "order handler to receive (second)", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await ready from handler (second recv)", + cmd => fun(#{tester := Tester, handler := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + ?SEV_IPRINT("second recv: ~p", [Result]), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (second recv)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, recv), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order handler to terminate", + cmd => fun(#{handler := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Pid} = State) -> + ?SEV_AWAIT_TERMINATION(Pid), + State1 = maps:remove(csock, State), + State2 = maps:remove(handler, State1), + {ok, State2} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := LSock} = State) -> + case socket:close(LSock) of + ok -> + {ok, maps:remove(lsock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client on client node", + cmd => fun(#{node := Node, + send := Send} = State) -> + Pid = sc_rs_tcp_client_start(Node, Send), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, send, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{rclient_data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to send", + cmd => fun(#{rclient := Client, + rclient_data := Data}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send), + ok + end}, + + + #{desc => "await continue (shutdown)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, shutdown, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to shutdown", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, shutdown), + ok + end}, + #{desc => "await remote client ready (shiutdown)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, shutdown, + [{tester, Tester}]) + end}, + #{desc => "announce ready (shutdown)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, shutdown), + ok + end}, + + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to close", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, close), + ok + end}, + #{desc => "await remote client ready (closed)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, close, + [{tester, Tester}]) + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + State1 = maps:remove(node_id, State), + State2 = maps:remove(node, State1), + {ok, State2} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client(s) + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid, + data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send, Data), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]), + ok + end}, + + #{desc => "order client continue (shutdown)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, shutdown), + ok + end}, + #{desc => "await client ready (shutdown)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, shutdown, + [{server, Server}]), + ok + end}, + + #{desc => "order server continue (first recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (first recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]), + ok + end}, + + #{desc => "order server continue (second recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + #{desc => "await server ready (second recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]), + ok + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "order client continue (close)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await client ready (close)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, close, + [{server, Server}]), + ok + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + recv => maps:get(recv, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + send => maps:get(send, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + data => maps:get(data, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +sc_rs_tcp_client_start(Node, Send) -> + Self = self(), + Fun = fun() -> sc_rs_tcp_client(Self, Send) end, + erlang:spawn(Node, Fun). + + +sc_rs_tcp_client(Parent, Send) -> + sc_rs_tcp_client_init(Parent), + ServerSA = sc_rs_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = sc_rs_tcp_client_create(Domain), + sc_rs_tcp_client_bind(Sock, Domain), + sc_rs_tcp_client_announce_ready(Parent, init), + sc_rs_tcp_client_await_continue(Parent, connect), + sc_rs_tcp_client_connect(Sock, ServerSA), + sc_rs_tcp_client_announce_ready(Parent, connect), + Data = sc_rs_tcp_client_await_continue(Parent, send), + sc_rs_tcp_client_send(Sock, Send, Data), + sc_rs_tcp_client_announce_ready(Parent, send), + sc_rs_tcp_client_await_continue(Parent, shutdown), + sc_rs_tcp_client_shutdown(Sock), + sc_rs_tcp_client_announce_ready(Parent, shutdown), + sc_rs_tcp_client_await_continue(Parent, close), + sc_rs_tcp_client_close(Sock), + sc_rs_tcp_client_announce_ready(Parent, close), + Reason = sc_rs_tcp_client_await_terminate(Parent), + ?SEV_IPRINT("terminate"), + exit(Reason). + +sc_rs_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +sc_rs_tcp_client_await_start(Parent) -> + i("sc_rs_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +sc_rs_tcp_client_create(Domain) -> + i("sc_rs_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +sc_rs_tcp_client_bind(Sock, Domain) -> + i("sc_rs_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +sc_rs_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +sc_rs_tcp_client_await_continue(Parent, Slogan) -> + i("sc_rs_tcp_client_await_continue -> entry"), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ok; + {ok, Extra} -> + Extra; + {error, Reason} -> + exit({await_continue, Slogan, Reason}) + end. + + +sc_rs_tcp_client_connect(Sock, ServerSA) -> + i("sc_rs_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +sc_rs_tcp_client_send(Sock, Send, Data) -> + i("sc_rs_tcp_client_send -> entry"), + case Send(Sock, Data) of + ok -> + ok; + {error, Reason} -> + exit({send, Reason}) + end. + +sc_rs_tcp_client_shutdown(Sock) -> + i("sc_rs_tcp_client_shutdown -> entry"), + case socket:shutdown(Sock, write) of + ok -> + ok; + {error, Reason} -> + exit({shutdown, Reason}) + end. + +sc_rs_tcp_client_close(Sock) -> + i("sc_rs_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +sc_rs_tcp_client_await_terminate(Parent) -> + i("sc_rs_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +%% The handlers run on the same node as the server (the local node). + +sc_rs_tcp_handler_start(Recv, Sock) -> + Self = self(), + Fun = fun() -> sc_rs_tcp_handler(Self, Recv, Sock) end, + {Pid, _} = erlang:spawn_monitor(Fun), + Pid. + +sc_rs_tcp_handler(Parent, Recv, Sock) -> + sc_rs_tcp_handler_init(Parent), + sc_rs_tcp_handler_await(Parent, recv), + ok = sc_rs_tcp_handler_recv(Recv, Sock, true), + sc_rs_tcp_handler_announce_ready(Parent, recv, received), + sc_rs_tcp_handler_await(Parent, recv), + ok = sc_rs_tcp_handler_recv(Recv, Sock, false), + sc_rs_tcp_handler_announce_ready(Parent, recv, closed), + Reason = sc_rs_tcp_handler_await(Parent, terminate), + exit(Reason). + +sc_rs_tcp_handler_init(Parent) -> + put(sname, "handler"), + _MRef = erlang:monitor(process, Parent), + ?SEV_IPRINT("started"), + ?SEV_ANNOUNCE_READY(Parent, init), + ok. + +sc_rs_tcp_handler_await(Parent, terminate) -> + ?SEV_IPRINT("await terminate"), + ?SEV_AWAIT_TERMINATE(Parent, tester); +sc_rs_tcp_handler_await(Parent, Slogan) -> + ?SEV_IPRINT("await ~w", [Slogan]), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +%% This hould actually work - we leave it for now +sc_rs_tcp_handler_recv(Recv, Sock, First) -> + ?SEV_IPRINT("recv"), + try Recv(Sock) of + {ok, _} when (First =:= true) -> + ok; + {error, closed} when (First =:= false) -> + ok; + {ok, _} -> + ?SEV_IPRINT("unexpected success"), + {error, unexpected_success}; + {error, Reason} = ERROR -> + ?SEV_IPRINT("receive error: " + "~n ~p", [Reason]), + ERROR + catch + C:E:S -> + ?SEV_IPRINT("receive failure: " + "~n Class: ~p" + "~n Error: ~p" + "~n Stack: ~p", [C, E, S]), + {error, {recv, C, E, S}} + end. + +sc_rs_tcp_handler_announce_ready(Parent, Slogan, Result) -> + ?SEV_IPRINT("announce ready"), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Result), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv4. + +sc_rs_recvmsg_send_shutdown_receive_tcp4(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp4(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp4(_Config) when is_list(_Config) -> + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp4, + fun() -> + ?TT(?SECS(30)), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => inet, + type => stream, + protocol => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test what happens when a socket is +%% remotely closed while the process is calling the recvmsg function. +%% The remote client sends data, then shutdown(write) and then the +%% reader attempts a recv. +%% Socket is IPv6. + +sc_rs_recvmsg_send_shutdown_receive_tcp6(suite) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp6(doc) -> + []; +sc_rs_recvmsg_send_shutdown_receive_tcp6(_Config) when is_list(_Config) -> + tc_try(sc_rs_recvmsg_send_shutdown_receive_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(10)), + MsgData = ?DATA, + Recv = fun(Sock) -> + case socket:recvmsg(Sock) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr) + end, + InitState = #{domain => inet6, + type => stream, + protocol => tcp, + recv => Recv, + send => Send, + data => MsgData}, + ok = sc_rs_send_shutdown_receive_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv4. + +traffic_send_and_recv_chunks_tcp4(suite) -> + []; +traffic_send_and_recv_chunks_tcp4(doc) -> + []; +traffic_send_and_recv_chunks_tcp4(_Config) when is_list(_Config) -> + tc_try(traffic_send_and_recv_chunks_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% behave as expected when sending and/or reading chunks. +%% First send data in one "big" chunk, and read it in "small" chunks. +%% Second, send in a bunch of "small" chunks, and read in one "big" chunk. +%% Socket is IPv6. + +traffic_send_and_recv_chunks_tcp6(suite) -> + []; +traffic_send_and_recv_chunks_tcp6(doc) -> + []; +traffic_send_and_recv_chunks_tcp6(_Config) when is_list(_Config) -> + tc_try(traffic_send_and_recv_chunks_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + InitState = #{domain => inet6}, + ok = traffic_send_and_recv_chunks_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +traffic_send_and_recv_chunks_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case 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, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + + #{desc => "await continue (recv-many-small)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv_many_small) + end}, + #{desc => "recv chunk 1", + cmd => fun(#{csock := Sock} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 1 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 2", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 2 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 3", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 3 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 4", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 4 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 5", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 5 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 6", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 6 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 7", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 7 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 8", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 8 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 9", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 9 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv chunk 10", + cmd => fun(#{csock := Sock, + chunks := Chunks} = State) -> + case socket:recv(Sock, 100) of + {ok, Chunk} -> + ?SEV_IPRINT("recv of chunk 10 of ~p bytes", + [size(Chunk)]), + {ok, State#{chunks => [b2l(Chunk)|Chunks]}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv-many-small)", + cmd => fun(#{tester := Tester, + chunks := Chunks} = State) -> + Data = lists:flatten(lists:reverse(Chunks)), + ?SEV_ANNOUNCE_READY(Tester, recv_many_small, Data), + {ok, maps:remove(chunks, State)} + end}, + + #{desc => "await continue (recv-one-big)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, recv_one_big) of + {ok, Size} -> + {ok, State#{size => Size}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "recv (one big)", + cmd => fun(#{tester := Tester, csock := Sock, size := Size} = _State) -> + case socket:recv(Sock, Size) of + {ok, Data} -> + ?SEV_ANNOUNCE_READY(Tester, + recv_one_big, + b2l(Data)), + ok; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close connection socket (just in case)", + cmd => fun(#{csock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(csock, State)} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start remote client", + cmd => fun(#{node := Node} = State) -> + Pid = traffic_snr_tcp_client_start(Node), + ?SEV_IPRINT("client ~p started", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := Client, server_sa := ServerSA}) -> + ?SEV_ANNOUNCE_START(Client, ServerSA), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, Client}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, connect), + ok + end}, + #{desc => "await client process ready (connect)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + ?SEV_AWAIT_READY(Client, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + + #{desc => "await continue (send-one-big)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_one_big, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := Client, data := Data}) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, Data), + ok + end}, + #{desc => "await client process ready (send)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-one-big)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_one_big), + ok + end}, + + #{desc => "await continue (send-many-small)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_CONTINUE(Tester, tester, + send_many_small, + [{rclient, Client}]) of + {ok, Data} -> + {ok, State#{data => Data}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 1)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 1: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 1)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 2)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 2: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 2)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 3)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 3: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 3)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 4)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 4: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 4)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 5)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 5: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 5)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 6)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 6: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 6)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 7)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 7: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 7)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 8)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 8: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 8)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 9)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, RestData} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 9: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, State#{data => RestData}} + end}, + #{desc => "await client process ready (send chunk 9)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send chunk 10)", + cmd => fun(#{rclient := Client, + data := Data} = State) -> + {Chunk, []} = lists:split(100, Data), + %% ?SEV_IPRINT("order send of chunk 10: " + %% "~n Size: ~p" + %% "~n ~p", [length(Chunk), Chunk]), + ?SEV_ANNOUNCE_CONTINUE(Client, send, Chunk), + {ok, maps:remove(data, State)} + end}, + #{desc => "await client process ready (send chunk 10)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order remote client to continue (send stop)", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, send, stop), + {ok, maps:remove(data, State)} + end}, + #{desc => "await client process ready (send stop)", + cmd => fun(#{tester := Tester, + rclient := Client} = _State) -> + case ?SEV_AWAIT_READY(Client, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + Result; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send-many-small)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, send_many_small), + ok + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := Client} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, Client}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "kill remote client", + cmd => fun(#{rclient := Client}) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) + end}, + + #{desc => "generate data", + cmd => fun(State) -> + D1 = lists:seq(1,250), + D2 = lists:duplicate(4, D1), + D3 = lists:flatten(D2), + {ok, State#{data => D3}} + end}, + + %% (client) Send one big and (server) recv may small + #{desc => "order server continue (recv-many-small)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_many_small), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send-one-big)", + cmd => fun(#{client := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_one_big, Data), + ok + end}, + #{desc => "await client ready (send-one-big)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, send_one_big, + [{server, Server}]) + end}, + #{desc => "await server ready (recv-many-small)", + cmd => fun(#{server := Server, + client := Client, + data := Data} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv_many_small, + [{client, Client}]) of + {ok, Data} -> + ok; + {ok, OtherData} -> + {error, {mismatched_data, Data, OtherData}}; + {error, _} = ERROR -> + ERROR + end + end}, + + #{desc => "order server continue (recv-one-big)", + cmd => fun(#{server := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv_one_big, length(Data)), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send-many-small)", + cmd => fun(#{client := Pid, data := Data} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send_many_small, Data), + ok + end}, + #{desc => "await client ready (send-many-small)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, send_many_small, + [{server, Server}]) + end}, + #{desc => "await server ready (recv-one-big)", + cmd => fun(#{server := Server, + client := Client, + data := Data} = State) -> + case ?SEV_AWAIT_READY(Server, server, recv_one_big, + [{client, Client}]) of + {ok, Data} -> + {ok, maps:remove(data, State)}; + {ok, OtherData} -> + {error, {mismatched_data, Data, OtherData}}; + {error, _} = ERROR -> + ERROR + end + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = InitState, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +traffic_snr_tcp_client_start(Node) -> + Self = self(), + Fun = fun() -> traffic_snr_tcp_client(Self) end, + erlang:spawn(Node, Fun). + +traffic_snr_tcp_client(Parent) -> + {Sock, ServerSA} = traffic_snr_tcp_client_init(Parent), + traffic_snr_tcp_client_announce_ready(Parent, init), + traffic_snr_tcp_client_await_continue(Parent, connect), + traffic_snr_tcp_client_connect(Sock, ServerSA), + traffic_snr_tcp_client_announce_ready(Parent, connect), + traffic_snr_tcp_client_send_loop(Parent, Sock), + Reason = traffic_snr_tcp_client_await_terminate(Parent), + traffic_snr_tcp_client_close(Sock), + exit(Reason). + + +traffic_snr_tcp_client_send_loop(Parent, Sock) -> + case ?SEV_AWAIT_CONTINUE(Parent, parent, send) of + {ok, stop} -> % Breakes the loop + ?SEV_ANNOUNCE_READY(Parent, send, ok), + ok; + {ok, Data} -> + case socket:send(Sock, Data) of + ok -> + ?SEV_ANNOUNCE_READY(Parent, send, ok), + traffic_snr_tcp_client_send_loop(Parent, Sock); + {error, Reason} = ERROR -> + ?SEV_ANNOUNCE_READY(Parent, send, ERROR), + exit({send, Reason}) + end; + {error, Reason} -> + exit({await_continue, Reason}) + end. + +traffic_snr_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ServerSA = traffic_snr_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = traffic_snr_tcp_client_create(Domain), + traffic_snr_tcp_client_bind(Sock, Domain), + {Sock, ServerSA}. + +traffic_snr_tcp_client_await_start(Parent) -> + i("traffic_snr_tcp_client_await_start -> entry"), + ?SEV_AWAIT_START(Parent). + +traffic_snr_tcp_client_create(Domain) -> + i("traffic_snr_tcp_client_create -> entry"), + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +traffic_snr_tcp_client_bind(Sock, Domain) -> + i("traffic_snr_tcp_client_bind -> entry"), + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +traffic_snr_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_ANNOUNCE_READY(Parent, Slogan). + +traffic_snr_tcp_client_await_continue(Parent, Slogan) -> + i("traffic_snr_tcp_client_await_continue -> entry"), + ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan). + +traffic_snr_tcp_client_connect(Sock, ServerSA) -> + i("traffic_snr_tcp_client_connect -> entry"), + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +traffic_snr_tcp_client_close(Sock) -> + i("traffic_snr_tcp_client_close -> entry"), + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +traffic_snr_tcp_client_await_terminate(Parent) -> + i("traffic_snr_tcp_client_await_terminate -> entry"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_send_and_recv_tcp4, + fun() -> + ?TT(?SECS(15)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_small_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_small_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_send_and_recv_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(15)), + InitState = #{domain => inet6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_send_and_recv_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_medium_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_send_and_recv_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + InitState = #{domain => inet6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv4. + +traffic_ping_pong_large_send_and_recv_tcp4(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcp4(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, + tc_try(traffic_ping_pong_large_send_and_recv_tcp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the send and recv functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv6. + +traffic_ping_pong_large_send_and_recv_tcp6(suite) -> + []; +traffic_ping_pong_large_send_and_recv_tcp6(doc) -> + []; +traffic_ping_pong_large_send_and_recv_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, + tc_try(traffic_ping_pong_large_send_and_recv_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(45)), + InitState = #{domain => inet6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_send_and_recv_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendto_and_recvfrom_udp4(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp4(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendto_and_recvfrom_udp6(suite) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp6(doc) -> + []; +traffic_ping_pong_small_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendto_and_recvfrom_udp6, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp4, + fun() -> + ?TT(?SECS(45)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendto and recvfrom +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for two different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(suite) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(doc) -> + []; +traffic_ping_pong_medium_sendto_and_recvfrom_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendto_and_recvfrom_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(45)), + InitState = #{domain => inet6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendto_and_recvfrom_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(20)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + InitState = #{domain => inet6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + InitState = #{domain => ine6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv4. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes), medium (8K) and large (8M). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'large' message test case, for IPv6. + +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(suite) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(doc) -> + []; +traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_LARGE), + Num = ?TPP_LARGE_NUM, + tc_try(traffic_ping_pong_large_sendmsg_and_recvmsg_tcp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(30)), + InitState = #{domain => inet6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv4. + +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp4, + fun() -> + ?TT(?SECS(20)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg functions +%% by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'small' message test case, for IPv6. + +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(suite) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(doc) -> + []; +traffic_ping_pong_small_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_SMALL), + Num = ?TPP_SMALL_NUM, + tc_try(traffic_ping_pong_small_sendmsg_and_recvmsg_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv4. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp4, + fun() -> + ?TT(?SECS(30)), + InitState = #{domain => inet, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case is intended to test that the sendmsg and recvmsg +%% functions by repeatedly sending a meassage between two entities. +%% The same basic test case is used for three different message sizes; +%% small (8 bytes) and medium (8K). +%% The message is sent from A to B and then back again. This is +%% repeated a set number of times (more times the small the message). +%% This is the 'medium' message test case, for IPv6. + +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(suite) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(doc) -> + []; +traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6(_Config) when is_list(_Config) -> + Msg = l2b(?TPP_MEDIUM), + Num = ?TPP_MEDIUM_NUM, + tc_try(traffic_ping_pong_medium_sendmsg_and_recvmsg_udp6, + fun() -> + not_yet_implemented(), + ?TT(?SECS(20)), + InitState = #{domain => ine6, + msg => Msg, + num => Num}, + ok = traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) + end). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Ping-Pong for TCP + +traffic_ping_pong_send_and_recv_tcp(InitState) -> + Send = fun(Sock, Data) -> socket:send(Sock, Data) end, + Recv = fun(Sock, Sz) -> socket:recv(Sock, Sz) end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_tcp(InitState) -> + Send = fun(Sock, Data) when is_binary(Data) -> + MsgHdr = #{iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := undefined, + iov := [Data]}} -> + {ok, Data}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_tcp(InitState2). + + +traffic_ping_pong_send_and_receive_tcp(#{msg := Msg} = InitState) -> + Fun = fun(Sock) -> + {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + ?SEV_IPRINT("RcvBuf is ~p (needs atleast ~p)", + [RcvSz, 16+size(Msg)]), + if (RcvSz < size(Msg)) -> + case socket:setopt(Sock, + socket, rcvbuf, 1024+size(Msg)) of + ok -> + ok; + {error, enobufs} -> + skip({failed_change, rcvbuf}); + {error, Reason1} -> + ?FAIL({rcvbuf, Reason1}) + end; + true -> + ok + end, + {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + ?SEV_IPRINT("SndBuf is ~p (needs atleast ~p)", + [SndSz, 16+size(Msg)]), + if (SndSz < size(Msg)) -> + case socket:setopt(Sock, + socket, sndbuf, 1024+size(Msg)) of + ok -> + ok; + {error, enobufs} -> + skip({failed_change, sndbuf}); + {error, Reason2} -> + ?FAIL({sndbuf, Reason2}) + end; + true -> + ok + end, + ok = socket:setopt(Sock, otp, rcvbuf, {12, 1024}) + end, + traffic_ping_pong_send_and_receive_tcp2(InitState#{buf_init => Fun}). + +traffic_ping_pong_send_and_receive_tcp2(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + {ok, State#{lsock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "bind to local address", + cmd => fun(#{lsock := LSock, local_sa := LSA} = State) -> + case socket:bind(LSock, LSA) of + {ok, Port} -> + {ok, State#{lport => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "maybe init buffers", + cmd => fun(#{lsock := LSock, buf_init := BufInit} = _State) -> + BufInit(LSock) + end}, + #{desc => "make listen socket", + cmd => fun(#{lsock := LSock}) -> + socket:listen(LSock) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, lport := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (accept)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, accept) + end}, + #{desc => "accept", + cmd => fun(#{lsock := LSock} = State) -> + case socket:accept(LSock) of + {ok, Sock} -> + {ok, State#{csock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "create handler", + cmd => fun(State) -> + Handler = tpp_tcp_handler_create(), + ?SEV_IPRINT("handler created: ~p", [Handler]), + {ok, State#{handler => Handler}} + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Handler} = _State) -> + _MRef = erlang:monitor(process, Handler), + ok + end}, + #{desc => "transfer connection socket ownership to handler", + cmd => fun(#{handler := Handler, csock := Sock} = _State) -> + socket:setopt(Sock, otp, controlling_process, Handler) + end}, + #{desc => "start handler", + cmd => fun(#{handler := Handler, + csock := Sock, + send := Send, + recv := Recv} = _State) -> + ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}), + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (accept)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, accept), + ok + end}, + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv, + [{handler, Handler}]) + end}, + #{desc => "order handler to recv", + cmd => fun(#{handler := Handler} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, recv), + ok + end}, + #{desc => "await handler ready (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("Result: ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, recv, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + #{desc => "close listen socket", + cmd => fun(#{lsock := Sock} = State) -> + (catch socket:close(Sock)), + {ok, maps:remove(lsock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create remote client", + cmd => fun(#{node := Node} = State) -> + Pid = tpp_tcp_client_create(Node), + ?SEV_IPRINT("remote client created: ~p", [Pid]), + {ok, State#{rclient => Pid}} + end}, + #{desc => "monitor remote client", + cmd => fun(#{rclient := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote client to start", + cmd => fun(#{rclient := RClient, + server_sa := ServerSA, + buf_init := BufInit, + send := Send, + recv := Recv}) -> + ?SEV_ANNOUNCE_START(RClient, + {ServerSA, BufInit, Send, Recv}), + ok + end}, + #{desc => "await remote client ready", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_READY(RClient, rclient, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, connect, + [{rclient, RClient}]), + ok + end}, + #{desc => "order remote client to continue (connect)", + cmd => fun(#{rclient := RClient}) -> + ?SEV_ANNOUNCE_CONTINUE(RClient, connect), + ok + end}, + #{desc => "await remote client ready (connect)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_READY(RClient, rclient, connect, + [{tester, Tester}]) + end}, + #{desc => "announce ready (connect)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, connect), + ok + end}, + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + rclient := RClient} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, + send, + [{rclient, RClient}]) + end}, + #{desc => "order remote client to continue (send)", + cmd => fun(#{rclient := RClient, + msg := Msg, + num := Num} = State) -> + Data = {Msg, Num}, + ?SEV_ANNOUNCE_CONTINUE(RClient, send, Data), + {ok, maps:remove(data, State)} + end}, + #{desc => "await remote client ready (send)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_READY(RClient, rclient, send, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("remote client result: " + %% "~n ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester, result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rclient, RClient}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop remote client", + cmd => fun(#{rclient := RClient}) -> + ?SEV_ANNOUNCE_TERMINATE(RClient), + ok + end}, + #{desc => "await remote client termination", + cmd => fun(#{rclient := RClient} = State) -> + ?SEV_AWAIT_TERMINATION(RClient), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (accept)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, accept), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (connect)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, connect), + ok + end}, + #{desc => "await server ready (accept)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Server, server, accept, + [{client, Client}]), + ok + end}, + #{desc => "await client ready (connect)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + ?SEV_AWAIT_READY(Client, client, connect, + [{server, Server}]) + end}, + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + {ok, {_, _, _, _} = Result} -> + ?SEV_IPRINT("client result: " + "~n ~p", [Result]), + {ok, State#{client_result => Result}}; + {ok, BadResult} -> + ?SEV_EPRINT("client result: " + "~n ~p", [BadResult]), + {error, {invalid_client_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + client := Client, + num := Num} = State) -> + case ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]) of + {ok, {Num, _, _, _, _} = Result} -> + ?SEV_IPRINT("server result: " + "~n ~p", [Result]), + Result2 = erlang:delete_element(1, Result), + {ok, State#{server_result => Result2}}; + {ok, BadResult} -> + ?SEV_EPRINT("bad server result: " + "~n ~p", [BadResult]), + {error, {invalid_server_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "present result", + cmd => fun(#{server_result := SRes, + client_result := CRes, + num := Num} = State) -> + {SSent, SReceived, SStart, SStop} = SRes, + {CSent, CReceived, CStart, CStop} = CRes, + STime = tdiff(SStart, SStop), + CTime = tdiff(CStart, CStop), + %% Note that the sizes we are counting is only + %% the "data" part of the messages. There is also + %% fixed header for each message, which of cource + %% is small for the large messages, but comparatively + %% big for the small messages! + ?SEV_IPRINT("Results: ~w messages exchanged" + "~n Server: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received" + "~n Client: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received", + [Num, + STime, + STime / Num, + Num / STime, + SSent div STime, + SReceived div STime, + CTime, + CTime / Num, + Num / CTime, + CSent div CTime, + CReceived div CTime]), + State1 = maps:remove(server_result, State), + State2 = maps:remove(client_result, State1), + {ok, State2} + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + recv => maps:get(recv, InitState), + send => maps:get(send, InitState), + buf_init => maps:get(buf_init, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + num => maps:get(num, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + +tpp_tcp_handler_create() -> + Self = self(), + erlang:spawn(fun() -> tpp_tcp_handler(Self) end). + +tpp_tcp_handler(Parent) -> + tpp_tcp_handler_init(Parent), + {Sock, Send, Recv} = tpp_tcp_handler_await_start(Parent), + tpp_tcp_handler_announce_ready(Parent, init), + tpp_tcp_handler_await_continue(Parent, recv), + Result = tpp_tcp_handler_msg_exchange(Sock, Send, Recv), + tpp_tcp_handler_announce_ready(Parent, recv, Result), + Reason = tpp_tcp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_tcp_handler_init(Parent) -> + put(sname, "handler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_tcp_handler_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_tcp_handler_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_tcp_handler_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_tcp_handler_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + %% ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_tcp_handler_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +tpp_tcp_handler_msg_exchange(Sock, Send, Recv) -> + tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, 0, 0, 0, undefined). + +tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, N, Sent, Received, Start) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + case tpp_tcp_recv_req(Sock, Recv) of + {ok, Msg, RecvSz} -> + NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); + true -> Start end, + %% ?SEV_IPRINT("[~w] received - now try send", [N]), + case tpp_tcp_send_rep(Sock, Send, Msg) of + {ok, SendSz} -> + tpp_tcp_handler_msg_exchange_loop(Sock, Send, Recv, + N+1, + Sent+SendSz, + Received+RecvSz, + NewStart); + {error, SReason} -> + ?SEV_EPRINT("send (~w): ~p", [N, SReason]), + exit({send, SReason, N}) + end; + %% {error, timeout} -> + %% ?SEV_IPRINT("timeout(~w) - try again", [N]), + %% case Send(Sock, list_to_binary("ping")) of + %% ok -> + %% exit({'ping-send', ok, N}); + %% {error, Reason} -> + %% exit({'ping-send', Reason, N}) + %% end; + {error, closed} -> + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", [N, Sent, Received]), + Stop = ?LIB:timestamp(), + {N, Sent, Received, Start, Stop}; + {error, RReason} -> + ?SEV_EPRINT("recv (~w): ~p", [N, RReason]), + exit({recv, RReason, N}) + end. + +%% The (remote) client process + +tpp_tcp_client_create(Node) -> + Self = self(), + Fun = fun() -> tpp_tcp_client(Self) end, + erlang:spawn(Node, Fun). + +tpp_tcp_client(Parent) -> + tpp_tcp_client_init(Parent), + {ServerSA, BufInit, Send, Recv} = tpp_tcp_client_await_start(Parent), + Domain = maps:get(family, ServerSA), + Sock = tpp_tcp_client_sock_open(Domain, BufInit), + tpp_tcp_client_sock_bind(Sock, Domain), + tpp_tcp_client_announce_ready(Parent, init), + tpp_tcp_client_await_continue(Parent, connect), + tpp_tcp_client_sock_connect(Sock, ServerSA), + tpp_tcp_client_announce_ready(Parent, connect), + {InitMsg, Num} = tpp_tcp_client_await_continue(Parent, send), + Result = tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num), + tpp_tcp_client_announce_ready(Parent, send, Result), + Reason = tpp_tcp_client_await_terminate(Parent), + tpp_tcp_client_sock_close(Sock), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_tcp_client_init(Parent) -> + put(sname, "rclient"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_tcp_client_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_tcp_client_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_tcp_client_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p): ~p", [Slogan, Extra]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_tcp_client_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {ok, Data} -> + ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + Data; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_tcp_client_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + +tpp_tcp_client_msg_exchange(Sock, Send, Recv, InitMsg, Num) -> + Start = ?LIB:timestamp(), + tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, InitMsg, + Num, 0, 0, 0, Start). + +tpp_tcp_client_msg_exchange_loop(Sock, _Send, _Recv, _Msg, + Num, Num, Sent, Received, + Start) -> + Stop = ?LIB:timestamp(), + case socket:close(Sock) of + ok -> + {Sent, Received, Start, Stop}; + {error, Reason} -> + exit({failed_closing, Reason}) + end; +tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, Data, + Num, N, Sent, Received, Start) -> + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) try send", [Num,N]), + case tpp_tcp_send_req(Sock, Send, Data) of + {ok, SendSz} -> + %% d("tpp_tcp_client_msg_exchange_loop(~w,~w) sent - " + %% "now try recv", [Num,N]), + case tpp_tcp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz} -> + tpp_tcp_client_msg_exchange_loop(Sock, Send, Recv, + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]), + exit({send, SReason, N}) + end. + +tpp_tcp_client_sock_open(Domain, BufInit) -> + case socket:open(Domain, stream, tcp) of + {ok, Sock} -> + ok = BufInit(Sock), + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +tpp_tcp_client_sock_bind(Sock, Domain) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +tpp_tcp_client_sock_connect(Sock, ServerSA) -> + case socket:connect(Sock, ServerSA) of + ok -> + ok; + {error, Reason} -> + exit({connect, Reason}) + end. + +tpp_tcp_client_sock_close(Sock) -> + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + +-define(TPP_REQUEST, 1). +-define(TPP_REPLY, 2). + +tpp_tcp_recv_req(Sock, Recv) -> + tpp_tcp_recv(Sock, Recv, ?TPP_REQUEST). + +tpp_tcp_recv_rep(Sock, Recv) -> + tpp_tcp_recv(Sock, Recv, ?TPP_REPLY). + +tpp_tcp_recv(Sock, Recv, Tag) -> + case Recv(Sock, 0) of + {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg} + when (Sz =:= size(Data)) -> + %% We got it all + {ok, Data, size(Msg)}; + {ok, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg} -> + Remains = Sz - size(Data), + tpp_tcp_recv(Sock, Recv, Tag, Remains, size(Msg), [Data]); + {ok, <<Tag:32/integer, _/binary>>} -> + {error, {invalid_msg_tag, Tag}}; + {error, _} = ERROR -> + ERROR + end. + +tpp_tcp_recv(Sock, Recv, Tag, Remaining, AccSz, Acc) -> + case Recv(Sock, Remaining) of + {ok, Data} when (Remaining =:= size(Data)) -> + %% We got the rest + TotSz = AccSz + size(Data), + {ok, erlang:iolist_to_binary(lists:reverse([Data | Acc])), TotSz}; + {ok, Data} when (Remaining > size(Data)) -> + tpp_tcp_recv(Sock, Recv, Tag, + Remaining - size(Data), AccSz + size(Data), + [Data | Acc]); + {error, _} = ERROR -> + ERROR + end. + + +tpp_tcp_send_req(Sock, Send, Data) -> + tpp_tcp_send(Sock, Send, ?TPP_REQUEST, Data). + +tpp_tcp_send_rep(Sock, Send, Data) -> + tpp_tcp_send(Sock, Send, ?TPP_REPLY, Data). + +tpp_tcp_send(Sock, Send, Tag, Data) -> + DataSz = size(Data), + Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>, + tpp_tcp_send_msg(Sock, Send, Msg, 0). + +tpp_tcp_send_msg(Sock, Send, Msg, AccSz) when is_binary(Msg) -> + case Send(Sock, Msg) of + ok -> + {ok, AccSz+size(Msg)}; + {ok, Rest} -> % This is an IOVec + RestBin = list_to_binary(Rest), + tpp_tcp_send_msg(Sock, Send, RestBin, AccSz+(size(Msg)-size(RestBin))); + {error, _} = ERROR -> + ERROR + end. + + +%% size_of_data(Data) when is_binary(Data) -> +%% size(Data); +%% size_of_data(Data) when is_list(Data) -> +%% size_of_iovec(Data, 0). + +%% size_of_iovec([], Sz) -> +%% Sz; +%% size_of_iovec([B|IOVec], Sz) -> +%% size_of_iovec(IOVec, Sz+size(B)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Ping-Pong for UDP + +traffic_ping_pong_sendto_and_recvfrom_udp(InitState) -> + Send = fun(Sock, Data, Dest) -> + socket:sendto(Sock, Data, Dest) + end, + Recv = fun(Sock, Sz) -> + socket:recvfrom(Sock, Sz) + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_udp(InitState2). + +traffic_ping_pong_sendmsg_and_recvmsg_udp(InitState) -> + Send = fun(Sock, Data, Dest) when is_binary(Data) -> + MsgHdr = #{addr => Dest, iov => [Data]}, + socket:sendmsg(Sock, MsgHdr); + (Sock, Data, Dest) when is_list(Data) -> %% We assume iovec... + MsgHdr = #{addr => Dest, iov => Data}, + socket:sendmsg(Sock, MsgHdr) + end, + Recv = fun(Sock, Sz) -> + case socket:recvmsg(Sock, Sz, 0) of + {ok, #{addr := Source, + iov := [Data]}} -> + {ok, {Source, Data}}; + {error, _} = ERROR -> + ERROR + end + end, + InitState2 = InitState#{send => Send, % Send function + recv => Recv % Receive function + }, + traffic_ping_pong_send_and_receive_udp(InitState2). + + +traffic_ping_pong_send_and_receive_udp(#{msg := Msg} = InitState) -> + Fun = fun(Sock) -> + {ok, RcvSz} = socket:getopt(Sock, socket, rcvbuf), + if (RcvSz =< (8+size(Msg))) -> + i("adjust socket rcvbuf buffer size"), + ok = socket:setopt(Sock, socket, rcvbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, SndSz} = socket:getopt(Sock, socket, sndbuf), + if (SndSz =< (8+size(Msg))) -> + i("adjust socket sndbuf buffer size"), + ok = socket:setopt(Sock, socket, sndbuf, 1024+size(Msg)); + true -> + ok + end, + {ok, OtpRcvBuf} = socket:getopt(Sock, otp, rcvbuf), + if + (OtpRcvBuf =< (8+size(Msg))) -> + i("adjust otp rcvbuf buffer size"), + ok = socket:setopt(Sock, otp, rcvbuf, 1024+size(Msg)); + true -> + ok + end + end, + traffic_ping_pong_send_and_receive_udp2(InitState#{buf_init => Fun}). + +traffic_ping_pong_send_and_receive_udp2(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create listen 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, local_sa := LSA} = State) -> + case socket:bind(Sock, LSA) of + {ok, Port} -> + {ok, State#{port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "maybe init buffers", + cmd => fun(#{sock := Sock, buf_init := BufInit} = _State) -> + BufInit(Sock) + end}, + #{desc => "create handler", + cmd => fun(State) -> + Handler = tpp_udp_server_handler_create(), + ?SEV_IPRINT("handler created: ~p", [Handler]), + {ok, State#{handler => Handler}} + end}, + #{desc => "monitor handler", + cmd => fun(#{handler := Handler} = _State) -> + _MRef = erlang:monitor(process, Handler), + ok + end}, + #{desc => "start handler", + cmd => fun(#{handler := Handler, + sock := Sock, + send := Send, + recv := Recv} = _State) -> + ?SEV_ANNOUNCE_START(Handler, {Sock, Send, Recv}), + ok + end}, + #{desc => "await handler ready (init)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) of + ok -> + {ok, maps:remove(csock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, local_sa := LSA, port := Port}) -> + ServerSA = LSA#{port => Port}, + ?SEV_ANNOUNCE_READY(Tester, init, ServerSA), + ok + end}, + + %% The actual test + #{desc => "await continue (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, recv, + [{handler, Handler}]) + end}, + #{desc => "order handler to recv", + cmd => fun(#{handler := Handler} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Handler, recv), + ok + end}, + #{desc => "await continue (close)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, close, + [{handler, Handler}]) + end}, + + ?SEV_SLEEP(?SECS(1)), + + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + %% socket:setopt(Sock, otp, debug, true), + case socket:close(Sock) of + ok -> + {ok, maps:remove(sock, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (close)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_ANNOUNCE_READY(Tester, close), + ok + end}, + #{desc => "await handler ready (recv)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, recv, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("Result: ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (recv)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, recv, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, ServerSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_sa => ServerSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + ?SEV_IPRINT("(remote) client node ~p started", + [Node]), + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "create (remote) handler", + cmd => fun(#{node := Node} = State) -> + Pid = tpp_udp_client_handler_create(Node), + ?SEV_IPRINT("handler created: ~p", [Pid]), + {ok, State#{handler => Pid}} + end}, + #{desc => "monitor remote handler", + cmd => fun(#{handler := Pid}) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "order remote handler to start", + cmd => fun(#{handler := Handler, + server_sa := ServerSA, + buf_init := BufInit, + send := Send, + recv := Recv}) -> + ?SEV_ANNOUNCE_START(Handler, + {ServerSA, BufInit, Send, Recv}), + ok + end}, + #{desc => "await (remote) handler ready", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_READY(Handler, handler, init, + [{tester, Tester}]) + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (send)", + cmd => fun(#{tester := Tester, + handler := Handler} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, + send, + [{handler, Handler}]) + end}, + #{desc => "order handler to continue (send)", + cmd => fun(#{handler := Handler, + msg := Msg, + num := Num} = State) -> + Data = {Msg, Num}, + ?SEV_ANNOUNCE_CONTINUE(Handler, send, Data), + {ok, maps:remove(data, State)} + end}, + #{desc => "await remote handler ready (send)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_READY(Handler, handler, send, + [{tester, Tester}]) of + {ok, Result} -> + %% ?SEV_IPRINT("remote client result: " + %% "~n ~p", [Result]), + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (send)", + cmd => fun(#{tester := Tester, result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, send, Result), + {ok, maps:remove(result, State)} + end}, + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + handler := Handler} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{handler, Handler}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop (remote) handler", + cmd => fun(#{handler := Handler}) -> + ?SEV_ANNOUNCE_TERMINATE(Handler), + ok + end}, + #{desc => "await (remote) handler termination", + cmd => fun(#{handler := Handler} = State) -> + ?SEV_AWAIT_TERMINATION(Handler), + State1 = maps:remove(handler, State), + {ok, State1} + end}, + #{desc => "stop client node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await client node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, ServerSA} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_sa => ServerSA}} + end}, + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_sa := ServerSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, ServerSA), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, client, init) + end}, + + %% The actual test + #{desc => "order server continue (recv)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, recv), + ok + end}, + ?SEV_SLEEP(?SECS(1)), + #{desc => "order client continue (send)", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, send), + ok + end}, + #{desc => "await client ready (send)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, send, + [{server, Server}]) of + {ok, {_, _, _, _} = Result} -> + ?SEV_IPRINT("client result: " + "~n ~p", [Result]), + {ok, State#{client_result => Result}}; + {ok, BadResult} -> + ?SEV_EPRINT("client result: " + "~n ~p", [BadResult]), + {error, {invalid_client_result, BadResult}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server continue (close)", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Pid, close), + ok + end}, + #{desc => "await server ready (close)", + cmd => fun(#{server := Pid} = _State) -> + ok = ?SEV_AWAIT_READY(Pid, server, close) + end}, + %% Because of the way we control the server, there is no real + %% point in collecting statistics from it (the time will include + %% our communication with it). + #{desc => "await server ready (recv)", + cmd => fun(#{server := Server, + client := Client} = _State) -> + case ?SEV_AWAIT_READY(Server, server, recv, + [{client, Client}]) of + {ok, _Result} -> + ok; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "present result", + cmd => fun(#{client_result := CRes, + num := Num} = State) -> + {CSent, CReceived, CStart, CStop} = CRes, + CTime = tdiff(CStart, CStop), + %% Note that the sizes we are counting is only + %% the "data" part of the messages. There is also + %% fixed header for each message, which of cource + %% is small for the large messages, but comparatively + %% big for the small messages! + ?SEV_IPRINT("Results: ~w messages exchanged" + "~n Client: ~w msec" + "~n ~.2f msec/message (roundtrip)" + "~n ~.2f messages/msec (roundtrip)" + "~n ~w bytes/msec sent" + "~n ~w bytes/msec received", + [Num, + CTime, + CTime / Num, + Num / CTime, + CSent div CTime, + CReceived div CTime]), + State1 = maps:remove(client_result, State), + {ok, State1} + end}, + + %% Terminations + #{desc => "order client to terminate", + cmd => fun(#{client := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await client termination", + cmd => fun(#{client := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(client, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "order server to terminate", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Pid), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Pid} = State) -> + case ?SEV_AWAIT_TERMINATION(Pid) of + ok -> + State1 = maps:remove(server, State), + {ok, State1}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("start server evaluator"), + ServerInitState = #{domain => maps:get(domain, InitState), + recv => maps:get(recv, InitState), + send => maps:get(send, InitState), + buf_init => maps:get(buf_init, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator(s)"), + ClientInitState = InitState#{host => local_host()}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid, + num => maps:get(num, InitState)}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +%% Server side handler process +%% We don't actually need a separate process for this socket, +%% but we do it anyway to simplify the sequence. +tpp_udp_server_handler_create() -> + Self = self(), + erlang:spawn(fun() -> tpp_udp_server_handler(Self) end). + +tpp_udp_server_handler(Parent) -> + tpp_udp_server_handler_init(Parent), + {Sock, Send, Recv} = tpp_udp_handler_await_start(Parent), + tpp_udp_handler_announce_ready(Parent, init), + tpp_udp_handler_await_continue(Parent, recv), + Result = tpp_udp_server_handler_msg_exchange(Sock, Send, Recv), + tpp_udp_handler_announce_ready(Parent, recv, Result), + Reason = tpp_udp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_udp_server_handler_init(Parent) -> + put(sname, "shandler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_udp_server_handler_msg_exchange(Sock, Send, Recv) -> + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + 0, 0, 0, undefined). + +tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + N, Sent, Received, Start) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + %% if + %% (N =:= (?TPP_SMALL_NUM-2)) -> + %% ?SEV_IPRINT("[~w] try receive", [N]), + %% socket:setopt(Sock, otp, debug, true); + %% true -> ok + %% end, + try tpp_udp_recv_req(Sock, Recv) of + {ok, Msg, RecvSz, From} -> + NewStart = if (Start =:= undefined) -> ?LIB:timestamp(); + true -> Start end, + %% ?SEV_IPRINT("[~w] received - now try send", [N]), + try tpp_udp_send_rep(Sock, Send, Msg, From) of + {ok, SendSz} -> + tpp_udp_server_handler_msg_exchange_loop(Sock, Send, Recv, + N+1, + Sent+SendSz, + Received+RecvSz, + NewStart); + {error, SReason} -> + ?SEV_EPRINT("send (~w): ~p", [N, SReason]), + exit({send, SReason, N}) + catch + SC:SE:SS -> + exit({send, {SC, SE, SS}, N}) + end; + {error, closed} -> + ?SEV_IPRINT("closed - we are done: ~w, ~w, ~w", + [N, Sent, Received]), + Stop = ?LIB:timestamp(), + {N, Sent, Received, Start, Stop}; + {error, RReason} -> + ?SEV_EPRINT("recv (~w): ~p", [N, RReason]), + exit({recv, RReason, N}) + catch + RC:RE:RS -> + exit({recv, {RC, RE, RS}, N}) + end. + + +%% The (remote) client side handler process + +tpp_udp_client_handler_create(Node) -> + Self = self(), + Fun = fun() -> put(sname, "chandler"), tpp_udp_client_handler(Self) end, + erlang:spawn(Node, Fun). + +tpp_udp_client_handler(Parent) -> + tpp_udp_client_handler_init(Parent), + ?SEV_IPRINT("await start command"), + {ServerSA, BufInit, Send, Recv} = tpp_udp_handler_await_start(Parent), + ?SEV_IPRINT("start command with" + "~n ServerSA: ~p", [ServerSA]), + Domain = maps:get(family, ServerSA), + Sock = tpp_udp_sock_open(Domain, BufInit), + tpp_udp_sock_bind(Sock, Domain), + ?SEV_IPRINT("announce ready", []), + tpp_udp_handler_announce_ready(Parent, init), + {InitMsg, Num} = tpp_udp_handler_await_continue(Parent, send), + ?SEV_IPRINT("received continue with" + "~n Num: ~p", [Num]), + Result = tpp_udp_client_handler_msg_exchange(Sock, ServerSA, + Send, Recv, InitMsg, Num), + ?SEV_IPRINT("ready"), + tpp_udp_handler_announce_ready(Parent, send, Result), + ?SEV_IPRINT("await terminate"), + Reason = tpp_udp_handler_await_terminate(Parent), + ?SEV_IPRINT("terminate with ~p", [Reason]), + tpp_udp_sock_close(Sock), + ?SEV_IPRINT("terminating"), + exit(Reason). + +tpp_udp_client_handler_init(Parent) -> + put(sname, "chandler"), + ?SEV_IPRINT("init"), + _MRef = erlang:monitor(process, Parent), + ok. + +tpp_udp_client_handler_msg_exchange(Sock, ServerSA, + Send, Recv, InitMsg, Num) -> + Start = ?LIB:timestamp(), + tpp_udp_client_handler_msg_exchange_loop(Sock, ServerSA, + Send, Recv, InitMsg, + Num, 0, 0, 0, Start). + +tpp_udp_client_handler_msg_exchange_loop(_Sock, _Dest, _Send, _Recv, _Msg, + Num, Num, Sent, Received, + Start) -> + Stop = ?LIB:timestamp(), + {Sent, Received, Start, Stop}; +tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, Send, Recv, Data, + Num, N, Sent, Received, Start) -> + case tpp_udp_send_req(Sock, Send, Data, Dest) of + {ok, SendSz} -> + case tpp_udp_recv_rep(Sock, Recv) of + {ok, NewData, RecvSz, Dest} -> + tpp_udp_client_handler_msg_exchange_loop(Sock, Dest, + Send, Recv, + NewData, Num, N+1, + Sent+SendSz, + Received+RecvSz, + Start); + {error, RReason} -> + ?SEV_EPRINT("recv (~w of ~w): ~p", [N, Num, RReason]), + exit({recv, RReason, N}) + end; + {error, SReason} -> + ?SEV_EPRINT("send (~w of ~w): ~p", [N, Num, SReason]), + exit({send, SReason, N}) + end. + + +tpp_udp_recv_req(Sock, Recv) -> + tpp_udp_recv(Sock, Recv, ?TPP_REQUEST). + +tpp_udp_recv_rep(Sock, Recv) -> + tpp_udp_recv(Sock, Recv, ?TPP_REPLY). + +tpp_udp_recv(Sock, Recv, Tag) -> + %% ok = socket:setopt(Sock, otp, debug, true), + try Recv(Sock, 0) of + {ok, {Source, <<Tag:32/integer, Sz:32/integer, Data/binary>> = Msg}} + when (Sz =:= size(Data)) -> + %% ok = socket:setopt(Sock, otp, debug, false), + %% We got it all + %% ?SEV_IPRINT("tpp_udp_recv -> got all: " + %% "~n Source: ~p" + %% "~n Tag: ~p" + %% "~n Sz: ~p" + %% "~n size(Data): ~p", + %% [Source, Tag, Sz, size(Data)]), + {ok, Data, size(Msg), Source}; + {ok, {_Source, <<Tag:32/integer, Sz:32/integer, Data/binary>>}} -> + %% ok = socket:setopt(Sock, otp, debug, false), + {error, {invalid_msg, Sz, size(Data)}}; + {ok, {_, <<Tag:32/integer, _/binary>>}} -> + %% ok = socket:setopt(Sock, otp, debug, false), + {error, {invalid_msg_tag, Tag}}; + {error, _} = ERROR -> + %% ok = socket:setopt(Sock, otp, debug, false), + ERROR + catch + C:E:S -> + {error, {catched, C, E, S}} + end. + +tpp_udp_send_req(Sock, Send, Data, Dest) -> + tpp_udp_send(Sock, Send, ?TPP_REQUEST, Data, Dest). + +tpp_udp_send_rep(Sock, Send, Data, Dest) -> + tpp_udp_send(Sock, Send, ?TPP_REPLY, Data, Dest). + +tpp_udp_send(Sock, Send, Tag, Data, Dest) -> + DataSz = size(Data), + Msg = <<Tag:32/integer, DataSz:32/integer, Data/binary>>, + tpp_udp_send_msg(Sock, Send, Msg, Dest, 0). + +tpp_udp_send_msg(Sock, Send, Msg, Dest, AccSz) when is_binary(Msg) -> + case Send(Sock, Msg, Dest) of + ok -> + {ok, AccSz+size(Msg)}; + {ok, Rest} -> % This is an IOVec + RestBin = list_to_binary(Rest), + tpp_udp_send_msg(Sock, Send, RestBin, Dest, + AccSz+(size(Msg)-size(RestBin))); + {error, _} = ERROR -> + ERROR + end. + + +tpp_udp_handler_await_start(Parent) -> + ?SEV_IPRINT("await start"), + ?SEV_AWAIT_START(Parent). + +tpp_udp_handler_announce_ready(Parent, Slogan) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan). +tpp_udp_handler_announce_ready(Parent, Slogan, Extra) -> + ?SEV_IPRINT("announce ready (~p)", [Slogan]), + ?SEV_ANNOUNCE_READY(Parent, Slogan, Extra). + +tpp_udp_handler_await_continue(Parent, Slogan) -> + ?SEV_IPRINT("await continue (~p)", [Slogan]), + case ?SEV_AWAIT_CONTINUE(Parent, parent, Slogan) of + ok -> + ?SEV_IPRINT("continue (~p): ok", [Slogan]), + ok; + {ok, Data} -> + ?SEV_IPRINT("continue (~p): ok with data", [Slogan]), + Data; + {error, Reason} -> + ?SEV_EPRINT("continue (~p): error" + "~n ~p", [Slogan, Reason]), + exit({continue, Slogan, Reason}) + end. + +tpp_udp_handler_await_terminate(Parent) -> + ?SEV_IPRINT("await terminate"), + case ?SEV_AWAIT_TERMINATE(Parent, parent) of + ok -> + ok; + {error, Reason} -> + Reason + end. + + +tpp_udp_sock_open(Domain, BufInit) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + ok = BufInit(Sock), + Sock; + {error, Reason} -> + exit({open_failed, Reason}) + end. + +tpp_udp_sock_bind(Sock, Domain) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, + addr => LAddr}, + case socket:bind(Sock, LSA) of + {ok, _} -> + ok; + {error, Reason} -> + exit({bind, Reason}) + end. + +tpp_udp_sock_close(Sock) -> + case socket:close(Sock) of + ok -> + ok; + {error, Reason} -> + exit({close, Reason}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgenf_small_tcp4(suite) -> + []; +ttest_sgenf_cgenf_small_tcp4(doc) -> + []; +ttest_sgenf_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_small_tcp4, + inet, + gen, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_small_tcp6(suite) -> + []; +ttest_sgenf_cgenf_small_tcp6(doc) -> + []; +ttest_sgenf_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_small_tcp6, + inet6, + gen, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgenf_medium_tcp4(suite) -> + []; +ttest_sgenf_cgenf_medium_tcp4(doc) -> + []; +ttest_sgenf_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_medium_tcp4, + inet, + gen, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_medium_tcp6(suite) -> + []; +ttest_sgenf_cgenf_medium_tcp6(doc) -> + []; +ttest_sgenf_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_medium_tcp6, + inet6, + gen, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgenf_large_tcp4(suite) -> + []; +ttest_sgenf_cgenf_large_tcp4(doc) -> + []; +ttest_sgenf_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_large_tcp4, + inet, + gen, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgenf_large_tcp6(suite) -> + []; +ttest_sgenf_cgenf_large_tcp6(doc) -> + []; +ttest_sgenf_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgenf_large_tcp6, + inet6, + gen, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgeno_small_tcp4(suite) -> + []; +ttest_sgenf_cgeno_small_tcp4(doc) -> + []; +ttest_sgenf_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_small_tcp4, + inet, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_small_tcp6(suite) -> + []; +ttest_sgenf_cgeno_small_tcp6(doc) -> + []; +ttest_sgenf_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_small_tcp6, + inet6, + gen, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgeno_medium_tcp4(suite) -> + []; +ttest_sgenf_cgeno_medium_tcp4(doc) -> + []; +ttest_sgenf_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_medium_tcp4, + inet, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_medium_tcp6(suite) -> + []; +ttest_sgenf_cgeno_medium_tcp6(doc) -> + []; +ttest_sgenf_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_medium_tcp6, + inet6, + gen, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgeno_large_tcp4(suite) -> + []; +ttest_sgenf_cgeno_large_tcp4(doc) -> + []; +ttest_sgenf_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_large_tcp4, + inet, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgeno_large_tcp6(suite) -> + []; +ttest_sgenf_cgeno_large_tcp6(doc) -> + []; +ttest_sgenf_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_large_tcp6, + inet6, + gen, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_cgent_small_tcp4(suite) -> + []; +ttest_sgenf_cgent_small_tcp4(doc) -> + []; +ttest_sgenf_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_small_tcp4, + inet, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_small_tcp6(suite) -> + []; +ttest_sgenf_cgent_small_tcp6(doc) -> + []; +ttest_sgenf_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgeno_small_tcp6, + inet6, + gen, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_cgent_medium_tcp4(suite) -> + []; +ttest_sgenf_cgent_medium_tcp4(doc) -> + []; +ttest_sgenf_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_medium_tcp4, + inet, + gen, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_medium_tcp6(suite) -> + []; +ttest_sgenf_cgent_medium_tcp6(doc) -> + []; +ttest_sgenf_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_medium_tcp6, + inet6, + gen, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_cgent_large_tcp4(suite) -> + []; +ttest_sgenf_cgent_large_tcp4(doc) -> + []; +ttest_sgenf_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_large_tcp4, + inet, + gen, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_cgent_large_tcp6(suite) -> + []; +ttest_sgenf_cgent_large_tcp6(doc) -> + []; +ttest_sgenf_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_cgent_large_tcp6, + inet6, + gen, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csockf_small_tcp4(suite) -> + []; +ttest_sgenf_csockf_small_tcp4(doc) -> + []; +ttest_sgenf_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_small_tcp4, + inet, + gen, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_small_tcp6(suite) -> + []; +ttest_sgenf_csockf_small_tcp6(doc) -> + []; +ttest_sgenf_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_small_tcp6, + inet6, + gen, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csockf_medium_tcp4(suite) -> + []; +ttest_sgenf_csockf_medium_tcp4(doc) -> + []; +ttest_sgenf_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_medium_tcp4, + inet, + gen, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_medium_tcp6(suite) -> + []; +ttest_sgenf_csockf_medium_tcp6(doc) -> + []; +ttest_sgenf_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_medium_tcp6, + inet6, + gen, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csockf_large_tcp4(suite) -> + []; +ttest_sgenf_csockf_large_tcp4(doc) -> + []; +ttest_sgenf_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_large_tcp4, + inet, + gen, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csockf_large_tcp6(suite) -> + []; +ttest_sgenf_csockf_large_tcp6(doc) -> + []; +ttest_sgenf_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockf_large_tcp6, + inet6, + gen, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csocko_small_tcp4(suite) -> + []; +ttest_sgenf_csocko_small_tcp4(doc) -> + []; +ttest_sgenf_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_small_tcp4, + inet, + gen, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_small_tcp6(suite) -> + []; +ttest_sgenf_csocko_small_tcp6(doc) -> + []; +ttest_sgenf_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_small_tcp6, + inet6, + gen, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csocko_medium_tcp4(suite) -> + []; +ttest_sgenf_csocko_medium_tcp4(doc) -> + []; +ttest_sgenf_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_medium_tcp4, + inet, + gen, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_medium_tcp6(suite) -> + []; +ttest_sgenf_csocko_medium_tcp6(doc) -> + []; +ttest_sgenf_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_medium_tcp6, + inet6, + gen, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csocko_large_tcp4(suite) -> + []; +ttest_sgenf_csocko_large_tcp4(doc) -> + []; +ttest_sgenf_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_large_tcp4, + inet, + gen, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csocko_large_tcp6(suite) -> + []; +ttest_sgenf_csocko_large_tcp6(doc) -> + []; +ttest_sgenf_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_large_tcp6, + inet6, + gen, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgenf_csockt_small_tcp4(suite) -> + []; +ttest_sgenf_csockt_small_tcp4(doc) -> + []; +ttest_sgenf_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_small_tcp4, + inet, + gen, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_small_tcp6(suite) -> + []; +ttest_sgenf_csockt_small_tcp6(doc) -> + []; +ttest_sgenf_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csocko_small_tcp6, + inet6, + gen, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgenf_csockt_medium_tcp4(suite) -> + []; +ttest_sgenf_csockt_medium_tcp4(doc) -> + []; +ttest_sgenf_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_medium_tcp4, + inet, + gen, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_medium_tcp6(suite) -> + []; +ttest_sgenf_csockt_medium_tcp6(doc) -> + []; +ttest_sgenf_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_medium_tcp6, + inet6, + gen, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgenf_csockt_large_tcp4(suite) -> + []; +ttest_sgenf_csockt_large_tcp4(doc) -> + []; +ttest_sgenf_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_large_tcp4, + inet, + gen, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgenf_csockt_large_tcp6(suite) -> + []; +ttest_sgenf_csockt_large_tcp6(doc) -> + []; +ttest_sgenf_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgenf_csockt_large_tcp6, + inet6, + gen, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgenf_small_tcp4(suite) -> + []; +ttest_sgeno_cgenf_small_tcp4(doc) -> + []; +ttest_sgeno_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_small_tcp4, + inet, + gen, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_small_tcp6(suite) -> + []; +ttest_sgeno_cgenf_small_tcp6(doc) -> + []; +ttest_sgeno_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_small_tcp6, + inet6, + gen, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgenf_medium_tcp4(suite) -> + []; +ttest_sgeno_cgenf_medium_tcp4(doc) -> + []; +ttest_sgeno_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_medium_tcp4, + inet, + gen, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_medium_tcp6(suite) -> + []; +ttest_sgeno_cgenf_medium_tcp6(doc) -> + []; +ttest_sgeno_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_medium_tcp6, + inet6, + gen, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgenf_large_tcp4(suite) -> + []; +ttest_sgeno_cgenf_large_tcp4(doc) -> + []; +ttest_sgeno_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_large_tcp4, + inet, + gen, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgenf_large_tcp6(suite) -> + []; +ttest_sgeno_cgenf_large_tcp6(doc) -> + []; +ttest_sgeno_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgenf_large_tcp6, + inet6, + gen, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgeno_small_tcp4(suite) -> + []; +ttest_sgeno_cgeno_small_tcp4(doc) -> + []; +ttest_sgeno_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_small_tcp4, + inet, + gen, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_small_tcp6(suite) -> + []; +ttest_sgeno_cgeno_small_tcp6(doc) -> + []; +ttest_sgeno_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_small_tcp6, + inet6, + gen, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgeno_medium_tcp4(suite) -> + []; +ttest_sgeno_cgeno_medium_tcp4(doc) -> + []; +ttest_sgeno_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_medium_tcp4, + inet, + gen, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_medium_tcp6(suite) -> + []; +ttest_sgeno_cgeno_medium_tcp6(doc) -> + []; +ttest_sgeno_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_medium_tcp6, + inet6, + gen, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgeno_large_tcp4(suite) -> + []; +ttest_sgeno_cgeno_large_tcp4(doc) -> + []; +ttest_sgeno_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_large_tcp4, + inet, + gen, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgeno_large_tcp6(suite) -> + []; +ttest_sgeno_cgeno_large_tcp6(doc) -> + []; +ttest_sgeno_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_large_tcp6, + inet6, + gen, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_cgent_small_tcp4(suite) -> + []; +ttest_sgeno_cgent_small_tcp4(doc) -> + []; +ttest_sgeno_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_small_tcp4, + inet, + gen, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_small_tcp6(suite) -> + []; +ttest_sgeno_cgent_small_tcp6(doc) -> + []; +ttest_sgeno_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgeno_small_tcp6, + inet6, + gen, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_cgent_medium_tcp4(suite) -> + []; +ttest_sgeno_cgent_medium_tcp4(doc) -> + []; +ttest_sgeno_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_medium_tcp4, + inet, + gen, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_medium_tcp6(suite) -> + []; +ttest_sgeno_cgent_medium_tcp6(doc) -> + []; +ttest_sgeno_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_medium_tcp6, + inet6, + gen, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_cgent_large_tcp4(suite) -> + []; +ttest_sgeno_cgent_large_tcp4(doc) -> + []; +ttest_sgeno_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_large_tcp4, + inet, + gen, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_cgent_large_tcp6(suite) -> + []; +ttest_sgeno_cgent_large_tcp6(doc) -> + []; +ttest_sgeno_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_cgent_large_tcp6, + inet6, + gen, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csockf_small_tcp4(suite) -> + []; +ttest_sgeno_csockf_small_tcp4(doc) -> + []; +ttest_sgeno_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_small_tcp4, + inet, + gen, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_small_tcp6(suite) -> + []; +ttest_sgeno_csockf_small_tcp6(doc) -> + []; +ttest_sgeno_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_small_tcp6, + inet6, + gen, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csockf_medium_tcp4(suite) -> + []; +ttest_sgeno_csockf_medium_tcp4(doc) -> + []; +ttest_sgeno_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_medium_tcp4, + inet, + gen, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_medium_tcp6(suite) -> + []; +ttest_sgeno_csockf_medium_tcp6(doc) -> + []; +ttest_sgeno_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_medium_tcp6, + inet6, + gen, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csockf_large_tcp4(suite) -> + []; +ttest_sgeno_csockf_large_tcp4(doc) -> + []; +ttest_sgeno_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_large_tcp4, + inet, + gen, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csockf_large_tcp6(suite) -> + []; +ttest_sgeno_csockf_large_tcp6(doc) -> + []; +ttest_sgeno_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockf_large_tcp6, + inet6, + gen, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csocko_small_tcp4(suite) -> + []; +ttest_sgeno_csocko_small_tcp4(doc) -> + []; +ttest_sgeno_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_small_tcp4, + inet, + gen, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_small_tcp6(suite) -> + []; +ttest_sgeno_csocko_small_tcp6(doc) -> + []; +ttest_sgeno_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_small_tcp6, + inet6, + gen, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csocko_medium_tcp4(suite) -> + []; +ttest_sgeno_csocko_medium_tcp4(doc) -> + []; +ttest_sgeno_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_medium_tcp4, + inet, + gen, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_medium_tcp6(suite) -> + []; +ttest_sgeno_csocko_medium_tcp6(doc) -> + []; +ttest_sgeno_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_medium_tcp6, + inet6, + gen, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csocko_large_tcp4(suite) -> + []; +ttest_sgeno_csocko_large_tcp4(doc) -> + []; +ttest_sgeno_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_large_tcp4, + inet, + gen, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csocko_large_tcp6(suite) -> + []; +ttest_sgeno_csocko_large_tcp6(doc) -> + []; +ttest_sgeno_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_large_tcp6, + inet6, + gen, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgeno_csockt_small_tcp4(suite) -> + []; +ttest_sgeno_csockt_small_tcp4(doc) -> + []; +ttest_sgeno_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_small_tcp4, + inet, + gen, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_small_tcp6(suite) -> + []; +ttest_sgeno_csockt_small_tcp6(doc) -> + []; +ttest_sgeno_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csocko_small_tcp6, + inet6, + gen, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgeno_csockt_medium_tcp4(suite) -> + []; +ttest_sgeno_csockt_medium_tcp4(doc) -> + []; +ttest_sgeno_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_medium_tcp4, + inet, + gen, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_medium_tcp6(suite) -> + []; +ttest_sgeno_csockt_medium_tcp6(doc) -> + []; +ttest_sgeno_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_medium_tcp6, + inet6, + gen, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgeno_csockt_large_tcp4(suite) -> + []; +ttest_sgeno_csockt_large_tcp4(doc) -> + []; +ttest_sgeno_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_large_tcp4, + inet, + gen, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgeno_csockt_large_tcp6(suite) -> + []; +ttest_sgeno_csockt_large_tcp6(doc) -> + []; +ttest_sgeno_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgeno_csockt_large_tcp6, + inet6, + gen, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgenf_small_tcp4(suite) -> + []; +ttest_sgent_cgenf_small_tcp4(doc) -> + []; +ttest_sgent_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_small_tcp4, + inet, + gen, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_small_tcp6(suite) -> + []; +ttest_sgent_cgenf_small_tcp6(doc) -> + []; +ttest_sgent_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_small_tcp6, + inet6, + gen, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgenf_medium_tcp4(suite) -> + []; +ttest_sgent_cgenf_medium_tcp4(doc) -> + []; +ttest_sgent_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_medium_tcp4, + inet, + gen, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_medium_tcp6(suite) -> + []; +ttest_sgent_cgenf_medium_tcp6(doc) -> + []; +ttest_sgent_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_medium_tcp6, + inet6, + gen, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgenf_large_tcp4(suite) -> + []; +ttest_sgent_cgenf_large_tcp4(doc) -> + []; +ttest_sgent_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_large_tcp4, + inet, + gen, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgenf_large_tcp6(suite) -> + []; +ttest_sgent_cgenf_large_tcp6(doc) -> + []; +ttest_sgent_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgenf_large_tcp6, + inet6, + gen, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgeno_small_tcp4(suite) -> + []; +ttest_sgent_cgeno_small_tcp4(doc) -> + []; +ttest_sgent_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_small_tcp4, + inet, + gen, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_small_tcp6(suite) -> + []; +ttest_sgent_cgeno_small_tcp6(doc) -> + []; +ttest_sgent_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_small_tcp6, + inet6, + gen, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgeno_medium_tcp4(suite) -> + []; +ttest_sgent_cgeno_medium_tcp4(doc) -> + []; +ttest_sgent_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_medium_tcp4, + inet, + gen, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_medium_tcp6(suite) -> + []; +ttest_sgent_cgeno_medium_tcp6(doc) -> + []; +ttest_sgent_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_medium_tcp6, + inet6, + gen, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgeno_large_tcp4(suite) -> + []; +ttest_sgent_cgeno_large_tcp4(doc) -> + []; +ttest_sgent_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_large_tcp4, + inet, + gen, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgeno_large_tcp6(suite) -> + []; +ttest_sgent_cgeno_large_tcp6(doc) -> + []; +ttest_sgent_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_large_tcp6, + inet6, + gen, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_cgent_small_tcp4(suite) -> + []; +ttest_sgent_cgent_small_tcp4(doc) -> + []; +ttest_sgent_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_small_tcp4, + inet, + gen, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_cgent_small_tcp6(suite) -> + []; +ttest_sgent_cgent_small_tcp6(doc) -> + []; +ttest_sgent_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgeno_small_tcp6, + inet6, + gen, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_cgent_medium_tcp4(suite) -> + []; +ttest_sgent_cgent_medium_tcp4(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet, msg=medium"]; +ttest_sgent_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_medium_tcp4, + inet, + gen, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_cgent_medium_tcp6(suite) -> + []; +ttest_sgent_cgent_medium_tcp6(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet6, msg=medium"]; +ttest_sgent_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_medium_tcp6, + inet6, + gen, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_cgent_large_tcp4(suite) -> + []; +ttest_sgent_cgent_large_tcp4(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet, msg=large"]; +ttest_sgent_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_large_tcp4, + inet, + gen, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_cgent_large_tcp6(suite) -> + []; +ttest_sgent_cgent_large_tcp6(doc) -> + ["Server(gen,true), Client(gen,true), Domain=inet6, msg=large"]; +ttest_sgent_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_cgent_large_tcp6, + inet6, + gen, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csockf_small_tcp4(suite) -> + []; +ttest_sgent_csockf_small_tcp4(doc) -> + []; +ttest_sgent_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_small_tcp4, + inet, + gen, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csockf_small_tcp6(suite) -> + []; +ttest_sgent_csockf_small_tcp6(doc) -> + []; +ttest_sgent_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_small_tcp6, + inet6, + gen, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csockf_medium_tcp4(suite) -> + []; +ttest_sgent_csockf_medium_tcp4(doc) -> + []; +ttest_sgent_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_medium_tcp4, + inet, + gen, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csockf_medium_tcp6(suite) -> + []; +ttest_sgent_csockf_medium_tcp6(doc) -> + []; +ttest_sgent_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_medium_tcp6, + inet6, + gen, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csockf_large_tcp4(suite) -> + []; +ttest_sgent_csockf_large_tcp4(doc) -> + []; +ttest_sgent_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_large_tcp4, + inet, + gen, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csockf_large_tcp6(suite) -> + []; +ttest_sgent_csockf_large_tcp6(doc) -> + []; +ttest_sgent_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockf_large_tcp6, + inet6, + gen, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csocko_small_tcp4(suite) -> + []; +ttest_sgent_csocko_small_tcp4(doc) -> + []; +ttest_sgent_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_small_tcp4, + inet, + gen, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csocko_small_tcp6(suite) -> + []; +ttest_sgent_csocko_small_tcp6(doc) -> + []; +ttest_sgent_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_small_tcp6, + inet6, + gen, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csocko_medium_tcp4(suite) -> + []; +ttest_sgent_csocko_medium_tcp4(doc) -> + []; +ttest_sgent_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_medium_tcp4, + inet, + gen, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csocko_medium_tcp6(suite) -> + []; +ttest_sgent_csocko_medium_tcp6(doc) -> + []; +ttest_sgent_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_medium_tcp6, + inet6, + gen, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csocko_large_tcp4(suite) -> + []; +ttest_sgent_csocko_large_tcp4(doc) -> + []; +ttest_sgent_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_large_tcp4, + inet, + gen, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csocko_large_tcp6(suite) -> + []; +ttest_sgent_csocko_large_tcp6(doc) -> + []; +ttest_sgent_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_large_tcp6, + inet6, + gen, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_sgent_csockt_small_tcp4(suite) -> + []; +ttest_sgent_csockt_small_tcp4(doc) -> + []; +ttest_sgent_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_small_tcp4, + inet, + gen, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_sgent_csockt_small_tcp6(suite) -> + []; +ttest_sgent_csockt_small_tcp6(doc) -> + []; +ttest_sgent_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csocko_small_tcp6, + inet6, + gen, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_sgent_csockt_medium_tcp4(suite) -> + []; +ttest_sgent_csockt_medium_tcp4(doc) -> + []; +ttest_sgent_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_medium_tcp4, + inet, + gen, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_sgent_csockt_medium_tcp6(suite) -> + []; +ttest_sgent_csockt_medium_tcp6(doc) -> + []; +ttest_sgent_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_medium_tcp6, + inet6, + gen, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_sgent_csockt_large_tcp4(suite) -> + []; +ttest_sgent_csockt_large_tcp4(doc) -> + []; +ttest_sgent_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_large_tcp4, + inet, + gen, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = gen_tcp, Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_sgent_csockt_large_tcp6(suite) -> + []; +ttest_sgent_csockt_large_tcp6(doc) -> + []; +ttest_sgent_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_sgent_csockt_large_tcp6, + inet6, + gen, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgenf_small_tcp4(suite) -> + []; +ttest_ssockf_cgenf_small_tcp4(doc) -> + []; +ttest_ssockf_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_small_tcp4, + inet, + sock, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_small_tcp6(suite) -> + []; +ttest_ssockf_cgenf_small_tcp6(doc) -> + []; +ttest_ssockf_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_small_tcp6, + inet6, + sock, false, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgenf_medium_tcp4(suite) -> + []; +ttest_ssockf_cgenf_medium_tcp4(doc) -> + []; +ttest_ssockf_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_medium_tcp4, + inet, + sock, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_medium_tcp6(suite) -> + []; +ttest_ssockf_cgenf_medium_tcp6(doc) -> + []; +ttest_ssockf_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_medium_tcp6, + inet6, + sock, false, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgenf_large_tcp4(suite) -> + []; +ttest_ssockf_cgenf_large_tcp4(doc) -> + []; +ttest_ssockf_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_large_tcp4, + inet, + sock, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgenf_large_tcp6(suite) -> + []; +ttest_ssockf_cgenf_large_tcp6(doc) -> + []; +ttest_ssockf_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgenf_large_tcp6, + inet6, + sock, false, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgeno_small_tcp4(suite) -> + []; +ttest_ssockf_cgeno_small_tcp4(doc) -> + []; +ttest_ssockf_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_small_tcp4, + inet, + sock, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_small_tcp6(suite) -> + []; +ttest_ssockf_cgeno_small_tcp6(doc) -> + []; +ttest_ssockf_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_small_tcp6, + inet6, + sock, false, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgeno_medium_tcp4(suite) -> + []; +ttest_ssockf_cgeno_medium_tcp4(doc) -> + []; +ttest_ssockf_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_medium_tcp4, + inet, + sock, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_medium_tcp6(suite) -> + []; +ttest_ssockf_cgeno_medium_tcp6(doc) -> + []; +ttest_ssockf_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_medium_tcp6, + inet6, + sock, false, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgeno_large_tcp4(suite) -> + []; +ttest_ssockf_cgeno_large_tcp4(doc) -> + []; +ttest_ssockf_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_large_tcp4, + inet, + sock, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgeno_large_tcp6(suite) -> + []; +ttest_ssockf_cgeno_large_tcp6(doc) -> + []; +ttest_ssockf_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_large_tcp6, + inet6, + sock, false, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_cgent_small_tcp4(suite) -> + []; +ttest_ssockf_cgent_small_tcp4(doc) -> + []; +ttest_ssockf_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_small_tcp4, + inet, + sock, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_small_tcp6(suite) -> + []; +ttest_ssockf_cgent_small_tcp6(doc) -> + []; +ttest_ssockf_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgeno_small_tcp6, + inet6, + sock, false, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_cgent_medium_tcp4(suite) -> + []; +ttest_ssockf_cgent_medium_tcp4(doc) -> + []; +ttest_ssockf_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_medium_tcp4, + inet, + sock, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_medium_tcp6(suite) -> + []; +ttest_ssockf_cgent_medium_tcp6(doc) -> + []; +ttest_ssockf_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_medium_tcp6, + inet6, + sock, false, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_cgent_large_tcp4(suite) -> + []; +ttest_ssockf_cgent_large_tcp4(doc) -> + []; +ttest_ssockf_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_large_tcp4, + inet, + sock, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_cgent_large_tcp6(suite) -> + []; +ttest_ssockf_cgent_large_tcp6(doc) -> + []; +ttest_ssockf_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_cgent_large_tcp6, + inet6, + sock, false, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csockf_small_tcp4(suite) -> + []; +ttest_ssockf_csockf_small_tcp4(doc) -> + []; +ttest_ssockf_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_small_tcp4, + inet, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_small_tcp6(suite) -> + []; +ttest_ssockf_csockf_small_tcp6(doc) -> + []; +ttest_ssockf_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_small_tcp6, + inet6, + sock, false, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csockf_medium_tcp4(suite) -> + []; +ttest_ssockf_csockf_medium_tcp4(doc) -> + []; +ttest_ssockf_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_medium_tcp4, + inet, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_medium_tcp6(suite) -> + []; +ttest_ssockf_csockf_medium_tcp6(doc) -> + []; +ttest_ssockf_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_medium_tcp6, + inet6, + sock, false, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csockf_large_tcp4(suite) -> + []; +ttest_ssockf_csockf_large_tcp4(doc) -> + []; +ttest_ssockf_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_large_tcp4, + inet, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csockf_large_tcp6(suite) -> + []; +ttest_ssockf_csockf_large_tcp6(doc) -> + []; +ttest_ssockf_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockf_large_tcp6, + inet6, + sock, false, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csocko_small_tcp4(suite) -> + []; +ttest_ssockf_csocko_small_tcp4(doc) -> + []; +ttest_ssockf_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_small_tcp4, + inet, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_small_tcp6(suite) -> + []; +ttest_ssockf_csocko_small_tcp6(doc) -> + []; +ttest_ssockf_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_small_tcp6, + inet6, + sock, false, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csocko_medium_tcp4(suite) -> + []; +ttest_ssockf_csocko_medium_tcp4(doc) -> + []; +ttest_ssockf_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_medium_tcp4, + inet, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_medium_tcp6(suite) -> + []; +ttest_ssockf_csocko_medium_tcp6(doc) -> + []; +ttest_ssockf_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_medium_tcp6, + inet6, + sock, false, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csocko_large_tcp4(suite) -> + []; +ttest_ssockf_csocko_large_tcp4(doc) -> + []; +ttest_ssockf_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_large_tcp4, + inet, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csocko_large_tcp6(suite) -> + []; +ttest_ssockf_csocko_large_tcp6(doc) -> + []; +ttest_ssockf_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_large_tcp6, + inet6, + sock, false, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockf_csockt_small_tcp4(suite) -> + []; +ttest_ssockf_csockt_small_tcp4(doc) -> + []; +ttest_ssockf_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_small_tcp4, + inet, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_small_tcp6(suite) -> + []; +ttest_ssockf_csockt_small_tcp6(doc) -> + []; +ttest_ssockf_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csocko_small_tcp6, + inet6, + sock, false, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockf_csockt_medium_tcp4(suite) -> + []; +ttest_ssockf_csockt_medium_tcp4(doc) -> + []; +ttest_ssockf_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_medium_tcp4, + inet, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_medium_tcp6(suite) -> + []; +ttest_ssockf_csockt_medium_tcp6(doc) -> + []; +ttest_ssockf_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_medium_tcp6, + inet6, + sock, false, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockf_csockt_large_tcp4(suite) -> + []; +ttest_ssockf_csockt_large_tcp4(doc) -> + []; +ttest_ssockf_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_large_tcp4, + inet, + sock, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockf_csockt_large_tcp6(suite) -> + []; +ttest_ssockf_csockt_large_tcp6(doc) -> + []; +ttest_ssockf_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockf_csockt_large_tcp6, + inet6, + sock, false, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgenf_small_tcp4(suite) -> + []; +ttest_ssocko_cgenf_small_tcp4(doc) -> + []; +ttest_ssocko_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_small_tcp4, + inet, + sock, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_small_tcp6(suite) -> + []; +ttest_ssocko_cgenf_small_tcp6(doc) -> + []; +ttest_ssocko_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_small_tcp6, + inet6, + sock, once, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgenf_medium_tcp4(suite) -> + []; +ttest_ssocko_cgenf_medium_tcp4(doc) -> + []; +ttest_ssocko_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_medium_tcp4, + inet, + sock, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_medium_tcp6(suite) -> + []; +ttest_ssocko_cgenf_medium_tcp6(doc) -> + []; +ttest_ssocko_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_medium_tcp6, + inet6, + sock, once, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgenf_large_tcp4(suite) -> + []; +ttest_ssocko_cgenf_large_tcp4(doc) -> + []; +ttest_ssocko_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_large_tcp4, + inet, + sock, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgenf_large_tcp6(suite) -> + []; +ttest_ssocko_cgenf_large_tcp6(doc) -> + []; +ttest_ssocko_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgenf_large_tcp6, + inet6, + sock, once, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgeno_small_tcp4(suite) -> + []; +ttest_ssocko_cgeno_small_tcp4(doc) -> + []; +ttest_ssocko_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_small_tcp4, + inet, + sock, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_small_tcp6(suite) -> + []; +ttest_ssocko_cgeno_small_tcp6(doc) -> + []; +ttest_ssocko_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_small_tcp6, + inet6, + sock, once, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgeno_medium_tcp4(suite) -> + []; +ttest_ssocko_cgeno_medium_tcp4(doc) -> + []; +ttest_ssocko_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_medium_tcp4, + inet, + sock, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_medium_tcp6(suite) -> + []; +ttest_ssocko_cgeno_medium_tcp6(doc) -> + []; +ttest_ssocko_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_medium_tcp6, + inet6, + sock, once, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgeno_large_tcp4(suite) -> + []; +ttest_ssocko_cgeno_large_tcp4(doc) -> + []; +ttest_ssocko_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_large_tcp4, + inet, + sock, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgeno_large_tcp6(suite) -> + []; +ttest_ssocko_cgeno_large_tcp6(doc) -> + []; +ttest_ssocko_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgeno_large_tcp6, + inet6, + sock, once, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_cgent_small_tcp4(suite) -> + []; +ttest_ssocko_cgent_small_tcp4(doc) -> + []; +ttest_ssocko_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_small_tcp4, + inet, + sock, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_small_tcp6(suite) -> + []; +ttest_ssocko_cgent_small_tcp6(doc) -> + []; +ttest_ssocko_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_small_tcp6, + inet6, + sock, once, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_cgent_medium_tcp4(suite) -> + []; +ttest_ssocko_cgent_medium_tcp4(doc) -> + []; +ttest_ssocko_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_medium_tcp4, + inet, + sock, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_medium_tcp6(suite) -> + []; +ttest_ssocko_cgent_medium_tcp6(doc) -> + []; +ttest_ssocko_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_medium_tcp6, + inet6, + sock, once, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_cgent_large_tcp4(suite) -> + []; +ttest_ssocko_cgent_large_tcp4(doc) -> + []; +ttest_ssocko_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_large_tcp4, + inet, + sock, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = false +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_cgent_large_tcp6(suite) -> + []; +ttest_ssocko_cgent_large_tcp6(doc) -> + []; +ttest_ssocko_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_cgent_large_tcp6, + inet6, + sock, once, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csockf_small_tcp4(suite) -> + []; +ttest_ssocko_csockf_small_tcp4(doc) -> + []; +ttest_ssocko_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_small_tcp4, + inet, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_small_tcp6(suite) -> + []; +ttest_ssocko_csockf_small_tcp6(doc) -> + []; +ttest_ssocko_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_small_tcp6, + inet6, + sock, once, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csockf_medium_tcp4(suite) -> + []; +ttest_ssocko_csockf_medium_tcp4(doc) -> + []; +ttest_ssocko_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_medium_tcp4, + inet, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_medium_tcp6(suite) -> + []; +ttest_ssocko_csockf_medium_tcp6(doc) -> + []; +ttest_ssocko_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_medium_tcp6, + inet6, + sock, once, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csockf_large_tcp4(suite) -> + []; +ttest_ssocko_csockf_large_tcp4(doc) -> + []; +ttest_ssocko_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_large_tcp4, + inet, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csockf_large_tcp6(suite) -> + []; +ttest_ssocko_csockf_large_tcp6(doc) -> + []; +ttest_ssocko_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockf_large_tcp6, + inet6, + sock, once, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csocko_small_tcp4(suite) -> + []; +ttest_ssocko_csocko_small_tcp4(doc) -> + []; +ttest_ssocko_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_small_tcp4, + inet, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_small_tcp6(suite) -> + []; +ttest_ssocko_csocko_small_tcp6(doc) -> + []; +ttest_ssocko_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_small_tcp6, + inet6, + sock, once, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csocko_medium_tcp4(suite) -> + []; +ttest_ssocko_csocko_medium_tcp4(doc) -> + []; +ttest_ssocko_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_medium_tcp4, + inet, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_medium_tcp6(suite) -> + []; +ttest_ssocko_csocko_medium_tcp6(doc) -> + []; +ttest_ssocko_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_medium_tcp6, + inet6, + sock, once, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csocko_large_tcp4(suite) -> + []; +ttest_ssocko_csocko_large_tcp4(doc) -> + []; +ttest_ssocko_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_large_tcp4, + inet, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csocko_large_tcp6(suite) -> + []; +ttest_ssocko_csocko_large_tcp6(doc) -> + []; +ttest_ssocko_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_large_tcp6, + inet6, + sock, once, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssocko_csockt_small_tcp4(suite) -> + []; +ttest_ssocko_csockt_small_tcp4(doc) -> + []; +ttest_ssocko_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_small_tcp4, + inet, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_small_tcp6(suite) -> + []; +ttest_ssocko_csockt_small_tcp6(doc) -> + []; +ttest_ssocko_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csocko_small_tcp6, + inet6, + sock, once, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssocko_csockt_medium_tcp4(suite) -> + []; +ttest_ssocko_csockt_medium_tcp4(doc) -> + []; +ttest_ssocko_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_medium_tcp4, + inet, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_medium_tcp6(suite) -> + []; +ttest_ssocko_csockt_medium_tcp6(doc) -> + []; +ttest_ssocko_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_medium_tcp6, + inet6, + sock, once, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssocko_csockt_large_tcp4(suite) -> + []; +ttest_ssocko_csockt_large_tcp4(doc) -> + []; +ttest_ssocko_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_large_tcp4, + inet, + sock, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = once +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssocko_csockt_large_tcp6(suite) -> + []; +ttest_ssocko_csockt_large_tcp6(doc) -> + []; +ttest_ssocko_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssocko_csockt_large_tcp6, + inet6, + sock, once, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgenf_small_tcp4(suite) -> + []; +ttest_ssockt_cgenf_small_tcp4(doc) -> + []; +ttest_ssockt_cgenf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_small_tcp4, + inet, + sock, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_small_tcp6(suite) -> + []; +ttest_ssockt_cgenf_small_tcp6(doc) -> + []; +ttest_ssockt_cgenf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_small_tcp6, + inet6, + sock, true, + gen, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgenf_medium_tcp4(suite) -> + []; +ttest_ssockt_cgenf_medium_tcp4(doc) -> + []; +ttest_ssockt_cgenf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_medium_tcp4, + inet, + sock, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_medium_tcp6(suite) -> + []; +ttest_ssockt_cgenf_medium_tcp6(doc) -> + []; +ttest_ssockt_cgenf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_medium_tcp6, + inet6, + sock, true, + gen, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgenf_large_tcp4(suite) -> + []; +ttest_ssockt_cgenf_large_tcp4(doc) -> + []; +ttest_ssockt_cgenf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_large_tcp4, + inet, + sock, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgenf_large_tcp6(suite) -> + []; +ttest_ssockt_cgenf_large_tcp6(doc) -> + []; +ttest_ssockt_cgenf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgenf_large_tcp6, + inet6, + sock, true, + gen, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgeno_small_tcp4(suite) -> + []; +ttest_ssockt_cgeno_small_tcp4(doc) -> + []; +ttest_ssockt_cgeno_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_small_tcp4, + inet, + sock, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_small_tcp6(suite) -> + []; +ttest_ssockt_cgeno_small_tcp6(doc) -> + []; +ttest_ssockt_cgeno_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_small_tcp6, + inet6, + sock, true, + gen, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgeno_medium_tcp4(suite) -> + []; +ttest_ssockt_cgeno_medium_tcp4(doc) -> + []; +ttest_ssockt_cgeno_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_medium_tcp4, + inet, + sock, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_medium_tcp6(suite) -> + []; +ttest_ssockt_cgeno_medium_tcp6(doc) -> + []; +ttest_ssockt_cgeno_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_medium_tcp6, + inet6, + sock, true, + gen, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgeno_large_tcp4(suite) -> + []; +ttest_ssockt_cgeno_large_tcp4(doc) -> + []; +ttest_ssockt_cgeno_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_large_tcp4, + inet, + sock, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgeno_large_tcp6(suite) -> + []; +ttest_ssockt_cgeno_large_tcp6(doc) -> + []; +ttest_ssockt_cgeno_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgeno_large_tcp6, + inet6, + sock, true, + gen, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_cgent_small_tcp4(suite) -> + []; +ttest_ssockt_cgent_small_tcp4(doc) -> + []; +ttest_ssockt_cgent_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_small_tcp4, + inet, + sock, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_small_tcp6(suite) -> + []; +ttest_ssockt_cgent_small_tcp6(doc) -> + []; +ttest_ssockt_cgent_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_small_tcp6, + inet6, + sock, true, + gen, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_cgent_medium_tcp4(suite) -> + []; +ttest_ssockt_cgent_medium_tcp4(doc) -> + []; +ttest_ssockt_cgent_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_medium_tcp4, + inet, + sock, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_medium_tcp6(suite) -> + []; +ttest_ssockt_cgent_medium_tcp6(doc) -> + []; +ttest_ssockt_cgent_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_medium_tcp6, + inet6, + sock, true, + gen, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_cgent_large_tcp4(suite) -> + []; +ttest_ssockt_cgent_large_tcp4(doc) -> + []; +ttest_ssockt_cgent_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_large_tcp4, + inet, + sock, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = gen_tcp, Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_cgent_large_tcp6(suite) -> + []; +ttest_ssockt_cgent_large_tcp6(doc) -> + []; +ttest_ssockt_cgent_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_cgent_large_tcp6, + inet6, + sock, true, + gen, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csockf_small_tcp4(suite) -> + []; +ttest_ssockt_csockf_small_tcp4(doc) -> + []; +ttest_ssockt_csockf_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_small_tcp4, + inet, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_small_tcp6(suite) -> + []; +ttest_ssockt_csockf_small_tcp6(doc) -> + []; +ttest_ssockt_csockf_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_small_tcp6, + inet6, + sock, true, + sock, false, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csockf_medium_tcp4(suite) -> + []; +ttest_ssockt_csockf_medium_tcp4(doc) -> + []; +ttest_ssockt_csockf_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_medium_tcp4, + inet, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_medium_tcp6(suite) -> + []; +ttest_ssockt_csockf_medium_tcp6(doc) -> + []; +ttest_ssockt_csockf_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_medium_tcp6, + inet6, + sock, true, + sock, false, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csockf_large_tcp4(suite) -> + []; +ttest_ssockt_csockf_large_tcp4(doc) -> + []; +ttest_ssockt_csockf_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_large_tcp4, + inet, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = false +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csockf_large_tcp6(suite) -> + []; +ttest_ssockt_csockf_large_tcp6(doc) -> + []; +ttest_ssockt_csockf_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockf_large_tcp6, + inet6, + sock, true, + sock, false, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csocko_small_tcp4(suite) -> + []; +ttest_ssockt_csocko_small_tcp4(doc) -> + []; +ttest_ssockt_csocko_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_small_tcp4, + inet, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_small_tcp6(suite) -> + []; +ttest_ssockt_csocko_small_tcp6(doc) -> + []; +ttest_ssockt_csocko_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_small_tcp6, + inet6, + sock, true, + sock, once, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csocko_medium_tcp4(suite) -> + []; +ttest_ssockt_csocko_medium_tcp4(doc) -> + []; +ttest_ssockt_csocko_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_medium_tcp4, + inet, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_medium_tcp6(suite) -> + []; +ttest_ssockt_csocko_medium_tcp6(doc) -> + []; +ttest_ssockt_csocko_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_medium_tcp6, + inet6, + sock, true, + sock, once, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csocko_large_tcp4(suite) -> + []; +ttest_ssockt_csocko_large_tcp4(doc) -> + []; +ttest_ssockt_csocko_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_large_tcp4, + inet, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = once +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csocko_large_tcp6(suite) -> + []; +ttest_ssockt_csocko_large_tcp6(doc) -> + []; +ttest_ssockt_csocko_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_large_tcp6, + inet6, + sock, true, + sock, once, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet +%% + +ttest_ssockt_csockt_small_tcp4(suite) -> + []; +ttest_ssockt_csockt_small_tcp4(doc) -> + []; +ttest_ssockt_csockt_small_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_small_tcp4, + inet, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: small (=1) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_small_tcp6(suite) -> + []; +ttest_ssockt_csockt_small_tcp6(doc) -> + []; +ttest_ssockt_csockt_small_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csocko_small_tcp6, + inet6, + sock, true, + sock, true, + 1, 200). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet +%% + +ttest_ssockt_csockt_medium_tcp4(suite) -> + []; +ttest_ssockt_csockt_medium_tcp4(doc) -> + []; +ttest_ssockt_csockt_medium_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_medium_tcp4, + inet, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: medium (=2) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_medium_tcp6(suite) -> + []; +ttest_ssockt_csockt_medium_tcp6(doc) -> + []; +ttest_ssockt_csockt_medium_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_medium_tcp6, + inet6, + sock, true, + sock, true, + 2, 20). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet +%% + +ttest_ssockt_csockt_large_tcp4(suite) -> + []; +ttest_ssockt_csockt_large_tcp4(doc) -> + []; +ttest_ssockt_csockt_large_tcp4(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_large_tcp4, + inet, + sock, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% This test case uses the time test (ttest) utility to implement a +%% ping-pong like test case. +%% Server: Transport = socket(tcp), Active = true +%% Client: Transport = socket(tcp), Active = true +%% Message Size: large (=3) +%% Domain: inet6 +%% + +ttest_ssockt_csockt_large_tcp6(suite) -> + []; +ttest_ssockt_csockt_large_tcp6(doc) -> + []; +ttest_ssockt_csockt_large_tcp6(_Config) when is_list(_Config) -> + ttest_tcp(ttest_ssockt_csockt_large_tcp6, + inet6, + sock, true, + sock, true, + 3, 2). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ttest_tcp(TC, + Domain, + ServerMod, ServerActive, + ClientMod, ClientActive, + MsgID, MaxOutstanding) -> + Runtime = ?TTEST_RUNTIME, + tc_try(TC, + fun() -> + if (Domain =/= inet) -> not_yet_implemented(); true -> ok end, + %% This may be overkill, depending on the runtime, + %% but better safe then sorry... + ?TT(Runtime + ?SECS(60)), + InitState = #{domain => Domain, + msg_id => MsgID, + max_outstanding => MaxOutstanding, + runtime => Runtime, + server_mod => ServerMod, + server_active => ServerActive, + client_mod => ClientMod, + client_active => ClientActive}, + ok = ttest_tcp(InitState) + end). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ttest_tcp(InitState) -> + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + Tester = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, server) of + {ok, Node} -> + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor server node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "start ttest (remote) server", + cmd => fun(#{mod := Mod, + active := Active, + node := Node} = State) -> + case ttest_tcp_server_start(Node, Mod, Active) of + {ok, {{Pid, _MRef}, {Addr, Port}}} -> + {ok, State#{rserver => Pid, + addr => Addr, + port => Port}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester, + addr := Addr, + port := Port}) -> + ?SEV_ANNOUNCE_READY(Tester, init, {Addr, Port}), + ok + end}, + + + %% *** Termination *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester, + rserver := RServer} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester, + [{rserver, RServer}]) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + %% The remote server is in a accept, with a timeout of 5 seconds, + %% so may have to wait a bit... + #{desc => "order (remote) ttest server terminate", + cmd => fun(#{node := _Node, + rserver := RServer}) -> + ttest_tcp_server_stop(RServer), + ok + end}, + #{desc => "await ttest (remote) server termination", + cmd => fun(#{rserver := RServer} = State) -> + ?SEV_AWAIT_TERMINATION(RServer), + State1 = maps:remove(rserver, State), + {ok, State1} + end}, + #{desc => "stop (server) node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await (server) node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + ClientSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, {ServerAddr, ServerPort}} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, + server_addr => ServerAddr, + server_port => ServerPort}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + + %% *** Init part *** + #{desc => "create node", + cmd => fun(#{host := Host} = State) -> + case start_node(Host, client) of + {ok, Node} -> + {ok, State#{node => Node}}; + {error, Reason, _} -> + {error, Reason} + end + end}, + #{desc => "monitor client node", + cmd => fun(#{node := Node} = _State) -> + true = erlang:monitor_node(Node, true), + ok + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + + %% The actual test + #{desc => "await continue (ttest)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, ttest), + ok + end}, + #{desc => "start ttest (remote) client", + cmd => fun(#{node := Node, + mod := Mod, + active := Active, + msg_id := MsgID, + max_outstanding := MaxOutstanding, + runtime := RunTime, + server_addr := Addr, + server_port := Port} = State) -> + Self = self(), + Notify = + fun(Result) -> + ?SEV_ANNOUNCE_READY(Self, ttest, Result) + end, + case ttest_tcp_client_start(Node, Notify, + Mod, Active, + Addr, Port, + MsgID, MaxOutstanding, + RunTime) of + {ok, {Pid, _MRef}} -> + {ok, State#{rclient => Pid}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ttest ready", + cmd => fun(#{tester := Tester, + rclient := RClient} = State) -> + %% TTestResult = ?SEV_AWAIT_READY(RClient, rclient, ttest, + %% [{tester, Tester}]), + case ?SEV_AWAIT_READY(RClient, rclient, ttest, + [{tester, Tester}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "await ttest (remote) client termination", + cmd => fun(#{rclient := RClient} = State) -> + ?SEV_AWAIT_TERMINATION(RClient), + State1 = maps:remove(rclient, State), + {ok, State1} + end}, + #{desc => "announce ready (ttest)", + cmd => fun(#{tester := Tester, + result := Result} = State) -> + ?SEV_ANNOUNCE_READY(Tester, ttest, Result), + {ok, maps:remove(result, State)} + end}, + + + %% *** Termination *** + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "stop (client) node", + cmd => fun(#{node := Node} = _State) -> + stop_node(Node) + end}, + #{desc => "await (client) node termination", + cmd => fun(#{node := Node} = State) -> + receive + {nodedown, Node} -> + {ok, maps:remove(node, State)} + end + end}, + + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + #{desc => "monitor client", + cmd => fun(#{client := Pid} = _State) -> + _MRef = erlang:monitor(process, Pid), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid} = _State) -> + ?SEV_ANNOUNCE_START(Pid), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = State) -> + {ok, {Addr, Port}} = ?SEV_AWAIT_READY(Pid, server, init), + {ok, State#{server_addr => Addr, + server_port => Port}} + end}, + + + %% Start the client + #{desc => "order client start", + cmd => fun(#{client := Pid, + server_addr := Addr, + server_port := Port} = _State) -> + ?SEV_ANNOUNCE_START(Pid, {Addr, Port}), + ok + end}, + #{desc => "await client ready (init)", + cmd => fun(#{client := Client} = _State) -> + ok = ?SEV_AWAIT_READY(Client, client, init) + end}, + + %% The actual test + #{desc => "order client continue (ttest)", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Client, ttest), + ok + end}, + #{desc => "await client ready (ttest)", + cmd => fun(#{server := Server, + client := Client} = State) -> + case ?SEV_AWAIT_READY(Client, client, ttest, + [{server, Server}]) of + {ok, Result} -> + {ok, State#{result => Result}}; + {error, _} = ERROR -> + ERROR + end + end}, + + + %% *** Terminate server *** + #{desc => "order client terminate", + cmd => fun(#{client := Client} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Client), + ok + end}, + #{desc => "await client down", + cmd => fun(#{client := Client} = State) -> + ?SEV_AWAIT_TERMINATION(Client), + State1 = maps:remove(client, State), + {ok, State1} + end}, + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server down", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_TERMINATION(Server), + ok + end}, + + + %% Present the results + #{desc => "present the results", + cmd => fun(#{result := Result} = State) -> + case Result of + #{status := ok, + runtime := RunTime, + cnt := Cnt, + bcnt := BCnt} -> + ?SEV_IPRINT( + "TTest results: " + "~n Run Time: ~s" + "~n Byte Count: ~s" + "~n Number of message exchanges: ~s" + "~n~n", + [ + ?TTEST_LIB:format_time(RunTime), + if ((BCnt =:= 0) orelse (RunTime =:= 0)) -> + ?TTEST_LIB:format("~w, ~w", + [BCnt, RunTime]); + true -> + ?TTEST_LIB:format("~p => ~p byte / ms", + [BCnt, BCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + ?TTEST_LIB:format("~p => ~p iterations / ms", + [Cnt, Cnt div RunTime]) + end + ]), + {ok, maps:remove(result, State)}; + + #{status := Failure, + runtime := RunTime, + sid := SID, + rid := RID, + scnt := SCnt, + rcnt := RCnt, + bcnt := BCnt, + num := Num} -> + ?SEV_EPRINT("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + ?TTEST_LIB:format_time(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]), + {error, Failure} + end + end}, + + %% This is just so that the printout above shall have time to come + %% out before then end of the test case. + ?SEV_SLEEP(?SECS(1)), + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + i("start server evaluator"), + ServerInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + mod => maps:get(server_mod, InitState), + active => maps:get(server_active, InitState)}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start client evaluator"), + ClientInitState = #{host => local_host(), + domain => maps:get(domain, InitState), + mod => maps:get(client_mod, InitState), + active => maps:get(client_active, InitState), + msg_id => maps:get(msg_id, InitState), + max_outstanding => maps:get(max_outstanding, InitState), + runtime => maps:get(runtime, InitState)}, + Client = ?SEV_START("client", ClientSeq, ClientInitState), + + i("start 'tester' evaluator"), + TesterInitState = #{server => Server#ev.pid, + client => Client#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Server, Client, Tester]). + + + +ttest_tcp_server_start(Node, gen, Active) -> + Transport = socket_test_ttest_tcp_gen, + socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active); +ttest_tcp_server_start(Node, sock, Active) -> + TransportMod = socket_test_ttest_tcp_socket, + Transport = {TransportMod, #{method => plain}}, + socket_test_ttest_tcp_server:start_monitor(Node, Transport, Active). + +ttest_tcp_server_stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). + +ttest_tcp_client_start(Node, + Notify, + gen, + Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + Transport = socket_test_ttest_tcp_gen, + socket_test_ttest_tcp_client:start_monitor(Node, + Notify, + Transport, + Active, + Addr, Port, + MsgID, MaxOutstanding, RunTime); +ttest_tcp_client_start(Node, + Notify, + sock, + Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + TransportMod = socket_test_ttest_tcp_socket, + Transport = {TransportMod, #{method => plain}}, + socket_test_ttest_tcp_client:start_monitor(Node, + Notify, + Transport, + Active, + Addr, Port, + MsgID, MaxOutstanding, RunTime). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_node(Host, NodeName) -> + UniqueNodeName = f("~w_~w", [NodeName, erlang:system_time(millisecond)]), + case do_start_node(Host, UniqueNodeName) of + {ok, _} = OK -> + global:sync(), + %% i("Node ~p started: " + %% "~n Nodes: ~p" + %% "~n Logger: ~p" + %% "~n Global Names: ~p", + %% [NodeName, nodes(), + %% global:whereis_name(socket_test_logger), + %% global:registered_names()]), + OK; + {error, Reason, _} -> + {error, Reason} + end. + +do_start_node(Host, NodeName) when is_list(NodeName) -> + do_start_node(Host, list_to_atom(NodeName)); +do_start_node(Host, NodeName) when is_atom(NodeName) -> + Dir = filename:dirname(code:which(?MODULE)), + Flags = "-pa " ++ Dir, + Opts = [{monitor_master, true}, {erl_flags, Flags}], + ct_slave:start(Host, NodeName, Opts). + + +stop_node(Node) -> + case ct_slave:stop(Node) of + {ok, _} -> + ok; + {error, _} = ERROR -> + ERROR + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sock_open(Domain, Type, Proto) -> + try socket:open(Domain, Type, Proto) of + {ok, Socket} -> + Socket; + {error, Reason} -> + ?FAIL({open, Reason}) + catch + C:E:S -> + ?FAIL({open, C, E, S}) + end. + + +sock_bind(Sock, SockAddr) -> + try socket:bind(Sock, SockAddr) of + {ok, Port} -> + Port; + {error, Reason} -> + i("sock_bind -> error: ~p", [Reason]), + ?FAIL({bind, Reason}) + catch + C:E:S -> + i("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} -> +%% i("sock_accept -> error: ~p", [Reason]), +%% ?FAIL({accept, Reason}) +%% catch +%% C:E:S -> +%% i("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} -> + i("sock_close -> error: ~p", [Reason]), + ?FAIL({close, Reason}) + catch + C:E:S -> + i("sock_close -> failed: ~p, ~p, ~p", [C, E, S]), + ?FAIL({close, C, E, S}) + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +local_host() -> + try net_adm:localhost() of + Host when is_list(Host) -> + %% Convert to shortname if long + case string:tokens(Host, [$.]) of + [H|_] -> + list_to_atom(H) + end + catch + C:E:S -> + erlang:raise(C, E, S) + end. + + +%% 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, [{"lo" ++ _, _}|IFL]) -> + which_addr(Domain, IFL); +which_addr(Domain, [{_Name, IFO}|_IFL]) -> + 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). + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +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), + tc_print("begin ***", + "~n----------------------------------------------------~n", ""). + +tc_end(Result) when is_list(Result) -> + tc_print("done: ~s", [Result], + "", "----------------------------------------------------~n~n"), + ok. + + +tc_try(Case, Fun) when is_atom(Case) andalso is_function(Fun, 0) -> + tc_begin(Case), + try + begin + Fun(), + ?SLEEP(?SECS(1)), + tc_end("ok") + end + catch + throw:{skip, _} = SKIP -> + tc_end("skipping"), + SKIP; + Class:Error:Stack -> + tc_end("failed"), + erlang:raise(Class, Error, Stack) + end. + + +tc_print(F, Before, After) -> + tc_print(F, [], Before, After). + +tc_print(F, A, Before, After) -> + Name = tc_which_name(), + FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", + [formated_timestamp(),Name,self()|A]), + io:format(user, Before ++ FStr ++ After, []). + +tc_which_name() -> + case get(tc_name) of + undefined -> + case get(sname) of + undefined -> + ""; + SName when is_list(SName) -> + SName + end; + Name when is_list(Name) -> + Name + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +l2a(S) when is_list(S) -> + list_to_atom(S). + +l2b(L) when is_list(L) -> + list_to_binary(L). + +b2l(B) when is_binary(B) -> + binary_to_list(B). + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + +%% p(F) -> +%% p(F, []). + +%% p(F, A) -> +%% p(F, A, "", ""). + +%% p(F, A, Before, After) when is_list(Before) andalso is_list(After) -> +%% 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, +%% FStr = f("*** [~s][~s][~p] " ++ F ++ "~n", +%% [formated_timestamp(),TcName,self()|A]), +%% i(Before ++ FStr ++ After, []). + + +%% d(F, A) -> +%% d(get(dbg_fd), F, A). + +%% d(undefined, F, A) -> +%% [NodeNameStr|_] = string:split(atom_to_list(node()), [$@]), +%% DbgFileName = f("~s-dbg.txt", [NodeNameStr]), +%% case file:open(DbgFileName, [write]) of +%% {ok, FD} -> +%% put(dbg_fd, FD), +%% d(FD, F, A); +%% {error, Reason} -> +%% exit({failed_open_dbg_file, Reason}) +%% end; +%% d(FD, F, A) -> +%% io:format(FD, "~s~n", [f("[~s] " ++ F, [formated_timestamp()|A])]). + +i(F) -> + i(F, []). + +i(F, A) -> + FStr = f("[~s] " ++ F, [formated_timestamp()|A]), + io:format(user, FStr ++ "~n", []), + io:format(FStr, []). + diff --git a/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) -> +%% <<Type:32/integer, N:32/integer, Data/binary>>. + +%% dec_msg(<<?REQ:32/integer, N:32/integer, Data/binary>>) -> +%% {request, N, Data}; +%% dec_msg(<<?REP:32/integer, N:32/integer, Data/binary>>) -> +%% {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) -> + <<Type:32/integer, N:32/integer, Data/binary>>. + +dec_msg(<<?REQ:32/integer, N:32/integer, Data/binary>>) -> + {request, N, Data}; +dec_msg(<<?REP:32/integer, N:32/integer, Data/binary>>) -> + {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("<ERROR> " ++ 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, <<Type:32>>} -> + 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/erts/emulator/test/socket_test_evaluator.erl b/erts/emulator/test/socket_test_evaluator.erl new file mode 100644 index 0000000000..fe6a6ff70a --- /dev/null +++ b/erts/emulator/test/socket_test_evaluator.erl @@ -0,0 +1,524 @@ +%% +%% %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_test_evaluator). + +%% Evaluator control functions +-export([ + start/3, + await_finish/1 + ]). + +%% Functions used by evaluators to interact with eachother +-export([ + %% Announce functions + %% (Send an announcement from one evaluator to another) + announce_start/1, announce_start/2, + announce_continue/2, announce_continue/3, + announce_ready/2, announce_ready/3, + announce_terminate/1, + + %% Await functions + %% (Wait for an announcement from another evaluator) + await_start/0, await_start/1, + await_continue/3, await_continue/4, + await_ready/3, await_ready/4, + await_terminate/2, await_terminate/3, + await_termination/1, await_termination/2 + ]). + +%% Utility functions +-export([ + iprint/2, % Info printouts + eprint/2 % Error printouts + ]). + +-export_type([ + ev/0, + initial_evaluator_state/0, + evaluator_state/0, + command_fun/0, + command/0 + ]). + + +-include("socket_test_evaluator.hrl"). + +-type ev() :: #ev{}. +-type initial_evaluator_state() :: map(). +-type evaluator_state() :: term(). +-type command_fun() :: + fun((State :: evaluator_state()) -> ok) | + fun((State :: evaluator_state()) -> {ok, evaluator_state()}) | + fun((State :: evaluator_state()) -> {error, term()}). + +-type command() :: #{desc := string(), + cmd := command_fun()}. + + +%% ============================================================================ + +-define(LIB, socket_test_lib). +-define(LOGGER, socket_test_logger). + +-define(EXTRA_NOTHING, '$nothing'). +-define(ANNOUNCEMENT_START, '$start'). +-define(ANNOUNCEMENT_READY, '$ready'). +-define(ANNOUNCEMENT_CONTINUE, '$continue'). +-define(ANNOUNCEMENT_TERMINATE, '$terminate'). + +-define(START_NAME_NONE, '$no-name'). +-define(START_SLOGAN, ?ANNOUNCEMENT_START). +-define(TERMINATE_SLOGAN, ?ANNOUNCEMENT_TERMINATE). + + +%% ============================================================================ + +-spec start(Name, Seq, Init) -> ev() when + Name :: string(), + Seq :: [command()], + Init :: initial_evaluator_state(). + +start(Name, Seq, InitState) + when is_list(Name) andalso is_list(Seq) andalso (Seq =/= []) -> + %% Make sure 'parent' is not already used + case maps:find(parent, InitState) of + {ok, _} -> + erlang:error({already_used, parent}); + error -> + InitState2 = InitState#{parent => self()}, + {Pid, MRef} = erlang:spawn_monitor( + fun() -> init(Name, Seq, InitState2) end), + #ev{name = Name, pid = Pid, mref = MRef} + end. + +init(Name, Seq, Init) -> + put(sname, Name), + loop(1, Seq, Init). + +loop(_ID, [], FinalState) -> + exit(FinalState); +loop(ID, [#{desc := Desc, + cmd := Cmd}|Cmds], State) when is_function(Cmd, 1) -> + iprint("evaluate command ~2w: ~s", [ID, Desc]), + try Cmd(State) of + ok -> + loop(ID + 1, Cmds, State); + {ok, NewState} -> + loop(ID + 1, Cmds, NewState); + {skip, Reason} -> + exit({skip, Reason}); + {error, Reason} -> + eprint("command ~w failed: " + "~n Reason: ~p", [ID, Reason]), + exit({command_failed, ID, Reason, State}) + catch + throw:{skip, R} = E:_ -> + eprint("command ~w skip: " + "~n Skip Reason: ~p", [ID, R]), + exit(E); + C:E:S -> + eprint("command ~w crashed: " + "~n Class: ~p" + "~n Error: ~p" + "~n Call Stack: ~p", [ID, C, E, S]), + exit({command_crashed, ID, {C,E,S}, State}) + end. + + +%% ============================================================================ + +-spec await_finish(Evs) -> term() when + Evs :: [ev()]. + +await_finish(Evs) -> + await_finish(Evs, []). + +await_finish([], []) -> + ok; +await_finish([], Fails) -> + ?SEV_EPRINT("Fails: " + "~n ~p", [Fails]), + Fails; +await_finish(Evs, Fails) -> + receive + %% Successfull termination of evaluator + {'DOWN', _MRef, process, Pid, normal} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + iprint("evaluator '~s' (~p) success", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_finish(NewEvs, Fails); + false -> + iprint("unknown process ~p died (normal)", [Pid]), + await_finish(Evs, Fails) + end; + + %% The evaluator can skip the teat case: + {'DOWN', _MRef, process, Pid, {skip, Reason}} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + iprint("evaluator '~s' (~p) issued SKIP: " + "~n ~p", [Name, Pid, Reason]); + false -> + iprint("unknown process ~p issued SKIP: " + "~n ~p", [Pid, Reason]) + end, + ?LIB:skip(Reason); + + %% Evaluator failed + {'DOWN', _MRef, process, Pid, Reason} -> + case lists:keysearch(Pid, #ev.pid, Evs) of + {value, #ev{name = Name}} -> + iprint("evaluator '~s' (~p) failed", [Name, Pid]), + NewEvs = lists:keydelete(Pid, #ev.pid, Evs), + await_finish(NewEvs, [{Pid, Reason}|Fails]); + false -> + iprint("unknown process ~p died: " + "~n ~p", [Pid, Reason]), + await_finish(Evs, Fails) + end + end. + + +%% ============================================================================ + +-spec announce_start(To) -> ok when + To :: pid(). + +announce_start(To) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN). + +-spec announce_start(To, Extra) -> ok when + To :: pid(), + Extra :: term(). + +announce_start(To, Extra) -> + announce(To, ?ANNOUNCEMENT_START, ?START_SLOGAN, Extra). + + +%% ============================================================================ + +-spec announce_continue(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_continue(To, Slogan) -> + announce_continue(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_continue(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_continue(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_CONTINUE, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_ready(To, Slogan) -> ok when + To :: pid(), + Slogan :: atom(). + +announce_ready(To, Slogan) -> + announce_ready(To, Slogan, ?EXTRA_NOTHING). + +-spec announce_ready(To, Slogan, Extra) -> ok when + To :: pid(), + Slogan :: atom(), + Extra :: term(). + +announce_ready(To, Slogan, Extra) -> + announce(To, ?ANNOUNCEMENT_READY, Slogan, Extra). + + +%% ============================================================================ + +-spec announce_terminate(To) -> ok when + To :: pid(). + +announce_terminate(To) -> + announce(To, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN). + + +%% ============================================================================ + +-spec announce(To, Announcement, Slogan) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(). + +announce(To, Announcement, Slogan) -> + announce(To, Announcement, Slogan, ?EXTRA_NOTHING). + +-spec announce(To, Announcement, Slogan, Extra) -> ok when + To :: pid(), + Announcement :: atom(), + Slogan :: atom(), + Extra :: term(). + +announce(To, Announcement, Slogan, Extra) + when is_pid(To) andalso + is_atom(Announcement) andalso + is_atom(Slogan) -> + %% iprint("announce -> entry with: " + %% "~n To: ~p" + %% "~n Announcement: ~p" + %% "~n Slogan: ~p" + %% "~n Extra: ~p", + %% [To, Announcement, Slogan, Extra]), + To ! {Announcement, self(), Slogan, Extra}, + ok. + + + +%% ============================================================================ + +-spec await_start() -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start() -> + await_start(any). + +-spec await_start(Pid) -> Pid | {Pid, Extra} when + Pid :: pid(), + Extra :: term(). + +await_start(P) when is_pid(P) orelse (P =:= any) -> + case await(P, ?START_NAME_NONE, ?ANNOUNCEMENT_START, ?START_SLOGAN, []) of + {ok, Any} when is_pid(P) -> + Any; + {ok, Pid} when is_pid(Pid) andalso (P =:= any) -> + Pid; + {ok, {Pid, _} = OK} when is_pid(Pid) andalso (P =:= any) -> + OK + end. + + +%% ============================================================================ + +-spec await_continue(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan) -> + await_continue(From, Name, Slogan, []). + +-spec await_continue(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_continue(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_CONTINUE, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_ready(From, Name, Slogan) -> ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan) -> + await_ready(From, Name, Slogan, []). + +-spec await_ready(From, Name, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + From :: pid(), + Name :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await_ready(From, Name, Slogan, OtherPids) + when is_pid(From) andalso + is_atom(Name) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + await(From, Name, ?ANNOUNCEMENT_READY, Slogan, OtherPids). + + + +%% ============================================================================ + +-spec await_terminate(Pid, Name) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + Reason :: term(). + +await_terminate(Pid, Name) when is_pid(Pid) andalso is_atom(Name) -> + await_terminate(Pid, Name, []). + +-spec await_terminate(Pid, Name, OtherPids) -> ok | {error, Reason} when + Pid :: pid(), + Name :: atom(), + OtherPids :: [{pid(), atom()}], + Reason :: term(). + +await_terminate(Pid, Name, OtherPids) -> + await(Pid, Name, ?ANNOUNCEMENT_TERMINATE, ?TERMINATE_SLOGAN, OtherPids). + + +%% ============================================================================ + +-spec await_termination(Pid) -> ok | {error, Reason} when + Pid :: pid(), + Reason :: term(). + +await_termination(Pid) when is_pid(Pid) -> + await_termination(Pid, any). + +-spec await_termination(Pid, ExpReason) -> ok | {error, Reason} when + Pid :: pid(), + ExpReason :: term(), + Reason :: term(). + +await_termination(Pid, ExpReason) -> + receive + {'DOWN', _, process, Pid, _} when (ExpReason =:= any) -> + ok; + {'DOWN', _, process, Pid, Reason} when (ExpReason =:= Reason) -> + ok; + {'DOWN', _, process, Pid, Reason} -> + {error, {unexpected_exit, ExpReason, Reason}} + end. + + +%% ============================================================================ + +%% We expect a message (announcement) from Pid, but we also watch for DOWN from +%% both Pid and OtherPids, in which case the test has failed! + +-spec await(ExpPid, Name, Announcement, Slogan, OtherPids) -> + ok | {ok, Extra} | {error, Reason} when + ExpPid :: any | pid(), + Name :: atom(), + Announcement :: atom(), + Slogan :: atom(), + OtherPids :: [{pid(), atom()}], + Extra :: term(), + Reason :: term(). + +await(ExpPid, Name, Announcement, Slogan, OtherPids) + when (is_pid(ExpPid) orelse (ExpPid =:= any)) andalso + is_atom(Name) andalso + is_atom(Announcement) andalso + is_atom(Slogan) andalso + is_list(OtherPids) -> + receive + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (ExpPid =:= any) -> + {ok, Pid}; + {Announcement, Pid, Slogan, Extra} when (ExpPid =:= any) -> + {ok, {Pid, Extra}}; + {Announcement, Pid, Slogan, ?EXTRA_NOTHING} when (Pid =:= ExpPid) -> + ok; + {Announcement, Pid, Slogan, Extra} when (Pid =:= ExpPid) -> + {ok, Extra}; + {'DOWN', _, process, Pid, {skip, SkipReason}} when (Pid =:= ExpPid) -> + iprint("Unexpected SKIP from ~w (~p): " + "~n ~p", [Name, Pid, SkipReason]), + ?LIB:skip({Name, SkipReason}); + {'DOWN', _, process, Pid, Reason} when (Pid =:= ExpPid) -> + eprint("Unexpected DOWN from ~w (~p): " + "~n ~p", [Name, Pid, Reason]), + {error, {unexpected_exit, Name}}; + {'DOWN', _, process, OtherPid, Reason} -> + case check_down(OtherPid, Reason, OtherPids) of + ok -> + iprint("DOWN from unknown process ~p: " + "~n ~p", [OtherPid, Reason]), + await(ExpPid, Name, Announcement, Slogan, OtherPids); + {error, _} = ERROR -> + ERROR + end + after infinity -> % For easy debugging, just change to some valid time (5000) + iprint("await -> timeout for msg from ~p (~w): " + "~n Announcement: ~p" + "~n Slogan: ~p" + "~nwhen" + "~n Messages: ~p", + [ExpPid, Name, Announcement, Slogan, pi(messages)]), + await(ExpPid, Name, Announcement, Slogan, OtherPids) + end. + +pi(Item) -> + pi(self(), Item). + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + +check_down(Pid, DownReason, Pids) -> + case lists:keymember(Pid, 1, Pids) of + {value, {_, Name}} -> + eprint("Unexpected DOWN from ~w (~p): " + "~n ~p", [Name, Pid, DownReason]), + {error, {unexpected_exit, Name}}; + false -> + ok + end. + + +%% ============================================================================ + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +iprint(F, A) -> + print("", F, A). + +eprint(F, A) -> + print("<ERROR> ", F, A). + +print(Prefix, F, A) -> + %% The two prints is to get the output both in the shell (for when + %% "personal" testing is going on) and in the logs. + IDStr = + case get(sname) of + undefined -> + %% This means its not an evaluator, + %% or a named process. Instead its + %% most likely the test case itself, + %% so skip the name and the pid. + ""; + SName -> + f("[~s][~p]", [SName, self()]) + end, + ?LOGGER:format("[~s]~s ~s" ++ F, + [?LIB:formated_timestamp(), IDStr, Prefix | A]). diff --git a/erts/emulator/test/socket_test_evaluator.hrl b/erts/emulator/test/socket_test_evaluator.hrl new file mode 100644 index 0000000000..5be49dc022 --- /dev/null +++ b/erts/emulator/test/socket_test_evaluator.hrl @@ -0,0 +1,68 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_evaluator). +-define(socket_test_evaluator, true). + +-record(ev, {name :: string(), + pid :: pid(), + mref :: reference()}). + +-define(SEV, socket_test_evaluator). + +-define(SEV_START(N, S, IS), ?SEV:start(N, S, IS)). +-define(SEV_AWAIT_FINISH(Evs), ?SEV:await_finish(Evs)). + +-define(SEV_ANNOUNCE_START(To), ?SEV:announce_start(To)). +-define(SEV_ANNOUNCE_START(To, Ex), ?SEV:announce_start(To, Ex)). +-define(SEV_ANNOUNCE_CONTINUE(To, S), ?SEV:announce_continue(To, S)). +-define(SEV_ANNOUNCE_CONTINUE(To, S, Ex), ?SEV:announce_continue(To, S, Ex)). +-define(SEV_ANNOUNCE_READY(To, S), ?SEV:announce_ready(To, S)). +-define(SEV_ANNOUNCE_READY(To, S, Ex), ?SEV:announce_ready(To, S, Ex)). +-define(SEV_ANNOUNCE_TERMINATE(To), ?SEV:announce_terminate(To)). + +-define(SEV_AWAIT_START(), ?SEV:await_start()). +-define(SEV_AWAIT_START(P), ?SEV:await_start(P)). +-define(SEV_AWAIT_CONTINUE(F, N, S), ?SEV:await_continue(F, N, S)). +-define(SEV_AWAIT_CONTINUE(F, N, S, Ps), ?SEV:await_continue(F, N, S, Ps)). +-define(SEV_AWAIT_READY(F, N, S), ?SEV:await_ready(F, N, S)). +-define(SEV_AWAIT_READY(F, N, S, Ps), ?SEV:await_ready(F, N, S, Ps)). +-define(SEV_AWAIT_TERMINATE(F, N), ?SEV:await_terminate(F, N)). +-define(SEV_AWAIT_TERMINATE(F, N, Ps), ?SEV:await_terminate(F, N, Ps)). +-define(SEV_AWAIT_TERMINATION(P), ?SEV:await_termination(P)). +-define(SEV_AWAIT_TERMINATION(P, R), ?SEV:await_termination(P, R)). + +-define(SEV_IPRINT(F, A), ?SEV:iprint(F, A)). +-define(SEV_IPRINT(F), ?SEV_IPRINT(F, [])). +-define(SEV_EPRINT(F, A), ?SEV:eprint(F, A)). +-define(SEV_EPRINT(F), ?SEV_EPRINT(F, [])). + +-define(SEV_SLEEP(T), #{desc => "sleep", + cmd => fun(_) -> + ?SLEEP(T), + ok + end}). +-define(SEV_FINISH_NORMAL, #{desc => "finish", + cmd => fun(_) -> + {ok, normal} + end}). + +-endif. % -ifdef(socket_test_evaluator). + diff --git a/erts/emulator/test/socket_test_lib.erl b/erts/emulator/test/socket_test_lib.erl new file mode 100644 index 0000000000..4e65c4f3c0 --- /dev/null +++ b/erts/emulator/test/socket_test_lib.erl @@ -0,0 +1,98 @@ +%% +%% %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_test_lib). + +-export([ + pi/1, pi/2, pi/3, + + %% Time stuff + timestamp/0, + tdiff/2, + formated_timestamp/0, + format_timestamp/1, + + %% String and format + f/2, + + %% Skipping + not_yet_implemented/0, + skip/1 + ]). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +pi(Item) when is_atom(Item) -> + pi(self(), Item). + +pi(Pid, Item) when is_pid(Pid) andalso is_atom(Item) -> + {Item, Info} = process_info(Pid, Item), + Info; +pi(Node, Pid) when is_pid(Pid) -> + rpc:call(Node, erlang, process_info, [Pid]). + +pi(Node, Pid, Item) when is_pid(Pid) andalso is_atom(Item) -> + rpc:call(Node, erlang, process_info, [Pid, Item]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +timestamp() -> + os:timestamp(). + + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, _N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + %% {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + %% FormatTS = + %% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w.~w", + %% [YYYY, MM, DD, Hour, Min, Sec, N3]), + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w", [Hour, Min, Sec]), + lists:flatten(FormatTS). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +not_yet_implemented() -> + skip("not yet implemented"). + +skip(Reason) -> + throw({skip, Reason}). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/erts/emulator/test/socket_test_logger.erl b/erts/emulator/test/socket_test_logger.erl new file mode 100644 index 0000000000..e42251158f --- /dev/null +++ b/erts/emulator/test/socket_test_logger.erl @@ -0,0 +1,111 @@ +%% +%% %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_test_logger). + +-export([ + start/0, + stop/0, + format/2 + ]). + + +-define(LIB, socket_test_lib). +-define(LOGGER, ?MODULE). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start() -> + case global:whereis_name(?LOGGER) of + Pid when is_pid(Pid) -> + ok; + undefined -> + Self = self(), + Pid = spawn_link(fun() -> init(Self) end), + yes = global:register_name(?LOGGER, Pid), + ok + end. + + +stop() -> + case global:whereis_name(?LOGGER) of + undefined -> + ok; + Pid when is_pid(Pid) -> + global:unregister_name(?LOGGER), + Pid ! {?LOGGER, '$logger', stop}, + ok + end. + + +format(F, []) -> + do_format(F); +format(F, A) -> + do_format(?LIB:f(F, A)). + +do_format(Msg) -> + case global:whereis_name(?LOGGER) of + undefined -> + ok; + Pid when is_pid(Pid) -> + Pid ! {?MODULE, '$logger', {msg, Msg}}, + ok + end. + +init(Parent) -> + put(sname, "logger"), + print("[~s][logger] starting~n", [?LIB:formated_timestamp()]), + loop(#{parent => Parent}). + +loop(#{parent := Parent} = State) -> + receive + {'EXIT', Parent, _} -> + print("[~s][logger] parent exit~n", [?LIB:formated_timestamp()]), + exit(normal); + + {?MODULE, '$logger', stop} -> + print("[~s][logger] stopping~n", [?LIB:formated_timestamp()]), + exit(normal); + + {?MODULE, '$logger', {msg, Msg}} -> + print(Msg), + loop(State) + end. + + +print(F, A) -> + print(?LIB:f(F, A)). + +print(Str) -> + try + begin + io:format(user, Str ++ "~n", []), + io:format(Str, []) + end + catch + _:_:_ -> + io:format(user, + "~nFailed Format message:" + "~n~p~n", [Str]), + io:format("~nFailed Format message:" + "~n~p~n", [Str]) + end. + diff --git a/erts/emulator/test/socket_test_ttest.hrl b/erts/emulator/test/socket_test_ttest.hrl new file mode 100644 index 0000000000..1a004a9a7a --- /dev/null +++ b/erts/emulator/test/socket_test_ttest.hrl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_ttest). +-define(socket_test_ttest, true). + +-define(TTEST_TAG, 42). +-define(TTEST_TYPE_REQUEST, 101). +-define(TTEST_TYPE_REPLY, 102). + +-define(SECS(I), timer:seconds(I)). + +-define(SLEEP(T), receive after T -> ok end). + +-endif. % -ifdef(socket_test_ttest). diff --git a/erts/emulator/test/socket_test_ttest_client.hrl b/erts/emulator/test/socket_test_ttest_client.hrl new file mode 100644 index 0000000000..84e736cc34 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_client.hrl @@ -0,0 +1,141 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(socket_test_ttest_client). +-define(socket_test_ttest_client, true). + +-define(MSG_ID_DEFAULT, 2). +-define(RUNTIME_DEFAULT, ?SECS(10)). +-define(MAX_ID, 16#FFFFFFFF). + +-define(MSG_DATA1, <<"This is test data 0123456789 0123456789 0123456789">>). +-define(MSG_DATA2, <<"This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789">>). +-define(MSG_DATA3, <<"This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789" + "This is test data 0123456789 0123456789 0123456789">>). + + +-endif. % -ifdef(socket_test_ttest_client). diff --git a/erts/emulator/test/socket_test_ttest_lib.erl b/erts/emulator/test/socket_test_ttest_lib.erl new file mode 100644 index 0000000000..71679bc6d6 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_lib.erl @@ -0,0 +1,125 @@ +%% +%% %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_test_ttest_lib). + +-compile({no_auto_import, [error/2]}). + +-export([ + t/0, tdiff/2, + formated_timestamp/0, format_timestamp/1, + format_time/1, + + formated_process_stats/1, formated_process_stats/2, + + format/2, + error/1, error/2, + info/1, info/2 + ]). + +%% ========================================================================== + +t() -> + os:timestamp(). + +tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> + T1 = A1*1000000000+B1*1000+(C1 div 1000), + T2 = A2*1000000000+B2*1000+(C2 div 1000), + T2 - T1. + +formated_timestamp() -> + format_timestamp(os:timestamp()). + +format_timestamp({_N1, _N2, N3} = TS) -> + {_Date, Time} = calendar:now_to_local_time(TS), + {Hour,Min,Sec} = Time, + FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", + [Hour, Min, Sec, round(N3/1000)]), + lists:flatten(FormatTS). + +%% Time is always in number os ms (milli seconds) +%% At some point, we should convert this to a more readable format... +format_time(T) -> + format("~p", [T]). + + +formated_process_stats(Pid) -> + formated_process_stats("", Pid). + +formated_process_stats(Prefix, Pid) when is_list(Prefix) andalso is_pid(Pid) -> + try + begin + TotHeapSz = pi(Pid, total_heap_size), + HeapSz = pi(Pid, heap_size), + StackSz = pi(Pid, stack_size), + Reds = pi(Pid, reductions), + GCInfo = pi(Pid, garbage_collection), + MinBinVHeapSz = proplists:get_value(min_bin_vheap_size, GCInfo), + MinHeapSz = proplists:get_value(min_heap_size, GCInfo), + MinGCS = proplists:get_value(minor_gcs, GCInfo), + format("~n ~sTotal Heap Size: ~p" + "~n ~sHeap Size: ~p" + "~n ~sStack Size: ~p" + "~n ~sReductions: ~p" + "~n ~s[GC] Min Bin VHeap Size: ~p" + "~n ~s[GC] Min Heap Size: ~p" + "~n ~s[GC] Minor GCS: ~p", + [Prefix, TotHeapSz, + Prefix, HeapSz, + Prefix, StackSz, + Prefix, Reds, + Prefix, MinBinVHeapSz, + Prefix, MinHeapSz, + Prefix, MinGCS]) + end + catch + _:_:_ -> + "" + end. + + +pi(Pid, Item) -> + {Item, Info} = process_info(Pid, Item), + Info. + + + +%% ========================================================================== + +format(F, A) -> + lists:flatten(io_lib:format(F, A)). + +error(F) -> + error(F, []). + +error(F, A) -> + print(get(sname), "<ERROR> " ++ F, A). + +info(F) -> + info(F, []). + +info(F, A) -> + print(get(sname), "<INFO> " ++ F, A). + +print(undefined, F, A) -> + print("- ", F, A); +print(Prefix, F, A) -> + io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). + diff --git a/erts/emulator/test/socket_test_ttest_tcp_client.erl b/erts/emulator/test/socket_test_ttest_tcp_client.erl new file mode 100644 index 0000000000..bf34a8c2ab --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_client.erl @@ -0,0 +1,671 @@ +%% +%% %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% +%% + +%% ========================================================================== +%% +%% This is the "simple" client using gen_tcp. The client is supposed to be +%% as simple as possible in order to incur as little overhead as possible. +%% +%% There are three ways to run the client: active, passive or active-once. +%% +%% The client is the entity that controls the test, timing and counting. +%% +%% ========================================================================== +%% +%% Before the actual test starts, the client performs a "warmup". +%% The warmup has two functions. First, to ensure that everything is "loaded" +%% and, second, to calculate an approximate roundtrip time, in order to +%% "know" how many iterations we should make (to run for the expected time). +%% This is not intended to be exact, but just to ensure that all tests take +%% approx the same time to run. +%% +%% ========================================================================== + +-module(socket_test_ttest_tcp_client). + +-export([ + %% These are for the test suite + start_monitor/6, start_monitor/7, start_monitor/9, + + %% These are for starting in a shell when run "manually" + start/4, start/5, start/7, + stop/1 + ]). + +%% Internal exports +-export([ + do_start/9 + ]). + +-include_lib("kernel/include/inet.hrl"). +-include("socket_test_ttest.hrl"). +-include("socket_test_ttest_client.hrl"). + +-define(RECV_TIMEOUT, 10000). +-define(MAX_OUTSTANDING_DEFAULT_1, 100). +-define(MAX_OUTSTANDING_DEFAULT_2, 10). +-define(MAX_OUTSTANDING_DEFAULT_3, 3). + +-define(LIB, socket_test_ttest_lib). +-define(I(F), ?LIB:info(F)). +-define(I(F,A), ?LIB:info(F, A)). +-define(E(F,A), ?LIB:error(F, A)). +-define(F(F,A), ?LIB:format(F, A)). +-define(FORMAT_TIME(T), ?LIB:format_time(T)). +-define(T(), ?LIB:t()). +-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)). + +-type active() :: once | boolean(). +-type msg_id() :: 1..3. +-type max_outstanding() :: pos_integer(). +-type runtime() :: pos_integer(). + + +%% ========================================================================== + +start_monitor(Node, Notify, Transport, Active, Addr, Port) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, ?MSG_ID_DEFAULT). + +start_monitor(Node, Notify, Transport, Active, Addr, Port, 1 = MsgID) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start_monitor(Node, Notify, Transport, Active, Addr, Port, 2 = MsgID) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start_monitor(Node, Notify, Transport, Active, Addr, Port, 3 = MsgID) -> + start_monitor(Node, Notify, Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +start_monitor(Node, Notify, Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) + when (Node =/= node()) -> + %% ?I("start_monitor -> entry with" + %% "~n Node: ~p" + %% "~n Transport: ~p" + %% "~n Active: ~p" + %% "~n Addr: ~p" + %% "~n Port: ~p" + %% "~n MsgID: ~p" + %% "~n MaxOutstanding: ~p" + %% "~n RunTime: ~p", + %% [Node, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), + Args = [self(), Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime], + case rpc:call(Node, ?MODULE, do_start, Args) of + {badrpc, _} = Reason -> + {error, Reason}; + {ok, Pid} when is_pid(Pid) -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef}}; + {error, _} = ERROR -> + ERROR + end; +start_monitor(_, Notify, Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) -> + case do_start(self(), Notify, + Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) of + {ok, Pid} -> + MRef = erlang:monitor(process, Pid), + {ok, {Pid, MRef}}; + {error, _} = ERROR -> + ERROR + end. + + +start(Transport, Active, Addr, Port) -> + start(Transport, Active, Addr, Port, ?MSG_ID_DEFAULT). + +start(Transport, Active, Addr, Port, 1 = MsgID) -> + start(Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_1, ?RUNTIME_DEFAULT); +start(Transport, Active, Addr, Port, 2 = MsgID) -> + start(Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_2, ?RUNTIME_DEFAULT); +start(Transport, Active, Addr, Port, 3 = MsgID) -> + start(Transport, Active, Addr, Port, MsgID, + ?MAX_OUTSTANDING_DEFAULT_3, ?RUNTIME_DEFAULT). + +start(Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + Notify = fun(R) -> present_results(R) end, + do_start(self(), Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime). + + +-spec do_start(Parent, + Notify, + Transport, + Active, + Addr, + Port, + MsgID, + MaxOutstanding, + RunTime) -> {ok, Pid} | {error, Reason} when + Parent :: pid(), + Notify :: function(), + Transport :: atom() | tuple(), + Active :: active(), + Addr :: inet:ip_address(), + Port :: inet:port_number(), + MsgID :: msg_id(), + MaxOutstanding :: max_outstanding(), + RunTime :: runtime(), + Pid :: pid(), + Reason :: term(). + +do_start(Parent, Notify, + Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) + when is_pid(Parent) andalso + is_function(Notify) andalso + (is_atom(Transport) orelse is_tuple(Transport)) andalso + (is_boolean(Active) orelse (Active =:= once)) andalso + is_tuple(Addr) andalso + (is_integer(Port) andalso (Port > 0)) andalso + (is_integer(MsgID) andalso (MsgID >= 1) andalso (MsgID =< 3)) andalso + (is_integer(MaxOutstanding) andalso (MaxOutstanding > 0)) andalso + (is_integer(RunTime) andalso (RunTime > 0)) -> + %% ?I("do_start -> entry with" + %% "~n Parent: ~p" + %% "~n Transport: ~p" + %% "~n Active: ~p" + %% "~n Addr: ~p" + %% "~n Port: ~p" + %% "~n MsgID: ~p" + %% "~n MaxOutstanding: ~p" + %% "~n RunTime: ~p", + %% [Parent, Transport, Active, Addr, Port, MsgID, MaxOutstanding, RunTime]), + Starter = self(), + Init = fun() -> put(sname, "client"), + init(Starter, + Parent, + Notify, + Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) + end, + {Pid, MRef} = spawn_monitor(Init), + receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; + {?MODULE, Pid, ok} -> + erlang:demonitor(MRef), + {ok, Pid}; + {?MODULE, Pid, {error, _} = ERROR} -> + erlang:demonitor(MRef, [flush]), + ERROR + end. + + +%% We should not normally stop this (it terminates when its done). +stop(Pid) when is_pid(Pid) -> + req(Pid, stop). + + +%% ========================================================================== + +init(Starter, + Parent, Notify, + Transport, Active, Addr, Port, + MsgID, MaxOutstanding, RunTime) -> + ?I("init with" + "~n Transport: ~p" + "~n Active: ~p" + "~n Addr: ~s" + "~n Port: ~p" + "~n Msg ID: ~p (=> 16 + ~w bytes)" + "~n Max Outstanding: ~p" + "~n (Suggested) Run Time: ~p ms", + [Transport, Active, inet:ntoa(Addr), Port, + MsgID, size(which_msg_data(MsgID)), MaxOutstanding, RunTime]), + {Mod, Connect} = process_transport(Transport), + case Connect(Addr, Port) of + {ok, Sock} -> + ?I("connected"), + Starter ! {?MODULE, self(), ok}, + initial_activation(Mod, Sock, Active), + Results = loop(#{slogan => run, + runtime => RunTime, + start => ?T(), + parent => Parent, + mod => Mod, + sock => Sock, + active => Active, + msg_data => which_msg_data(MsgID), + outstanding => 0, + max_outstanding => MaxOutstanding, + sid => 1, + rid => 1, + scnt => 0, + rcnt => 0, + bcnt => 0, + num => undefined, + acc => <<>>}), + Notify(Results), + (catch Mod:close(Sock)), + exit(normal); + {error, Reason} -> + ?E("connect failed: ~p", [Reason]), + exit({connect, Reason}) + end. + +process_transport(Mod) when is_atom(Mod) -> + {Mod, fun(A, P) -> Mod:connect(A, P) end}; +process_transport({Mod, Opts}) -> + {Mod, fun(A, P) -> Mod:connect(A, P, Opts) end}. + + +which_msg_data(1) -> ?MSG_DATA1; +which_msg_data(2) -> ?MSG_DATA2; +which_msg_data(3) -> ?MSG_DATA3. + + +present_results(#{status := ok, + runtime := RunTime, + bcnt := ByteCnt, + cnt := NumIterations}) -> + ?I("Results: " + "~n Run Time: ~s" + "~n ByteCnt: ~s" + "~n NumIterations: ~s", + [?FORMAT_TIME(RunTime), + if ((ByteCnt =:= 0) orelse (RunTime =:= 0)) -> + ?F("~w, ~w", [ByteCnt, RunTime]); + true -> + ?F("~p => ~p byte / ms", [ByteCnt, ByteCnt div RunTime]) + end, + if (RunTime =:= 0) -> + "-"; + true -> + ?F("~p => ~p iterations / ms", + [NumIterations, NumIterations div RunTime]) + end]), + ok; +present_results(#{status := Failure, + runtime := RunTime, + sid := SID, + rid := RID, + scnt := SCnt, + rcnt := RCnt, + bcnt := BCnt, + num := Num}) -> + ?I("Time Test failed: " + "~n ~p" + "~n" + "~nwhen" + "~n" + "~n Run Time: ~s" + "~n Send ID: ~p" + "~n Recv ID: ~p" + "~n Send Count: ~p" + "~n Recv Count: ~p" + "~n Byte Count: ~p" + "~n Num Iterations: ~p", + [Failure, + ?FORMAT_TIME(RunTime), + SID, RID, SCnt, RCnt, BCnt, Num]). + + + +loop(#{runtime := RunTime} = State) -> + erlang:start_timer(RunTime, self(), stop), + try do_loop(State) + catch + throw:Results -> + Results + end. + +do_loop(State) -> + do_loop( handle_message( msg_exchange(State) ) ). + +msg_exchange(#{rcnt := Num, num := Num} = State) -> + finish(ok, State); +msg_exchange(#{scnt := Num, num := Num} = State) -> + %% We are done sending more requests - now we will just await + %% the replies for the (still) outstanding replies. + msg_exchange( recv_reply(State) ); +msg_exchange(#{outstanding := Outstanding, + max_outstanding := MaxOutstanding} = State) + when (Outstanding < MaxOutstanding) -> + msg_exchange( send_request(State) ); +msg_exchange(State) -> + send_request( recv_reply(State) ). + + +finish(ok, + #{start := Start, bcnt := BCnt, num := Num}) -> + Stop = ?T(), + throw(#{status => ok, + runtime => ?TDIFF(Start, Stop), + bcnt => BCnt, + cnt => Num}); +finish(Reason, + #{start := Start, + sid := SID, rid := RID, + scnt := SCnt, rcnt := RCnt, bcnt := BCnt, + num := Num}) -> + Stop = ?T(), + throw(#{status => Reason, + runtime => ?TDIFF(Start, Stop), + sid => SID, + rid => RID, + scnt => SCnt, + rcnt => RCnt, + bcnt => BCnt, + num => Num}). + +send_request(#{mod := Mod, + sock := Sock, + sid := ID, + scnt := Cnt, + outstanding := Outstanding, + max_outstanding := MaxOutstanding, + msg_data := Data} = State) + when (MaxOutstanding > Outstanding) -> + SZ = size(Data), + Req = <<?TTEST_TAG:32, + ?TTEST_TYPE_REQUEST:32, + ID:32, + SZ:32, + Data/binary>>, + case Mod:send(Sock, Req) of + ok -> + State#{sid => next_id(ID), + scnt => Cnt + 1, + outstanding => Outstanding + 1}; + {error, Reason} -> + ?E("Failed sending request: ~p", [Reason]), + exit({send, Reason}) + end; +send_request(State) -> + State. + + + +recv_reply(#{mod := Mod, + sock := Sock, + rid := ID, + active := false, + bcnt := BCnt, + rcnt := Cnt, + outstanding := Outstanding} = State) -> + case recv_reply_message1(Mod, Sock, ID) of + {ok, MsgSz} -> + State#{rid => next_id(ID), + bcnt => BCnt + MsgSz, + rcnt => Cnt + 1, + outstanding => Outstanding - 1}; + + {error, timeout} -> + ?I("receive timeout"), + State; + + {error, Reason} -> + finish(Reason, State) + end; +recv_reply(#{mod := Mod, + sock := Sock, + rid := ID, + active := Active, + bcnt := BCnt, + scnt := SCnt, + rcnt := RCnt, + outstanding := Outstanding, + acc := Acc} = State) -> + case recv_reply_message2(Mod, Sock, ID, Acc) of + {ok, {MsgSz, NewAcc}} when is_integer(MsgSz) andalso is_binary(NewAcc) -> + maybe_activate(Mod, Sock, Active), + State#{rid => next_id(ID), + bcnt => BCnt + MsgSz, + rcnt => RCnt + 1, + outstanding => Outstanding - 1, + acc => NewAcc}; + + ok -> + State; + + {error, stop} -> + ?I("receive [~w] -> stop", [Active]), + %% This will have the effect that no more requests are sent... + State#{num => SCnt, stop_started => ?T()}; + + {error, timeout} -> + ?I("receive[~w] -> timeout", [Active]), + State; + + {error, Reason} -> + finish(Reason, State) + end. + + +%% This function reads exactly one (reply) message. No more no less. +recv_reply_message1(Mod, Sock, ID) -> + case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of + {ok, <<?TTEST_TAG:32, + ?TTEST_TYPE_REPLY:32, + ID:32, + SZ:32>> = Hdr} -> + %% Receive the ping-pong reply boby + case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of + {ok, Data} when (size(Data) =:= SZ) -> + {ok, size(Hdr) + size(Data)}; + {error, Reason2} -> + ?E("Failed reading body: " + "~n ~p: ~p", [Reason2]), + {error, {recv_body, Reason2}} + end; + + {ok, <<BadTag:32, + BadType:32, + BadID:32, + BadSZ:32>>} -> + {error, {invalid_hdr, + {?TTEST_TAG, BadTag}, + {?TTEST_TYPE_REPLY, BadType}, + {ID, BadID}, + BadSZ}}; + {ok, _InvHdr} -> + {error, invalid_hdr}; + + {error, Reason1} -> + ?E("Feiled reading header: " + "~n ~p", [Reason1]), + {error, {recv_hdr, Reason1}} + end. + + +%% This function first attempts to process the data we have already +%% accumulated. If that is not enough for a (complete) reply, it +%% will attempt to receive more. +recv_reply_message2(Mod, Sock, ID, Acc) -> + case process_acc_data(ID, Acc) of + ok -> + %% No or insufficient data, so get more + recv_reply_message3(Mod, Sock, ID, Acc); + + {ok, _} = OK -> % We already had a reply accumulated - no need to read more + OK; + + {error, _} = ERROR -> + ERROR + end. + +%% This function receives a "chunk" of data, then it tries to extract +%% one (reply) message from the accumulated and new data (combined). +recv_reply_message3(_Mod, Sock, ID, Acc) -> + receive + {timeout, _TRef, stop} -> + {error, stop}; + + {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + {error, closed}; + + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + {error, Reason}; + + {Tag, Sock, Msg} when (Tag =:= tcp) orelse + (Tag =:= socket) -> + process_acc_data(ID, <<Acc/binary, Msg/binary>>) + + after ?RECV_TIMEOUT -> + ?I("timeout when" + "~n ID: ~p" + "~n size(Acc): ~p", + [ID, size(Acc)]), + %% {error, timeout} + recv_reply_message3(_Mod, Sock, ID, Acc) + end. + + +process_acc_data(ID, <<?TTEST_TAG:32, + ?TTEST_TYPE_REPLY:32, + ID:32, + SZ:32, + Data/binary>>) when (SZ =< size(Data)) -> + <<_Body:SZ/binary, Rest/binary>> = Data, + {ok, {4*4+SZ, Rest}}; +process_acc_data(ID, <<BadTag:32, + BadType:32, + BadID:32, + BadSZ:32, + _Data/binary>>) + when ((BadTag =/= ?TTEST_TAG) orelse + (BadType =/= ?TTEST_TYPE_REPLY) orelse + (BadID =/= ID)) -> + {error, {invalid_hdr, + {?TTEST_TAG, BadTag}, + {?TTEST_TYPE_REPLY, BadType}, + {ID, BadID}, + BadSZ}}; +%% Not enough for an entire (reply) message +process_acc_data(_ID, _Data) -> + ok. + + +handle_message(#{parent := Parent, sock := Sock, scnt := SCnt} = State) -> + receive + {timeout, _TRef, stop} -> + ?I("STOP"), + %% This will have the effect that no more requests are sent... + State#{num => SCnt, stop_started => ?T()}; + + {?MODULE, Ref, Parent, stop} -> + %% This *aborts* the test + reply(Parent, Ref, ok), + exit(normal); + + %% Only when active + {TagClosed, Sock, Reason} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + %% We should never get this (unless the server crashed) + exit({closed, Reason}); + + %% Only when active + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + exit({error, Reason}) + + after 0 -> + State + end. + + +initial_activation(_Mod, _Sock, false = _Active) -> + ok; +initial_activation(Mod, Sock, Active) -> + Mod:active(Sock, Active). + + +maybe_activate(Mod, Sock, once = Active) -> + Mod:active(Sock, Active); +maybe_activate(_, _, _) -> + ok. + + +%% ========================================================================== + +req(Pid, Req) -> + Ref = make_ref(), + Pid ! {?MODULE, Ref, Pid, Req}, + receive + {'EXIT', Pid, Reason} -> + {error, {exit, Reason}}; + {?MODULE, Ref, Reply} -> + Reply + end. + +reply(Pid, Ref, Reply) -> + Pid ! {?MODULE, Ref, Reply}. + + +%% ========================================================================== + +next_id(ID) when (ID < ?MAX_ID) -> + ID + 1; +next_id(_) -> + 1. + + +%% ========================================================================== + +%% t() -> +%% os:timestamp(). + +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp({_N1, _N2, N3} = TS) -> +%% {_Date, Time} = calendar:now_to_local_time(TS), +%% {Hour,Min,Sec} = Time, +%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", +%% [Hour, Min, Sec, round(N3/1000)]), +%% lists:flatten(FormatTS). + +%% %% Time is always in number os ms (milli seconds) +%% format_time(T) -> +%% f("~p", [T]). + + +%% ========================================================================== + +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). + +%% %% e(F) -> +%% %% i("<ERROR> " ++ F). + +%% e(F, A) -> +%% p(get(sname), "<ERROR> " ++ F, A). + +%% i(F) -> +%% i(F, []). + +%% i(F, A) -> +%% p(get(sname), "<INFO> " ++ F, A). + +%% p(undefined, F, A) -> +%% p("- ", F, A); +%% p(Prefix, F, A) -> +%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl new file mode 100644 index 0000000000..33eebfb10f --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_client_gen.erl @@ -0,0 +1,42 @@ +%% +%% %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_test_ttest_tcp_client_gen). + +-export([ + start/3, start/4, start/6, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). + +start(Active, Addr, Port) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, Active, Addr, Port). + +start(Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, Active, Addr, Port, MsgID). + +start(Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start(?TRANSPORT_MOD, + Active, Addr, Port, + MsgID, MaxOutstanding, RunTime). + +stop(Pid) -> + socket_test_ttest_tcp_client:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl new file mode 100644 index 0000000000..c0a3302ecd --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_client_socket.erl @@ -0,0 +1,44 @@ +%% +%% %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_test_ttest_tcp_client_socket). + +-export([ + start/4, start/5, start/7, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). +-define(MOD(M), {?TRANSPORT_MOD, #{method => Method}}). + +start(Method, Active, Addr, Port) -> + socket_test_ttest_tcp_client:start_monitor(?MOD(Method), Active, Addr, Port). + +start(Method, Active, Addr, Port, MsgID) -> + socket_test_ttest_tcp_client:start(?MOD(Method), + Active, Addr, Port, MsgID). + +start(Method, Active, Addr, Port, MsgID, MaxOutstanding, RunTime) -> + socket_test_ttest_tcp_client:start(?MOD(Method), + Active, Addr, Port, + MsgID, MaxOutstanding, RunTime). + +stop(Pid) -> + socket_test_ttest_client:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_gen.erl new file mode 100644 index 0000000000..604408c489 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_gen.erl @@ -0,0 +1,138 @@ +%% +%% %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_test_ttest_tcp_gen). + +-export([ + accept/1, accept/2, + active/2, + close/1, + connect/2, + controlling_process/2, + listen/0, listen/1, + peername/1, + port/1, + recv/2, recv/3, + send/2, + shutdown/2, + sockname/1 + ]). + + +%% ========================================================================== + +%% getopt(Sock, Opt) when is_atom(Opt) -> +%% case inet:getopts(Sock, [Opt]) of +%% {ok, [{Opt, Value}]} -> +%% {ok, Value}; +%% {error, _} = ERROR -> +%% ERROR +%% end. + +%% setopt(Sock, Opt, Value) when is_atom(Opt) -> +%% inet:setopts(Sock, [{Opt, Value}]). + + +%% ========================================================================== + +accept(Sock) -> + case gen_tcp:accept(Sock) of + {ok, NewSock} -> + {ok, NewSock}; + {error, _} = ERROR -> + ERROR + end. + +accept(Sock, Timeout) -> + case gen_tcp:accept(Sock, Timeout) of + {ok, NewSock} -> + {ok, NewSock}; + {error, _} = ERROR -> + ERROR + end. + + +active(Sock, NewActive) + when (is_boolean(NewActive) orelse (NewActive =:= once)) -> + inet:setopts(Sock, [{active, NewActive}]). + + +close(Sock) -> + gen_tcp:close(Sock). + + +connect(Addr, Port) -> + Opts = [binary, {packet, raw}, {active, false}, {buffer, 32*1024}], + case gen_tcp:connect(Addr, Port, Opts) of + {ok, Sock} -> + {ok, Sock}; + {error, _} = ERROR -> + ERROR + end. + +controlling_process(Sock, NewPid) -> + gen_tcp:controlling_process(Sock, NewPid). + + +%% Create a listen socket +listen() -> + listen(0). + +listen(Port) when is_integer(Port) andalso (Port >= 0) -> + Opts = [binary, {ip, {0,0,0,0}}, {packet, raw}, {active, false}, + {buffer, 32*1024}], + case gen_tcp:listen(Port, Opts) of + {ok, Sock} -> + {ok, Sock}; + {error, _} = ERROR -> + ERROR + end. + + +peername(Sock) -> + inet:peername(Sock). + + +port(Sock) -> + inet:port(Sock). + + +recv(Sock, Length) -> + gen_tcp:recv(Sock, Length). +recv(Sock, Length, Timeout) -> + gen_tcp:recv(Sock, Length, Timeout). + + +send(Sock, Data) -> + gen_tcp:send(Sock, Data). + + +shutdown(Sock, How) -> + gen_tcp:shutdown(Sock, How). + + +sockname(Sock) -> + inet:sockname(Sock). + + +%% ========================================================================== + + + diff --git a/erts/emulator/test/socket_test_ttest_tcp_server.erl b/erts/emulator/test/socket_test_ttest_tcp_server.erl new file mode 100644 index 0000000000..e8d626e3d8 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_server.erl @@ -0,0 +1,642 @@ +%% +%% %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% +%% + +%% ========================================================================== +%% +%% This is the "simple" server using gen_tcp. The server is supposed to be +%% as simple as possible in order to incur as little overhead as possible. +%% +%% There are three ways to run the server: active, passive or active-once. +%% +%% The server does only two things; accept connnections and then reply +%% to requests (actually the handler(s) does that). No timing or counting. +%% That is all done by the clients. +%% +%% ========================================================================== + +-module(socket_test_ttest_tcp_server). + +-export([ + %% This are for the test suite + start_monitor/3, + + %% This are for starting in a shell when run "manually" + start/2, + + stop/1 + ]). + +%% Internal exports +-export([ + do_start/3 + ]). + +-include_lib("kernel/include/inet.hrl"). +-include("socket_test_ttest.hrl"). + +-define(ACC_TIMEOUT, 5000). +-define(RECV_TIMEOUT, 5000). + +-define(LIB, socket_test_ttest_lib). +-define(I(F), ?LIB:info(F)). +-define(I(F,A), ?LIB:info(F, A)). +-define(E(F,A), ?LIB:error(F, A)). +-define(F(F,A), ?LIB:format(F, A)). +-define(FORMAT_TIME(T), ?LIB:format_time(T)). +-define(T(), ?LIB:t()). +-define(TDIFF(T1,T2), ?LIB:tdiff(T1, T2)). + + +%% ========================================================================== + +start_monitor(Node, Transport, Active) when (Node =/= node()) -> + case rpc:call(Node, ?MODULE, do_start, [self(), Transport, Active]) of + {badrpc, _} = Reason -> + {error, Reason}; + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {{Pid, MRef}, AddrPort}}; + {error, _} = ERROR -> + ERROR + end; +start_monitor(_, Transport, Active) -> + case do_start(self(), Transport, Active) of + {ok, {Pid, AddrPort}} -> + MRef = erlang:monitor(process, Pid), + {ok, {{Pid, MRef}, AddrPort}}; + {error, _} = ERROR -> + ERROR + end. + + + +start(Transport, Active) -> + do_start(self(), Transport, Active). + + +do_start(Parent, Transport, Active) + when is_pid(Parent) andalso + (is_atom(Transport) orelse is_tuple(Transport)) andalso + (is_boolean(Active) orelse (Active =:= once)) -> + Starter = self(), + ServerInit = fun() -> put(sname, "server"), + server_init(Starter, Parent, Transport, Active) + end, + {Pid, MRef} = spawn_monitor(ServerInit), + receive + {'DOWN', MRef, process, Pid, Reason} -> + {error, Reason}; + {?MODULE, Pid, {ok, AddrPort}} -> + erlang:demonitor(MRef), + {ok, {Pid, AddrPort}}; + {?MODULE, Pid, {error, _} = ERROR} -> + erlang:demonitor(MRef, [flush]), + ERROR + end. + + +stop(Pid) when is_pid(Pid) -> + req(Pid, stop). + + +%% ========================================================================== + +server_init(Starter, Parent, Transport, Active) -> + ?I("init with" + "~n Transport: ~p" + "~n Active: ~p", [Transport, Active]), + {Mod, Listen, StatsInterval} = process_transport(Transport, Active), + case Listen(0) of + {ok, LSock} -> + case Mod:port(LSock) of + {ok, Port} -> + Addr = which_addr(), % This is just for convenience + ?I("listening on:" + "~n Addr: ~p (~s)" + "~n Port: ~w" + "~n", [Addr, inet:ntoa(Addr), Port]), + Starter ! {?MODULE, self(), {ok, {Addr, Port}}}, + server_loop(#{parent => Parent, + mod => Mod, + active => Active, + lsock => LSock, + handlers => [], + stats_interval => StatsInterval, + %% Accumulation + runtime => 0, + mcnt => 0, + bcnt => 0, + hcnt => 0 + }); + {error, PReason} -> + (catch Mod:close(LSock)), + exit({port, PReason}) + end; + {error, LReason} -> + exit({listen, LReason}) + end. + +process_transport(Mod, _) when is_atom(Mod) -> + {Mod, fun(Port) -> Mod:listen(Port) end, infinity}; +process_transport({Mod, #{stats_interval := T} = Opts}, Active) + when (Active =/= false) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts#{stats_to => self()}) end, T}; +process_transport({Mod, #{stats_interval := T} = Opts}, _Active) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts) end, T}; +process_transport({Mod, Opts}, _Active) -> + {Mod, fun(Port) -> Mod:listen(Port, Opts) end, infinity}. + + + +server_loop(State) -> + server_loop( server_handle_message( server_accept(State) ) ). + +server_accept(#{mod := Mod, + active := Active, + lsock := LSock, + handlers := Handlers} = State) -> + case Mod:accept(LSock, ?ACC_TIMEOUT) of + {ok, Sock} -> + ?I("accepted connection from ~s", + [case Mod:peername(Sock) of + {ok, Peer} -> + format_peername(Peer); + {error, _} -> + "-" + end]), + {Pid, _} = handler_start(), + ?I("handler ~p started -> try transfer socket control", [Pid]), + case Mod:controlling_process(Sock, Pid) of + ok -> + maybe_start_stats_timer(State, Pid), + ?I("server-accept: handler ~p started", [Pid]), + handler_continue(Pid, Mod, Sock, Active), + Handlers2 = [Pid | Handlers], + State#{handlers => Handlers2}; + {error, CPReason} -> + (catch Mod:close(Sock)), + (catch Mod:close(LSock)), + exit({controlling_process, CPReason}) + end; + {error, timeout} -> + State; + {error, AReason} -> + (catch Mod:close(LSock)), + exit({accept, AReason}) + end. + +format_peername({Addr, Port}) -> + case inet:gethostbyaddr(Addr) of + {ok, #hostent{h_name = N}} -> + ?F("~s (~s:~w)", [N, inet:ntoa(Addr), Port]); + {error, _} -> + ?F("~p, ~p", [Addr, Port]) + end. + +maybe_start_stats_timer(#{active := Active, stats_interval := Time}, Handler) + when (Active =/= false) andalso (is_integer(Time) andalso (Time > 0)) -> + start_stats_timer(Time, "handler", Handler); +maybe_start_stats_timer(_, _) -> + ok. + +start_stats_timer(Time, ProcStr, Pid) -> + erlang:start_timer(Time, self(), {stats, Time, ProcStr, Pid}). + +server_handle_message(#{parent := Parent, handlers := H} = State) -> + receive + {timeout, _TRef, {stats, Interval, ProcStr, Pid}} -> + case server_handle_stats(ProcStr, Pid) of + ok -> + start_stats_timer(Interval, ProcStr, Pid); + skip -> + ok + end, + State; + + {?MODULE, Ref, Parent, stop} -> + reply(Parent, Ref, ok), + lists:foreach(fun(P) -> handler_stop(P) end, H), + exit(normal); + + {'DOWN', _MRef, process, Pid, Reason} -> + server_handle_down(Pid, Reason, State) + + after 0 -> + State + end. + +server_handle_stats(ProcStr, Pid) -> + case ?LIB:formated_process_stats(Pid) of + "" -> + skip; + FormatedStats -> + ?I("Statistics for ~s ~p:~s", [ProcStr, Pid, FormatedStats]), + ok + end. + + +server_handle_down(Pid, Reason, #{handlers := Handlers} = State) -> + case lists:delete(Pid, Handlers) of + Handlers -> + ?I("unknown process ~p died", [Pid]), + State; + Handlers2 -> + server_handle_handler_down(Pid, Reason, State#{handlers => Handlers2}) + end. + + +server_handle_handler_down(Pid, + {done, RunTime, MCnt, BCnt}, + #{runtime := AccRunTime, + mcnt := AccMCnt, + bcnt := AccBCnt, + hcnt := AccHCnt} = State) -> + AccRunTime2 = AccRunTime + RunTime, + AccMCnt2 = AccMCnt + MCnt, + AccBCnt2 = AccBCnt + BCnt, + AccHCnt2 = AccHCnt + 1, + ?I("handler ~p (~w) done => accumulated results: " + "~n Run Time: ~s ms" + "~n Message Count: ~s" + "~n Byte Count: ~s", + [Pid, AccHCnt2, + ?FORMAT_TIME(AccRunTime2), + if (AccRunTime2 > 0) -> + ?F("~w => ~w (~w) msgs / ms", + [AccMCnt2, + AccMCnt2 div AccRunTime2, + (AccMCnt2 div AccHCnt2) div AccRunTime2]); + true -> + ?F("~w", [AccMCnt2]) + end, + if (AccRunTime2 > 0) -> + ?F("~w => ~w (~w) bytes / ms", + [AccBCnt2, + AccBCnt2 div AccRunTime2, + (AccBCnt2 div AccHCnt2) div AccRunTime2]); + true -> + ?F("~w", [AccBCnt2]) + end]), + State#{runtime => AccRunTime2, + mcnt => AccMCnt2, + bcnt => AccBCnt2, + hcnt => AccHCnt2}; +server_handle_handler_down(Pid, Reason, State) -> + ?I("handler ~p terminated: " + "~n ~p", [Pid, Reason]), + State. + + + +%% ========================================================================== + +handler_start() -> + Self = self(), + HandlerInit = fun() -> put(sname, "handler"), handler_init(Self) end, + spawn_monitor(HandlerInit). + +handler_continue(Pid, Mod, Sock, Active) -> + req(Pid, {continue, Mod, Sock, Active}). + +handler_stop(Pid) -> + req(Pid, stop). + +handler_init(Parent) -> + ?I("starting"), + receive + {?MODULE, Ref, Parent, {continue, Mod, Sock, Active}} -> + ?I("received continue"), + reply(Parent, Ref, ok), + handler_initial_activation(Mod, Sock, Active), + handler_loop(#{parent => Parent, + mod => Mod, + sock => Sock, + active => Active, + start => ?T(), + mcnt => 0, + bcnt => 0, + last_reply => none, + acc => <<>>}) + + after 5000 -> + ?I("timeout when message queue: " + "~n ~p" + "~nwhen" + "~n Parent: ~p", [process_info(self(), messages), Parent]), + handler_init(Parent) + end. + +handler_loop(State) -> + handler_loop( handler_handle_message( handler_recv_message(State) ) ). + +%% When passive, we read *one* request and then attempt to reply to it. +handler_recv_message(#{mod := Mod, + sock := Sock, + active := false, + mcnt := MCnt, + bcnt := BCnt, + last_reply := LID} = State) -> + case handler_recv_message2(Mod, Sock) of + {ok, {MsgSz, ID, Body}} -> + handler_send_reply(Mod, Sock, ID, Body), + State#{mcnt => MCnt + 1, + bcnt => BCnt + MsgSz, + last_reply => ID}; + {error, closed} -> + handler_done(State); + {error, timeout} -> + ?I("timeout when: " + "~n MCnt: ~p" + "~n BCnt: ~p" + "~n LID: ~p", [MCnt, BCnt, LID]), + State + end; + + +%% When "active" (once or true), we receive one data "message", which may +%% contain any number of requests or only part of a request. Then we +%% process this data together with whatever we had "accumulated" from +%% prevous messages. Each request will be extracted and replied to. If +%% there is some data left, not enough for a complete request, we store +%% this in 'acc' (accumulate it). +handler_recv_message(#{mod := Mod, + sock := Sock, + active := Active, + mcnt := MCnt, + bcnt := BCnt, + last_reply := LID, + acc := Acc} = State) -> + case handler_recv_message3(Mod, Sock, Acc, LID) of + {ok, {MCnt2, BCnt2, LID2}, NewAcc} -> + handler_maybe_activate(Mod, Sock, Active), + State#{mcnt => MCnt + MCnt2, + bcnt => BCnt + BCnt2, + last_reply => LID2, + acc => NewAcc}; + + {error, closed} -> + if + (size(Acc) =:= 0) -> + handler_done(State); + true -> + ?E("client done with partial message: " + "~n Last Reply Sent: ~w" + "~n Message Count: ~w" + "~n Byte Count: ~w" + "~n Partial Message: ~w bytes", + [LID, MCnt, BCnt, size(Acc)]), + exit({closed_with_partial_message, LID}) + end; + + {error, timeout} -> + ?I("timeout when: " + "~n MCnt: ~p" + "~n BCnt: ~p" + "~n LID: ~p" + "~n size(Acc): ~p", [MCnt, BCnt, LID, size(Acc)]), + State + end. + +handler_process_data(Acc, Mod, Sock, LID) -> + handler_process_data(Acc, Mod, Sock, 0, 0, LID). + +%% Extract each complete request, one at a time. +handler_process_data(<<?TTEST_TAG:32, + ?TTEST_TYPE_REQUEST:32, + ID:32, + SZ:32, + Rest/binary>>, + Mod, Sock, + MCnt, BCnt, _LID) when (size(Rest) >= SZ) -> + <<Body:SZ/binary, Rest2/binary>> = Rest, + case handler_send_reply(Mod, Sock, ID, Body) of + ok -> + handler_process_data(Rest2, Mod, Sock, MCnt+1, BCnt+16+SZ, ID); + {error, _} = ERROR -> + ERROR + end; +handler_process_data(Data, _Mod, _Sock, MCnt, BCnt, LID) -> + {ok, {MCnt, BCnt, LID}, Data}. + + +handler_recv_message2(Mod, Sock) -> + case Mod:recv(Sock, 4*4, ?RECV_TIMEOUT) of + {ok, <<?TTEST_TAG:32, + ?TTEST_TYPE_REQUEST:32, + ID:32, + SZ:32>> = Hdr} -> + case Mod:recv(Sock, SZ, ?RECV_TIMEOUT) of + {ok, Body} when (SZ =:= size(Body)) -> + {ok, {size(Hdr) + size(Body), ID, Body}}; + {error, BReason} -> + ?E("failed reading body (~w) of message ~w:" + "~n ~p", [SZ, ID, BReason]), + exit({recv, body, ID, SZ, BReason}) + end; + {error, timeout} = ERROR -> + ERROR; + {error, closed} = ERROR -> + ERROR; + {error, HReason} -> + ?E("Failed reading header of message:" + "~n ~p", [HReason]), + exit({recv, header, HReason}) + end. + + +handler_recv_message3(Mod, Sock, Acc, LID) -> + receive + {TagClosed, Sock} when (TagClosed =:= tcp_closed) orelse + (TagClosed =:= socket_closed) -> + {error, closed}; + + {TagErr, Sock, Reason} when (TagErr =:= tcp_error) orelse + (TagErr =:= socket_error) -> + {error, Reason}; + + {Tag, Sock, Msg} when (Tag =:= tcp) orelse + (Tag =:= socket) -> + handler_process_data(<<Acc/binary, Msg/binary>>, Mod, Sock, LID) + + after ?RECV_TIMEOUT -> + {error, timeout} + end. + + + +handler_send_reply(Mod, Sock, ID, Data) -> + SZ = size(Data), + Msg = <<?TTEST_TAG:32, + ?TTEST_TYPE_REPLY:32, + ID:32, + SZ:32, + Data/binary>>, + case Mod:send(Sock, Msg) of + ok -> + ok; + {error, Reason} -> + (catch Mod:close(Sock)), + exit({send, Reason}) + end. + + +handler_done(State) -> + handler_done(State, ?T()). + +handler_done(#{start := Start, + mod := Mod, + sock := Sock, + mcnt := MCnt, + bcnt := BCnt}, Stop) -> + (catch Mod:close(Sock)), + exit({done, ?TDIFF(Start, Stop), MCnt, BCnt}). + + +handler_handle_message(#{parent := Parent} = State) -> + receive + {'EXIT', Parent, Reason} -> + exit({parent_exit, Reason}) + after 0 -> + State + end. + + +handler_initial_activation(_Mod, _Sock, false = _Active) -> + ok; +handler_initial_activation(Mod, Sock, Active) -> + Mod:active(Sock, Active). + + +handler_maybe_activate(Mod, Sock, once = Active) -> + Mod:active(Sock, Active); +handler_maybe_activate(_, _, _) -> + ok. + + + +%% ========================================================================== + +which_addr() -> + case inet:getifaddrs() of + {ok, IfAddrs} -> + which_addrs(inet, IfAddrs); + {error, Reason} -> + exit({getifaddrs, Reason}) + end. + +which_addrs(_Family, []) -> + exit({getifaddrs, not_found}); +which_addrs(Family, [{"lo", _} | IfAddrs]) -> + %% Skip + which_addrs(Family, IfAddrs); +which_addrs(Family, [{"docker" ++ _, _} | IfAddrs]) -> + %% Skip docker + which_addrs(Family, IfAddrs); +which_addrs(Family, [{"br-" ++ _, _} | IfAddrs]) -> + %% Skip docker + which_addrs(Family, IfAddrs); +which_addrs(Family, [{"en" ++ _, IfOpts} | IfAddrs]) -> + %% Maybe take this one + case which_addr(Family, IfOpts) of + {ok, Addr} -> + Addr; + error -> + which_addrs(Family, IfAddrs) + end; +which_addrs(Family, [{_IfName, IfOpts} | IfAddrs]) -> + case which_addr(Family, IfOpts) of + {ok, Addr} -> + Addr; + error -> + which_addrs(Family, IfAddrs) + end. + +which_addr(_, []) -> + error; +which_addr(inet, [{addr, Addr}|_]) + when is_tuple(Addr) andalso (size(Addr) =:= 4) -> + {ok, Addr}; +which_addr(inet6, [{addr, Addr}|_]) + when is_tuple(Addr) andalso (size(Addr) =:= 8) -> + {ok, Addr}; +which_addr(Family, [_|IfOpts]) -> + which_addr(Family, IfOpts). + + +%% ========================================================================== + +req(Pid, Req) -> + Ref = make_ref(), + Pid ! {?MODULE, Ref, self(), Req}, + receive + {'EXIT', Pid, Reason} -> + {error, {exit, Reason}}; + {?MODULE, Ref, Reply} -> + Reply + end. + +reply(Pid, Ref, Reply) -> + Pid ! {?MODULE, Ref, Reply}. + + +%% ========================================================================== + +%% t() -> +%% os:timestamp(). + +%% tdiff({A1, B1, C1} = _T1x, {A2, B2, C2} = _T2x) -> +%% T1 = A1*1000000000+B1*1000+(C1 div 1000), +%% T2 = A2*1000000000+B2*1000+(C2 div 1000), +%% T2 - T1. + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp({_N1, _N2, N3} = TS) -> +%% {_Date, Time} = calendar:now_to_local_time(TS), +%% {Hour,Min,Sec} = Time, +%% FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.4~w", +%% [Hour, Min, Sec, round(N3/1000)]), +%% lists:flatten(FormatTS). + +%% %% Time is always in number os ms (milli seconds) +%% format_time(T) -> +%% f("~p", [T]). + + +%% ========================================================================== + +%% f(F, A) -> +%% lists:flatten(io_lib:format(F, A)). + +%% e(F, A) -> +%% p(get(sname), "<ERROR> " ++ F, A). + +%% i(F) -> +%% i(F, []). + +%% i(F, A) -> +%% p(get(sname), "<INFO> " ++ F, A). + +%% p(undefined, F, A) -> +%% p("- ", F, A); +%% p(Prefix, F, A) -> +%% io:format("[~s, ~s] " ++ F ++ "~n", [formated_timestamp(), Prefix |A]). + diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl new file mode 100644 index 0000000000..b1b31f5158 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_server_gen.erl @@ -0,0 +1,41 @@ +%% +%% %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_test_ttest_tcp_server_gen). + +-export([ + start/1, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_gen). + +start(Active) -> + socket_test_ttest_tcp_server:start(?TRANSPORT_MOD, Active). + %% {ok, {Pid, AddrPort}} -> + %% MRef = erlang:monitor(process, Pid), + %% {ok, {Pid, MRef, AddrPort}}; + %% {error, _} = ERROR -> + %% ERROR + %% end. + + +stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl new file mode 100644 index 0000000000..b7ea1e8e93 --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_server_socket.erl @@ -0,0 +1,43 @@ +%% +%% %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_test_ttest_tcp_server_socket). + +-export([ + start/2, + stop/1 + ]). + +-define(TRANSPORT_MOD, socket_test_ttest_tcp_socket). +%% -define(MOD(M), {?TRANSPORT_MOD, #{method => M, +%% stats_interval => 10000}}). +-define(MOD(M), {?TRANSPORT_MOD, #{method => M}}). + +start(Method, Active) -> + socket_test_ttest_tcp_server:start(?MOD(Method), Active). + %% {ok, {Pid, AddrPort}} -> + %% MRef = erlang:monitor(process, Pid), + %% {ok, {Pid, MRef, AddrPort}}; + %% {error, _} = ERROR -> + %% ERROR + %% end. + +stop(Pid) -> + socket_test_ttest_tcp_server:stop(Pid). diff --git a/erts/emulator/test/socket_test_ttest_tcp_socket.erl b/erts/emulator/test/socket_test_ttest_tcp_socket.erl new file mode 100644 index 0000000000..0ae2412e4c --- /dev/null +++ b/erts/emulator/test/socket_test_ttest_tcp_socket.erl @@ -0,0 +1,414 @@ +%% +%% %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_test_ttest_tcp_socket). + +-export([ + accept/1, accept/2, + active/2, + close/1, + connect/2, connect/3, + controlling_process/2, + listen/0, listen/1, listen/2, + port/1, + peername/1, + recv/2, recv/3, + send/2, + shutdown/2, + sockname/1 + ]). + + +-define(READER_RECV_TIMEOUT, 1000). + +-define(DATA_MSG(Sock, Method, Data), + {socket, + #{sock => Sock, reader => self(), method => Method}, + Data}). + +-define(CLOSED_MSG(Sock, Method), + {socket_closed, + #{sock => Sock, reader => self(), method => Method}}). + +-define(ERROR_MSG(Sock, Method, Reason), + {socket_error, + #{sock => Sock, reader => self(), method => Method}, + Reason}). + + +%% ========================================================================== + +%% This does not really work. Its just a placeholder for the time beeing... + +%% getopt(Sock, Opt) when is_atom(Opt) -> +%% socket:getopt(Sock, socket, Opt). + +%% setopt(Sock, Opt, Value) when is_atom(Opt) -> +%% socket:setopts(Sock, socket, Opt, Value). + + +%% ========================================================================== + +accept(#{sock := LSock, opts := #{method := Method} = Opts}) -> + case socket:accept(LSock) of + {ok, Sock} -> + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}}; + {error, _} = ERROR -> + ERROR + end. + +accept(#{sock := LSock, opts := #{method := Method} = Opts}, Timeout) -> + case socket:accept(LSock, Timeout) of + {ok, Sock} -> + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}}; + {error, _} = ERROR -> + ERROR + end. + + +active(#{reader := Pid}, NewActive) + when (is_boolean(NewActive) orelse (NewActive =:= once)) -> + Pid ! {?MODULE, active, NewActive}, + ok. + + +close(#{sock := Sock, reader := Pid}) -> + Pid ! {?MODULE, stop}, + socket:close(Sock). + +%% Create a socket and connect it to a peer +connect(Addr, Port) -> + connect(Addr, Port, #{method => plain}). + +connect(Addr, Port, #{method := Method} = Opts) -> + try + begin + Sock = + case socket:open(inet, stream, tcp) of + {ok, S} -> + S; + {error, OReason} -> + throw({error, {open, OReason}}) + end, + case socket:bind(Sock, any) of + {ok, _} -> + ok; + {error, BReason} -> + (catch socket:close(Sock)), + throw({error, {bind, BReason}}) + end, + SA = #{family => inet, + addr => Addr, + port => Port}, + case socket:connect(Sock, SA) of + ok -> + ok; + {error, CReason} -> + (catch socket:close(Sock)), + throw({error, {connect, CReason}}) + end, + Self = self(), + Reader = spawn(fun() -> reader_init(Self, Sock, false, Method) end), + maybe_start_stats_timer(Opts, Reader), + {ok, #{sock => Sock, reader => Reader, method => Method}} + end + catch + throw:ERROR:_ -> + ERROR + end. + + +maybe_start_stats_timer(#{stats_to := Pid, + stats_interval := T}, + Reader) when is_pid(Pid) -> + erlang:start_timer(T, Pid, {stats, T, "reader", Reader}); +maybe_start_stats_timer(_O, _) -> + ok. + +controlling_process(#{sock := Sock, reader := Pid}, NewPid) -> + case socket:setopt(Sock, otp, controlling_process, NewPid) of + ok -> + Pid ! {?MODULE, self(), controlling_process, NewPid}, + receive + {?MODULE, Pid, controlling_process} -> + ok + end; + {error, _} = ERROR -> + ERROR + end. + + +%% Create a listen socket +listen() -> + listen(0, #{method => plain}). + +listen(Port) -> + listen(Port, #{method => plain}). +listen(Port, #{method := Method} = Opts) + when (is_integer(Port) andalso (Port >= 0)) andalso + ((Method =:= plain) orelse (Method =:= msg)) -> + try + begin + Sock = case socket:open(inet, stream, tcp) of + {ok, S} -> + S; + {error, OReason} -> + throw({error, {open, OReason}}) + end, + SA = #{family => inet, + port => Port}, + case socket:bind(Sock, SA) of + {ok, _} -> + ok; + {error, BReason} -> + (catch socket:close(Sock)), + throw({error, {bind, BReason}}) + end, + case socket:listen(Sock) of + ok -> + ok; + {error, LReason} -> + (catch socket:close(Sock)), + throw({error, {listen, LReason}}) + end, + {ok, #{sock => Sock, opts => Opts}} + end + catch + throw:{error, Reason}:_ -> + {error, Reason} + end. + + +port(#{sock := Sock}) -> + case socket:sockname(Sock) of + {ok, #{port := Port}} -> + {ok, Port}; + {error, _} = ERROR -> + ERROR + end. + + +peername(#{sock := Sock}) -> + case socket:peername(Sock) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + {error, _} = ERROR -> + ERROR + end. + + +recv(#{sock := Sock, method := plain}, Length) -> + socket:recv(Sock, Length); +recv(#{sock := Sock, method := msg}, Length) -> + case socket:recvmsg(Sock, Length, 0, [], infinity) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. + +recv(#{sock := Sock, method := plain}, Length, Timeout) -> + socket:recv(Sock, Length, Timeout); +recv(#{sock := Sock, method := msg}, Length, Timeout) -> + case socket:recvmsg(Sock, Length, 0, [], Timeout) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. + + +send(#{sock := Sock, method := plain}, Bin) -> + socket:send(Sock, Bin); +send(#{sock := Sock, method := msg}, Bin) -> + socket:sendmsg(Sock, #{iov => [Bin]}). + + +shutdown(#{sock := Sock}, How) -> + socket:shutdown(Sock, How). + + +sockname(#{sock := Sock}) -> + case socket:sockname(Sock) of + {ok, #{addr := Addr, port := Port}} -> + {ok, {Addr, Port}}; + {error, _} = ERROR -> + ERROR + end. + + +%% ========================================================================== + +reader_init(ControllingProcess, Sock, Active, Method) + when is_pid(ControllingProcess) andalso + (is_boolean(Active) orelse (Active =:= once)) andalso + ((Method =:= plain) orelse (Method =:= msg)) -> + MRef = erlang:monitor(process, ControllingProcess), + reader_loop(#{ctrl_proc => ControllingProcess, + ctrl_proc_mref => MRef, + active => Active, + sock => Sock, + method => Method}). + + +%% Never read +reader_loop(#{active := false, + ctrl_proc := Pid} = State) -> + receive + {?MODULE, stop} -> + exit(normal); + + {?MODULE, Pid, controlling_process, NewPid} -> + MRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(MRef, [flush]), + NewMRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => NewMRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef when (Reason =:= normal) -> + exit(normal); + MRef -> + exit({controlling_process, Reason}); + _ -> + reader_loop(State) + end + end; + +%% Read *once* and then change to false +reader_loop(#{active := once, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + case do_recv(Method, Sock) of + {ok, Data} -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State#{active => false}); + {error, timeout} -> + receive + {?MODULE, stop} -> + exit(normal); + + {?MODULE, Pid, controlling_process, NewPid} -> + MRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(MRef, [flush]), + MRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => MRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef when (Reason =:= normal) -> + exit(normal); + MRef -> + exit({controlling_process, Reason}); + _ -> + reader_loop(State) + end + after 0 -> + reader_loop(State) + end; + + {error, closed} -> + Pid ! ?CLOSED_MSG(Sock, Method), + exit(normal); + + {error, Reason} -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + exit(Reason) + end; + +%% Read and forward data continuously +reader_loop(#{active := true, + sock := Sock, + method := Method, + ctrl_proc := Pid} = State) -> + case do_recv(Method, Sock) of + {ok, Data} -> + Pid ! ?DATA_MSG(Sock, Method, Data), + reader_loop(State); + {error, timeout} -> + receive + {?MODULE, stop} -> + exit(normal); + + {?MODULE, Pid, controlling_process, NewPid} -> + MRef = maps:get(ctrl_proc_mref, State), + erlang:demonitor(MRef, [flush]), + MRef = erlang:monitor(process, NewPid), + Pid ! {?MODULE, self(), controlling_process}, + reader_loop(State#{ctrl_proc => NewPid, + ctrl_proc_mref => MRef}); + + {?MODULE, active, NewActive} -> + reader_loop(State#{active => NewActive}); + + {'DOWN', MRef, process, Pid, Reason} -> + case maps:get(ctrl_proc_mref, State) of + MRef when (Reason =:= normal) -> + exit(normal); + MRef -> + exit({controlling_process, Reason}); + _ -> + reader_loop(State) + end + after 0 -> + reader_loop(State) + end; + + {error, closed} -> + Pid ! ?CLOSED_MSG(Sock, Method), + exit(normal); + + {error, Reason} -> + Pid ! ?ERROR_MSG(Sock, Method, Reason), + exit(Reason) + end. + + +do_recv(plain, Sock) -> + socket:recv(Sock, 0, ?READER_RECV_TIMEOUT); +do_recv(msg, Sock) -> + case socket:recvmsg(Sock, 0, 0, [], ?READER_RECV_TIMEOUT) of + {ok, #{iov := [Bin]}} -> + {ok, Bin}; + {error, _} = ERROR -> + ERROR + end. + + + +%% ========================================================================== + diff --git a/erts/preloaded/ebin/erl_init.beam b/erts/preloaded/ebin/erl_init.beam Binary files differindex bac2cbb15b..81be5b021a 100644 --- a/erts/preloaded/ebin/erl_init.beam +++ b/erts/preloaded/ebin/erl_init.beam diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex dbc080cf2d..3392674e3c 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/ebin/net.beam b/erts/preloaded/ebin/net.beam Binary files differnew file mode 100644 index 0000000000..762434450a --- /dev/null +++ b/erts/preloaded/ebin/net.beam diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam Binary files differnew file mode 100644 index 0000000000..4a2d183537 --- /dev/null +++ b/erts/preloaded/ebin/socket.beam diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index c655bff72e..efeb92dce9 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -39,6 +39,8 @@ PRE_LOADED_ERL_MODULES = \ prim_buffer \ prim_file \ prim_inet \ + socket \ + net \ zlib \ prim_zip \ erl_init \ @@ -79,6 +81,10 @@ STDLIB_INCLUDE=$(ERL_TOP)/lib/stdlib/include ERL_COMPILE_FLAGS += +debug_info -I$(KERNEL_SRC) -I$(KERNEL_INCLUDE) +DIA_PLT = erts-preloaded.plt +DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis + + debug opt: $(TARGET_FILES) clean: @@ -90,7 +96,6 @@ copy: $(APP_TARGET): $(APP_SRC) $(ERL_TOP)/erts/vsn.mk $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ - include $(ERL_TOP)/make/otp_release_targets.mk release_spec: $(APP_TARGET) @@ -105,6 +110,30 @@ release_docs_spec: list_preloaded: @echo $(PRE_LOADED_MODULES) +dclean: + rm -f $(DIA_PLT) + rm -f $(DIA_ANALYSIS) + +dialyzer_plt: $(DIA_PLT) + +$(DIA_PLT): $(ERL_FILES) + @echo "Building ($(basename $(DIA_PLT))) plt file" + @dialyzer --build_plt \ + --output_plt $@ \ + -r ../ebin \ + ../../../lib/kernel/ebin \ + ../../../lib/stdlib/ebin \ + ../../../lib/crypto/ebin \ + ../../../lib/compiler/ebin \ + --output $(DIA_ANALYSIS) \ + --verbose + +dialyzer: $(DIA_PLT) + @echo "Running dialyzer on $(basename $(DIA_PLT))" + @dialyzer --plt $< \ + ../ebin \ + --verbose + # # Combine a BEAM assembly script file a stub Erlang file into a BEAM file. # See add_abstract_chunk script. diff --git a/erts/preloaded/src/erl_init.erl b/erts/preloaded/src/erl_init.erl index d681f05398..6edead362c 100644 --- a/erts/preloaded/src/erl_init.erl +++ b/erts/preloaded/src/erl_init.erl @@ -35,6 +35,8 @@ start(Mod, BootArgs) -> erl_tracer:on_load(), prim_buffer:on_load(), prim_file:on_load(), + socket:on_load(), + net:on_load(), %% Proceed to the specified boot module run(Mod, boot, BootArgs). diff --git a/erts/preloaded/src/net.erl b/erts/preloaded/src/net.erl new file mode 100644 index 0000000000..0183a69ddb --- /dev/null +++ b/erts/preloaded/src/net.erl @@ -0,0 +1,329 @@ +%% +%% %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(net). + +-compile(no_native). + +%% Administrative and "global" utility functions +-export([ + on_load/0, on_load/1, + info/0, + command/1 + ]). + +-export([ + gethostname/0, + getnameinfo/1, getnameinfo/2, + getaddrinfo/1, getaddrinfo/2, + + if_name2index/1, + if_index2name/1, + if_names/0 + ]). + +%% Deprecated functions from the "old" net module +-export([call/4, + cast/4, + broadcast/3, + ping/1, + relay/1, + sleep/1]). + +-export_type([ + address_info/0, + name_info/0, + + name_info_flags/0, + name_info_flag/0, + name_info_flag_ext/0, + + network_interface_name/0, + network_interface_index/0 + ]). + + +-type name_info_flags() :: [name_info_flag()|name_info_flag_ext()]. +-type name_info_flag() :: namereqd | + dgram | + nofqdn | + numerichost | + nomericserv. +-type name_info_flag_ext() :: idn | + idna_allow_unassigned | + idna_use_std3_ascii_rules. +-type name_info() :: #{host := string(), + service := string()}. +-type address_info() :: #{family := socket:domain(), + socktype := socket:type(), + protocol := socket:protocol(), + address := socket:sockaddr()}. +-type network_interface_name() :: string(). +-type network_interface_index() :: non_neg_integer(). + + +%% =========================================================================== +%% +%% D E P R E C A T E D F U N C T I O N S +%% +%% =========================================================================== + +call(N,M,F,A) -> rpc:call(N,M,F,A). +cast(N,M,F,A) -> rpc:cast(N,M,F,A). +broadcast(M,F,A) -> rpc:eval_everywhere(M,F,A). +ping(Node) -> net_adm:ping(Node). +sleep(T) -> receive after T -> ok end. +relay(X) -> slave:relay(X). + + + +%% =========================================================================== +%% +%% Administrative and utility API +%% +%% =========================================================================== + +-spec on_load() -> ok. + +%% Should we require that the Extra arg is a map? +on_load() -> + on_load(#{}). + +-spec on_load(Extra) -> ok when + Extra :: map(). + +on_load(Extra) -> + ok = erlang:load_nif(atom_to_list(?MODULE), Extra). + + +-spec info() -> list(). + +info() -> + nif_info(). + + +-spec command(Cmd :: term()) -> term(). + +command(Cmd) -> + nif_command(Cmd). + + + +%% =========================================================================== +%% +%% The proper net API +%% +%% =========================================================================== + +%% =========================================================================== +%% +%% gethostname - Get the name of the current host. +%% +%% + +-spec gethostname() -> {ok, HostName} | {error, Reason} when + HostName :: string(), + Reason :: term(). + +gethostname() -> + nif_gethostname(). + + +%% =========================================================================== +%% +%% getnameinfo - Address-to-name translation in protocol-independent manner. +%% +%% + +-spec getnameinfo(SockAddr) -> {ok, Info} | {error, Reason} when + SockAddr :: socket:sockaddr(), + Info :: name_info(), + Reason :: term(). + +getnameinfo(SockAddr) -> + getnameinfo(SockAddr, undefined). + +-spec getnameinfo(SockAddr, Flags) -> {ok, Info} | {error, Reason} when + SockAddr :: socket:sockaddr(), + Flags :: name_info_flags() | undefined, + Info :: name_info(), + Reason :: term(). + +getnameinfo(SockAddr, [] = _Flags) -> + getnameinfo(SockAddr, undefined); +getnameinfo(#{family := Fam, addr := _Addr} = SockAddr, Flags) + when ((Fam =:= inet) orelse (Fam =:= inet6)) andalso + (is_list(Flags) orelse (Flags =:= undefined)) -> + nif_getnameinfo(socket:ensure_sockaddr(SockAddr), Flags); +getnameinfo(#{family := Fam, path := _Path} = SockAddr, Flags) + when (Fam =:= local) andalso (is_list(Flags) orelse (Flags =:= undefined)) -> + nif_getnameinfo(SockAddr, Flags). + + + +%% =========================================================================== +%% +%% getaddrinfo - Network address and service translation +%% +%% There is also a "hint" argument that we "at some point" should implement. + +-spec getaddrinfo(Host) -> {ok, Info} | {error, Reason} when + Host :: string(), + Info :: [address_info()], + Reason :: term(). + +getaddrinfo(Host) when is_list(Host) -> + getaddrinfo(Host, undefined). + + +-spec getaddrinfo(Host, undefined) -> {ok, Info} | {error, Reason} when + Host :: string(), + Info :: [address_info()], + Reason :: term() + ; (undefined, Service) -> {ok, Info} | {error, Reason} when + Service :: string(), + Info :: [address_info()], + Reason :: term() + ; (Host, Service) -> {ok, Info} | {error, Reason} when + Host :: string(), + Service :: string(), + Info :: [address_info()], + Reason :: term(). + +getaddrinfo(Host, Service) + when (is_list(Host) orelse (Host =:= undefined)) andalso + (is_list(Service) orelse (Service =:= undefined)) andalso + (not ((Service =:= undefined) andalso (Host =:= undefined))) -> + nif_getaddrinfo(Host, Service, undefined). + + + +%% =========================================================================== +%% +%% if_name2index - Mappings between network interface names and indexes: +%% name -> idx +%% +%% + +-spec if_name2index(Name) -> {ok, Idx} | {error, Reason} when + Name :: network_interface_name(), + Idx :: network_interface_index(), + Reason :: term(). + +if_name2index(If) when is_list(If) -> + nif_if_name2index(If). + + + +%% =========================================================================== +%% +%% if_index2name - Mappings between network interface index and names: +%% idx -> name +%% +%% + +-spec if_index2name(Idx) -> {ok, Name} | {error, Reason} when + Idx :: network_interface_index(), + Name :: network_interface_name(), + Reason :: term(). + +if_index2name(Idx) when is_integer(Idx) -> + nif_if_index2name(Idx). + + + +%% =========================================================================== +%% +%% if_names - Get network interface names and indexes +%% +%% + +-spec if_names() -> Names | {error, Reason} when + Names :: [{Idx, If}], + Idx :: network_interface_index(), + If :: network_interface_name(), + Reason :: term(). + +if_names() -> + nif_if_names(). + + + +%% =========================================================================== +%% +%% Misc utility functions +%% +%% =========================================================================== + +%% formated_timestamp() -> +%% format_timestamp(os:timestamp()). + +%% format_timestamp(Now) -> +%% N2T = fun(N) -> calendar:now_to_local_time(N) end, +%% format_timestamp(Now, N2T, true). + +%% format_timestamp({_N1, _N2, N3} = N, N2T, true) -> +%% FormatExtra = ".~.2.0w", +%% ArgsExtra = [N3 div 10000], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra); +%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) -> +%% FormatExtra = "", +%% ArgsExtra = [], +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra). + +%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) -> +%% {Date, Time} = N2T(N), +%% {YYYY,MM,DD} = Date, +%% {Hour,Min,Sec} = Time, +%% FormatDate = +%% io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra, +%% [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra), +%% lists:flatten(FormatDate). + + +%% =========================================================================== +%% +%% The actual NIF-functions. +%% +%% =========================================================================== + +nif_info() -> + erlang:nif_error(undef). + +nif_command(_Cmd) -> + erlang:nif_error(undef). + +nif_gethostname() -> + erlang:nif_error(undef). + +nif_getnameinfo(_Addr, _Flags) -> + erlang:nif_error(undef). + +nif_getaddrinfo(_Host, _Service, _Hints) -> + erlang:nif_error(undef). + +nif_if_name2index(_Name) -> + erlang:nif_error(undef). + +nif_if_index2name(_Id) -> + erlang:nif_error(undef). + +nif_if_names() -> + erlang:nif_error(undef). diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl new file mode 100644 index 0000000000..287ef1d791 --- /dev/null +++ b/erts/preloaded/src/socket.erl @@ -0,0 +1,3663 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018-2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(socket). + +-compile(no_native). +-compile({no_auto_import,[error/1]}). + +%% Administrative and "global" utility functions +-export([ + on_load/0, on_load/1, + info/0, + supports/0, supports/1, supports/2, supports/3, + ensure_sockaddr/1 + ]). + +-export([ + open/2, open/3, open/4, + bind/2, bind/3, + connect/2, connect/3, + listen/1, listen/2, + accept/1, accept/2, + + send/2, send/3, send/4, + sendto/3, sendto/4, sendto/5, + sendmsg/2, sendmsg/3, sendmsg/4, + + recv/1, recv/2, recv/3, recv/4, + recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, + recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/5, + + close/1, + shutdown/2, + + setopt/4, + getopt/3, + + sockname/1, + peername/1 + ]). + +-export_type([ + domain/0, + type/0, + protocol/0, + socket/0, + + port_number/0, + ip_address/0, + ip4_address/0, + ip6_address/0, + sockaddr/0, + sockaddr_in4/0, + sockaddr_in6/0, + sockaddr_un/0, + + accept_flags/0, + accept_flag/0, + + send_flags/0, + send_flag/0, + + shutdown_how/0, + + sockopt_level/0, + otp_socket_option/0, + socket_option/0, + ip_socket_option/0, + ipv6_socket_option/0, + tcp_socket_option/0, + udp_socket_option/0, + sctp_socket_option/0, + raw_socket_option/0, + + timeval/0, + ip_tos/0, + ip_mreq/0, + ip_mreq_source/0, + ip_pmtudisc/0, + ip_msfilter_mode/0, + ip_msfilter/0, + ip_pktinfo/0, + ipv6_mreq/0, + ipv6_pmtudisc/0, + sctp_assoc_id/0, + sctp_sndrcvinfo/0, + sctp_event_subscribe/0, + sctp_assocparams/0, + sctp_initmsg/0, + sctp_rtoinfo/0, + + + msghdr_flag/0, + msghdr_flags/0, + msghdr/0, + cmsghdr_level/0, + cmsghdr_type/0, + %% cmsghdr_data/0, + cmsghdr_recv/0, cmsghdr_send/0, + + uint8/0, + uint16/0, + uint20/0, + uint32/0, + int32/0 + ]). + + +-type uint8() :: 0..16#FF. +-type uint16() :: 0..16#FFFF. +-type uint20() :: 0..16#FFFFF. +-type uint32() :: 0..16#FFFFFFFF. +-type int32() :: -2147483648..2147483647. + + +%% We support only a subset of all domains. +-type domain() :: local | inet | inet6. + +%% We support only a subset of all types. +%% RDM - Reliably Delivered Messages +-type type() :: stream | dgram | raw | rdm | seqpacket. + +%% We support only a subset of all protocols: +%% Note that the '{raw, integer()}' construct is intended +%% to be used with type = raw. +%% Note also that only the "superuser" can create a raw socket. +-type protocol() :: ip | tcp | udp | sctp | icmp | igmp | {raw, integer()}. + +-type port_number() :: 0..65535. + +-type ip_address() :: ip4_address() | ip6_address(). + +-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}. + +-type in6_flow_info() :: uint20(). +-type in6_scope_id() :: uint32(). + +-type ip6_address() :: + {0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535, + 0..65535}. + +-type timeval() :: #{sec := integer(), + usec := integer()}. + +-type ip_pktinfo() :: #{ + ifindex := non_neg_integer(), % Interface Index + spec_dst := ip4_address(), % Local Address + addr := ip4_address() % Header Destination address + }. + +%% If the integer value is used, its up to the caller to ensure its valid! +-type ip_tos() :: lowdeley | + throughput | + reliability | + mincost | + integer(). + +%% This type is used when requesting to become member of a multicast +%% group with a call to setopt. Example: +%% +%% socket:setopt(Socket, ip, add_membership, #{multiaddr => Addr, +%% interface => any}). +%% +%% Its also used when removing from a multicast group. Example: +%% +%% socket:setopt(Socket, ip, drop_membership, #{multiaddr => Addr, +%% interface => any}). +%% + +-type ip_mreq() :: #{multiaddr := ip4_address(), + interface := any | ip4_address()}. +%% -type ip_mreqn() :: #{multiaddr := ip4_address(), +%% address := any | ip4_address(), +%% ifindex := integer()}. + +-type ip_mreq_source() :: #{multiaddr := ip4_address(), + interface := ip4_address(), + sourceaddr := ip4_address()}. + +-type ip_pmtudisc() :: want | dont | do | probe. + +%% multiaddr: Multicast group address +%% interface: Address of local interface +%% mode: Filter mode +%% slist: List of source addresses +-type ip_msfilter_mode() :: include | exclude. + +-type ip_msfilter() :: #{multiaddr := ip4_address(), + interface := ip4_address(), + mode := ip_msfilter_mode(), + slist := [ip4_address()]}. + +-type ipv6_mreq() :: #{multiaddr := ip6_address(), + interface := non_neg_integer()}. + +-type ipv6_pmtudisc() :: ip_pmtudisc(). + +-type ipv6_pktinfo() :: #{ + addr := ip6_address(), + ifindex := integer() + }. + + +-type sctp_assoc_id() :: int32(). +-type sctp_sndrcvinfo() :: #{ + stream := uint16(), + ssn := uint16(), + flags := uint16(), + ppid := uint16(), + context := uint16(), + timetolive := uint16(), + tsn := uint16(), + cumtsn := uint16(), + assoc_id := sctp_assoc_id() + }. + +-type sctp_event_subscribe() :: #{data_in := boolean(), + association := boolean(), + address := boolean(), + send_failure := boolean(), + peer_error := boolean(), + shutdown := boolean(), + partial_delivery := boolean(), + adaptation_layer := boolean(), + authentication := boolean(), + sender_dry := boolean()}. + +-type sctp_assocparams() :: #{assoc_id := sctp_assoc_id(), + max_rxt := uint16(), + num_peer_dests := uint16(), + peer_rwnd := uint32(), + local_rwnd := uint32(), + cookie_life := uint32()}. + +-type sctp_initmsg() :: #{num_outstreams := uint16(), + max_instreams := uint16(), + max_attempts := uint16(), + max_init_timeo := uint16() + }. + +-type sctp_rtoinfo() :: #{assoc_id := sctp_assoc_id(), + initial := uint32(), + max := uint32(), + min := uint32()}. + +-type sockaddr_un() :: #{family := local, + path := binary() | string()}. +-type sockaddr_in4() :: #{family := inet, + port := port_number(), + addr := any | loopback | ip4_address()}. +-type sockaddr_in6() :: #{family := inet6, + port := port_number(), + addr := any | loopback | ip6_address(), + flowinfo := in6_flow_info(), + scope_id := in6_scope_id()}. +-type sockaddr() :: sockaddr_in4() | + sockaddr_in6() | + sockaddr_un(). + +-define(SOCKADDR_IN4_DEFAULTS(A), #{port => 0, + addr => A}). +-define(SOCKADDR_IN4_DEFAULTS, ?SOCKADDR_IN4_DEFAULTS(any)). +-define(SOCKADDR_IN4_DEFAULT(A), (?SOCKADDR_IN4_DEFAULTS(A))#{family => inet}). +-define(SOCKADDR_IN6_DEFAULTS(A), #{port => 0, + addr => A, + flowinfo => 0, + scope_id => 0}). +-define(SOCKADDR_IN6_DEFAULTS, ?SOCKADDR_IN6_DEFAULTS(any)). +-define(SOCKADDR_IN6_DEFAULT(A), (?SOCKADDR_IN6_DEFAULTS(A))#{family => inet6}). + +%% otp - The option is internal to our (OTP) imeplementation. +%% socket - The socket layer (SOL_SOCKET). +%% ip - The IP layer (SOL_IP or is it IPPROTO_IP?). +%% ipv6 - The IPv6 layer (SOL_IPV6). +%% tcp - The TCP (Transport Control Protocol) layer (IPPROTO_TCP). +%% udp - The UDP (User Datagram Protocol) layer (IPPROTO_UDP). +%% sctp - The SCTP (Stream Control Transmission Protocol) layer (IPPROTO_SCTP). +%% Int - Raw level, sent down and used "as is". +-type sockopt_level() :: otp | + socket | + ip | ipv6 | tcp | udp | sctp | + non_neg_integer(). + +%% There are some options that are 'read-only'. +%% Should those be included here or in a special list? +%% Should we just document it and leave it to the user? +%% Or catch it in the encode functions? +%% A setopt for a readonly option leads to einval? +%% Do we really need a sndbuf? + +-type otp_socket_option() :: debug | + iow | + controlling_process | + rcvbuf | % sndbuf | + rcvctrlbuf | + sndctrlbuf | + fd. +%% Shall we have special treatment of linger?? +%% read-only options: +%% domain | protocol | type. +%% FreeBSD (only?): acceptfilter +-type socket_option() :: acceptconn | + acceptfilter | + bindtodevice | + broadcast | + busy_poll | + debug | + domain | + dontroute | + error | + keepalive | + linger | + mark | + oobinline | + passcred | + peek_off | + peekcred | + priority | + protocol | + rcvbuf | + rcvbufforce | + rcvlowat | + rcvtimeo | + reuseaddr | + reuseport | + rxq_ovfl | + setfib | + sndbuf | + sndbufforce | + sndlowat | + sndtimeo | + timestamp | + type. + +%% Read-only options: +%% mtu +%% +%% Options only valid on FreeBSD?: +%% dontfrag +%% Options only valid for RAW sockets: +%% nodefrag (linux only?) +-type ip_socket_option() :: add_membership | + add_source_membership | + block_source | + dontfrag | + drop_membership | + drop_source_membership | + freebind | + hdrincl | + minttl | + msfilter | + mtu | + mtu_discover | + multicast_all | + multicast_if | + multicast_loop | + multicast_ttl | + nodefrag | + options | + pktinfo | + recverr | + recvif | + recvdstaddr | + recvopts | + recvorigdstaddr | + recvtos | + recvttl | + retopts | + router_alert | + sndsrcaddr | + tos | + transparent | + ttl | + unblock_source. +-type ipv6_socket_option() :: + addrform | + add_membership | + authhdr | + auth_level | + checksum | + drop_membership | + dstopts | + esp_trans_level | + esp_network_level | + faith | + flowinfo | + hoplimit | + hopopts | + ipcomp_level | + join_group | + leave_group | + mtu | + mtu_discover | + multicast_hops | + multicast_if | + multicast_loop | + portrange | + pktoptions | + recverr | + recvpktinfo | pktinfo | + recvtclass | + router_alert | + rthdr | + tclass | + unicast_hops | + use_min_mtu | + v6only. + +-type tcp_socket_option() :: congestion | + cork | + info | + keepcnt | + keepidle | + keepintvl | + maxseg | + md5sig | + nodelay | + noopt | + nopush | + syncnt | + user_timeout. + +-type udp_socket_option() :: cork. + +-type sctp_socket_option() :: + adaption_layer | + associnfo | + auth_active_key | + auth_asconf | + auth_chunk | + auth_key | + auth_delete_key | + autoclose | + context | + default_send_params | + delayed_ack_time | + disable_fragments | + hmac_ident | + events | + explicit_eor | + fragment_interleave | + get_peer_addr_info | + initmsg | + i_want_mapped_v4_addr | + local_auth_chunks | + maxseg | + maxburst | + nodelay | + partial_delivery_point | + peer_addr_params | + peer_auth_chunks | + primary_addr | + reset_streams | + rtoinfo | + set_peer_primary_addr | + status | + use_ext_recvinfo. + +-type raw_socket_option() :: filter. + +%% -type plain_socket_option() :: integer(). +%% -type sockopt() :: otp_socket_option() | +%% socket_option() | +%% ip_socket_option() | +%% ipv6_socket_option() | +%% tcp_socket_option() | +%% udp_socket_option() | +%% sctp_socket_option() | +%% raw_socket_option() | +%% plain_socket_option(). + +-record(socket, {ref :: reference()}). + +-opaque socket() :: #socket{}. + +-type accept_flags() :: [accept_flag()]. +-type accept_flag() :: nonblock | cloexec. + +-type send_flags() :: [send_flag()]. +-type send_flag() :: confirm | + dontroute | + eor | + more | + nosignal | + oob. + +%% Extend with OWN flags for other usage: +%% - adapt-buffer-sz: +%% This will have the effect that the nif recvfrom will use +%% MSG_PEEK to ensure no part of the message is lost, but if +%% necessary adapt (increase) the buffer size until all of +%% it fits. +%% +%% Note that not all of these flags is useful for every recv function! +%% +-type recv_flags() :: [recv_flag()]. +-type recv_flag() :: cmsg_cloexec | + errqueue | + oob | + peek | + trunc. + +-type shutdown_how() :: read | write | read_write. + +%% These are just place-holder(s) - used by the sendmsg/recvmsg functions... +-type msghdr_flag() :: ctrunc | eor | errqueue | oob | trunc. +-type msghdr_flags() :: [msghdr_flag()]. +-type msghdr() :: #{ + %% *Optional* target address + %% Used on an unconnected socket to specify the + %% target address for a datagram. + addr := sockaddr(), + + iov := [binary()], + + %% The maximum size of the control buffer is platform + %% specific. It is the users responsibility to ensure + %% that its not exceeded. + ctrl := [cmsghdr_recv()] | [cmsghdr_send()], + + %% Only valid with recvmsg + flags := msghdr_flags() + }. +%% We are able to (completely) decode *some* control message headers. +%% Even if we are able to decode both level and type, we may not be +%% able to decode the data, in which case it will be a binary. + +-type cmsghdr_level() :: socket | ip | ipv6 | integer(). +-type cmsghdr_type() :: timestamp | + pktinfo | + tos | + ttl | + rights | + credentials | + origdstaddr | + integer(). +-type cmsghdr_recv() :: + #{level := socket, type := timestamp, data := timeval()} | + #{level := socket, type := rights, data := binary()} | + #{level := socket, type := credentials, data := binary()} | + #{level := socket, type := integer(), data := binary()} | + #{level := ip, type := tos, data := ip_tos()} | + #{level := ip, type := ttl, data := integer()} | + #{level := ip, type := pktinfo, data := ip_pktinfo()} | + #{level := ip, type := origdstaddr, data := sockaddr_in4()} | + #{level := ip, type := integer(), data := binary()} | + #{level := ipv6, type := pktinfo, data := ipv6_pktinfo()} | + #{level := ipv6, type := integer(), data := binary()} | + #{level := integer(), type := integer(), data := binary()}. +-type cmsghdr_send() :: + #{level := socket, type := integer(), data := binary()} | + #{level := ip, type := tos, data := ip_tos() | binary()} | + #{level := ip, type := ttl, data := integer() | binary()} | + #{level := ip, type := integer(), data := binary()} | + #{level := ipv6, type := integer(), data := binary()} | + #{level := udp, type := integer(), data := binary()} | + #{level := integer(), type := integer(), data := binary()}. + + +-define(SOCKET_DOMAIN_LOCAL, 1). +-define(SOCKET_DOMAIN_UNIX, ?SOCKET_DOMAIN_LOCAL). +-define(SOCKET_DOMAIN_INET, 2). +-define(SOCKET_DOMAIN_INET6, 3). + +-define(SOCKET_TYPE_STREAM, 1). +-define(SOCKET_TYPE_DGRAM, 2). +-define(SOCKET_TYPE_RAW, 3). +%% -define(SOCKET_TYPE_RDM, 4). +-define(SOCKET_TYPE_SEQPACKET, 5). + +-define(SOCKET_PROTOCOL_IP, 1). +-define(SOCKET_PROTOCOL_TCP, 2). +-define(SOCKET_PROTOCOL_UDP, 3). +-define(SOCKET_PROTOCOL_SCTP, 4). +-define(SOCKET_PROTOCOL_ICMP, 5). +-define(SOCKET_PROTOCOL_IGMP, 6). + +-define(SOCKET_LISTEN_BACKLOG_DEFAULT, 5). + +-define(SOCKET_ACCEPT_TIMEOUT_DEFAULT, infinity). + +-define(SOCKET_SEND_FLAG_CONFIRM, 0). +-define(SOCKET_SEND_FLAG_DONTROUTE, 1). +-define(SOCKET_SEND_FLAG_EOR, 2). +-define(SOCKET_SEND_FLAG_MORE, 3). +-define(SOCKET_SEND_FLAG_NOSIGNAL, 4). +-define(SOCKET_SEND_FLAG_OOB, 5). + +-define(SOCKET_SEND_FLAGS_DEFAULT, []). +-define(SOCKET_SEND_TIMEOUT_DEFAULT, infinity). +-define(SOCKET_SENDTO_FLAGS_DEFAULT, []). +-define(SOCKET_SENDTO_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). +-define(SOCKET_SENDMSG_FLAGS_DEFAULT, []). +-define(SOCKET_SENDMSG_TIMEOUT_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). + +-define(SOCKET_RECV_FLAG_CMSG_CLOEXEC, 0). +-define(SOCKET_RECV_FLAG_ERRQUEUE, 1). +-define(SOCKET_RECV_FLAG_OOB, 2). +-define(SOCKET_RECV_FLAG_PEEK, 3). +-define(SOCKET_RECV_FLAG_TRUNC, 4). + +-define(SOCKET_RECV_FLAGS_DEFAULT, []). +-define(SOCKET_RECV_TIMEOUT_DEFAULT, infinity). + +-define(SOCKET_OPT_LEVEL_OTP, 0). +-define(SOCKET_OPT_LEVEL_SOCKET, 1). +-define(SOCKET_OPT_LEVEL_IP, 2). +-define(SOCKET_OPT_LEVEL_IPV6, 3). +-define(SOCKET_OPT_LEVEL_TCP, 4). +-define(SOCKET_OPT_LEVEL_UDP, 5). +-define(SOCKET_OPT_LEVEL_SCTP, 6). + +%% *** OTP (socket) options +-define(SOCKET_OPT_OTP_DEBUG, 1). +-define(SOCKET_OPT_OTP_IOW, 2). +-define(SOCKET_OPT_OTP_CTRL_PROC, 3). +-define(SOCKET_OPT_OTP_RCVBUF, 4). +%%-define(SOCKET_OPT_OTP_SNDBUF, 5). +-define(SOCKET_OPT_OTP_RCVCTRLBUF, 6). +-define(SOCKET_OPT_OTP_SNDCTRLBUF, 7). +-define(SOCKET_OPT_OTP_FD, 8). +-define(SOCKET_OPT_OTP_DOMAIN, 16#FF01). % INTERNAL +-define(SOCKET_OPT_OTP_TYPE, 16#FF02). % INTERNAL +-define(SOCKET_OPT_OTP_PROTOCOL, 16#FF03). % INTERNAL + +%% *** SOCKET (socket) options +-define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). +%% -define(SOCKET_OPT_SOCK_ACCEPTFILTER, 2). % FreeBSD +-define(SOCKET_OPT_SOCK_BINDTODEVICE, 3). +-define(SOCKET_OPT_SOCK_BROADCAST, 4). +%% -define(SOCKET_OPT_SOCK_BUSY_POLL, 5). +-define(SOCKET_OPT_SOCK_DEBUG, 6). +-define(SOCKET_OPT_SOCK_DOMAIN, 7). +-define(SOCKET_OPT_SOCK_DONTROUTE, 8). +%% -define(SOCKET_OPT_SOCK_ERROR, 9). +-define(SOCKET_OPT_SOCK_KEEPALIVE, 10). +-define(SOCKET_OPT_SOCK_LINGER, 11). +%% -define(SOCKET_OPT_SOCK_MARK, 12). +-define(SOCKET_OPT_SOCK_OOBINLINE, 13). +%% -define(SOCKET_OPT_SOCK_PASSCRED, 14). +-define(SOCKET_OPT_SOCK_PEEK_OFF, 15). +%% -define(SOCKET_OPT_SOCK_PEEKCRED, 16). +-define(SOCKET_OPT_SOCK_PRIORITY, 17). +-define(SOCKET_OPT_SOCK_PROTOCOL, 18). +-define(SOCKET_OPT_SOCK_RCVBUF, 19). +%% -define(SOCKET_OPT_SOCK_RCVBUFFORCE, 20). +-define(SOCKET_OPT_SOCK_RCVLOWAT, 21). +-define(SOCKET_OPT_SOCK_RCVTIMEO, 22). +-define(SOCKET_OPT_SOCK_REUSEADDR, 23). +-define(SOCKET_OPT_SOCK_REUSEPORT, 24). +%% -define(SOCKET_OPT_SOCK_RXQ_OVFL, 25). +%% -define(SOCKET_OPT_SOCK_SETFIB, 26). % FreeBSD +-define(SOCKET_OPT_SOCK_SNDBUF, 27). +%% -define(SOCKET_OPT_SOCK_SNDBUFFORCE, 28). +-define(SOCKET_OPT_SOCK_SNDLOWAT, 29). +-define(SOCKET_OPT_SOCK_SNDTIMEO, 30). +-define(SOCKET_OPT_SOCK_TIMESTAMP, 31). +-define(SOCKET_OPT_SOCK_TYPE, 32). + +%% *** IP (socket) options +-define(SOCKET_OPT_IP_ADD_MEMBERSHIP, 1). +-define(SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP, 2). +-define(SOCKET_OPT_IP_BLOCK_SOURCE, 3). +%% -define(SOCKET_OPT_IP_DONTFRAG, 4). % FreeBSD +-define(SOCKET_OPT_IP_DROP_MEMBERSHIP, 5). +-define(SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP, 6). +-define(SOCKET_OPT_IP_FREEBIND, 7). +-define(SOCKET_OPT_IP_HDRINCL, 8). +-define(SOCKET_OPT_IP_MINTTL, 9). +-define(SOCKET_OPT_IP_MSFILTER, 10). +-define(SOCKET_OPT_IP_MTU, 11). +-define(SOCKET_OPT_IP_MTU_DISCOVER, 12). +-define(SOCKET_OPT_IP_MULTICAST_ALL, 13). +-define(SOCKET_OPT_IP_MULTICAST_IF, 14). +-define(SOCKET_OPT_IP_MULTICAST_LOOP, 15). +-define(SOCKET_OPT_IP_MULTICAST_TTL, 16). +-define(SOCKET_OPT_IP_NODEFRAG, 17). +%% -define(SOCKET_OPT_IP_OPTIONS, 18). % FreeBSD +-define(SOCKET_OPT_IP_PKTINFO, 19). +-define(SOCKET_OPT_IP_RECVDSTADDR, 20). % FreeBSD +-define(SOCKET_OPT_IP_RECVERR, 21). +-define(SOCKET_OPT_IP_RECVIF, 22). +-define(SOCKET_OPT_IP_RECVOPTS, 23). +-define(SOCKET_OPT_IP_RECVORIGDSTADDR, 24). +-define(SOCKET_OPT_IP_RECVTOS, 25). +-define(SOCKET_OPT_IP_RECVTTL, 26). +-define(SOCKET_OPT_IP_RETOPTS, 27). +-define(SOCKET_OPT_IP_ROUTER_ALERT, 28). +-define(SOCKET_OPT_IP_SENDSRCADDR, 29). % FreeBSD +-define(SOCKET_OPT_IP_TOS, 30). +-define(SOCKET_OPT_IP_TRANSPARENT, 31). +-define(SOCKET_OPT_IP_TTL, 32). +-define(SOCKET_OPT_IP_UNBLOCK_SOURCE, 33). + +%% *** IPv6 (socket) options +-define(SOCKET_OPT_IPV6_ADDRFORM, 1). +-define(SOCKET_OPT_IPV6_ADD_MEMBERSHIP, 2). +-define(SOCKET_OPT_IPV6_AUTHHDR, 3). % Obsolete? +%% -define(SOCKET_OPT_IPV6_AUTH_LEVEL, 4). % FreeBSD +%% -define(SOCKET_OPT_IPV6_CHECKSUM, 5). % FreeBSD +-define(SOCKET_OPT_IPV6_DROP_MEMBERSHIP, 6). +-define(SOCKET_OPT_IPV6_DSTOPTS, 7). +%% -define(SOCKET_OPT_IPV6_ESP_NETWORK_LEVEL, 8). % FreeBSD +%% -define(SOCKET_OPT_IPV6_ESP_TRANS_LEVEL, 9). % FreeBSD +%% -define(SOCKET_OPT_IPV6_FAITH, 10). % FreeBSD +-define(SOCKET_OPT_IPV6_FLOWINFO, 11). +-define(SOCKET_OPT_IPV6_HOPLIMIT, 12). +-define(SOCKET_OPT_IPV6_HOPOPTS, 13). +%% -define(SOCKET_OPT_IPV6_IPCOMP_LEVEL, 14). % FreeBSD +%% -define(SOCKET_OPT_IPV6_JOIN_GROUP, 15). % FreeBSD +%% -define(SOCKET_OPT_IPV6_LEAVE_GROUP, 16). % FreeBSD +-define(SOCKET_OPT_IPV6_MTU, 17). +-define(SOCKET_OPT_IPV6_MTU_DISCOVER, 18). +-define(SOCKET_OPT_IPV6_MULTICAST_HOPS, 19). +-define(SOCKET_OPT_IPV6_MULTICAST_IF, 20). +-define(SOCKET_OPT_IPV6_MULTICAST_LOOP, 21). +%% -define(SOCKET_OPT_IPV6_PORTRANGE, 22). % FreeBSD +%% -define(SOCKET_OPT_IPV6_PKTOPTIONS, 23). % FreeBSD +-define(SOCKET_OPT_IPV6_RECVERR, 24). +-define(SOCKET_OPT_IPV6_RECVPKTINFO, 25). % On FreeBSD: PKTINFO +%% -define(SOCKET_OPT_IPV6_RECVTCLASS, 26). +-define(SOCKET_OPT_IPV6_ROUTER_ALERT, 27). +-define(SOCKET_OPT_IPV6_RTHDR, 28). +%% -define(SOCKET_OPT_IPV6_TCLASS, 29). % FreeBSD +-define(SOCKET_OPT_IPV6_UNICAST_HOPS, 30). +%% -define(SOCKET_OPT_IPV6_USE_MIN_MTU, 31). % FreeBSD +-define(SOCKET_OPT_IPV6_V6ONLY, 32). + +%% *** TCP (socket) options +-define(SOCKET_OPT_TCP_CONGESTION, 1). +-define(SOCKET_OPT_TCP_CORK, 2). +%% -define(SOCKET_OPT_TCP_INFO, 3). +%% -define(SOCKET_OPT_TCP_KEEPCNT, 4). +%% -define(SOCKET_OPT_TCP_KEEPIDLE, 5). +%% -define(SOCKET_OPT_TCP_KEEPINTVL, 6). +-define(SOCKET_OPT_TCP_MAXSEG, 7). +%% -define(SOCKET_OPT_TCP_MD5SIG, 8). +-define(SOCKET_OPT_TCP_NODELAY, 9). +%% -define(SOCKET_OPT_TCP_NOOPT, 10). +%% -define(SOCKET_OPT_TCP_NOPUSH, 11). +%% -define(SOCKET_OPT_TCP_SYNCNT, 12). +%% -define(SOCKET_OPT_TCP_USER_TIMEOUT, 13). + +%% *** UDP (socket) options +-define(SOCKET_OPT_UDP_CORK, 1). + +%% *** SCTP (socket) options +%% -define(SOCKET_OPT_SCTP_ADAPTION_LAYER, 1). +-define(SOCKET_OPT_SCTP_ASSOCINFO, 2). +%% -define(SOCKET_OPT_SCTP_AUTH_ACTIVE_KEY, 3). +%% -define(SOCKET_OPT_SCTP_AUTH_ASCONF, 4). +%% -define(SOCKET_OPT_SCTP_AUTH_CHUNK, 5). +%% -define(SOCKET_OPT_SCTP_AUTH_KEY, 6). +%% -define(SOCKET_OPT_SCTP_AUTH_DELETE_KEY, 7). +-define(SOCKET_OPT_SCTP_AUTOCLOSE, 8). +%% -define(SOCKET_OPT_SCTP_CONTEXT, 9). +%% -define(SOCKET_OPT_SCTP_DEFAULT_SEND_PARAMS, 10). +%% -define(SOCKET_OPT_SCTP_DELAYED_ACK_TIME, 11). +-define(SOCKET_OPT_SCTP_DISABLE_FRAGMENTS, 12). +%% -define(SOCKET_OPT_SCTP_HMAC_IDENT, 13). +-define(SOCKET_OPT_SCTP_EVENTS, 14). +%% -define(SOCKET_OPT_SCTP_EXPLICIT_EOR, 15). +%% -define(SOCKET_OPT_SCTP_FRAGMENT_INTERLEAVE, 16). +%% -define(SOCKET_OPT_SCTP_GET_PEER_ADDR_INFO, 17). +-define(SOCKET_OPT_SCTP_INITMSG, 18). +%% -define(SOCKET_OPT_SCTP_I_WANT_MAPPED_V4_ADDR, 19). +%% -define(SOCKET_OPT_SCTP_LOCAL_AUTH_CHUNKS, 20). +-define(SOCKET_OPT_SCTP_MAXSEG, 21). +%% -define(SOCKET_OPT_SCTP_MAXBURST, 22). +-define(SOCKET_OPT_SCTP_NODELAY, 23). +%% -define(SOCKET_OPT_SCTP_PARTIAL_DELIVERY_POINT, 24). +%% -define(SOCKET_OPT_SCTP_PEER_ADDR_PARAMS, 25). +%% -define(SOCKET_OPT_SCTP_PEER_AUTH_CHUNKS, 26). +%% -define(SOCKET_OPT_SCTP_PRIMARY_ADDR, 27). +%% -define(SOCKET_OPT_SCTP_RESET_STREAMS, 28). +-define(SOCKET_OPT_SCTP_RTOINFO, 29). +%% -define(SOCKET_OPT_SCTP_SET_PEER_PRIMARY_ADDR, 30). +%% -define(SOCKET_OPT_SCTP_STATUS, 31). +%% -define(SOCKET_OPT_SCTP_USE_EXT_RECVINFO, 32). + +-define(SOCKET_SHUTDOWN_HOW_READ, 0). +-define(SOCKET_SHUTDOWN_HOW_WRITE, 1). +-define(SOCKET_SHUTDOWN_HOW_READ_WRITE, 2). + + +-define(SOCKET_SUPPORTS_OPTIONS, 16#0001). +-define(SOCKET_SUPPORTS_SCTP, 16#0002). +-define(SOCKET_SUPPORTS_IPV6, 16#0003). + + +%% =========================================================================== +%% +%% Administrative and utility API +%% +%% =========================================================================== + +-spec on_load() -> ok. + +%% Should we require that the Extra arg is a map? +on_load() -> + on_load(#{}). + +-spec on_load(Extra) -> ok when + Extra :: map(). + +on_load(Extra) -> + ok = erlang:load_nif(atom_to_list(?MODULE), Extra). + + + +-spec info() -> list(). + +info() -> + nif_info(). + + + +%% =========================================================================== +%% +%% supports - get information about what the platform "supports". +%% +%% Generates a list of various info about what the plaform can support. +%% The most obvious case is 'options'. +%% +%% Each item in a 'supports'-list will appear only *one* time. +%% +%% =========================================================================== + +-type supports_options_socket() :: [{socket_option(), boolean()}]. +-type supports_options_ip() :: [{ip_socket_option(), boolean()}]. +-type supports_options_ipv6() :: [{ipv6_socket_option(), boolean()}]. +-type supports_options_tcp() :: [{tcp_socket_option(), boolean()}]. +-type supports_options_udp() :: [{udp_socket_option(), boolean()}]. +-type supports_options_sctp() :: [{sctp_socket_option(), boolean()}]. +-type supports_options() :: [{socket, supports_options_socket()} | + {ip, supports_options_ip()} | + {ipv6, supports_options_ipv6()} | + {tcp, supports_options_tcp()} | + {udp, supports_options_udp()} | + {sctp, supports_options_sctp()}]. + +-spec supports() -> [{options, supports_options()} | + {sctp, boolean()} | + {ipv6, boolean()}]. + +supports() -> + [{options, supports(options)}, + {sctp, supports(sctp)}, + {ipv6, supports(ipv6)}]. + + +-spec supports(options) -> supports_options(); + (sctp) -> boolean(); + (ipv6) -> boolean(); + (Key1) -> false when + Key1 :: term(). + +supports(options) -> + nif_supports(?SOCKET_SUPPORTS_OPTIONS); +supports(sctp) -> + nif_supports(?SOCKET_SUPPORTS_SCTP); +supports(ipv6) -> + nif_supports(?SOCKET_SUPPORTS_IPV6); +supports(_Key1) -> + false. + +-spec supports(options, socket) -> supports_options_socket(); + (options, ip) -> supports_options_ip(); + (options, ipv6) -> supports_options_ipv6(); + (options, tcp) -> supports_options_tcp(); + (options, udp) -> supports_options_udp(); + (options, sctp) -> supports_options_sctp(); + (Key1, Key2) -> false when + Key1 :: term(), + Key2 :: term(). + +supports(options, Level) -> + proplists:get_value(Level, supports(options), false); +supports(_Key1, _Level) -> + false. + + +-spec supports(options, socket, Opt) -> boolean() when + Opt :: socket_option(); + (options, ip, Opt) -> boolean() when + Opt :: ip_socket_option(); + (options, ipv6, Opt) -> boolean() when + Opt :: ipv6_socket_option(); + (options, tcp, Opt) -> boolean() when + Opt :: tcp_socket_option(); + (options, udp, Opt) -> boolean() when + Opt :: udp_socket_option(); + (options, sctp, Opt) -> boolean() when + Opt :: sctp_socket_option(); + (Key1, Key2, Key3) -> false when + Key1 :: term(), + Key2 :: term(), + Key3 :: term(). + +supports(options, Level, Opt) -> + case supports(options, Level) of + S when is_list(S) -> + proplists:get_value(Opt, S, false); + _ -> + false + end; +supports(_Key1, _Key2, _Key3) -> + false. + + + +%% =========================================================================== +%% +%% The proper socket API +%% +%% =========================================================================== + +%% =========================================================================== +%% +%% open - create an endpoint for communication +%% +%% Extra: netns +%% +%% <KOLLA> +%% +%% How do we handle the case when an fd has beem created (somehow) +%% and we shall create a socket "from it". +%% Can we figure out Domain, Type and Protocol from fd? +%% Yes we can: SO_DOMAIN, SO_PROTOCOL, SO_TYPE +%% +%% </KOLLA> +%% +%% +%% <KOLLA> +%% +%% Start a controller process here, *before* the nif_open call. +%% If that call is successful, update with owner process (controlling +%% process) and SockRef. If the open fails, kill the process. +%% "Register" the process on success: +%% +%% nif_register(SockRef, self()). +%% +%% <ALSO> +%% +%% Maybe register the process under a name? +%% Something like: +%% +%% list_to_atom(lists:flatten(io_lib:format("socket-~p", [SockRef]))). +%% +%% </ALSO> +%% +%% The nif sets up a monitor to this process, and if it dies the socket +%% is closed. It is also used if someone wants to monitor the socket. +%% +%% We therefor need monitor function(s): +%% +%% socket:monitor(Socket) +%% socket:demonitor(Socket) +%% +%% These are basically used to monitor the controller process. +%% Should the socket record therefor contain the pid of the controller process? +%% +%% </KOLLA> +%% + +%% -spec open(FD) -> {ok, Socket} | {error, Reason} when +%% Socket :: socket(), +%% Reason :: term(). + +%% open(FD) -> +%% try +%% begin +%% case nif_open(FD) of +%% {ok, {SockRef, Domain, Type, Protocol}} -> +%% SocketInfo = #{domain => Domain, +%% type => Type, +%% protocol => Protocol}, +%% Socket = #socket{info = SocketInfo, +%% ref = SockRef}, +%% {ok, Socket}; +%% {error, _} = ERROR -> +%% ERROR +%% end +%% end +%% catch +%% _:_ -> % This must be improved!! +%% {error, einval} +%% end. + +-spec open(Domain, Type) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Socket :: socket(), + Reason :: term(). + +open(Domain, Type) -> + open(Domain, Type, null). + +-spec open(Domain, Type, Protocol) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Protocol :: null | protocol(), + Socket :: socket(), + Reason :: term(). + +open(Domain, Type, Protocol) -> + open(Domain, Type, Protocol, #{}). + +-spec open(Domain, Type, Protocol, Extra) -> {ok, Socket} | {error, Reason} when + Domain :: domain(), + Type :: type(), + Protocol :: null | protocol(), + Extra :: map(), + Socket :: socket(), + Reason :: term(). + +open(Domain, Type, Protocol0, Extra) when is_map(Extra) -> + try + begin + Protocol = default_protocol(Protocol0, Type), + EDomain = enc_domain(Domain), + EType = enc_type(Domain, Type), + EProtocol = enc_protocol(Type, Protocol), + case nif_open(EDomain, EType, EProtocol, Extra) of + {ok, SockRef} -> + Socket = #socket{ref = SockRef}, + {ok, Socket}; + {error, _} = ERROR -> + ERROR + end + end + catch + throw:T -> + T; + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + error:Reason -> + {error, Reason} + end. + +%% Note that this is just a convenience function for when the protocol was +%% not specified. If its actually specified, then that will be selected. +%% Also, this only works for the some of the type's (stream, dgram and +%% seqpacket). +default_protocol(null, stream) -> tcp; +default_protocol(null, dgram) -> udp; +default_protocol(null, seqpacket) -> sctp; +default_protocol(null, Type) -> throw({error, {no_default_protocol, Type}}); +default_protocol(Protocol, _) -> Protocol. + + +%% =========================================================================== +%% +%% bind - bind a name to a socket +%% + +-spec bind(Socket, Addr) -> ok | {error, Reason} when + Socket :: socket(), + Addr :: any | loopback | sockaddr(), + Reason :: term(). + +bind(#socket{ref = SockRef}, Addr) + when ((Addr =:= any) orelse (Addr =:= loopback)) -> + try which_domain(SockRef) of + inet -> + nif_bind(SockRef, ?SOCKADDR_IN4_DEFAULT(Addr)); + inet6 -> + nif_bind(SockRef, ?SOCKADDR_IN6_DEFAULT(Addr)) + catch + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + throw:ERROR -> + ERROR + end; +bind(#socket{ref = SockRef} = _Socket, Addr) when is_map(Addr) -> + try + begin + nif_bind(SockRef, ensure_sockaddr(Addr)) + end + catch + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + throw:ERROR -> + ERROR + end. + + + +%% =========================================================================== +%% +%% bind - Add or remove a bind addresses on a socket +%% +%% Calling this function is only valid if the socket is: +%% type = seqpacket +%% protocol = sctp +%% +%% If the domain is inet, then all addresses *must* be IPv4. +%% If the domain is inet6, the addresses can be aither IPv4 or IPv6. +%% + +-spec bind(Socket, Addrs, Action) -> ok | {error, Reason} when + Socket :: socket(), + Addrs :: [sockaddr()], + Action :: add | remove, + Reason :: term(). + +bind(#socket{ref = SockRef}, Addrs, Action) + when is_list(Addrs) andalso ((Action =:= add) orelse (Action =:= remove)) -> + try + begin + ensure_type(SockRef, seqpacket), + ensure_proto(SockRef, sctp), + validate_addrs(which_domain(SockRef), Addrs), + nif_bind(SockRef, Addrs, Action) + end + catch + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + throw:ERROR -> + ERROR + end. + +ensure_type(SockRef, Type) -> + case which_type(SockRef) of + Type -> + ok; + _InvalidType -> + einval() + end. + +ensure_proto(SockRef, Proto) -> + case which_protocol(SockRef) of + Proto -> + ok; + _InvalidProto -> + einval() + end. + +validate_addrs(inet = _Domain, Addrs) -> + validate_inet_addrs(Addrs); +validate_addrs(inet6 = _Domain, Addrs) -> + validate_inet6_addrs(Addrs). + +validate_inet_addrs(Addrs) -> + Validator = fun(#{family := inet, + addrs := Addr}) when is_tuple(Addr) andalso + (size(Addr) =:= 4) -> + ok; + (X) -> + throw({error, {invalid_address, X}}) + end, + lists:foreach(Validator, Addrs). + +validate_inet6_addrs(Addrs) -> + Validator = fun(#{family := inet, + addrs := Addr}) when is_tuple(Addr) andalso + (size(Addr) =:= 4) -> + ok; + (#{family := inet6, + addrs := Addr}) when is_tuple(Addr) andalso + (size(Addr) =:= 8) -> + ok; + (X) -> + throw({error, {invalid_address, X}}) + end, + lists:foreach(Validator, Addrs). + + +%% =========================================================================== +%% +%% connect - initiate a connection on a socket +%% + +-spec connect(Socket, SockAddr) -> ok | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: term(). + +connect(Socket, SockAddr) -> + connect(Socket, SockAddr, infinity). + +-spec connect(Socket, SockAddr, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Timeout :: timeout(), + Reason :: term(). + +%% <KOLLA> +%% Is it possible to connect with family = local for the (dest) sockaddr? +%% </KOLLA> +connect(_Socket, _SockAddr, Timeout) + when (is_integer(Timeout) andalso (Timeout =< 0)) -> + {error, timeout}; +connect(#socket{ref = SockRef}, #{family := Fam} = SockAddr, Timeout) + when ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso + ((Timeout =:= infinity) orelse is_integer(Timeout)) -> + TS = timestamp(Timeout), + case nif_connect(SockRef, SockAddr) of + ok -> + %% Connected! + ok; + {ok, Ref} -> + %% Connecting... + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, Ref, ready_output} -> + %% <KOLLA> + %% + %% See open above!! + %% + %% * Here we should start and *register* the reader process + %% (This will cause the nif code to create a monitor to + %% the process) + %% * The reader is basically used to implement the active-X + %% feature! + %% * If the reader dies for whatever reason, then the socket + %% (resource) closes and the owner (controlling) process + %% is informed (closed message). + %% + %% </KOLLA> + nif_finalize_connection(SockRef) + after NewTimeout -> + cancel(SockRef, connect, Ref), + {error, timeout} + end; + {error, _} = ERROR -> + ERROR + end. + + +%% =========================================================================== +%% +%% listen - listen for connections on a socket +%% + +-spec listen(Socket) -> ok | {error, Reason} when + Socket :: socket(), + Reason :: term(). + +listen(Socket) -> + listen(Socket, ?SOCKET_LISTEN_BACKLOG_DEFAULT). + +-spec listen(Socket, Backlog) -> ok | {error, Reason} when + Socket :: socket(), + Backlog :: pos_integer(), + Reason :: term(). + +listen(#socket{ref = SockRef}, Backlog) + when (is_integer(Backlog) andalso (Backlog >= 0)) -> + nif_listen(SockRef, Backlog). + + + + +%% =========================================================================== +%% +%% accept, accept4 - accept a connection on a socket +%% + +-spec accept(LSocket) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Socket :: socket(), + Reason :: term(). + +accept(Socket) -> + accept(Socket, ?SOCKET_ACCEPT_TIMEOUT_DEFAULT). + +-spec accept(LSocket, Timeout) -> {ok, Socket} | {error, Reason} when + LSocket :: socket(), + Timeout :: timeout(), + Socket :: socket(), + Reason :: term(). + +%% Do we really need this optimization? +accept(_, Timeout) when is_integer(Timeout) andalso (Timeout =< 0) -> + {error, timeout}; +accept(#socket{ref = LSockRef}, Timeout) + when is_integer(Timeout) orelse (Timeout =:= infinity) -> + do_accept(LSockRef, Timeout). + +do_accept(LSockRef, Timeout) -> + TS = timestamp(Timeout), + AccRef = make_ref(), + case nif_accept(LSockRef, AccRef) of + {ok, SockRef} -> + %% <KOLLA> + %% + %% * Here we should start and *register* the reader process + %% (This will cause the nif code to create a monitor to the process) + %% * The reader is basically used to implement the active-X feature! + %% * If the reader dies for whatever reason, then the socket (resource) + %% closes and the owner (controlling) process is informed (closed + %% message). + %% + %% </KOLLA> + Socket = #socket{ref = SockRef}, + {ok, Socket}; + + {error, eagain} -> + %% Each call is non-blocking, but even then it takes + %% *some* time, so just to be sure, recalculate before + %% the receive. + NewTimeout = next_timeout(TS, Timeout), + receive + {select, LSockRef, AccRef, ready_input} -> + do_accept(LSockRef, next_timeout(TS, Timeout)); + + {'$socket', _, abort, {AccRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(LSockRef, accept, AccRef), + {error, timeout} + end; + + {error, _} = ERROR -> + cancel(LSockRef, accept, AccRef), % Just to be on the safe side... + ERROR + end. + + + +%% =========================================================================== +%% +%% send, sendto, sendmsg - send a message on a socket +%% + +-spec send(Socket, Data) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Reason :: term(). + +send(Socket, Data) -> + send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, ?SOCKET_SEND_TIMEOUT_DEFAULT). + +-spec send(Socket, Data, Flags) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + Reason :: term() + ; (Socket, Data, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Timeout :: timeout(), + Reason :: term(). + +send(Socket, Data, Flags) when is_list(Flags) -> + send(Socket, Data, Flags, ?SOCKET_SEND_TIMEOUT_DEFAULT); +send(Socket, Data, Timeout) -> + send(Socket, Data, ?SOCKET_SEND_FLAGS_DEFAULT, Timeout). + +-spec send(Socket, Data, Flags, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Flags :: send_flags(), + Timeout :: timeout(), + Reason :: term(). + +send(Socket, Data, Flags, Timeout) when is_list(Data) -> + Bin = erlang:list_to_binary(Data), + send(Socket, Bin, Flags, Timeout); +send(#socket{ref = SockRef}, Data, Flags, Timeout) + when is_binary(Data) andalso is_list(Flags) -> + EFlags = enc_send_flags(Flags), + do_send(SockRef, Data, EFlags, Timeout). + +do_send(SockRef, Data, EFlags, Timeout) -> + TS = timestamp(Timeout), + SendRef = make_ref(), + case nif_send(SockRef, SendRef, Data, EFlags) of + ok -> + ok; + {ok, Written} -> + NewTimeout = next_timeout(TS, Timeout), + %% We are partially done, wait for continuation + receive + {select, SockRef, SendRef, ready_output} when (Written > 0) -> + <<_:Written/binary, Rest/binary>> = Data, + do_send(SockRef, Rest, EFlags, + next_timeout(TS, Timeout)); + {select, SockRef, SendRef, ready_output} -> + do_send(SockRef, Data, EFlags, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {SendRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(SockRef, send, SendRef), + {error, {timeout, size(Data)}} + end; + {error, eagain} -> + receive + {select, SockRef, SendRef, ready_output} -> + do_send(SockRef, Data, EFlags, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {SendRef, Reason}} -> + {error, Reason} + + after Timeout -> + cancel(SockRef, send, SendRef), + {error, {timeout, size(Data)}} + end; + + {error, _} = ERROR -> + ERROR + end. + + + + +%% --------------------------------------------------------------------------- +%% + +-spec sendto(Socket, Data, Dest) -> + ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: null | sockaddr(), + Reason :: term(). + +sendto(Socket, Data, Dest) -> + sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT). + +-spec sendto(Socket, Data, Dest, Flags) -> ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: null | sockaddr(), + Flags :: send_flags(), + Reason :: term() + ; (Socket, Data, Dest, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: iodata(), + Dest :: null | sockaddr(), + Timeout :: timeout(), + Reason :: term(). + +sendto(Socket, Data, Dest, Flags) when is_list(Flags) -> + sendto(Socket, Data, Dest, Flags, ?SOCKET_SENDTO_TIMEOUT_DEFAULT); +sendto(Socket, Data, Dest, Timeout) -> + sendto(Socket, Data, Dest, ?SOCKET_SENDTO_FLAGS_DEFAULT, Timeout). + + +-spec sendto(Socket, Data, Dest, Flags, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Dest :: null | sockaddr(), + Flags :: send_flags(), + Timeout :: timeout(), + Reason :: term(). + +sendto(Socket, Data, Dest, Flags, Timeout) when is_list(Data) -> + Bin = erlang:list_to_binary(Data), + sendto(Socket, Bin, Dest, Flags, Timeout); +sendto(#socket{ref = SockRef}, Data, Dest, Flags, Timeout) + when is_binary(Data) andalso + (Dest =:= null) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_send_flags(Flags), + do_sendto(SockRef, Data, Dest, EFlags, Timeout); +sendto(#socket{ref = SockRef}, Data, #{family := Fam} = Dest, Flags, Timeout) + when is_binary(Data) andalso + ((Fam =:= inet) orelse (Fam =:= inet6) orelse (Fam =:= local)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_send_flags(Flags), + do_sendto(SockRef, Data, Dest, EFlags, Timeout). + +do_sendto(SockRef, Data, Dest, EFlags, Timeout) -> + TS = timestamp(Timeout), + SendRef = make_ref(), + case nif_sendto(SockRef, SendRef, Data, Dest, EFlags) of + ok -> + %% We are done + ok; + + {ok, Written} -> + %% We are partially done, wait for continuation + receive + {select, SockRef, SendRef, ready_output} when (Written > 0) -> + <<_:Written/binary, Rest/binary>> = Data, + do_sendto(SockRef, Rest, Dest, EFlags, + next_timeout(TS, Timeout)); + {select, SockRef, SendRef, ready_output} -> + do_sendto(SockRef, Data, Dest, EFlags, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {SendRef, Reason}} -> + {error, Reason} + + after Timeout -> + cancel(SockRef, sendto, SendRef), + {error, timeout} + end; + + {error, eagain} -> + receive + {select, SockRef, SendRef, ready_output} -> + do_sendto(SockRef, Data, Dest, EFlags, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {SendRef, Reason}} -> + {error, Reason} + + after Timeout -> + cancel(SockRef, sendto, SendRef), + {error, timeout} + end; + + {error, _} = ERROR -> + ERROR + end. + + + +%% --------------------------------------------------------------------------- +%% +%% The only part of the msghdr() that *must* exist (a connected +%% socket need not specify the addr field) is the iov. +%% The ctrl field is optional, and the addr and flags are not +%% used when sending. +%% + +-spec sendmsg(Socket, MsgHdr) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Reason :: term(). + +sendmsg(Socket, MsgHdr) -> + sendmsg(Socket, MsgHdr, + ?SOCKET_SENDMSG_FLAGS_DEFAULT, ?SOCKET_SENDMSG_TIMEOUT_DEFAULT). + + +-spec sendmsg(Socket, MsgHdr, Flags) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Reason :: term() + ; (Socket, MsgHdr, Timeout) -> ok | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Timeout :: timeout(), + Reason :: term(). + +sendmsg(Socket, MsgHdr, Flags) when is_list(Flags) -> + sendmsg(Socket, MsgHdr, Flags, ?SOCKET_SENDMSG_TIMEOUT_DEFAULT); +sendmsg(Socket, MsgHdr, Timeout) + when is_integer(Timeout) orelse (Timeout =:= infinity) -> + sendmsg(Socket, MsgHdr, ?SOCKET_SENDMSG_FLAGS_DEFAULT, Timeout). + + +-spec sendmsg(Socket, MsgHdr, Flags, Timeout) -> ok | {ok, Remaining} | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Flags :: send_flags(), + Timeout :: timeout(), + Remaining :: erlang:iovec(), + Reason :: term(). + +sendmsg(#socket{ref = SockRef}, #{iov := IOV} = MsgHdr, Flags, Timeout) + when is_list(IOV) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + try ensure_msghdr(MsgHdr) of + M -> + EFlags = enc_send_flags(Flags), + do_sendmsg(SockRef, M, EFlags, Timeout) + catch + throw:T -> + T; + error:Reason -> + {error, Reason} + end. + +do_sendmsg(SockRef, MsgHdr, EFlags, Timeout) -> + TS = timestamp(Timeout), + SendRef = make_ref(), + case nif_sendmsg(SockRef, SendRef, MsgHdr, EFlags) of + ok -> + %% We are done + ok; + + {ok, Written} when is_integer(Written) andalso (Written > 0) -> + + %% We should not retry here since the protocol may not + %% be able to handle a message being split. Leave it to + %% the caller to figure out (call again with the rest). + %% + %% We should really not need to cancel, since this is + %% accepted for sendmsg! + %% + cancel(SockRef, sendmsg, SendRef), + {ok, do_sendmsg_rest(maps:get(iov, MsgHdr), Written)}; + + {error, eagain} -> + receive + {select, SockRef, SendRef, ready_output} -> + do_sendmsg(SockRef, MsgHdr, EFlags, + next_timeout(TS, Timeout)) + after Timeout -> + cancel(SockRef, sendmsg, SendRef), + {error, timeout} + end; + + {error, _} = ERROR -> + ERROR + end. + +do_sendmsg_rest([B|IOVec], Written) when (Written >= size(B)) -> + do_sendmsg_rest(IOVec, Written - size(B)); +do_sendmsg_rest([B|IOVec], Written) -> + <<_:Written/binary, Rest/binary>> = B, + [Rest|IOVec]. + +ensure_msghdr(#{ctrl := []} = M) -> + ensure_msghdr(maps:remove(ctrl, M)); +ensure_msghdr(#{iov := IOV} = M) when is_list(IOV) andalso (IOV =/= []) -> + M#{iov := erlang:iolist_to_iovec(IOV)}; +ensure_msghdr(_) -> + einval(). + + + + +%% =========================================================================== +%% +%% writev - write data into multiple buffers +%% + + + +%% =========================================================================== +%% +%% recv, recvfrom, recvmsg - receive a message from a socket +%% +%% Description: +%% There is a special case for the argument Length. If its set to zero (0), +%% it means "give me everything you have". +%% +%% Returns: {ok, Binary} | {error, Reason} +%% Binary - The received data as a binary +%% Reason - The error reason: +%% timeout | {timeout, AccData} | +%% posix() | {posix(), AccData} | +%% atom() | {atom(), AccData} +%% AccData - The data (as a binary) that we did manage to receive +%% before the timeout. +%% +%% Arguments: +%% Socket - The socket to read from. +%% Length - The number of bytes to read. +%% Flags - A list of "options" for the read. +%% Timeout - Time-out in milliseconds. + +-spec recv(Socket) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Data :: binary(), + Reason :: term(). + +recv(Socket) -> + recv(Socket, 0). + +-spec recv(Socket, Length) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Data :: binary(), + Reason :: term(). + +recv(Socket, Length) -> + recv(Socket, Length, + ?SOCKET_RECV_FLAGS_DEFAULT, + ?SOCKET_RECV_TIMEOUT_DEFAULT). + +-spec recv(Socket, Length, Flags) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Data :: binary(), + Reason :: term() + ; (Socket, Length, Timeout) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Timeout :: timeout(), + Data :: binary(), + Reason :: term(). + +recv(Socket, Length, Flags) when is_list(Flags) -> + recv(Socket, Length, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); +recv(Socket, Length, Timeout) -> + recv(Socket, Length, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). + +-spec recv(Socket, Length, Flags, Timeout) -> {ok, Data} | {error, Reason} when + Socket :: socket(), + Length :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + Data :: binary(), + Reason :: term(). + +recv(#socket{ref = SockRef}, Length, Flags, Timeout) + when (is_integer(Length) andalso (Length >= 0)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_recv_flags(Flags), + do_recv(SockRef, undefined, Length, EFlags, <<>>, Timeout). + +%% We need to pass the "old recv ref" around because of the special case +%% with Length = 0. This case makes it neccessary to have a timeout function +%% clause since we may never wait for anything (no receive select), and so the +%% the only timeout check will be the function clause. +do_recv(SockRef, _OldRef, Length, EFlags, Acc, Timeout) + when (Timeout =:= infinity) orelse + (is_integer(Timeout) andalso (Timeout > 0)) -> + TS = timestamp(Timeout), + RecvRef = make_ref(), + %% p("do_recv -> try read with" + %% "~n Length: ~p", [Length]), + case nif_recv(SockRef, RecvRef, Length, EFlags) of + {ok, true = _Complete, Bin} when (size(Acc) =:= 0) -> + %% p("do_recv -> complete success: ~w", [size(Bin)]), + {ok, Bin}; + {ok, true = _Complete, Bin} -> + %% p("do_recv -> completed success: ~w (~w)", [size(Bin), size(Acc)]), + {ok, <<Acc/binary, Bin/binary>>}; + + %% It depends on the amount of bytes we tried to read: + %% 0 - Read everything available + %% We got something, but there may be more - keep reading. + %% > 0 - We got a part of the message and we will be notified + %% when there is more to read (a select message) + {ok, false = _Complete, Bin} when (Length =:= 0) -> + %% p("do_recv -> partial success: ~w", [size(Bin)]), + do_recv(SockRef, RecvRef, + Length, EFlags, + <<Acc/binary, Bin/binary>>, + next_timeout(TS, Timeout)); + + {ok, false = _Completed, Bin} when (size(Acc) =:= 0) -> + %% We got the first chunk of it. + %% We will be notified (select message) when there + %% is more to read. + %% p("do_recv -> partial success(~w): ~w" + %% "~n ~p", [Length, size(Bin), Bin]), + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recv(SockRef, RecvRef, + Length-size(Bin), EFlags, + Bin, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {RecvRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(SockRef, recv, RecvRef), + {error, {timeout, Acc}} + end; + + {ok, false = _Completed, Bin} -> + %% We got a chunk of it! + %% p("do_recv -> partial success(~w): ~w (~w)", + %% [Length, size(Bin), size(Acc)]), + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recv(SockRef, RecvRef, + Length-size(Bin), EFlags, + <<Acc/binary, Bin/binary>>, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {RecvRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(SockRef, recv, RecvRef), + {error, {timeout, Acc}} + end; + + %% We return with the accumulated binary (if its non-empty) + {error, eagain} when (Length =:= 0) andalso (size(Acc) > 0) -> + %% CAN WE REALLY DO THIS? THE NIF HAS SELECTED!! OR? + {ok, Acc}; + + {error, eagain} -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + %% p("do_recv -> eagain(~w): ~w", [Length, size(Acc)]), + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recv(SockRef, RecvRef, + Length, EFlags, + Acc, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {RecvRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(SockRef, recv, RecvRef), + {error, timeout} + end; + + {error, closed = Reason} -> + do_close(SockRef), + if + (size(Acc) =:= 0) -> + {error, Reason}; + true -> + {error, {Reason, Acc}} + end; + + {error, _} = ERROR when (size(Acc) =:= 0) -> + ERROR; + + {error, Reason} -> + {error, {Reason, Acc}} + + end; + +do_recv(SockRef, RecvRef, 0 = _Length, _Eflags, Acc, _Timeout) -> + %% The current recv operation is to be cancelled, so no need for a ref... + %% The cancel will end our 'read everything you have' and "activate" + %% any waiting reader. + cancel(SockRef, recv, RecvRef), + {ok, Acc}; +do_recv(_SockRef, _RecvRef, _Length, _EFlags, Acc, _Timeout) + when (size(Acc) > 0) -> + {error, {timeout, Acc}}; +do_recv(_SockRef, _RecvRef, _Length, _EFlags, _Acc, _Timeout) -> + {error, timeout}. + + + +%% --------------------------------------------------------------------------- +%% +%% With recvfrom we get messages, which means that regardless of how +%% much we want to read, we return when we get a message. +%% The MaxSize argument basically defines the size of our receive +%% buffer. By setting the size to zero (0), we use the configured +%% size (see setopt). +%% It may be impossible to know what (buffer) size is appropriate +%% "in advance", and in those cases it may be convenient to use the +%% (recv) 'peek' flag. When this flag is provided the message is *not* +%% "consumed" from the underlying buffers, so another recvfrom call +%% is needed, possibly with a then adjusted buffer size. +%% + +-spec recvfrom(Socket) -> {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). + +recvfrom(Socket) -> + recvfrom(Socket, 0). + +-spec recvfrom(Socket, BufSz) -> {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). + +recvfrom(Socket, BufSz) -> + recvfrom(Socket, BufSz, + ?SOCKET_RECV_FLAGS_DEFAULT, + ?SOCKET_RECV_TIMEOUT_DEFAULT). + +-spec recvfrom(Socket, Flags, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term() + ; (Socket, BufSz, Flags) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term() + ; (Socket, BufSz, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). + +recvfrom(Socket, Flags, Timeout) when is_list(Flags) -> + recvfrom(Socket, 0, Flags, Timeout); +recvfrom(Socket, BufSz, Flags) when is_list(Flags) -> + recvfrom(Socket, BufSz, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); +recvfrom(Socket, BufSz, Timeout) -> + recvfrom(Socket, BufSz, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). + +-spec recvfrom(Socket, BufSz, Flags, Timeout) -> + {ok, {Source, Data}} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + Source :: sockaddr() | undefined, + Data :: binary(), + Reason :: term(). + +recvfrom(#socket{ref = SockRef}, BufSz, Flags, Timeout) + when (is_integer(BufSz) andalso (BufSz >= 0)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_recv_flags(Flags), + do_recvfrom(SockRef, BufSz, EFlags, Timeout). + +do_recvfrom(SockRef, BufSz, EFlags, Timeout) -> + TS = timestamp(Timeout), + RecvRef = make_ref(), + case nif_recvfrom(SockRef, RecvRef, BufSz, EFlags) of + {ok, {_Source, _NewData}} = OK -> + OK; + + {error, eagain} -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recvfrom(SockRef, BufSz, EFlags, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {RecvRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(SockRef, recvfrom, RecvRef), + {error, timeout} + end; + + {error, _Reason} = ERROR -> + ERROR + + end. + +%% pi(Item) -> +%% pi(self(), Item). + +%% pi(Pid, Item) -> +%% {Item, Info} = process_info(Pid, Item), +%% Info. + + +%% --------------------------------------------------------------------------- +%% + +-spec recvmsg(Socket) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + MsgHdr :: msghdr(), + Reason :: term(). + +recvmsg(Socket) -> + recvmsg(Socket, 0, 0, + ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). + +-spec recvmsg(Socket, Flags) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + MsgHdr :: msghdr(), + Reason :: term() + ; (Socket, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: term(). + +recvmsg(Socket, Flags) when is_list(Flags) -> + recvmsg(Socket, 0, 0, Flags, ?SOCKET_RECV_TIMEOUT_DEFAULT); +recvmsg(Socket, Timeout) -> + recvmsg(Socket, 0, 0, ?SOCKET_RECV_FLAGS_DEFAULT, Timeout). + +-spec recvmsg(Socket, Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + Flags :: recv_flags(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: term() + ; (Socket, BufSz, CtrlSz) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + MsgHdr :: msghdr(), + Reason :: term(). + +recvmsg(Socket, Flags, Timeout) when is_list(Flags) -> + recvmsg(Socket, 0, 0, Flags, Timeout); +recvmsg(Socket, BufSz, CtrlSz) when is_integer(BufSz) andalso is_integer(CtrlSz) -> + recvmsg(Socket, BufSz, CtrlSz, + ?SOCKET_RECV_FLAGS_DEFAULT, ?SOCKET_RECV_TIMEOUT_DEFAULT). + + +-spec recvmsg(Socket, + BufSz, CtrlSz, + Flags, Timeout) -> {ok, MsgHdr} | {error, Reason} when + Socket :: socket(), + BufSz :: non_neg_integer(), + CtrlSz :: non_neg_integer(), + Flags :: recv_flags(), + Timeout :: timeout(), + MsgHdr :: msghdr(), + Reason :: term(). + +recvmsg(#socket{ref = SockRef}, BufSz, CtrlSz, Flags, Timeout) + when (is_integer(BufSz) andalso (BufSz >= 0)) andalso + (is_integer(CtrlSz) andalso (CtrlSz >= 0)) andalso + is_list(Flags) andalso + (is_integer(Timeout) orelse (Timeout =:= infinity)) -> + EFlags = enc_recv_flags(Flags), + do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout). + +do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, Timeout) -> + TS = timestamp(Timeout), + RecvRef = make_ref(), + case nif_recvmsg(SockRef, RecvRef, BufSz, CtrlSz, EFlags) of + {ok, _MsgHdr} = OK -> + OK; + + {error, eagain} -> + %% There is nothing just now, but we will be notified when there + %% is something to read (a select message). + NewTimeout = next_timeout(TS, Timeout), + receive + {select, SockRef, RecvRef, ready_input} -> + do_recvmsg(SockRef, BufSz, CtrlSz, EFlags, + next_timeout(TS, Timeout)); + + {'$socket', _, abort, {RecvRef, Reason}} -> + {error, Reason} + + after NewTimeout -> + cancel(SockRef, recvmsg, RecvRef), + {error, timeout} + end; + + {error, closed} = ERROR -> + do_close(SockRef), + ERROR; + + {error, _Reason} = ERROR -> + ERROR + + end. + + + +%% =========================================================================== +%% +%% readv - read data into multiple buffers +%% + + + +%% =========================================================================== +%% +%% close - close a file descriptor +%% + +-spec close(Socket) -> ok | {error, Reason} when + Socket :: socket(), + Reason :: term(). + +close(#socket{ref = SockRef}) -> + do_close(SockRef). + +do_close(SockRef) -> + case nif_close(SockRef) of + ok -> + nif_finalize_close(SockRef); + {ok, CloseRef} -> + %% We must wait + receive + {'$socket', _, close, CloseRef} -> +%% {close, CloseRef} -> + %% <KOLLA> + %% + %% WHAT HAPPENS IF THIS PROCESS IS KILLED + %% BEFORE WE CAN EXECUTE THE FINAL CLOSE??? + %% + %% </KOLLA> + nif_finalize_close(SockRef) + end; + {error, _} = ERROR -> + ERROR + end. + + + + +%% =========================================================================== +%% +%% shutdown - shut down part of a full-duplex connection +%% + +-spec shutdown(Socket, How) -> ok | {error, Reason} when + Socket :: socket(), + How :: shutdown_how(), + Reason :: term(). + +shutdown(#socket{ref = SockRef}, How) -> + try + begin + EHow = enc_shutdown_how(How), + nif_shutdown(SockRef, EHow) + end + catch + throw:T -> + T; + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + error:Reason -> + {error, Reason} + end. + + + + +%% =========================================================================== +%% +%% setopt - manipulate individual properties of a socket +%% +%% What properties are valid depend on what kind of socket it is +%% (domain, type and protocol) +%% If its an "invalid" option (or value), we should not crash but return some +%% useful error... +%% +%% <KOLLA> +%% +%% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING +%% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!! +%% +%% </KOLLA> + +-spec setopt(Socket, otp, otp_socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, socket, socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, ip, ip_socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, ipv6, ipv6_socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, tcp, tcp_socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, udp, udp_socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, sctp, sctp_socket_option(), Value) -> ok | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key, Value) -> ok | {error, Reason} when + Socket :: socket(), + Level :: non_neg_integer(), + Key :: non_neg_integer(), + Value :: binary(), + Reason :: term(). + +setopt(#socket{ref = SockRef}, Level, Key, Value) -> + try + begin + Domain = which_domain(SockRef), + Type = which_type(SockRef), + Protocol = which_protocol(SockRef), + {EIsEncoded, ELevel} = enc_setopt_level(Level), + EKey = enc_setopt_key(Level, Key, Domain, Type, Protocol), + EVal = enc_setopt_value(Level, Key, Value, Domain, Type, Protocol), + nif_setopt(SockRef, EIsEncoded, ELevel, EKey, EVal) + end + catch + throw:T -> + T; + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + error:Reason -> + {error, Reason} % Process more? + end. + + + + +%% =========================================================================== +%% +%% getopt - retrieve individual properties of a socket +%% +%% What properties are valid depend on what kind of socket it is +%% (domain, type and protocol). +%% If its an "invalid" option, we should not crash but return some +%% useful error... +%% +%% When specifying level as an integer, and therefor using "native mode", +%% we should make it possible to specify common types instead of the +%% value size. Example: int | bool | {string, pos_integer()} | non_neg_integer() +%% + +-spec getopt(Socket, otp, otp_socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, socket, socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, ip, ip_socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, ipv6, ipv6_socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, tcp, tcp_socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, udp, udp_socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, sctp, sctp_socket_option()) -> {ok, Value} | {error, Reason} when + Socket :: socket(), + Value :: term(), + Reason :: term() + ; (Socket, Level, Key) -> ok | {ok, Value} | {error, Reason} when + Socket :: socket(), + Level :: integer(), + Key :: {NativeOpt, ValueSize}, + NativeOpt :: integer(), + ValueSize :: int | bool | non_neg_integer(), + Value :: term(), + Reason :: term(). + +getopt(#socket{ref = SockRef}, Level, Key) -> + try + begin + Domain = which_domain(SockRef), + Type = which_type(SockRef), + Protocol = which_protocol(SockRef), + {EIsEncoded, ELevel} = enc_getopt_level(Level), + EKey = enc_getopt_key(Level, Key, Domain, Type, Protocol), + %% We may need to decode the value (for the same reason + %% we (may have) needed to encode the value for setopt). + case nif_getopt(SockRef, EIsEncoded, ELevel, EKey) of + ok -> + ok; + {ok, EVal} -> + Val = dec_getopt_value(Level, Key, EVal, + Domain, Type, Protocol), + {ok, Val}; + {error, _} = ERROR -> + ERROR + end + end + catch + throw:E:_S -> + E; + %% <WIN32-TEMPORARY> + error:nosup:S -> + erlang:raise(error, nosup, S); + %% </WIN32-TEMPORARY> + error:Reason:_Stack -> + {error, Reason} % Process more? + end. + + +%% These are internal "shortcut" functions for the options +%% domain, type and protocol. + +-spec which_domain(SockRef) -> Domain when + SockRef :: reference(), + Domain :: domain(). + +which_domain(SockRef) -> + case nif_getopt(SockRef, true, + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_DOMAIN) of + {ok, Domain} -> + Domain; + {error, _} = ERROR -> + throw(ERROR) + end. + + +-spec which_type(SockRef) -> Type when + SockRef :: reference(), + Type :: type(). + +which_type(SockRef) -> + case nif_getopt(SockRef, true, + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_TYPE) of + {ok, Type} -> + Type; + {error, _} = ERROR -> + throw(ERROR) + end. + +-spec which_protocol(SockRef) -> Protocol when + SockRef :: reference(), + Protocol :: protocol(). + +which_protocol(SockRef) -> + case nif_getopt(SockRef, true, + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_PROTOCOL) of + {ok, Proto} -> + Proto; + {error, _} = ERROR -> + throw(ERROR) + end. + + +%% =========================================================================== +%% +%% sockname - return the current address of the socket. +%% +%% + +-spec sockname(Socket) -> {ok, SockAddr} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: term(). + +sockname(#socket{ref = SockRef}) -> + nif_sockname(SockRef). + + + +%% =========================================================================== +%% +%% peername - return the address of the peer *connected* to the socket. +%% +%% + +-spec peername(Socket) -> {ok, SockAddr} | {error, Reason} when + Socket :: socket(), + SockAddr :: sockaddr(), + Reason :: term(). + +peername(#socket{ref = SockRef}) -> + nif_peername(SockRef). + + + +%% =========================================================================== +%% +%% Encode / decode +%% +%% =========================================================================== + +-spec enc_domain(Domain) -> non_neg_integer() when + Domain :: domain(). + +enc_domain(local) -> ?SOCKET_DOMAIN_LOCAL; +enc_domain(inet) -> ?SOCKET_DOMAIN_INET; +enc_domain(inet6) -> ?SOCKET_DOMAIN_INET6; +enc_domain(Domain) -> throw({error, {invalid_domain, Domain}}). + +-spec enc_type(Domain, Type) -> non_neg_integer() when + Domain :: domain(), + Type :: type(). + +%% What combos are valid? +enc_type(_, stream) -> ?SOCKET_TYPE_STREAM; +enc_type(_, dgram) -> ?SOCKET_TYPE_DGRAM; +enc_type(_, raw) -> ?SOCKET_TYPE_RAW; +enc_type(_, seqpacket) -> ?SOCKET_TYPE_SEQPACKET; +enc_type(_, Type) -> throw({error, {invalid_type, Type}}). + +-spec enc_protocol(Type, Protocol) -> non_neg_integer() | + {raw, non_neg_integer()} when + Type :: type(), + Protocol :: protocol(). + +enc_protocol(dgram, ip) -> ?SOCKET_PROTOCOL_IP; +enc_protocol(stream, tcp) -> ?SOCKET_PROTOCOL_TCP; +enc_protocol(dgram, udp) -> ?SOCKET_PROTOCOL_UDP; +enc_protocol(seqpacket, sctp) -> ?SOCKET_PROTOCOL_SCTP; +enc_protocol(raw, icmp) -> ?SOCKET_PROTOCOL_ICMP; +enc_protocol(raw, igmp) -> ?SOCKET_PROTOCOL_IGMP; +enc_protocol(raw, {raw, P} = RAW) when is_integer(P) -> RAW; +enc_protocol(Type, Proto) -> + throw({error, {invalid_protocol, {Type, Proto}}}). + + +-spec enc_send_flags(Flags) -> non_neg_integer() when + Flags :: send_flags(). + +enc_send_flags(Flags) -> + EFlags = [{confirm, ?SOCKET_SEND_FLAG_CONFIRM}, + {dontroute, ?SOCKET_SEND_FLAG_DONTROUTE}, + {eor, ?SOCKET_SEND_FLAG_EOR}, + {more, ?SOCKET_SEND_FLAG_MORE}, + {nosignal, ?SOCKET_SEND_FLAG_NOSIGNAL}, + {oob, ?SOCKET_SEND_FLAG_OOB}], + enc_flags(Flags, EFlags). + +-spec enc_recv_flags(Flags) -> non_neg_integer() when + Flags :: recv_flags(). + +enc_recv_flags(Flags) -> + EFlags = [{cmsg_cloexec, ?SOCKET_RECV_FLAG_CMSG_CLOEXEC}, + {errqueue, ?SOCKET_RECV_FLAG_ERRQUEUE}, + {oob, ?SOCKET_RECV_FLAG_OOB}, + {peek, ?SOCKET_RECV_FLAG_PEEK}, + {trunc, ?SOCKET_RECV_FLAG_TRUNC}], + enc_flags(Flags, EFlags). + + +enc_flags([], _) -> + 0; +enc_flags(Flags, EFlags) -> + F = fun(Flag, Acc) -> + case lists:keysearch(Flag, 1, EFlags) of + {value, {Flag, EFlag}} -> + Acc bor (1 bsl EFlag); + false -> + throw({error, {unknown_flag, Flag}}) + end + end, + lists:foldl(F, 0, Flags). + + +%% +++ Encode setopt level +++ + +-spec enc_setopt_level(Level) -> {IsEncoded, EncodedLevel} when + Level :: sockopt_level(), + IsEncoded :: boolean(), + EncodedLevel :: integer(). + +enc_setopt_level(otp) -> + {true, ?SOCKET_OPT_LEVEL_OTP}; +enc_setopt_level(socket) -> + {true, ?SOCKET_OPT_LEVEL_SOCKET}; +enc_setopt_level(ip) -> + {true, ?SOCKET_OPT_LEVEL_IP}; +enc_setopt_level(ipv6) -> + {true, ?SOCKET_OPT_LEVEL_IPV6}; +enc_setopt_level(tcp) -> + {true, ?SOCKET_OPT_LEVEL_TCP}; +enc_setopt_level(udp) -> + {true, ?SOCKET_OPT_LEVEL_UDP}; +enc_setopt_level(sctp) -> + {true, ?SOCKET_OPT_LEVEL_SCTP}; +%% Any option that is of an plain level must be provided as a binary +%% already fully encoded! +enc_setopt_level(L) when is_integer(L) -> + {false, L}. + + +%% +++ Encode setopt key +++ + +%% We should ...really... do something with the domain, type and protocol args... +%% Also, any option (key) which has an integer level (plain) must also be provided +%% in a plain mode, that is, as an integer. +%% Also, not all options are available on all platforms. That is something we +%% don't check here, but in the nif-code. + +enc_setopt_key(Level, Opt, Domain, Type, Protocol) -> + enc_sockopt_key(Level, Opt, set, Domain, Type, Protocol). + + +%% +++ Encode setopt value +++ +%% +%% For the most part this function does *not* do an actual encode, +%% it simply validates the value type. But in some cases it will +%% encode the value into an more "manageable" type. +%% It also handles "aliases" (see linger). + +-spec enc_setopt_value(otp, otp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (socket, socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (ip, ip_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (ipv6, ipv6_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (tcp, tcp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (udp, udp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (sctp, sctp_socket_option(), + Value, Domain, Type, Protocol) -> term() when + Value :: term(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Opt, + Value, Domain, Type, Protocol) -> term() when + Level :: integer(), + Opt :: integer(), + Value :: binary(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol(). + +enc_setopt_value(otp, debug, V, _, _, _) when is_boolean(V) -> + V; +enc_setopt_value(otp, iow, V, _, _, _) when is_boolean(V) -> + V; +enc_setopt_value(otp, controlling_process, V, _, _, _) when is_pid(V) -> + V; +enc_setopt_value(otp, rcvbuf, V, _, _, _) when (V =:= default) -> + 0; % This will cause the nif-code to choose the default value +enc_setopt_value(otp, rcvbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +%% N: Number of reads (when specifying length = 0) +%% V: Size of the "read" buffer +enc_setopt_value(otp, rcvbuf, {N, BufSz} = V, _, stream = _T, tcp = _P) + when (is_integer(N) andalso (N > 0)) andalso + (is_integer(BufSz) andalso (BufSz > 0)) -> + V; +enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, rcvctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when (V =:= default) -> + 0; +enc_setopt_value(otp, sndctrlbuf, V, _, _, _) when is_integer(V) andalso (V > 0) -> + V; +enc_setopt_value(otp = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(socket, bindtodevice, V, _D, _T, _P) when is_list(V) -> + V; +enc_setopt_value(socket, broadcast, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket, debug, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, dontroute, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket, keepalive, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket, linger, abort, D, T, P) -> + enc_setopt_value(socket, linger, {true, 0}, D, T, P); +enc_setopt_value(socket, linger, {OnOff, Secs} = V, _D, _T, _P) + when is_boolean(OnOff) andalso is_integer(Secs) andalso (Secs >= 0) -> + V; +enc_setopt_value(socket, oobinline, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket, peek_off, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, priority, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, rcvbuf, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, rcvlowat, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, rcvtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) + when is_integer(Sec) andalso is_integer(USec) -> + V; +enc_setopt_value(socket, reuseaddr, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket, reuseport, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket, sndbuf, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, sndlowat, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(socket, sndtimeo, #{sec := Sec, usec := USec} = V, _D, _T, _P) + when is_integer(Sec) andalso is_integer(USec) -> + V; +enc_setopt_value(socket, timestamp, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(socket = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(ip, add_membership, #{multiaddr := MA, + interface := IF} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> + V; +enc_setopt_value(ip, add_source_membership, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; +enc_setopt_value(ip, block_source, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; +enc_setopt_value(ip, drop_membership, #{multiaddr := MA, + interface := IF} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + ((IF =:= any) orelse (is_tuple(IF) andalso (size(IF) =:= 4))) -> + V; +enc_setopt_value(ip, drop_source_membership, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; +enc_setopt_value(ip, freebind, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(ip, hdrincl, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(ip, minttl, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(ip, msfilter, null = V, _D, _T, _P) -> + V; +enc_setopt_value(ip, msfilter, #{multiaddr := MA, + interface := IF, + fmode := FMode, + slist := SL} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + ((FMode =:= include) orelse (FMode =:= exclude)) andalso + is_list(SL) -> + ensure_ip_msfilter_slist(SL), + V; +enc_setopt_value(ip, mtu_discover, V, _D, _T, _P) + when (V =:= want) orelse + (V =:= dont) orelse + (V =:= do) orelse + (V =:= probe) orelse + is_integer(V) -> + V; +enc_setopt_value(ip, multicast_all, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, multicast_if, V, _D, _T, _P) + when (V =:= any) orelse (is_tuple(V) andalso (size(V) =:= 4)) -> + V; +enc_setopt_value(ip, multicast_loop, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, multicast_ttl, V, _D, _T, _P) + when is_integer(V) andalso (0 =< V) andalso (V =< 255) -> + V; +enc_setopt_value(ip, nodefrag, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, pktinfo, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recvdstaddr, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recverr, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recvif, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recvopts, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recvorigdstaddr, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recvtos, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, recvttl, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, retopts, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, router_alert, V, _D, _T, _P) + when is_integer(V) -> + V; +enc_setopt_value(ip, sendsrcaddr, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, tos, V, _D, _T, _P) + when (V =:= lowdelay) orelse + (V =:= throughput) orelse + (V =:= reliability) orelse + (V =:= mincost) orelse + is_integer(V) -> + V; +enc_setopt_value(ip, transparent, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ip, ttl, V, _D, _T, _P) + when is_integer(V) -> + V; +enc_setopt_value(ip, unblock_source, #{multiaddr := MA, + interface := IF, + sourceaddr := SA} = V, _D, _T, _P) + when (is_tuple(MA) andalso (size(MA) =:= 4)) andalso + (is_tuple(IF) andalso (size(IF) =:= 4)) andalso + (is_tuple(SA) andalso (size(SA) =:= 4)) -> + V; +enc_setopt_value(ip = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(ipv6, addrform, inet = V, _D, _T, _P) -> + enc_domain(V); +enc_setopt_value(ipv6, add_membership, #{multiaddr := MA, + interface := IF} = V, _D, _T, _P) + when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso + (is_integer(IF) andalso (IF >= 0))) -> + V; +%% Is this obsolete? When get, the result is enoprotoopt and in the +%% header file it says 'obsolete'... +%% But there might be (old?) versions of linux where it still works... +enc_setopt_value(ipv6, authhdr, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6, dstopts, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6, drop_membership, #{multiaddr := MA, + interface := IF} = V, _D, _T, _P) + when ((is_tuple(MA) andalso (size(MA) =:= 8)) andalso + (is_integer(IF) andalso (IF >= 0))) -> + V; +enc_setopt_value(ipv6, flowinfo, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6, hoplimit, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6, hopopts, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6, mtu, V, _D, _T, _P) when is_integer(V) -> + V; +enc_setopt_value(ipv6, mtu_discover, V, _D, _T, _P) + when (V =:= want) orelse + (V =:= dont) orelse + (V =:= do) orelse + (V =:= probe) orelse + is_integer(V) -> + V; +enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) + when (V =:= default) -> + -1; +enc_setopt_value(ipv6, multicast_hops, V, _D, _T, _P) + when is_integer(V) andalso (V >= 0) andalso (V =< 255) -> + V; +enc_setopt_value(ipv6, multicast_if, V, _D, _T, _P) + when is_integer(V) -> + V; +enc_setopt_value(ipv6, multicast_loop, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ipv6, recverr, V, _D, _T, _P) + when is_boolean(V) -> + V; +enc_setopt_value(ipv6, Opt, V, _D, _T, _P) + when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso + is_boolean(V) -> + V; +enc_setopt_value(ipv6, router_alert, V, _D, T, _P) + when is_integer(V) andalso (T =:= raw) -> + V; +enc_setopt_value(ipv6, rthdr, V, _D, T, _P) + when is_boolean(V) andalso ((T =:= dgram) orelse (T =:= raw)) -> + V; +enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P) + when (V =:= default) -> + -1; +enc_setopt_value(ipv6, unicast_hops, V, _D, _T, _P) + when is_integer(V) andalso (V >= 0) andalso (V =< 255) -> + V; +enc_setopt_value(ipv6, v6only, V, _D, _T, _P) when is_boolean(V) -> + V; +enc_setopt_value(ipv6 = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(tcp, congestion, V, _D, T, P) + when is_list(V) andalso + (T =:= stream) andalso + (P =:= tcp) -> + V; +enc_setopt_value(tcp, maxseg, V, _D, T, P) + when is_integer(V) andalso + (T =:= stream) andalso + (P =:= tcp) -> + V; +enc_setopt_value(tcp, nodelay, V, _D, T, P) + when is_boolean(V) andalso + (T =:= stream) andalso + (P =:= tcp) -> + V; +enc_setopt_value(tcp = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +enc_setopt_value(udp, cork, V, _D, T, P) + when is_boolean(V) andalso (T =:= dgram) andalso (P =:= udp) -> + V; +enc_setopt_value(udp = L, Opt, _V, _D, _T, _P) -> + not_supported({L, Opt}); + +enc_setopt_value(sctp, associnfo, #{assoc_id := AssocId, + asocmaxrxt := MaxRxt, + num_peer_dests := NumPeerDests, + peer_rwnd := PeerRWND, + local_rwnd := LocalRWND, + cookie_life := CLife} = V, + _D, _T, P) + when is_integer(AssocId) andalso + is_integer(MaxRxt) andalso (MaxRxt >= 0) andalso + is_integer(NumPeerDests) andalso (NumPeerDests >= 0) andalso + is_integer(PeerRWND) andalso (PeerRWND >= 0) andalso + is_integer(LocalRWND) andalso (LocalRWND >= 0) andalso + is_integer(CLife) andalso (CLife >= 0) andalso + (P =:= sctp) -> + V; +enc_setopt_value(sctp, autoclose, V, _D, _T, P) + when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) -> + V; +enc_setopt_value(sctp, disable_fragments, V, _D, _T, P) + when is_boolean(V) andalso (P =:= sctp) -> + V; +enc_setopt_value(sctp, events, #{data_in := DataIn, + association := Assoc, + address := Addr, + send_failure := SndFailure, + peer_error := PeerError, + shutdown := Shutdown, + partial_delivery := PartialDelivery, + adaptation_layer := AdaptLayer, + authentication := Auth, + sender_dry := SndDry} = V, _D, _T, P) + when is_boolean(DataIn) andalso + is_boolean(Assoc) andalso + is_boolean(Addr) andalso + is_boolean(SndFailure) andalso + is_boolean(PeerError) andalso + is_boolean(Shutdown) andalso + is_boolean(PartialDelivery) andalso + is_boolean(AdaptLayer) andalso + is_boolean(Auth) andalso + is_boolean(SndDry) andalso + (P =:= sctp) -> + V; +enc_setopt_value(sctp, initmsg, #{num_outstreams := NumOut, + max_instreams := MaxIn, + max_attempts := MaxAttempts, + max_init_timeo := MaxInitTO} = V, + _D, _T, P) + when is_integer(NumOut) andalso (NumOut >= 0) andalso + is_integer(MaxIn) andalso (MaxIn >= 0) andalso + is_integer(MaxAttempts) andalso (MaxAttempts >= 0) andalso + is_integer(MaxInitTO) andalso (MaxInitTO >= 0) andalso + (P =:= sctp) -> + V; +enc_setopt_value(sctp, maxseg, V, _D, _T, P) + when is_integer(V) andalso (V >= 0) andalso (P =:= sctp) -> + V; +enc_setopt_value(sctp, nodelay, V, _D, _T, P) + when is_boolean(V) andalso (P =:= sctp) -> + V; +enc_setopt_value(sctp, rtoinfo, #{assoc_id := AssocId, + initial := Init, + max := Max, + min := Min} = V, + _D, _T, P) + when is_integer(AssocId) andalso + is_integer(Init) andalso (Init >= 0) andalso + is_integer(Max) andalso (Max >= 0) andalso + is_integer(Min) andalso (Min >= 0) andalso + (P =:= sctp) -> + V; +enc_setopt_value(sctp = L, Opt, V, _D, _T, _P) -> + not_supported({L, Opt, V}); + +%% enc_setopt_value(raw = L, Opt, _V, _D, _T, _P) -> +%% not_supported({L, Opt}); + +%% Is this correct? What about getopt? +enc_setopt_value(L, Opt, V, _, _, _) + when is_integer(L) andalso is_integer(Opt) andalso is_binary(V) -> + V. + + + + +%% +++ Encode getopt value +++ + +enc_getopt_level(Level) -> + enc_setopt_level(Level). + + +%% +++ Encode getopt key +++ + +enc_getopt_key(Level, Opt, Domain, Type, Protocol) -> + enc_sockopt_key(Level, Opt, get, Domain, Type, Protocol). + + +%% +++ Decode getopt value +++ +%% +%% For the most part, we simply let the value pass through, but for some +%% values we may need to do an actual decode. +%% + +%% Let the user deal with this for now... +dec_getopt_value(_L, _Opt, V, _D, _T, _P) -> + V. + + + +%% +++ Encode socket option key +++ + +%% Most options are usable both for set and get, but some are +%% are only available for e.g. get. +-spec enc_sockopt_key(Level, Opt, + Direction, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: otp, + Direction :: set | get, + Opt :: otp_socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: socket, + Direction :: set | get, + Opt :: socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: ip, + Direction :: set | get, + Opt :: ip_socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: ipv6, + Direction :: set | get, + Opt :: ipv6_socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: tcp, + Direction :: set | get, + Opt :: tcp_socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: udp, + Direction :: set | get, + Opt :: udp_socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: sctp, + Direction :: set | get, + Opt :: sctp_socket_option(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: integer(), + Direction :: set, + Opt :: integer(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol() + ; (Level, Direction, Opt, + Domain, Type, Protocol) -> non_neg_integer() when + Level :: integer(), + Direction :: get, + Opt :: {NativeOpt, ValueSize}, + NativeOpt :: integer(), + ValueSize :: non_neg_integer(), + Domain :: domain(), + Type :: type(), + Protocol :: protocol(). + + +%% +++ OTP socket options +++ +enc_sockopt_key(otp, debug, _, _, _, _) -> + ?SOCKET_OPT_OTP_DEBUG; +enc_sockopt_key(otp, iow, _, _, _, _) -> + ?SOCKET_OPT_OTP_IOW; +enc_sockopt_key(otp, controlling_process, _, _, _, _) -> + ?SOCKET_OPT_OTP_CTRL_PROC; +enc_sockopt_key(otp, rcvbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_RCVBUF; +enc_sockopt_key(otp, rcvctrlbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_RCVCTRLBUF; +enc_sockopt_key(otp, sndctrlbuf, _, _, _, _) -> + ?SOCKET_OPT_OTP_SNDCTRLBUF; +enc_sockopt_key(otp, fd, get = _Dir, _, _, _) -> + ?SOCKET_OPT_OTP_FD; +enc_sockopt_key(otp = L, Opt, _, _, _, _) -> + not_supported({L, Opt}); + +%% +++ SOCKET socket options +++ +enc_sockopt_key(socket = _L, acceptconn = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_ACCEPTCONN; +enc_sockopt_key(socket = L, acceptfilter = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +%% Before linux 3.8, this socket option could be set. +%% Maximum size of buffer for name: IFNAMSZIZ +%% So, we let the implementation decide. +enc_sockopt_key(socket = _L, bindtodevice = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_BINDTODEVICE; +enc_sockopt_key(socket, broadcast = _Opt, _Dir, _D, dgram = _T, _P) -> + ?SOCKET_OPT_SOCK_BROADCAST; +enc_sockopt_key(socket = L, busy_poll = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = _L, debug = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_DEBUG; +enc_sockopt_key(socket, domain = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_DOMAIN; +enc_sockopt_key(socket, dontroute = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_DONTROUTE; +enc_sockopt_key(socket = L, error = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +%% This is only for connection-oriented sockets, but who are those? +%% Type = stream or Protocol = tcp? +%% For now, we just let is pass and it will fail later if not ok... +enc_sockopt_key(socket, keepalive = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_KEEPALIVE; +enc_sockopt_key(socket, linger = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_LINGER; +enc_sockopt_key(socket = L, mark = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = _L, oobinline = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_OOBINLINE; +enc_sockopt_key(socket = L, passcred = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = _L, peek_off = _Opt, _Dir, local = _D, _T, _P) -> + ?SOCKET_OPT_SOCK_PEEK_OFF; +enc_sockopt_key(socket = L, peekcred = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket, priority = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_PRIORITY; +enc_sockopt_key(socket, protocol = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_PROTOCOL; +enc_sockopt_key(socket, rcvbuf = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_RCVBUF; +enc_sockopt_key(socket = L, rcvbufforce = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +%% May not work on linux. +enc_sockopt_key(socket = _L, rcvlowat = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_RCVLOWAT; +enc_sockopt_key(socket = _L, rcvtimeo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_RCVTIMEO; +enc_sockopt_key(socket = _L, reuseaddr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_REUSEADDR; +enc_sockopt_key(socket = _L, reuseport = _Opt, _Dir, D, _T, _P) + when ((D =:= inet) orelse (D =:= inet6)) -> + ?SOCKET_OPT_SOCK_REUSEPORT; +enc_sockopt_key(socket = L, rxq_ovfl = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = L, setfib = Opt, set = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(socket = _L, sndbuf = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_SNDBUF; +enc_sockopt_key(socket = L, sndbufforce = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +%% Not changeable on linux. +enc_sockopt_key(socket = _L, sndlowat = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_SNDLOWAT; +enc_sockopt_key(socket = _L, sndtimeo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_SNDTIMEO; +enc_sockopt_key(socket = _L, timestamp = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_TIMESTAMP; +enc_sockopt_key(socket = _L, type = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SOCK_TYPE; +enc_sockopt_key(socket = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); + +%% +++ IP socket options +++ +enc_sockopt_key(ip = _L, add_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_ADD_MEMBERSHIP; +enc_sockopt_key(ip = _L, add_source_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_ADD_SOURCE_MEMBERSHIP; +enc_sockopt_key(ip = _L, block_source = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_BLOCK_SOURCE; +%% FreeBSD only? +%% Only respected on udp and raw ip (unless the hdrincl option has been set). +enc_sockopt_key(ip = L, dontfrag = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ip = _L, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_DROP_MEMBERSHIP; +enc_sockopt_key(ip = _L, drop_source_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_DROP_SOURCE_MEMBERSHIP; +%% Linux only? +enc_sockopt_key(ip = _L, freebind = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_FREEBIND; +enc_sockopt_key(ip = _L, hdrincl = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_HDRINCL; +enc_sockopt_key(ip = _L, minttl = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_MINTTL; +enc_sockopt_key(ip = _L, msfilter = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MSFILTER; +enc_sockopt_key(ip = _L, mtu = _Opt, get = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MTU; +enc_sockopt_key(ip = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MTU_DISCOVER; +enc_sockopt_key(ip = _L, multicast_all = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_ALL; +enc_sockopt_key(ip = _L, multicast_if = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_IF; +enc_sockopt_key(ip = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_LOOP; +enc_sockopt_key(ip = _L, multicast_ttl = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_MULTICAST_TTL; +enc_sockopt_key(ip = _L, nodefrag = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_NODEFRAG; +enc_sockopt_key(ip = L, options = Opt, _Dir, _D, _T, _P) -> + not_supported({Opt, L}); +enc_sockopt_key(ip = _L, pktinfo = _Opt, _Dir, _D, dgram = _T, _P) -> + ?SOCKET_OPT_IP_PKTINFO; +enc_sockopt_key(ip = _L, recvdstaddr = _Opt, _Dir, _D, T, _P) when (T =:= dgram) -> + ?SOCKET_OPT_IP_RECVDSTADDR; +enc_sockopt_key(ip = _L, recverr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_RECVERR; +enc_sockopt_key(ip = _L, recvif = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IP_RECVIF; +enc_sockopt_key(ip = _L, recvopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> + ?SOCKET_OPT_IP_RECVOPTS; +enc_sockopt_key(ip = _L, recvorigdstaddr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_RECVORIGDSTADDR; +enc_sockopt_key(ip, recvtos = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_RECVTOS; +enc_sockopt_key(ip = _L, recvttl = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> + ?SOCKET_OPT_IP_RECVTTL; +enc_sockopt_key(ip = _L, retopts = _Opt, _Dir, _D, T, _P) when (T =/= stream) -> + ?SOCKET_OPT_IP_RETOPTS; +enc_sockopt_key(ip, router_alert = _Opt, _Dir, _D, raw = _T, _P) -> + ?SOCKET_OPT_IP_ROUTER_ALERT; +enc_sockopt_key(ip, sendsrcaddr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_SENDSRCADDR; +%% On FreeBSD it specifies that this option is only valid +%% for stream, dgram and "some" raw sockets... +%% No such condition on linux (in the man page)... +enc_sockopt_key(ip, tos = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_TOS; +enc_sockopt_key(ip = _L, transparent = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_TRANSPARENT; +enc_sockopt_key(ip, ttl = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_TTL; +enc_sockopt_key(ip = _L, unblock_source = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IP_UNBLOCK_SOURCE; +enc_sockopt_key(ip = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); + +%% IPv6 socket options +enc_sockopt_key(ipv6 = _L, addrform = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_ADDRFORM; +enc_sockopt_key(ipv6, add_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_ADD_MEMBERSHIP; +enc_sockopt_key(ipv6 = _L, authhdr = _Opt, _Dir, _D, T, _P) + when ((T =:= dgram) orelse (T =:= raw)) -> + ?SOCKET_OPT_IPV6_AUTHHDR; +enc_sockopt_key(ipv6 = L, auth_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, checksum = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6, drop_membership = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_DROP_MEMBERSHIP; +enc_sockopt_key(ipv6 = _L, dstopts = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IPV6_DSTOPTS; +enc_sockopt_key(ipv6 = L, esp_trans_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, esp_network_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, flowinfo = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IPV6_DSTOPTS; +enc_sockopt_key(ipv6, hoplimit = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IPV6_HOPLIMIT; +enc_sockopt_key(ipv6 = _L, hopopts = _Opt, _Dir, _D, T, _P) + when ((T =:= dgram) orelse (T =:= raw)) -> + ?SOCKET_OPT_IPV6_HOPOPTS; +enc_sockopt_key(ipv6 = L, ipcomp_level = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, join_group = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, leave_group = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, mtu = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MTU; +enc_sockopt_key(ipv6 = _L, mtu_discover = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MTU_DISCOVER; +enc_sockopt_key(ipv6 = _L, multicast_hops = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MULTICAST_HOPS; +enc_sockopt_key(ipv6 = _L, multicast_if = _Opt, _Dir, _D, T, _P) + when (T =:= dgram) orelse (T =:= raw) -> + ?SOCKET_OPT_IPV6_MULTICAST_IF; +enc_sockopt_key(ipv6 = _L, multicast_loop = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_MULTICAST_LOOP; +enc_sockopt_key(ipv6 = L, portrange = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = L, pktoptions = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, recverr = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_RECVERR; +enc_sockopt_key(ipv6 = _L, Opt, _Dir, _D, T, _P) + when ((Opt =:= recvpktinfo) orelse (Opt =:= pktinfo)) andalso + ((T =:= dgram) orelse (T =:= raw)) -> + ?SOCKET_OPT_IPV6_RECVPKTINFO; +enc_sockopt_key(ipv6 = L, recvtclass = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, router_alert = _Opt, _Dir, _D, T, _P) when (T =:= raw) -> + ?SOCKET_OPT_IPV6_ROUTER_ALERT; +enc_sockopt_key(ipv6 = _L, rthdr = _Opt, _Dir, _D, T, _P) + when ((T =:= dgram) orelse (T =:= raw)) -> + ?SOCKET_OPT_IPV6_RTHDR; +enc_sockopt_key(ipv6 = L, tclass = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, unicast_hops = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_UNICAST_HOPS; +enc_sockopt_key(ipv6 = L, use_min_mtu = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(ipv6 = _L, v6only = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_IPV6_V6ONLY; +enc_sockopt_key(ipv6 = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); + +%% TCP socket options +%% There are other options that would be useful; info, +%% but they are difficult to get portable... +enc_sockopt_key(tcp, congestion = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_TCP_CONGESTION; +enc_sockopt_key(tcp = L, cork = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, keepidle = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, keepintvl = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, keepcnt = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp, maxseg = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_TCP_MAXSEG; +enc_sockopt_key(tcp = L, md5sig = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp, nodelay = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_TCP_NODELAY; +enc_sockopt_key(tcp = L, noopt = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, nopush = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, syncnt = Opt, _Dir, _D, _T, _P) -> % Only set? 1..255 + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, user_timeout = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(tcp = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); + +%% UDP socket options +enc_sockopt_key(udp, cork = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_UDP_CORK; +enc_sockopt_key(udp = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); + +%% SCTP socket options +enc_sockopt_key(sctp = L, adaption_layer = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, associnfo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_ASSOCINFO; +enc_sockopt_key(sctp = L, auth_active_key = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_asconf = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_chunk = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_key = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, auth_delete_key = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp, autoclose = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_AUTOCLOSE; +enc_sockopt_key(sctp = L, context = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, default_send_params = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, delayed_ack_time = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, disable_fragments = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_DISABLE_FRAGMENTS; +enc_sockopt_key(sctp = L, hmac_ident = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, events = _Opt, set = _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_EVENTS; +enc_sockopt_key(sctp = L, explicit_eor = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, fragment_interleave = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, get_peer_addr_info = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, initmsg = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_INITMSG; +enc_sockopt_key(sctp = L, i_want_mapped_v4_addr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, local_auth_chunks = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, maxseg = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_MAXSEG; +enc_sockopt_key(sctp = L, maxburst = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp, nodelay = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_NODELAY; +enc_sockopt_key(sctp = L, partial_delivery_point = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, peer_addr_params = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, peer_auth_chunks = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, primary_addr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, reset_streams = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = _L, rtoinfo = _Opt, _Dir, _D, _T, _P) -> + ?SOCKET_OPT_SCTP_RTOINFO; +enc_sockopt_key(sctp = L, set_peer_primary_addr = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, status = Opt, get = _Dir, _D, _T, _P) -> + not_supported({L, Opt}); % ?SOCKET_OPT_SCTP_RTOINFO; +enc_sockopt_key(sctp = L, use_exp_recvinfo = Opt, _Dir, _D, _T, _P) -> + not_supported({L, Opt}); +enc_sockopt_key(sctp = L, UnknownOpt, _Dir, _D, _T, _P) -> + unknown({L, UnknownOpt}); + +%% +++ "Native" socket options +++ +enc_sockopt_key(Level, Opt, set = _Dir, _D, _T, _P) + when is_integer(Level) andalso is_integer(Opt) -> + Opt; +enc_sockopt_key(Level, {NativeOpt, ValueSize} = Opt, get = _Dir, _D, _T, _P) + when is_integer(Level) andalso + is_integer(NativeOpt) andalso + ((is_integer(ValueSize) andalso (ValueSize >= 0)) orelse + ((ValueSize =:= int) orelse (ValueSize =:= bool))) -> + Opt; + +enc_sockopt_key(Level, Opt, _Dir, _Domain, _Type, _Protocol) -> + unknown({Level, Opt}). + + + +enc_shutdown_how(read) -> + ?SOCKET_SHUTDOWN_HOW_READ; +enc_shutdown_how(write) -> + ?SOCKET_SHUTDOWN_HOW_WRITE; +enc_shutdown_how(read_write) -> + ?SOCKET_SHUTDOWN_HOW_READ_WRITE. + + + + +%% =========================================================================== +%% +%% Misc utility functions +%% +%% =========================================================================== + +ensure_ip_msfilter_slist(SL) -> + EnsureSA = fun(SA) when is_tuple(SA) andalso (size(SA) =:= 4) -> ok; + (_) -> einval() + end, + lists:foreach(EnsureSA, SL). + + +ensure_sockaddr(#{family := inet} = SockAddr) -> + maps:merge(?SOCKADDR_IN4_DEFAULTS, SockAddr); +ensure_sockaddr(#{family := inet6} = SockAddr) -> + maps:merge(?SOCKADDR_IN6_DEFAULTS, SockAddr); +ensure_sockaddr(#{family := local, path := Path} = SockAddr) + when is_list(Path) andalso + (length(Path) > 0) andalso + (length(Path) =< 255) -> + BinPath = unicode:characters_to_binary(Path, file:native_name_encoding()), + ensure_sockaddr(SockAddr#{path => BinPath}); +ensure_sockaddr(#{family := local, path := Path} = SockAddr) + when is_binary(Path) andalso + (byte_size(Path) > 0) andalso + (byte_size(Path) =< 255) -> + SockAddr; +ensure_sockaddr(_SockAddr) -> + einval(). + + + +cancel(SockRef, Op, OpRef) -> + case nif_cancel(SockRef, Op, OpRef) of + %% The select has already completed + {error, select_sent} -> + flush_select_msgs(SockRef, OpRef); + Other -> + Other + end. + +flush_select_msgs(SockRef, Ref) -> + receive + {select, SockRef, Ref, _} -> + flush_select_msgs(SockRef, Ref) + after 0 -> + 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). + + +%% A timestamp in ms + +timestamp(infinity) -> + undefined; +timestamp(_) -> + timestamp(). + +timestamp() -> + {A,B,C} = os:timestamp(), + A*1000000000+B*1000+(C div 1000). + +next_timeout(_, infinity = Timeout) -> + Timeout; +next_timeout(TS, Timeout) -> + NewTimeout = Timeout - tdiff(TS, timestamp()), + if + (NewTimeout > 0) -> + NewTimeout; + true -> + 0 + end. + +tdiff(T1, T2) -> + T2 - T1. + + + +%% p(F) -> +%% p(F, []). + +%% p(F, A) -> +%% p(get(sname), F, A). + +%% p(undefined, F, A) -> +%% p("***", F, A); +%% p(SName, F, A) -> +%% io:format(user,"[~s,~p] " ++ F ++ "~n", [SName, self()|A]), +%% io:format("[~s,~p] " ++ F ++ "~n", [SName, self()|A]). + + + +%% =========================================================================== +%% +%% Error functions +%% +%% =========================================================================== + +-spec not_supported(What) -> no_return() when + What :: term(). + +not_supported(What) -> + error({not_supported, What}). + +-spec unknown(What) -> no_return() when + What :: term(). + +unknown(What) -> + error({unknown, What}). + +-spec einval() -> no_return(). + +einval() -> + error(einval). + +-spec error(Reason) -> no_return() when + Reason :: term(). + +error(Reason) -> + throw({error, Reason}). + + +%% =========================================================================== +%% +%% Below follows the actual NIF-functions. +%% +%% =========================================================================== + +nif_info() -> + erlang:nif_error(undef). + +nif_supports(_Key) -> + erlang:nif_error(undef). + +nif_open(_Domain, _Type, _Protocol, _Extra) -> + erlang:nif_error(undef). + +nif_bind(_SRef, _SockAddr) -> + erlang:nif_error(undef). + +nif_bind(_SRef, _SockAddrs, _Action) -> + erlang:nif_error(undef). + +nif_connect(_SRef, _SockAddr) -> + erlang:nif_error(undef). + +nif_finalize_connection(_SRef) -> + erlang:nif_error(undef). + +nif_listen(_SRef, _Backlog) -> + erlang:nif_error(undef). + +nif_accept(_SRef, _Ref) -> + erlang:nif_error(undef). + +nif_send(_SockRef, _SendRef, _Data, _Flags) -> + erlang:nif_error(undef). + +nif_sendto(_SRef, _SendRef, _Data, _Dest, _Flags) -> + erlang:nif_error(undef). + +nif_sendmsg(_SRef, _SendRef, _MsgHdr, _Flags) -> + erlang:nif_error(undef). + +nif_recv(_SRef, _RecvRef, _Length, _Flags) -> + erlang:nif_error(undef). + +nif_recvfrom(_SRef, _RecvRef, _Length, _Flags) -> + erlang:nif_error(undef). + +nif_recvmsg(_SRef, _RecvRef, _BufSz, _CtrlSz, _Flags) -> + erlang:nif_error(undef). + +nif_cancel(_SRef, _Op, _Ref) -> + erlang:nif_error(undef). + +nif_close(_SRef) -> + erlang:nif_error(undef). + +nif_shutdown(_SRef, _How) -> + erlang:nif_error(undef). + +nif_finalize_close(_SRef) -> + erlang:nif_error(undef). + +nif_setopt(_Ref, _IsEnc, _Lev, _Key, _Val) -> + erlang:nif_error(undef). + +nif_getopt(_Ref, _IsEnc, _Lev, _Key) -> + erlang:nif_error(undef). + +nif_sockname(_Ref) -> + erlang:nif_error(undef). + +nif_peername(_Ref) -> + erlang:nif_error(undef). + |