 /*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2018-2020. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 *
 * ----------------------------------------------------------------------
 * Purpose : The NIF (C) part of the socket interface
 *
 * All of the nif-functions which are part of the API has two parts.
 * The first function is called 'nif_<something>', e.g. nif_open.
 * This does the initial validation and argument processing and then 
 * calls the function that does the actual work. This is called
 * 'esock_<something>'.
 * ----------------------------------------------------------------------
 *
 *
 * This is just a code snippet in case there is need of extra debugging
 *
 * esock_dbg_printf("DEMONP", "[%d] %s: %T\r\n",
 *                  descP->sock, slogan,
 *                  esock_make_monitor_term(env, &mon));
 *
 */

#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 <stdint.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
#ifdef HAVE_LINUX_ERRQUEUE_H
#include <linux/types.h>     /* On some (I assume) "old" linux,    *
                              * for example SLES 10 SP1, this is   *
                              * not (explicitly) included by the   *
                              * errqueue file. And for some reason *
                              * configure does not detect this.    *
                              * So, to simplify, we include here.  */
#include <linux/errqueue.h>
#include <linux/icmp.h>
#include <linux/icmpv6.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(ERTS_INLINE)
#  define ESOCK_INLINE ERTS_INLINE
#else
#  if defined(__GNUC__)
#    define ESOCK_INLINE __inline__
#  elif defined(__WIN32__)
#    define ESOCK_INLINE __inline
#  else
#    define ESOCK_INLINE
#  endif
#endif


#if defined(SOL_IPV6) || defined(IPPROTO_IPV6)
#define HAVE_IPV6
#endif

/* All platforms fail on malloc errors. */
#define FATAL_MALLOC


/* Debug stuff... */
#define ESOCK_GLOBAL_DEBUG_DEFAULT FALSE
#define ESOCK_DEBUG_DEFAULT        FALSE

/* Counters and stuff (Don't know where to send this stuff anyway) */
#define ESOCK_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 ESOCK_OPT_TCP_CONGESTION_NAME_MAX TCP_CA_NAME_MAX
#else
/* This is really excessive, but just in case... */
#define ESOCK_OPT_TCP_CONGESTION_NAME_MAX 256
#endif


#if defined(TCP_CONGESTION) || defined(SO_BINDTODEVICE)
#define USE_GETOPT_STR_OPT
#define USE_SETOPT_STR_OPT
#endif



/* *** Socket state defs, debug only *** */

#define ESOCK_STATE_BOUND        0x0001 /* readState */
#define ESOCK_STATE_LISTENING    0x0002 /* readState */
#define ESOCK_STATE_ACCEPTING    0x0004 /* readState */
#define ESOCK_STATE_CONNECTING   0x0010 /* writeState */
#define ESOCK_STATE_CONNECTED    0x0020 /* writeState */
#define ESOCK_STATE_DTOR         0x8000

#define IS_CLOSED(d)                            \
    ((d)->sock == INVALID_SOCKET)

#define IS_CLOSING(d)                           \
    ((d)->closing)

#define IS_OPEN(d)                              \
    (! (IS_CLOSED(d) || IS_CLOSING(d)) )

    
#define ESOCK_GET_RESOURCE(ENV, REF, RES) \
    enif_get_resource((ENV), (REF), esocks, (RES))

#define ESOCK_RECV_BUFFER_COUNT_DEFAULT     0
#define ESOCK_RECV_BUFFER_SIZE_DEFAULT      8192
#define ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT 1024
#define ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT 1024

#define VT2S(__VT__) (((__VT__) == ESOCK_OPT_VALUE_TYPE_UNSPEC) ? "unspec" : \
                      (((__VT__) == ESOCK_OPT_VALUE_TYPE_INT) ? "int" : \
                       ((__VT__) == ESOCK_OPT_VALUE_TYPE_BOOL) ? "bool" : \
                       "undef"))

#define ESOCK_OPT_VALUE_TYPE_UNSPEC 0
#define ESOCK_OPT_VALUE_TYPE_INT    1
#define ESOCK_OPT_VALUE_TYPE_BOOL   2

#define ESOCK_DESC_PATTERN_CREATED 0x03030303
#define ESOCK_DESC_PATTERN_DTOR    0xC0C0C0C0

/*
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;
*/

/*----------------------------------------------------------------------------
 * Interface constants.
 *
 * This section must be "identical" to the corresponding socket.erl
 */

#define ESOCK_SEND_FLAG_CONFIRM         (1 << 0)
#define ESOCK_SEND_FLAG_DONTROUTE       (1 << 1)
#define ESOCK_SEND_FLAG_EOR             (1 << 2)
#define ESOCK_SEND_FLAG_MORE            (1 << 3)
#define ESOCK_SEND_FLAG_NOSIGNAL        (1 << 4)
#define ESOCK_SEND_FLAG_OOB             (1 << 5)
/**/
#define ESOCK_SEND_FLAG_MASK            ((1 << 6) - 1)

#define ESOCK_RECV_FLAG_CMSG_CLOEXEC    (1 << 0)
#define ESOCK_RECV_FLAG_ERRQUEUE        (1 << 1)
#define ESOCK_RECV_FLAG_OOB             (1 << 2)
#define ESOCK_RECV_FLAG_PEEK            (1 << 3)
#define ESOCK_RECV_FLAG_TRUNC           (1 << 4)
/**/
#define ESOCK_RECV_FLAG_MASK            ((1 << 5) - 1)


/* shutdown how */
#define ESOCK_SHUTDOWN_HOW_RD    0
#define ESOCK_SHUTDOWN_HOW_WR    1
#define ESOCK_SHUTDOWN_HOW_RDWR  2



/* domain */
#define ESOCK_DOMAIN_LOCAL       1
#define ESOCK_DOMAIN_INET        2
#define ESOCK_DOMAIN_INET6       3

/* type */
#define ESOCK_TYPE_STREAM        101
#define ESOCK_TYPE_DGRAM         102
#define ESOCK_TYPE_RAW           103
// #define ESOCK_TYPE_RDM           104
#define ESOCK_TYPE_SEQPACKET     105

/* protocol */
#define ESOCK_PROTOCOL_DEFAULT   200
#define ESOCK_PROTOCOL_IP        201
#define ESOCK_PROTOCOL_TCP       202
#define ESOCK_PROTOCOL_UDP       203
#define ESOCK_PROTOCOL_SCTP      204
#define ESOCK_PROTOCOL_ICMP      205
#define ESOCK_PROTOCOL_IGMP      206

/* option level */
#define ESOCK_OPT_LEVEL_OTP        301
#define ESOCK_OPT_LEVEL_SOCKET     302
#define ESOCK_OPT_LEVEL_IP         303
#define ESOCK_OPT_LEVEL_IPV6       304
#define ESOCK_OPT_LEVEL_TCP        305
#define ESOCK_OPT_LEVEL_UDP        306
#define ESOCK_OPT_LEVEL_SCTP       307

/* level 'otp' options */
#define ESOCK_OPT_OTP_DEBUG        1001
#define ESOCK_OPT_OTP_IOW          1002
#define ESOCK_OPT_OTP_CTRL_PROC    1003
#define ESOCK_OPT_OTP_RCVBUF       1004
//#define ESOCK_OPT_OTP_SNDBUF       1005
#define ESOCK_OPT_OTP_RCVCTRLBUF   1006
#define ESOCK_OPT_OTP_SNDCTRLBUF   1007
#define ESOCK_OPT_OTP_FD           1008
#define ESOCK_OPT_OTP_META         1009
#define ESOCK_OPT_OTP_USE_REGISTRY 1010
/**/
#define ESOCK_OPT_OTP_DOMAIN       1999 // INTERNAL AND ONLY GET
#define ESOCK_OPT_OTP_TYPE         1998 // INTERNAL AND ONLY GET
#define ESOCK_OPT_OTP_PROTOCOL     1997 // INTERNAL AND ONLY GET
#define ESOCK_OPT_OTP_DTP          1996 // INTERNAL AND ONLY GET

/* level 'socket' options */
#define ESOCK_OPT_SOCK_ACCEPTCONN    2001
//#define ESOCK_OPT_SOCK_ACCEPTFILTER  2002
#define ESOCK_OPT_SOCK_BINDTODEVICE  2003
#define ESOCK_OPT_SOCK_BROADCAST     2004
//#define ESOCK_OPT_SOCK_BUSY_POLL     2005
#define ESOCK_OPT_SOCK_DEBUG         2006
#define ESOCK_OPT_SOCK_DOMAIN        2007
#define ESOCK_OPT_SOCK_DONTROUTE     2008
//#define ESOCK_OPT_SOCK_ERROR         2009
#define ESOCK_OPT_SOCK_KEEPALIVE     2010
#define ESOCK_OPT_SOCK_LINGER        2011
//#define ESOCK_OPT_SOCK_MARK          2012
#define ESOCK_OPT_SOCK_OOBINLINE     2013
#define ESOCK_OPT_SOCK_PASSCRED      2014
#define ESOCK_OPT_SOCK_PEEK_OFF      2015
//#define ESOCK_OPT_SOCK_PEERCRED      2016
#define ESOCK_OPT_SOCK_PRIORITY      2017
#define ESOCK_OPT_SOCK_PROTOCOL      2018
#define ESOCK_OPT_SOCK_RCVBUF        2019
//#define ESOCK_OPT_SOCK_RCVBUFFORCE   2020
#define ESOCK_OPT_SOCK_RCVLOWAT      2021
#define ESOCK_OPT_SOCK_RCVTIMEO      2022
#define ESOCK_OPT_SOCK_REUSEADDR     2023
#define ESOCK_OPT_SOCK_REUSEPORT     2024
//#define ESOCK_OPT_SOCK_RXQ_OVFL      2025
//#define ESOCK_OPT_SOCK_SETFIB        2026
#define ESOCK_OPT_SOCK_SNDBUF        2027
//#define ESOCK_OPT_SOCK_SNDBUFFORCE   2028
#define ESOCK_OPT_SOCK_SNDLOWAT      2029
#define ESOCK_OPT_SOCK_SNDTIMEO      2030
#define ESOCK_OPT_SOCK_TIMESTAMP     2031
#define ESOCK_OPT_SOCK_TYPE          2032

/* level 'ip' options */
#define ESOCK_OPT_IP_ADD_MEMBERSHIP         3001
#define ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP  3002
#define ESOCK_OPT_IP_BLOCK_SOURCE           3003
//#define ESOCK_OPT_IP_DONTFRAG               3004
#define ESOCK_OPT_IP_DROP_MEMBERSHIP        3005
#define ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP 3006
#define ESOCK_OPT_IP_FREEBIND               3007
#define ESOCK_OPT_IP_HDRINCL                3008
#define ESOCK_OPT_IP_MINTTL                 3009
#define ESOCK_OPT_IP_MSFILTER               3010
#define ESOCK_OPT_IP_MTU                    3011
#define ESOCK_OPT_IP_MTU_DISCOVER           3012
#define ESOCK_OPT_IP_MULTICAST_ALL          3013
#define ESOCK_OPT_IP_MULTICAST_IF           3014
#define ESOCK_OPT_IP_MULTICAST_LOOP         3015
#define ESOCK_OPT_IP_MULTICAST_TTL          3016
#define ESOCK_OPT_IP_NODEFRAG               3017
//#define ESOCK_OPT_IP_OPTIONS                3018
#define ESOCK_OPT_IP_PKTINFO                3019
#define ESOCK_OPT_IP_RECVDSTADDR            3020
#define ESOCK_OPT_IP_RECVERR                3021
#define ESOCK_OPT_IP_RECVIF                 3022
#define ESOCK_OPT_IP_RECVOPTS               3023
#define ESOCK_OPT_IP_RECVORIGDSTADDR        3024
#define ESOCK_OPT_IP_RECVTOS                3025
#define ESOCK_OPT_IP_RECVTTL                3026
#define ESOCK_OPT_IP_RETOPTS                3027
#define ESOCK_OPT_IP_ROUTER_ALERT           3028
#define ESOCK_OPT_IP_SENDSRCADDR            3029 // Same as IP_RECVDSTADDR?
#define ESOCK_OPT_IP_TOS                    3030
#define ESOCK_OPT_IP_TRANSPARENT            3031
#define ESOCK_OPT_IP_TTL                    3032
#define ESOCK_OPT_IP_UNBLOCK_SOURCE         3033

/* level 'ipv6' options */
#define ESOCK_OPT_IPV6_ADDRFORM             4001
#define ESOCK_OPT_IPV6_ADD_MEMBERSHIP       4002
#define ESOCK_OPT_IPV6_AUTHHDR              4003
//#define ESOCK_OPT_IPV6_AUTH_LEVEL           4004
//#define ESOCK_OPT_IPV6_CHECKSUM             4005
#define ESOCK_OPT_IPV6_DROP_MEMBERSHIP      4006
#define ESOCK_OPT_IPV6_DSTOPTS              4007
//#define ESOCK_OPT_IPV6_ESP_NETWORK_LEVEL    4008
//#define ESOCK_OPT_IPV6_ESP_TRANS_LEVEL      4009
//#define ESOCK_OPT_IPV6_FAITH                4010
#define ESOCK_OPT_IPV6_FLOWINFO             4011
#define ESOCK_OPT_IPV6_HOPLIMIT             4012
#define ESOCK_OPT_IPV6_HOPOPTS              4013
//#define ESOCK_OPT_IPV6_IPCOMP_LEVEL         4014
//#define ESOCK_OPT_IPV6_JOIN_GROUP           4015
//#define ESOCK_OPT_IPV6_LEAVE_GROUP          4016
#define ESOCK_OPT_IPV6_MTU                  4017
#define ESOCK_OPT_IPV6_MTU_DISCOVER         4018
#define ESOCK_OPT_IPV6_MULTICAST_HOPS       4019
#define ESOCK_OPT_IPV6_MULTICAST_IF         4020
#define ESOCK_OPT_IPV6_MULTICAST_LOOP       4021
//#define ESOCK_OPT_IPV6_PORTRANGE            4022
//#define ESOCK_OPT_IPV6_PKTOPTIONS           4023
#define ESOCK_OPT_IPV6_RECVERR              4024
#define ESOCK_OPT_IPV6_RECVHOPLIMIT         4025
#define ESOCK_OPT_IPV6_RECVPKTINFO          4026 // PKTINFO on FreeBSD
#define ESOCK_OPT_IPV6_RECVTCLASS           4027 // Linux and ?
#define ESOCK_OPT_IPV6_ROUTER_ALERT         4028
#define ESOCK_OPT_IPV6_RTHDR                4029
#define ESOCK_OPT_IPV6_TCLASS               4030
#define ESOCK_OPT_IPV6_UNICAST_HOPS         4031
//#define ESOCK_OPT_IPV6_USE_MIN_MTU          4032
#define ESOCK_OPT_IPV6_V6ONLY               4033

/* level 'tcp' options */
#define ESOCK_OPT_TCP_CONGESTION        5001
#define ESOCK_OPT_TCP_CORK              5002
//#define ESOCK_OPT_TCP_INFO              5003
//#define ESOCK_OPT_TCP_KEEPCNT           5004
//#define ESOCK_OPT_TCP_KEEPIDLE          5005
//#define ESOCK_OPT_TCP_KEEPINTVL         5006
#define ESOCK_OPT_TCP_MAXSEG            5007
//#define ESOCK_OPT_TCP_MD5SIG            5008
#define ESOCK_OPT_TCP_NODELAY           5009
//#define ESOCK_OPT_TCP_NOOPT             5010
//#define ESOCK_OPT_TCP_NOPUSH            5011
//#define ESOCK_OPT_TCP_SYNCNT            5012
//#define ESOCK_OPT_TCP_USER_TIMEOUT      5013

/* level 'udp' options */
#define ESOCK_OPT_UDP_CORK              6001

/* level 'sctp' options */
//#define ESOCK_OPT_SCTP_ADAPTION_LAYER          7001
#define ESOCK_OPT_SCTP_ASSOCINFO               7002
//#define ESOCK_OPT_SCTP_AUTH_ACTIVE_KEY         7003
//#define ESOCK_OPT_SCTP_AUTH_ASCONF             7004
//#define ESOCK_OPT_SCTP_AUTH_CHUNK              7005
//#define ESOCK_OPT_SCTP_AUTH_KEY                7006
//#define ESOCK_OPT_SCTP_AUTH_DELETE_KEY         7007
#define ESOCK_OPT_SCTP_AUTOCLOSE               7008
//#define ESOCK_OPT_SCTP_CONTEXT                 7009
//#define ESOCK_OPT_SCTP_DEFAULT_SEND_PARAMS     7010
//#define ESOCK_OPT_SCTP_DELAYED_ACK_TIME        7011
#define ESOCK_OPT_SCTP_DISABLE_FRAGMENTS       7012
//#define ESOCK_OPT_SCTP_HMAC_IDENT              7013
#define ESOCK_OPT_SCTP_EVENTS                  7014
//#define ESOCK_OPT_SCTP_EXPLICIT_EOR            7015
//#define ESOCK_OPT_SCTP_FRAGMENT_INTERLEAVE     7016
//#define ESOCK_OPT_SCTP_GET_PEER_ADDR_INFO      7017
#define ESOCK_OPT_SCTP_INITMSG                 7018
//#define ESOCK_OPT_SCTP_I_WANT_MAPPED_V4_ADDR   7019
//#define ESOCK_OPT_SCTP_LOCAL_AUTH_CHUNKS       7020
#define ESOCK_OPT_SCTP_MAXSEG                  7021
//#define ESOCK_OPT_SCTP_MAXBURST                7022
#define ESOCK_OPT_SCTP_NODELAY                 7023
//#define ESOCK_OPT_SCTP_PARTIAL_DELIVERY_POINT  7024
//#define ESOCK_OPT_SCTP_PEER_ADDR_PARAMS        7025
//#define ESOCK_OPT_SCTP_PEER_AUTH_CHUNKS        7026
//#define ESOCK_OPT_SCTP_PRIMARY_ADDR            7027
//#define ESOCK_OPT_SCTP_RESET_STREAMS           7028
#define ESOCK_OPT_SCTP_RTOINFO                 7029
//#define ESOCK_OPT_SCTP_SET_PEER_PRIMARY_ADDR   7030
//#define ESOCK_OPT_SCTP_STATUS                  7031
//#define ESOCK_OPT_SCTP_USE_EXT_RECVINFO        7032


/*--------------------------------------------------------------------------*/


/* We should *eventually* use this instead of hard-coding the size (to 1) */
#define ESOCK_RECVMSG_IOVEC_SZ 1

#define ESOCK_CMD_DEBUG               0x0001
#define ESOCK_CMD_SOCKET_DEBUG        0x0002
#define ESOCK_CMD_USE_SOCKET_REGISTRY 0x0003

#define ESOCK_WHICH_DOMAIN_ERROR  -1
#define ESOCK_WHICH_DOMAIN_UNSUP  -2
#define ESOCK_WHICH_TYPE_ERROR    -1
#define ESOCK_WHICH_TYPE_UNSUP    -2
#define ESOCK_WHICH_PROTO_ERROR   -1
#define ESOCK_WHICH_PROTO_UNSUP   -2


/* =================================================================== *
 *                                                                     *
 *                        Various esockmacros                          *
 *                                                                     *
 * =================================================================== */

/* Global socket debug */
#define SGDBG( proto )            ESOCK_DBG_PRINTF( data.dbg , proto )
/* Socket specific debug */
#define SSDBG( __D__ , proto )    ESOCK_DBG_PRINTF( (__D__)->dbg , proto )
#define SSDBG2( __DBG__ , proto ) ESOCK_DBG_PRINTF( (__DBG__) , proto )

#define ESOCK_CNT_INC( __E__, __D__, SF, ACNT, CNT, INC)  \
    {                                                    \
        if (cnt_inc(CNT, INC) && (__D__)->iow) {         \
            esock_send_wrap_msg(__E__, __D__, SF, ACNT); \
        }                                                \
    }


/* =================================================================== *
 *                                                                     *
 *                    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 struct {
    ErlNifMonitor mon;
    BOOLEAN_T     isActive;
} ESockMonitor;

typedef struct {
    ErlNifPid    pid; // PID of the requesting process
    ESockMonitor mon; // Monitor to the requesting process

    /* We need an environment for the copy of the ref we store here.
     * We will also use this environment for any messages we send
     * (with the ref in it). Such as the select message (used in the 
     * select call) or the abort message.
     */
    ErlNifEnv*   env;
    ERL_NIF_TERM ref; // The (unique) reference (ID) of the request
} ESockRequestor;

typedef struct{
    // Holding the socket level 'otp' option 'meta' term
    ErlNifEnv* env;
    ERL_NIF_TERM ref;
} ESockMeta;

typedef struct esock_request_queue_element {
    struct esock_request_queue_element* nextP;
    ESockRequestor                      data;
} ESockRequestQueueElement;

typedef struct {
    ESockRequestQueueElement* first;
    ESockRequestQueueElement* last;
} ESockRequestQueue;

/*** The point of this is primarily testing ***/

// #define ESOCK_COUNTER_SIZE 16
// #define ESOCK_COUNTER_SIZE 24
// #define ESOCK_COUNTER_SIZE 32
// #define ESOCK_COUNTER_SIZE 48

#if ESOCK_COUNTER_SIZE == 16

typedef Uint16 ESockCounter;
#define ESOCK_COUNTER_MAX 0xFFFF
#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKUI((ENV), (CNT)))

#elif ESOCK_COUNTER_SIZE == 24

typedef Uint32 ESockCounter;
#define ESOCK_COUNTER_MAX 0xFFFFFF
#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKUI((ENV), (CNT)))

#elif ESOCK_COUNTER_SIZE == 32

typedef Uint32 ESockCounter;
#define ESOCK_COUNTER_MAX 0xFFFFFFFF
#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKUI((ENV), (CNT)))

#elif ESOCK_COUNTER_SIZE == 48

typedef Uint64 ESockCounter;
#define ESOCK_COUNTER_MAX 0xFFFFFFFFFFFF
#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKUI64((ENV), (CNT)))

#elif ESOCK_COUNTER_SIZE == 64

typedef Uint64 ESockCounter;
#define ESOCK_COUNTER_MAX 0xFFFFFFFFFFFFFFFF
#define MKCT(ENV, TAG, CNT) MKT2((ENV), (TAG), MKUI64((ENV), (CNT)))

#else

#error "Invalid counter size"

#endif

typedef struct {
    /* 
     * +++ This is a way to, possibly, detect memory overrides "and stuff" +++
     *
     * We have two patterns. One is set when the descriptor is created (allocated)
     * and one is set when the descriptor is dtor'ed.
     */
    Uint32             pattern;

    /* +++ Stuff "about" the socket +++ */

    /* "Constant" - set when socket is created and never changed */
    int                domain;
    int                type;
    int                protocol;

    /* The state is for debugging only, decisions are made based
     * on other variables.  The state is divided in a readState half
     * and a writeState half that can be OR:ed together to create
     * the complete state.  The halves are locked by their
     * corresponding lock.
     */

    /* +++ Write stuff +++ */
    ErlNifMutex*       writeMtx;
    /**/
    unsigned int       writeState; // For debugging
    ESockRequestor     currentWriter;
    ESockRequestor*    currentWriterP; // NULL or points to currentWriter
    ESockRequestQueue  writersQ;
    ESockCounter       writePkgCnt;
    ESockCounter       writePkgMax;
    ESockCounter       writePkgMaxCnt;
    ESockCounter       writeByteCnt;
    ESockCounter       writeTries;
    ESockCounter       writeWaits;
    ESockCounter       writeFails;
    /* +++ Connector +++ */
    ESockRequestor     connector;
    ESockRequestor*    connectorP;
    /* +++ Config stuff +++ */
    size_t             wCtrlSz; // Write control buffer size
    ESockMeta          meta;    // Level 'otp' option 'meta' term

    /* +++ Read stuff +++ */
    ErlNifMutex*       readMtx;
    /**/
    unsigned int       readState; // For debugging
    ESockRequestor     currentReader;
    ESockRequestor*    currentReaderP; // NULL or points to currentReader
    ESockRequestQueue  readersQ;
    ErlNifBinary       rbuffer;      // DO WE NEED THIS
    Uint32             readCapacity; // DO WE NEED THIS
    ESockCounter       readPkgCnt;
    ESockCounter       readPkgMax;
    ESockCounter       readPkgMaxCnt;
    ESockCounter       readByteCnt;
    ESockCounter       readTries;
    ESockCounter       readWaits;
    ESockCounter       readFails;
    /* +++ Accept stuff +++ */
    ESockRequestor     currentAcceptor;
    ESockRequestor*    currentAcceptorP; // NULL or points to currentAcceptor
    ESockRequestQueue  acceptorsQ;
    ESockCounter       accSuccess;
    ESockCounter       accTries;
    ESockCounter       accWaits;
    ESockCounter       accFails;
    /* +++ Config 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

    /* Locked by readMtx and writeMtx combined for writing,
     * which means only one of them is required for reading
     */
    /* +++ Close stuff +++ */
    BOOLEAN_T          closing; // We are calling esock_select_stop
    ErlNifPid          closerPid;
    ESockMonitor       closerMon;
    ErlNifEnv*         closeEnv;
    ERL_NIF_TERM       closeRef;
    /* +++ Inform On (counter) Wrap +++ */
    BOOLEAN_T          iow;
    /* +++ Controller (owner) process +++ */
    ErlNifPid          ctrlPid;
    ESockMonitor       ctrlMon;
    /* +++ The actual socket +++ */
    SOCKET             sock;
    HANDLE             event;
    SOCKET             origFD; // A 'socket' created from this FD
    BOOLEAN_T          closeOnClose; // Have we dup'ed or not
    /* +++ The dbg flag for SSDBG +++ */
    BOOLEAN_T          dbg;
    BOOLEAN_T          useReg;

    /* Lock order: readMtx, writeMtx, cntMtx
     */
} ESockDescriptor;


/* Global stuff.
 */
typedef struct {
    /* These are for debugging, testing and the like */
    // ERL_NIF_TERM version;
    // ERL_NIF_TERM buildDate;

    /* XXX Should be locked but too awkward and small gain */
    BOOLEAN_T    dbg;
    BOOLEAN_T    useReg;

    /* Registry stuff */
    ErlNifPid    regPid; /* Constant - not locked */

    /* XXX
     * Should be locked but too awkward for no gain since it is not used yet
     */
    BOOLEAN_T    iow; // Where do we send this? Subscription?

    ErlNifMutex* cntMtx; /* Locks the below */
    /* Its extreme overkill to have these counters be 64-bit,
     * but since the other counters are, its much simpler to
     * let these be 64-bit also.
     */
    ESockCounter numSockets;
    ESockCounter numTypeStreams;
    ESockCounter numTypeDGrams;
    ESockCounter numTypeSeqPkgs;
    ESockCounter numDomainInet;
    ESockCounter numDomainInet6;
    ESockCounter numDomainLocal;
    ESockCounter numProtoIP;
    ESockCounter numProtoTCP;
    ESockCounter numProtoUDP;
    ESockCounter numProtoSCTP;
    //
    BOOLEAN_T    sockDbg;
} ESockData;


/* ----------------------------------------------------------------------
 *  F o r w a r d s
 * ----------------------------------------------------------------------
 */


extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */


/* All the nif "callback" functions for the socket API has
 * the exact same API:
 *
 * nif_<funcname>(ErlNifEnv*         env,
 *                int                argc,
 *                const ERL_NIF_TERM argv[]);
 *
 * So, to simplify, use some macro magic to define those.
 *
 * These are the functions making up the "official" API.
 * Basically, these functions does some preliminary checks and argument
 * extractions and then call the functions called 'n<funcname>', which 
 * does the actual work. Except for the info function.
 *
 * nif_info
 * nif_command
 * nif_supports
 * nif_open
 * nif_bind
 * nif_connect
 * nif_listen
 * nif_accept
 * nif_send
 * nif_sendto
 * nif_sendmsg
 * nif_recv
 * nif_recvfrom
 * nif_recvmsg
 * nif_close
 * nif_shutdown
 * nif_setopt
 * nif_getopt
 * nif_sockname
 * nif_peername
 * nif_finalize_close
 * nif_cancel
 */

#define ESOCK_NIF_FUNCS                             \
    ESOCK_NIF_FUNC_DEF(info);                       \
    ESOCK_NIF_FUNC_DEF(command);                    \
    ESOCK_NIF_FUNC_DEF(supports);                   \
    ESOCK_NIF_FUNC_DEF(open);                       \
    ESOCK_NIF_FUNC_DEF(bind);                       \
    ESOCK_NIF_FUNC_DEF(connect);                    \
    ESOCK_NIF_FUNC_DEF(listen);                     \
    ESOCK_NIF_FUNC_DEF(accept);                     \
    ESOCK_NIF_FUNC_DEF(send);                       \
    ESOCK_NIF_FUNC_DEF(sendto);                     \
    ESOCK_NIF_FUNC_DEF(sendmsg);                    \
    ESOCK_NIF_FUNC_DEF(recv);                       \
    ESOCK_NIF_FUNC_DEF(recvfrom);                   \
    ESOCK_NIF_FUNC_DEF(recvmsg);                    \
    ESOCK_NIF_FUNC_DEF(close);                      \
    ESOCK_NIF_FUNC_DEF(shutdown);                   \
    ESOCK_NIF_FUNC_DEF(setopt);                     \
    ESOCK_NIF_FUNC_DEF(getopt);                     \
    ESOCK_NIF_FUNC_DEF(sockname);                   \
    ESOCK_NIF_FUNC_DEF(peername);                   \
    ESOCK_NIF_FUNC_DEF(finalize_close);             \
    ESOCK_NIF_FUNC_DEF(cancel);

#define ESOCK_NIF_FUNC_DEF(F)                              \
    static ERL_NIF_TERM nif_##F(ErlNifEnv*         env,    \
                                int                argc,   \
                                const ERL_NIF_TERM argv[]);
ESOCK_NIF_FUNCS
#undef ESOCK_NIF_FUNC_DEF


#if !defined(__WIN32__)

/* And here comes the functions that does the actual work (for the most part) */

static BOOLEAN_T ecommand2command(ErlNifEnv*    env,
                                  ERL_NIF_TERM  ecommand,
                                  Uint16*       command,
                                  ERL_NIF_TERM* edata);
static ERL_NIF_TERM esock_command(ErlNifEnv*   env,
                                  Uint16       cmd,
                                  ERL_NIF_TERM ecdata);
static ERL_NIF_TERM esock_command_debug(ErlNifEnv*   env,
                                        ERL_NIF_TERM ecdata);
static ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv*   env,
                                               ERL_NIF_TERM ecdata);
static ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv*   env,
                                                      ERL_NIF_TERM ecdata);

static ERL_NIF_TERM esock_global_info(ErlNifEnv* env);
static ERL_NIF_TERM esock_socket_info(ErlNifEnv*       env,
                                      ESockDescriptor* descP);
static ERL_NIF_TERM esock_socket_info_domain(ErlNifEnv*       env,
                                             ESockDescriptor* descP);
static ERL_NIF_TERM esock_socket_info_type(ErlNifEnv*       env,
                                           ESockDescriptor* descP);
static ERL_NIF_TERM esock_socket_info_protocol(ErlNifEnv*       env,
                                               ESockDescriptor* descP);
static ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv*       env,
                                               ESockDescriptor* descP);
static ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv*       env,
                                            ESockDescriptor* descP);
#define ESOCK_SOCKET_INFO_REQ_FUNCS              \
    ESOCK_SOCKET_INFO_REQ_FUNC_DEF(readers);     \
    ESOCK_SOCKET_INFO_REQ_FUNC_DEF(writers);     \
    ESOCK_SOCKET_INFO_REQ_FUNC_DEF(acceptors);

#define ESOCK_SOCKET_INFO_REQ_FUNC_DEF(F)                               \
    static ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv*         env,   \
                                              ESockDescriptor*   descP);
ESOCK_SOCKET_INFO_REQ_FUNCS
#undef ESOCK_SOCKET_INFO_REQ_FUNC_DEF

static ERL_NIF_TERM socket_info_reqs(ErlNifEnv*         env,
                                     ESockDescriptor*   descP,
                                     ESockRequestor*    crp,
                                     ESockRequestQueue* q);

static ERL_NIF_TERM esock_supports_0(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key);
static ERL_NIF_TERM esock_supports_2(ErlNifEnv* env,
                                     ERL_NIF_TERM key1, int key2);
//static ERL_NIF_TERM esock_supports_options(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_socket(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_ip(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_ipv6(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_tcp(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_udp(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_options_sctp(ErlNifEnv* env);
//static ERL_NIF_TERM esock_supports_sctp(ErlNifEnv* env);
//static ERL_NIF_TERM esock_supports_ipv6(ErlNifEnv* env);
//static ERL_NIF_TERM esock_supports_local(ErlNifEnv* env);
//static ERL_NIF_TERM esock_supports_netns(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_send_flags(ErlNifEnv* env);
static ERL_NIF_TERM esock_supports_recv_flags(ErlNifEnv* env);

static ERL_NIF_TERM esock_open2(ErlNifEnv*   env,
                                int          fd,
                                ERL_NIF_TERM eextra);
static BOOLEAN_T esock_open2_todup(ErlNifEnv*   env,
                                   ERL_NIF_TERM eextra);
static BOOLEAN_T esock_open2_get_domain(ErlNifEnv*   env,
                                        ERL_NIF_TERM eopts,
                                        int*         domain);
static BOOLEAN_T esock_open2_get_type(ErlNifEnv*   env,
                                      ERL_NIF_TERM eopt,
                                      int*         type);
static BOOLEAN_T esock_open2_get_protocol(ErlNifEnv*   env,
                                          ERL_NIF_TERM eopts,
                                          int*         protocol);
static ERL_NIF_TERM esock_open4(ErlNifEnv*   env,
                                int          domain,
                                int          type,
                                int          protocol,
                                ERL_NIF_TERM eopts);
static BOOLEAN_T esock_open_is_debug(ErlNifEnv*   env,
                                     ERL_NIF_TERM eextra,
                                     BOOLEAN_T dflt);
static BOOLEAN_T esock_open_use_registry(ErlNifEnv*   env,
                                         ERL_NIF_TERM eextra,
                                         BOOLEAN_T dflt);
static BOOLEAN_T esock_open_which_domain(SOCKET sock,   int* domain);
static BOOLEAN_T esock_open_which_type(SOCKET sock,     int* type);
static BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto);

static ERL_NIF_TERM esock_bind(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ESockAddress*    sockAddrP,
                               unsigned int     addrLen);
static ERL_NIF_TERM esock_connect(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     sockRef,
                                  ESockAddress*    addrP,
                                  socklen_t        addrLen);
static ERL_NIF_TERM esock_listen(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 int              backlog);
static ERL_NIF_TERM esock_accept(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 ERL_NIF_TERM     sockRef,
                                 ERL_NIF_TERM     ref);
static ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     sockRef,
                                                 ERL_NIF_TERM     accRef,
                                                 ErlNifPid        caller,
                                                 int              save_errno);
static ERL_NIF_TERM esock_accept_listening_accept(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     sockRef,
                                                  SOCKET           accSock,
                                                  ErlNifPid        caller);
static ERL_NIF_TERM esock_accept_accepting_current(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     sockRef,
                                                   ERL_NIF_TERM     ref);
static ERL_NIF_TERM
esock_accept_accepting_current_accept(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     sockRef,
                                      SOCKET           accSock);
static ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv*       env,
                                                         ESockDescriptor* descP,
                                                         ERL_NIF_TERM     sockRef,
                                                         ERL_NIF_TERM     opRef,
                                                         int              save_errno);
static ERL_NIF_TERM esock_accept_accepting_other(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     ref,
                                                 ErlNifPid        caller);
static ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     sockRef,
                                            ERL_NIF_TERM     accRef,
                                            ErlNifPid*       pid);
static BOOLEAN_T esock_accept_accepted(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ERL_NIF_TERM     sockRef,
                                       SOCKET           accSock,
                                       ErlNifPid        pid,
                                       ERL_NIF_TERM*    result);
static ERL_NIF_TERM esock_send(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ERL_NIF_TERM     sockRef,
                               ERL_NIF_TERM     sendRef,
                               ErlNifBinary*    dataP,
                               int              flags);
static ERL_NIF_TERM esock_sendto(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 ERL_NIF_TERM     sockRef,
                                 ERL_NIF_TERM     sendRef,
                                 ErlNifBinary*    dataP,
                                 int              flags,
                                 ESockAddress*    toAddrP,
                                 unsigned int     toAddrLen);
static ERL_NIF_TERM esock_sendmsg(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     sockRef,
                                  ERL_NIF_TERM     sendRef,
                                  ERL_NIF_TERM     eMsgHdr,
                                  int              flags);
static ERL_NIF_TERM esock_recv(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ERL_NIF_TERM     sendRef,
                               ERL_NIF_TERM     recvRef,
                               size_t           len,
                               int              flags);
static ERL_NIF_TERM esock_recvfrom(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   ERL_NIF_TERM     sockRef,
                                   ERL_NIF_TERM     recvRef,
                                   Uint16           bufSz,
                                   int              flags);
static ERL_NIF_TERM esock_recvmsg(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     sockRef,
                                  ERL_NIF_TERM     recvRef,
                                  Uint16           bufLen,
                                  Uint16           ctrlLen,
                                  int              flags);
static ERL_NIF_TERM esock_close(ErlNifEnv*       env,
                                ESockDescriptor* descP);
static int esock_do_stop(ErlNifEnv* env,
                         ESockDescriptor* descP);
static ERL_NIF_TERM esock_shutdown(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   int              how);
static ERL_NIF_TERM esock_setopt(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 BOOLEAN_T        isEncoded,
                                 BOOLEAN_T        isOTP,
                                 int              level,
                                 int              eOpt,
                                 ERL_NIF_TERM     eVal);

/* Set OTP level options */
static ERL_NIF_TERM esock_setopt_otp(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     int              eOpt,
                                     ERL_NIF_TERM     eVal);
/* *** esock_setopt_otp_debug        ***
 * *** esock_setopt_otp_iow          ***
 * *** esock_setopt_otp_ctrl_proc    ***
 * *** esock_setopt_otp_rcvbuf       ***
 * *** esock_setopt_otp_rcvctrlbuf   ***
 * *** esock_setopt_otp_sndctrlbuf   ***
 * *** esock_setopt_otp_meta         ***
 */
#define ESOCK_SETOPT_OTP_FUNCS               \
    ESOCK_SETOPT_OTP_FUNC_DEF(debug);        \
    ESOCK_SETOPT_OTP_FUNC_DEF(iow);          \
    ESOCK_SETOPT_OTP_FUNC_DEF(ctrl_proc);    \
    ESOCK_SETOPT_OTP_FUNC_DEF(rcvbuf);       \
    ESOCK_SETOPT_OTP_FUNC_DEF(rcvctrlbuf);   \
    ESOCK_SETOPT_OTP_FUNC_DEF(sndctrlbuf);   \
    ESOCK_SETOPT_OTP_FUNC_DEF(meta);
#define ESOCK_SETOPT_OTP_FUNC_DEF(F)                                 \
    static ERL_NIF_TERM esock_setopt_otp_##F(ErlNifEnv*       env,   \
                                             ESockDescriptor* descP, \
                                             ERL_NIF_TERM     eVal)
ESOCK_SETOPT_OTP_FUNCS
#undef ESOCK_SETOPT_OTP_FUNC_DEF

/* Set native options */
static ERL_NIF_TERM esock_setopt_native(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        int              level,
                                        int              eOpt,
                                        ERL_NIF_TERM     eVal);
static ERL_NIF_TERM esock_setopt_level(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       int              level,
                                       int              eOpt,
                                       ERL_NIF_TERM     eVal);
static ERL_NIF_TERM esock_setopt_lvl_socket(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            int              eOpt,
                                            ERL_NIF_TERM     eVal);


/* *** Handling set of socket options for level = socket *** */

#if defined(SO_BINDTODEVICE)
static ERL_NIF_TERM esock_setopt_lvl_sock_bindtodevice(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(SO_BROADCAST)
static ERL_NIF_TERM esock_setopt_lvl_sock_broadcast(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SO_DEBUG)
static ERL_NIF_TERM esock_setopt_lvl_sock_debug(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(SO_DONTROUTE)
static ERL_NIF_TERM esock_setopt_lvl_sock_dontroute(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SO_KEEPALIVE)
static ERL_NIF_TERM esock_setopt_lvl_sock_keepalive(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SO_LINGER)
static ERL_NIF_TERM esock_setopt_lvl_sock_linger(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(SO_OOBINLINE)
static ERL_NIF_TERM esock_setopt_lvl_sock_oobinline(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SO_PASSCRED)
static ERL_NIF_TERM esock_setopt_lvl_sock_passcred(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_PEEK_OFF)
static ERL_NIF_TERM esock_setopt_lvl_sock_peek_off(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_PRIORITY)
static ERL_NIF_TERM esock_setopt_lvl_sock_priority(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_RCVBUF)
static ERL_NIF_TERM esock_setopt_lvl_sock_rcvbuf(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(SO_RCVLOWAT)
static ERL_NIF_TERM esock_setopt_lvl_sock_rcvlowat(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_lvl_sock_rcvtimeo(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_REUSEADDR)
static ERL_NIF_TERM esock_setopt_lvl_sock_reuseaddr(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SO_REUSEPORT)
static ERL_NIF_TERM esock_setopt_lvl_sock_reuseport(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SO_SNDBUF)
static ERL_NIF_TERM esock_setopt_lvl_sock_sndbuf(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(SO_SNDLOWAT)
static ERL_NIF_TERM esock_setopt_lvl_sock_sndlowat(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_lvl_sock_sndtimeo(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(SO_TIMESTAMP)
static ERL_NIF_TERM esock_setopt_lvl_sock_timestamp(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
static ERL_NIF_TERM esock_setopt_lvl_ip(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        int              eOpt,
                                        ERL_NIF_TERM     eVal);

/* *** Handling set of socket options for level = ip *** */
#if defined(IP_ADD_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ip_add_membership(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(IP_ADD_SOURCE_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ip_add_source_membership(ErlNifEnv*       env,
                                                              ESockDescriptor* descP,
                                                              ERL_NIF_TERM     eVal);
#endif
#if defined(IP_BLOCK_SOURCE)
static ERL_NIF_TERM esock_setopt_lvl_ip_block_source(ErlNifEnv*       env,
                                                     ESockDescriptor* descP,
                                                     ERL_NIF_TERM     eVal);
#endif
#if defined(IP_DROP_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ip_drop_membership(ErlNifEnv*       env,
                                                        ESockDescriptor* descP,
                                                        ERL_NIF_TERM     eVal);
#endif
#if defined(IP_DROP_SOURCE_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ip_drop_source_membership(ErlNifEnv*       env,
                                                               ESockDescriptor* descP,
                                                               ERL_NIF_TERM     eVal);
#endif
#if defined(IP_FREEBIND)
static ERL_NIF_TERM esock_setopt_lvl_ip_freebind(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(IP_HDRINCL)
static ERL_NIF_TERM esock_setopt_lvl_ip_hdrincl(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IP_MINTTL)
static ERL_NIF_TERM esock_setopt_lvl_ip_minttl(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     eVal);
#endif
#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
static ERL_NIF_TERM esock_setopt_lvl_ip_msfilter(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
static BOOLEAN_T decode_ip_msfilter_mode(ErlNifEnv*   env,
                                         ERL_NIF_TERM eVal,
                                         Uint32*      mode);
static ERL_NIF_TERM esock_setopt_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 esock_setopt_lvl_ip_mtu_discover(ErlNifEnv*       env,
                                                     ESockDescriptor* descP,
                                                     ERL_NIF_TERM     eVal);
#endif
#if defined(IP_MULTICAST_ALL)
static ERL_NIF_TERM esock_setopt_lvl_ip_multicast_all(ErlNifEnv*       env,
                                                      ESockDescriptor* descP,
                                                      ERL_NIF_TERM     eVal);
#endif
#if defined(IP_MULTICAST_IF)
static ERL_NIF_TERM esock_setopt_lvl_ip_multicast_if(ErlNifEnv*       env,
                                                     ESockDescriptor* descP,
                                                     ERL_NIF_TERM     eVal);
#endif
#if defined(IP_MULTICAST_LOOP)
static ERL_NIF_TERM esock_setopt_lvl_ip_multicast_loop(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(IP_MULTICAST_TTL)
static ERL_NIF_TERM esock_setopt_lvl_ip_multicast_ttl(ErlNifEnv*       env,
                                                      ESockDescriptor* descP,
                                                      ERL_NIF_TERM     eVal);
#endif
#if defined(IP_NODEFRAG)
static ERL_NIF_TERM esock_setopt_lvl_ip_nodefrag(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(IP_PKTINFO)
static ERL_NIF_TERM esock_setopt_lvl_ip_pktinfo(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVDSTADDR)
static ERL_NIF_TERM esock_setopt_lvl_ip_recvdstaddr(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVERR)
static ERL_NIF_TERM esock_setopt_lvl_ip_recverr(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVIF)
static ERL_NIF_TERM esock_setopt_lvl_ip_recvif(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVOPTS)
static ERL_NIF_TERM esock_setopt_lvl_ip_recvopts(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVORIGDSTADDR)
static ERL_NIF_TERM esock_setopt_lvl_ip_recvorigdstaddr(ErlNifEnv*       env,
                                                        ESockDescriptor* descP,
                                                        ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVTOS)
static ERL_NIF_TERM esock_setopt_lvl_ip_recvtos(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RECVTTL)
static ERL_NIF_TERM esock_setopt_lvl_ip_recvttl(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IP_RETOPTS)
static ERL_NIF_TERM esock_setopt_lvl_ip_retopts(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IP_ROUTER_ALERT)
static ERL_NIF_TERM esock_setopt_lvl_ip_router_alert(ErlNifEnv*       env,
                                                     ESockDescriptor* descP,
                                                     ERL_NIF_TERM     eVal);
#endif
#if defined(IP_SENDSRCADDR)
static ERL_NIF_TERM esock_setopt_lvl_ip_sendsrcaddr(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(IP_TOS)
static ERL_NIF_TERM esock_setopt_lvl_ip_tos(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal);
#endif
#if defined(IP_TRANSPARENT)
static ERL_NIF_TERM esock_setopt_lvl_ip_transparent(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(IP_TTL)
static ERL_NIF_TERM esock_setopt_lvl_ip_ttl(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal);
#endif
#if defined(IP_UNBLOCK_SOURCE)
static ERL_NIF_TERM esock_setopt_lvl_ip_unblock_source(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif

#if defined(IP_DROP_MEMBERSHIP) || defined(IP_ADD_MEMBERSHIP)
static
ERL_NIF_TERM esock_setopt_lvl_ip_update_membership(ErlNifEnv*       env,
                                                   ESockDescriptor* 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 esock_setopt_lvl_ip_update_source(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     eVal,
                                               int              opt);
#endif


/* *** Handling set of socket options for level = ipv6 *** */
#if defined(HAVE_IPV6)
static ERL_NIF_TERM esock_setopt_lvl_ipv6(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              eOpt,
                                          ERL_NIF_TERM     eVal);
#if defined(IPV6_ADDRFORM)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_addrform(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_ADD_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_add_membership(ErlNifEnv*       env,
                                                         ESockDescriptor* descP,
                                                         ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_AUTHHDR)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_authhdr(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_DROP_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_drop_membership(ErlNifEnv*       env,
                                                          ESockDescriptor* descP,
                                                          ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_DSTOPTS)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_dstopts(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_FLOWINFO)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_flowinfo(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_HOPLIMIT)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_hoplimit(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_HOPOPTS)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_hopopts(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_MTU)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_mtu(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_MTU_DISCOVER)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_mtu_discover(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_MULTICAST_HOPS)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_multicast_hops(ErlNifEnv*       env,
                                                         ESockDescriptor* descP,
                                                         ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_MULTICAST_IF)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_multicast_if(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_MULTICAST_LOOP)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_multicast_loop(ErlNifEnv*       env,
                                                         ESockDescriptor* descP,
                                                         ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_RECVERR)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_recverr(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_RECVHOPLIMIT)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_recvhoplimit(ErlNifEnv*       env,
						       ESockDescriptor* descP,
						       ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_recvpktinfo(ErlNifEnv*       env,
                                                      ESockDescriptor* descP,
                                                      ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_RECVTCLASS)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_recvtclass(ErlNifEnv*       env,
                                                     ESockDescriptor* descP,
                                                     ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_ROUTER_ALERT)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_router_alert(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_RTHDR)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_rthdr(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_TCLASS)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_tclass(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_UNICAST_HOPS)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_unicast_hops(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal);
#endif
#if defined(IPV6_V6ONLY)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_v6only(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif

#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP)
static ERL_NIF_TERM esock_setopt_lvl_ipv6_update_membership(ErlNifEnv*       env,
                                                            ESockDescriptor* descP,
                                                            ERL_NIF_TERM     eVal,
                                                            int              opt);
#endif

#endif // defined(HAVE_IPV6)
static ERL_NIF_TERM esock_setopt_lvl_tcp(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              eOpt,
                                         ERL_NIF_TERM     eVal);
#if defined(TCP_CONGESTION)
static ERL_NIF_TERM esock_setopt_lvl_tcp_congestion(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(TCP_CORK)
static ERL_NIF_TERM esock_setopt_lvl_tcp_cork(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal);
#endif
#if defined(TCP_MAXSEG)
static ERL_NIF_TERM esock_setopt_lvl_tcp_maxseg(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal);
#endif
#if defined(TCP_NODELAY)
static ERL_NIF_TERM esock_setopt_lvl_tcp_nodelay(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
static ERL_NIF_TERM esock_setopt_lvl_udp(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              eOpt,
                                         ERL_NIF_TERM     eVal);
#if defined(UDP_CORK)
static ERL_NIF_TERM esock_setopt_lvl_udp_cork(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal);
#endif
#if defined(HAVE_SCTP)
static ERL_NIF_TERM esock_setopt_lvl_sctp(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              eOpt,
                                          ERL_NIF_TERM     eVal);
#if defined(SCTP_ASSOCINFO)
static ERL_NIF_TERM esock_setopt_lvl_sctp_associnfo(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_AUTOCLOSE)
static ERL_NIF_TERM esock_setopt_lvl_sctp_autoclose(ErlNifEnv*       env,
                                                    ESockDescriptor* descP,
                                                    ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_DISABLE_FRAGMENTS)
static ERL_NIF_TERM esock_setopt_lvl_sctp_disable_fragments(ErlNifEnv*       env,
                                                            ESockDescriptor* descP,
                                                            ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_EVENTS)
static ERL_NIF_TERM esock_setopt_lvl_sctp_events(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_INITMSG)
static ERL_NIF_TERM esock_setopt_lvl_sctp_initmsg(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_MAXSEG)
static ERL_NIF_TERM esock_setopt_lvl_sctp_maxseg(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_NODELAY)
static ERL_NIF_TERM esock_setopt_lvl_sctp_nodelay(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#if defined(SCTP_RTOINFO)
static ERL_NIF_TERM esock_setopt_lvl_sctp_rtoinfo(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal);
#endif
#endif // defined(HAVE_SCTP)

static ERL_NIF_TERM esock_getopt(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 BOOLEAN_T        isEncoded,
                                 BOOLEAN_T        isOTP,
                                 int              level,
                                 ERL_NIF_TERM     eOpt);

static ERL_NIF_TERM esock_getopt_otp(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     int              eOpt);
/* *** esock_getopt_otp_debug        ***
 * *** esock_getopt_otp_iow          ***
 * *** esock_getopt_otp_ctrl_proc    ***
 * *** esock_getopt_otp_rcvbuf       ***
 * *** esock_getopt_otp_rcvctrlbuf   ***
 * *** esock_getopt_otp_sndctrlbuf   ***
 * *** esock_getopt_otp_fd           ***
 * *** esock_getopt_otp_meta         ***
 * *** esock_getopt_otp_use_registry ***
 * *** esock_getopt_otp_domain       ***
 * *** esock_getopt_otp_type         ***
 * *** esock_getopt_otp_protocol     ***
 * *** esock_getopt_otp_dtp          ***
 */
#define ESOCK_GETOPT_OTP_FUNCS               \
    ESOCK_GETOPT_OTP_FUNC_DEF(debug);        \
    ESOCK_GETOPT_OTP_FUNC_DEF(meta);         \
    ESOCK_GETOPT_OTP_FUNC_DEF(iow);          \
    ESOCK_GETOPT_OTP_FUNC_DEF(ctrl_proc);    \
    ESOCK_GETOPT_OTP_FUNC_DEF(rcvbuf);       \
    ESOCK_GETOPT_OTP_FUNC_DEF(rcvctrlbuf);   \
    ESOCK_GETOPT_OTP_FUNC_DEF(sndctrlbuf);   \
    ESOCK_GETOPT_OTP_FUNC_DEF(fd);           \
    ESOCK_GETOPT_OTP_FUNC_DEF(use_registry); \
    ESOCK_GETOPT_OTP_FUNC_DEF(domain);       \
    ESOCK_GETOPT_OTP_FUNC_DEF(type);         \
    ESOCK_GETOPT_OTP_FUNC_DEF(protocol);     \
    ESOCK_GETOPT_OTP_FUNC_DEF(dtp);
#define ESOCK_GETOPT_OTP_FUNC_DEF(F)                               \
    static ERL_NIF_TERM esock_getopt_otp_##F(ErlNifEnv*        env, \
                                             ESockDescriptor* descP)
ESOCK_GETOPT_OTP_FUNCS
#undef ESOCK_GETOPT_OTP_FUNC_DEF

static ERL_NIF_TERM esock_getopt_native(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        int              level,
                                        ERL_NIF_TERM     eOpt);
static ERL_NIF_TERM esock_getopt_native_unspec(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               int              level,
                                               int              opt,
                                               SOCKOPTLEN_T     valueSz);
static ERL_NIF_TERM esock_getopt_level(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       int              level,
                                       int              eOpt);
static ERL_NIF_TERM esock_getopt_lvl_socket(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            int              eOpt);
#if defined(SO_ACCEPTCONN)
static ERL_NIF_TERM esock_getopt_lvl_sock_acceptconn(ErlNifEnv*       env,
                                                     ESockDescriptor* descP);
#endif
#if defined(SO_BINDTODEVICE)
static ERL_NIF_TERM esock_getopt_lvl_sock_bindtodevice(ErlNifEnv*       env,
                                                       ESockDescriptor* descP);
#endif
#if defined(SO_BROADCAST)
static ERL_NIF_TERM esock_getopt_lvl_sock_broadcast(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_DEBUG)
static ERL_NIF_TERM esock_getopt_lvl_sock_debug(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(SO_DOMAIN)
static ERL_NIF_TERM esock_getopt_lvl_sock_domain(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(SO_DONTROUTE)
static ERL_NIF_TERM esock_getopt_lvl_sock_dontroute(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_KEEPALIVE)
static ERL_NIF_TERM esock_getopt_lvl_sock_keepalive(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_LINGER)
static ERL_NIF_TERM esock_getopt_lvl_sock_linger(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(SO_OOBINLINE)
static ERL_NIF_TERM esock_getopt_lvl_sock_oobinline(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_PASSCRED)
static ERL_NIF_TERM esock_getopt_lvl_sock_passcred(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_PEEK_OFF)
static ERL_NIF_TERM esock_getopt_lvl_sock_peek_off(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_PRIORITY)
static ERL_NIF_TERM esock_getopt_lvl_sock_priority(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_PROTOCOL)
static ERL_NIF_TERM esock_getopt_lvl_sock_protocol(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_RCVBUF)
static ERL_NIF_TERM esock_getopt_lvl_sock_rcvbuf(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(SO_RCVLOWAT)
static ERL_NIF_TERM esock_getopt_lvl_sock_rcvlowat(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_lvl_sock_rcvtimeo(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_REUSEADDR)
static ERL_NIF_TERM esock_getopt_lvl_sock_reuseaddr(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_REUSEPORT)
static ERL_NIF_TERM esock_getopt_lvl_sock_reuseport(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_SNDBUF)
static ERL_NIF_TERM esock_getopt_lvl_sock_sndbuf(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(SO_SNDLOWAT)
static ERL_NIF_TERM esock_getopt_lvl_sock_sndlowat(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_lvl_sock_sndtimeo(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(SO_TIMESTAMP)
static ERL_NIF_TERM esock_getopt_lvl_sock_timestamp(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SO_TYPE)
static ERL_NIF_TERM esock_getopt_lvl_sock_type(ErlNifEnv*       env,
                                               ESockDescriptor* descP);
#endif
static ERL_NIF_TERM esock_getopt_lvl_ip(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        int              eOpt);
#if defined(IP_FREEBIND)
static ERL_NIF_TERM esock_getopt_lvl_ip_freebind(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(IP_HDRINCL)
static ERL_NIF_TERM esock_getopt_lvl_ip_hdrincl(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IP_MINTTL)
static ERL_NIF_TERM esock_getopt_lvl_ip_minttl(ErlNifEnv*       env,
                                               ESockDescriptor* descP);
#endif
#if defined(IP_MTU)
static ERL_NIF_TERM esock_getopt_lvl_ip_mtu(ErlNifEnv*       env,
                                            ESockDescriptor* descP);
#endif
#if defined(IP_MTU_DISCOVER)
static ERL_NIF_TERM esock_getopt_lvl_ip_mtu_discover(ErlNifEnv*       env,
                                                     ESockDescriptor* descP);
#endif
#if defined(IP_MULTICAST_ALL)
static ERL_NIF_TERM esock_getopt_lvl_ip_multicast_all(ErlNifEnv*       env,
                                                      ESockDescriptor* descP);
#endif
#if defined(IP_MULTICAST_IF)
static ERL_NIF_TERM esock_getopt_lvl_ip_multicast_if(ErlNifEnv*       env,
                                                     ESockDescriptor* descP);
#endif
#if defined(IP_MULTICAST_LOOP)
static ERL_NIF_TERM esock_getopt_lvl_ip_multicast_loop(ErlNifEnv*       env,
                                                       ESockDescriptor* descP);
#endif
#if defined(IP_MULTICAST_TTL)
static ERL_NIF_TERM esock_getopt_lvl_ip_multicast_ttl(ErlNifEnv*       env,
                                                      ESockDescriptor* descP);
#endif
#if defined(IP_NODEFRAG)
static ERL_NIF_TERM esock_getopt_lvl_ip_nodefrag(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(IP_PKTINFO)
static ERL_NIF_TERM esock_getopt_lvl_ip_pktinfo(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IP_RECVDSTADDR)
static ERL_NIF_TERM esock_getopt_lvl_ip_recvdstaddr(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(IP_RECVERR)
static ERL_NIF_TERM esock_getopt_lvl_ip_recverr(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IP_RECVIF)
static ERL_NIF_TERM esock_getopt_lvl_ip_recvif(ErlNifEnv*       env,
                                               ESockDescriptor* descP);
#endif
#if defined(IP_RECVOPTS)
static ERL_NIF_TERM esock_getopt_lvl_ip_recvopts(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(IP_RECVORIGDSTADDR)
static ERL_NIF_TERM esock_getopt_lvl_ip_recvorigdstaddr(ErlNifEnv*       env,
                                                        ESockDescriptor* descP);
#endif
#if defined(IP_RECVTOS)
static ERL_NIF_TERM esock_getopt_lvl_ip_recvtos(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IP_RECVTTL)
static ERL_NIF_TERM esock_getopt_lvl_ip_recvttl(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IP_RETOPTS)
static ERL_NIF_TERM esock_getopt_lvl_ip_retopts(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IP_ROUTER_ALERT)
static ERL_NIF_TERM esock_getopt_lvl_ip_router_alert(ErlNifEnv*       env,
                                                     ESockDescriptor* descP);
#endif
#if defined(IP_SENDSRCADDR)
static ERL_NIF_TERM esock_getopt_lvl_ip_sendsrcaddr(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(IP_TOS)
static ERL_NIF_TERM esock_getopt_lvl_ip_tos(ErlNifEnv*       env,
                                            ESockDescriptor* descP);
#endif
#if defined(IP_TRANSPARENT)
static ERL_NIF_TERM esock_getopt_lvl_ip_transparent(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(IP_TTL)
static ERL_NIF_TERM esock_getopt_lvl_ip_ttl(ErlNifEnv*       env,
                                            ESockDescriptor* descP);
#endif
#if defined(HAVE_IPV6)
static ERL_NIF_TERM esock_getopt_lvl_ipv6(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              eOpt);
#if defined(IPV6_AUTHHDR)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_authhdr(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#if defined(IPV6_DSTOPTS)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_dstopts(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#if defined(IPV6_FLOWINFO)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_flowinfo(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(IPV6_HOPLIMIT)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_hoplimit(ErlNifEnv*       env,
                                                   ESockDescriptor* descP);
#endif
#if defined(IPV6_HOPOPTS)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_hopopts(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#if defined(IPV6_MTU)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_mtu(ErlNifEnv*       env,
                                              ESockDescriptor* descP);
#endif
#if defined(IPV6_MTU_DISCOVER)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_mtu_discover(ErlNifEnv*       env,
                                                       ESockDescriptor* descP);
#endif
#if defined(IPV6_MULTICAST_HOPS)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_multicast_hops(ErlNifEnv*       env,
                                                         ESockDescriptor* descP);
#endif
#if defined(IPV6_MULTICAST_IF)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_multicast_if(ErlNifEnv*       env,
                                                       ESockDescriptor* descP);
#endif
#if defined(IPV6_MULTICAST_LOOP)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_multicast_loop(ErlNifEnv*       env,
                                                         ESockDescriptor* descP);
#endif
#if defined(IPV6_RECVERR)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_recverr(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#if defined(IPV6_RECVHOPLIMIT)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_recvhoplimit(ErlNifEnv*       env,
						       ESockDescriptor* descP);
#endif
#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_recvpktinfo(ErlNifEnv*       env,
                                                      ESockDescriptor* descP);
#endif
#if defined(IPV6_RECVTCLASS)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_recvtclass(ErlNifEnv*       env,
                                                     ESockDescriptor* descP);
#endif
#if defined(IPV6_ROUTER_ALERT)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_router_alert(ErlNifEnv*       env,
                                                       ESockDescriptor* descP);
#endif
#if defined(IPV6_RTHDR)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_rthdr(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(IPV6_TCLASS)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_tclass(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(IPV6_UNICAST_HOPS)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_unicast_hops(ErlNifEnv*       env,
                                                       ESockDescriptor* descP);
#endif
#if defined(IPV6_V6ONLY)
static ERL_NIF_TERM esock_getopt_lvl_ipv6_v6only(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif

#endif // defined(HAVE_IPV6)

static ERL_NIF_TERM esock_getopt_lvl_tcp(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              eOpt);
#if defined(TCP_CONGESTION)
static ERL_NIF_TERM esock_getopt_lvl_tcp_congestion(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(TCP_CORK)
static ERL_NIF_TERM esock_getopt_lvl_tcp_cork(ErlNifEnv*       env,
                                              ESockDescriptor* descP);
#endif
#if defined(TCP_MAXSEG)
static ERL_NIF_TERM esock_getopt_lvl_tcp_maxseg(ErlNifEnv*       env,
                                                ESockDescriptor* descP);
#endif
#if defined(TCP_NODELAY)
static ERL_NIF_TERM esock_getopt_lvl_tcp_nodelay(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
static ERL_NIF_TERM esock_getopt_lvl_udp(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              eOpt);
#if defined(UDP_CORK)
static ERL_NIF_TERM esock_getopt_lvl_udp_cork(ErlNifEnv*       env,
                                              ESockDescriptor* descP);
#endif
#if defined(HAVE_SCTP)
static ERL_NIF_TERM esock_getopt_lvl_sctp(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              eOpt);
#if defined(SCTP_ASSOCINFO)
static ERL_NIF_TERM esock_getopt_lvl_sctp_associnfo(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SCTP_AUTOCLOSE)
static ERL_NIF_TERM esock_getopt_lvl_sctp_autoclose(ErlNifEnv*       env,
                                                    ESockDescriptor* descP);
#endif
#if defined(SCTP_DISABLE_FRAGMENTS)
static ERL_NIF_TERM esock_getopt_lvl_sctp_disable_fragments(ErlNifEnv*       env,
                                                            ESockDescriptor* descP);
#endif
#if defined(SCTP_MAXSEG)
static ERL_NIF_TERM esock_getopt_lvl_sctp_maxseg(ErlNifEnv*       env,
                                                 ESockDescriptor* descP);
#endif
#if defined(SCTP_INITMSG)
static ERL_NIF_TERM esock_getopt_lvl_sctp_initmsg(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#if defined(SCTP_NODELAY)
static ERL_NIF_TERM esock_getopt_lvl_sctp_nodelay(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#if defined(SCTP_RTOINFO)
static ERL_NIF_TERM esock_getopt_lvl_sctp_rtoinfo(ErlNifEnv*       env,
                                                  ESockDescriptor* descP);
#endif
#endif // defined(HAVE_SCTP)
static ERL_NIF_TERM esock_sockname(ErlNifEnv*       env,
                                   ESockDescriptor* descP);
static ERL_NIF_TERM esock_peername(ErlNifEnv*       env,
                                   ESockDescriptor* descP);
static ERL_NIF_TERM esock_cancel(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 ERL_NIF_TERM     op,
                                 ERL_NIF_TERM     sockRef,
                                 ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_connect(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_accept(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        ERL_NIF_TERM     sockRef,
                                        ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_accept_current(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM esock_cancel_accept_waiting(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_send(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     sockRef,
                                      ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_send_current(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM esock_cancel_send_waiting(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_recv(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     sockRef,
                                      ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_recv_current(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM esock_cancel_recv_waiting(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_read_select(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     opRef);
static ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     opRef,
                                             int              smode,
                                             int              rmode);

#if defined(USE_SETOPT_STR_OPT)
static ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              level,
                                         int              opt,
                                         int              max,
                                         ERL_NIF_TERM     eVal);
#endif
static ERL_NIF_TERM esock_setopt_bool_opt(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              level,
                                          int              opt,
                                          ERL_NIF_TERM     eVal);
static ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              level,
                                         int              opt,
                                         ERL_NIF_TERM     eVal);
#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             int              level,
                                             int              opt,
                                             ERL_NIF_TERM     eVal);
#endif

#if defined(USE_GETOPT_STR_OPT)
static ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              level,
                                         int              opt,
                                         int              max);
#endif
static ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              level,
                                          int              opt);
static ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              level,
                                         int              opt);
#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             int              level,
                                             int              opt);
#endif

static BOOLEAN_T send_check_writer(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   ERL_NIF_TERM     ref,
                                   ERL_NIF_TERM*    checkResult);
static ERL_NIF_TERM send_check_result(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ssize_t          written,
                                      ssize_t          dataSize,
                                      int              saveErrno,
                                      ERL_NIF_TERM     sockRef,
                                      ERL_NIF_TERM     sendRef);
static ERL_NIF_TERM send_check_ok(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ssize_t          written,
                                  ssize_t          dataSize,
                                  ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM send_check_fail(ErlNifEnv*       env,
                                    ESockDescriptor* descP,
                                    int              saveErrno,
                                    ERL_NIF_TERM     sockRef);
static void send_error_waiting_writers(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ERL_NIF_TERM     sockRef,
                                       ERL_NIF_TERM     reason);
static ERL_NIF_TERM send_check_retry(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     ssize_t          written,
                                     ERL_NIF_TERM     sockRef,
                                     ERL_NIF_TERM     sendRef);
static BOOLEAN_T recv_check_reader(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   ERL_NIF_TERM     ref,
                                   ERL_NIF_TERM*    checkResult);
static char* recv_init_current_reader(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     ref);
static ERL_NIF_TERM recv_update_current_reader(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     sockRef);
static void recv_error_current_reader(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     sockRef,
                                      ERL_NIF_TERM     reason);
static ERL_NIF_TERM recv_check_result(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ssize_t          read,
                                      size_t           toRead,
                                      int              saveErrno,
                                      ErlNifBinary*    bufP,
                                      ERL_NIF_TERM     sockRef,
                                      ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_full(ErlNifEnv*       env,
                                    ESockDescriptor* descP,
                                    ssize_t          read,
                                    size_t           toRead,
                                    ErlNifBinary*    bufP,
                                    ERL_NIF_TERM     sockRef,
                                    ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ssize_t          read,
                                               size_t           toRead,
                                               ErlNifBinary*    bufP,
                                               ERL_NIF_TERM     sockRef,
                                               ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_full_done(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ssize_t          read,
                                         ErlNifBinary*    bufP,
                                         ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM recv_check_fail(ErlNifEnv*       env,
                                    ESockDescriptor* descP,
                                    int              saveErrno,
                                    ErlNifBinary*    buf1P,
                                    ErlNifBinary*    buf2P,
                                    ERL_NIF_TERM     sockRef,
                                    ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     sockRef,
                                               ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_partial(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ssize_t          read,
                                       size_t           toRead,
                                       ErlNifBinary*    bufP,
                                       ERL_NIF_TERM     sockRef,
                                       ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_partial_done(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ssize_t          read,
                                            ErlNifBinary*    bufP,
                                            ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM recv_check_partial_part(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ssize_t          read,
                                            ErlNifBinary*    bufP,
                                            ERL_NIF_TERM     sockRef,
                                            ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_retry(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     ERL_NIF_TERM     sockRef,
                                     ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recv_check_fail_gen(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        int              saveErrno,
                                        ERL_NIF_TERM     sockRef);
static ERL_NIF_TERM recvfrom_check_result(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          int              read,
                                          int              saveErrno,
                                          ErlNifBinary*    bufP,
                                          ESockAddress*    fromAddrP,
                                          unsigned int     fromAddrLen,
                                          ERL_NIF_TERM     sockRef,
                                          ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recvmsg_check_result(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         int              read,
                                         int              saveErrno,
                                         struct msghdr*   msgHdrP,
                                         ErlNifBinary*    dataBufP,
                                         ErlNifBinary*    ctrlBufP,
                                         ERL_NIF_TERM     sockRef,
                                         ERL_NIF_TERM     recvRef);
static ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      int              read,
                                      struct msghdr*   msgHdrP,
                                      ErlNifBinary*    dataBufP,
                                      ErlNifBinary*    ctrlBufP,
                                      ERL_NIF_TERM     sockRef);

static ERL_NIF_TERM esock_finalize_close(ErlNifEnv*       env,
                                         ESockDescriptor* descP);
static int esock_close_socket(ErlNifEnv*       env,
                              ESockDescriptor* descP);

extern char* encode_msghdr(ErlNifEnv*       env,
                           ESockDescriptor* descP,
                           int              read,
                           struct msghdr*   msgHdrP,
                           ErlNifBinary*    dataBufP,
                           ErlNifBinary*    ctrlBufP,
                           ERL_NIF_TERM*    eSockAddr);
extern char* encode_cmsghdrs(ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             ErlNifBinary*    cmsgBinP,
                             struct msghdr*   msgHdrP,
                             ERL_NIF_TERM*    eCMsgHdr);
extern char* decode_cmsghdrs(ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             ERL_NIF_TERM     eCMsgHdr,
                             char*            cmsgHdrBufP,
                             size_t           cmsgHdrBufLen,
                             size_t*          cmsgHdrBufUsed);
extern char* decode_cmsghdr(ErlNifEnv*       env,
                            ESockDescriptor* 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,
                                 ESockDescriptor* descP,
                                 int              msgFlags,
                                 ERL_NIF_TERM*    flags);
static char* decode_cmsghdr_data(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 char*            bufP,
                                 size_t           rem,
                                 int              level,
                                 int              type,
                                 ERL_NIF_TERM     eData,
                                 size_t*          used);
static char* decode_cmsghdr_final(ESockDescriptor* 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 esock_stop_handle_current(ErlNifEnv*       env,
                                      const char*      role,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     sockRef,
                                      ESockRequestor*  reqP);
static void inform_waiting_procs(ErlNifEnv*         env,
                                 const char*        role,
                                 ESockDescriptor*   descP,
                                 ERL_NIF_TERM       sockRef,
                                 ESockRequestQueue* q,
                                 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(ESockDescriptor* descP, int* err);

static ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event);


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 esock_open4_get_netns(ErlNifEnv*   env,
                                       ERL_NIF_TERM opts,
                                       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(ESockCounter* cnt, ESockCounter inc);
static void      cnt_dec(ESockCounter* cnt, ESockCounter dec);

static void inc_socket(int domain, int type, int protocol);
static void dec_socket(int domain, int type, int protocol);



/* *** activate_next_acceptor ***
 * *** activate_next_writer   ***
 * *** activate_next_reader   ***
 *
 * All the activate-next functions for acceptor, writer and reader
 * have exactly the same API, so we apply some macro magic to simplify.
 * They simply operates on dufferent data structures.
 *
 */

#define ACTIVATE_NEXT_FUNCS_DEFS     \
    ACTIVATE_NEXT_FUNC_DEF(acceptor) \
    ACTIVATE_NEXT_FUNC_DEF(writer)   \
    ACTIVATE_NEXT_FUNC_DEF(reader)

#define ACTIVATE_NEXT_FUNC_DEF(F)                                 \
    static BOOLEAN_T activate_next_##F(ErlNifEnv*       env,      \
                                       ESockDescriptor* descP,    \
                                       ERL_NIF_TERM     sockRef);
ACTIVATE_NEXT_FUNCS_DEFS
#undef ACTIVATE_NEXT_FUNC_DEF
    
/* *** acceptor_search4pid | writer_search4pid | reader_search4pid ***
 * *** acceptor_push       | writer_push       | reader_push       ***
 * *** acceptor_pop        | writer_pop        | reader_pop        ***
 * *** acceptor_unqueue    | writer_unqueue    | reader_unqueue    ***
 *
 * All the queue operator functions (search4pid, push, pop
 * and unqueue) for acceptor, writer and reader has exactly
 * the same API, so we apply some macro magic to simplify.
 */

#define ESOCK_OPERATOR_FUNCS_DEFS      \
    ESOCK_OPERATOR_FUNCS_DEF(acceptor) \
    ESOCK_OPERATOR_FUNCS_DEF(writer)   \
    ESOCK_OPERATOR_FUNCS_DEF(reader)

#define ESOCK_OPERATOR_FUNCS_DEF(O)                            \
    static BOOLEAN_T O##_search4pid(ErlNifEnv*       env,      \
                                    ESockDescriptor* descP,    \
                                    ErlNifPid*       pid);     \
    static ERL_NIF_TERM O##_push(ErlNifEnv*       env,         \
                                 ESockDescriptor* descP,       \
                                 ErlNifPid        pid,         \
                                 ERL_NIF_TERM     ref);        \
    static BOOLEAN_T O##_pop(ErlNifEnv*       env,             \
                             ESockDescriptor* descP,           \
                             ESockRequestor*  reqP);           \
    static BOOLEAN_T O##_unqueue(ErlNifEnv*       env,         \
                                 ESockDescriptor* descP,       \
                                 const ErlNifPid* pid);
ESOCK_OPERATOR_FUNCS_DEFS
#undef ESOCK_OPERATOR_FUNCS_DEF

static BOOLEAN_T requestor_pop(ESockRequestQueue* q,
                               ESockRequestor*    reqP);

static void requestor_init(ESockRequestor* reqP);
static int requestor_release(const char*      slogan,
                             ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             ESockRequestor* reqP);

static BOOLEAN_T qsearch4pid(ErlNifEnv*         env,
                             ESockRequestQueue* q,
                             ErlNifPid*         pid);
static void qpush(ESockRequestQueue*        q,
                  ESockRequestQueueElement* e);
static ESockRequestQueueElement* qpop(ESockRequestQueue* q);
static BOOLEAN_T qunqueue(ErlNifEnv*         env,
                          ESockDescriptor*   descP,
                          const char*        slogan,
                          ESockRequestQueue* q,
                          const ErlNifPid*   pid);

static int esock_monitor(const char*      slogan,
                         ErlNifEnv*       env,
                         ESockDescriptor* descP,
                         const ErlNifPid* pid,
                         ESockMonitor*    mon);
static int esock_demonitor(const char*      slogan,
                           ErlNifEnv*       env,
                           ESockDescriptor* descP,
                           ESockMonitor*    monP);
static void esock_monitor_init(ESockMonitor* mon);
static ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv*          env,
                                            const ESockMonitor* monP);
static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
                                  const ErlNifMonitor* mon);


#endif // if defined(__WIN32__)

/*
#if defined(HAVE_SYS_UN_H) || defined(SO_BINDTODEVICE)
static size_t my_strnlen(const char *s, size_t maxlen);
#endif
*/

static void esock_dtor(ErlNifEnv* env, void* obj);
static void esock_stop(ErlNifEnv* env,
                       void*      obj,
                       ErlNifEvent fd,
                       int        is_direct_call);
static void esock_down(ErlNifEnv*           env,
                       void*                obj,
                       const ErlNifPid*     pid,
                       const ErlNifMonitor* mon);

#if !defined(__WIN32__)

static void esock_down_acceptor(ErlNifEnv*       env,
                                ESockDescriptor* descP,
                                ERL_NIF_TERM     sockRef,
                                const ErlNifPid* pid,
                                const ErlNifMonitor* mon);
static void esock_down_writer(ErlNifEnv*       env,
                              ESockDescriptor* descP,
                              ERL_NIF_TERM     sockRef,
                              const ErlNifPid* pid,
                              const ErlNifMonitor* mon);
static void esock_down_reader(ErlNifEnv*       env,
                              ESockDescriptor* descP,
                              ERL_NIF_TERM     sockRef,
                              const ErlNifPid* pid,
                              const ErlNifMonitor* mon);

static void esock_send_reg_add_msg(ErlNifEnv*   env,
                                   ERL_NIF_TERM sockRef);
static void esock_send_reg_del_msg(ErlNifEnv*   env,
                                   ERL_NIF_TERM sockRef);

static char* esock_send_wrap_msg(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 ERL_NIF_TERM     sockRef,
                                 ERL_NIF_TERM     cnt);
static char* esock_send_close_msg(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ErlNifPid*       pid);
static char* esock_send_abort_msg(ErlNifEnv*   env,
                                  ERL_NIF_TERM sockRef,
                                  ERL_NIF_TERM recvRef,
                                  ErlNifEnv*   msgEnv,
                                  ERL_NIF_TERM reason,
                                  ErlNifPid*   pid);
static char* esock_send_msg(ErlNifEnv*   env,
                            ErlNifPid*   pid,
                            ERL_NIF_TERM msg,
                            ErlNifEnv*   msgEnv);

static ERL_NIF_TERM mk_reg_add_msg(ErlNifEnv*   env,
                                   ERL_NIF_TERM sockRef);
static ERL_NIF_TERM mk_reg_del_msg(ErlNifEnv*   env,
                                   ERL_NIF_TERM sockRef);
static ERL_NIF_TERM mk_reg_msg(ErlNifEnv*   env,
                               ERL_NIF_TERM tag,
                               ERL_NIF_TERM sockRef);
static ERL_NIF_TERM mk_abort_msg(ErlNifEnv*   env,
                                 ERL_NIF_TERM sockRef,
                                 ERL_NIF_TERM opRef,
                                 ERL_NIF_TERM reason);
static ERL_NIF_TERM mk_wrap_msg(ErlNifEnv*   env,
                                ERL_NIF_TERM sockRef,
                                ERL_NIF_TERM cnt);
static ERL_NIF_TERM mk_close_msg(ErlNifEnv*   env,
                                 ERL_NIF_TERM sockRef,
                                 ERL_NIF_TERM closeRef);
static ERL_NIF_TERM mk_select_msg(ErlNifEnv*   env,
                                  ERL_NIF_TERM sockRef,
                                  ERL_NIF_TERM selectRef);
static ERL_NIF_TERM mk_socket_msg(ErlNifEnv*   env,
                                  ERL_NIF_TERM sockRef,
                                  ERL_NIF_TERM tag,
                                  ERL_NIF_TERM info);
static ERL_NIF_TERM mk_socket(ErlNifEnv*   env,
                              ERL_NIF_TERM sockRef);

static int esock_select_read(ErlNifEnv*       env,
                             ErlNifEvent      event,
                             void*            obj,
                             const ErlNifPid* pid,
                             ERL_NIF_TERM     sockRef,
                             ERL_NIF_TERM     selectRef);
static int esock_select_write(ErlNifEnv*       env,
                              ErlNifEvent      event,
                              void*            obj,
                              const ErlNifPid* pid,
                              ERL_NIF_TERM     sockRef,
                              ERL_NIF_TERM     selectRef);
static int esock_select_stop(ErlNifEnv*  env,
                             ErlNifEvent event,
                             void*       obj);
static int esock_select_cancel(ErlNifEnv*             env,
                               ErlNifEvent            event,
                               enum ErlNifSelectFlags mode,
                               void*                  obj);

static char* extract_debug_filename(ErlNifEnv*   env,
                                    ERL_NIF_TERM map);

#endif // if defined(__WIN32__)

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 */



/* (special) error string constants */
static char str_exmonitor[]      = "exmonitor";  // failed monitor
static char str_exself[]         = "exself";     // failed self
static char str_exsend[]         = "exsend";     // failed send



/* *** Global atoms *** 
 * Note that when an (global) atom is added here, it must also be added
 * in the socket_int.h file!
 */
#define GLOBAL_ATOMS                                   \
    GLOBAL_ATOM_DECL(abort);                           \
    GLOBAL_ATOM_DECL(accept);                          \
    GLOBAL_ATOM_DECL(acceptconn);                      \
    GLOBAL_ATOM_DECL(acceptfilter);                    \
    GLOBAL_ATOM_DECL(adaption_layer);                  \
    GLOBAL_ATOM_DECL(addr);                            \
    GLOBAL_ATOM_DECL(addrform);                        \
    GLOBAL_ATOM_DECL(add_membership);                  \
    GLOBAL_ATOM_DECL(add_source_membership);           \
    GLOBAL_ATOM_DECL(any);                             \
    GLOBAL_ATOM_DECL(associnfo);                       \
    GLOBAL_ATOM_DECL(authhdr);                         \
    GLOBAL_ATOM_DECL(auth_active_key);                 \
    GLOBAL_ATOM_DECL(auth_asconf);                     \
    GLOBAL_ATOM_DECL(auth_chunk);                      \
    GLOBAL_ATOM_DECL(auth_delete_key);                 \
    GLOBAL_ATOM_DECL(auth_key);                        \
    GLOBAL_ATOM_DECL(auth_level);                      \
    GLOBAL_ATOM_DECL(autoclose);                       \
    GLOBAL_ATOM_DECL(bindtodevice);                    \
    GLOBAL_ATOM_DECL(block_source);                    \
    GLOBAL_ATOM_DECL(broadcast);                       \
    GLOBAL_ATOM_DECL(busy_poll);                       \
    GLOBAL_ATOM_DECL(checksum);                        \
    GLOBAL_ATOM_DECL(close);                           \
    GLOBAL_ATOM_DECL(cmsg_cloexec);                    \
    GLOBAL_ATOM_DECL(command);                         \
    GLOBAL_ATOM_DECL(confirm);                         \
    GLOBAL_ATOM_DECL(congestion);                      \
    GLOBAL_ATOM_DECL(connect);                         \
    GLOBAL_ATOM_DECL(context);                         \
    GLOBAL_ATOM_DECL(cork);                            \
    GLOBAL_ATOM_DECL(credentials);                     \
    GLOBAL_ATOM_DECL(ctrl);                            \
    GLOBAL_ATOM_DECL(ctrunc);                          \
    GLOBAL_ATOM_DECL(data);                            \
    GLOBAL_ATOM_DECL(debug);                           \
    GLOBAL_ATOM_DECL(default);                         \
    GLOBAL_ATOM_DECL(default_send_params);             \
    GLOBAL_ATOM_DECL(delayed_ack_time);                \
    GLOBAL_ATOM_DECL(dgram);                           \
    GLOBAL_ATOM_DECL(disable_fragments);               \
    GLOBAL_ATOM_DECL(domain);                          \
    GLOBAL_ATOM_DECL(dontfrag);                        \
    GLOBAL_ATOM_DECL(dontroute);                       \
    GLOBAL_ATOM_DECL(drop_membership);                 \
    GLOBAL_ATOM_DECL(drop_source_membership);          \
    GLOBAL_ATOM_DECL(dstopts);                         \
    GLOBAL_ATOM_DECL(egp);                             \
    GLOBAL_ATOM_DECL(enotsup);                         \
    GLOBAL_ATOM_DECL(eor);                             \
    GLOBAL_ATOM_DECL(error);                           \
    GLOBAL_ATOM_DECL(errqueue);                        \
    GLOBAL_ATOM_DECL(esp_network_level);               \
    GLOBAL_ATOM_DECL(esp_trans_level);                 \
    GLOBAL_ATOM_DECL(events);                          \
    GLOBAL_ATOM_DECL(explicit_eor);                    \
    GLOBAL_ATOM_DECL(faith);                           \
    GLOBAL_ATOM_DECL(false);                           \
    GLOBAL_ATOM_DECL(family);                          \
    GLOBAL_ATOM_DECL(fastroute);                       \
    GLOBAL_ATOM_DECL(flags);                           \
    GLOBAL_ATOM_DECL(flowinfo);                        \
    GLOBAL_ATOM_DECL(fragment_interleave);             \
    GLOBAL_ATOM_DECL(freebind);                        \
    GLOBAL_ATOM_DECL(get_peer_addr_info);              \
    GLOBAL_ATOM_DECL(hatype);                          \
    GLOBAL_ATOM_DECL(hdrincl);                         \
    GLOBAL_ATOM_DECL(hmac_ident);                      \
    GLOBAL_ATOM_DECL(hoplimit);                        \
    GLOBAL_ATOM_DECL(hopopts);                         \
    GLOBAL_ATOM_DECL(host);                            \
    GLOBAL_ATOM_DECL(icmp);                            \
    GLOBAL_ATOM_DECL(icmp6);                           \
    GLOBAL_ATOM_DECL(ifindex);                         \
    GLOBAL_ATOM_DECL(igmp);                            \
    GLOBAL_ATOM_DECL(inet);                            \
    GLOBAL_ATOM_DECL(inet6);                           \
    GLOBAL_ATOM_DECL(info);                            \
    GLOBAL_ATOM_DECL(initmsg);                         \
    GLOBAL_ATOM_DECL(iov);                             \
    GLOBAL_ATOM_DECL(ip);                              \
    GLOBAL_ATOM_DECL(ipcomp_level);                    \
    GLOBAL_ATOM_DECL(ipip);                            \
    GLOBAL_ATOM_DECL(ipv6);                            \
    GLOBAL_ATOM_DECL(i_want_mapped_v4_addr);           \
    GLOBAL_ATOM_DECL(join_group);                      \
    GLOBAL_ATOM_DECL(keepalive);                       \
    GLOBAL_ATOM_DECL(keepcnt);                         \
    GLOBAL_ATOM_DECL(keepidle);                        \
    GLOBAL_ATOM_DECL(keepintvl);                       \
    GLOBAL_ATOM_DECL(kernel);                          \
    GLOBAL_ATOM_DECL(leave_group);                     \
    GLOBAL_ATOM_DECL(level);                           \
    GLOBAL_ATOM_DECL(linger);                          \
    GLOBAL_ATOM_DECL(local);                           \
    GLOBAL_ATOM_DECL(local_auth_chunks);               \
    GLOBAL_ATOM_DECL(loopback);                        \
    GLOBAL_ATOM_DECL(lowdelay);                        \
    GLOBAL_ATOM_DECL(mark);                            \
    GLOBAL_ATOM_DECL(maxburst);                        \
    GLOBAL_ATOM_DECL(maxseg);                          \
    GLOBAL_ATOM_DECL(md5sig);                          \
    GLOBAL_ATOM_DECL(mincost);                         \
    GLOBAL_ATOM_DECL(minttl);                          \
    GLOBAL_ATOM_DECL(more);                            \
    GLOBAL_ATOM_DECL(msfilter);                        \
    GLOBAL_ATOM_DECL(mtu);                             \
    GLOBAL_ATOM_DECL(mtu_discover);                    \
    GLOBAL_ATOM_DECL(multicast);                       \
    GLOBAL_ATOM_DECL(multicast_all);                   \
    GLOBAL_ATOM_DECL(multicast_hops);                  \
    GLOBAL_ATOM_DECL(multicast_if);                    \
    GLOBAL_ATOM_DECL(multicast_loop);                  \
    GLOBAL_ATOM_DECL(multicast_ttl);                   \
    GLOBAL_ATOM_DECL(nodelay);                         \
    GLOBAL_ATOM_DECL(nodefrag);                        \
    GLOBAL_ATOM_DECL(noopt);                           \
    GLOBAL_ATOM_DECL(nopush);                          \
    GLOBAL_ATOM_DECL(nosignal);                        \
    GLOBAL_ATOM_DECL(not_found);                       \
    GLOBAL_ATOM_DECL(not_owner);                       \
    GLOBAL_ATOM_DECL(ok);                              \
    GLOBAL_ATOM_DECL(oob);                             \
    GLOBAL_ATOM_DECL(oobinline);                       \
    GLOBAL_ATOM_DECL(options);                         \
    GLOBAL_ATOM_DECL(origdstaddr);                     \
    GLOBAL_ATOM_DECL(otherhost);                       \
    GLOBAL_ATOM_DECL(outgoing);                        \
    GLOBAL_ATOM_DECL(packet);                          \
    GLOBAL_ATOM_DECL(partial_delivery_point);          \
    GLOBAL_ATOM_DECL(passcred);                        \
    GLOBAL_ATOM_DECL(path);                            \
    GLOBAL_ATOM_DECL(peek);                            \
    GLOBAL_ATOM_DECL(peekcred);                        \
    GLOBAL_ATOM_DECL(peek_off);                        \
    GLOBAL_ATOM_DECL(peer_addr_params);                \
    GLOBAL_ATOM_DECL(peer_auth_chunks);                \
    GLOBAL_ATOM_DECL(pktinfo);                         \
    GLOBAL_ATOM_DECL(pktoptions);                      \
    GLOBAL_ATOM_DECL(pkttype);                         \
    GLOBAL_ATOM_DECL(port);                            \
    GLOBAL_ATOM_DECL(portrange);                       \
    GLOBAL_ATOM_DECL(primary_addr);                    \
    GLOBAL_ATOM_DECL(priority);                        \
    GLOBAL_ATOM_DECL(protocol);                        \
    GLOBAL_ATOM_DECL(raw);                             \
    GLOBAL_ATOM_DECL(rcvbuf);                          \
    GLOBAL_ATOM_DECL(rcvbufforce);                     \
    GLOBAL_ATOM_DECL(rcvlowat);                        \
    GLOBAL_ATOM_DECL(rcvtimeo);                        \
    GLOBAL_ATOM_DECL(rdm);                             \
    GLOBAL_ATOM_DECL(recv);                            \
    GLOBAL_ATOM_DECL(recvdstaddr);                     \
    GLOBAL_ATOM_DECL(recverr);                         \
    GLOBAL_ATOM_DECL(recvfrom);                        \
    GLOBAL_ATOM_DECL(recvhoplimit);                    \
    GLOBAL_ATOM_DECL(recvif);                          \
    GLOBAL_ATOM_DECL(recvmsg);                         \
    GLOBAL_ATOM_DECL(recvopts);                        \
    GLOBAL_ATOM_DECL(recvorigdstaddr);                 \
    GLOBAL_ATOM_DECL(recvpktinfo);                     \
    GLOBAL_ATOM_DECL(recvtclass);                      \
    GLOBAL_ATOM_DECL(recvtos);                         \
    GLOBAL_ATOM_DECL(recvttl);                         \
    GLOBAL_ATOM_DECL(reliability);                     \
    GLOBAL_ATOM_DECL(reset_streams);                   \
    GLOBAL_ATOM_DECL(retopts);                         \
    GLOBAL_ATOM_DECL(reuseaddr);                       \
    GLOBAL_ATOM_DECL(reuseport);                       \
    GLOBAL_ATOM_DECL(rights);                          \
    GLOBAL_ATOM_DECL(router_alert);                    \
    GLOBAL_ATOM_DECL(rthdr);                           \
    GLOBAL_ATOM_DECL(rtoinfo);                         \
    GLOBAL_ATOM_DECL(rxq_ovfl);                        \
    GLOBAL_ATOM_DECL(scope_id);                        \
    GLOBAL_ATOM_DECL(sctp);                            \
    GLOBAL_ATOM_DECL(sec);                             \
    GLOBAL_ATOM_DECL(select_failed);                   \
    GLOBAL_ATOM_DECL(select_sent);                     \
    GLOBAL_ATOM_DECL(send);                            \
    GLOBAL_ATOM_DECL(sendmsg);                         \
    GLOBAL_ATOM_DECL(sendsrcaddr);                     \
    GLOBAL_ATOM_DECL(sendto);                          \
    GLOBAL_ATOM_DECL(seqpacket);                       \
    GLOBAL_ATOM_DECL(setfib);                          \
    GLOBAL_ATOM_DECL(set_peer_primary_addr);           \
    GLOBAL_ATOM_DECL(socket);                          \
    GLOBAL_ATOM_DECL(sndbuf);                          \
    GLOBAL_ATOM_DECL(sndbufforce);                     \
    GLOBAL_ATOM_DECL(sndlowat);                        \
    GLOBAL_ATOM_DECL(sndtimeo);                        \
    GLOBAL_ATOM_DECL(spec_dst);                        \
    GLOBAL_ATOM_DECL(status);                          \
    GLOBAL_ATOM_DECL(stream);                          \
    GLOBAL_ATOM_DECL(syncnt);                          \
    GLOBAL_ATOM_DECL(tclass);                          \
    GLOBAL_ATOM_DECL(tcp);                             \
    GLOBAL_ATOM_DECL(throughput);                      \
    GLOBAL_ATOM_DECL(timestamp);                       \
    GLOBAL_ATOM_DECL(tos);                             \
    GLOBAL_ATOM_DECL(transparent);                     \
    GLOBAL_ATOM_DECL(true);                            \
    GLOBAL_ATOM_DECL(trunc);                           \
    GLOBAL_ATOM_DECL(ttl);                             \
    GLOBAL_ATOM_DECL(type);                            \
    GLOBAL_ATOM_DECL(udp);                             \
    GLOBAL_ATOM_DECL(unblock_source);                  \
    GLOBAL_ATOM_DECL(undefined);                       \
    GLOBAL_ATOM_DECL(unicast_hops);                    \
    GLOBAL_ATOM_DECL(unknown);                         \
    GLOBAL_ATOM_DECL(usec);                            \
    GLOBAL_ATOM_DECL(user);                            \
    GLOBAL_ATOM_DECL(user_timeout);                    \
    GLOBAL_ATOM_DECL(use_ext_recvinfo);                \
    GLOBAL_ATOM_DECL(use_min_mtu);                     \
    GLOBAL_ATOM_DECL(v6only)


/* *** Global error reason atoms *** */
#define GLOBAL_ERROR_REASON_ATOMS   \
    GLOBAL_ATOM_DECL(eagain);       \
    GLOBAL_ATOM_DECL(eafnosupport); \
    GLOBAL_ATOM_DECL(einval)


#define GLOBAL_ATOM_DECL(A) ERL_NIF_TERM esock_atom_##A
GLOBAL_ATOMS;
GLOBAL_ERROR_REASON_ATOMS;
#undef GLOBAL_ATOM_DECL
ERL_NIF_TERM esock_atom_socket_tag; // This has a "special" name ('$socket')

/* *** Local atoms *** */
#define LOCAL_ATOMS                    \
    LOCAL_ATOM_DECL(acc_success);      \
    LOCAL_ATOM_DECL(acc_fails);        \
    LOCAL_ATOM_DECL(acc_tries);        \
    LOCAL_ATOM_DECL(acc_waits);        \
    LOCAL_ATOM_DECL(adaptation_layer); \
    LOCAL_ATOM_DECL(add);              \
    LOCAL_ATOM_DECL(addr_unreach);     \
    LOCAL_ATOM_DECL(address);          \
    LOCAL_ATOM_DECL(adm_prohibited);   \
    LOCAL_ATOM_DECL(association);      \
    LOCAL_ATOM_DECL(assoc_id);         \
    LOCAL_ATOM_DECL(authentication);   \
    LOCAL_ATOM_DECL(bool);             \
    LOCAL_ATOM_DECL(close);            \
    LOCAL_ATOM_DECL(closed);           \
    LOCAL_ATOM_DECL(closing);          \
    LOCAL_ATOM_DECL(code);             \
    LOCAL_ATOM_DECL(cookie_life);      \
    LOCAL_ATOM_DECL(counter_wrap);     \
    LOCAL_ATOM_DECL(counters);         \
    LOCAL_ATOM_DECL(ctype);            \
    LOCAL_ATOM_DECL(data_in);          \
    LOCAL_ATOM_DECL(debug_filename);   \
    LOCAL_ATOM_DECL(del);              \
    LOCAL_ATOM_DECL(dest_unreach);     \
    LOCAL_ATOM_DECL(do);               \
    LOCAL_ATOM_DECL(dont);             \
    LOCAL_ATOM_DECL(dup);              \
    LOCAL_ATOM_DECL(exclude);          \
    LOCAL_ATOM_DECL(false);            \
    LOCAL_ATOM_DECL(frag_needed);      \
    LOCAL_ATOM_DECL(host_unknown);     \
    LOCAL_ATOM_DECL(host_unreach);     \
    LOCAL_ATOM_DECL(in4_sockaddr);     \
    LOCAL_ATOM_DECL(in6_sockaddr);     \
    LOCAL_ATOM_DECL(include);          \
    LOCAL_ATOM_DECL(initial);          \
    LOCAL_ATOM_DECL(int);              \
    LOCAL_ATOM_DECL(interface);        \
    LOCAL_ATOM_DECL(iow);              \
    LOCAL_ATOM_DECL(local_rwnd);       \
    LOCAL_ATOM_DECL(max);              \
    LOCAL_ATOM_DECL(max_attempts);     \
    LOCAL_ATOM_DECL(max_init_timeo);   \
    LOCAL_ATOM_DECL(max_instreams);    \
    LOCAL_ATOM_DECL(max_rxt);          \
    LOCAL_ATOM_DECL(min);              \
    LOCAL_ATOM_DECL(missing);          \
    LOCAL_ATOM_DECL(mode);             \
    LOCAL_ATOM_DECL(multiaddr);        \
    LOCAL_ATOM_DECL(net_unknown);      \
    LOCAL_ATOM_DECL(net_unreach);      \
    LOCAL_ATOM_DECL(netns);            \
    LOCAL_ATOM_DECL(none);             \
    LOCAL_ATOM_DECL(noroute);          \
    LOCAL_ATOM_DECL(not_neighbour);    \
    LOCAL_ATOM_DECL(null);             \
    LOCAL_ATOM_DECL(num_acceptors);    \
    LOCAL_ATOM_DECL(num_cnt_bits);     \
    LOCAL_ATOM_DECL(num_dinet);        \
    LOCAL_ATOM_DECL(num_dinet6);       \
    LOCAL_ATOM_DECL(num_dlocal);       \
    LOCAL_ATOM_DECL(num_outstreams);   \
    LOCAL_ATOM_DECL(num_peer_dests);   \
    LOCAL_ATOM_DECL(num_pip);          \
    LOCAL_ATOM_DECL(num_psctp);        \
    LOCAL_ATOM_DECL(num_ptcp);         \
    LOCAL_ATOM_DECL(num_pudp);         \
    LOCAL_ATOM_DECL(num_readers);      \
    LOCAL_ATOM_DECL(num_sockets);      \
    LOCAL_ATOM_DECL(num_tdgrams);      \
    LOCAL_ATOM_DECL(num_tseqpkgs);     \
    LOCAL_ATOM_DECL(num_tstreams);     \
    LOCAL_ATOM_DECL(num_writers);      \
    LOCAL_ATOM_DECL(offender);         \
    LOCAL_ATOM_DECL(options);          \
    LOCAL_ATOM_DECL(origin);           \
    LOCAL_ATOM_DECL(partial_delivery); \
    LOCAL_ATOM_DECL(peer_error);       \
    LOCAL_ATOM_DECL(peer_rwnd);        \
    LOCAL_ATOM_DECL(pkt_toobig);       \
    LOCAL_ATOM_DECL(policy_fail);      \
    LOCAL_ATOM_DECL(port_unreach);     \
    LOCAL_ATOM_DECL(probe);            \
    LOCAL_ATOM_DECL(read_byte);        \
    LOCAL_ATOM_DECL(read_fails);       \
    LOCAL_ATOM_DECL(read_pkg);         \
    LOCAL_ATOM_DECL(read_pkg_max);     \
    LOCAL_ATOM_DECL(read_tries);       \
    LOCAL_ATOM_DECL(read_waits);       \
    LOCAL_ATOM_DECL(recv_flags);       \
    LOCAL_ATOM_DECL(registry);         \
    LOCAL_ATOM_DECL(reject_route);     \
    LOCAL_ATOM_DECL(remote);           \
    LOCAL_ATOM_DECL(select);           \
    LOCAL_ATOM_DECL(sender_dry);       \
    LOCAL_ATOM_DECL(send_failure);     \
    LOCAL_ATOM_DECL(send_flags);       \
    LOCAL_ATOM_DECL(shutdown);         \
    LOCAL_ATOM_DECL(slist);            \
    LOCAL_ATOM_DECL(socket_debug);     \
    LOCAL_ATOM_DECL(sourceaddr);       \
    LOCAL_ATOM_DECL(time_exceeded);    \
    LOCAL_ATOM_DECL(timeout);          \
    LOCAL_ATOM_DECL(true);             \
    LOCAL_ATOM_DECL(txstatus);         \
    LOCAL_ATOM_DECL(txtime);           \
    LOCAL_ATOM_DECL(use_registry);     \
    LOCAL_ATOM_DECL(want);             \
    LOCAL_ATOM_DECL(write_byte);       \
    LOCAL_ATOM_DECL(write_fails);      \
    LOCAL_ATOM_DECL(write_pkg);        \
    LOCAL_ATOM_DECL(write_pkg_max);    \
    LOCAL_ATOM_DECL(write_tries);      \
    LOCAL_ATOM_DECL(write_waits);      \
    LOCAL_ATOM_DECL(zerocopy)

/* Local error reason atoms */
#define LOCAL_ERROR_REASON_ATOMS                \
    LOCAL_ATOM_DECL(econnreset);                \
    LOCAL_ATOM_DECL(eisconn);                   \
    LOCAL_ATOM_DECL(enotconn);                  \
                                                \
    LOCAL_ATOM_DECL(exalloc);                   \
    LOCAL_ATOM_DECL(exbadstate);                \
    LOCAL_ATOM_DECL(exmonitor);                 \
    LOCAL_ATOM_DECL(exselect);                  \
    LOCAL_ATOM_DECL(exself);                    \
    LOCAL_ATOM_DECL(exsend)

#define LOCAL_ATOM_DECL(LA) static ERL_NIF_TERM atom_##LA
LOCAL_ATOMS;
LOCAL_ERROR_REASON_ATOMS;
#undef LOCAL_ATOM_DECL


/* *** Sockets *** */
static ErlNifResourceType*    esocks;
static ErlNifResourceTypeInit esockInit = {
   esock_dtor,
   esock_stop,
   (ErlNifResourceDown*) esock_down
};

// Initiated when the nif is loaded
static ESockData data;


/* These two (inline) functions are primarily intended for debugging,
 * that is, to make it easy to add debug printouts.
 */
static ESOCK_INLINE void esock_free_env(const char* slogan, ErlNifEnv* env)
{
    SGDBG( ("SOCKET", "env free - %s: 0x%lX\r\n", slogan, env) );
    // esock_dbg_printf("SOCK ENV", "free - %s: 0x%lX\r\n", slogan, env);

    if (env != NULL) enif_free_env(env);
}


static ESOCK_INLINE ErlNifEnv* esock_alloc_env(const char* slogan)
{
    ErlNifEnv* env = enif_alloc_env();

    SGDBG( ("SOCKET", "env alloc - %s: 0x%lX\r\n", slogan, env) );
    // esock_dbg_printf("SOCK ENV", "alloc - %s: 0x%lX\r\n", slogan, env);

    return env;
}


/* ----------------------------------------------------------------------
 *  N I F   F u n c t i o n s
 * ----------------------------------------------------------------------
 *
 * Utility and admin functions:
 * ----------------------------
 * nif_info/0
 * nif_command/1
 * nif_supports/1
 *
 * The "proper" socket functions:
 * ------------------------------
 * nif_open(FD, Extra)
 * 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...
 */

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, "notsup"));
#else
    ERL_NIF_TERM info;

    SGDBG( ("SOCKET", "nif_info -> entry with %d args\r\n", argc) );
    
    switch (argc) {
    case 0:
        info = esock_global_info(env);
        break;

    case 1:
        {
            ESockDescriptor* descP;

            if (!ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
                return enif_make_badarg(env);
            }

            MLOCK(descP->readMtx);
            MLOCK(descP->writeMtx);

            SSDBG( descP, ("SOCKET", "nif_info(%T) {%d,%s,0x%X} -> get socket info\r\n",
                           argv[0], descP->sock, B2S(descP->closing),
                           descP->readState | descP->writeState) );

            info = esock_socket_info(env, descP);

            SSDBG( descP, ("SOCKET", "nif_info(%T) -> get socket info done with"
                           "\r\n   info: %T"
                           "\r\n", argv[0], info) );

            MUNLOCK(descP->writeMtx);
            MUNLOCK(descP->readMtx);
        }
        break;
        
    default:
        return enif_make_badarg(env);
    }

    return info;

#endif
}


/*
 * This function return a property list containing "global" info.
 *
 * Note that we also include something in the counter list that is not
 * actually a counter, the num_cnt_bits. This is the "size" of each counter,
 * in number of bits: 16 | 24 | 32 | 48 | 64.
 */
#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_global_info(ErlNifEnv* env)
{
    ERL_NIF_TERM
        numBits, numSockets, numTypeDGrams, numTypeStreams,
        numTypeSeqPkgs, numDomLocal, numDomInet, numDomInet6,
        numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP;

    MLOCK(data.cntMtx);
    numBits        = MKUI(env, ESOCK_COUNTER_SIZE);
    numSockets     = MKUI(env, data.numSockets);
    numTypeDGrams  = MKUI(env, data.numTypeDGrams);
    numTypeStreams = MKUI(env, data.numTypeStreams);
    numTypeSeqPkgs = MKUI(env, data.numTypeSeqPkgs);
    numDomLocal    = MKUI(env, data.numDomainLocal);
    numDomInet     = MKUI(env, data.numDomainInet);
    numDomInet6    = MKUI(env, data.numDomainInet6);
    numProtoIP     = MKUI(env, data.numProtoIP);
    numProtoTCP    = MKUI(env, data.numProtoTCP);
    numProtoUDP    = MKUI(env, data.numProtoUDP);
    numProtoSCTP   = MKUI(env, data.numProtoSCTP);
    MUNLOCK(data.cntMtx);

    {
        ERL_NIF_TERM gcntVals[] =
            {numBits,
             numSockets,
             numTypeDGrams, numTypeStreams, numTypeSeqPkgs,
             numDomLocal, numDomInet, numDomInet6,
             numProtoIP, numProtoTCP, numProtoUDP, numProtoSCTP};
        ERL_NIF_TERM gcntKeys[] =
            {atom_num_cnt_bits,
             atom_num_sockets,
             atom_num_tdgrams, atom_num_tstreams, atom_num_tseqpkgs,
             atom_num_dlocal, atom_num_dinet, atom_num_dinet6,
             atom_num_pip, atom_num_ptcp, atom_num_pudp, atom_num_psctp};
        unsigned int numGCntVals = sizeof(gcntVals) / sizeof(ERL_NIF_TERM);
        unsigned int numGCntKeys = sizeof(gcntKeys) / sizeof(ERL_NIF_TERM);
        ERL_NIF_TERM gcnt;

        ESOCK_ASSERT( (numGCntKeys == numGCntVals) );
        
        if (!MKMA(env, gcntKeys, gcntVals, numGCntKeys, &gcnt))
            return enif_make_badarg(env);

        {
            ERL_NIF_TERM
                keys[] = {esock_atom_debug,
                          atom_socket_debug,
                          atom_use_registry,
                          atom_iow,
                          atom_counters},
                vals[] = {BOOL2ATOM(data.dbg),
                          BOOL2ATOM(data.sockDbg),
                          BOOL2ATOM(data.useReg),
                          BOOL2ATOM(data.iow),
                          gcnt},
                info;
            unsigned int
                numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM),
                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;
        }
    }
}



/*
 * This function return a property *map*. The properties are: 
 *    domain:    The domain of the socket
 *    type:      The type of the socket
 *    protocol:  The protocol of the socket
 *    ctrl:      Controlling process of the socket)
 *    counters:  A list of each socket counter and there current values
 *    readers:   The number of current and waiting readers
 *    writers:   The number of current and waiting writers
 *    acceptors: The number of current and waiting acceptors
 */
static
ERL_NIF_TERM esock_socket_info(ErlNifEnv*       env,
                               ESockDescriptor* descP)
{
    ERL_NIF_TERM domain    = esock_socket_info_domain(env, descP);
    ERL_NIF_TERM type      = esock_socket_info_type(env, descP);
    ERL_NIF_TERM protocol  = esock_socket_info_protocol(env, descP);
    ERL_NIF_TERM ctrlPid   = MKPID(env, &descP->ctrlPid);
    ERL_NIF_TERM ctype     = esock_socket_info_ctype(env, descP);
    ERL_NIF_TERM counters  = esock_socket_info_counters(env, descP);
    ERL_NIF_TERM readers   = esock_socket_info_readers(env, descP);
    ERL_NIF_TERM writers   = esock_socket_info_writers(env, descP);
    ERL_NIF_TERM acceptors = esock_socket_info_acceptors(env, descP);

    {
        ERL_NIF_TERM keys[]
            = {esock_atom_domain,
               esock_atom_type,
               esock_atom_protocol,
               esock_atom_ctrl,
               atom_ctype,
               atom_counters,
               atom_num_readers,
               atom_num_writers,
               atom_num_acceptors};
        ERL_NIF_TERM vals[]
            = {domain,
               type,
               protocol,
               ctrlPid,
               ctype,
               counters,
               readers,
               writers,
               acceptors};
        ERL_NIF_TERM info;
        unsigned int numKeys  = sizeof(keys) / sizeof(ERL_NIF_TERM);
        unsigned int numVals  = sizeof(vals) / sizeof(ERL_NIF_TERM);

        SSDBG( descP, ("SOCKET", "esock_socket_info -> "
                       "\r\n   numKeys: %d"
                       "\r\n   numVals: %d"
                       "\r\n", numKeys, numVals) );

        ESOCK_ASSERT( (numKeys == numVals) );

        if (!MKMA(env, keys, vals, numKeys, &info))
            return enif_make_badarg(env);

        return info;
    }
}


/*
 * Encode the socket domain
 */
static
ERL_NIF_TERM esock_socket_info_domain(ErlNifEnv*       env,
                                      ESockDescriptor* descP)
{
    int          domain = descP->domain;
    ERL_NIF_TERM edomain;

    if (NULL != esock_encode_domain(env, domain, &edomain)) {
        edomain = MKI(env, domain);
    }

    return edomain;
}


/*
 * Encode the socket type
 */
static
ERL_NIF_TERM esock_socket_info_type(ErlNifEnv*       env,
                                    ESockDescriptor* descP)
{
    int          type = descP->type;
    ERL_NIF_TERM etype;

    if (NULL != esock_encode_type(env, type, &etype)) {
        etype = MKI(env, type);
    }

    return etype;
}


/*
 * Encode the socket protocol
 */
static
ERL_NIF_TERM esock_socket_info_protocol(ErlNifEnv*       env,
                                        ESockDescriptor* descP)
{
    int          proto = descP->protocol;
    ERL_NIF_TERM eproto;

    if (NULL != esock_encode_protocol(env, proto, &eproto)) {
        eproto = MKI(env, proto);
    }

    return eproto;
}


/*
 * Encode the socket "create type"
 * That is "show" how this socket was created:
 *
 *           normal | fromfd | {fromfd, integer()}
 */
static
ERL_NIF_TERM esock_socket_info_ctype(ErlNifEnv*       env,
                                     ESockDescriptor* descP)
{
    ERL_NIF_TERM ctype;

    SSDBG( descP, ("SOCKET", "esock_socket_info_ctype {%d} -> entry with"
                   "\r\n   origFD:       %d"
                   "\r\n   closeOnClose: %s"
                   "\r\n", descP->sock,
                   descP->origFD, B2S(descP->closeOnClose)) );

    if (descP->origFD > 0) {
        /* Created from other FD */
        if (descP->closeOnClose) {
            /* We *have* dup'ed: {fromfd, integer()} */
            ctype = MKT2(env, MKA(env, "fromfd"), MKI(env, descP->origFD));
        } else {
            /* We have *not* dup'ed: fromfd */
            ctype = MKA(env, "fromfd");
        }
    } else {
        /* Normal socket */
        ctype = MKA(env, "normal");
    }

    SSDBG( descP, ("SOCKET", "esock_socket_info_ctype {%d} -> done:"
                   "\r\n   ctype: %T"
                   "\r\n", descP->sock, ctype) );

    return ctype;
}


/*
 * Collect all counters for a socket.
 */
static
ERL_NIF_TERM esock_socket_info_counters(ErlNifEnv*       env,
                                        ESockDescriptor* descP)
{
    ERL_NIF_TERM keys[] = {atom_read_byte,
                           atom_read_fails,
                           atom_read_pkg,
                           atom_read_pkg_max,
                           atom_read_tries,
                           atom_read_waits,
                           atom_write_byte,
                           atom_write_fails,
                           atom_write_pkg,
                           atom_write_pkg_max,
                           atom_write_tries,
                           atom_write_waits,
                           atom_acc_success,
                           atom_acc_fails,
                           atom_acc_tries,
                           atom_acc_waits};
    unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM);
    ERL_NIF_TERM vals[] = {MKUI(env, descP->readByteCnt),
                           MKUI(env, descP->readFails),
                           MKUI(env, descP->readPkgCnt),
                           MKUI(env, descP->readPkgMax),
                           MKUI(env, descP->readTries),
                           MKUI(env, descP->readWaits),
                           MKUI(env, descP->writeByteCnt),
                           MKUI(env, descP->writeFails),
                           MKUI(env, descP->writePkgCnt),
                           MKUI(env, descP->writePkgMax),
                           MKUI(env, descP->writeTries),
                           MKUI(env, descP->writeWaits),
                           MKUI(env, descP->accSuccess),
                           MKUI(env, descP->accFails),
                           MKUI(env, descP->accTries),
                           MKUI(env, descP->accWaits)};
    unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM);
    ERL_NIF_TERM info;

    SSDBG( descP, ("SOCKET", "esock_socket_info_counters -> "
                   "\r\n   numKeys: %d"
                   "\r\n   numVals: %d"
                   "\r\n", numKeys, numVals) );

    ESOCK_ASSERT( (numKeys == numVals) );

    if (!MKMA(env, keys, vals, numKeys, &info)) {
        SSDBG( descP, ("SOCKET", "esock_socket_info_counters -> "
                       "failed creating counters map\r\n") );        
        return enif_make_badarg(env);
    }

    SSDBG( descP, ("SOCKET", "esock_socket_info_counters -> done with"
                   "\r\n   info: %T"
                   "\r\n", info) );

    return info;
}
#endif


/* ----------------------------------------------------------------------
 * nif_command
 *
 * Description:
 * This function is intended to handle "various" commands. That is,
 * commands and operations that are not part of the socket API proper.
 * Currently it handles setting the global debug. Its a map with two
 * attributes command and (command) data:
 * #{command :: atom(), data :: term()}
 *
 * Command                    Data
 * 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, "notsup"));
#else
    ERL_NIF_TERM ecmd, ecdata, result;
    Uint16       cmd;

    SGDBG( ("SOCKET", "nif_command -> entry with %d args\r\n", argc) );

    if ((argc != 1) ||
        !IS_MAP(env,  argv[0])) {
        return enif_make_badarg(env);
    }
    ecmd = argv[0];

    SGDBG( ("SOCKET", "nif_command -> "
            "\r\n   (e) command: %T"
            "\r\n", ecmd) );

    if (!ecommand2command(env, ecmd, &cmd, &ecdata)) {
        SGDBG( ("SOCKET", "nif_command -> invalid command\r\n") );
        return esock_make_error(env, esock_atom_einval);
    }

    SGDBG( ("SOCKET", "nif_command -> "
            "\r\n   command:          %d"
            "\r\n   (e) command data: %T"
            "\r\n", cmd, ecdata) );

    result = esock_command(env, cmd, ecdata);
    
    SGDBG( ("SOCKET", "nif_command -> done with result: "
           "\r\n   %T"
           "\r\n", result) );

    return result;

#endif
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_command(ErlNifEnv* env, Uint16 cmd, ERL_NIF_TERM ecdata)
{
    ERL_NIF_TERM result;

    SGDBG( ("SOCKET", "esock_command -> entry with 0x%lX\r\n", cmd) );

    switch (cmd) {
    case ESOCK_CMD_DEBUG:
        result = esock_command_debug(env, ecdata);
        break;

    case ESOCK_CMD_SOCKET_DEBUG:
        result = esock_command_socket_debug(env, ecdata);
        break;

    case ESOCK_CMD_USE_SOCKET_REGISTRY:
        result = esock_command_use_socket_registry(env, ecdata);
        break;

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}

static
ERL_NIF_TERM esock_command_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata)
{
    ERL_NIF_TERM result;

    /* The data *should* be a boolean() */

    if (COMPARE(ecdata, esock_atom_true) == 0) {
        data.dbg = TRUE;
        result   = esock_atom_ok;
    } else if (COMPARE(ecdata, esock_atom_false) == 0) {
        data.dbg = FALSE;
        result   = esock_atom_ok;
    } else {
        SGDBG( ("SOCKET", "esock_command_debug -> invalid debug value: %T\r\n",
                ecdata) );
        result = esock_make_error(env, esock_atom_einval);
    }

    return result;
}

static
ERL_NIF_TERM esock_command_socket_debug(ErlNifEnv* env, ERL_NIF_TERM ecdata)
{
    BOOLEAN_T dbg = FALSE;

    /* The data *should* be a boolean() */

    if (COMPARE(ecdata, esock_atom_true) == 0) {
        dbg = TRUE;
    } else if (COMPARE(ecdata, esock_atom_false) == 0) {
        dbg = FALSE;
    } else {

        SGDBG( ("SOCKET",
                "esock_command_socket_debug -> invalid debug value: %T\r\n",
                ecdata) );

        return esock_make_error(env, esock_atom_einval);
    }

    MLOCK(data.cntMtx);
    data.sockDbg = dbg;
    MUNLOCK(data.cntMtx);

    return esock_atom_ok;;
}

static
ERL_NIF_TERM esock_command_use_socket_registry(ErlNifEnv* env, ERL_NIF_TERM ecdata)
{
    BOOLEAN_T useReg = FALSE;

    /* The data *should* be a boolean() */

    if (COMPARE(ecdata, esock_atom_true) == 0) {
        useReg = TRUE;
    } else if (COMPARE(ecdata, esock_atom_false) == 0) {
        useReg = FALSE;
    } else {

        SGDBG( ("SOCKET",
                "esock_command_use_socket_registry -> invalid debug value: %T\r\n",
                ecdata) );

        return esock_make_error(env, esock_atom_einval);
    }

    MLOCK(data.cntMtx);
    data.useReg = useReg;
    MUNLOCK(data.cntMtx);

    return esock_atom_ok;;
}

#endif


/* *** esock_socket_info_readers   ***
 * *** esock_socket_info_writers   ***
 * *** esock_socket_info_acceptors ***
 *
 * Calculate how many readers | writers | acceptors we have for this socket.
 * Current requestor + any waiting requestors (of the type).
 *
 */

#if !defined(__WIN32__)
#define ESOCK_INFO_REQ_FUNCS                                            \
    ESOCK_INFO_REQ_FUNC_DECL(readers,   currentReaderP,   readersQ)     \
    ESOCK_INFO_REQ_FUNC_DECL(writers,   currentWriterP,   writersQ)     \
    ESOCK_INFO_REQ_FUNC_DECL(acceptors, currentAcceptorP, acceptorsQ)

#define ESOCK_INFO_REQ_FUNC_DECL(F, CRP, Q)                             \
    static                                                              \
    ERL_NIF_TERM esock_socket_info_##F(ErlNifEnv*       env,            \
                                       ESockDescriptor* descP)          \
    {                                                                   \
        return socket_info_reqs(env, descP, descP->CRP, &descP->Q);     \
    }
ESOCK_INFO_REQ_FUNCS
#undef ESOCK_INFO_REQ_FUNC_DECL


static
ERL_NIF_TERM socket_info_reqs(ErlNifEnv*         env,
                              ESockDescriptor*   descP,
                              ESockRequestor*    crp,
                              ESockRequestQueue* q)
{
    ESockRequestQueueElement* tmp;
    ERL_NIF_TERM              info;
    unsigned int              cnt = 0;

    if (crp != NULL) {
        // We have an active requestor!
        cnt++;

        // And add all the waiting requestors
        tmp = q->first;
        while (tmp != NULL) {
            cnt++;
            tmp = tmp->nextP;
        }
    }

    info = MKUI(env, cnt);

    SSDBG( descP, ("SOCKET", "socket_info_reqs -> done with"
                   "\r\n   info: %T"
                   "\r\n", info) );

    return info;
}
#endif


/* ----------------------------------------------------------------------
 * nif_supports
 *
 * Description:
 * This function is intended to answer the question: "Is X supported?"
 * Currently three keys are "supported": options | sctp | ipv6
 * 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()}]}]
 * sctp            boolean()
 * ipv6            boolean()
 * local           boolean()
 * netns           boolean()
 * send_flags      [{SendFlag, boolean()}]
 * recv_flags      [{RecvFlag, 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, "notsup"));
#else
    int key2;
    
    SGDBG( ("SOCKET", "nif_supports -> entry with %d args\r\n", argc) );
    
    /* Extract arguments and perform preliminary validation */

    if (argc == 0)
        return esock_supports_0(env);

    if (argc == 1)
        return esock_supports_1(env, argv[0]);

    if (! GET_INT(env, argv[1], &key2))
        return enif_make_badarg(env);

    if (argc == 2)
        return esock_supports_2(env, argv[0], key2);

    return enif_make_badarg(env);
#endif
}

/* esock_supports - what features do we support?
 *
 * This gives information about what features actually
 * work on the current platform.
 */
#if !defined(__WIN32__)

static
ERL_NIF_TERM esock_supports_0(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(8);
    ERL_NIF_TERM is_supported, opts_list;

    SGDBG( ("SOCKET", "esock_supports_0 -> entry\r\n") );

#if defined(HAVE_SCTP)
    is_supported = esock_atom_true;
#else
    is_supported = esock_atom_false;
#endif
    TARRAY_ADD(opts, MKT2(env, esock_atom_sctp, is_supported));
               
    /* Is this (test) really sufficient for testing if we support IPv6? */
#if defined(HAVE_IPV6)
    is_supported = esock_atom_true;
#else
    is_supported = esock_atom_false;
#endif
    TARRAY_ADD(opts, MKT2(env, esock_atom_ipv6, is_supported));

#if defined(AF_LOCAL)
    is_supported = esock_atom_true;
#else
    is_supported = esock_atom_false;
#endif
    TARRAY_ADD(opts, MKT2(env, esock_atom_local, is_supported));

#if defined(HAVE_SETNS)
    is_supported = esock_atom_true;
#else
    is_supported = esock_atom_false;
#endif
    TARRAY_ADD(opts, MKT2(env, atom_netns, is_supported));

    TARRAY_TOLIST(opts, env, &opts_list);
    return opts_list;
}

static
ERL_NIF_TERM esock_supports_1(ErlNifEnv* env, ERL_NIF_TERM key)
{
    ERL_NIF_TERM result;

    SGDBG( ("SOCKET",
            "esock_supports_2 -> entry"
            "\r\n   key: %T"
            "\r\n", key) );

    if (COMPARE(key, atom_send_flags) == 0)
        result = esock_supports_send_flags(env);
    else if (COMPARE(key, atom_recv_flags) == 0)
        result = esock_supports_recv_flags(env);
    else
        result = MKEL(env);

    return result;
}

static
ERL_NIF_TERM esock_supports_2(ErlNifEnv* env, ERL_NIF_TERM key1, int key2)
{
    ERL_NIF_TERM result;

    SGDBG( ("SOCKET",
            "esock_supports_2 -> entry"
            "\r\n   key1: %%T"
            "\r\n   key2: %d"
            "\r\n", key1, key2) );

    if (COMPARE(key1, atom_options) == 0) {

        switch (key2) {
        case ESOCK_OPT_LEVEL_SOCKET:
            result = esock_supports_options_socket(env);
            break;
        case ESOCK_OPT_LEVEL_IP:
            result = esock_supports_options_ip(env);
            break;
        case ESOCK_OPT_LEVEL_IPV6:
            result = esock_supports_options_ipv6(env);
            break;
        case ESOCK_OPT_LEVEL_TCP:
            result = esock_supports_options_tcp(env);
            break;
        case ESOCK_OPT_LEVEL_UDP:
            result = esock_supports_options_udp(env);
            break;
        case ESOCK_OPT_LEVEL_SCTP:
            result = esock_supports_options_sctp(env);
            break;
        default:
            result = MKEL(env);
            break;
        }

    } else {
        result = MKEL(env);
    }

    return result;
}

#endif


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_supports_options_socket(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(128);
    ERL_NIF_TERM tmp, optsL;


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_ACCEPTFILTER => SO_ACCEPTFILTER *** */
    tmp = MKT2(env, esock_atom_acceptfilter, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_BUSY_POLL => SO_BUSY_POLL *** */
    tmp = MKT2(env, esock_atom_busy_poll, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_ERROR => SO_ERROR *** */
    tmp = MKT2(env, esock_atom_error, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_MARK => SO_MARK *** */
    tmp = MKT2(env, esock_atom_mark, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_PASSCRED => SO_PASSCRED *** */
#if defined(SO_PASSCRED)
    tmp = MKT2(env, esock_atom_passcred, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_passcred, esock_atom_false);
#endif
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SOCK_PEEK_OFF => SO_PEEK_OFF *** */
    /*
     * Due to the behaviour of peek when peek_off is used,
     * this option is reported as not supported.
     * Can cause an infinite loop when calling recv with
     * the peek flag (the second of two calls).
     * So, until we have added extra code here to know when
     * peek-off is used, we do not support this!
     *
#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
    */
    tmp = MKT2(env, esock_atom_peek_off, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SOCK_PEEKCRED => SO_PEEKCRED *** */
    tmp = MKT2(env, esock_atom_peekcred, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_RCVBUFFORCE => SO_RCVBUFFORCE *** */
    tmp = MKT2(env, esock_atom_rcvbufforce, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_RCVTIMEO => SO_RCVTIMEO *** */
#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
    tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_rcvtimeo, esock_atom_false);
#endif
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_RXQ_OVFL => SO_RXQ_OVFL *** */
    tmp = MKT2(env, esock_atom_rxq_ovfl, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SOCK_SETFIB => SO_SETFIB *** */
    tmp = MKT2(env, esock_atom_setfib, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_SNDBUFFORCE => SO_SNDBUFFORCE *** */
    tmp = MKT2(env, esock_atom_sndbufforce, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SOCK_SNDTIMEO => SO_SNDTIMEO *** */
#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
    tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_sndtimeo, esock_atom_false);
#endif
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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 esock_supports_options_ip(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(128);
    ERL_NIF_TERM tmp, optsL;


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IP_DONTFRAG => IP_DONTFRAG *** */
    tmp = MKT2(env, esock_atom_dontfrag, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IP_MTU => IP_MTU *** */
    tmp = MKT2(env, esock_atom_mtu, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IP_OPTIONS => IP_OPTIONS *** */
    tmp = MKT2(env, esock_atom_options, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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 esock_supports_options_ipv6(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(128);
    ERL_NIF_TERM tmp, optsL;


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_AUTH_LEVEL => IPV6_AUTH_LEVEL *** */
    tmp = MKT2(env, esock_atom_auth_level, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_IPV6_CHECKSUM => IPV6_CHECKSUM *** */
    tmp = MKT2(env, esock_atom_checksum, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_ESP_NETWORK_LEVEL => IPV6_ESP_NETWORK_LEVEL *** */
    tmp = MKT2(env, esock_atom_esp_network_level, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_IPV6_ESP_TRANS_LEVEL => IPV6_ESP_TRANS_LEVEL *** */
    tmp = MKT2(env, esock_atom_esp_trans_level, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_IPV6_FAITH => IPV6_FAITH *** */
    tmp = MKT2(env, esock_atom_faith, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_IPCOMP_LEVEL => IPV6_IPCOMP_LEVEL *** */
    tmp = MKT2(env, esock_atom_ipcomp_level, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_IPV6_JOIN_GROUP => IPV6_JOIN_GROUP *** */
    tmp = MKT2(env, esock_atom_join_group, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_IPV6_LEAVE_GROUP => IPV6_LEAVE_GROUP *** */
    tmp = MKT2(env, esock_atom_leave_group, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_PORTRANGE => IPV6_PORTRANGE *** */
    tmp = MKT2(env, esock_atom_portrange, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_IPV6_PKTOPTIONS => IPV6_PKTOPTIONS *** */
    tmp = MKT2(env, esock_atom_pktoptions, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_RECVHOPLIMIT => IPV6_RECVHOPLIMIT *** */
#if defined(IPV6_HOPLIMIT)
    tmp = MKT2(env, esock_atom_recvhoplimit, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_recvhoplimit, esock_atom_false);
#endif
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_RECVTCLASS => IPV6_RECVTCLASS *** */
#if defined(IPV6_RECVTCLASS)
    tmp = MKT2(env, esock_atom_recvtclass, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_recvtclass, esock_atom_false);
#endif
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_TCLASS => IPV6_TCLASS *** */
#if defined(IPV6_TCLASS)
    tmp = MKT2(env, esock_atom_tclass, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_tclass, esock_atom_false);
#endif
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_IPV6_USE_MIN_MTU => IPV6_USE_MIN_MTU *** */
    tmp = MKT2(env, esock_atom_use_min_mtu, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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 esock_supports_options_tcp(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(32);
    ERL_NIF_TERM tmp, optsL;


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_TCP_INFO => TCP_INFO *** */
    tmp = MKT2(env, esock_atom_info, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_KEEPCNT => TCP_KEEPCNT *** */
    tmp = MKT2(env, esock_atom_keepcnt, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_KEEPIDLE => TCP_KEEPIDLE *** */
    tmp = MKT2(env, esock_atom_keepidle, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_KEEPINTVL => TCP_KEEPINTVL *** */
    tmp = MKT2(env, esock_atom_keepintvl, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_MAXSEG => TCP_MAXSEG *** */
#if defined(TCP_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);


    /* *** ESOCK_OPT_TCP_MD5SIG => TCP_MD5SIG *** */
    tmp = MKT2(env, esock_atom_md5sig, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_NODELAY => TCP_NODELAY *** */
#if defined(TCP_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);


    /* *** ESOCK_OPT_TCP_NOOPT => TCP_NOOPT *** */
    tmp = MKT2(env, esock_atom_noopt, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_NOPUSH => TCP_NOPUSH *** */
    tmp = MKT2(env, esock_atom_nopush, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_TCP_SYNCNT => TCP_SYNCNT *** */
    tmp = MKT2(env, esock_atom_syncnt, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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 esock_supports_options_udp(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(8);
    ERL_NIF_TERM tmp, optsL;


    /* *** ESOCK_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 esock_supports_options_sctp(ErlNifEnv* env)
{
    SocketTArray opts = TARRAY_CREATE(64);
    ERL_NIF_TERM tmp, optsL;


    /* *** ESOCK_OPT_SCTP_ADAPTION_LAYER => SCTP_ADAPTION_LAYER *** */
    tmp = MKT2(env, esock_atom_adaption_layer, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_AUTH_ACTIVE_KEY => SCTP_AUTH_ACTIVE_KEY *** */
    tmp = MKT2(env, esock_atom_auth_active_key, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_AUTH_ASCONF => SCTP_AUTH_ASCONF *** */
    tmp = MKT2(env, esock_atom_auth_asconf, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_AUTH_CHUNK => SCTP_AUTH_CHUNK *** */
    tmp = MKT2(env, esock_atom_auth_chunk, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_AUTH_DELETE_KEY => SCTP_AUTH_DELETE_KEY *** */
    tmp = MKT2(env, esock_atom_auth_delete_key, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_AUTH_KEY => SCTP_AUTH_KEY *** */
    tmp = MKT2(env, esock_atom_auth_key, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_CONTEXT => SCTP_CONTEXT *** */
    tmp = MKT2(env, esock_atom_context, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_DEFAULT_SEND_PARAMS => SCTP_DEFAULT_SEND_PARAMS *** */
    tmp = MKT2(env, esock_atom_default_send_params, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_DELAYED_ACK_TIME => SCTP_DELAYED_ACK_TIME *** */
    tmp = MKT2(env, esock_atom_delayed_ack_time, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_HMAC_IDENT => SCTP_HMAC_IDENT *** */
    tmp = MKT2(env, esock_atom_hmac_ident, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_EXPLICIT_EOR => SCTP_EXPLICIT_EOR *** */
    tmp = MKT2(env, esock_atom_explicit_eor, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_FRAGMENT_INTERLEAVE => SCTP_FRAGMENT_INTERLEAVE *** */
    tmp = MKT2(env, esock_atom_fragment_interleave, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_LOCAL_AUTH_CHUNKS => SCTP_LOCAL_AUTH_CHUNKS *** */
    tmp = MKT2(env, esock_atom_local_auth_chunks, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_MAXBURST => SCTP_MAXBURST *** */
    tmp = MKT2(env, esock_atom_maxburst, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_PARTIAL_DELIVERY_POINT => SCTP_PARTIAL_DELIVERY_POINT *** */
    tmp = MKT2(env, esock_atom_partial_delivery_point, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_PEER_ADDR_PARAMS => SCTP_PEER_ADDR_PARAMS *** */
    tmp = MKT2(env, esock_atom_peer_addr_params, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_PEER_AUTH_CHUNKS => SCTP_PEER_AUTH_CHUNKS *** */
    tmp = MKT2(env, esock_atom_peer_auth_chunks, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_PRIMARY_ADDR => SCTP_PRIMARY_ADDR *** */
    tmp = MKT2(env, esock_atom_primary_addr, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_OPT_SCTP_RESET_STREAMS => SCTP_RESET_STREAMS *** */
    tmp = MKT2(env, esock_atom_reset_streams, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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);


    /* *** ESOCK_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);


    /* *** ESOCK_OPT_SCTP_STATUS => SCTP_STATUS *** */
    tmp = MKT2(env, esock_atom_status, esock_atom_false);
    TARRAY_ADD(opts, tmp);


    /* *** ESOCK_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 0

#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_supports_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 esock_supports_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



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_supports_local(ErlNifEnv* env)
{
    ERL_NIF_TERM supports;

#if defined(AF_LOCAL)
    supports = esock_atom_true;
#else
    supports = esock_atom_false;
#endif

    return supports;
}
#endif



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_supports_netns(ErlNifEnv* env)
{
    ERL_NIF_TERM supports;

#if defined(HAVE_SETNS)
    supports = esock_atom_true;
#else
    supports = esock_atom_false;
#endif

    return supports;
}
#endif

#endif // #if 0


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_supports_send_flags(ErlNifEnv* env)
{
    SocketTArray sflags = TARRAY_CREATE(8);
    ERL_NIF_TERM tmp, sflagsL;


    /* *** ESOCK_SEND_FLAG_OOB => MSG_OOB *** */
#if defined(MSG_OOB)
    tmp = MKT2(env, esock_atom_oob, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_oob, esock_atom_false);
#endif
    TARRAY_ADD(sflags, tmp);


    /* *** ESOCK_SEND_FLAG_DONTROUTE => MSG_DONTROUTE *** */
#if defined(MSG_DONTROUTE)
    tmp = MKT2(env, esock_atom_dontroute, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_dontroute, esock_atom_false);
#endif
    TARRAY_ADD(sflags, tmp);


    /* *** ESOCK_SEND_FLAG_EOR => MSG_EOR *** */
#if defined(MSG_EOR)
    tmp = MKT2(env, esock_atom_eor, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_eor, esock_atom_false);
#endif
    TARRAY_ADD(sflags, tmp);


    /* *** ESOCK_SEND_FLAG_CONFIRM => MSG_CONFIRM *** */
#if defined(MSG_CONFIRM)
    tmp = MKT2(env, esock_atom_confirm, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_confirm, esock_atom_false);
#endif
    TARRAY_ADD(sflags, tmp);


    /* *** ESOCK_SEND_FLAG_NOSIGNAL => MSG_NOSIGNAL *** */
#if defined(MSG_NOSIGNAL)
    tmp = MKT2(env, esock_atom_nosignal, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_nosignal, esock_atom_false);
#endif
    TARRAY_ADD(sflags, tmp);


    /* *** ESOCK_SEND_FLAG_MORE => MSG_MORE *** */
#if defined(MSG_MORE)
    tmp = MKT2(env, esock_atom_more, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_more, esock_atom_false);
#endif
    TARRAY_ADD(sflags, tmp);


    TARRAY_TOLIST(sflags, env, &sflagsL);
    
    return sflagsL;
}
#endif





#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_supports_recv_flags(ErlNifEnv* env)
{
    SocketTArray rflags = TARRAY_CREATE(8);
    ERL_NIF_TERM tmp, rflagsL;


    /* *** ESOCK_SEND_FLAG_OOB => MSG_OOB *** */
#if defined(MSG_OOB)
    tmp = MKT2(env, esock_atom_oob, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_oob, esock_atom_false);
#endif
    TARRAY_ADD(rflags, tmp);


    /* *** ESOCK_RECV_FLAG_PEEK => MSG_PEEK *** */
#if defined(MSG_PEEK)
    tmp = MKT2(env, esock_atom_peek, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_peek, esock_atom_false);
#endif
    TARRAY_ADD(rflags, tmp);


    /* *** ESOCK_RECV_FLAG_TRUNC => MSG_TRUNC *** */
#if defined(MSG_TRUNC)
    tmp = MKT2(env, esock_atom_trunc, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_trunc, esock_atom_false);
#endif
    TARRAY_ADD(rflags, tmp);


    /* *** ESOCK_RECV_FLAG_ERRQUEUE => MSG_ERRQUEUE *** */
#if defined(MSG_ERRQUEUE)
    tmp = MKT2(env, esock_atom_errqueue, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_errqueue, esock_atom_false);
#endif
    TARRAY_ADD(rflags, tmp);


    /* *** ESOCK_RECV_FLAG_CMSG_CLOEXEC => MSG_CMSG_CLOEXEC *** */
#if defined(MSG_CMSG_CLOEXEC)
    tmp = MKT2(env, esock_atom_cmsg_cloexec, esock_atom_true);
#else
    tmp = MKT2(env, esock_atom_cmsg_cloexec, esock_atom_false);
#endif
    TARRAY_ADD(rflags, tmp);


    TARRAY_TOLIST(rflags, env, &rflagsL);
    
    return rflagsL;
}
#endif





/* ----------------------------------------------------------------------
 * nif_open
 *
 * Description:
 * Create an endpoint for communication.
 * This function "exist" in two variants.
 * One with two args and onewith four.
 *
 * Arguments (2):
 * FD       - File Descriptor (of an already open socket).
 * Extra    - A map with extra options.
 *            The options are:
 *               [M] dup    - boolean() - Shall the fd be dup'ed or not.
 *               [O] bound  - boolean() - Is the fd already bound.
 *               [O] domain - domain()  - We may not be able to retrieve
 *                                        this on all platforms, and in
 *                                        *those* cases this *must* be
 *                                        provided.
 *               [O] type   - type()    - We may not be able to retrieve
 *                                        this on all platforms, and in
 *                                        *those* cases this *must* be
 *                                        provided.
 *               [O] proto - protocol() - We may not be able to retrieve
 *                                        this on all platforms, and in
 *                                        *those* cases this *must* be
 *                                        provided.
 *               [O] use_registry - boolean() - Shall we use the socket 
 *                                        registry for this socket.
 * Arguments (4):
 * 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 are:
 *               netns        - string()  - Network namespace.
 *               use_registry - boolean() - Shall we use the socket 
 *                                          registry for this socket.
 *            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, "notsup"));
#else
    ERL_NIF_TERM result;

    SGDBG( ("SOCKET", "nif_open -> "
            "\r\n   argc: %d"
            "\r\n", argc) );

    switch (argc) {
    case 2:
        /* The FD-version */
        {
            int          fd;
            ERL_NIF_TERM eopts;

            if (!GET_INT(env, argv[0], &fd) ||
                !IS_MAP(env,  argv[1])) {
                return enif_make_badarg(env);
            }
            eopts = argv[1];

            SGDBG( ("SOCKET", "nif_open -> "
                    "\r\n   FD:    %d"
                    "\r\n   eopts: %T"
                    "\r\n", fd, eopts) );

            MLOCK(data.cntMtx);
            result = esock_open2(env, fd, eopts);
            MUNLOCK(data.cntMtx);
        }
        break;

    case 4:
        /* The normal version */
        {
            int          edomain, etype;
            ERL_NIF_TERM eproto; // integer() (normal case) | {raw, integer()}
            ERL_NIF_TERM eopts;
            int          domain, type, proto;

            /* Extract arguments and perform preliminary validation */

            if (!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];
            eopts  = argv[3];

            SGDBG( ("SOCKET", "nif_open -> "
                    "\r\n   edomain: %T"
                    "\r\n   etype:   %T"
                    "\r\n   eproto:  %T"
                    "\r\n   eopts:   %T"
                    "\r\n", argv[0], argv[1], eproto, eopts) );

            if (!edomain2domain(edomain, &domain)) {
                SGDBG( ("SOCKET", "nif_open -> invalid domain: %d\r\n", edomain) );
                return esock_make_error(env, esock_atom_einval);
            }

            if (!etype2type(etype, &type)) {
                SGDBG( ("SOCKET", "nif_open -> invalid type: %d\r\n", etype) );
                return esock_make_error(env, esock_atom_einval);
            }

            if (!eproto2proto(env, eproto, &proto)) {
                SGDBG( ("SOCKET", "nif_open -> invalid protocol: %d\r\n", eproto) );
                return esock_make_error(env, esock_atom_einval);
            }

            MLOCK(data.cntMtx);
            result = esock_open4(env, domain, type, proto, eopts);
            MUNLOCK(data.cntMtx);
        }
        break;

    default:
        SGDBG( ("SOCKET", "nif_open -> invalid number of arguments: %d"
                "\r\n", argc) );
        result = enif_make_badarg(env);
        break;
    }

    SGDBG( ("SOCKET", "nif_open -> done with result: "
            "\r\n   %T"
            "\r\n", result) );

    return result;

#endif // if defined(__WIN32__)
}


/* esock_open - create an endpoint (from an existing fd) 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 esock_open2(ErlNifEnv*   env,
                         int          fd,
                         ERL_NIF_TERM eopts)
{
    BOOLEAN_T        dbg    = esock_open_is_debug(env, eopts, data.sockDbg);
    BOOLEAN_T        useReg = esock_open_use_registry(env, eopts, data.useReg);
    ESockDescriptor* descP;
    ERL_NIF_TERM     res, reason;
    int              domain, type, protocol;
    int              save_errno = 0;
    BOOLEAN_T        closeOnClose;
    SOCKET           sock;
    HANDLE           event;

    SSDBG2( dbg,
            ("SOCKET", "esock_open2 -> entry with"
             "\r\n   fd:    %d"
             "\r\n   eopts: %T"
             "\r\n", fd, eopts) );

    /*
     * Before we do anything else, we try to retrieve domain, type and protocol
     * This information is either present in the eopts map or if not we need
     * to "get" it from the system (getsockopt).
     * Note that its not possible to get all of these on all platoforms,
     * and in those cases the user *must* provide us with them (eopts).
     *
     * We try the system first (since its more reliable) and if that fails
     * we check the eopts map. If neither one works, we *give up*!
     */

    if (!esock_open_which_domain(fd, &domain)) {
        SSDBG2( dbg,
                ("SOCKET",
                 "esock_open2 -> failed get domain from system\r\n") );
        if (!esock_open2_get_domain(env, eopts, &domain)) {
            reason = MKA(env, "epfnosupport");
            return esock_make_error(env, reason);
        }
    }
    
    if (!esock_open_which_type(fd, &type)) {
        SSDBG2( dbg,
                ("SOCKET", "esock_open2 -> failed get type from system\r\n") );
        if (!esock_open2_get_type(env, eopts, &type)) {
            reason = MKA(env, "esocktnosupport");
            return esock_make_error(env, reason);
                                    
        }
    }
    
    if (!esock_open_which_protocol(fd, &protocol)) {
        SSDBG2( dbg,
                ("SOCKET", "esock_open2 -> failed get protocol from system\r\n") );
        if (!esock_open2_get_protocol(env, eopts, &protocol)) {
            reason = MKA(env, "eprotonosupport");
            return esock_make_error(env, reason);
        }
    }


    SSDBG2( dbg,
            ("SOCKET", "esock_open2 -> "
             "\r\n   domain:   %d"
             "\r\n   type:     %d"
             "\r\n   protocol: %d"
             "\r\n", domain, type, protocol) );

    
    if (esock_open2_todup(env, eopts)) {
        /* We shall dup the socket */
        if (IS_SOCKET_ERROR(sock = dup(fd))) {
            save_errno = sock_errno();

            SSDBG2( dbg,
                    ("SOCKET",
                     "esock_open2 -> dup failed: %d\r\n",
                     save_errno) );

            reason = MKA(env, erl_errno_id(save_errno));
            return esock_make_error(env, reason);
        }
        closeOnClose = TRUE;
    } else {
        sock         = fd;
        closeOnClose = FALSE;
    }

    event = sock;

    SET_NONBLOCKING(sock);

    /* Create and initiate the socket "descriptor" */
    if ((descP = alloc_descriptor(sock, event)) == NULL) {
        if (closeOnClose) sock_close(sock);
        // Not sure if this is really the proper error, but...
        return enif_make_badarg(env);
    }

    descP->domain       = domain;
    descP->type         = type;
    descP->protocol     = protocol;
    descP->closeOnClose = closeOnClose;
    descP->origFD       = fd;

    /* Check if we are already connected, if so change state */
    {
        ESockAddress remote;
        socklen_t    addrLen = sizeof(remote);
        sys_memzero((char *) &remote, addrLen);
        if (sock_peer(descP->sock,
                      (struct sockaddr*) &remote,
                      &addrLen) == 0) {
            SSDBG2( dbg, ("SOCKET", "esock_open2 -> connected\r\n") );
            descP->writeState |= ESOCK_STATE_CONNECTED;
        } else {
            SSDBG2( dbg, ("SOCKET", "esock_open2 -> not connected\r\n") );
        }
    }

    /* And create the 'socket' resource */
    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("esock_open2 -> ctrl",
             env, descP,
             &descP->ctrlPid,
             &descP->ctrlMon) != 0)
        return esock_make_error(env, atom_exmonitor);


    descP->dbg    = dbg;
    descP->useReg = useReg;
    inc_socket(domain, type, protocol);

    /* And finally (maybe) update the registry.
     * Shall we keep track of the fact that this socket is created elsewhere?
     */
    if (descP->useReg) esock_send_reg_add_msg(env, res);

    SSDBG2( dbg,
            ("SOCKET", "esock_open2 -> done: %T\r\n", res) );

    return esock_make_ok2(env, res);
}


/* The eextra contains a boolean 'dup' key. Defaults to TRUE.
 */
static
BOOLEAN_T esock_open2_todup(ErlNifEnv* env, ERL_NIF_TERM eextra)
{
    return esock_get_bool_from_map(env, eextra, atom_dup, TRUE);
}

/* The eextra contains an integer 'domain' key.
 */
static
BOOLEAN_T esock_open2_get_domain(ErlNifEnv* env,
                                 ERL_NIF_TERM eopts, int* domain)
{
    ERL_NIF_TERM key = MKA(env, "domain");
    int          edomain;
    
    SGDBG( ("SOCKET", "esock_open2_get_domain -> entry with"
            "\r\n   eopts: %T"
            "\r\n", eopts) );

    if (esock_get_int_from_map(env, eopts, key, &edomain)) {
        /* decode */
        if (edomain2domain(edomain, domain))
            return TRUE;
        else
            return FALSE;
    } else {
        *domain = edomain; // Contains an "error code"
        return FALSE;
    }
}

/* The eextra contains an integer 'type' key.
 */
static
BOOLEAN_T esock_open2_get_type(ErlNifEnv* env,
                               ERL_NIF_TERM eopts, int* type)
{
    ERL_NIF_TERM key = MKA(env, "type");
    int          etype;
    
    SGDBG( ("SOCKET", "esock_open2_get_type -> entry with"
            "\r\n   eopts: %T"
            "\r\n", eopts) );

    if (esock_get_int_from_map(env, eopts, key, &etype)) {
        /* decode */
        if (etype2type(etype, type))
            return TRUE;
        else
            return FALSE;
    } else {
        *type = etype; // Contains an "error code"
        return FALSE;
    }
}

/* The eextra contains an integer 'protocol' key.
 */
static
BOOLEAN_T esock_open2_get_protocol(ErlNifEnv* env,
                                   ERL_NIF_TERM eopts, int* protocol)
{
    ERL_NIF_TERM key = MKA(env, "protocol");
    int          eproto;
    
    SGDBG( ("SOCKET", "esock_open2_get_protocol -> entry with"
            "\r\n   eopts: %T"
            "\r\n", eopts) );

    if (esock_get_int_from_map(env, eopts, key, &eproto)) {
        /* decode */
        if (eproto2proto(env, eproto, protocol))
            return TRUE;
        else
            return FALSE;
    } else {
        *protocol = eproto; // Contains an "error code"
        return FALSE;
    }
}

#endif // if defined(__WIN32__)

    
/* esock_open4 - 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 esock_open4(ErlNifEnv*   env,
                         int          domain,
                         int          type,
                         int          protocol,
                         ERL_NIF_TERM eopts)
{
    BOOLEAN_T        dbg    = esock_open_is_debug(env, eopts, data.sockDbg);
    BOOLEAN_T        useReg = esock_open_use_registry(env, eopts, data.useReg);
    ESockDescriptor* descP;
    ERL_NIF_TERM     res;
    int              proto = protocol, save_errno;
    SOCKET           sock;
    HANDLE           event;
    char*            netns;
#ifdef HAVE_SETNS
    int              current_ns = 0;
#endif

    SSDBG2( dbg,
            ("SOCKET", "esock_open4 -> entry with"
             "\r\n   domain:   %d"
             "\r\n   type:     %d"
             "\r\n   protocol: %d"
             "\r\n   eopts:    %T"
             "\r\n", domain, type, protocol, eopts) );


#ifdef HAVE_SETNS
    if (esock_open4_get_netns(env, eopts, &netns)) {
        SGDBG( ("SOCKET", "nif_open -> namespace: %s\r\n", netns) );
    }
#else
    netns = NULL;
#endif


#ifdef HAVE_SETNS
    if ((netns != NULL) &&
        !change_network_namespace(netns, &current_ns, &save_errno)) {
        FREE(netns);
        return esock_make_error_errno(env, save_errno);
    }
#endif

    if (IS_SOCKET_ERROR(sock = sock_open(domain, type, proto))) {
        if (netns != NULL) FREE(netns);
        return esock_make_error_errno(env, sock_errno());
    }

    SSDBG2( dbg, ("SOCKET", "esock_open -> open success: %d\r\n", sock) );


    /* NOTE that if the protocol = 0 (default) and the domain is not
     * local (AF_LOCAL) we need to explicitly get the protocol here!
     */
    
    if ((proto == 0)
#if defined(AF_LOCAL)
        && (domain != AF_LOCAL)
#endif
        )
        if (!esock_open_which_protocol(sock, &proto)) {
            if (proto == ESOCK_WHICH_PROTO_ERROR) {
                save_errno = sock_errno();
                while (IS_SOCKET_ERROR(sock_close(sock)) &&
                       (sock_errno() == EINTR));
                if (netns != NULL) FREE(netns);
                return esock_make_error_errno(env, save_errno);
            } else {
                while (IS_SOCKET_ERROR(sock_close(sock)) &&
                       (sock_errno() == EINTR));
                if (netns != NULL) FREE(netns);
                return esock_make_error(env, esock_atom_eafnosupport);
            }
        }


#ifdef HAVE_SETNS
    if ((netns != NULL) &&
        !restore_network_namespace(current_ns, sock, &save_errno)) {
        FREE(netns);
        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 (IS_SOCKET_ERROR(sock_close(sock)) && (sock_errno() == EINTR));
        return esock_make_error_errno(env, save_errno);
    }

    SSDBG2( dbg, ("SOCKET", "esock_open4 -> 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->domain   = domain;
    descP->type     = type;
    descP->protocol = proto;

    /*
     * 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) {
        sock_close(sock);
        descP->sock = INVALID_SOCKET;
        return esock_make_error(env, atom_exself);
    }

    if (MONP("esock_open -> ctrl",
             env, descP,
             &descP->ctrlPid,
             &descP->ctrlMon) != 0) {
        sock_close(sock);
        descP->sock = INVALID_SOCKET;
        return esock_make_error(env, atom_exmonitor);
    }


    descP->dbg    = data.sockDbg;
    descP->useReg = useReg;
    inc_socket(domain, type, protocol);

    /* And finally (maybe) update the registry */
    if (descP->useReg) esock_send_reg_add_msg(env, res);

    return esock_make_ok2(env, res);
}


/* The eextra map "may" contain a boolean 'debug' key.
 */
static
BOOLEAN_T esock_open_is_debug(ErlNifEnv* env, ERL_NIF_TERM eextra,
                              BOOLEAN_T dflt)
{
    return esock_get_bool_from_map(env, eextra, esock_atom_debug, dflt);
}


static
BOOLEAN_T esock_open_use_registry(ErlNifEnv* env, ERL_NIF_TERM eextra,
                                  BOOLEAN_T dflt)
{
    return esock_get_bool_from_map(env, eextra, atom_use_registry, dflt);
}


static
BOOLEAN_T esock_open_which_domain(SOCKET sock, int* domain)
{
#if defined(SO_DOMAIN)
    int          val;
    SOCKOPTLEN_T valSz = sizeof(val);
    int          res;

    res = sock_getopt(sock, SOL_SOCKET, SO_DOMAIN, &val, &valSz);

    if (res != 0) {
        *domain = ESOCK_WHICH_DOMAIN_ERROR;
        return FALSE;
    } else {
        *domain = val;
        return TRUE;
    }
#else
    *domain = ESOCK_WHICH_DOMAIN_UNSUP;
    return FALSE;
#endif
}


static
BOOLEAN_T esock_open_which_type(SOCKET sock, int* type)
{
#if defined(SO_TYPE)
    int          val;
    SOCKOPTLEN_T valSz = sizeof(val);
    int          res;

    res = sock_getopt(sock, SOL_SOCKET, SO_TYPE, &val, &valSz);

    if (res != 0) {
        *type = ESOCK_WHICH_TYPE_ERROR;
        return FALSE;
    } else {
        *type = val;
        return TRUE;
    }
#else
    *type = ESOCK_WHICH_TYPE_UNSUP;
    return FALSE;
#endif
}


static
BOOLEAN_T esock_open_which_protocol(SOCKET sock, int* proto)
{
#if defined(SO_PROTOCOL)
    int          val;
    SOCKOPTLEN_T valSz = sizeof(val);
    int          res;

    res = sock_getopt(sock, SOL_SOCKET, SO_PROTOCOL, &val, &valSz);

    if (res != 0) {
        *proto = ESOCK_WHICH_PROTO_ERROR;
        return FALSE;
    } else {
        *proto = val;
        return TRUE;
    }
#else
    *proto = ESOCK_WHICH_PROTO_UNSUP;
    return FALSE;
#endif
}


#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"
            "\r\n", netns) );

    if (netns != NULL) {
        current_ns = open("/proc/self/ns/net", O_RDONLY);
        if (IS_SOCKET_ERROR(current_ns)) {
            *cns = current_ns;
            *err = sock_errno();
            return FALSE;
        }
        new_ns = open(netns, O_RDONLY);
        if (IS_SOCKET_ERROR(new_ns)) {
            save_errno = sock_errno();
            while (IS_SOCKET_ERROR(close(current_ns)) &&
                   sock_errno() == EINTR);
            *cns = -1;
            *err = save_errno;
            return FALSE;
        }
        if (setns(new_ns, CLONE_NEWNET) != 0) {
            save_errno = sock_errno();
            while (IS_SOCKET_ERROR(close(new_ns)) &&
                   (sock_errno() == EINTR));
            while (IS_SOCKET_ERROR(close(current_ns)) &&
                   (sock_errno() == EINTR));
            *cns = -1;
            *err = save_errno;
            return FALSE;
        } else {
            while (IS_SOCKET_ERROR(close(new_ns)) &&
                   (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"
            "\r\n", 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 (IS_SOCKET_ERROR(close(sock)) &&
                   sock_errno() == EINTR);
            sock = INVALID_SOCKET;
            while (IS_SOCKET_ERROR(close(ns)) &&
                   sock_errno() == EINTR);
            *err = save_errno;
            return FALSE;
        } else {
            while (IS_SOCKET_ERROR(close(ns)) &&
                   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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     eSockAddr, ret;
    ESockAddress     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) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
        return enif_make_badarg(env);
    }
    eSockAddr = argv[1];

    if ((xres = esock_decode_sockaddr(env, eSockAddr, &sockAddr, &addrLen)) != NULL)
        return esock_make_error_str(env, xres);

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_bind(%T) {%d,%s,0x%X} ->"
            "\r\n   SockAddr: %T"
            "\r\n",
            argv[0], descP->sock, B2S(descP->closing), descP->readState,
            eSockAddr) );

    ret = esock_bind(env, descP, &sockAddr, addrLen);

    SSDBG( descP, ("SOCKET", "nif_bind(%T) -> done with"
                   "\r\n   ret: %T"
                   "\r\n", argv[0], ret) );

    MUNLOCK(descP->readMtx);

    return ret;
#endif // if defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_bind(ErlNifEnv*       env,
                        ESockDescriptor* descP,
                        ESockAddress*    sockAddrP,
                        unsigned int     addrLen)
{
    int port;

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    if (IS_SOCKET_ERROR(sock_bind(descP->sock,
                                  (struct sockaddr*) sockAddrP, addrLen))) {
        return esock_make_error_errno(env, sock_errno());
    }

    descP->readState |= ESOCK_STATE_BOUND;

    port = which_address_port(sockAddrP);

    SSDBG( descP, ("SOCKET", "esock_bind {%d} -> port: %d\r\n",
                   descP->sock, sock_ntohs(port)) );

    if (port == 0) {
        SOCKLEN_T len = sizeof(ESockAddress);
        sys_memzero((char *) sockAddrP, len);
        sock_name(descP->sock, &sockAddrP->sa, &len);
        port = which_address_port(sockAddrP);
    } else if (port == -1) {
        port = 0;
    }
    
    return esock_make_ok2(env, MKI(env, sock_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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     res, sockRef;
    char*            xres;
    ESockAddress     addr, *addrP;
    socklen_t        addrLen;

    SGDBG( ("SOCKET", "nif_connect -> entry with argc: %d\r\n", argc) );

    /* Extract arguments and perform preliminary validation */

    if (argc >= 1) {
        sockRef = argv[0];
        if (! ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP))
            return enif_make_badarg(env);
    } else {
        return enif_make_badarg(env);
    }

    if (argc >= 2) {
        ERL_NIF_TERM eSockAddr = argv[1];

        if ((xres = esock_decode_sockaddr(env, eSockAddr, &addr, &addrLen))
            != NULL) {
            return esock_make_error_str(env, xres);
        }
        addrP = &addr;

        MLOCK(descP->writeMtx);

        SSDBG( descP,
               ("SOCKET", "nif_connect(%T), {%d,%s,0x%X} ->"
                "\r\n   SockAddr: %T"
                "\r\n",
                sockRef, descP->sock, B2S(descP->closing), descP->writeState,
                eSockAddr) );
    } else {
        addrP = NULL;
        addrLen = 0;

        MLOCK(descP->writeMtx);

        SSDBG( descP,
               ("SOCKET", "nif_connect(%T), {%d,%s,0x%X} ->"
                "\r\n",
                sockRef, descP->sock, B2S(descP->closing), descP->writeState
                ) );
    }

    res = esock_connect(env, descP, sockRef, addrP, addrLen);

    SSDBG( descP, ("SOCKET", "nif_connect(%T) -> done with"
                   "\r\n   res: %T"
                   "\r\n", sockRef, res) );

    MUNLOCK(descP->writeMtx);

    return res;

#endif // if !defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_connect(ErlNifEnv*       env,
                           ESockDescriptor* descP,
                           ERL_NIF_TERM     sockRef,
                           ESockAddress*    addrP,
                           socklen_t        addrLen)
{
    int              sres, save_errno;
    ErlNifPid        self;

    /* 
     * Verify that we are in the proper state
     */

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);
    
    if (enif_self(env, &self) == NULL)
        return esock_make_error(env, atom_exself);

    /* Connect and Write uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->currentWriterP != NULL)
        return esock_make_error(env, esock_atom_einval);

    if (descP->connectorP != NULL) {
        /* Connect in progress */

        if (COMPARE_PIDS(&self, &descP->connector.pid) != 0) {
            /* Other process has connect in progress */
            return esock_make_error_errno(env, EALREADY);
        }

        /* Finalize after received select message */

        requestor_release("esock_connect finalize -> connected",
                          env, descP, descP->connectorP);
        descP->connectorP = NULL;

        descP->writeState &= ~ESOCK_STATE_CONNECTING;

        if (! verify_is_connected(descP, &save_errno)) {
            return esock_make_error_errno(env, save_errno);
        }

        descP->writeState |= ESOCK_STATE_CONNECTED;

        return esock_atom_ok;
    }

    /* No connect in progress */

    if (addrP == NULL)
        return esock_make_error(env, esock_atom_einval);
    
    /* Initial connect call, with address */

    if (sock_connect(descP->sock, (struct sockaddr*) addrP, addrLen) == 0) {
        /* Success already! */
        SSDBG( descP, ("SOCKET", "esock_connect {%d} -> connected\r\n",
                       descP->sock) );

        descP->writeState |= ESOCK_STATE_CONNECTED;

        return esock_atom_ok;
    }

    /* Connect returned error */
    save_errno = sock_errno();

    switch (save_errno) {

    case ERRNO_BLOCK:   /* Winsock2            */
    case EINPROGRESS:   /* Unix & OSE!!        */
        SSDBG( descP,
               ("SOCKET", "esock_connect {%d} -> would block => select\r\n",
                descP->sock) );
        {
            /* Initiate connector */

            descP->connector.pid = self;
            if (MONP("esock_connect -> conn",
                     env, descP, &self, &descP->connector.mon) != 0) {

                MON_INIT(&descP->connector.mon);
                return esock_make_error(env, atom_exmonitor);
            }
            descP->connector.env = esock_alloc_env("connector");
            descP->connectorP = &descP->connector;
            descP->connector.ref = MKREF(descP->connector.env);

            if ((sres =
                 esock_select_write(env, descP->sock, descP, NULL,
                                    sockRef,
                                    CP_TERM(env,
                                            descP->connector.ref))) < 0) {

                requestor_release("esock_connect -> select failed",
                                  env, descP, descP->connectorP);
                descP->connectorP = NULL;
                return esock_make_error(env, atom_exselect);
            } else {
                descP->writeState |= ESOCK_STATE_CONNECTING;
                return
                    MKT2(env, atom_select,
                         CP_TERM(env, descP->connector.ref));
            }
        }
        break;

    default:
        SSDBG( descP,
               ("SOCKET", "esock_connect {%d} -> error: %d\r\n",
                descP->sock, save_errno) );

        return esock_make_error_errno(env, save_errno);

    } // switch(save_errno)
}
#endif // if !defined(__WIN32__)



/* *** verify_is_connected ***
 * Check if a connection has been established.
 */
#if !defined(__WIN32__)
static
BOOLEAN_T verify_is_connected(ESockDescriptor* 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", "The Sockets Networking API",
     * W.R.Stevens, Volume 1, third edition, 16.4 Nonblocking 'connect',
     * before Interrupted 'connect' (p 412) for a discussion about
     * Unix portability and non blocking connect.
     */

    int error = 0;

#ifdef SO_ERROR
    SOCKLEN_T sz = sizeof(error);

    if (IS_SOCKET_ERROR(sock_getopt(descP->sock, SOL_SOCKET, SO_ERROR,
                                    (void *)&error, &sz))) {
        // Solaris does it this way according to W.R.Stevens
        error = sock_errno();
    }
#elif 1
    char buf[0];
    if (IS_SOCKET_ERROR(read(descP->sock, buf, 0))) {
        error = sock_errno();
    }
#else
    /* This variant probably returns wrong error value
     * ENOTCONN instead of the actual connect error
     */
    ESockAddress remote;
    socklen_t    addrLen = sizeof(remote);
    sys_memzero((char *) &remote, addrLen);
    if (IS_SOCKET_ERROR(sock_peer(descP->sock,
                                  (struct sockaddr*) &remote, &addrLen))) {
        error = sock_errno();
    }
#endif

    if (error != 0) {
        *err = error;
        return FALSE;
    }
    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, "notsup"));
#else
    ESockDescriptor* descP;
    int              backlog;
    ERL_NIF_TERM     ret;

    SGDBG( ("SOCKET", "nif_listen -> entry with argc: %d\r\n", argc) );
    
    /* Extract arguments and perform preliminary validation */

    if ((argc != 2) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP) ||
        !GET_INT(env, argv[1], &backlog)) {
        return enif_make_badarg(env);
    }

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_listen(%T), {%d,%s,0x%X} ->"
            "\r\n   backlog: %d"
            "\r\n",
            argv[0], descP->sock, B2S(descP->closing), descP->readState,
            backlog) );

    ret = esock_listen(env, descP, backlog);

    SSDBG( descP, ("SOCKET", "nif_listen(%T) -> done with"
                   "\r\n   ret: %T"
                   "\r\n", argv[0], ret) );

    MUNLOCK(descP->readMtx);

    return ret;
#endif // if defined(__WIN32__)
}



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_listen(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          int              backlog)
{
    
    /* 
     * Verify that we are in the proper state
     */

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* 
     * And attempt to make socket listening
     */
    
    if (IS_SOCKET_ERROR(sock_listen(descP->sock, backlog)))
        return esock_make_error_errno(env, sock_errno());

    descP->readState |= ESOCK_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, "notsup"));

#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     sockRef, ref, res;

    SGDBG( ("SOCKET", "nif_accept -> entry with argc: %d\r\n", argc) );
    
    /* Extract arguments and perform preliminary validation */

    sockRef = argv[0];
    if ((argc != 2) ||
        !ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        return enif_make_badarg(env); 
    }
    ref = argv[1];
    
    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_accept%T), {%d,%s,0x%X} ->"
            "\r\n   ReqRef:                %T"
            "\r\n   Current Acceptor Addr: %p"
            "\r\n   Current Acceptor pid:  %T"
            "\r\n   Current Acceptor mon:  %T"
            "\r\n   Current Acceptor env:  0x%lX"
            "\r\n   Current Acceptor ref:  %T"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->readState,
            ref, 
            descP->currentAcceptorP,
            descP->currentAcceptor.pid,
            esock_make_monitor_term(env, &descP->currentAcceptor.mon),
            descP->currentAcceptor.env,
            descP->currentAcceptor.ref) );

    res = esock_accept(env, descP, sockRef, ref);

    SSDBG( descP, ("SOCKET", "nif_accept(%T) -> done with"
                   "\r\n   res: %T"
                   "\r\n", sockRef, res) );

    MUNLOCK(descP->readMtx);

    return res;

#endif // if defined(__WIN32__)
}

#if !defined(__WIN32__)

static
ERL_NIF_TERM esock_accept(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          ERL_NIF_TERM     sockRef,
                          ERL_NIF_TERM     accRef)
{
    ErlNifPid     caller;

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Accept and Read uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->currentReaderP != NULL)
        return esock_make_error(env, esock_atom_einval);

    if (enif_self(env, &caller) == NULL)
        return esock_make_error(env, atom_exself);

    if (descP->currentAcceptorP == NULL) {
        SOCKET        accSock;

        /* We have no active acceptor (and therefor no acceptors in queue)
         */

        SSDBG( descP, ("SOCKET", "esock_accept {%d} -> try accept\r\n",
                       descP->sock) );

        accSock = sock_accept(descP->sock, NULL, NULL);

        if (IS_SOCKET_ERROR(accSock)) {
            int           save_errno;

            save_errno = sock_errno();

            return esock_accept_listening_error(env, descP, sockRef,
                                                accRef, caller, save_errno);
        } else {
            /* We got an incoming connection */
            return
                esock_accept_listening_accept(env, descP, sockRef,
                                              accSock, caller);
        }
    } else {

        /* 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 (acceptor) queue.
         */

        SSDBG( descP, ("SOCKET", "esock_accept_accepting -> check: "
                       "is caller current acceptor:"
                       "\r\n   Caller:  %T"
                       "\r\n   Current: %T"
                       "\r\n", caller, descP->currentAcceptor.pid) );

        if (COMPARE_PIDS(&descP->currentAcceptor.pid, &caller) == 0) {

            SSDBG( descP,
                   ("SOCKET",
                    "esock_accept_accepting {%d} -> current acceptor\r\n",
                    descP->sock) );

            return esock_accept_accepting_current(env, descP, sockRef, accRef);

        } else {

            /* Not the "current acceptor", so (maybe) push onto queue */

            SSDBG( descP,
                   ("SOCKET",
                    "esock_accept_accepting {%d} -> *not* current acceptor\r\n",
                    descP->sock) );

            return esock_accept_accepting_other(env, descP, accRef, caller);

        }
    }
}

/* *** esock_accept_listening_error ***
 *
 * The accept call resultet in an error - handle it.
 * There are only two cases: 
 * 1) BLOCK => Attempt a "retry"
 * 2) Other => Return the value (converted to an atom)
 */
static
ERL_NIF_TERM esock_accept_listening_error(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     sockRef,
                                          ERL_NIF_TERM     accRef,
                                          ErlNifPid        caller,
                                          int              save_errno)
{
    ERL_NIF_TERM res;

    if (save_errno == ERRNO_BLOCK) {

        /* *** Try again later *** */

        SSDBG( descP,
               ("SOCKET",
                "esock_accept_listening_error {%d} -> would block\r\n",
                descP->sock) );

        ESOCK_CNT_INC(env, descP, sockRef, atom_acc_tries, &descP->accTries, 1);

        descP->currentAcceptor.pid = caller;
        if (MONP("esock_accept_listening -> current acceptor",
                 env, descP,
                 &descP->currentAcceptor.pid,
                 &descP->currentAcceptor.mon) != 0) {
            enif_set_pid_undefined(&descP->currentAcceptor.pid);
            res = esock_make_error(env, atom_exmonitor);
        } else {
            ESOCK_ASSERT(!descP->currentAcceptor.env);
            descP->currentAcceptor.env = esock_alloc_env("current acceptor");
            descP->currentAcceptor.ref = CP_TERM(descP->currentAcceptor.env,
                                                 accRef);
            descP->currentAcceptorP    = &descP->currentAcceptor;
            res = esock_accept_busy_retry(env, descP, sockRef, accRef, NULL);
        }
    } else {
        SSDBG( descP,
               ("SOCKET",
                "esock_accept_listening {%d} -> errno: %d\r\n",
                descP->sock, save_errno) );

        ESOCK_CNT_INC(env, descP, sockRef, atom_acc_fails, &descP->accFails, 1);

        res = esock_make_error_errno(env, save_errno);
    }

    return res;
}


/* *** esock_accept_listening_accept ***
 *
 * The accept call was successful (accepted) - handle the new connection.
 */
static
ERL_NIF_TERM esock_accept_listening_accept(ErlNifEnv*       env,
                                           ESockDescriptor* descP,
                                           ERL_NIF_TERM     sockRef,
                                           SOCKET           accSock,
                                           ErlNifPid        caller)
{
    ERL_NIF_TERM res;

    esock_accept_accepted(env, descP, sockRef, accSock, caller, &res);
    
    return res;
}


/* *** esock_accept_accepting_current ***
 * Handles when the current acceptor makes another attempt.
 */
static
ERL_NIF_TERM esock_accept_accepting_current(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     sockRef,
                                            ERL_NIF_TERM     accRef)
{
    SOCKET        accSock;
    int           save_errno;
    ERL_NIF_TERM  res;

    SSDBG( descP,
           ("SOCKET",
            "esock_accept_accepting_current {%d} -> try accept\r\n",
            descP->sock) );

    accSock = sock_accept(descP->sock, NULL, NULL);

    if (IS_SOCKET_ERROR(accSock)) {

        save_errno = sock_errno();

        res = esock_accept_accepting_current_error(env, descP, sockRef,
                                                   accRef, save_errno);
    } else {

        res = esock_accept_accepting_current_accept(env, descP, sockRef,
                                                    accSock);
    }

    return res;
}


/* *** esock_accept_accepting_current_accept ***
 * Handles when the current acceptor succeeded in its accept call - 
 * handle the new connection.
 */
static
ERL_NIF_TERM esock_accept_accepting_current_accept(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     sockRef,
                                                   SOCKET           accSock)
{
    ERL_NIF_TERM res;

    SSDBG( descP,
           ("SOCKET",
            "esock_accept_accepting_current_accept {%d}"
            "\r\n", descP->sock) );

    if (esock_accept_accepted(env, descP, sockRef, accSock,
                              descP->currentAcceptor.pid, &res)) {

        if (!activate_next_acceptor(env, descP, sockRef)) {

            SSDBG( descP,
                   ("SOCKET",
                    "esock_accept_accepting_current_accept {%d} ->"
                    " no more acceptors"
                    "\r\n", descP->sock) );

            descP->readState &= ~ESOCK_STATE_ACCEPTING;

            descP->currentAcceptorP    = NULL;
        }

    }

    return res;
}


/* *** esock_accept_accepting_current_error ***
 * The accept call of current acceptor resultet in an error - handle it.
 * There are only two cases: 
 * 1) BLOCK => Attempt a "retry"
 * 2) Other => Return the value (converted to an atom)
 */
static
ERL_NIF_TERM esock_accept_accepting_current_error(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     sockRef,
                                                  ERL_NIF_TERM     opRef,
                                                  int              save_errno)
{
    ERL_NIF_TERM   res, reason;

    if (save_errno == ERRNO_BLOCK) {

        /*
         * Just try again, no real error, just a ghost trigger from poll,
         */

        SSDBG( descP,
               ("SOCKET",
                "esock_accept_accepting_current_error {%d} -> "
                "would block: try again\r\n", descP->sock) );

        ESOCK_CNT_INC(env, descP, sockRef, atom_acc_waits, &descP->accWaits, 1);

        res = esock_accept_busy_retry(env, descP, sockRef, opRef,
                                      &descP->currentAcceptor.pid);

    } else {
        ESockRequestor req;

        SSDBG( descP,
               ("SOCKET",
                "esock_accept_accepting_current_error {%d} -> "
                "error: %d\r\n", descP->sock, save_errno) );

        ESOCK_CNT_INC(env, descP, sockRef, atom_acc_fails, &descP->accFails, 1);

        requestor_release("esock_accept_accepting_current_error",
                          env, descP, descP->currentAcceptorP);

        reason = MKA(env, erl_errno_id(save_errno));
        res    = esock_make_error(env, reason);

        req.env = NULL;
        while (acceptor_pop(env, descP, &req)) {
            SSDBG( descP,
                   ("SOCKET",
                    "esock_accept_accepting_current_error {%d} -> abort %T\r\n",
                    descP->sock, req.pid) );
            esock_send_abort_msg(env, sockRef, req.ref, req.env,
                                 reason, &req.pid);
            req.env = NULL;
            DEMONP("esock_accept_accepting_current_error -> pop'ed writer",
                   env, descP, &req.mon);
        }
        descP->currentAcceptorP    = NULL;
    }

    return res;
}


/* *** esock_accept_accepting_other ***
 * Handles when the another acceptor makes an attempt, which
 * results (maybe) in the request beeing pushed onto the 
 * acceptor queue.
 */
static
ERL_NIF_TERM esock_accept_accepting_other(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     ref,
                                          ErlNifPid        caller)
{
    ERL_NIF_TERM  result;

    if (!acceptor_search4pid(env, descP, &caller)) // Ugh! (&caller)
        result = acceptor_push(env, descP, caller, ref);
    else
        result = esock_make_error(env, esock_atom_eagain);
    
    return result;
}



/* *** esock_accept_busy_retry ***
 *
 * Perform a retry select. If successful, set nextState.
 */
static
ERL_NIF_TERM esock_accept_busy_retry(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     ERL_NIF_TERM     sockRef,
                                     ERL_NIF_TERM     accRef,
                                     ErlNifPid*       pid)
{
    int          sres;
    ERL_NIF_TERM res;

    if ((sres = esock_select_read(env, descP->sock, descP, pid,
                                  sockRef, accRef)) < 0) {

        DEMONP("esock_accept_busy_retry - select failed",
               env, descP, &descP->currentAcceptor.mon);
        /* It is very unlikely that a next acceptor will be able
         * to do anything succesful, but we will clean the queue
         */
        if (!activate_next_acceptor(env, descP, sockRef)) {
            SSDBG( descP,
                   ("SOCKET",
                    "esock_accept_busy_retry {%d} -> no more acceptors\r\n",
                    descP->sock) );

            descP->readState &= ~ESOCK_STATE_ACCEPTING;

            descP->currentAcceptorP    = NULL;
        }

        res = esock_make_error(env, atom_exselect);
    } else {
        descP->readState |= ESOCK_STATE_ACCEPTING;
        res = esock_make_error(env, esock_atom_eagain); // OK!!
    }

    return res;
}



/* *** esock_accept_accepted ***
 *
 * Generic function handling a successful accept.
 */
static
BOOLEAN_T esock_accept_accepted(ErlNifEnv*       env,
                                ESockDescriptor* descP,
                                ERL_NIF_TERM     sockRef,
                                SOCKET           accSock,
                                ErlNifPid        pid,
                                ERL_NIF_TERM*    result)
{
    ESockDescriptor* accDescP;
    HANDLE           accEvent;
    ERL_NIF_TERM     accRef;
    int              save_errno;

    /*
     * We got one
     */

    ESOCK_CNT_INC(env, descP, sockRef, atom_acc_success, &descP->accSuccess, 1);

    if ((accEvent = sock_create_event(accSock)) == INVALID_EVENT) {
        save_errno = sock_errno();
        while (IS_SOCKET_ERROR(sock_close(accSock)) &&
               (sock_errno() == EINTR));
        *result = esock_make_error_errno(env, save_errno);
        return FALSE;
    }

    if ((accDescP = alloc_descriptor(accSock, accEvent)) == NULL) {
        sock_close(accSock);
        *result = enif_make_badarg(env);
        return FALSE;
    }

    accDescP->domain   = descP->domain;
    accDescP->type     = descP->type;
    accDescP->protocol = descP->protocol;

    MLOCK(descP->writeMtx);

    accDescP->rBufSz   = descP->rBufSz;  // Inherit buffer size
    accDescP->rNum     = descP->rNum;    // Inherit buffer uses
    accDescP->rNumCnt  = 0;
    accDescP->rCtrlSz  = descP->rCtrlSz; // Inherit buffer size
    accDescP->wCtrlSz  = descP->wCtrlSz; // Inherit buffer size
    accDescP->iow      = descP->iow;     // Inherit iow
    accDescP->dbg      = descP->dbg;     // Inherit debug flag
    accDescP->useReg   = descP->useReg;  // Inherit useReg flag

    accRef = enif_make_resource(env, accDescP);
    enif_release_resource(accDescP);

    accDescP->ctrlPid = pid;
    if (MONP("esock_accept_accepted -> ctrl",
             env, accDescP,
             &accDescP->ctrlPid,
             &accDescP->ctrlMon) != 0) {
        sock_close(accSock);
        enif_set_pid_undefined(&descP->ctrlPid);
        MUNLOCK(descP->writeMtx);
        *result = esock_make_error(env, atom_exmonitor);
        return FALSE;
    }

    SET_NONBLOCKING(accDescP->sock);

    descP->writeState |= ESOCK_STATE_CONNECTED;

    MUNLOCK(descP->writeMtx);

    /* And finally (maybe) update the registry */
    if (descP->useReg) esock_send_reg_add_msg(env, accRef);

    *result = esock_make_ok2(env, accRef);

    return TRUE;
}

#endif // if !defined(__WIN32__) /* for accept */



/* ----------------------------------------------------------------------
 * 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, "notsup"));
#else
    ESockDescriptor* 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)) {
        SGDBG( ("SOCKET", "nif_send -> argv decode failed\r\n") );
        return enif_make_badarg(env);
    }
    sockRef = argv[0]; // We need this in case we send in case we send abort
    sendRef = argv[1];

    if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        SSDBG( descP, ("SOCKET", "nif_send -> get resource failed\r\n") );
        return enif_make_badarg(env);
    }

    if (!esendflags2sendflags(eflags, &flags)) {
        SSDBG( descP, ("SOCKET", "nif_send -> sendflags decode failed\r\n") );
        return esock_make_error(env, esock_atom_einval);
    }

    MLOCK(descP->writeMtx);

    SSDBG( descP,
           ("SOCKET", "nif_send(%T), {%d,%s,0x%X} ->"
            "\r\n   SendRef:   %T"
            "\r\n   Data size: %u"
            "\r\n   flags:     0x%X"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->writeState,
            sendRef, sndData.size, flags) );

    /* 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!
     */

    res = esock_send(env, descP, sockRef, sendRef, &sndData, flags);

    SSDBG( descP, ("SOCKET", "nif_send(%T) -> done with"
                   "\r\n   res: %T"
                   "\r\n", sockRef, res) );

    MUNLOCK(descP->writeMtx);

    SGDBG( ("SOCKET", "nif_send -> done with result: "
            "\r\n   %T"
            "\r\n", res) );
    return res;

#endif // if defined(__WIN32__)
}



/* *** esock_send ***
 *
 * 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 esock_send(ErlNifEnv*       env,
                        ESockDescriptor* descP,
                        ERL_NIF_TERM     sockRef,
                        ERL_NIF_TERM     sendRef,
                        ErlNifBinary*    sndDataP,
                        int              flags)
{
    int          save_errno;
    ssize_t      written;
    ERL_NIF_TERM writerCheck;

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Connect and Write uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->connectorP != NULL)
        return esock_make_error(env, esock_atom_einval);

    /* Ensure that we either have no current writer or we are it,
     * or enqueue this process if there is a current writer  */
    if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
        SSDBG( descP, ("SOCKET", "esock_send {%d} -> writer check failed: "
                       "\r\n   %T\r\n", descP->sock, writerCheck) );
        return writerCheck;
    }
    
    /* We ignore the wrap for the moment.
     * Maybe we should issue a wrap-message to controlling process...
     */
    ESOCK_CNT_INC(env, descP, sockRef, atom_write_tries, &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 = 0; // 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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     sockRef, sendRef;
    ErlNifBinary     sndData;
    unsigned int     eflags;
    int              flags;
    ERL_NIF_TERM     eSockAddr;
    ESockAddress     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) ||
        !GET_BIN(env, argv[2], &sndData) ||
        !GET_UINT(env, argv[4], &eflags)) {
        SGDBG( ("SOCKET", "nif_sendto -> argv decode failed\r\n") );
        return enif_make_badarg(env);
    }
    sockRef   = argv[0]; // We need this in case we send abort (to the caller)
    sendRef   = argv[1];
    eSockAddr = argv[3];

    if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        SSDBG( descP, ("SOCKET", "nif_sendto -> get resource failed\r\n") );
        return enif_make_badarg(env);
    }

    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);

    SSDBG( descP,
           ("SOCKET", "nif_sendto(%T), {%d,%s,0x%X} ->"
            "\r\n   sendRef:   %T"
            "\r\n   Data size: %u"
            "\r\n   eSockAddr: %T"
            "\r\n   flags:     0x%X"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->readState,
            sendRef, sndData.size, eSockAddr, flags) );

    res = esock_sendto(env, descP, sockRef, sendRef, &sndData, flags,
                       &remoteAddr, remoteAddrLen);

    SSDBG( descP, ("SOCKET", "nif_sendto(%T) -> done with"
                   "\r\n   res: %T"
                   "\r\n", sockRef, res) );

    MUNLOCK(descP->writeMtx);

    return res;

#endif // if defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_sendto(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          ERL_NIF_TERM     sockRef,
                          ERL_NIF_TERM     sendRef,
                          ErlNifBinary*    dataP,
                          int              flags,
                          ESockAddress*    toAddrP,
                          unsigned int     toAddrLen)
{
    int          save_errno;
    ssize_t      written;
    ERL_NIF_TERM writerCheck;

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Connect and Write uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->connectorP != NULL)
        return esock_make_error(env, esock_atom_einval);

    /* Ensure that we either have no current writer or we are it,
     * or enqueue this process if there is a current writer  */
    if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
        SSDBG( descP, ("SOCKET", "esock_sendto {%d} -> writer check failed: "
                       "\r\n   %T\r\n", descP->sock, writerCheck) );
        return writerCheck;
    }
    
    /* We ignore the wrap for the moment.
     * Maybe we should issue a wrap-message to controlling process...
     */
    ESOCK_CNT_INC(env, descP, sockRef, atom_write_tries, &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 = 0; // 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, "notsup"));
#else
    ERL_NIF_TERM     res, sockRef, sendRef, eMsgHdr;
    ESockDescriptor* 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)) {
        SGDBG( ("SOCKET", "nif_sendmsg -> argv decode failed\r\n") );
        return enif_make_badarg(env);
    }
    sockRef = argv[0]; // We need this in case we send abort (to the caller)
    sendRef = argv[1];
    eMsgHdr = argv[2];

    if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        SSDBG( descP, ("SOCKET", "nif_sendmsg -> get resource failed\r\n") );
        return enif_make_badarg(env);
    }
    
    if (!esendflags2sendflags(eflags, &flags)) {
        SSDBG( descP, ("SOCKET", "nif_sendmsg -> sendflags decode failed\r\n") );
        return esock_make_error(env, esock_atom_einval);
    }

    MLOCK(descP->writeMtx);

    SSDBG( descP,
           ("SOCKET", "nif_sendmsg(%T), {%d,%s,0x%X} ->"
            "\r\n   SendRef:   %T"
            "\r\n   flags:     0x%X"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->writeState,
            sendRef, flags) );

    res = esock_sendmsg(env, descP, sockRef, sendRef, eMsgHdr, flags);

    MUNLOCK(descP->writeMtx);

    SSDBG( descP, ("SOCKET", "nif_sendmsg(%T) -> done with"
                   "\r\n   res: %T"
                   "\r\n", sockRef, res) );

    return res;

#endif // if defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_sendmsg(ErlNifEnv*       env,
                           ESockDescriptor* descP,
                           ERL_NIF_TERM     sockRef,
                           ERL_NIF_TERM     sendRef,
                           ERL_NIF_TERM     eMsgHdr,
                           int              flags)
{
    ERL_NIF_TERM  res, eAddr, eIOV, eCtrl;
    ESockAddress  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 (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Connect and Write uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->connectorP != NULL)
        return esock_make_error(env, esock_atom_einval);

    /* Ensure that we either have no current writer or we are it,
     * or enqueue this process if there is a current writer  */
    if (! send_check_writer(env, descP, sendRef, &writerCheck)) {
        SSDBG( descP,
               ("SOCKET", "esock_sendmsg {%d} -> writer check failed: "
                "\r\n   %T\r\n", descP->sock, writerCheck) );
      return writerCheck;
    }
    
    /* Initiate the .name and .namelen fields depending on if
     * we have an address or not
     */
    if (! GET_MAP_VAL(env, eMsgHdr, esock_atom_addr, &eAddr)) {

        SSDBG( descP, ("SOCKET",
                       "esock_sendmsg {%d} -> no address\r\n", descP->sock) );

        msgHdr.msg_name    = NULL;
        msgHdr.msg_namelen = 0;
    } else {
        msgHdr.msg_name    = (void*) &addr;
        msgHdr.msg_namelen = sizeof(addr);
        sys_memzero((char *) msgHdr.msg_name, msgHdr.msg_namelen);

        SSDBG( descP, ("SOCKET", "esock_sendmsg {%d} ->"
                       "\r\n   address: %T"
                       "\r\n", descP->sock, 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",
            "esock_sendmsg {%d} -> iov length: %d\r\n", descP->sock, iovLen) );

    iovBins = MALLOC(iovLen * sizeof(ErlNifBinary));
    ESOCK_ASSERT( (iovBins != NULL) );

    iov     = MALLOC(iovLen * sizeof(struct iovec));
    ESOCK_ASSERT( (iov != NULL) );

    /* The *optional* 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", "esock_sendmsg {%d} -> optional ctrl: "
                   "\r\n   ctrlBuf:    0x%lX"
                   "\r\n   ctrlBufLen: %d"
                   "\r\n   eCtrl:      %T"
                   "\r\n", descP->sock, 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",
                   "esock_sendmsg {%d} -> "
                   "total (iov) data size: %d\r\n", descP->sock, 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...
     */
    ESOCK_CNT_INC(env, descP, sockRef, atom_write_tries, &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 = 0; // 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 FOOBAR
static
ERL_NIF_TERM nwritev(ErlNifEnv*       env,
                     ESockDescriptor* 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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     sockRef, recvRef;
    ErlNifUInt64     elen;
    unsigned int     eflags;
    ssize_t          len; /* ssize_t due to the return type of recv() */
    int              flags;
    ERL_NIF_TERM     res;

    if ((argc != 4) ||
        !GET_UINT64(env, argv[2], &elen) ||
        !GET_UINT(env, argv[3], &eflags)) {
        return enif_make_badarg(env);
    }
    len = (ssize_t) elen;
    if (elen != (ErlNifUInt64)len) return enif_make_badarg(env);

    sockRef = argv[0]; // We need this in case we send abort (to the caller)
    recvRef = argv[1];

    if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        return enif_make_badarg(env);
    }
    
    if (!erecvflags2recvflags(eflags, &flags))
        return esock_make_error(env, esock_atom_einval);

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_recv(%T), {%d,%s,0x%X} ->"
            "\r\n   recvRef: %T"
            "\r\n   len:     %ld"
            "\r\n   flags:   0x%X"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->readState,
            recvRef, (long) len, flags) );

    /* 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!
     */

    res = esock_recv(env, descP, sockRef, recvRef, (size_t)len, flags);

    SSDBG( descP, ("SOCKET", "nif_recv(%T) -> done"
                   "\r\n", sockRef) );

    MUNLOCK(descP->readMtx);

    return res;

#endif // if defined(__WIN32__)
}


/* The (read) buffer handling should 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 esock_recv(ErlNifEnv*       env,
                        ESockDescriptor* descP,
                        ERL_NIF_TERM     sockRef,
                        ERL_NIF_TERM     recvRef,
                        size_t           len,
                        int              flags)
{
    ssize_t      read;
    ErlNifBinary buf;
    ERL_NIF_TERM readerCheck;
    int          save_errno;
    size_t       bufSz = (len ? len : descP->rBufSz);

    SSDBG( descP, ("SOCKET", "esock_recv {%d} -> entry with"
                   "\r\n   count,size: (%u:lu)"
                   "\r\n", descP->sock, (unsigned long) len,
                   descP->rNumCnt, (unsigned long) bufSz) );

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Accept and Read uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->currentAcceptorP != NULL)
        return esock_make_error(env, esock_atom_einval);

    /* Ensure that we either have no current reader or that we are it,
     * or enqueue this process if there is a current reader */
    if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
        SSDBG( descP,
               ("SOCKET", "esock_recv {%d} -> reader check failed: "
                "\r\n   %T\r\n", descP->sock, 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);

    ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &descP->readTries, 1);

    // If it fails (read = -1), we need errno...
    SSDBG( descP, ("SOCKET", "esock_recv {%d} -> try read (%lu)\r\n",
                   descP->sock, (unsigned long) buf.size) );

    read = sock_recv(descP->sock, buf.data, buf.size, flags);
    if (IS_SOCKET_ERROR(read)) {
        save_errno = sock_errno();
    } else {
        save_errno = 0; // The value does not actually matter in this case
    }
    
    SSDBG( descP, ("SOCKET",
                   "esock_recv {%d} -> read: %ld (%d)\r\n",
                   descP->sock, (long) 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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     sockRef, recvRef;
    unsigned int     len;
    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], &len) ||
        !GET_UINT(env, argv[3], &eflags)) {
        return enif_make_badarg(env);
    }
    sockRef = argv[0]; // We need this in case we send abort (to the caller)
    recvRef = argv[1];

    if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        return enif_make_badarg(env);
    }

    if (!erecvflags2recvflags(eflags, &flags)) {
        SSDBG( descP, ("SOCKET", "nif_recvfrom -> recvflags decode failed\r\n") );
        return esock_make_error(env, esock_atom_einval);
    }

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_recvfrom(%T), {%d,%s,0x%X} ->"
            "\r\n   recvRef: %T"
            "\r\n   len:     %u"
            "\r\n   flags:   0x%X"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->readState,
            recvRef, len, flags) );

    /* <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 = esock_recvfrom(env, descP, sockRef, recvRef, len, flags);

    SSDBG( descP, ("SOCKET", "nif_recvfrom(%T) -> done"
                   "\r\n", sockRef) );

    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 esock_recvfrom(ErlNifEnv*       env,
                            ESockDescriptor* descP,
                            ERL_NIF_TERM     sockRef,
                            ERL_NIF_TERM     recvRef,
                            Uint16           len,
                            int              flags)
{
    ESockAddress  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", "esock_recvfrom {%d} -> entry with"
                   "\r\n   bufSz: %d"
                   "\r\n", descP->sock, bufSz) );

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Accept and Read uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->currentAcceptorP != NULL)
        return esock_make_error(env, esock_atom_einval);

    /* Ensure that we either have no current reader or that we are it,
     * or enqueue this process if there is a current reader */
    if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
        SSDBG( descP,
               ("SOCKET", "esock_recv {%d} -> reader check failed: "
                "\r\n   %T\r\n", descP->sock, 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);

    ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &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 = 0; // 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, "notsup"));
#else
    ESockDescriptor* 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 abort (to the caller)
    recvRef = argv[1];

    if (!ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        return enif_make_badarg(env);
    }

    if (!erecvflags2recvflags(eflags, &flags))
        return esock_make_error(env, esock_atom_einval);

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_recvmsg(%T), {%d,%s,0x%X} ->"
            "\r\n   recvRef: %T"
            "\r\n   bufSz:   %u"
            "\r\n   ctrlSz:  %u"
            "\r\n   flags:   0x%X"
            "\r\n",
            sockRef, descP->sock, B2S(descP->closing), descP->readState,
            recvRef, bufSz, ctrlSz, flags) );

    /* <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 = esock_recvmsg(env, descP, sockRef, recvRef, bufSz, ctrlSz, flags);

    SSDBG( descP, ("SOCKET", "nif_recvmsg(%T) -> done"
                   "\r\n", sockRef) );

    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 esock_recvmsg(ErlNifEnv*       env,
                           ESockDescriptor* 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;
    ESockAddress  addr;

    SSDBG( descP, ("SOCKET", "esock_recvmsg {%d} -> entry with"
                   "\r\n   bufSz:  %d (%d)"
                   "\r\n   ctrlSz: %d (%d)"
                   "\r\n", descP->sock, bufSz, bufLen, ctrlSz, ctrlLen) );

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);

    /* Accept and Read uses the same select flag
     * so they can not be simultaneous
     */
    if (descP->currentAcceptorP != NULL)
        return esock_make_error(env, esock_atom_einval);

    /* Ensure that we either have no current reader or that we are it,
     * or enqueue this process if there is a current reader */
    if (! recv_check_reader(env, descP, recvRef, &readerCheck)) {
        SSDBG( descP,
               ("SOCKET", "esock_recv {%d} -> reader check failed: "
                "\r\n   %T\r\n", descP->sock, 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);

    ESOCK_CNT_INC(env, descP, sockRef, atom_read_tries, &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 = 0; // 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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM res;

    SGDBG( ("SOCKET", "nif_close -> entry with argc: %d\r\n", argc) );

    if ((argc != 1) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
        return enif_make_badarg(env);
    }

    MLOCK(descP->readMtx);
    MLOCK(descP->writeMtx);

    SSDBG( descP,
           ("SOCKET", "nif_close(%T), {%d,%s,0x%X}\r\n",
            argv[0], descP->sock, B2S(descP->closing), descP->readState) );

    res = esock_close(env, descP);

    MUNLOCK(descP->writeMtx);
    MUNLOCK(descP->readMtx);

    SSDBG( descP, ("SOCKET", "nif_close(%T) -> done"
                   "\r\n   res: %T"
                   "\r\n", argv[0], res) );

    return res;
#endif // if defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_close(ErlNifEnv*       env,
                         ESockDescriptor* descP)
{
    int          sres;

    if (! IS_OPEN(descP))
        /* A bit of cheeting; maybe not closed yet - do we need a queue? */
        return esock_make_error(env, atom_closed);

    /* 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)
        return esock_make_error(env, atom_exself);

    /* If the caller is not the owner; monitor the caller,
     * since we should complete this operation even if the caller dies
     * (for whatever reason).
     */
    if (COMPARE_PIDS(&descP->closerPid, &descP->ctrlPid) != 0) {

        if (MONP("esock_close_check -> closer",
                 env, descP,
                 &descP->closerPid,
                 &descP->closerMon) != 0) {

            enif_set_pid_undefined(&descP->closerPid);
            return esock_make_error(env, atom_exmonitor);

        }
    }

    /* Create closeRef */
    descP->closeEnv = esock_alloc_env("esock_close_do - close-env");
    descP->closeRef = MKREF(descP->closeEnv);
    descP->closing  = TRUE;

    /* Call or schedule call to esock_stop() */
    sres = esock_do_stop(env, descP);

    if (sres < 0) { // Error
        /* Calling esock_select_stop failed in some mysterious way,
         * we are kind of toasted - we'll leave the socket leaked
         * with descP->closing == TRUE
         */

        esock_warning_msg("Failed select stop when closing socket: "
                          "\r\n   Select Res:          %d"
                          "\r\n   Controlling Process: %T"
                          "\r\n   Descriptor:          %d"
                          "\r\n   Closer:              %T"
                          "\r\n", sres,
                          descP->ctrlPid, descP->sock,
                          descP->closerPid);

        /* <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?
         *
         * XXX Maybe send a concerned message to the registry?
         *
         * </KOLLA>
         */

        enif_set_pid_undefined(&descP->closerPid);
        DEMONP("esock_close_do -> closer", env, descP, &descP->closerMon);
        esock_free_env("esock_close_do - close-env", descP->closeEnv);
        descP->closeEnv = NULL;
        descP->closeRef = esock_atom_undefined;
        return esock_make_error(env, atom_exselect);

    }

    if (sres & ERL_NIF_SELECT_STOP_CALLED) {

        /* Prep done - inform the caller it can finalize (close) directly */
        SSDBG( descP,
               ("SOCKET",
                "esock_close {%d} -> stop was called\r\n",
                descP->sock) );

        return esock_atom_ok;
    }

    // if (sres & 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", "esock_close {%d} -> stop was scheduled\r\n",
            descP->sock) );

    return esock_make_ok2(env, CP_TERM(env, descP->closeRef));

}


static
int esock_do_stop(ErlNifEnv* env,
                  ESockDescriptor* descP) {
    int sres;
    ERL_NIF_TERM sockRef;

    sockRef = enif_make_resource(env, descP);
    sres = esock_select_stop(env, descP->sock, descP);

    if (sres < 0) { // Error

        SSDBG( descP,
               ("SOCKET", "esock_do_stop {%d} -> stop failed: %d\r\n",
                descP->sock, sres) );

        return sres;
    }

    /* +++++++ Current and waiting Writers +++++++ */

    if (descP->currentWriterP != NULL) {

        /* We have a current Writer; was it deselected?
         */

        if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {

            /* The current Writer will not get a select message
             * - send it an abort message
             */

            esock_stop_handle_current(env,
                                      "writer",
                                      descP, sockRef, descP->currentWriterP);
        }

        /* Inform the waiting Writers (in the same way) */

        SSDBG( descP,
               ("SOCKET",
                "esock_do_stop {%d} -> handle waiting writer(s)\r\n",
                descP->sock) );

        inform_waiting_procs(env, "writer",
                             descP, sockRef, &descP->writersQ, atom_closed);

        descP->currentWriterP = NULL;
    }

    /* +++++++ Connector +++++++
     * Note that there should not be Writers and a Connector
     * at the same time so the check for if the
     * current Writer/Connecter was deselected is only correct
     * under that assumption
     */

    if (descP->connectorP != NULL) {

        /* We have a Connector; was it deselected?
         */

        if (sres & ERL_NIF_SELECT_WRITE_CANCELLED) {

            /* The Connector will not get a select message
             * - send it an abort message
             */

            esock_stop_handle_current(env,
                                      "connector",
                                      descP, sockRef, descP->connectorP);
        }

        descP->connectorP = NULL;
    }

    /* +++++++ Current and waiting Readers +++++++ */

    if (descP->currentReaderP != NULL) {

        /* We have a current Reader; was it deselected?
         */

        if (sres & ERL_NIF_SELECT_READ_CANCELLED) {

            /* The current Reader will not get a select message
             * - send it an abort message
             */

            esock_stop_handle_current(env,
                                      "reader",
                                      descP, sockRef, descP->currentReaderP);
        }

        /* Inform the Readers (in the same way) */

        SSDBG( descP,
               ("SOCKET",
                "esock_do_stop {%d} -> handle waiting reader(s)\r\n",
                descP->sock) );

        inform_waiting_procs(env, "writer",
                             descP, sockRef, &descP->readersQ, atom_closed);

        descP->currentReaderP = NULL;
    }

    /* +++++++ Current and waiting Acceptors +++++++
     *
     * Note that there should not be Readers and Acceptors
     * at the same time so the check for if the
     * current Reader/Acceptor was deselected is only correct
     * under that assumption
     */

    if (descP->currentAcceptorP != NULL) {

        /* We have a current Acceptor; was it deselected?
         */

        if (sres & ERL_NIF_SELECT_READ_CANCELLED) {

            /* The current Acceptor will not get a select message
             * - send it an abort message
             */

            esock_stop_handle_current(env,
                                      "acceptor",
                                      descP, sockRef, descP->currentAcceptorP);
        }

        /* Inform the waiting Acceptor (in the same way) */

        SSDBG( descP,
               ("SOCKET",
                "esock_do_stop {%d} -> handle waiting acceptors(s)\r\n",
                descP->sock) );

        inform_waiting_procs(env, "acceptor",
                             descP, sockRef, &descP->acceptorsQ, atom_closed);

        descP->currentAcceptorP = NULL;
    }

    return sres;
}
#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[])
{
    ESockDescriptor* descP;
    ERL_NIF_TERM result;
#if defined(__WIN32__)
    return enif_raise_exception(env, MKA(env, "notsup"));
#else

    /* Extract arguments and perform preliminary validation */

    if ((argc != 1) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
        return enif_make_badarg(env);
    }

    MLOCK(descP->readMtx);
    MLOCK(descP->writeMtx);

    SSDBG( descP,
           ("SOCKET", "nif_finalize_close(%T), {%d,%s,0x%X}\r\n",
            argv[0], descP->sock, B2S(descP->closing), descP->readState) );

    result = esock_finalize_close(env, descP);

    SSDBG( descP, ("SOCKET", "nif_finalize_close(%T) -> done with"
                   "\r\n   result: %T"
                   "\r\n", argv[0], result) );

    MUNLOCK(descP->writeMtx);
    MUNLOCK(descP->readMtx);

    return result;
#endif // if defined(__WIN32__)
}


/* *** esock_finalize_close ***
 * Perform the final step in the socket close.
 */
#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_finalize_close(ErlNifEnv*       env,
                                  ESockDescriptor* descP)
{
    int err;
    ErlNifPid self;

    if (IS_CLOSED(descP))
        return esock_make_error(env, atom_closed);

    if (! IS_CLOSING(descP))
        return esock_make_error_errno(env, EALREADY);

    if (enif_self(env, &self) == NULL)
        return esock_make_error(env, atom_exself);

    if (COMPARE_PIDS(&descP->closerPid, &self) != 0)
        return esock_make_error_errno(env, EALREADY);

    /* closeEnv should be NULL or else esock_stop() has not been called */
    if (descP->closeEnv != NULL)
        return esock_make_error_errno(env, EALREADY);

    /* This process is the closer - go ahead and close the socket */

    /* Stop monitoring the closer */

    enif_set_pid_undefined(&descP->closerPid);
    DEMONP("esock_finalize_close -> closer", env, descP, &descP->closerMon);

    /* Stop monitoring the owner */

    enif_set_pid_undefined(&descP->ctrlPid);
    DEMONP("esock_finalize_close -> ctrl", env, descP, &descP->ctrlMon);

    /* 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);

    err = esock_close_socket(env, descP);

    if (err != 0) {
        if (err == ERRNO_BLOCK) {
            /* Not all data in the buffers where sent,
             * make sure the caller gets this.
             */
            return esock_make_error(env, atom_timeout);
        } else {
            return esock_make_error_errno(env, err);
        }
    }

    return esock_atom_ok;
}
#endif // if !defined(__WIN32__)


static int esock_close_socket(ErlNifEnv*       env,
                              ESockDescriptor* descP) {
    int err = 0;
    ERL_NIF_TERM sockRef;

    if (descP->closeOnClose && sock_close(descP->sock) != 0)
        err = sock_errno();
    sock_close_event(descP->event);

    descP->sock    = INVALID_SOCKET;
    descP->event   = INVALID_EVENT;
    descP->closing = FALSE;
    dec_socket(descP->domain, descP->type, descP->protocol);

    /* (maybe) Update the registry */
    if (descP->useReg) {
        sockRef = enif_make_resource(env, descP);
        esock_send_reg_del_msg(env, sockRef);
    }

    return err;
}


/* ----------------------------------------------------------------------
 * 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, "notsup"));
#else
    ESockDescriptor* descP;
    unsigned int     ehow;
    int              how;
    ERL_NIF_TERM     res;

    if ((argc != 2) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP) ||
        !GET_UINT(env, argv[1], &ehow)) {
        return enif_make_badarg(env);
    }

    if (!ehow2how(ehow, &how))
        return enif_make_badarg(env);

    MLOCK(descP->readMtx);
    MLOCK(descP->writeMtx);

    SSDBG( descP,
           ("SOCKET", "nif_shutdown(%T), {%d,%s,0x%X} ->"
            "\r\n   how: %d"
            "\r\n",
            argv[0], descP->sock, B2S(descP->closing),
            descP->readState | descP->writeState,
            how) );

    res = esock_shutdown(env, descP, how);

    MUNLOCK(descP->writeMtx);
    MUNLOCK(descP->readMtx);

    SSDBG( descP, ("SOCKET", "nif_shutdown(%T) -> done with"
                   "\r\n   res: %T"
                   "\r\n", argv[0], res) );

    return res;
#endif // if defined(__WIN32__)
}



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_shutdown(ErlNifEnv*       env,
                            ESockDescriptor* descP,
                            int              how)
{
    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);
    
    if (sock_shutdown(descP->sock, how) == 0)
        return esock_atom_ok;
    else
        return esock_make_error_errno(env, sock_errno());
}
#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, "notsup"));
#else
    ESockDescriptor* descP = NULL;
    int              eLevel, level = -1, eOpt;
    ERL_NIF_TERM     eIsEncoded, eVal;
    BOOLEAN_T        isEncoded, isOTP;

    SGDBG( ("SOCKET", "nif_setopt -> entry with argc: %d\r\n", argc) );

    /* Extract arguments and perform preliminary validation */

    if ((argc != 5) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (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];

    isEncoded = esock_decode_bool(eIsEncoded);

    /* SGDBG( ("SOCKET", "nif_setopt -> eIsDecoded (%T) decoded: %d\r\n", */
    /*         eIsEncoded, isEncoded) ); */

    if (!elevel2level(isEncoded, eLevel, &isOTP, &level)) {
        SGDBG( ("SOCKET", "nif_setopt(%T) -> failed decode level\r\n",
                argv[0]) );
        return esock_make_error(env, esock_atom_einval);
    }

    MLOCK(descP->readMtx);
    return esock_setopt(env, descP, isEncoded, isOTP, level, eOpt, eVal);
    /* Surprise! MUNLOCK in called function */
    
#endif // if defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_setopt(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          BOOLEAN_T        isEncoded,
                          BOOLEAN_T        isOTP,
                          int              level,
                          int              eOpt,
                          ERL_NIF_TERM     eVal)
{
    if (! IS_OPEN(descP)) {
        MUNLOCK(descP->readMtx);
        return esock_make_error(env, atom_closed);
    }

    if (isOTP) {
        /* These are not actual socket options,
         * but options for our implementation.
         */
        return esock_setopt_otp(env, descP, eOpt, eVal);
    } else if (! isEncoded) {
        return esock_setopt_native(env, descP, level, eOpt, eVal);
    } else {
        return esock_setopt_level(env, descP, level, eOpt, eVal);
    }
}



/* esock_setopt_otp - Handle OTP (level) options
 */
static
ERL_NIF_TERM esock_setopt_otp(ErlNifEnv*       env,
                              ESockDescriptor* descP,
                              int              eOpt,
                              ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
    case ESOCK_OPT_OTP_DEBUG:
        MLOCK(descP->writeMtx);
        result = esock_setopt_otp_debug(env, descP, eVal);
        MUNLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_IOW:
        MLOCK(descP->writeMtx);
        result = esock_setopt_otp_iow(env, descP, eVal);
        MUNLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_CTRL_PROC:
        MLOCK(descP->writeMtx);
        result = esock_setopt_otp_ctrl_proc(env, descP, eVal);
        MUNLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_RCVBUF:
        result = esock_setopt_otp_rcvbuf(env, descP, eVal);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_RCVCTRLBUF:
        result = esock_setopt_otp_rcvctrlbuf(env, descP, eVal);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_SNDCTRLBUF:
        MLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        result = esock_setopt_otp_sndctrlbuf(env, descP, eVal);
        MUNLOCK(descP->writeMtx);
        break;

    case ESOCK_OPT_OTP_META:
        MLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        result = esock_setopt_otp_meta(env, descP, eVal);
        MUNLOCK(descP->writeMtx);
        break;

    default:
        MLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        SSDBG( descP,
               ("SOCKET", "esock_setopt_otp {%d} -> einval with"
                "\r\n   eOpt: %d"
                "\r\n   eVal: %T"
                "\r\n", descP->sock, eOpt, eVal) );
        MUNLOCK(descP->writeMtx);

        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_setopt_otp_debug - Handle the OTP (level) debug options
 */
static
ERL_NIF_TERM esock_setopt_otp_debug(ErlNifEnv*       env,
                                    ESockDescriptor* descP,
                                    ERL_NIF_TERM     eVal)
{
    descP->dbg = esock_decode_bool(eVal);

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_debug {%d} -> ok"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_atom_ok;
}


/* esock_setopt_otp_iow - Handle the OTP (level) iow options
 */
static
ERL_NIF_TERM esock_setopt_otp_iow(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     eVal)
{
    descP->iow = esock_decode_bool(eVal);

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_iow {%d} -> ok"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_atom_ok;
}



/* esock_setopt_otp_ctrl_proc - Handle the OTP (level) 
 * controlling_process options
 */
static
ERL_NIF_TERM esock_setopt_otp_ctrl_proc(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        ERL_NIF_TERM     eVal)
{
    ErlNifPid     caller, newCtrlPid;
    ESockMonitor  newCtrlMon;
    int           xres;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_ctrl_proc {%d} -> entry"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, 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(&descP->ctrlPid, &caller) != 0) {
        SSDBG( descP, ("SOCKET",
                       "esock_setopt_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("esock_setopt_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("esock_setopt_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", "esock_setopt_otp_ctrl_proc {%d} -> ok"
            "\r\n", descP->sock) );

    return esock_atom_ok;
}



/* esock_setopt_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 esock_setopt_otp_rcvbuf(ErlNifEnv*       env,
                                     ESockDescriptor* 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;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_rcvbuf {%d} -> entry"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    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,
                                       ESOCK_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],
                                       ESOCK_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;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_rcvbuf {%d} -> ok"
            "\r\n", descP->sock) );

    return esock_atom_ok;
}



/* esock_setopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option
 */
static
ERL_NIF_TERM esock_setopt_otp_rcvctrlbuf(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
    size_t val;
    char*  xres;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_recvctrlbuf {%d} -> entry"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    if ((xres = esock_decode_bufsz(env,
                                   eVal,
                                   ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT,
                                   &val)) != NULL)
        return esock_make_error_str(env, xres);

    descP->rCtrlSz = val;
    
    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_rcvctrlbuf {%d} -> ok"
            "\r\n", descP->sock) );

    return esock_atom_ok;
}



/* esock_setopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option
 */
static
ERL_NIF_TERM esock_setopt_otp_sndctrlbuf(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
    size_t val;
    char*  xres;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_sndvctrlbuf {%d} -> entry"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    if ((xres = esock_decode_bufsz(env,
                                   eVal,
                                   ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT,
                                   &val)) != NULL)
        return esock_make_error_str(env, xres);

    descP->wCtrlSz = val;
    
    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_sndctrlbuf {%d} -> ok"
            "\r\n", descP->sock) );

    return esock_atom_ok;
}


/* esock_setopt_otp_meta - Handle the OTP (level) meta options
 */
static
ERL_NIF_TERM esock_setopt_otp_meta(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   ERL_NIF_TERM     eVal)
{
    ErlNifPid caller;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_meta {%d} -> entry"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    if (enif_self(env, &caller) == NULL)
        return esock_make_error(env, atom_exself);

    if (COMPARE_PIDS(&descP->ctrlPid, &caller) != 0) {
        SSDBG( descP, ("SOCKET",
                       "esock_setopt_otp_meta -> not owner (%T)\r\n",
                       descP->ctrlPid) );
        return esock_make_error(env, esock_atom_not_owner);
    }

    enif_clear_env(descP->meta.env);
    descP->meta.ref = CP_TERM(descP->meta.env, eVal);

    SSDBG( descP,
           ("SOCKET", "esock_setopt_otp_meta {%d} -> ok"
            "\r\n", descP->sock) );

    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 esock_setopt_native(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 int              level,
                                 int              opt,
                                 ERL_NIF_TERM     eVal)
{
    ErlNifBinary val;
    ERL_NIF_TERM result;

    MLOCK(descP->writeMtx);
    MUNLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "esock_setopt_native {%d} -> entry"
            "\r\n   level: %d"
            "\r\n   opt:   %d"
            "\r\n   eVal: %T"
            "\r\n", descP->sock,
            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", "esock_setopt_native {%d} -> done when"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    MUNLOCK(descP->writeMtx);

    return result;
}



/* esock_setopt_level - A "proper" level (option) has been specified
 */
static
ERL_NIF_TERM esock_setopt_level(ErlNifEnv*       env,
                                ESockDescriptor* descP,
                                int              level,
                                int              eOpt,
                                ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    MLOCK(descP->writeMtx);
    MUNLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "esock_setopt_level {%d} -> entry with"
            "\r\n   level: %d"
            "\r\n   eVal:  %T"
            "\r\n", descP->sock, level, eVal) );

    switch (level) {
    case SOL_SOCKET:
        result = esock_setopt_lvl_socket(env, descP, eOpt, eVal);
        break;

#if defined(SOL_IP)
    case SOL_IP:
#else
    case IPPROTO_IP:
#endif
        result = esock_setopt_lvl_ip(env, descP, eOpt, eVal);
        break;

#if defined(HAVE_IPV6)
#if defined(SOL_IPV6)
    case SOL_IPV6:
#else
    case IPPROTO_IPV6:
#endif
        result = esock_setopt_lvl_ipv6(env, descP, eOpt, eVal);
        break;
#endif

    case IPPROTO_TCP:
        result = esock_setopt_lvl_tcp(env, descP, eOpt, eVal);
        break;

    case IPPROTO_UDP:
        result = esock_setopt_lvl_udp(env, descP, eOpt, eVal);
        break;

#if defined(HAVE_SCTP)
    case IPPROTO_SCTP:
        result = esock_setopt_lvl_sctp(env, descP, eOpt, eVal);
        break;
#endif

    default:
        SSDBG( descP,
               ("SOCKET",
                "esock_setopt_level -> unknown level (%d)\r\n", level) );
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    SSDBG( descP,
           ("SOCKET", "esock_setopt_level {%d} -> done when"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    MUNLOCK(descP->writeMtx);

    return result;
}



/* esock_setopt_lvl_socket - Level *SOCKET* option
 */
static
ERL_NIF_TERM esock_setopt_lvl_socket(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     int              eOpt,
                                     ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_socket -> entry with"
            "\r\n   opt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
#if defined(SO_BINDTODEVICE)
    case ESOCK_OPT_SOCK_BINDTODEVICE:
        result = esock_setopt_lvl_sock_bindtodevice(env, descP, eVal);
        break;
#endif

#if defined(SO_BROADCAST)
    case ESOCK_OPT_SOCK_BROADCAST:
        result = esock_setopt_lvl_sock_broadcast(env, descP, eVal);
        break;
#endif

#if defined(SO_DEBUG)
    case ESOCK_OPT_SOCK_DEBUG:
        result = esock_setopt_lvl_sock_debug(env, descP, eVal);
        break;
#endif

#if defined(SO_DONTROUTE)
    case ESOCK_OPT_SOCK_DONTROUTE:
        result = esock_setopt_lvl_sock_dontroute(env, descP, eVal);
        break;
#endif

#if defined(SO_KEEPALIVE)
    case ESOCK_OPT_SOCK_KEEPALIVE:
        result = esock_setopt_lvl_sock_keepalive(env, descP, eVal);
        break;
#endif

#if defined(SO_LINGER)
    case ESOCK_OPT_SOCK_LINGER:
        result = esock_setopt_lvl_sock_linger(env, descP, eVal);
        break;
#endif

#if defined(SO_PEEK_OFF)
    case ESOCK_OPT_SOCK_PEEK_OFF:
        result = esock_setopt_lvl_sock_peek_off(env, descP, eVal);
        break;
#endif

#if defined(SO_OOBINLINE)
    case ESOCK_OPT_SOCK_OOBINLINE:
        result = esock_setopt_lvl_sock_oobinline(env, descP, eVal);
        break;
#endif

#if defined(SO_PASSCRED)
    case ESOCK_OPT_SOCK_PASSCRED:
        result = esock_setopt_lvl_sock_passcred(env, descP, eVal);
        break;
#endif

#if defined(SO_PRIORITY)
    case ESOCK_OPT_SOCK_PRIORITY:
        result = esock_setopt_lvl_sock_priority(env, descP, eVal);
        break;
#endif

#if defined(SO_RCVBUF)
    case ESOCK_OPT_SOCK_RCVBUF:
        result = esock_setopt_lvl_sock_rcvbuf(env, descP, eVal);
        break;
#endif

#if defined(SO_RCVLOWAT)
    case ESOCK_OPT_SOCK_RCVLOWAT:
        result = esock_setopt_lvl_sock_rcvlowat(env, descP, eVal);
        break;
#endif

#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
    case ESOCK_OPT_SOCK_RCVTIMEO:
        result = esock_setopt_lvl_sock_rcvtimeo(env, descP, eVal);
        break;
#endif

#if defined(SO_REUSEADDR)
    case ESOCK_OPT_SOCK_REUSEADDR:
        result = esock_setopt_lvl_sock_reuseaddr(env, descP, eVal);
        break;
#endif

#if defined(SO_REUSEPORT)
    case ESOCK_OPT_SOCK_REUSEPORT:
        result = esock_setopt_lvl_sock_reuseport(env, descP, eVal);
        break;
#endif

#if defined(SO_SNDBUF)
    case ESOCK_OPT_SOCK_SNDBUF:
        result = esock_setopt_lvl_sock_sndbuf(env, descP, eVal);
        break;
#endif

#if defined(SO_SNDLOWAT)
    case ESOCK_OPT_SOCK_SNDLOWAT:
        result = esock_setopt_lvl_sock_sndlowat(env, descP, eVal);
        break;
#endif

#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
    case ESOCK_OPT_SOCK_SNDTIMEO:
        result = esock_setopt_lvl_sock_sndtimeo(env, descP, eVal);
        break;
#endif

#if defined(SO_TIMESTAMP)
    case ESOCK_OPT_SOCK_TIMESTAMP:
        result = esock_setopt_lvl_sock_timestamp(env, descP, eVal);
        break;
#endif

    default:
        SSDBG( descP,
               ("SOCKET",
                "esock_setopt_lvl_socket -> unknown opt (%d)\r\n", eOpt) );
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_socket -> done when"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
}


#if defined(SO_BINDTODEVICE)
static
ERL_NIF_TERM esock_setopt_lvl_sock_bindtodevice(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
    return esock_setopt_str_opt(env, descP,
                                SOL_SOCKET, SO_BINDTODEVICE,
                                IFNAMSIZ, eVal);
}
#endif


#if defined(SO_BROADCAST)
static
ERL_NIF_TERM esock_setopt_lvl_sock_broadcast(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST, eVal);
}
#endif


#if defined(SO_DEBUG)
static
ERL_NIF_TERM esock_setopt_lvl_sock_debug(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG, eVal);
}
#endif


#if defined(SO_DONTROUTE)
static
ERL_NIF_TERM esock_setopt_lvl_sock_dontroute(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE, eVal);
}
#endif


#if defined(SO_KEEPALIVE)
static
ERL_NIF_TERM esock_setopt_lvl_sock_keepalive(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE, eVal);
}
#endif


#if defined(SO_LINGER)
static
ERL_NIF_TERM esock_setopt_lvl_sock_linger(ErlNifEnv*       env,
                                          ESockDescriptor* 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 esock_setopt_lvl_sock_oobinline(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE, eVal);
}
#endif


#if defined(SO_PASSCRED)
static
ERL_NIF_TERM esock_setopt_lvl_sock_passcred(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_PASSCRED, eVal);
}
#endif


#if defined(SO_PEEK_OFF)
static
ERL_NIF_TERM esock_setopt_lvl_sock_peek_off(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF, eVal);
}
#endif


#if defined(SO_PRIORITY)
static
ERL_NIF_TERM esock_setopt_lvl_sock_priority(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY, eVal);
}
#endif


#if defined(SO_RCVBUF)
static
ERL_NIF_TERM esock_setopt_lvl_sock_rcvbuf(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF, eVal);
}
#endif


#if defined(SO_RCVLOWAT)
static
ERL_NIF_TERM esock_setopt_lvl_sock_rcvlowat(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT, eVal);
}
#endif


#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_setopt_lvl_sock_rcvtimeo(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    return esock_setopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO, eVal);
}
#endif


#if defined(SO_REUSEADDR)
static
ERL_NIF_TERM esock_setopt_lvl_sock_reuseaddr(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR, eVal);
}
#endif


#if defined(SO_REUSEPORT)
static
ERL_NIF_TERM esock_setopt_lvl_sock_reuseport(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT, eVal);
}
#endif


#if defined(SO_SNDBUF)
static
ERL_NIF_TERM esock_setopt_lvl_sock_sndbuf(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF, eVal);
}
#endif


#if defined(SO_SNDLOWAT)
static
ERL_NIF_TERM esock_setopt_lvl_sock_sndlowat(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT, eVal);
}
#endif


#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_setopt_lvl_sock_sndtimeo(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_sock_sndtimeo -> entry with"
            "\r\n   eVal: %T"
            "\r\n", eVal) );

    return esock_setopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO, eVal);
}
#endif


#if defined(SO_TIMESTAMP)
static
ERL_NIF_TERM esock_setopt_lvl_sock_timestamp(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP, eVal);
}
#endif



/* esock_setopt_lvl_ip - Level *IP* option(s)
 */
static
ERL_NIF_TERM esock_setopt_lvl_ip(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 int              eOpt,
                                 ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_ip -> entry with"
            "\r\n   opt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
#if defined(IP_ADD_MEMBERSHIP)
    case ESOCK_OPT_IP_ADD_MEMBERSHIP:
        result = esock_setopt_lvl_ip_add_membership(env, descP, eVal);
        break;
#endif

#if defined(IP_ADD_SOURCE_MEMBERSHIP)
    case ESOCK_OPT_IP_ADD_SOURCE_MEMBERSHIP:
        result = esock_setopt_lvl_ip_add_source_membership(env, descP, eVal);
        break;
#endif

#if defined(IP_BLOCK_SOURCE)
    case ESOCK_OPT_IP_BLOCK_SOURCE:
        result = esock_setopt_lvl_ip_block_source(env, descP, eVal);
        break;
#endif

#if defined(IP_DROP_MEMBERSHIP)
    case ESOCK_OPT_IP_DROP_MEMBERSHIP:
        result = esock_setopt_lvl_ip_drop_membership(env, descP, eVal);
        break;
#endif

#if defined(IP_DROP_SOURCE_MEMBERSHIP)
    case ESOCK_OPT_IP_DROP_SOURCE_MEMBERSHIP:
        result = esock_setopt_lvl_ip_drop_source_membership(env, descP, eVal);
        break;
#endif

#if defined(IP_FREEBIND)
    case ESOCK_OPT_IP_FREEBIND:
        result = esock_setopt_lvl_ip_freebind(env, descP, eVal);
        break;
#endif

#if defined(IP_HDRINCL)
    case ESOCK_OPT_IP_HDRINCL:
        result = esock_setopt_lvl_ip_hdrincl(env, descP, eVal);
        break;
#endif

#if defined(IP_MINTTL)
    case ESOCK_OPT_IP_MINTTL:
        result = esock_setopt_lvl_ip_minttl(env, descP, eVal);
        break;
#endif

#if defined(IP_MSFILTER) && defined(IP_MSFILTER_SIZE)
    case ESOCK_OPT_IP_MSFILTER:
        result = esock_setopt_lvl_ip_msfilter(env, descP, eVal);
        break;
#endif

#if defined(IP_MTU_DISCOVER)
    case ESOCK_OPT_IP_MTU_DISCOVER:
        result = esock_setopt_lvl_ip_mtu_discover(env, descP, eVal);
        break;
#endif

#if defined(IP_MULTICAST_ALL)
    case ESOCK_OPT_IP_MULTICAST_ALL:
        result = esock_setopt_lvl_ip_multicast_all(env, descP, eVal);
        break;
#endif

#if defined(IP_MULTICAST_IF)
    case ESOCK_OPT_IP_MULTICAST_IF:
        result = esock_setopt_lvl_ip_multicast_if(env, descP, eVal);
        break;
#endif

#if defined(IP_MULTICAST_LOOP)
    case ESOCK_OPT_IP_MULTICAST_LOOP:
        result = esock_setopt_lvl_ip_multicast_loop(env, descP, eVal);
        break;
#endif

#if defined(IP_MULTICAST_TTL)
    case ESOCK_OPT_IP_MULTICAST_TTL:
        result = esock_setopt_lvl_ip_multicast_ttl(env, descP, eVal);
        break;
#endif

#if defined(IP_NODEFRAG)
    case ESOCK_OPT_IP_NODEFRAG:
        result = esock_setopt_lvl_ip_nodefrag(env, descP, eVal);
        break;
#endif

#if defined(IP_PKTINFO)
    case ESOCK_OPT_IP_PKTINFO:
        result = esock_setopt_lvl_ip_pktinfo(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVDSTADDR)
    case ESOCK_OPT_IP_RECVDSTADDR:
        result = esock_setopt_lvl_ip_recvdstaddr(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVERR)
    case ESOCK_OPT_IP_RECVERR:
        result = esock_setopt_lvl_ip_recverr(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVIF)
    case ESOCK_OPT_IP_RECVIF:
        result = esock_setopt_lvl_ip_recvif(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVOPTS)
    case ESOCK_OPT_IP_RECVOPTS:
        result = esock_setopt_lvl_ip_recvopts(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVORIGDSTADDR)
    case ESOCK_OPT_IP_RECVORIGDSTADDR:
        result = esock_setopt_lvl_ip_recvorigdstaddr(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVTOS)
    case ESOCK_OPT_IP_RECVTOS:
        result = esock_setopt_lvl_ip_recvtos(env, descP, eVal);
        break;
#endif

#if defined(IP_RECVTTL)
    case ESOCK_OPT_IP_RECVTTL:
        result = esock_setopt_lvl_ip_recvttl(env, descP, eVal);
        break;
#endif

#if defined(IP_RETOPTS)
    case ESOCK_OPT_IP_RETOPTS:
        result = esock_setopt_lvl_ip_retopts(env, descP, eVal);
        break;
#endif

#if defined(IP_ROUTER_ALERT)
    case ESOCK_OPT_IP_ROUTER_ALERT:
        result = esock_setopt_lvl_ip_router_alert(env, descP, eVal);
        break;
#endif

#if defined(IP_SENDSRCADDR)
    case ESOCK_OPT_IP_SENDSRCADDR:
        result = esock_setopt_lvl_ip_sendsrcaddr(env, descP, eVal);
        break;
#endif

#if defined(IP_TOS)
    case ESOCK_OPT_IP_TOS:
        result = esock_setopt_lvl_ip_tos(env, descP, eVal);
        break;
#endif

#if defined(IP_TRANSPARENT)
    case ESOCK_OPT_IP_TRANSPARENT:
        result = esock_setopt_lvl_ip_transparent(env, descP, eVal);
        break;
#endif

#if defined(IP_TTL)
    case ESOCK_OPT_IP_TTL:
        result = esock_setopt_lvl_ip_ttl(env, descP, eVal);
        break;
#endif

#if defined(IP_UNBLOCK_SOURCE)
    case ESOCK_OPT_IP_UNBLOCK_SOURCE:
        result = esock_setopt_lvl_ip_unblock_source(env, descP, eVal);
        break;
#endif

    default:
        SSDBG( descP, ("SOCKET",
                       "esock_setopt_lvl_ip -> unknown opt (%d)\r\n", eOpt) );
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_ip -> done when"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
}


/* esock_setopt_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 esock_setopt_lvl_ip_add_membership(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ip_update_membership(env, descP, eVal,
                                                 IP_ADD_MEMBERSHIP);
}
#endif


/* esock_setopt_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 esock_setopt_lvl_ip_add_source_membership(ErlNifEnv*       env,
                                                       ESockDescriptor* descP,
                                                       ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ip_update_source(env, descP, eVal,
                                             IP_ADD_SOURCE_MEMBERSHIP);
}
#endif


/* esock_setopt_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 esock_setopt_lvl_ip_block_source(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ip_update_source(env, descP, eVal, IP_BLOCK_SOURCE);
}
#endif


/* esock_setopt_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 esock_setopt_lvl_ip_drop_membership(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ip_update_membership(env, descP, eVal,
                                                 IP_DROP_MEMBERSHIP);
}
#endif



/* esock_setopt_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 esock_setopt_lvl_ip_drop_source_membership(ErlNifEnv*       env,
                                                        ESockDescriptor* descP,
                                                        ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ip_update_source(env, descP, eVal,
                                             IP_DROP_SOURCE_MEMBERSHIP);
}
#endif



/* esock_setopt_lvl_ip_freebind - Level IP FREEBIND option
 */
#if defined(IP_FREEBIND)
static
ERL_NIF_TERM esock_setopt_lvl_ip_freebind(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_FREEBIND, eVal);
}
#endif



/* esock_setopt_lvl_ip_hdrincl - Level IP HDRINCL option
 */
#if defined(IP_HDRINCL)
static
ERL_NIF_TERM esock_setopt_lvl_ip_hdrincl(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_HDRINCL, eVal);
}
#endif



/* esock_setopt_lvl_ip_minttl - Level IP MINTTL option
 */
#if defined(IP_MINTTL)
static
ERL_NIF_TERM esock_setopt_lvl_ip_minttl(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_int_opt(env, descP, level, IP_MINTTL, eVal);
}
#endif



/* esock_setopt_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 esock_setopt_lvl_ip_msfilter(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    if (COMPARE(eVal, atom_null) == 0) {
        return esock_setopt_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, (Uint32*) &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 = esock_setopt_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 esock_setopt_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



/* esock_setopt_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 esock_setopt_lvl_ip_mtu_discover(ErlNifEnv*       env,
                                              ESockDescriptor* 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


/* esock_setopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option
 */
#if defined(IP_MULTICAST_ALL)
static
ERL_NIF_TERM esock_setopt_lvl_ip_multicast_all(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_MULTICAST_ALL, eVal);
}
#endif


/* esock_setopt_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 esock_setopt_lvl_ip_multicast_if(ErlNifEnv*       env,
                                              ESockDescriptor* 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


/* esock_setopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option
 */
#if defined(IP_MULTICAST_LOOP)
static
ERL_NIF_TERM esock_setopt_lvl_ip_multicast_loop(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP, eVal);
}
#endif


/* esock_setopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option
 */
#if defined(IP_MULTICAST_TTL)
static
ERL_NIF_TERM esock_setopt_lvl_ip_multicast_ttl(ErlNifEnv*       env,
                                               ESockDescriptor* descP,
                                               ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_int_opt(env, descP, level, IP_MULTICAST_TTL, eVal);
}
#endif


/* esock_setopt_lvl_ip_nodefrag - Level IP NODEFRAG option
 */
#if defined(IP_NODEFRAG)
static
ERL_NIF_TERM esock_setopt_lvl_ip_nodefrag(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_NODEFRAG, eVal);
}
#endif


/* esock_setopt_lvl_ip_pktinfo - Level IP PKTINFO option
 */
#if defined(IP_PKTINFO)
static
ERL_NIF_TERM esock_setopt_lvl_ip_pktinfo(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_PKTINFO, eVal);
}
#endif


/* esock_setopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option
 */
#if defined(IP_RECVDSTADDR)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recvdstaddr(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVDSTADDR, eVal);
}
#endif


/* esock_setopt_lvl_ip_recverr - Level IP RECVERR option
 */
#if defined(IP_RECVERR)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recverr(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVERR, eVal);
}
#endif


/* esock_setopt_lvl_ip_recvif - Level IP RECVIF option
 */
#if defined(IP_RECVIF)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recvif(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVIF, eVal);
}
#endif


/* esock_setopt_lvl_ip_recvopts - Level IP RECVOPTS option
 */
#if defined(IP_RECVOPTS)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recvopts(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVOPTS, eVal);
}
#endif


/* esock_setopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option
 */
#if defined(IP_RECVORIGDSTADDR)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recvorigdstaddr(ErlNifEnv*       env,
                                                 ESockDescriptor* descP,
                                                 ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR, eVal);
}
#endif


/* esock_setopt_lvl_ip_recvtos - Level IP RECVTOS option
 */
#if defined(IP_RECVTOS)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recvtos(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVTOS, eVal);
}
#endif


/* esock_setopt_lvl_ip_recvttl - Level IP RECVTTL option
 */
#if defined(IP_RECVTTL)
static
ERL_NIF_TERM esock_setopt_lvl_ip_recvttl(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RECVTTL, eVal);
}
#endif


/* esock_setopt_lvl_ip_retopts - Level IP RETOPTS option
 */
#if defined(IP_RETOPTS)
static
ERL_NIF_TERM esock_setopt_lvl_ip_retopts(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_RETOPTS, eVal);
}
#endif


/* esock_setopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option
 */
#if defined(IP_ROUTER_ALERT)
static
ERL_NIF_TERM esock_setopt_lvl_ip_router_alert(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_int_opt(env, descP, level, IP_ROUTER_ALERT, eVal);
}
#endif


/* esock_setopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option
 */
#if defined(IP_SENDSRCADDR)
static
ERL_NIF_TERM esock_setopt_lvl_ip_sendsrcaddr(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_SENDSRCADDR, eVal);
}
#endif


/* esock_setopt_lvl_ip_tos - Level IP TOS option
 */
#if defined(IP_TOS)
static
ERL_NIF_TERM esock_setopt_lvl_ip_tos(ErlNifEnv*       env,
                                     ESockDescriptor* 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


/* esock_setopt_lvl_ip_transparent - Level IP TRANSPARENT option
 */
#if defined(IP_TRANSPARENT)
static
ERL_NIF_TERM esock_setopt_lvl_ip_transparent(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_bool_opt(env, descP, level, IP_TRANSPARENT, eVal);
}
#endif



/* esock_setopt_lvl_ip_ttl - Level IP TTL option
 */
#if defined(IP_TTL)
static
ERL_NIF_TERM esock_setopt_lvl_ip_ttl(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     ERL_NIF_TERM     eVal)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_setopt_int_opt(env, descP, level, IP_TTL, eVal);
}
#endif



/* esock_setopt_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 esock_setopt_lvl_ip_unblock_source(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ip_update_source(env, descP, eVal,
                                             IP_UNBLOCK_SOURCE);
}
#endif



#if defined(IP_ADD_MEMBERSHIP) || defined(IP_DROP_MEMBERSHIP)
static
ERL_NIF_TERM esock_setopt_lvl_ip_update_membership(ErlNifEnv*       env,
                                                   ESockDescriptor* 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)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "value *not* a map\r\n") );
        return enif_make_badarg(env);
    }

    // It must have atleast two attributes
    if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "invalid map value: %T\r\n", eVal) );
        return enif_make_badarg(env);
    }

    if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "failed get multiaddr (map) attribute\r\n") );
        return enif_make_badarg(env);
    }

    if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "failed get interface (map) attribute\r\n") );
        return enif_make_badarg(env);
    }

    if ((xres = esock_decode_ip4_address(env,
                                         eMultiAddr,
                                         &mreq.imr_multiaddr)) != NULL) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "failed decode multiaddr %T: %s\r\n", eMultiAddr, xres) );
        return esock_make_error_str(env, xres);
    }

    if ((xres = esock_decode_ip4_address(env,
                                         eInterface,
                                         &mreq.imr_interface)) != NULL) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "failed decode interface %T: %s\r\n", eInterface, xres) );
        return esock_make_error_str(env, xres);
    }

    res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));

    if (res != 0) {
        int save_errno = sock_errno();

        result = esock_make_error_errno(env, save_errno);

        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "failed setopt: %T (%d)\r\n", result, save_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 esock_setopt_lvl_ip_update_source(ErlNifEnv*       env,
                                               ESockDescriptor* 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 *** */

/* esock_setopt_lvl_ipv6 - Level *IPv6* option(s)
 */
#if defined(HAVE_IPV6)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   int              eOpt,
                                   ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_ipv6 -> entry with"
            "\r\n   opt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
#if defined(IPV6_ADDRFORM)
    case ESOCK_OPT_IPV6_ADDRFORM:
        result = esock_setopt_lvl_ipv6_addrform(env, descP, eVal);
        break;
#endif

#if defined(IPV6_ADD_MEMBERSHIP)
    case ESOCK_OPT_IPV6_ADD_MEMBERSHIP:
        result = esock_setopt_lvl_ipv6_add_membership(env, descP, eVal);
        break;
#endif

#if defined(IPV6_AUTHHDR)
    case ESOCK_OPT_IPV6_AUTHHDR:
        result = esock_setopt_lvl_ipv6_authhdr(env, descP, eVal);
        break;
#endif

#if defined(IPV6_DROP_MEMBERSHIP)
    case ESOCK_OPT_IPV6_DROP_MEMBERSHIP:
        result = esock_setopt_lvl_ipv6_drop_membership(env, descP, eVal);
        break;
#endif

#if defined(IPV6_DSTOPTS)
    case ESOCK_OPT_IPV6_DSTOPTS:
        result = esock_setopt_lvl_ipv6_dstopts(env, descP, eVal);
        break;
#endif

#if defined(IPV6_FLOWINFO)
    case ESOCK_OPT_IPV6_FLOWINFO:
        result = esock_setopt_lvl_ipv6_flowinfo(env, descP, eVal);
        break;
#endif

#if defined(IPV6_HOPLIMIT)
    case ESOCK_OPT_IPV6_HOPLIMIT:
      result = esock_setopt_lvl_ipv6_hoplimit(env, descP, eVal);
      break;
#endif

#if defined(IPV6_HOPOPTS)
    case ESOCK_OPT_IPV6_HOPOPTS:
        result = esock_setopt_lvl_ipv6_hopopts(env, descP, eVal);
        break;
#endif

#if defined(IPV6_MTU)
    case ESOCK_OPT_IPV6_MTU:
        result = esock_setopt_lvl_ipv6_mtu(env, descP, eVal);
        break;
#endif

#if defined(IPV6_MTU_DISCOVER)
    case ESOCK_OPT_IPV6_MTU_DISCOVER:
        result = esock_setopt_lvl_ipv6_mtu_discover(env, descP, eVal);
        break;
#endif

#if defined(IPV6_MULTICAST_HOPS)
    case ESOCK_OPT_IPV6_MULTICAST_HOPS:
        result = esock_setopt_lvl_ipv6_multicast_hops(env, descP, eVal);
        break;
#endif

#if defined(IPV6_MULTICAST_IF)
    case ESOCK_OPT_IPV6_MULTICAST_IF:
        result = esock_setopt_lvl_ipv6_multicast_if(env, descP, eVal);
        break;
#endif

#if defined(IPV6_MULTICAST_LOOP)
    case ESOCK_OPT_IPV6_MULTICAST_LOOP:
        result = esock_setopt_lvl_ipv6_multicast_loop(env, descP, eVal);
        break;
#endif

#if defined(IPV6_RECVERR)
    case ESOCK_OPT_IPV6_RECVERR:
        result = esock_setopt_lvl_ipv6_recverr(env, descP, eVal);
        break;
#endif

#if defined(IPV6_RECVHOPLIMIT)
    case ESOCK_OPT_IPV6_RECVHOPLIMIT:
      result = esock_setopt_lvl_ipv6_recvhoplimit(env, descP, eVal);
      break;
#endif

#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
    case ESOCK_OPT_IPV6_RECVPKTINFO:
      result = esock_setopt_lvl_ipv6_recvpktinfo(env, descP, eVal);
      break;
#endif

#if defined(IPV6_RECVTCLASS)
    case ESOCK_OPT_IPV6_RECVTCLASS:
        result = esock_setopt_lvl_ipv6_recvtclass(env, descP, eVal);
        break;
#endif

#if defined(IPV6_ROUTER_ALERT)
    case ESOCK_OPT_IPV6_ROUTER_ALERT:
        result = esock_setopt_lvl_ipv6_router_alert(env, descP, eVal);
        break;
#endif

#if defined(IPV6_RTHDR)
    case ESOCK_OPT_IPV6_RTHDR:
        result = esock_setopt_lvl_ipv6_rthdr(env, descP, eVal);
        break;
#endif

#if defined(IPV6_TCLASS)
    case ESOCK_OPT_IPV6_TCLASS:
        result = esock_setopt_lvl_ipv6_tclass(env, descP, eVal);
        break;
#endif

#if defined(IPV6_UNICAST_HOPS)
    case ESOCK_OPT_IPV6_UNICAST_HOPS:
        result = esock_setopt_lvl_ipv6_unicast_hops(env, descP, eVal);
        break;
#endif

#if defined(IPV6_V6ONLY)
    case ESOCK_OPT_IPV6_V6ONLY:
        result = esock_setopt_lvl_ipv6_v6only(env, descP, eVal);
        break;
#endif

    default:
        SSDBG( descP,
               ("SOCKET",
                "esock_setopt_lvl_ipv6 -> unknown opt (%d)\r\n", eOpt) );
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_ipv6 -> done when"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
}


#if defined(IPV6_ADDRFORM)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_addrform(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;
    int          res, edomain, domain;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_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", "esock_setopt_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",
                   "esock_setopt_lvl_ipv6_addrform -> try set opt to %d\r\n",
                   domain) );
    
    res = socket_setopt(descP->sock,
#if defined(SOL_IPV6)
                        SOL_IPV6,
#else
                        IPPROTO_IPV6,
#endif
                        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 esock_setopt_lvl_ipv6_add_membership(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ipv6_update_membership(env, descP, eVal,
                                              IPV6_ADD_MEMBERSHIP);
}
#endif


#if defined(IPV6_AUTHHDR)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_authhdr(ErlNifEnv*       env,
                                           ESockDescriptor* descP,
                                           ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif


    return esock_setopt_bool_opt(env, descP, level, IPV6_AUTHHDR, eVal);
}
#endif


#if defined(IPV6_DROP_MEMBERSHIP)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_drop_membership(ErlNifEnv*       env,
                                                   ESockDescriptor* descP,
                                                   ERL_NIF_TERM     eVal)
{
    return esock_setopt_lvl_ipv6_update_membership(env, descP, eVal,
                                              IPV6_DROP_MEMBERSHIP);
}
#endif


#if defined(IPV6_DSTOPTS)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_dstopts(ErlNifEnv*       env,
                                           ESockDescriptor* descP,
                                           ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_DSTOPTS, eVal);
}
#endif


#if defined(IPV6_FLOWINFO)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_flowinfo(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_FLOWINFO, eVal);
}
#endif


#if defined(IPV6_HOPLIMIT)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_hoplimit(ErlNifEnv*       env,
                                            ESockDescriptor* descP,
                                            ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
  int level = SOL_IPV6;
#else
  int level = IPPROTO_IPV6;
#endif

  return esock_setopt_bool_opt(env, descP, level, IPV6_HOPLIMIT, eVal);
}
#endif


#if defined(IPV6_HOPOPTS)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_hopopts(ErlNifEnv*       env,
                                           ESockDescriptor* descP,
                                           ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_HOPOPTS, eVal);
}
#endif


#if defined(IPV6_MTU)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_mtu(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_int_opt(env, descP, level, IPV6_MTU, eVal);
}
#endif


/* esock_setopt_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 esock_setopt_lvl_ipv6_mtu_discover(ErlNifEnv*       env,
                                                ESockDescriptor* 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 esock_setopt_lvl_ipv6_multicast_hops(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS, eVal);
}
#endif



#if defined(IPV6_MULTICAST_IF)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_multicast_if(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_int_opt(env, descP, level, IPV6_MULTICAST_IF, eVal);
}
#endif



#if defined(IPV6_MULTICAST_LOOP)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_multicast_loop(ErlNifEnv*       env,
                                                  ESockDescriptor* descP,
                                                  ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP, eVal);
}
#endif


#if defined(IPV6_RECVERR)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_recverr(ErlNifEnv*       env,
                                           ESockDescriptor* descP,
                                           ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_RECVERR, eVal);
}
#endif


#if defined(IPV6_RECVHOPLIMIT)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_recvhoplimit(ErlNifEnv*       env,
						ESockDescriptor* descP,
						ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
  int level = SOL_IPV6;
#else
  int level = IPPROTO_IPV6;
#endif

  return esock_setopt_bool_opt(env, descP, level, IPV6_RECVHOPLIMIT, eVal);
}
#endif


#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_recvpktinfo(ErlNifEnv*       env,
                                               ESockDescriptor* 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 esock_setopt_bool_opt(env, descP, level, opt, eVal);
}
#endif


#if defined(IPV6_RECVTCLASS)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_recvtclass(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif
    int opt = IPV6_RECVTCLASS;

    return esock_setopt_bool_opt(env, descP, level, opt, eVal);
}
#endif


#if defined(IPV6_ROUTER_ALERT)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_router_alert(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT, eVal);
}
#endif



#if defined(IPV6_RTHDR)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_rthdr(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_RTHDR, eVal);
}
#endif


#if defined(IPV6_TCLASS)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_tclass(ErlNifEnv*       env,
                                              ESockDescriptor* descP,
                                              ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif
    int opt   = IPV6_TCLASS;

    return esock_setopt_bool_opt(env, descP, level, opt, eVal);
}
#endif


#if defined(IPV6_UNICAST_HOPS)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_unicast_hops(ErlNifEnv*       env,
                                                ESockDescriptor* descP,
                                                ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS, eVal);
}
#endif



#if defined(IPV6_V6ONLY)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_v6only(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_setopt_bool_opt(env, descP, level, IPV6_V6ONLY, eVal);
}
#endif


#if defined(IPV6_ADD_MEMBERSHIP) || defined(IPV6_DROP_MEMBERSHIP)
static
ERL_NIF_TERM esock_setopt_lvl_ipv6_update_membership(ErlNifEnv*       env,
                                                     ESockDescriptor* 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)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ipv6_update_membership -> "
                "value *not* a map\r\n") );
        return enif_make_badarg(env);
    }

    // It must have atleast two attributes
    if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ipv6_update_membership -> "
                "invalid map value: %T\r\n", eVal) );
        return enif_make_badarg(env);
    }

    if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ipv6_update_membership -> "
                "failed get multiaddr (map) attribute\r\n") );
        return enif_make_badarg(env);
    }

    if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ipv6_update_membership -> "
                "failed get interface (map) attribute\r\n") );
        return enif_make_badarg(env);
    }

    if ((xres = esock_decode_ip6_address(env,
                                         eMultiAddr,
                                         &mreq.ipv6mr_multiaddr)) != NULL) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ipv6_update_membership -> "
                "failed decode multiaddr %T: %s\r\n", eMultiAddr, xres) );
        return esock_make_error_str(env, xres);
    }

    if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface)) {
        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ip_update_membership -> "
                "failed decode interface %T: %s\r\n", eInterface, xres) );
        return esock_make_error(env, esock_atom_einval);
    }

    res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));

    if (res != 0) {
        int save_errno = sock_errno();

        result = esock_make_error_errno(env, save_errno);

        SSDBG( descP,
               ("SOCKET", "esock_setopt_lvl_ipv6_update_membership -> "
                "failed setopt: %T (%d)\r\n", result, save_errno) );

    } else {
        result = esock_atom_ok;
    }

    return result;
}
#endif



#endif // defined(HAVE_IPV6)



/* esock_setopt_lvl_tcp - Level *TCP* option(s)
 */
static
ERL_NIF_TERM esock_setopt_lvl_tcp(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  int              eOpt,
                                  ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_tcp -> entry with"
            "\r\n   opt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
#if defined(TCP_CONGESTION)
    case ESOCK_OPT_TCP_CONGESTION:
        result = esock_setopt_lvl_tcp_congestion(env, descP, eVal);
        break;
#endif

#if defined(TCP_CORK)
    case ESOCK_OPT_TCP_CORK:
        result = esock_setopt_lvl_tcp_cork(env, descP, eVal);
        break;
#endif

#if defined(TCP_MAXSEG)
    case ESOCK_OPT_TCP_MAXSEG:
        result = esock_setopt_lvl_tcp_maxseg(env, descP, eVal);
        break;
#endif

#if defined(TCP_NODELAY)
    case ESOCK_OPT_TCP_NODELAY:
        result = esock_setopt_lvl_tcp_nodelay(env, descP, eVal);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_setopt_lvl_tcp_congestion - Level TCP CONGESTION option
 */
#if defined(TCP_CONGESTION)
static
ERL_NIF_TERM esock_setopt_lvl_tcp_congestion(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    int max = ESOCK_OPT_TCP_CONGESTION_NAME_MAX+1;

    return esock_setopt_str_opt(env, descP,
                                IPPROTO_TCP, TCP_CONGESTION, max, eVal);
}
#endif


/* esock_setopt_lvl_tcp_cork - Level TCP CORK option
 */
#if defined(TCP_CORK)
static
ERL_NIF_TERM esock_setopt_lvl_tcp_cork(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, IPPROTO_TCP, TCP_CORK, eVal);
}
#endif


/* esock_setopt_lvl_tcp_maxseg - Level TCP MAXSEG option
 */
#if defined(TCP_MAXSEG)
static
ERL_NIF_TERM esock_setopt_lvl_tcp_maxseg(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG, eVal);
}
#endif


/* esock_setopt_lvl_tcp_nodelay - Level TCP NODELAY option
 */
#if defined(TCP_NODELAY)
static
ERL_NIF_TERM esock_setopt_lvl_tcp_nodelay(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY, eVal);
}
#endif



/* esock_setopt_lvl_udp - Level *UDP* option(s)
 */
static
ERL_NIF_TERM esock_setopt_lvl_udp(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  int              eOpt,
                                  ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_udp -> entry with"
            "\r\n   opt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
#if defined(UDP_CORK)
    case ESOCK_OPT_UDP_CORK:
        result = esock_setopt_lvl_udp_cork(env, descP, eVal);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_setopt_lvl_udp_cork - Level UDP CORK option
 */
#if defined(UDP_CORK)
static
ERL_NIF_TERM esock_setopt_lvl_udp_cork(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK, eVal);
}
#endif




/* esock_setopt_lvl_sctp - Level *SCTP* option(s)
 */
#if defined(HAVE_SCTP)
static
ERL_NIF_TERM esock_setopt_lvl_sctp(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   int              eOpt,
                                   ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_lvl_sctp -> entry with"
            "\r\n   opt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
#if defined(SCTP_ASSOCINFO)
    case ESOCK_OPT_SCTP_ASSOCINFO:
        result = esock_setopt_lvl_sctp_associnfo(env, descP, eVal);
        break;
#endif

#if defined(SCTP_AUTOCLOSE)
    case ESOCK_OPT_SCTP_AUTOCLOSE:
        result = esock_setopt_lvl_sctp_autoclose(env, descP, eVal);
        break;
#endif

#if defined(SCTP_DISABLE_FRAGMENTS)
    case ESOCK_OPT_SCTP_DISABLE_FRAGMENTS:
        result = esock_setopt_lvl_sctp_disable_fragments(env, descP, eVal);
        break;
#endif

#if defined(SCTP_EVENTS)
    case ESOCK_OPT_SCTP_EVENTS:
        result = esock_setopt_lvl_sctp_events(env, descP, eVal);
        break;
#endif

#if defined(SCTP_INITMSG)
    case ESOCK_OPT_SCTP_INITMSG:
        result = esock_setopt_lvl_sctp_initmsg(env, descP, eVal);
        break;
#endif

#if defined(SCTP_MAXSEG)
    case ESOCK_OPT_SCTP_MAXSEG:
        result = esock_setopt_lvl_sctp_maxseg(env, descP, eVal);
        break;
#endif

#if defined(SCTP_NODELAY)
    case ESOCK_OPT_SCTP_NODELAY:
        result = esock_setopt_lvl_sctp_nodelay(env, descP, eVal);
        break;
#endif

#if defined(SCTP_RTOINFO)
    case ESOCK_OPT_SCTP_RTOINFO:
        result = esock_setopt_lvl_sctp_rtoinfo(env, descP, eVal);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_setopt_lvl_sctp_associnfo - Level SCTP ASSOCINFO option
 */
#if defined(SCTP_ASSOCINFO)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_associnfo(ErlNifEnv*       env,
                                             ESockDescriptor* 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;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_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",
            "esock_setopt_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",
            "esock_setopt_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... 
     * Also, size of types matter, so adjust for that...
     */

#if (SIZEOF_INT == 4)
    {
        int tmpAssocId;
        if (!GET_INT(env, eAssocId, &tmpAssocId))
            return esock_make_error(env, esock_atom_einval);
        assocParams.sasoc_assoc_id =
            (typeof(assocParams.sasoc_assoc_id)) tmpAssocId;
    }
#elif (SIZEOF_LONG == 4)
    {
        long tmpAssocId;
        if (!GET_LONG(env, eAssocId, &tmpAssocId))
            return esock_make_error(env, esock_atom_einval);
        assocParams.sasoc_assoc_id =
            (typeof(assocParams.sasoc_assoc_id)) tmpAssocId;
    }
#else
    SIZE CHECK FOR ASSOC ID FAILED
#endif

    
    /*
     * 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",
            "esock_setopt_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", "esock_setopt_lvl_sctp_associnfo -> done with"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
    
}
#endif


/* esock_setopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option
 */
#if defined(SCTP_AUTOCLOSE)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_autoclose(ErlNifEnv*       env,
                                             ESockDescriptor* descP,
                                             ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP,
                                IPPROTO_SCTP, SCTP_AUTOCLOSE, eVal);
}
#endif


/* esock_setopt_lvl_sctp_disable_fragments -
 * Level SCTP DISABLE_FRAGMENTS option
 */
#if defined(SCTP_DISABLE_FRAGMENTS)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_disable_fragments(ErlNifEnv*       env,
                                                     ESockDescriptor* descP,
                                                     ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP,
                                 IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, eVal);
}
#endif


/* esock_setopt_lvl_sctp_events - Level SCTP EVENTS option
 */
#if defined(SCTP_EVENTS)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_events(ErlNifEnv*       env,
                                          ESockDescriptor* 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;
#if defined(HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE_SCTP_AUTHENTICATION_EVENT)
    ERL_NIF_TERM                eAuth;
#endif
#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", "esock_setopt_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",
            "esock_setopt_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",
            "esock_setopt_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",
            "esock_setopt_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", "esock_setopt_lvl_sctp_events -> done with"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
    
}
#endif


/* esock_setopt_lvl_sctp_initmsg - Level SCTP INITMSG option
 */
#if defined(SCTP_INITMSG)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_initmsg(ErlNifEnv*       env,
                                           ESockDescriptor* 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", "esock_setopt_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",
            "esock_setopt_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",
            "esock_setopt_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",
            "esock_setopt_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", "esock_setopt_lvl_sctp_initmsg -> done with"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
    
}
#endif


/* esock_setopt_lvl_sctp_maxseg - Level SCTP MAXSEG option
 */
#if defined(SCTP_MAXSEG)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_maxseg(ErlNifEnv*       env,
                                          ESockDescriptor* descP,
                                          ERL_NIF_TERM     eVal)
{
    return esock_setopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG, eVal);
}
#endif


/* esock_setopt_lvl_sctp_nodelay - Level SCTP NODELAY option
 */
#if defined(SCTP_NODELAY)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_nodelay(ErlNifEnv*       env,
                                           ESockDescriptor* descP,
                                           ERL_NIF_TERM     eVal)
{
    return esock_setopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY, eVal);
}
#endif


/* esock_setopt_lvl_sctp_rtoinfo - Level SCTP RTOINFO option
 */
#if defined(SCTP_RTOINFO)
static
ERL_NIF_TERM esock_setopt_lvl_sctp_rtoinfo(ErlNifEnv*       env,
                                           ESockDescriptor* 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;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_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",
            "esock_setopt_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",
            "esock_setopt_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... 
     * Also, size of types matter, so adjust for that...
     */

#if (SIZEOF_INT == 4)
    {
        int tmpAssocId;
        if (!GET_INT(env, eAssocId, &tmpAssocId))
            return esock_make_error(env, esock_atom_einval);
        rtoInfo.srto_assoc_id = (typeof(rtoInfo.srto_assoc_id)) tmpAssocId;
    }
#elif (SIZEOF_LONG == 4)
    {
        long tmpAssocId;
        if (!GET_LONG(env, eAssocId, &tmpAssocId))
            return esock_make_error(env, esock_atom_einval);
        rtoInfo.srto_assoc_id = (typeof(rtoInfo.srto_assoc_id)) tmpAssocId;
    }
#else
    SIZE CHECK FOR ASSOC ID FAILED
#endif
    
    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",
            "esock_setopt_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", "esock_setopt_lvl_sctp_rtoinfo -> done with"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
    
}
#endif



#endif // defined(HAVE_SCTP)




/* esock_setopt_bool_opt - set an option that has an (integer) bool value
 */
static
ERL_NIF_TERM esock_setopt_bool_opt(ErlNifEnv*       env,
                                   ESockDescriptor* 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;
}


/* esock_setopt_int_opt - set an option that has an integer value
 */
static
ERL_NIF_TERM esock_setopt_int_opt(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  int              level,
                                  int              opt,
                                  ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM result;
    int          val;

    if (GET_INT(env, eVal, &val)) {
        int res;

        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;
}


/* esock_setopt_str_opt - set an option that has an string value
 */
#if defined(USE_SETOPT_STR_OPT)
static
ERL_NIF_TERM esock_setopt_str_opt(ErlNifEnv*       env,
                                  ESockDescriptor* 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;
}
#endif


/* esock_setopt_timeval_opt - set an option that has an (timeval) bool value
 */
#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_setopt_timeval_opt(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      int              level,
                                      int              opt,
                                      ERL_NIF_TERM     eVal)
{
    ERL_NIF_TERM   result;
    struct timeval timeVal;
    int            res;
    char*          xres;

    SSDBG( descP,
           ("SOCKET", "esock_setopt_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", "esock_setopt_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", "esock_setopt_timeval_opt -> done with"
            "\r\n   result: %T"
            "\r\n", result) );

    return result;
    
}
#endif



static
BOOLEAN_T elevel2level(BOOLEAN_T  isEncoded,
                       int        eLevel,
                       BOOLEAN_T* isOTP,
                       int*       level)
{
    BOOLEAN_T result;

    if (isEncoded) {
        switch (eLevel) {
        case ESOCK_OPT_LEVEL_OTP:
            *isOTP = TRUE;
            *level = -1;
            result = TRUE;
            break;

        case ESOCK_OPT_LEVEL_SOCKET:
            *isOTP = FALSE;
            *level = SOL_SOCKET;
            result = TRUE;
            break;

        case ESOCK_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 ESOCK_OPT_LEVEL_IPV6:
            *isOTP = FALSE;
#if defined(SOL_IPV6)
            *level = SOL_IPV6;
#else
            *level = IPPROTO_IPV6;
#endif
            result = TRUE;
            break;
#endif

        case ESOCK_OPT_LEVEL_TCP:
            *isOTP = FALSE;
            *level = IPPROTO_TCP;
            result = TRUE;
            break;

        case ESOCK_OPT_LEVEL_UDP:
            *isOTP = FALSE;
            *level = IPPROTO_UDP;
            result = TRUE;
            break;

#ifdef HAVE_SCTP
        case ESOCK_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, "notsup"));
#else
    ESockDescriptor* 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) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (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}

    isEncoded = esock_decode_bool(eIsEncoded);

    if (!elevel2level(isEncoded, eLevel, &isOTP, &level))
        return esock_make_error(env, esock_atom_einval);

    MLOCK(descP->readMtx);
    return esock_getopt(env, descP, isEncoded, isOTP, level, eOpt);
    /* Surprise! MUNLOCK in called function */

#endif // if defined(__WIN32__)
}



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_getopt(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          BOOLEAN_T        isEncoded,
                          BOOLEAN_T        isOTP,
                          int              level,
                          ERL_NIF_TERM     eOpt)
{
    ERL_NIF_TERM result;
    int          opt;

    SSDBG( descP,
           ("SOCKET", "esock_getopt -> 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 (! IS_OPEN(descP)) {
        result = esock_make_error(env, atom_closed);
    } else if (isOTP) {
        /* These are not actual socket options,
         * but options for our implementation.
         */
        if (GET_INT(env, eOpt, &opt))
            return esock_getopt_otp(env, descP, opt);
        else
            result = esock_make_error(env, esock_atom_einval);
    } else if (!isEncoded) {
        return esock_getopt_native(env, descP, level, eOpt);
    } else {
        if (GET_INT(env, eOpt, &opt))
            return esock_getopt_level(env, descP, level, opt);
        else
            return esock_getopt_native(env, descP, level, eOpt);
    }

    SSDBG( descP,
           ("SOCKET", "esock_getopt -> done when"
            "\r\n   result: %T"
            "\r\n", result) );
    MUNLOCK(descP->readMtx);
    return result;
}



/* esock_getopt_otp - Handle OTP (level) options
 */
static
ERL_NIF_TERM esock_getopt_otp(ErlNifEnv*       env,
                              ESockDescriptor* descP,
                              int              eOpt)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp -> entry with"
            "\r\n   eOpt: %d"
            "\r\n", eOpt) );

    switch (eOpt) {
    case ESOCK_OPT_OTP_DEBUG:
        result = esock_getopt_otp_debug(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_IOW:
        result = esock_getopt_otp_iow(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_CTRL_PROC:
        result = esock_getopt_otp_ctrl_proc(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_RCVBUF:
        result = esock_getopt_otp_rcvbuf(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_RCVCTRLBUF:
        result = esock_getopt_otp_rcvctrlbuf(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_SNDCTRLBUF:
        MLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        result = esock_getopt_otp_sndctrlbuf(env, descP);
        MUNLOCK(descP->writeMtx);
        break;

    case ESOCK_OPT_OTP_FD:
        result = esock_getopt_otp_fd(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_META:
        MLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        result = esock_getopt_otp_meta(env, descP);
        MUNLOCK(descP->writeMtx);
        break;

    case ESOCK_OPT_OTP_USE_REGISTRY:
        MLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
        result = esock_getopt_otp_use_registry(env, descP);
        MUNLOCK(descP->writeMtx);
        break;

        /* *** INTERNAL *** */
    case ESOCK_OPT_OTP_DOMAIN:
        result = esock_getopt_otp_domain(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_TYPE:
        result = esock_getopt_otp_type(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_PROTOCOL:
        result = esock_getopt_otp_protocol(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    case ESOCK_OPT_OTP_DTP:
        result = esock_getopt_otp_dtp(env, descP);
        MUNLOCK(descP->readMtx);
        break;

    default:
        SSDBG( descP,
               ("SOCKET", "esock_getopt_otp {%d} -> einval with"
                "\r\n   eOpt: %d"
                "\r\n", descP->sock, eOpt) );
        MUNLOCK(descP->readMtx);

        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_getopt_otp_debug - Handle the OTP (level) debug option
 */
static
ERL_NIF_TERM esock_getopt_otp_debug(ErlNifEnv*       env,
                                    ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = esock_encode_bool(descP->dbg);

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_iow - Handle the OTP (level) iow option
 */
static
ERL_NIF_TERM esock_getopt_otp_iow(ErlNifEnv*       env,
                                  ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = esock_encode_bool(descP->iow);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_iow {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_ctrl_proc - Handle the OTP (level) controlling_process option
 */
static
ERL_NIF_TERM esock_getopt_otp_ctrl_proc(ErlNifEnv*       env,
                                        ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = MKPID(env, &descP->ctrlPid);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_ctrlProc {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}



/* esock_getopt_otp_rcvbuf - Handle the OTP (level) rcvbuf option
 */
static
ERL_NIF_TERM esock_getopt_otp_rcvbuf(ErlNifEnv*       env,
                                     ESockDescriptor* 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));
    }

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_rcvbuf {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_rcvctrlbuf - Handle the OTP (level) rcvctrlbuf option
 */
static
ERL_NIF_TERM esock_getopt_otp_rcvctrlbuf(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = MKI(env, descP->rCtrlSz);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_rcvctrlbuf {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_sndctrlbuf - Handle the OTP (level) sndctrlbuf option
 */
static
ERL_NIF_TERM esock_getopt_otp_sndctrlbuf(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = MKI(env, descP->wCtrlSz);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_sndctrlbuf {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_fd - Handle the OTP (level) fd option
 */
static
ERL_NIF_TERM esock_getopt_otp_fd(ErlNifEnv*       env,
                                 ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = MKI(env, descP->sock);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_fd {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_meta - Handle the OTP (level) meta option
 */
static
ERL_NIF_TERM esock_getopt_otp_meta(ErlNifEnv*       env,
                                   ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = CP_TERM(env, descP->meta.ref);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_meta {%d} ->"
            "\r\n   eVal: %T"
            "\r\n", descP->sock, eVal) );

    return esock_make_ok2(env, eVal);
}


/* esock_getopt_otp_use_registry - Handle the OTP (level) use_registry option
 */
static
ERL_NIF_TERM esock_getopt_otp_use_registry(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
    ERL_NIF_TERM eVal = esock_encode_bool(descP->useReg);

    return esock_make_ok2(env, eVal);
}


static
ERL_NIF_TERM getopt_otp_domain(ErlNifEnv* env, int domain)
{
    ERL_NIF_TERM result;

    switch (domain) {
    case AF_INET:
        result = esock_atom_inet;
        break;

#if defined(HAVE_IN6) && defined(AF_INET6)
    case AF_INET6:
        result = esock_atom_inet6;
        break;
#endif

#if defined(HAVE_SYS_UN_H)
    case AF_UNIX:
        result = esock_atom_local;
        break;
#endif

    default:
        result = MKI(env, domain);
        break;
    }
    
    return result;
}
/*
 * esock_getopt_otp_domain - Handle the OTP (level) domain option
 */
static
ERL_NIF_TERM esock_getopt_otp_domain(ErlNifEnv*       env,
                                     ESockDescriptor* descP)
{
    ERL_NIF_TERM domain, result;

    domain = getopt_otp_domain(env, descP->domain);
    result = esock_make_ok2(env, domain);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_domain {%d} ->"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    return result;
}


static
ERL_NIF_TERM getopt_otp_type(ErlNifEnv* env, int type)
{
    ERL_NIF_TERM result;

    switch (type) {
    case SOCK_STREAM:
        result = esock_atom_stream;
        break;

    case SOCK_DGRAM:
        result = esock_atom_dgram;
        break;

#ifdef SOCK_SEQPACKET
    case SOCK_SEQPACKET:
        result = esock_atom_seqpacket;
        break;
#endif
    case SOCK_RAW:
        result = esock_atom_raw;
        break;

#ifdef SOCK_RDM
    case SOCK_RDM:
        result = esock_atom_rdm;
        break;
#endif

    default:
        result = MKI(env, type);
        break;
    }

    return result;
}
/*
 * esock_getopt_otp_type - Handle the OTP (level) type options.
 */
static
ERL_NIF_TERM esock_getopt_otp_type(ErlNifEnv*       env,
                                   ESockDescriptor* descP)
{
    ERL_NIF_TERM type, result;

    type = getopt_otp_type(env, descP->type);
    result = esock_make_ok2(env, type);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_type {%d} ->"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    return result;
}


static
ERL_NIF_TERM getopt_otp_protocol(ErlNifEnv*       env,
                                 ESockDescriptor* descP)
{
    ERL_NIF_TERM result;
    int          val = descP->protocol;

    switch (val) {
    case IPPROTO_IP:
#if defined(AF_LOCAL)
        if (descP->domain == AF_LOCAL) {
            result = esock_atom_default;
        } else {
            result = esock_atom_ip;
        }
#else
        result = esock_atom_ip;
#endif
        break;

    case IPPROTO_TCP:
        result = esock_atom_tcp;
        break;

    case IPPROTO_UDP:
        result = esock_atom_udp;
        break;

#if defined(HAVE_SCTP)
    case IPPROTO_SCTP:
        result = esock_atom_sctp;
        break;
#endif

    default:
        result = MKI(env, val);
        break;
    }

    return result;
}
/*
 * esock_getopt_otp_protocol - Handle the OTP (level) protocol options.
 */
static
ERL_NIF_TERM esock_getopt_otp_protocol(ErlNifEnv*       env,
                                       ESockDescriptor* descP)
{
    ERL_NIF_TERM protocol, result;

    protocol = getopt_otp_protocol(env, descP);
    result = esock_make_ok2(env, protocol);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_protocol {%d} ->"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );
    
    return result;
}


/*
 * esock_getopt_otp_dtp - Handle the OTP (level) type options.
 */
static
ERL_NIF_TERM esock_getopt_otp_dtp(ErlNifEnv*       env,
                                   ESockDescriptor* descP)
{
    ERL_NIF_TERM domain, type, protocol, dtp, result;

    domain = getopt_otp_domain(env, descP->domain);
    type = getopt_otp_type(env, descP->type);
    protocol = getopt_otp_protocol(env, descP);
    dtp = MKT3(env, domain, type, protocol);
    result = esock_make_ok2(env, dtp);

    SSDBG( descP,
           ("SOCKET", "esock_getopt_otp_dtp {%d} ->"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    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 esock_getopt_native(ErlNifEnv*       env,
                                 ESockDescriptor* 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", "esock_getopt_native {%d} -> entry"
            "\r\n   level: %d"
            "\r\n   eOpt: %T"
            "\r\n", descP->sock,
            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", "esock_getopt_native {%d} -> decoded opt"
                "\r\n   valueType: %d (%s)"
                "\r\n   ValueSize: %d"
                "\r\n", descP->sock, valueType, VT2S(valueType), valueSz) );

        switch (valueType) {
        case ESOCK_OPT_VALUE_TYPE_UNSPEC:
            result = esock_getopt_native_unspec(env, descP,
                                                level, opt, valueSz);
            break;
        case ESOCK_OPT_VALUE_TYPE_INT:
            result = esock_getopt_int_opt(env, descP, level, opt);
            break;
        case ESOCK_OPT_VALUE_TYPE_BOOL:
            result = esock_getopt_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", "esock_getopt_native {%d} -> done when"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    MUNLOCK(descP->readMtx);

    return result;
}


static
ERL_NIF_TERM esock_getopt_native_unspec(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        int              level,
                                        int              opt,
                                        SOCKOPTLEN_T     valueSz)
{
    ERL_NIF_TERM result = esock_make_error(env, esock_atom_einval);
    int          res;

    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;

        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);
        }
    }

    return result;
}



/* esock_getopt_level - A "proper" level (option) has been specified
 */
static
ERL_NIF_TERM esock_getopt_level(ErlNifEnv*       env,
                                ESockDescriptor* descP,
                                int              level,
                                int              eOpt)
{
    ERL_NIF_TERM result;

    SSDBG( descP,
           ("SOCKET", "esock_getopt_level {%d} -> entry with"
            "\r\n   level: %d"
            "\r\n   eOpt:  %d"
            "\r\n", descP->sock, level, eOpt) );

    switch (level) {
    case SOL_SOCKET:
        result = esock_getopt_lvl_socket(env, descP, eOpt);
        break;

#if defined(SOL_IP)
    case SOL_IP:
#else
    case IPPROTO_IP:
#endif
        result = esock_getopt_lvl_ip(env, descP, eOpt);
        break;

#if defined(HAVE_IPV6)
#if defined(SOL_IPV6)
    case SOL_IPV6:
#else
    case IPPROTO_IPV6:
#endif
        result = esock_getopt_lvl_ipv6(env, descP, eOpt);
        break;
#endif

    case IPPROTO_TCP:
        result = esock_getopt_lvl_tcp(env, descP, eOpt);
        break;

    case IPPROTO_UDP:
        result = esock_getopt_lvl_udp(env, descP, eOpt);
        break;

#if defined(HAVE_SCTP)
    case IPPROTO_SCTP:
        result = esock_getopt_lvl_sctp(env, descP, eOpt);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    SSDBG( descP,
           ("SOCKET", "esock_getopt_level {%d} -> done when"
            "\r\n   result: %T"
            "\r\n", descP->sock, result) );

    MUNLOCK(descP->readMtx);

    return result;
}


/* esock_getopt_lvl_socket - Level *SOCKET* option
 */
static
ERL_NIF_TERM esock_getopt_lvl_socket(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     int              eOpt)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
#if defined(SO_ACCEPTCONN)
    case ESOCK_OPT_SOCK_ACCEPTCONN:
        result = esock_getopt_lvl_sock_acceptconn(env, descP);
        break;
#endif

#if defined(SO_BINDTODEVICE)
    case ESOCK_OPT_SOCK_BINDTODEVICE:
        result = esock_getopt_lvl_sock_bindtodevice(env, descP);
        break;
#endif

#if defined(SO_BROADCAST)
    case ESOCK_OPT_SOCK_BROADCAST:
        result = esock_getopt_lvl_sock_broadcast(env, descP);
        break;
#endif

#if defined(SO_DEBUG)
    case ESOCK_OPT_SOCK_DEBUG:
        result = esock_getopt_lvl_sock_debug(env, descP);
        break;
#endif

#if defined(SO_DOMAIN)
    case ESOCK_OPT_SOCK_DOMAIN:
        result = esock_getopt_lvl_sock_domain(env, descP);
        break;
#endif

#if defined(SO_DONTROUTE)
    case ESOCK_OPT_SOCK_DONTROUTE:
        result = esock_getopt_lvl_sock_dontroute(env, descP);
        break;
#endif

#if defined(SO_KEEPALIVE)
    case ESOCK_OPT_SOCK_KEEPALIVE:
        result = esock_getopt_lvl_sock_keepalive(env, descP);
        break;
#endif

#if defined(SO_LINGER)
    case ESOCK_OPT_SOCK_LINGER:
        result = esock_getopt_lvl_sock_linger(env, descP);
        break;
#endif

#if defined(SO_OOBINLINE)
    case ESOCK_OPT_SOCK_OOBINLINE:
        result = esock_getopt_lvl_sock_oobinline(env, descP);
        break;
#endif

#if defined(SO_PASSCRED)
    case ESOCK_OPT_SOCK_PASSCRED:
        result = esock_getopt_lvl_sock_passcred(env, descP);
        break;
#endif

#if defined(SO_PEEK_OFF)
    case ESOCK_OPT_SOCK_PEEK_OFF:
        result = esock_getopt_lvl_sock_peek_off(env, descP);
        break;
#endif

#if defined(SO_PRIORITY)
    case ESOCK_OPT_SOCK_PRIORITY:
        result = esock_getopt_lvl_sock_priority(env, descP);
        break;
#endif

#if defined(SO_PROTOCOL)
    case ESOCK_OPT_SOCK_PROTOCOL:
        result = esock_getopt_lvl_sock_protocol(env, descP);
        break;
#endif

#if defined(SO_RCVBUF)
    case ESOCK_OPT_SOCK_RCVBUF:
        result = esock_getopt_lvl_sock_rcvbuf(env, descP);
        break;
#endif

#if defined(SO_RCVLOWAT)
    case ESOCK_OPT_SOCK_RCVLOWAT:
        result = esock_getopt_lvl_sock_rcvlowat(env, descP);
        break;
#endif

#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
    case ESOCK_OPT_SOCK_RCVTIMEO:
        result = esock_getopt_lvl_sock_rcvtimeo(env, descP);
        break;
#endif

#if defined(SO_REUSEADDR)
    case ESOCK_OPT_SOCK_REUSEADDR:
        result = esock_getopt_lvl_sock_reuseaddr(env, descP);
        break;
#endif

#if defined(SO_REUSEPORT)
    case ESOCK_OPT_SOCK_REUSEPORT:
        result = esock_getopt_lvl_sock_reuseport(env, descP);
        break;
#endif

#if defined(SO_SNDBUF)
    case ESOCK_OPT_SOCK_SNDBUF:
        result = esock_getopt_lvl_sock_sndbuf(env, descP);
        break;
#endif

#if defined(SO_SNDLOWAT)
    case ESOCK_OPT_SOCK_SNDLOWAT:
        result = esock_getopt_lvl_sock_sndlowat(env, descP);
        break;
#endif

#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
    case ESOCK_OPT_SOCK_SNDTIMEO:
        result = esock_getopt_lvl_sock_sndtimeo(env, descP);
        break;
#endif

#if defined(SO_TIMESTAMP)
    case ESOCK_OPT_SOCK_TIMESTAMP:
        result = esock_getopt_lvl_sock_timestamp(env, descP);
        break;
#endif

#if defined(SO_TYPE)
    case ESOCK_OPT_SOCK_TYPE:
        result = esock_getopt_lvl_sock_type(env, descP);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


#if defined(SO_ACCEPTCONN)
static
ERL_NIF_TERM esock_getopt_lvl_sock_acceptconn(ErlNifEnv*       env,
                                              ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_ACCEPTCONN);
}
#endif


#if defined(SO_BINDTODEVICE)
static
ERL_NIF_TERM esock_getopt_lvl_sock_bindtodevice(ErlNifEnv*       env,
                                                ESockDescriptor* descP)
{
    return esock_getopt_str_opt(env, descP,
                                SOL_SOCKET, SO_BINDTODEVICE, IFNAMSIZ+1);
}
#endif


#if defined(SO_BROADCAST)
static
ERL_NIF_TERM esock_getopt_lvl_sock_broadcast(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_BROADCAST);
}
#endif


#if defined(SO_DEBUG)
static
ERL_NIF_TERM esock_getopt_lvl_sock_debug(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_DEBUG);
}
#endif


#if defined(SO_DOMAIN)
static
ERL_NIF_TERM esock_getopt_lvl_sock_domain(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
    ERL_NIF_TERM result, reason;
    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:
            reason = MKT2(env, esock_atom_unknown, MKI(env, val));
            result = esock_make_error(env, reason);
            break;
        }
    }

    return result;
}
#endif


#if defined(SO_DONTROUTE)
static
ERL_NIF_TERM esock_getopt_lvl_sock_dontroute(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_DONTROUTE);
}
#endif


#if defined(SO_KEEPALIVE)
static
ERL_NIF_TERM esock_getopt_lvl_sock_keepalive(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_KEEPALIVE);
}
#endif


#if defined(SO_LINGER)
static
ERL_NIF_TERM esock_getopt_lvl_sock_linger(ErlNifEnv*       env,
                                          ESockDescriptor* 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 esock_getopt_lvl_sock_oobinline(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_OOBINLINE);
}
#endif


#if defined(SO_PASSCRED)
static
ERL_NIF_TERM esock_getopt_lvl_sock_passcred(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_PASSCRED);
}
#endif


#if defined(SO_PEEK_OFF)
static
ERL_NIF_TERM esock_getopt_lvl_sock_peek_off(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_PEEK_OFF);
}
#endif


#if defined(SO_PRIORITY)
static
ERL_NIF_TERM esock_getopt_lvl_sock_priority(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_PRIORITY);
}
#endif


#if defined(SO_PROTOCOL)
static
ERL_NIF_TERM esock_getopt_lvl_sock_protocol(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    ERL_NIF_TERM result, reason;
    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:
#if defined(AF_LOCAL)
            if (descP->domain == AF_LOCAL)
                result = esock_make_ok2(env, esock_atom_default);
            else
                result = esock_make_ok2(env, esock_atom_ip);
#else
            result = esock_make_ok2(env, esock_atom_ip);
#endif
            break;

        case IPPROTO_ICMP: /* control message protocol */
            result = esock_make_ok2(env, esock_atom_icmp);
            break;

        case IPPROTO_IGMP: /* group mgmt protocol */
            result = esock_make_ok2(env, esock_atom_igmp);
            break;

        case IPPROTO_IPIP: /* IP inside IP */
            result = esock_make_ok2(env, esock_atom_ipip);
            break;

        case IPPROTO_TCP: /* tcp */
            result = esock_make_ok2(env, esock_atom_tcp);
            break;

        case IPPROTO_EGP: /* exterior gateway protocol */
            result = esock_make_ok2(env, esock_atom_egp);
            break;

        case IPPROTO_UDP: /* user datagram protocol */
            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

        case IPPROTO_RAW: /* raw IP packet */
            result = esock_make_ok2(env, esock_atom_raw);
            break;

        default:
            reason = MKT2(env, esock_atom_unknown, MKI(env, val));
            result = esock_make_error(env, reason);
            break;
        }
    }

    return result;
}
#endif


#if defined(SO_RCVBUF)
static
ERL_NIF_TERM esock_getopt_lvl_sock_rcvbuf(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_RCVBUF);
}
#endif


#if defined(SO_RCVLOWAT)
static
ERL_NIF_TERM esock_getopt_lvl_sock_rcvlowat(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_RCVLOWAT);
}
#endif


#if defined(SO_RCVTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_getopt_lvl_sock_rcvtimeo(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_timeval_opt(env, descP, SOL_SOCKET, SO_RCVTIMEO);
}
#endif


#if defined(SO_REUSEADDR)
static
ERL_NIF_TERM esock_getopt_lvl_sock_reuseaddr(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEADDR);
}
#endif


#if defined(SO_REUSEPORT)
static
ERL_NIF_TERM esock_getopt_lvl_sock_reuseport(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_REUSEPORT);
}
#endif


#if defined(SO_SNDBUF)
static
ERL_NIF_TERM esock_getopt_lvl_sock_sndbuf(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_SNDBUF);
}
#endif


#if defined(SO_SNDLOWAT)
static
ERL_NIF_TERM esock_getopt_lvl_sock_sndlowat(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, SOL_SOCKET, SO_SNDLOWAT);
}
#endif


#if defined(SO_SNDTIMEO) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_getopt_lvl_sock_sndtimeo(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
    return esock_getopt_timeval_opt(env, descP, SOL_SOCKET, SO_SNDTIMEO);
}
#endif


#if defined(SO_TIMESTAMP)
static
ERL_NIF_TERM esock_getopt_lvl_sock_timestamp(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_SOCKET, SO_TIMESTAMP);
}
#endif


#if defined(SO_TYPE)
static
ERL_NIF_TERM esock_getopt_lvl_sock_type(ErlNifEnv*       env,
                                        ESockDescriptor* descP)
{
    ERL_NIF_TERM result, reason;
    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 SOCK_SEQPACKET
        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;
#ifdef SOCK_RDM
        case SOCK_RDM:
            result = esock_make_ok2(env, esock_atom_rdm);
            break;
#endif
        default:
            reason = MKT2(env, esock_atom_unknown, MKI(env, val));
            result = esock_make_error(env, reason);
            break;
        }
    }

    return result;
}
#endif


/* esock_getopt_lvl_ip - Level *IP* option(s)
 */
static
ERL_NIF_TERM esock_getopt_lvl_ip(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 int              eOpt)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
#if defined(IP_FREEBIND)
    case ESOCK_OPT_IP_FREEBIND:
        result = esock_getopt_lvl_ip_freebind(env, descP);
        break;
#endif

#if defined(IP_HDRINCL)
    case ESOCK_OPT_IP_HDRINCL:
        result = esock_getopt_lvl_ip_hdrincl(env, descP);
        break;
#endif

#if defined(IP_MINTTL)
    case ESOCK_OPT_IP_MINTTL:
        result = esock_getopt_lvl_ip_minttl(env, descP);
        break;
#endif

#if defined(IP_MTU)
    case ESOCK_OPT_IP_MTU:
        result = esock_getopt_lvl_ip_mtu(env, descP);
        break;
#endif

#if defined(IP_MTU_DISCOVER)
    case ESOCK_OPT_IP_MTU_DISCOVER:
        result = esock_getopt_lvl_ip_mtu_discover(env, descP);
        break;
#endif

#if defined(IP_MULTICAST_ALL)
    case ESOCK_OPT_IP_MULTICAST_ALL:
        result = esock_getopt_lvl_ip_multicast_all(env, descP);
        break;
#endif

#if defined(IP_MULTICAST_IF)
    case ESOCK_OPT_IP_MULTICAST_IF:
        result = esock_getopt_lvl_ip_multicast_if(env, descP);
        break;
#endif

#if defined(IP_MULTICAST_LOOP)
    case ESOCK_OPT_IP_MULTICAST_LOOP:
        result = esock_getopt_lvl_ip_multicast_loop(env, descP);
        break;
#endif

#if defined(IP_MULTICAST_TTL)
    case ESOCK_OPT_IP_MULTICAST_TTL:
        result = esock_getopt_lvl_ip_multicast_ttl(env, descP);
        break;
#endif

#if defined(IP_NODEFRAG)
    case ESOCK_OPT_IP_NODEFRAG:
        result = esock_getopt_lvl_ip_nodefrag(env, descP);
        break;
#endif

#if defined(IP_PKTINFO)
    case ESOCK_OPT_IP_PKTINFO:
        result = esock_getopt_lvl_ip_pktinfo(env, descP);
        break;
#endif

#if defined(IP_RECVDSTADDR)
    case ESOCK_OPT_IP_RECVDSTADDR:
        result = esock_getopt_lvl_ip_recvdstaddr(env, descP);
        break;
#endif

#if defined(IP_RECVERR)
    case ESOCK_OPT_IP_RECVERR:
        result = esock_getopt_lvl_ip_recverr(env, descP);
        break;
#endif

#if defined(IP_RECVIF)
    case ESOCK_OPT_IP_RECVIF:
        result = esock_getopt_lvl_ip_recvif(env, descP);
        break;
#endif

#if defined(IP_RECVOPTS)
    case ESOCK_OPT_IP_RECVOPTS:
        result = esock_getopt_lvl_ip_recvopts(env, descP);
        break;
#endif

#if defined(IP_RECVORIGDSTADDR)
    case ESOCK_OPT_IP_RECVORIGDSTADDR:
        result = esock_getopt_lvl_ip_recvorigdstaddr(env, descP);
        break;
#endif

#if defined(IP_RECVTOS)
    case ESOCK_OPT_IP_RECVTOS:
        result = esock_getopt_lvl_ip_recvtos(env, descP);
        break;
#endif

#if defined(IP_RECVTTL)
    case ESOCK_OPT_IP_RECVTTL:
        result = esock_getopt_lvl_ip_recvttl(env, descP);
        break;
#endif

#if defined(IP_RETOPTS)
    case ESOCK_OPT_IP_RETOPTS:
        result = esock_getopt_lvl_ip_retopts(env, descP);
        break;
#endif

#if defined(IP_ROUTER_ALERT)
    case ESOCK_OPT_IP_ROUTER_ALERT:
        result = esock_getopt_lvl_ip_router_alert(env, descP);
        break;
#endif

#if defined(IP_SENDSRCADDR)
    case ESOCK_OPT_IP_SENDSRCADDR:
        result = esock_getopt_lvl_ip_sendsrcaddr(env, descP);
        break;
#endif

#if defined(IP_TOS)
    case ESOCK_OPT_IP_TOS:
        result = esock_getopt_lvl_ip_tos(env, descP);
        break;
#endif

#if defined(IP_TRANSPARENT)
    case ESOCK_OPT_IP_TRANSPARENT:
        result = esock_getopt_lvl_ip_transparent(env, descP);
        break;
#endif

#if defined(IP_TTL)
    case ESOCK_OPT_IP_TTL:
        result = esock_getopt_lvl_ip_ttl(env, descP);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_getopt_lvl_ip_minttl - Level IP MINTTL option
 */
#if defined(IP_MINTTL)
static
ERL_NIF_TERM esock_getopt_lvl_ip_minttl(ErlNifEnv*       env,
                                        ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_int_opt(env, descP, level, IP_MINTTL);
}
#endif


/* esock_getopt_lvl_ip_freebind - Level IP FREEBIND option
 */
#if defined(IP_FREEBIND)
static
ERL_NIF_TERM esock_getopt_lvl_ip_freebind(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_FREEBIND);
}
#endif


/* esock_getopt_lvl_ip_hdrincl - Level IP HDRINCL option
 */
#if defined(IP_HDRINCL)
static
ERL_NIF_TERM esock_getopt_lvl_ip_hdrincl(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_HDRINCL);
}
#endif


/* esock_getopt_lvl_ip_mtu - Level IP MTU option
 */
#if defined(IP_MTU)
static
ERL_NIF_TERM esock_getopt_lvl_ip_mtu(ErlNifEnv*       env,
                                     ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_int_opt(env, descP, level, IP_MTU);
}
#endif


/* esock_getopt_lvl_ip_mtu_discover - Level IP MTU_DISCOVER option
 */
#if defined(IP_MTU_DISCOVER)
static
ERL_NIF_TERM esock_getopt_lvl_ip_mtu_discover(ErlNifEnv*       env,
                                              ESockDescriptor* 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


/* esock_getopt_lvl_ip_multicast_all - Level IP MULTICAST_ALL option
 */
#if defined(IP_MULTICAST_ALL)
static
ERL_NIF_TERM esock_getopt_lvl_ip_multicast_all(ErlNifEnv*       env,
                                               ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_MULTICAST_ALL);
}
#endif


/* esock_getopt_lvl_ip_multicast_if - Level IP MULTICAST_IF option
 */
#if defined(IP_MULTICAST_IF)
static
ERL_NIF_TERM esock_getopt_lvl_ip_multicast_if(ErlNifEnv*       env,
                                              ESockDescriptor* 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


/* esock_getopt_lvl_ip_multicast_loop - Level IP MULTICAST_LOOP option
 */
#if defined(IP_MULTICAST_LOOP)
static
ERL_NIF_TERM esock_getopt_lvl_ip_multicast_loop(ErlNifEnv*       env,
                                                ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_MULTICAST_LOOP);
}
#endif


/* esock_getopt_lvl_ip_multicast_ttl - Level IP MULTICAST_TTL option
 */
#if defined(IP_MULTICAST_TTL)
static
ERL_NIF_TERM esock_getopt_lvl_ip_multicast_ttl(ErlNifEnv*       env,
                                               ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_int_opt(env, descP, level, IP_MULTICAST_TTL);
}
#endif


/* esock_getopt_lvl_ip_nodefrag - Level IP NODEFRAG option
 */
#if defined(IP_NODEFRAG)
static
ERL_NIF_TERM esock_getopt_lvl_ip_nodefrag(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_NODEFRAG);
}
#endif


/* esock_getopt_lvl_ip_pktinfo - Level IP PKTINFO option
 */
#if defined(IP_PKTINFO)
static
ERL_NIF_TERM esock_getopt_lvl_ip_pktinfo(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_PKTINFO);
}
#endif


/* esock_getopt_lvl_ip_recvtos - Level IP RECVTOS option
 */
#if defined(IP_RECVTOS)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recvtos(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVTOS);
}
#endif


/* esock_getopt_lvl_ip_recvdstaddr - Level IP RECVDSTADDR option
 */
#if defined(IP_RECVDSTADDR)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recvdstaddr(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVDSTADDR);
}
#endif


/* esock_getopt_lvl_ip_recverr - Level IP RECVERR option
 */
#if defined(IP_RECVERR)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recverr(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVERR);
}
#endif


/* esock_getopt_lvl_ip_recvif - Level IP RECVIF option
 */
#if defined(IP_RECVIF)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recvif(ErlNifEnv*       env,
                                        ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVIF);
}
#endif


/* esock_getopt_lvl_ip_recvopt - Level IP RECVOPTS option
 */
#if defined(IP_RECVOPTS)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recvopts(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVOPTS);
}
#endif


/* esock_getopt_lvl_ip_recvorigdstaddr - Level IP RECVORIGDSTADDR option
 */
#if defined(IP_RECVORIGDSTADDR)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recvorigdstaddr(ErlNifEnv*       env,
                                                 ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVORIGDSTADDR);
}
#endif


/* esock_getopt_lvl_ip_recvttl - Level IP RECVTTL option
 */
#if defined(IP_RECVTTL)
static
ERL_NIF_TERM esock_getopt_lvl_ip_recvttl(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RECVTTL);
}
#endif


/* esock_getopt_lvl_ip_retopts - Level IP RETOPTS option
 */
#if defined(IP_RETOPTS)
static
ERL_NIF_TERM esock_getopt_lvl_ip_retopts(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_RETOPTS);
}
#endif


/* esock_getopt_lvl_ip_router_alert - Level IP ROUTER_ALERT option
 */
#if defined(IP_ROUTER_ALERT)
static
ERL_NIF_TERM esock_getopt_lvl_ip_router_alert(ErlNifEnv*       env,
                                              ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_int_opt(env, descP, level, IP_ROUTER_ALERT);
}
#endif


/* esock_getopt_lvl_ip_sendsrcaddr - Level IP SENDSRCADDR option
 */
#if defined(IP_SENDSRCADDR)
static
ERL_NIF_TERM esock_getopt_lvl_ip_sendsrcaddr(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_SENDSRCADDR);
}
#endif


/* esock_getopt_lvl_ip_tos - Level IP TOS option
 */
#if defined(IP_TOS)
static
ERL_NIF_TERM esock_getopt_lvl_ip_tos(ErlNifEnv*       env,
                                     ESockDescriptor* 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


/* esock_getopt_lvl_ip_transparent - Level IP TRANSPARENT option
 */
#if defined(IP_TRANSPARENT)
static
ERL_NIF_TERM esock_getopt_lvl_ip_transparent(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_bool_opt(env, descP, level, IP_TRANSPARENT);
}
#endif



/* esock_getopt_lvl_ip_ttl - Level IP TTL option
 */
#if defined(IP_TTL)
static
ERL_NIF_TERM esock_getopt_lvl_ip_ttl(ErlNifEnv*       env,
                                     ESockDescriptor* descP)
{
#if defined(SOL_IP)
    int level = SOL_IP;
#else
    int level = IPPROTO_IP;
#endif

    return esock_getopt_int_opt(env, descP, level, IP_TTL);
}
#endif



/* esock_getopt_lvl_ipv6 - Level *IPv6* option(s)
 */
#if defined(HAVE_IPV6)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   int              eOpt)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
#if defined(IPV6_AUTHHDR)
    case ESOCK_OPT_IPV6_AUTHHDR:
        result = esock_getopt_lvl_ipv6_authhdr(env, descP);
        break;
#endif

#if defined(IPV6_DSTOPTS)
    case ESOCK_OPT_IPV6_DSTOPTS:
        result = esock_getopt_lvl_ipv6_dstopts(env, descP);
        break;
#endif

#if defined(IPV6_FLOWINFO)
    case ESOCK_OPT_IPV6_FLOWINFO:
        result = esock_getopt_lvl_ipv6_flowinfo(env, descP);
        break;
#endif

#if defined(IPV6_HOPLIMIT)
    case ESOCK_OPT_IPV6_HOPLIMIT:
      result = esock_getopt_lvl_ipv6_hoplimit(env, descP);
      break;
#endif

#if defined(IPV6_HOPOPTS)
    case ESOCK_OPT_IPV6_HOPOPTS:
        result = esock_getopt_lvl_ipv6_hopopts(env, descP);
        break;
#endif

#if defined(IPV6_MTU)
    case ESOCK_OPT_IPV6_MTU:
        result = esock_getopt_lvl_ipv6_mtu(env, descP);
        break;
#endif

#if defined(IPV6_MTU_DISCOVER)
    case ESOCK_OPT_IPV6_MTU_DISCOVER:
        result = esock_getopt_lvl_ipv6_mtu_discover(env, descP);
        break;
#endif

#if defined(IPV6_MULTICAST_HOPS)
    case ESOCK_OPT_IPV6_MULTICAST_HOPS:
        result = esock_getopt_lvl_ipv6_multicast_hops(env, descP);
        break;
#endif

#if defined(IPV6_MULTICAST_IF)
    case ESOCK_OPT_IPV6_MULTICAST_IF:
        result = esock_getopt_lvl_ipv6_multicast_if(env, descP);
        break;
#endif

#if defined(IPV6_MULTICAST_LOOP)
    case ESOCK_OPT_IPV6_MULTICAST_LOOP:
        result = esock_getopt_lvl_ipv6_multicast_loop(env, descP);
        break;
#endif

#if defined(IPV6_RECVERR)
    case ESOCK_OPT_IPV6_RECVERR:
        result = esock_getopt_lvl_ipv6_recverr(env, descP);
        break;
#endif

#if defined(IPV6_RECVHOPLIMIT)
    case ESOCK_OPT_IPV6_RECVHOPLIMIT:
      result = esock_getopt_lvl_ipv6_recvhoplimit(env, descP);
      break;
#endif

#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
    case ESOCK_OPT_IPV6_RECVPKTINFO:
      result = esock_getopt_lvl_ipv6_recvpktinfo(env, descP);
      break;
#endif

#if defined(IPV6_RECVTCLASS)
    case ESOCK_OPT_IPV6_RECVTCLASS:
      result = esock_getopt_lvl_ipv6_recvtclass(env, descP);
      break;
#endif

#if defined(IPV6_ROUTER_ALERT)
    case ESOCK_OPT_IPV6_ROUTER_ALERT:
        result = esock_getopt_lvl_ipv6_router_alert(env, descP);
        break;
#endif

#if defined(IPV6_RTHDR)
    case ESOCK_OPT_IPV6_RTHDR:
        result = esock_getopt_lvl_ipv6_rthdr(env, descP);
        break;
#endif

#if defined(IPV6_TCLASS)
    case ESOCK_OPT_IPV6_TCLASS:
        result = esock_getopt_lvl_ipv6_tclass(env, descP);
        break;
#endif

#if defined(IPV6_UNICAST_HOPS)
    case ESOCK_OPT_IPV6_UNICAST_HOPS:
        result = esock_getopt_lvl_ipv6_unicast_hops(env, descP);
        break;
#endif

#if defined(IPV6_V6ONLY)
    case ESOCK_OPT_IPV6_V6ONLY:
        result = esock_getopt_lvl_ipv6_v6only(env, descP);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


#if defined(IPV6_AUTHHDR)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_authhdr(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, SOL_IPV6, IPV6_AUTHHDR);
}
#endif


#if defined(IPV6_DSTOPTS)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_dstopts(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif
    return esock_getopt_bool_opt(env, descP, level, IPV6_DSTOPTS);
}
#endif


#if defined(IPV6_FLOWINFO)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_flowinfo(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_bool_opt(env, descP, level, IPV6_FLOWINFO);
}
#endif


#if defined(IPV6_HOPLIMIT)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_hoplimit(ErlNifEnv*       env,
                                            ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
  int level = SOL_IPV6;
#else
  int level = IPPROTO_IPV6;
#endif

  return esock_getopt_bool_opt(env, descP, level, IPV6_HOPLIMIT);
}
#endif


#if defined(IPV6_HOPOPTS)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_hopopts(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_bool_opt(env, descP, level, IPV6_HOPOPTS);
}
#endif


#if defined(IPV6_MTU)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_mtu(ErlNifEnv*       env,
                                       ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_int_opt(env, descP, level, IPV6_MTU);
}
#endif


/* esock_getopt_lvl_ipv6_mtu_discover - Level IPv6 MTU_DISCOVER option
 */
#if defined(IPV6_MTU_DISCOVER)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_mtu_discover(ErlNifEnv*       env,
                                                ESockDescriptor* 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 esock_getopt_lvl_ipv6_multicast_hops(ErlNifEnv*       env,
                                                  ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_int_opt(env, descP, level, IPV6_MULTICAST_HOPS);
}
#endif


#if defined(IPV6_MULTICAST_IF)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_multicast_if(ErlNifEnv*       env,
                                                ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_int_opt(env, descP, level, IPV6_MULTICAST_IF);
}
#endif


#if defined(IPV6_MULTICAST_LOOP)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_multicast_loop(ErlNifEnv*       env,
                                                  ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_bool_opt(env, descP, level, IPV6_MULTICAST_LOOP);
}
#endif


#if defined(IPV6_RECVERR)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_recverr(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_bool_opt(env, descP, level, IPV6_RECVERR);
}
#endif


#if defined(IPV6_RECVHOPLIMIT)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_recvhoplimit(ErlNifEnv*       env,
						ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
  int level = SOL_IPV6;
#else
  int level = IPPROTO_IPV6;
#endif

  return esock_getopt_bool_opt(env, descP, level, IPV6_RECVHOPLIMIT);
}
#endif


#if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_recvpktinfo(ErlNifEnv*       env,
                                               ESockDescriptor* 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 esock_getopt_bool_opt(env, descP, level, opt);
}
#endif


#if defined(IPV6_RECVTCLASS)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_recvtclass(ErlNifEnv*       env,
                                              ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif
    int opt   = IPV6_RECVTCLASS;

    return esock_getopt_bool_opt(env, descP, level, opt);
}
#endif


#if defined(IPV6_ROUTER_ALERT)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_router_alert(ErlNifEnv*       env,
                                                ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_int_opt(env, descP, level, IPV6_ROUTER_ALERT);
}
#endif


#if defined(IPV6_RTHDR)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_rthdr(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_bool_opt(env, descP, level, IPV6_RTHDR);
}
#endif


#if defined(IPV6_TCLASS)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_tclass(ErlNifEnv*       env,
                                              ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif
    int opt   = IPV6_TCLASS;

    return esock_getopt_bool_opt(env, descP, level, opt);
}
#endif


#if defined(IPV6_UNICAST_HOPS)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_unicast_hops(ErlNifEnv*       env,
                                                ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_int_opt(env, descP, level, IPV6_UNICAST_HOPS);
}
#endif


#if defined(IPV6_V6ONLY)
static
ERL_NIF_TERM esock_getopt_lvl_ipv6_v6only(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
#if defined(SOL_IPV6)
    int level = SOL_IPV6;
#else
    int level = IPPROTO_IPV6;
#endif

    return esock_getopt_bool_opt(env, descP, level, IPV6_V6ONLY);
}
#endif


#endif // defined(HAVE_IPV6)



/* esock_getopt_lvl_tcp - Level *TCP* option(s)
 */
static
ERL_NIF_TERM esock_getopt_lvl_tcp(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  int              eOpt)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
#if defined(TCP_CONGESTION)
    case ESOCK_OPT_TCP_CONGESTION:
        result = esock_getopt_lvl_tcp_congestion(env, descP);
        break;
#endif

#if defined(TCP_CORK)
    case ESOCK_OPT_TCP_CORK:
        result = esock_getopt_lvl_tcp_cork(env, descP);
        break;
#endif

#if defined(TCP_MAXSEG)
    case ESOCK_OPT_TCP_MAXSEG:
        result = esock_getopt_lvl_tcp_maxseg(env, descP);
        break;
#endif

#if defined(TCP_NODELAY)
    case ESOCK_OPT_TCP_NODELAY:
        result = esock_getopt_lvl_tcp_nodelay(env, descP);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_getopt_lvl_tcp_congestion - Level TCP CONGESTION option
 */
#if defined(TCP_CONGESTION)
static
ERL_NIF_TERM esock_getopt_lvl_tcp_congestion(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    int max = ESOCK_OPT_TCP_CONGESTION_NAME_MAX+1;

    return esock_getopt_str_opt(env, descP, IPPROTO_TCP, TCP_CONGESTION, max);
}
#endif


/* esock_getopt_lvl_tcp_cork - Level TCP CORK option
 */
#if defined(TCP_CORK)
static
ERL_NIF_TERM esock_getopt_lvl_tcp_cork(ErlNifEnv*       env,
                                       ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, IPPROTO_TCP, TCP_CORK);
}
#endif

/* esock_getopt_lvl_tcp_maxseg - Level TCP MAXSEG option
 */
#if defined(TCP_MAXSEG)
static
ERL_NIF_TERM esock_getopt_lvl_tcp_maxseg(ErlNifEnv*       env,
                                         ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, IPPROTO_TCP, TCP_MAXSEG);
}
#endif


/* esock_getopt_lvl_tcp_nodelay - Level TCP NODELAY option
 */
#if defined(TCP_NODELAY)
static
ERL_NIF_TERM esock_getopt_lvl_tcp_nodelay(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, IPPROTO_TCP, TCP_NODELAY);
}
#endif



/* esock_getopt_lvl_udp - Level *UDP* option(s)
 */
static
ERL_NIF_TERM esock_getopt_lvl_udp(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  int              eOpt)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
#if defined(UDP_CORK)
    case ESOCK_OPT_UDP_CORK:
        result = esock_getopt_lvl_udp_cork(env, descP);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_getopt_lvl_udp_cork - Level UDP CORK option
 */
#if defined(UDP_CORK)
static
ERL_NIF_TERM esock_getopt_lvl_udp_cork(ErlNifEnv*       env,
                                       ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, IPPROTO_UDP, UDP_CORK);
}
#endif



/* esock_getopt_lvl_sctp - Level *SCTP* option(s)
 */
#if defined(HAVE_SCTP)
static
ERL_NIF_TERM esock_getopt_lvl_sctp(ErlNifEnv*       env,
                                   ESockDescriptor* descP,
                                   int              eOpt)
{
    ERL_NIF_TERM result;

    switch (eOpt) {
#if defined(SCTP_ASSOCINFO)
    case ESOCK_OPT_SCTP_ASSOCINFO:
        result = esock_getopt_lvl_sctp_associnfo(env, descP);
        break;
#endif

#if defined(SCTP_AUTOCLOSE)
    case ESOCK_OPT_SCTP_AUTOCLOSE:
        result = esock_getopt_lvl_sctp_autoclose(env, descP);
        break;
#endif

#if defined(SCTP_DISABLE_FRAGMENTS)
    case ESOCK_OPT_SCTP_DISABLE_FRAGMENTS:
        result = esock_getopt_lvl_sctp_disable_fragments(env, descP);
        break;
#endif

#if defined(SCTP_INITMSG)
    case ESOCK_OPT_SCTP_INITMSG:
        result = esock_getopt_lvl_sctp_initmsg(env, descP);
        break;
#endif

#if defined(SCTP_MAXSEG)
    case ESOCK_OPT_SCTP_MAXSEG:
        result = esock_getopt_lvl_sctp_maxseg(env, descP);
        break;
#endif

#if defined(SCTP_NODELAY)
    case ESOCK_OPT_SCTP_NODELAY:
        result = esock_getopt_lvl_sctp_nodelay(env, descP);
        break;
#endif

#if defined(SCTP_RTOINFO)
    case ESOCK_OPT_SCTP_RTOINFO:
        result = esock_getopt_lvl_sctp_rtoinfo(env, descP);
        break;
#endif

    default:
        result = esock_make_error(env, esock_atom_einval);
        break;
    }

    return result;
}


/* esock_getopt_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? Or just leave
 * it for the OS?
 *
 * </KOLLA>
 */
#if defined(SCTP_ASSOCINFO)
static
ERL_NIF_TERM esock_getopt_lvl_sctp_associnfo(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    ERL_NIF_TERM            result;
    struct sctp_assocparams val;
    SOCKOPTLEN_T            valSz = sizeof(val);
    int                     res;

    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);
    }

    return result;
}
#endif


/* esock_getopt_lvl_sctp_autoclose - Level SCTP AUTOCLOSE option
 */
#if defined(SCTP_AUTOCLOSE)
static
ERL_NIF_TERM esock_getopt_lvl_sctp_autoclose(ErlNifEnv*       env,
                                             ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_AUTOCLOSE);
}
#endif


/* esock_getopt_lvl_sctp_disable_fragments - Level SCTP DISABLE:FRAGMENTS option
 */
#if defined(SCTP_DISABLE_FRAGMENTS)
static
ERL_NIF_TERM esock_getopt_lvl_sctp_disable_fragments(ErlNifEnv*       env,
                                                     ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP,
                                 IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS);
}
#endif


/* esock_getopt_lvl_sctp_initmsg - Level SCTP INITMSG option
 *
 */
#if defined(SCTP_INITMSG)
static
ERL_NIF_TERM esock_getopt_lvl_sctp_initmsg(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
    ERL_NIF_TERM        result;
    struct sctp_initmsg val;
    SOCKOPTLEN_T        valSz = sizeof(val);
    int                 res;

    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);
    }

    return result;
}
#endif


/* esock_getopt_lvl_sctp_maxseg - Level SCTP MAXSEG option
 */
#if defined(SCTP_MAXSEG)
static
ERL_NIF_TERM esock_getopt_lvl_sctp_maxseg(ErlNifEnv*       env,
                                          ESockDescriptor* descP)
{
    return esock_getopt_int_opt(env, descP, IPPROTO_SCTP, SCTP_MAXSEG);
}
#endif


/* esock_getopt_lvl_sctp_nodelay - Level SCTP NODELAY option
 */
#if defined(SCTP_NODELAY)
static
ERL_NIF_TERM esock_getopt_lvl_sctp_nodelay(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
    return esock_getopt_bool_opt(env, descP, IPPROTO_SCTP, SCTP_NODELAY);
}
#endif


/* esock_getopt_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 esock_getopt_lvl_sctp_rtoinfo(ErlNifEnv*       env,
                                           ESockDescriptor* descP)
{
    ERL_NIF_TERM        result;
    struct sctp_rtoinfo val;
    SOCKOPTLEN_T        valSz = sizeof(val);
    int                 res;

    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);
    }

    return result;
}
#endif



#endif // defined(HAVE_SCTP)



/* esock_getopt_bool_opt - get an (integer) bool option
 */
static
ERL_NIF_TERM esock_getopt_bool_opt(ErlNifEnv*       env,
                                   ESockDescriptor* 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 {
        ERL_NIF_TERM bval = ((val) ? atom_true : atom_false);

        result = esock_make_ok2(env, bval);
    }
    return result;
}


/* esock_getopt_int_opt - get an integer option
 */
static
ERL_NIF_TERM esock_getopt_int_opt(ErlNifEnv*       env,
                                  ESockDescriptor* 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;
}



/* esock_getopt_timeval_opt - get an timeval option
 */
#if (defined(SO_RCVTIMEO) || defined(SO_SNDTIMEO)) && defined(ESOCK_USE_RCVSNDTIMEO)
static
ERL_NIF_TERM esock_getopt_timeval_opt(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      int              level,
                                      int              opt)
{
    ERL_NIF_TERM   result;
    struct timeval val;
    SOCKOPTLEN_T   valSz = sizeof(val);
    int            res;

    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);
    }

    return result;
}
#endif



/* esock_getopt_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 valSz variable.
 *
 * Its possible that some strings are NULL-terminated,
 * but there is no way this *general* funcrtion can assume that.
 * So leave it to the caller to deal with that.
 */
#if defined(USE_GETOPT_STR_OPT)
static
ERL_NIF_TERM esock_getopt_str_opt(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  int              level,
                                  int              opt,
                                  int              max)
{
    ERL_NIF_TERM result;
    char*        val   = MALLOC(max);
    SOCKOPTLEN_T valSz = max;
    int          res;

    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);
    }
    FREE(val);

    return result;
}
#endif // if defined(USE_GETOPT_STR_OPT)
#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, "notsup"));
#else
    ESockDescriptor* 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) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
        return enif_make_badarg(env);
    }

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_sockname(%T) {%d}"
            "\r\n", argv[0], descP->sock) );

    res = esock_sockname(env, descP);

    SSDBG( descP,
           ("SOCKET", "nif_sockname(%T) {%d} -> done with res = %T\r\n",
            argv[0], descP->sock, res) );

    MUNLOCK(descP->readMtx);

    return res;
#endif // if defined(__WIN32__)
}



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_sockname(ErlNifEnv*       env,
                            ESockDescriptor* descP)
{
    ESockAddress  sa;
    ESockAddress* saP = &sa;
    unsigned int  sz  = sizeof(ESockAddress);

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);
    
    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, "notsup"));
#else
    ESockDescriptor* 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) ||
        !ESOCK_GET_RESOURCE(env, argv[0], (void**) &descP)) {
        return enif_make_badarg(env);
    }

    if (! IS_OPEN(descP))
        return esock_make_error(env, atom_closed);
    
    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET", "nif_peername(%T) {%d}"
            "\r\n", argv[0], descP->sock) );

    res = esock_peername(env, descP);

    SSDBG( descP,
           ("SOCKET", "nif_peername(%T) {%d} -> done with res = %T\r\n",
            argv[0], descP->sock, res) );

    MUNLOCK(descP->readMtx);

    return res;
#endif // if defined(__WIN32__)
}



#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_peername(ErlNifEnv*       env,
                            ESockDescriptor* descP)
{
    ESockAddress  sa;
    ESockAddress* saP = &sa;
    unsigned int  sz  = sizeof(ESockAddress);

    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, "notsup"));
#else
    ESockDescriptor* descP;
    ERL_NIF_TERM     op, sockRef, opRef;

    SGDBG( ("SOCKET", "nif_cancel -> entry with argc: %d\r\n", argc) );

    /* Extract arguments and perform preliminary validation */

    sockRef = argv[0];
    if ((argc != 3) ||
        !ESOCK_GET_RESOURCE(env, sockRef, (void**) &descP)) {
        return enif_make_badarg(env);
    }
    op    = argv[1];
    opRef = argv[2];
        
    return esock_cancel(env, descP, op, sockRef, opRef);

#endif // if !defined(__WIN32__)
}


#if !defined(__WIN32__)
static
ERL_NIF_TERM esock_cancel(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          ERL_NIF_TERM     op,
                          ERL_NIF_TERM     sockRef,
                          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 esock_cancel_connect(env, descP, opRef);
    } else if (COMPARE(op, esock_atom_accept) == 0) {
        return esock_cancel_accept(env, descP, sockRef, opRef);
    } else if (COMPARE(op, esock_atom_send) == 0) {
        return esock_cancel_send(env, descP, sockRef, opRef);
    } else if (COMPARE(op, esock_atom_sendto) == 0) {
        return esock_cancel_send(env, descP, sockRef, opRef);
    } else if (COMPARE(op, esock_atom_sendmsg) == 0) {
        return esock_cancel_send(env, descP, sockRef, opRef);
    } else if (COMPARE(op, esock_atom_recv) == 0) {
        return esock_cancel_recv(env, descP, sockRef, opRef);
    } else if (COMPARE(op, esock_atom_recvfrom) == 0) {
        return esock_cancel_recv(env, descP, sockRef, opRef);
    } else if (COMPARE(op, esock_atom_recvmsg) == 0) {
        return esock_cancel_recv(env, descP, sockRef, opRef);
    } else {
        ERL_NIF_TERM res;

        MLOCK(descP->readMtx);
        MLOCK(descP->writeMtx);

        SSDBG( descP,
               ("SOCKET", "esock_cancel(%T), {%d,%s,0x%X} -> einval"
                "\r\n",
                sockRef,  descP->sock, B2S(descP->closing),
                descP->readState | descP->writeState) );

        if (! IS_OPEN(descP))
            res = esock_make_error(env, atom_closed);
        else
            res = esock_make_error(env, esock_atom_einval);

        MUNLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);

        return res;
    }
}



/* *** esock_cancel_connect ***
 *
 *
 */
static
ERL_NIF_TERM esock_cancel_connect(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     opRef)
{
    ERL_NIF_TERM res;

    MLOCK(descP->writeMtx);

    if (! IS_OPEN(descP))
        res = esock_make_error(env, atom_closed);
    else
        res = esock_cancel_write_select(env, descP, opRef);

    SSDBG( descP,
           ("SOCKET",
            "esock_cancel_connect {%d,%s,0x%X} ->"
            "\r\n   opRef: %T"
            "\r\n   res: %T"
            "\r\n",
            descP->sock, B2S(descP->closing), descP->writeState,
            opRef, res) );

    MUNLOCK(descP->writeMtx);

    return res;
}


/* *** esock_cancel_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 esock_cancel_accept(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 ERL_NIF_TERM     sockRef,
                                 ERL_NIF_TERM     opRef)
{
    ERL_NIF_TERM res;

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET",
            "esock_cancel_accept(%T), {%d,%s,0x%X} ->"
            "\r\n   opRef: %T"
            "\r\n   %s"
            "\r\n",
            sockRef,  descP->sock, B2S(descP->closing), descP->readState,
            opRef,
            ((descP->currentAcceptorP == NULL) ?
             "without acceptor" : "with acceptor")) );

    if (! IS_OPEN(descP)) {

        res = esock_make_error(env, atom_closed);

    } else {

        if (descP->currentAcceptorP != NULL) {
            if (COMPARE(opRef, descP->currentAcceptor.ref) == 0) {
                res = esock_cancel_accept_current(env, descP, sockRef);
            } else {
                res = esock_cancel_accept_waiting(env, descP, opRef);
            }
        } else {
            /* Or badarg? */
            res = esock_make_error(env, esock_atom_einval);
        }
    }

    SSDBG( descP,
           ("SOCKET", "esock_cancel_accept(%T) -> done with result:"
            "\r\n   %T"
            "\r\n", sockRef, res) );

    MUNLOCK(descP->readMtx);

    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 esock_cancel_accept_current(ErlNifEnv*       env,
                                         ESockDescriptor* descP,
                                         ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM res;

    DEMONP("esock_cancel_accept_current -> current acceptor",
           env, descP, &descP->currentAcceptor.mon);
    res = esock_cancel_read_select(env, descP, descP->currentAcceptor.ref);

    SSDBG( descP,
           ("SOCKET",
            "esock_cancel_accept_current(%T) {%d} -> cancel res: %T"
            "\r\n", sockRef, descP->sock, res) );

    if (!activate_next_acceptor(env, descP, sockRef)) {

        SSDBG( descP,
               ("SOCKET",
                "esock_cancel_accept_current(%T) {%d} -> no more acceptors\r\n",
                sockRef, descP->sock) );

        descP->readState &= ~ESOCK_STATE_ACCEPTING;

        descP->currentAcceptorP    = NULL;
    }

    return res;
}


/* These processes have not performed a select, so we can simply
 * remove them from the acceptor queue.
 */
static
ERL_NIF_TERM esock_cancel_accept_waiting(ErlNifEnv*       env,
                                         ESockDescriptor* 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? But we have a mutex look... */
        return esock_make_error(env, esock_atom_not_found);
    }
}



/* *** esock_cancel_send ***
 *
 * Cancel a send operation.
 * Its either the current writer or one of the waiting writers.
 */
static
ERL_NIF_TERM esock_cancel_send(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ERL_NIF_TERM     sockRef,
                               ERL_NIF_TERM     opRef)
{
    ERL_NIF_TERM res;

    MLOCK(descP->writeMtx);

    SSDBG( descP,
           ("SOCKET",
            "esock_cancel_send(%T), {%d,%s,0x%X} -> entry with"
            "\r\n   opRef: %T"
            "\r\n   %s"
            "\r\n",
            sockRef,  descP->sock, B2S(descP->closing), descP->writeState,
            opRef,
            ((descP->currentWriterP == NULL) ? "without writer" : "with writer")) );

    if (! IS_OPEN(descP)) {

        res = esock_make_error(env, atom_closed);

    } else {

        if (descP->currentWriterP != NULL) {
            if (COMPARE(opRef, descP->currentWriter.ref) == 0) {
                res = esock_cancel_send_current(env, descP, sockRef);
            } else {
                res = esock_cancel_send_waiting(env, descP, opRef);
            }
        } else {
            /* Or badarg? */
            res =  esock_make_error(env, esock_atom_einval);
        }
    }

    SSDBG( descP,
           ("SOCKET", "esock_cancel_send(%T) {%d} -> done with result:"
            "\r\n   %T"
            "\r\n", sockRef, descP->sock, res) );

    MUNLOCK(descP->writeMtx);

    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 esock_cancel_send_current(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM res;

    DEMONP("esock_cancel_send_current -> current writer",
           env, descP, &descP->currentWriter.mon);
    res = esock_cancel_write_select(env, descP, descP->currentWriter.ref);

    SSDBG( descP,
           ("SOCKET", "esock_cancel_send_current(%T) {%d} -> cancel res: %T"
            "\r\n", sockRef, descP->sock, res) );

    if (!activate_next_writer(env, descP, sockRef)) {
        SSDBG( descP,
               ("SOCKET",
                "esock_cancel_send_current(%T) {%d} -> no more writers"
                "\r\n", sockRef, descP->sock) );

        descP->currentWriterP = NULL;
    }

    return res;
}


/* These processes have not performed a select, so we can simply
 * remove them from the writer queue.
 */
static
ERL_NIF_TERM esock_cancel_send_waiting(ErlNifEnv*       env,
                                       ESockDescriptor* 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);
    }
}



/* *** esock_cancel_recv ***
 *
 * Cancel a read operation.
 * Its either the current reader or one of the waiting readers.
 */
static
ERL_NIF_TERM esock_cancel_recv(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ERL_NIF_TERM     sockRef,
                               ERL_NIF_TERM     opRef)
{
    ERL_NIF_TERM res;

    MLOCK(descP->readMtx);

    SSDBG( descP,
           ("SOCKET",
            "esock_cancel_recv(%T), {%d,%s,0x%X} -> entry with"
            "\r\n   opRef: %T"
            "\r\n   %s"
            "\r\n",
            sockRef,  descP->sock, B2S(descP->closing), descP->readState,
            opRef,
            ((descP->currentReaderP == NULL) ? "without reader" : "with reader")) );

    if (! IS_OPEN(descP)) {

        res = esock_make_error(env, atom_closed);

    } else {

        if (descP->currentReaderP != NULL) {
            if (COMPARE(opRef, descP->currentReader.ref) == 0) {
                res = esock_cancel_recv_current(env, descP, sockRef);
            } else {
                res = esock_cancel_recv_waiting(env, descP, opRef);
            }
        } else {
            /* Or badarg? */
            res =  esock_make_error(env, esock_atom_einval);
        }
    }

    SSDBG( descP,
           ("SOCKET", "esock_cancel_recv(%T) {%d} -> done with result:"
            "\r\n   %T"
            "\r\n", sockRef, descP->sock, res) );

    MUNLOCK(descP->readMtx);

    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 esock_cancel_recv_current(ErlNifEnv*       env,
                                       ESockDescriptor* descP,
                                       ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM res;

    DEMONP("esock_cancel_recv_current -> current reader",
           env, descP, &descP->currentReader.mon);
    res = esock_cancel_read_select(env, descP, descP->currentReader.ref);

    SSDBG( descP,
           ("SOCKET", "esock_cancel_recv_current(%T) {%d} -> cancel res: %T"
            "\r\n", sockRef, descP->sock, res) );

    if (!activate_next_reader(env, descP, sockRef)) {
        SSDBG( descP,
               ("SOCKET",
                "esock_cancel_recv_current(%T) {%d} -> no more readers"
                "\r\n", sockRef, descP->sock) );

        descP->currentReaderP = NULL;
    }

    return res;
}


/* These processes have not performed a select, so we can simply
 * remove them from the reader queue.
 */
static
ERL_NIF_TERM esock_cancel_recv_waiting(ErlNifEnv*       env,
                                       ESockDescriptor* 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 esock_cancel_read_select(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     opRef)
{
    return esock_cancel_mode_select(env, descP, opRef,
                                    ERL_NIF_SELECT_READ,
                                    ERL_NIF_SELECT_READ_CANCELLED);
}


static
ERL_NIF_TERM esock_cancel_write_select(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ERL_NIF_TERM     opRef)
{
    return esock_cancel_mode_select(env, descP, opRef,
                                    ERL_NIF_SELECT_WRITE,
                                    ERL_NIF_SELECT_WRITE_CANCELLED);
}


static
ERL_NIF_TERM esock_cancel_mode_select(ErlNifEnv*       env,
                                      ESockDescriptor* descP,
                                      ERL_NIF_TERM     opRef,
                                      int              smode,
                                      int              rmode)
{
    /* Assumes cancelling only one mode */

    int selectRes = esock_select_cancel(env, descP->sock, smode, descP);

    if (selectRes >= 0) {
        /* Success */
        if ((selectRes & rmode) != 0) {
            /* Was cancelled */
            return esock_atom_ok;
        } else {
            /* Has already sent the message */
            return esock_make_error(env, esock_atom_select_sent);
        }
    } else {
        /* Stopped? */
        SSDBG( descP,
               ("SOCKET",
                "esock_cancel_mode_select {%d} -> failed: %d (0x%lX)"
                "\r\n", descP->sock, 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 (current writer), 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,
                            ESockDescriptor* 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);
            SSDBG( descP,
                   ("SOCKET",
                    "send_check_writer {%d} -> exself"
                    "\r\n   ref: %T"
                    "\r\n", descP->sock, ref) );

            return FALSE;
        }

        if (COMPARE_PIDS(&descP->currentWriter.pid, &caller) != 0) {
            /* Not the "current writer", so (maybe) push onto queue */

            SSDBG( descP,
                   ("SOCKET",
                    "send_check_writer {%d} -> not (current) writer"
                    "\r\n   ref: %T"
                    "\r\n", descP->sock, ref) );

            if (!writer_search4pid(env, descP, &caller))
                *checkResult = writer_push(env, descP, caller, ref);
            else
                *checkResult = esock_make_error_errno(env, EALREADY);
            
            SSDBG( descP,
                   ("SOCKET",
                    "send_check_writer {%d} -> queue (push) result: %T\r\n"
                    "\r\n   ref: %T"
                    "\r\n", descP->sock, *checkResult, ref) );
            
            return FALSE;
        }
    }

    // Does not actually matter in this case, but ...
    *checkResult = esock_atom_ok;

    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,
                               ESockDescriptor* descP,
                               ssize_t          written,
                               ssize_t          dataSize,
                               int              saveErrno,
                               ERL_NIF_TERM     sockRef,
                               ERL_NIF_TERM     sendRef)
{
    ERL_NIF_TERM res;

    SSDBG( descP,
           ("SOCKET", "send_check_result(%T) {%d} -> entry with"
            "\r\n   written:   %d"
            "\r\n   dataSize:  %d"
            "\r\n   saveErrno: %d"
            "\r\n   sendRef:   %T"
            "\r\n", sockRef, descP->sock,
            written, dataSize, saveErrno, sendRef) );

    if (written >= dataSize) {

        res = send_check_ok(env, descP, written, dataSize, sockRef);

    } else if (written < 0) {

        /* Some kind of send failure - check what kind */

        if ((saveErrno != EAGAIN) && (saveErrno != EINTR)) {

            res = send_check_fail(env, descP, saveErrno, sockRef);

        } else {

            /* Ok, try again later */

            SSDBG( descP,
                   ("SOCKET",
                    "send_check_result(%T) {%d} -> try again"
                    "\r\n", sockRef, descP->sock) );

            res = send_check_retry(env, descP, written, sockRef, sendRef);
        }

    } else {

        /* Not the entire package */

        SSDBG( descP,
               ("SOCKET",
                "send_check_result(%T) {%d} -> "
                "not entire package written (%d of %d)"
                "\r\n", sockRef, descP->sock,
                written, dataSize) );

        res = send_check_retry(env, descP, written, sockRef, sendRef);
    }

    SSDBG( descP,
           ("SOCKET",
            "send_check_result(%T) {%d} -> done:"
            "\r\n   res: %T"
            "\r\n", sockRef, descP->sock,
            res) );

    return res;
}


/* *** send_check_ok ***
 *
 * Processing done upon successful send.
 */
static
ERL_NIF_TERM send_check_ok(ErlNifEnv*       env,
                           ESockDescriptor* descP,
                           ssize_t          written,
                           ssize_t          dataSize,
                           ERL_NIF_TERM     sockRef)
{
    ESOCK_CNT_INC(env, descP, sockRef,
                  atom_write_pkg, &descP->writePkgCnt, 1);
    ESOCK_CNT_INC(env, descP, sockRef,
                  atom_write_byte, &descP->writeByteCnt, written);
    descP->writePkgMaxCnt += written;
    if (descP->writePkgMaxCnt > descP->writePkgMax)
        descP->writePkgMax = descP->writePkgMaxCnt;
    descP->writePkgMaxCnt = 0;

    SSDBG( descP,
           ("SOCKET", "send_check_ok(%T) {%d} -> "
            "everything written (%d,%d) - done\r\n",
            sockRef, descP->sock, dataSize, written) );

    if (descP->currentWriterP != NULL) {
        DEMONP("send_check_ok -> current writer",
               env, descP, &descP->currentWriter.mon);
    }
    /*
     * Ok, this write is done maybe activate the next (if any)
     */
    if (!activate_next_writer(env, descP, sockRef)) {

        SSDBG( descP,
               ("SOCKET", "send_check_ok(%T) {%d} -> no more writers\r\n",
                sockRef, descP->sock) );

        descP->currentWriterP = NULL;
    }

    return esock_atom_ok;
}



/* *** send_check_failure ***
 *
 * Processing done upon failed send.
 * An actual failure - we (and everyone waiting) give up.
 */
static
ERL_NIF_TERM send_check_fail(ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             int              saveErrno,
                             ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM reason;

    ESOCK_CNT_INC(env, descP, sockRef, atom_write_fails, &descP->writeFails, 1);

    SSDBG( descP, ("SOCKET", "send_check_fail(%T) {%d} -> error: %d\r\n",
                   sockRef, descP->sock, saveErrno) );

    reason = MKA(env, erl_errno_id(saveErrno));

    if (saveErrno != EINVAL) {

        /*
         * We assume that anything other then einval (invalid input)
         * is basically fatal (=> all waiting sends are aborted)
         */

        if (descP->currentWriterP != NULL) {

            requestor_release("send_check_fail",
                              env, descP, descP->currentWriterP);

            send_error_waiting_writers(env, descP, sockRef, reason);

            descP->currentWriterP = NULL;
        }
    }
    return esock_make_error(env, reason);
}

/* *** send_error_waiting_writers ***
 *
 * Process all waiting writers when a fatal error has occured.
 * All waiting writers will be "aborted", that is a
 * nif_abort message will be sent (with ref and reason).
 */
static
void send_error_waiting_writers(ErlNifEnv*       env,
                                ESockDescriptor* descP,
                                ERL_NIF_TERM     sockRef,
                                ERL_NIF_TERM     reason)
{
    ESockRequestor req;

    req.env = NULL; /* read by writer_pop before free */
    while (writer_pop(env, descP, &req)) {
        SSDBG( descP,
               ("SOCKET",
                "send_error_waiting_writers(%T) {%d} -> abort"
                "\r\n   pid:    %T"
                "\r\n   reason: %T"
                "\r\n",
                sockRef, descP->sock, &req.pid, reason) );

        esock_send_abort_msg(env, sockRef, req.ref, req.env,
                             reason, &req.pid);
        req.env = NULL;

        DEMONP("send_error_waiting_writers -> pop'ed writer",
               env, descP, &req.mon);
    }
}



/* *** send_check_retry ***
 *
 * Processing done upon uncomplete or blocked send.
 *
 * 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.
 */
static
ERL_NIF_TERM send_check_retry(ErlNifEnv*       env,
                              ESockDescriptor* descP,
                              ssize_t          written,
                              ERL_NIF_TERM     sockRef,
                              ERL_NIF_TERM     sendRef)
{
    int          sres;
    ErlNifPid    caller;
    ERL_NIF_TERM res;

    if (descP->currentWriterP == NULL) {

        if (enif_self(env, &caller) == NULL)
            return esock_make_error(env, atom_exself);
        descP->currentWriter.pid = caller;

        if (MONP("send_check_retry -> current writer",
                 env, descP,
                 &descP->currentWriter.pid,
                 &descP->currentWriter.mon) != 0) {
            enif_set_pid_undefined(&descP->currentWriter.pid);
            return esock_make_error(env, atom_exmonitor);
        } else {
            ESOCK_ASSERT(descP->currentWriter.env == NULL);
            descP->currentWriter.env = esock_alloc_env("current-writer");
            descP->currentWriter.ref =
                CP_TERM(descP->currentWriter.env, sendRef);
            descP->currentWriterP    = &descP->currentWriter;
        }
    } else {
        enif_clear_env(descP->currentWriter.env);
        descP->currentWriter.ref = CP_TERM(descP->currentWriter.env, sendRef);
    }

    ESOCK_CNT_INC(env, descP, sockRef, atom_write_waits, &descP->writeWaits, 1);

    sres = esock_select_write(env, descP->sock, descP, NULL, sockRef, sendRef);

    if (written >= 0) {

        /* Partial *write* success */

        descP->writePkgMaxCnt += written;

        if (sres < 0) {
            /* Returned: {error, Reason}
             * Reason:   {select_failed, sres, written}
             */

            if (descP->writePkgMaxCnt > descP->writePkgMax)
                descP->writePkgMax = descP->writePkgMaxCnt;
            descP->writePkgMaxCnt = 0;

            res = esock_make_error(env,
                                   MKT3(env,
                                        esock_atom_select_failed,
                                        MKI(env, sres),
                                        MKI(env, written)));
        } else {
            res = esock_make_ok2(env, MKI(env, written));
        }

    } else {

        if (sres < 0) {
            /* Returned: {error, Reason}
             * Reason:   {select_failed, sres}
             */
            res = esock_make_error(env,
                                   MKT2(env,
                                        esock_atom_select_failed,
                                        MKI(env, sres)));
        } else {
            res = esock_make_error(env, esock_atom_eagain);
        }
    }

    return res;
}




/* *** 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.
 * Note that we do *not* actually initiate the currentReader structure
 * here, since we do not actually know yet if we need to! We do that in
 * the [recv|recvfrom|recvmsg]_check_result function.
 */
static
BOOLEAN_T recv_check_reader(ErlNifEnv*       env,
                            ESockDescriptor* 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(&descP->currentReader.pid, &caller) != 0) {
            /* Not the "current reader", so (maybe) push onto queue */

            SSDBG( descP,
                   ("SOCKET",
                    "recv_check_reader {%d} -> not (current) reader"
                    "\r\n   ref: %T"
                    "\r\n", descP->sock, ref) );

            if (!reader_search4pid(env, descP, &caller))
                *checkResult = reader_push(env, descP, caller, ref);
            else
                *checkResult = esock_make_error_errno(env, EALREADY);
            
            SSDBG( descP,
                   ("SOCKET",
                    "recv_check_reader {%d} -> queue (push) result: %T\r\n",
                    descP->sock, *checkResult) );

            return FALSE;
        }
    }

    // Does not actually matter in this case, but ...
    *checkResult = esock_atom_ok;

    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,
                               ESockDescriptor* 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) {
            enif_set_pid_undefined(&descP->currentReader.pid);
            return str_exmonitor;
        } else {
            ESOCK_ASSERT(!descP->currentReader.env);
            descP->currentReader.env = esock_alloc_env("current-reader");
            descP->currentReader.ref = CP_TERM(descP->currentReader.env,
                                               recvRef);
            descP->currentReaderP    = &descP->currentReader;
        }
    } else {

        /*
         * This is a retry:
         * We have done, for instance, recv(Sock, X), but only received Y < X.
         * We then call recv again with size = X-Y. So, we then get a new ref.
         * 
         * Make use of the existing environment
         */

        enif_clear_env(descP->currentReader.env);
        descP->currentReader.ref = CP_TERM(descP->currentReader.env, recvRef);
    }

    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,
                                        ESockDescriptor* descP,
                                        ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM res = esock_atom_ok;

    if (descP->currentReaderP != NULL) {
        
        DEMONP("recv_update_current_reader",
               env, descP, &descP->currentReader.mon);

        if (!activate_next_reader(env, descP, sockRef)) {

            SSDBG( descP,
                   ("SOCKET",
                    "recv_update_current_reader(%T) {%d} -> no more readers\r\n",
                    sockRef, descP->sock) );

            descP->currentReaderP = NULL;
        }

    }

    return res;
}



/* *** 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 ref and reason).
 */
static
void recv_error_current_reader(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ERL_NIF_TERM     sockRef,
                               ERL_NIF_TERM     reason)
{
    if (descP->currentReaderP != NULL) {
        ESockRequestor req;

        requestor_release("recv_error_current_reader",
                          env, descP, descP->currentReaderP);

        req.env = NULL; /* read by reader_pop before free */
        while (reader_pop(env, descP, &req)) {

            SSDBG( descP,
                   ("SOCKET", "recv_error_current_reader(%T) {%d} -> abort"
                    "\r\n   pid:   %T"
                    "\r\n   reason %T"
                    "\r\n", sockRef, descP->sock,
                    req.pid, reason) );

            esock_send_abort_msg(env, sockRef, req.ref, req.env,
                                 reason, &req.pid);
            req.env = NULL;
            DEMONP("recv_error_current_reader -> pop'ed reader",
                   env, descP, &req.mon);
        }

        descP->currentReaderP = NULL;
    }
}



/* *** recv_check_result ***
 *
 * Process the result of a call to recv.
 */
static
ERL_NIF_TERM recv_check_result(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               ssize_t          read,
                               size_t           toRead,
                               int              saveErrno,
                               ErlNifBinary*    bufP,
                               ERL_NIF_TERM     sockRef,
                               ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM res;

    SSDBG( descP,
           ("SOCKET", "recv_check_result(%T) {%d} -> entry with"
            "\r\n   read:      %ld"
            "\r\n   toRead:    %lu"
            "\r\n   saveErrno: %d"
            "\r\n   recvRef:   %T"
            "\r\n", sockRef, descP->sock,
            (long) read, (unsigned long) 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 reason = atom_closed;
        res = esock_make_error(env, reason);
        
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails, &descP->readFails, 1);

        /*
         * 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, reason);

        FREE_BIN(bufP);

    } else {
    
        /* There is a special case: If the provided 'to read' value is
         * zero (0) (only for type =/= stream).
         * That means that we read 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(%T) {%d} -> [%lu] filled the buffer\r\n",
                    sockRef, descP->sock, (unsigned long) toRead) );

            res = recv_check_full(env, descP, read, toRead, bufP,
                                  sockRef, recvRef);

        } else if (read < 0) {

            /* +++ Error handling +++ */

            res = recv_check_fail(env, descP, saveErrno, bufP, NULL,
                                  sockRef, recvRef);

        } else {

            /* +++ We did not fill the buffer +++ */

            SSDBG( descP,
                   ("SOCKET",
                    "recv_check_result(%T) {%d} -> [%lu] "
                    "did not fill the buffer (%ld of %lu)\r\n",
                    sockRef, descP->sock, (unsigned long) toRead,
                    (long) read, (unsigned long) bufP->size) );

            res = recv_check_partial(env, descP, read, toRead, bufP,
                                     sockRef, recvRef);
        }
    }

    return res;
}



/* *** recv_check_full ***
 *
 * This function is called if we filled the allocated buffer.
 * But are we done yet?
 *
 * toRead = 0 means: Give me everything you have => maybe
 * toRead > 0 means: Yes
 */
static
ERL_NIF_TERM recv_check_full(ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             ssize_t          read,
                             size_t           toRead,
                             ErlNifBinary*    bufP,
                             ERL_NIF_TERM     sockRef,
                             ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM res;

    if (toRead == 0) {

        /* +++ Give us everything you have got =>     *
         *     (maybe) needs to continue          +++ */

        /* 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...
         *
         * Also, we need to check if the rNumCnt has reached its max (rNum),
         * in which case we will assume the read to be done!
         */

        SSDBG( descP,
               ("SOCKET", "recv_check_full(%T) {%d} -> shall we continue reading?"
                "\r\n   read:    %ld"
                "\r\n   rNum:    %u"
                "\r\n   rNumCnt: %u"
                "\r\n", sockRef, descP->sock,
                (unsigned long) read, descP->rNum, descP->rNumCnt) );

        res = recv_check_full_maybe_done(env, descP, read, toRead, bufP,
                                         sockRef, recvRef);

    } else {

        /* +++ We got exactly as much as we requested => We are done +++ */

        SSDBG( descP,
               ("SOCKET",
                "recv_check_full(%T) {%d} -> [%lu] "
                "we got exactly what we could fit\r\n",
                sockRef, descP->sock, (unsigned long) toRead) );

        res = recv_check_full_done(env, descP, read, bufP, sockRef);

    }

    return res;

}



/* *** recv_check_full_maybe_done ***
 *
 * 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...
 *
 * Also, we need to check if the rNumCnt has reached its max (rNum),
 * in which case we will assume the read to be done!
 */
static
ERL_NIF_TERM recv_check_full_maybe_done(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        ssize_t          read,
                                        size_t           toRead,
                                        ErlNifBinary*    bufP,
                                        ERL_NIF_TERM     sockRef,
                                        ERL_NIF_TERM     recvRef)
{
    char* xres;

    ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte, &descP->readByteCnt, read);
    descP->readPkgMaxCnt += read;

    descP->rNumCnt++;
    if (descP->rNumCnt >= descP->rNum) {

        descP->rNumCnt = 0;

        ESOCK_CNT_INC(env, descP, sockRef,
                      atom_read_pkg, &descP->readPkgCnt, 1);
        if (descP->readPkgMaxCnt > descP->readPkgMax)
            descP->readPkgMax = descP->readPkgMaxCnt;
        descP->readPkgMaxCnt = 0;

        recv_update_current_reader(env, descP, sockRef);

        /* This transfers "ownership" of the *allocated* binary to an
         * erlang term (no need for an explicit free).
         */

        return esock_make_ok3(env, atom_true, MKBIN(env, bufP));

    }

    /* Yes, we *do* need to continue reading */

    if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) {
        descP->rNumCnt = 0;

        if (descP->readPkgMaxCnt > descP->readPkgMax)
            descP->readPkgMax = descP->readPkgMaxCnt;
        descP->readPkgMaxCnt = 0;

        FREE_BIN(bufP);
        return esock_make_error_str(env, xres);
    }

    /* This transfers "ownership" of the *allocated* binary to an
     * erlang term (no need for an explicit free).
     */

    SSDBG( descP,
           ("SOCKET",
            "recv_check_full_maybe_done(%T) {%d} -> [%lu] "
            "we are done for now - read more\r\n",
            sockRef, descP->sock, (unsigned long)toRead) );

    return esock_make_ok3(env, atom_false, MKBIN(env, bufP));
}



/* *** recv_check_full_done ***
 *
 * A successful recv and we filled the buffer.
 */
static
ERL_NIF_TERM recv_check_full_done(ErlNifEnv*       env,
                                  ESockDescriptor* descP,
                                  ssize_t          read,
                                  ErlNifBinary*    bufP,
                                  ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM data;

    ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
    ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
                  &descP->readByteCnt, read);

    descP->readPkgMaxCnt += read;
    if (descP->readPkgMaxCnt > descP->readPkgMax)
        descP->readPkgMax = descP->readPkgMaxCnt;
    descP->readPkgMaxCnt = 0;

    recv_update_current_reader(env, descP, sockRef);

    /* 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);
}



/* *** recv_check_fail ***
 *
 * Handle recv failure.
 */
static
ERL_NIF_TERM recv_check_fail(ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             int              saveErrno,
                             ErlNifBinary*    buf1P,
                             ErlNifBinary*    buf2P,
                             ERL_NIF_TERM     sockRef,
                             ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM res;

    FREE_BIN(buf1P); if (buf2P != NULL) FREE_BIN(buf2P);

    if (saveErrno == ECONNRESET)  {

        /* +++ Oups - closed +++ */

        SSDBG( descP,
               ("SOCKET",
                "recv_check_fail(%T) {%d} -> econnreset: closed"
                "\r\n   recvRef: %T"
                "\r\n", sockRef, descP->sock, recvRef) );

        // This is a bit overkill (to count here), but just in case...
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails,
                      &descP->readFails, 1);

        res = recv_check_fail_econnreset(env, descP, sockRef, recvRef);

    } else if ((saveErrno == ERRNO_BLOCK) ||
               (saveErrno == EAGAIN)) {

        SSDBG( descP,
               ("SOCKET",
                "recv_check_fail(%T) {%d} -> eagain"
                "\r\n   recvRef: %T"
                "\r\n", sockRef, descP->sock, recvRef) );

        res = recv_check_retry(env, descP, sockRef, recvRef);

    } else {

        SSDBG( descP,
               ("SOCKET",
                "recv_check_fail(%T) {%d} -> errno: %d\r\n"
                "\r\n   recvRef: %T"
                "\r\n", sockRef, descP->sock, saveErrno, recvRef) );

        ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails,
                      &descP->readFails, 1);

        res = recv_check_fail_gen(env, descP, saveErrno, sockRef);
    }

    return res;
}



/* *** recv_check_fail_econnreset ***
 *
 * We detected that the socket was closed wile reading.
 * Inform current and waiting readers.
 */
static
ERL_NIF_TERM recv_check_fail_econnreset(ErlNifEnv*       env,
                                        ESockDescriptor* descP,
                                        ERL_NIF_TERM     sockRef,
                                        ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM reason = atom_econnreset;
    ERL_NIF_TERM res = esock_make_error(env, atom_econnreset);

    /* <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>
     */

    recv_error_current_reader(env, descP, sockRef, reason);

    return res;
}



/* *** recv_check_retry ***
 *
 * The recv call would have blocked, so retry.
 */
static
ERL_NIF_TERM recv_check_retry(ErlNifEnv*       env,
                              ESockDescriptor* descP,
                              ERL_NIF_TERM     sockRef,
                              ERL_NIF_TERM     recvRef)
{
    int          sres;
    char*        xres;
    ERL_NIF_TERM reason;

    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_retry(%T) {%d} -> SELECT for more"
            "\r\n   recvRef: %T"
            "\r\n", sockRef, descP->sock, recvRef) );

    if ((sres = esock_select_read(env, descP->sock, descP, NULL,
                                  sockRef, recvRef)) < 0) {
        /* Ouch
         * Now what? We have copied ref into *its own* environment!
         */
        reason = atom_exselect;
    } else {
        reason = esock_atom_eagain;
    }

    return esock_make_error(env, reason);

}



/* *** recv_check_fail_gen ***
 *
 * The recv call had a "general" failure.
 */
static
ERL_NIF_TERM recv_check_fail_gen(ErlNifEnv*       env,
                                 ESockDescriptor* descP,
                                 int              saveErrno,
                                 ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM reason = MKA(env, erl_errno_id(saveErrno));

    recv_error_current_reader(env, descP, sockRef, reason);

    return esock_make_error(env, reason);
}



/* *** recv_check_partial ***
 *
 * Handle a sucessful recv which only partly filled the specified buffer.
 */
static
ERL_NIF_TERM recv_check_partial(ErlNifEnv*       env,
                                ESockDescriptor* descP,
                                ssize_t          read,
                                size_t           toRead,
                                ErlNifBinary*    bufP,
                                ERL_NIF_TERM     sockRef,
                                ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM res;

    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_partial(%T) {%d} -> [%lu] split buffer"
                "\r\n   recvRef: %T"
                "\r\n", sockRef, descP->sock,
                (unsigned long) toRead, recvRef) );

        res = recv_check_partial_done(env, descP, read, bufP, sockRef);

    } else {

        SSDBG( descP,
               ("SOCKET",
                "recv_check_partial(%T) {%d} -> [%lu]"
                " only part of message - expect more"
                "\r\n   recvRef: %T"
                "\r\n", sockRef, descP->sock,
                (unsigned long) toRead, recvRef) );

        res = recv_check_partial_part(env, descP, read, bufP, sockRef, recvRef);
    }

    return res;
}



/* *** recv_check_partial_done ***
 *
 * A successful but only partial recv, which fulfilled the required read.
 */
static
ERL_NIF_TERM recv_check_partial_done(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     ssize_t          read,
                                     ErlNifBinary*    bufP,
                                     ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM data;

    descP->rNumCnt = 0;
    ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
    ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
                  &descP->readByteCnt, read);

    descP->readPkgMaxCnt += read;
    if (descP->readPkgMaxCnt > descP->readPkgMax)
        descP->readPkgMax = descP->readPkgMaxCnt;
    descP->readPkgMaxCnt = 0;

    recv_update_current_reader(env, descP, sockRef);

    /* 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_partial_done(%T) {%d} -> [%ld] done\r\n",
            sockRef, descP->sock, (long) read) );

    return esock_make_ok3(env, atom_true, data);
}



/* *** recv_check_partial_part ***
 *
 * A successful but only partial recv, which only partly fulfilled
 * the required read.
 */
static
ERL_NIF_TERM recv_check_partial_part(ErlNifEnv*       env,
                                     ESockDescriptor* descP,
                                     ssize_t          read,
                                     ErlNifBinary*    bufP,
                                     ERL_NIF_TERM     sockRef,
                                     ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM res, reason, data;
    char*        xres;
    int          sres;

    if ((xres = recv_init_current_reader(env, descP, recvRef)) != NULL) {
        FREE_BIN(bufP);
        return esock_make_error_str(env, xres);
    }

    data = MKBIN(env, bufP);
    data = MKSBIN(env, data, 0, read);

    ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
                  &descP->readByteCnt, read);

    /* SELECT for more data */

    sres = esock_select_read(env, descP->sock, descP, NULL,
                             sockRef, recvRef);
    if (sres < 0) {
        /* Result: {error, Reason}
         * Reason: {select_failed, sres, data}
         */
        reason = MKT3(env, esock_atom_select_failed, MKI(env, sres), data);
        res    = esock_make_error(env, reason);

    } else {

        res = esock_make_ok3(env, atom_false, data);

    }

    /* This transfers "ownership" of the *allocated* binary to an
     * erlang term (no need for an explicit free).
     */
    return res;
}





/* The recvfrom function delivers one (1) message. If our buffer
 * is too 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,
                                   ESockDescriptor* descP,
                                   int              read,
                                   int              saveErrno,
                                   ErlNifBinary*    bufP,
                                   ESockAddress*    fromAddrP,
                                   unsigned int     fromAddrLen,
                                   ERL_NIF_TERM     sockRef,
                                   ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM data, res;

    SSDBG( descP,
           ("SOCKET", "recvfrom_check_result(%T) {%d} -> entry with"
            "\r\n   read:      %d"
            "\r\n   saveErrno: %d"
            "\r\n   recvRef:   %T"
            "\r\n", sockRef, descP->sock,
            read, saveErrno, recvRef) );

    if (read < 0) {

        /* +++ Error handling +++ */

        res = recv_check_fail(env, descP, saveErrno, bufP, NULL,
                              sockRef, recvRef);

    } 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);
        }

        ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg,
                      &descP->readPkgCnt, 1);
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
                      &descP->readByteCnt, read);

        recv_update_current_reader(env, descP, sockRef);
        
        res = esock_make_ok2(env, MKT2(env, eSockAddr, data));

    }

    return res;

}



/* *** recvmsg_check_result ***
 *
 * 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,
                                  ESockDescriptor* descP,
                                  int              read,
                                  int              saveErrno,
                                  struct msghdr*   msgHdrP,
                                  ErlNifBinary*    dataBufP,
                                  ErlNifBinary*    ctrlBufP,
                                  ERL_NIF_TERM     sockRef,
                                  ERL_NIF_TERM     recvRef)
{
    ERL_NIF_TERM res;

    SSDBG( descP,
           ("SOCKET", "recvmsg_check_result(%T) {%d} -> entry with"
            "\r\n   read:      %d"
            "\r\n   saveErrno: %d"
            "\r\n   recvRef:   %T"
            "\r\n", sockRef, descP->sock,
            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!
         */

        ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails, &descP->readFails, 1);

        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 +++ */

        res = recv_check_fail(env, descP, saveErrno, dataBufP, ctrlBufP,
                              sockRef, recvRef);

    } else {

        /* +++ We sucessfully got a message - time to encode it +++ */

        res = recvmsg_check_msg(env, descP, read, msgHdrP,
                                dataBufP, ctrlBufP, sockRef);

    }

    return res;

}



/* *** recvmsg_check_msg ***
 *
 * We successfully read one message. Time to process.
 */
static
ERL_NIF_TERM recvmsg_check_msg(ErlNifEnv*       env,
                               ESockDescriptor* descP,
                               int              read,
                               struct msghdr*   msgHdrP,
                               ErlNifBinary*    dataBufP,
                               ErlNifBinary*    ctrlBufP,
                               ERL_NIF_TERM     sockRef)
{
    ERL_NIF_TERM res, 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(%T) {%d} -> "
                "(msghdr) encode failed: %s\r\n",
                sockRef, descP->sock, xres) );

        /* So this is a bit strange. We did "successfully" read 'read' bytes,
         * but then we fail to process the message header. So what counters
         * shall we increment?
         *    Only failure?
         *    Or only success (pkg and byte), since the read was "ok" (or was it?)
         *    Or all of them?
         *
         * For now, we increment all three...
         */
         
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_fails, &descP->readFails, 1);
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
                     &descP->readByteCnt, read);

        recv_update_current_reader(env, descP, sockRef);

        FREE_BIN(dataBufP); FREE_BIN(ctrlBufP);

        res = esock_make_error_str(env, xres);

    } else {

        SSDBG( descP,
               ("SOCKET", "recvmsg_check_result(%T) {%d} ->"
                " (msghdr) encode ok\r\n",
                sockRef, descP->sock) );

        ESOCK_CNT_INC(env, descP, sockRef, atom_read_pkg, &descP->readPkgCnt, 1);
        ESOCK_CNT_INC(env, descP, sockRef, atom_read_byte,
                     &descP->readByteCnt, read);

        recv_update_current_reader(env, descP, sockRef);

        res = esock_make_ok2(env, eMsgHdr);
    }

    return res;
}




/* +++ 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,
                    ESockDescriptor* 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 {%d} -> entry with"
            "\r\n   read: %d"
            "\r\n", descP->sock, read) );

    /* The address is not used if we are connected (unless, maybe,
     * family is 'local'), so check (length = 0) before we try to encodel
     */
    if (msgHdrP->msg_namelen != 0) {
        if ((xres = esock_encode_sockaddr(env,
                                          (ESockAddress*) msgHdrP->msg_name,
                                          msgHdrP->msg_namelen,
                                          &addr)) != NULL)
            return xres;
    } else {
        addr = esock_atom_undefined;
    }

    SSDBG( descP,
           ("SOCKET", "encode_msghdr {%d} -> try encode iov\r\n",
            descP->sock) );

    if ((xres = esock_encode_iov(env,
                                 read,
                                 msgHdrP->msg_iov,
                                 msgHdrP->msg_iovlen,
                                 dataBufP,
                                 &iov)) != NULL)
        return xres;

    SSDBG( descP,
           ("SOCKET",
            "encode_msghdr {%d} -> try encode cmsghdrs\r\n",
            descP->sock) );

    if ((xres = encode_cmsghdrs(env, descP, ctrlBufP, msgHdrP, &ctrl)) != NULL)
        return xres;

    SSDBG( descP,
           ("SOCKET",
            "encode_msghdr {%d} -> try encode flags\r\n",
            descP->sock) );

    if ((xres = encode_msghdr_flags(env, descP, msgHdrP->msg_flags, &flags)) != NULL)
        return xres;

    SSDBG( descP,
           ("SOCKET", "encode_msghdr {%d} -> components encoded:"
            "\r\n   addr:  %T"
            "\r\n   ctrl:  %T"
            "\r\n   flags: %T"
            "\r\n", descP->sock, addr, 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 {%d} -> create msghdr map\r\n",
                descP->sock) );

        if (!MKMA(env, keys, vals, numKeys, &tmp))
            return ESOCK_STR_EINVAL;

        SSDBG( descP,
               ("SOCKET",
                "encode_msghdr {%d}-> msghdr encoded\r\n",
                descP->sock) );

        *eSockAddr = tmp;
    }

    SSDBG( descP,
           ("SOCKET", "encode_msghdr {%d} -> done\r\n", descP->sock) );

    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,
                      ESockDescriptor* 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 {%d} -> entry when"
                   "\r\n   msg ctrl len:  %d"
                   "\r\n   (ctrl) firstP: 0x%lX"
                   "\r\n", descP->sock,
                   msgHdrP->msg_controllen, firstP) );

    for (currentP = firstP;
         /*
          * In *old* versions of darwin, the CMSG_FIRSTHDR does not
          * check the msg_controllen, so we do it here.
          * We should really test this stuff during configure,
          * but for now, this will have to do.
          */
#if defined(__DARWIN__)
         (msgHdrP->msg_controllen >= sizeof(struct cmsghdr)) && (currentP != NULL);
#else
         (currentP != NULL);
#endif
         currentP = CMSG_NXTHDR(msgHdrP, currentP)) {

        SSDBG( descP,
               ("SOCKET", "encode_cmsghdrs {%d} -> process cmsg header when"
                "\r\n   TArray Size: %d"
                "\r\n", descP->sock, 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.
             */

            SSDBG( descP,
                   ("SOCKET", "encode_cmsghdrs {%d} -> check failed when: "
                    "\r\n   currentP:           0x%lX"
                    "\r\n   (current) cmsg_len: %d"
                    "\r\n   firstP:             0x%lX"
                    "\r\n   =>                  %d"
                    "\r\n   msg ctrl len:       %d"
                    "\r\n", descP->sock,
                    CHARP(currentP), currentP->cmsg_len, CHARP(firstP),
                    (CHARP(currentP) + currentP->cmsg_len) - CHARP(firstP),
                    msgHdrP->msg_controllen) );

            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 {%d} -> cmsg header data: "
                    "\r\n   dataPos: %d"
                    "\r\n   dataLen: %d"
                    "\r\n", descP->sock, 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 {%d} -> "
                    "\r\n   level: %T"
                    "\r\n   type:  %T"
                    "\r\n   data:  %T"
                    "\r\n", descP->sock, 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 {%d} -> cmsg headers processed when"
            "\r\n   TArray Size: %d"
            "\r\n", descP->sock, 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,
                      ESockDescriptor* 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 {%d} -> entry with"
                   "\r\n   eCMsgHdr:      %T"
                   "\r\n   cmsgHdrBufP:   0x%lX"
                   "\r\n   cmsgHdrBufLen: %d"
                   "\r\n", descP->sock,
                   eCMsgHdr, cmsgHdrBufP, cmsgHdrBufLen) );

    if (IS_LIST(env, eCMsgHdr) && GET_LIST_LEN(env, eCMsgHdr, &len)) {

        SSDBG( descP,
               ("SOCKET",
                "decode_cmsghdrs {%d} -> list length: %d\r\n",
                descP->sock, len) );

        for (i = 0, list = eCMsgHdr, rem  = cmsgHdrBufLen, bufP = cmsgHdrBufP;
             i < len; i++) {
            
            SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> process elem %d:"
                           "\r\n   (buffer) rem:     %u"
                           "\r\n   (buffer) totUsed: %u"
                           "\r\n", descP->sock, 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 {%d} -> all %d ctrl headers processed\r\n",
                descP->sock, len) );

        xres = NULL;
    } else {
        xres = ESOCK_STR_EINVAL;
    }

    *cmsgHdrBufUsed = totUsed;

    SSDBG( descP, ("SOCKET", "decode_cmsghdrs {%d} -> done with %s when"
                   "\r\n   totUsed = %u\r\n",
                   descP->sock, ((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,
                     ESockDescriptor* descP,
                     ERL_NIF_TERM     eCMsgHdr,
                     char*            bufP,
                     size_t           rem,
                     size_t*          used)
{
    SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> entry with"
                   "\r\n   eCMsgHdr: %T"
                   "\r\n", descP->sock, 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 {%d} -> eLevel: %T"
                       "\r\n", descP->sock, eLevel) );

        if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_type, &eType))
            return ESOCK_STR_EINVAL;
        
        SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eType:  %T"
                       "\r\n", descP->sock, eType) );

        if (!GET_MAP_VAL(env, eCMsgHdr, esock_atom_data, &eData))
            return ESOCK_STR_EINVAL;

        SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> eData:  %T"
                       "\r\n", descP->sock, eData) );

        /* Second, decode level */
        if ((xres = decode_cmsghdr_level(env, eLevel, &level)) != NULL)
            return xres;

        SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d}-> level:  %d\r\n",
                       descP->sock, level) );

        /* third, decode type */
        if ((xres = decode_cmsghdr_type(env, level, eType, &type)) != NULL)
            return xres;
        
        SSDBG( descP, ("SOCKET", "decode_cmsghdr {%d} -> type:   %d\r\n",
                       descP->sock, 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,
                          ESockDescriptor* 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 {%d} -> entry with"
                   "\r\n   eData: %T"
                   "\r\n", descP->sock, eData) );

    if (IS_BIN(env, eData)) {
        ErlNifBinary bin;
        
        if (GET_BIN(env, eData, &bin)) {
            SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> "
                           "do final decode with binary\r\n", descP->sock) );
            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 {%d} -> "
                                       "do final decode with tos (=%d)"
                                       "\r\n", descP->sock, data) );
                        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 {%d} -> "
                                       "do final decode with ttl (=%d)"
                                       "\r\n", descP->sock, data) );
                        return decode_cmsghdr_final(descP, bufP, rem, level, type,
                                                    (char*) &data,
                                                    sizeof(data),
                                                    used);
                    } else {
                        *used = 0;
                        xres  = ESOCK_STR_EINVAL;
                    }
                }
                break;
#endif

            }
            break;


#if defined(HAVE_IPV6)
#if defined(SOL_IPV6)
	case SOL_IPV6:
#else
	case IPPROTO_IPV6:
#endif // if defined(SOL_IPV6)
	  switch (type) {
#if defined(IPV6_TCLASS)
	  case IPV6_TCLASS:
	    {
	      int data;
	      if (GET_INT(env, eData, &data)) {
		SSDBG( descP, ("SOCKET", "decode_cmsghdr_data {%d} -> "
			       "do final decode with tclass (=%d)"
			       "\r\n", descP->sock, data) );
		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(IPV6_TCLASS)

	  default:
            *used = 0;
            xres  = ESOCK_STR_EINVAL;
	    break;
	  }
	  break;
#endif // if defined(HAVE_IPV6)

        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).
 * Note that we do a memzero of the (entire) structure before we begin
 * initiating it (len, level, type and data). This is to avoid complaints
 * from valgrind.
 */
static
char* decode_cmsghdr_final(ESockDescriptor* descP,
                           char*            bufP,
                           size_t           rem,
                           int              level,
                           int              type,
                           char*            data,
                           int              sz,
                           size_t*          used)
{
    int len   = CMSG_LEN(sz);   // length of *actual* data
    int space = CMSG_SPACE(sz); // length of (actual) data + padding

    SSDBG( descP, ("SOCKET", "decode_cmsghdr_final {%d} -> entry when"
                   "\r\n   level: %d"
                   "\r\n   type:  %d"
                   "\r\n   sz:    %d => %d, %d, %d"
                   "\r\n", descP->sock,
                   level, type, sz, len, space, rem) );

    if (rem >= space) {
        struct cmsghdr* cmsgP = (struct cmsghdr*) bufP;

        sys_memzero(cmsgP, space);

        /* The header */
        cmsgP->cmsg_len   = len;
        cmsgP->cmsg_level = level;
        cmsgP->cmsg_type  = type;

        sys_memcpy(CMSG_DATA(cmsgP), data, sz);
        *used = space;
    } else {
        SSDBG( descP, ("SOCKET", "decode_cmsghdr_final {%d} -> "
                       "not enough space (needs %d, have %d)\r\n",
                       descP->sock, space, rem) );
        *used = 0;
        return ESOCK_STR_EINVAL;
    }

    SSDBG( descP, ("SOCKET",
                   "decode_cmsghdr_final {%d} -> done ok\r\n",
                   descP->sock) );

    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_ipv6;
        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) {

      /* *** SOCKET *** */

    case SOL_SOCKET:
        switch (type) {
#if defined(SCM_TIMESTAMP)
        case SCM_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;
#elif defined(SCM_CREDS)
        case SCM_CREDS:
            *eType = esock_atom_credentials;
            break;
#endif

        default:
            *eType = MKI(env, type);
            break;
        }        
        break;


	/* *** IP *** */

#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

            /*
             * On Solaris (among others) TTL has type RECVTTL!
             * We could convert to TTL (that is esock_atom_ttl,
             * but that opens up pandoras box, so leave it to
             * the user).
             */
#if defined(IP_RECVTTL)
        case IP_RECVTTL:
            *eType = esock_atom_recvttl;
            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

            /*
             * On FreeBSD (among others) TOS has type RECVTOS!
             * We could convert to TOS (that is esock_atom_tos,
             * but that opens up pandoras box, so leave it to
             * the user).
             */
#if defined(IP_RECVTOS)
        case IP_RECVTOS:
            *eType = esock_atom_recvtos;
            break;
#endif

#if defined(IP_RECVERR)
        case IP_RECVERR:
            *eType = esock_atom_recverr;
            break;
#endif

        default:
            xres = ESOCK_STR_EINVAL;
            break;
        }
        break;


	/* *** IPv6 *** */

#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 // if defined(IPV6_PKTINFO)

#if defined(IPV6_HOPLIMIT)
	  case IPV6_HOPLIMIT:
              *eType = esock_atom_hoplimit;
              break;
#endif // if defined(IPV6_HOPLIMIT)

              /* Don't know if both this and RECVTCLASS can come here,
               * but hust in case... */
#if defined(IPV6_TCLASS)
	  case IPV6_TCLASS:
              *eType = esock_atom_tclass;
              break;
#endif // if defined(IPV6_TCLASS)

              /* Don't know if both this and TCLASS can come here,
               * but hust in case... */
#if defined(IPV6_RECVTCLASS)
	  case IPV6_RECVTCLASS:
              *eType = esock_atom_recvtclass;
              break;
#endif // if defined(IPV6_RECVTCLASS)

#if defined(IPV6_RECVERR)
        case IPV6_RECVERR:
            *eType = esock_atom_recverr;
            break;
#endif

	  default:
            xres = ESOCK_STR_EINVAL;
            break;
	  }        
	  break;
#endif // if defined(HAVE_IPV6)


	  /* *** TCP *** */

    case IPPROTO_TCP:
        switch (type) {
        default:
            xres = ESOCK_STR_EINVAL;
            break;
        }        
        break;


	/* *** UDP *** */

    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_ATOM(env, eType)) {
            if (COMPARE(eType, esock_atom_timestamp) == 0) {
#if defined(SCM_TIMESTAMP)
                *type = SCM_TIMESTAMP;
#else
                xres  = ESOCK_STR_EINVAL;
#endif
            } else if (COMPARE(eType, esock_atom_rights) == 0) {
#if defined(SCM_RIGHTS)
                *type = SCM_RIGHTS;
#else
                xres  = ESOCK_STR_EINVAL;
#endif
            } else if (COMPARE(eType, esock_atom_credentials) == 0) {
#if defined(SCM_CREDENTIALS)
                *type = SCM_CREDENTIALS;
#elif defined(SCM_CREDS)
                *type = SCM_CREDS;
#else
                xres  = ESOCK_STR_EINVAL;
#endif
            }
        } 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(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_ATOM(env, eType)) {
	if (COMPARE(eType, esock_atom_tclass) == 0) {
#if defined(IPV6_TCLASS)
	  *type = IPV6_TCLASS;
#else
	  *type = -1;
	  xres  = ESOCK_STR_EINVAL;
#endif
	} else {
	  *type = -1;
	  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;
#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(SCM_TIMESTAMP)
    case SCM_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;
}



#if defined(HAVE_LINUX_ERRQUEUE_H)

/* +++ encode_cmsghdr_data_recverr +++
 *
 * Encode the extended socker error in the data part of the cmsghdr().
 *
 */

static
char* encode_cmsghdr_data_recverr(ErlNifEnv*     env,
                                  unsigned char* dataP,
                                  size_t         dataLen,
                                  ERL_NIF_TERM*  eCMsgHdrData)
{
    struct sock_extended_err *sock_err =
        (struct sock_extended_err*) dataP;
    struct sockaddr *offender = SO_EE_OFFENDER(sock_err);

    ERL_NIF_TERM ee_errno  = MKA(env, erl_errno_id(sock_err->ee_errno));
    ERL_NIF_TERM ee_origin;
    ERL_NIF_TERM ee_type;
    ERL_NIF_TERM ee_code;
    ERL_NIF_TERM ee_info   = MKI(env, sock_err->ee_info);
    ERL_NIF_TERM ee_data  =  MKI(env, sock_err->ee_data);
    ERL_NIF_TERM eSockAddr;

    switch (sock_err->ee_origin) {
#if defined(SO_EE_ORIGIN_NONE)
    case SO_EE_ORIGIN_NONE:
        ee_origin = atom_none;
        ee_type = MKI(env, sock_err->ee_type);
        ee_code = MKI(env, sock_err->ee_code);
        break;
#endif

#if defined(SO_EE_ORIGIN_LOCAL)
    case SO_EE_ORIGIN_LOCAL:
        ee_origin = esock_atom_local;
        ee_type = MKI(env, sock_err->ee_type);
        ee_code = MKI(env, sock_err->ee_code);
        break;
#endif

#if defined(SO_EE_ORIGIN_ICMP)
    case SO_EE_ORIGIN_ICMP:
        ee_origin = esock_atom_icmp;
        switch (sock_err->ee_type) {

#if defined(ICMP_DEST_UNREACH)
        case ICMP_DEST_UNREACH:
            ee_type   = atom_dest_unreach;
            switch (sock_err->ee_code) {

#if defined(ICMP_NET_UNREACH)
            case ICMP_NET_UNREACH:
                ee_code = atom_net_unreach;
                break;
#endif

#if defined(ICMP_HOST_UNREACH)
            case ICMP_HOST_UNREACH:
                ee_code = atom_host_unreach;
                break;
#endif

#if defined(ICMP_PORT_UNREACH)
            case ICMP_PORT_UNREACH:
                ee_code = atom_port_unreach;
                break;
#endif

#if defined(ICMP_FRAG_NEEDED)
            case ICMP_FRAG_NEEDED:
                ee_code = atom_frag_needed;
                break;
#endif

#if defined(ICMP_NET_UNKNOWN)
            case ICMP_NET_UNKNOWN:
                ee_code = atom_net_unknown;
                break;
#endif

#if defined(ICMP_HOST_UNKNOWN)
            case ICMP_HOST_UNKNOWN:
                ee_code = atom_host_unknown;
                break;
#endif

            default:
                ee_code = MKI(env, sock_err->ee_code);
                break;
            }
            break;
#endif // ICMP_DEST_UNREACH

#if defined(ICMP_TIME_EXCEEDED)
        case ICMP_TIME_EXCEEDED:
            ee_type = atom_time_exceeded;
            ee_code = MKI(env, sock_err->ee_code);
            break;
#endif

        default:
            ee_type = MKI(env, sock_err->ee_type);
            ee_code = MKI(env, sock_err->ee_code);
            break;
        }
        break;
#endif // SO_EE_ORIGIN_ICMP

#if defined(SO_EE_ORIGIN_ICMP6)
    case SO_EE_ORIGIN_ICMP6:
        ee_origin = esock_atom_icmp6;
        switch (sock_err->ee_type) {

#if defined(ICMPV6_DEST_UNREACH)
        case ICMPV6_DEST_UNREACH:
            ee_type = atom_dest_unreach;
            switch (sock_err->ee_code) {

#if defined(ICMPV6_NOROUTE)
            case ICMPV6_NOROUTE:
                ee_code = atom_noroute;
                break;
#endif
#if defined(ICMPV6_ADM_PROHIBITED)
            case ICMPV6_ADM_PROHIBITED:
                ee_code = atom_adm_prohibited;
                break;
#endif

#if defined(ICMPV6_NOT_NEIGHBOUR)
            case ICMPV6_NOT_NEIGHBOUR:
                ee_code = atom_not_neighbour;
                break;
#endif

#if defined(ICMPV6_ADDR_UNREACH)
            case ICMPV6_ADDR_UNREACH:
                ee_code = atom_addr_unreach;
                break;
#endif

#if defined(ICMPV6_PORT_UNREACH)
            case ICMPV6_PORT_UNREACH:
                ee_code = atom_port_unreach;
                break;
#endif

#if defined(ICMPV6_POLICY_FAIL)
            case ICMPV6_POLICY_FAIL:
                ee_code = atom_policy_fail;
                break;
#endif

#if defined(ICMPV6_REJECT_ROUTE)
            case ICMPV6_REJECT_ROUTE:
                ee_code = atom_reject_route;
                break;
#endif

            default:
                ee_code = MKI(env, sock_err->ee_code);
                break;
            }
            break;
#endif // ICMPV6_DEST_UNREACH

#if defined(ICMPV6_PKT_TOOBIG)
        case ICMPV6_PKT_TOOBIG:
            ee_type = atom_pkt_toobig;
            ee_code = MKI(env, sock_err->ee_code);
            break;
#endif

#if defined(ICMPV6_TIME_EXCEED)
        case ICMPV6_TIME_EXCEED:
            ee_type = atom_time_exceeded;
            ee_code = MKI(env, sock_err->ee_code);
            break;
#endif

        default:
            ee_type = MKI(env, sock_err->ee_type);
            ee_code = MKI(env, sock_err->ee_code);
            break;
        }
        break;
#endif // SO_EE_ORIGIN_ICMP6

#if defined(SO_EE_ORIGIN_TXSTATUS)
    case SO_EE_ORIGIN_TXSTATUS:
        ee_origin = atom_txstatus;
        ee_type   = MKI(env, sock_err->ee_type);
        ee_code   = MKI(env, sock_err->ee_code);
        break;
#endif

#if defined(SO_EE_ORIGIN_ZEROCOPY)
    case SO_EE_ORIGIN_ZEROCOPY:
        ee_origin = atom_zerocopy;
        ee_type   = MKI(env, sock_err->ee_type);
        ee_code   = MKI(env, sock_err->ee_code);
        break;
#endif

#if defined(SO_EE_ORIGIN_TXTIME)
    case SO_EE_ORIGIN_TXTIME:
        ee_origin = atom_txtime;
        ee_type   = MKI(env, sock_err->ee_type);
        ee_code   = MKI(env, sock_err->ee_code);
        break;
#endif

    default:
        ee_origin = MKI(env, sock_err->ee_origin);
        ee_type   = MKI(env, sock_err->ee_type);
        ee_code   = MKI(env, sock_err->ee_code);
        break;
    }

    if (CHARP(dataP) + dataLen > CHARP(offender)) {
        esock_encode_sockaddr(env,
                              (ESockAddress *)offender,
                              (CHARP(dataP) + dataLen) - CHARP(offender),
                              &eSockAddr);
    } else {
        eSockAddr = esock_atom_undefined;
    }

    {
        ERL_NIF_TERM keys[] = {esock_atom_error,
                               atom_origin,
                               esock_atom_type,
                               atom_code,
                               esock_atom_info,
                               esock_atom_data,
                               atom_offender};
        ERL_NIF_TERM vals[] = {ee_errno, ee_origin, ee_type, ee_code,
                               ee_info, ee_data, eSockAddr};
        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;
        }
    }

    return NULL;
}

#endif // defined(HAVE_LINUX_ERRQUEUE_H)

/* +++ 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


        /*
         * On Solaris (among others) you don't get TTL when 
         * you order TTL with RECVTTL ( = "I want to receive TTL")
         * Instead, you receive RECVTTL...
         * And also, its not an 'int', its an 'uint8_t' (unsigned char)...
         * Why can't we all get along...
         */
#if defined(IP_RECVTTL)
    case IP_RECVTTL:
        {
            unsigned int ttl = *((unsigned char*) dataP);
            *eCMsgHdrData = MKUI(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


        /*
         * On FreeBSD (among others) you don't get TOS when 
         * you order TOS with RECVTOS ( = "I want to receive TOS")
         * Instead, you receive RECVTOS...
         */
#if defined(IP_RECVTOS)
    case IP_RECVTOS:
        {
            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_RECVTOS)

#if defined(IP_RECVERR) && defined(HAVE_LINUX_ERRQUEUE_H)
    case IP_RECVERR:
        xres = encode_cmsghdr_data_recverr(env, dataP, dataLen, eCMsgHdrData);
        break;
#endif // defined(IP_RECVERR) && defined(HAVE_LINUX_ERRQUEUE_H)

    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 = NULL;

    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;
            char*               xres;

            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


#if defined(IPV6_HOPLIMIT)
    case IPV6_HOPLIMIT:
        {
            int* hoplimitP = (int*) dataP;

            *eCMsgHdrData = MKI(env, *hoplimitP);
        }
        break;
#endif // if defined(IPV6_HOPLIMIT)

#if defined(IPV6_TCLASS) || defined(IPV6_RECVTCLASS)
#if defined(IPV6_RECVTCLASS)
    case IPV6_RECVTCLASS:
#endif
#if defined(IPV6_TCLASS)
    case IPV6_TCLASS:
#endif
        {
            int* tclassP = (int*) dataP;

            *eCMsgHdrData = MKI(env, *tclassP);
        }
        break;
#endif // if defined(IPV6_TCLASS) || defined(IPV6_RECVTCLASS)

#if defined(IPV6_RECVERR) && defined(HAVE_LINUX_ERRQUEUE_H)
    case IPV6_RECVERR:
        xres = encode_cmsghdr_data_recverr(env, dataP, dataLen, eCMsgHdrData);
        break;
#endif // defined(IPV6_RECVERR) && defined(HAVE_LINUX_ERRQUEUE_H)

    default:
        *eCMsgHdrData = MKSBIN(env, ctrlBuf, dataPos, dataLen);
        break;
    }

    return xres;
}
#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,
                          ESockDescriptor* descP,
                          int              msgFlags,
                          ERL_NIF_TERM*    flags)
{
    SSDBG( descP,
           ("SOCKET", "encode_cmsghdrs_flags {%d} -> entry with"
            "\r\n   msgFlags: %d (0x%lX)"
            "\r\n", descP->sock, 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 {%d} -> flags processed when"
                "\r\n   TArray size: %d"
                "\r\n", descP->sock, 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, &lt))
        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 = ESOCK_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 = ESOCK_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 = ESOCK_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
ESockDescriptor* alloc_descriptor(SOCKET sock, HANDLE event)
{
    ESockDescriptor* descP;

    if ((descP = enif_alloc_resource(esocks, sizeof(ESockDescriptor))) != NULL) {
        char buf[64]; /* Buffer used for building the mutex name */

        descP->pattern        = ESOCK_DESC_PATTERN_CREATED;

        requestor_init(&descP->connector);
        descP->connectorP = NULL;

        sprintf(buf, "esock.w[%d]", sock);
        descP->writeMtx       = MCREATE(buf);
        descP->writeState     = 0;
        requestor_init(&descP->currentWriter);
        descP->currentWriterP = NULL; // currentWriter not used
        descP->writersQ.first = NULL;
        descP->writersQ.last  = NULL;
        descP->writePkgCnt    = 0;
        descP->writePkgMax    = 0;
        descP->writePkgMaxCnt = 0;
        descP->writeByteCnt   = 0;
        descP->writeTries     = 0;
        descP->writeWaits     = 0;
        descP->writeFails     = 0;

        sprintf(buf, "esock.r[%d]", sock);
        descP->readMtx        = MCREATE(buf);
        descP->readState      = 0;
        requestor_init(&descP->currentReader);
        descP->currentReaderP = NULL; // currentReader not used
        descP->readersQ.first = NULL;
        descP->readersQ.last  = NULL;
        descP->readPkgCnt     = 0;
        descP->readPkgMax     = 0;
        descP->readPkgMaxCnt  = 0;
        descP->readByteCnt    = 0;
        descP->readTries      = 0;
        descP->readWaits      = 0;
        descP->readFails      = 0;
        sprintf(buf, "esock.acc[%d]", sock);
        requestor_init(&descP->currentAcceptor);
        descP->currentAcceptorP = NULL; // currentAcceptor not used
        descP->acceptorsQ.first = NULL;
        descP->acceptorsQ.last  = NULL;
        descP->accSuccess       = 0;
        descP->accFails         = 0;
        descP->accTries         = 0;
        descP->accWaits         = 0;

        sprintf(buf, "esock.close[%d]", sock);
        descP->closing          = FALSE;
        descP->closeEnv         = NULL;
        descP->closeRef         = esock_atom_undefined;
        enif_set_pid_undefined(&descP->closerPid);
        MON_INIT(&descP->closerMon);

        sprintf(buf, "esock.cfg[%d]", sock);
        descP->rBufSz         = ESOCK_RECV_BUFFER_SIZE_DEFAULT;
        descP->rNum           = ESOCK_RECV_BUFFER_COUNT_DEFAULT;
        descP->rNumCnt        = 0;
        descP->rCtrlSz        = ESOCK_RECV_CTRL_BUFFER_SIZE_DEFAULT;
        descP->wCtrlSz        = ESOCK_SEND_CTRL_BUFFER_SIZE_DEFAULT;
        descP->iow            = FALSE;
        descP->dbg            = ESOCK_DEBUG_DEFAULT;       // Overwritten by caller
        descP->useReg         = ESOCK_USE_SOCKET_REGISTRY; // Overwritten by caller
        descP->meta.env       = esock_alloc_env("alloc_descriptor - "
                                                "meta-env");
        descP->meta.ref       = esock_atom_undefined;

        descP->sock           = sock;
        descP->event          = event;
        descP->origFD         = INVALID_SOCKET;
        descP->closeOnClose   = TRUE;

        enif_set_pid_undefined(&descP->ctrlPid);
        MON_INIT(&descP->ctrlMon);

    }

    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);

    /* *** Domain counter *** */
    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

    /* *** Type counter *** */
    if (type == SOCK_STREAM)
        cnt_dec(&data.numTypeStreams, 1);
    else if (type == SOCK_DGRAM)
        cnt_dec(&data.numTypeDGrams, 1);
#ifdef SOCK_SEQPACKET
    else if (type == SOCK_SEQPACKET)
        cnt_dec(&data.numTypeSeqPkgs, 1);
#endif

    /* *** Protocol counter *** */
    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)
{
    cnt_inc(&data.numSockets, 1);
    
    /* *** Domain counter *** */
    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

    /* *** Type counter *** */
    if (type == SOCK_STREAM)
        cnt_inc(&data.numTypeStreams, 1);
    else if (type == SOCK_DGRAM)
        cnt_inc(&data.numTypeDGrams, 1);
#ifdef SOCK_SEQPACKET
    else if (type == SOCK_SEQPACKET)
        cnt_inc(&data.numTypeSeqPkgs, 1);
#endif

    /* *** Protocol counter *** */
    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
}


#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 ESOCK_DOMAIN_INET:
        *domain = AF_INET;
        break;

#if defined(HAVE_IN6) && defined(AF_INET6)
    case ESOCK_DOMAIN_INET6:
        *domain = AF_INET6;
        break;
#endif
#ifdef HAVE_SYS_UN_H
    case ESOCK_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 ESOCK_TYPE_STREAM:
        *type = SOCK_STREAM;
        break;

    case ESOCK_TYPE_DGRAM:
        *type = SOCK_DGRAM;
        break;

    case ESOCK_TYPE_RAW:
        *type = SOCK_RAW;
        break;

#ifdef SOCK_SEQPACKET
    case ESOCK_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 ESOCK_PROTOCOL_DEFAULT:
            *proto = 0; // default - note that _IP also has the value 0...
            break;
            
        case ESOCK_PROTOCOL_IP:
            *proto = IPPROTO_IP;
            break;
            
        case ESOCK_PROTOCOL_TCP:
            *proto = IPPROTO_TCP;
            break;
            
        case ESOCK_PROTOCOL_UDP:
            *proto = IPPROTO_UDP;
            break;
            
#if defined(HAVE_SCTP)
        case ESOCK_PROTOCOL_SCTP:
            *proto = IPPROTO_SCTP;
            break;
#endif
            
        case ESOCK_PROTOCOL_ICMP:
            *proto = IPPROTO_ICMP;
            break;
            
        case ESOCK_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
/* esock_open4_get_netns - extract the netns field from the opts map
 */
static
BOOLEAN_T esock_open4_get_netns(ErlNifEnv* env, ERL_NIF_TERM opts, char** netns)
{
    ERL_NIF_TERM val;
    ErlNifBinary bin;
    char*        buf;

    /* The currently only supported extra option is: netns */
    if (!GET_MAP_VAL(env, opts, atom_netns, &val)) {
        *netns = NULL; // Just in case...
        return FALSE;
    }

    /* The value should be a binary file name */
    if (! enif_inspect_binary(env, val, &bin)) {
        *netns = NULL; // Just in case...
        return FALSE;
    }

    if ((buf = MALLOC(bin.size+1)) == NULL) {
        *netns = NULL; // Just in case...
        return FALSE;
    }

    sys_memcpy(buf, bin.data, bin.size);
    buf[bin.size] = '\0';
    *netns = buf;
    return TRUE;
}
#endif


/* esendflags2sendflags - convert internal (erlang) send flags to (native)
 * send flags.
 *
 * We should really have a way to point out the faulty flag if we get one
 * we don't support. Or add something to the supports function.
 */
static
BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags)
{
    int tmp = 0;

    /* Optimize for no flags */
    if (eflags == 0) {
        *flags = 0;
        return TRUE;
    }

    /* Check for flags out of range */
    if ((eflags & ~ESOCK_SEND_FLAG_MASK) != 0) {
        esock_warning_msg("Use of unknown send flag (0x%lX)\r\n",
                          eflags);
    }

    if ((eflags & ESOCK_SEND_FLAG_CONFIRM) != 0) {
#if defined(MSG_CONFIRM)
        tmp |= MSG_CONFIRM;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_SEND_FLAG_DONTROUTE) != 0) {
#if defined(MSG_DONTROUTE)
        tmp |= MSG_DONTROUTE;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_SEND_FLAG_EOR) != 0) {
#if defined(MSG_EOR)
        tmp |= MSG_EOR;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_SEND_FLAG_MORE) != 0) {
#if defined(MSG_MORE)
        tmp |= MSG_MORE;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_SEND_FLAG_NOSIGNAL) != 0) {
#if defined(MSG_NOSIGNAL)
        tmp |= MSG_NOSIGNAL;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_SEND_FLAG_OOB) != 0) {
#if defined(MSG_OOB)
        tmp |= MSG_OOB;
#else
        return FALSE;
#endif
    }

    *flags = tmp;
    return TRUE;
}


/* erecvflags2recvflags - convert internal (erlang) send flags to (proper)
 * send flags.
 *
 * We should really have a way to point out the faulty flag if we get one
 * we don't support. Or add something to the supports function.
 */
static
BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags)
{
    int tmp = 0;


    /* Optimize for no flags */
    if (eflags == 0) {
        *flags = 0;
        return TRUE;
    }

    if ((eflags & ~ESOCK_RECV_FLAG_MASK) != 0) {
        esock_warning_msg("Use of unknown recv flag (0x%lX)\r\n",
                          eflags);
    }

    if ((eflags & ESOCK_RECV_FLAG_CMSG_CLOEXEC) != 0) {
#if defined(MSG_CMSG_CLOEXEC)
        tmp |= MSG_CMSG_CLOEXEC;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_RECV_FLAG_ERRQUEUE) != 0) {
#if defined(MSG_ERRQUEUE)
        tmp |= MSG_ERRQUEUE;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_RECV_FLAG_OOB) != 0) {
#if defined(MSG_OOB)
        tmp |= MSG_OOB;
#else
        return FALSE;
#endif
    }

    /*
     * <KOLLA>
     *
     * We need to handle this, because it may effect the read algorithm
     *
     * </KOLLA>
     */
    if ((eflags & ESOCK_RECV_FLAG_PEEK) != 0) {
#if defined(MSG_PEEK)
        tmp |= MSG_PEEK;
#else
        return FALSE;
#endif
    }

    if ((eflags & ESOCK_RECV_FLAG_TRUNC) != 0) {
#if defined(MSG_TRUNC)
        tmp |= MSG_TRUNC;
#else
        return FALSE;
#endif
    }

    *flags = tmp;
    return TRUE;
}



/* ehow2how - convert internal (erlang) "shutdown how" to
 * (proper) "shutdown how"
 */
static
BOOLEAN_T ehow2how(unsigned int ehow, int* how)
{
     switch (ehow) {
     case ESOCK_SHUTDOWN_HOW_RD:
         *how = SHUT_RD;
         break;

     case ESOCK_SHUTDOWN_HOW_WR:
         *how = SHUT_WR;
         break;

     case ESOCK_SHUTDOWN_HOW_RDWR:
         *how = SHUT_RDWR;
         break;

     default:
         return FALSE;
     }

     return TRUE;
}



/* ecommand2command - convert erlang command to "native" command (and data)
 */
static
BOOLEAN_T ecommand2command(ErlNifEnv*    env,
                           ERL_NIF_TERM  ecommand,
                           Uint16*       command,
                           ERL_NIF_TERM* edata)
{
    size_t       sz;
    ERL_NIF_TERM ecmd;

    if (!IS_MAP(env, ecommand)) {
        SGDBG( ("SOCKET", "ecommand2command -> (e)command not a map\r\n") );
        return FALSE;
    }

    /* The map shall have exactly two attrbutes: 
     *          'command' and 'data'
     */
    if (!enif_get_map_size(env, ecommand, &sz) || (sz != 2)) {
        SGDBG( ("SOCKET", "ecommand2command -> comamnd map size invalid\r\n") );
        return FALSE;
    }

    /* Get the command value, and transform into integer
     * (might as well do that, since theer is no point in
     *  extracting the data if command is invalid).
     */
    if (!GET_MAP_VAL(env, ecommand, esock_atom_command, &ecmd)) {
        SGDBG( ("SOCKET", "ecommand2command -> command attribute not found\r\n") );
        return FALSE;
    }
    if (COMPARE(ecmd, esock_atom_debug) == 0) {
        *command = ESOCK_CMD_DEBUG;
    } else if (COMPARE(ecmd, atom_socket_debug) == 0) {
        *command = ESOCK_CMD_SOCKET_DEBUG;
    } else if (COMPARE(ecmd, atom_use_registry) == 0) {
        *command = ESOCK_CMD_USE_SOCKET_REGISTRY;
    } else {
        SGDBG( ("SOCKET", "ecommand2command -> unknown command %T\r\n", ecmd) );
        return FALSE;
    }

    /* Get the command data value, we do *not* convert it to 
     * the native form (here) since it may "in theory" be complex.
     */
    if (!GET_MAP_VAL(env, ecommand, esock_atom_data, edata)) {
        SGDBG( ("SOCKET", "ecommand2command -> (command) data not found\r\n") );
        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




/* ===========================================================================
 *
 *                   Socket Registry message functions
 *
 * ===========================================================================
 */

/* Send a (socket) add message to the socket registry process.
 * We know that this process *is* alive since the VM would
 * terminate otherwise, so there is no need to test if
 * the sending fails.
 */
static
void esock_send_reg_add_msg(ErlNifEnv*   env,
                            ERL_NIF_TERM sockRef)
{
    ERL_NIF_TERM msg = mk_reg_add_msg(env, sockRef);

    esock_send_msg(env, &data.regPid, msg, NULL);
}



/* Send a (socket) del message to the socket registry process.
 * We know that this process *is* alive since the VM would
 * terminate otherwise, so there is no need to test if
 * the sending fails.
 */
static
void esock_send_reg_del_msg(ErlNifEnv*   env,
                            ERL_NIF_TERM sockRef)
{
    ERL_NIF_TERM msg = mk_reg_del_msg(env, sockRef);

    esock_send_msg(env, &data.regPid, msg, NULL);
}




/* ===========================================================================
 *
 *                   Socket user message functions
 *
 * ===========================================================================
 */

/* Send an counter wrap message to the controlling process:
 * A message in the form:
 *
 *     {'$socket', Socket, counter_wrap, Counter :: atom()}
 *
 * This message will only be sent if the iow (Inform On Wrap) is TRUE.
 */
static
char* esock_send_wrap_msg(ErlNifEnv*       env,
                          ESockDescriptor* descP,
                          ERL_NIF_TERM     sockRef,
                          ERL_NIF_TERM     cnt)
{
    ERL_NIF_TERM msg = mk_wrap_msg(env, sockRef, cnt);
    
    return esock_send_msg(env, &descP->ctrlPid, msg, NULL);
}


/* Send an close message to the specified process:
 * A message in the form:
 *
 *     {'$socket', Socket, 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,
                           ESockDescriptor* descP,
                           ErlNifPid*       pid)
{
    ERL_NIF_TERM sockRef, msg;

    sockRef = enif_make_resource(descP->closeEnv, descP);
    msg     = mk_close_msg(descP->closeEnv, sockRef, descP->closeRef);

    return esock_send_msg(env, pid, msg, descP->closeEnv);
}


/* Send an abort message to the specified process:
 * A message in the form:
 *
 *     {'$socket', Socket, 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 opRef,
                           ErlNifEnv*   msgEnv,
                           ERL_NIF_TERM reason,
                           ErlNifPid*   pid)
{
    ERL_NIF_TERM msg = mk_abort_msg(msgEnv,
                                    /* sockRef not in msgEnv so copy */
                                    CP_TERM(msgEnv, sockRef),
                                    opRef, reason);

    return esock_send_msg(env, pid, msg, msgEnv);
}


/* Send a message to the specified process.
 */
static
char* esock_send_msg(ErlNifEnv*   env,
                     ErlNifPid*   pid,
                     ERL_NIF_TERM msg,
                     ErlNifEnv*   msgEnv)
{
    int res = enif_send(env, pid, msgEnv, msg);
    esock_free_env("esock_msg_send - msg-env", msgEnv);

    if (!res)
        return str_exsend;
    else
        return NULL;
}



/* *** mk_reg_add_msg ***
 *
 * Construct a socket add message for the socket registry.
 *
 *         {'$socket', add, Socket}
 *
 */
static
ERL_NIF_TERM mk_reg_add_msg(ErlNifEnv*   env,
                            ERL_NIF_TERM sockRef)
{
    return mk_reg_msg(env, atom_add, sockRef);
}


/* *** mk_reg_del_msg ***
 *
 * Construct a socket del message for the socket registry.
 *
 *         {'$socket', del, Socket}
 *
 */
static
ERL_NIF_TERM mk_reg_del_msg(ErlNifEnv*   env,
                            ERL_NIF_TERM sockRef)
{
    return mk_reg_msg(env, atom_del, sockRef);
}


/* *** mk_reg_msg ***
 *
 * Construct a general message for the socket registry.
 * Tag is (at this time) either the atom 'add' or the atom 'del'.
 *
 *         {'$socket', Tag, Socket}
 *
 */
static
ERL_NIF_TERM mk_reg_msg(ErlNifEnv*   env,
                        ERL_NIF_TERM tag,
                        ERL_NIF_TERM sockRef)
{
    ERL_NIF_TERM socket = mk_socket(env, sockRef);

    return MKT3(env, esock_atom_socket_tag, tag, socket);
}


/* *** mk_abort_msg ***
 *
 * Create the abort message, which has the following form:
 *
 *     {'$socket', Socket, abort, {OpRef, Reason}}
 *
 * This message is for processes that are waiting in the
 * erlang API functions for a select (or this) message.
 */
static
ERL_NIF_TERM mk_abort_msg(ErlNifEnv*   env,
                          ERL_NIF_TERM sockRef,
                          ERL_NIF_TERM opRef,
                          ERL_NIF_TERM reason)
{
    ERL_NIF_TERM info = MKT2(env, opRef, reason);
    
    return mk_socket_msg(env, sockRef, esock_atom_abort, info);
}


/* *** mk_wrap_msg ***
 *
 * Construct a counter wrap (socket) message. It has the form: 
 *
 *         {'$socket', Socket, counter_wrap, Counter}
 *
 */
static
ERL_NIF_TERM mk_wrap_msg(ErlNifEnv*   env,
                         ERL_NIF_TERM sockRef,
                         ERL_NIF_TERM cnt)
{
    return mk_socket_msg(env, sockRef, atom_counter_wrap, cnt);
}


/* *** mk_close_msg ***
 *
 * Construct a close (socket) message. It has the form: 
 *
 *         {'$socket', Socket, close, closeRef}
 *
 */
static
ERL_NIF_TERM mk_close_msg(ErlNifEnv*   env,
                          ERL_NIF_TERM sockRef,
                          ERL_NIF_TERM closeRef)
{
    return mk_socket_msg(env, sockRef, esock_atom_close, closeRef);
}


/* *** mk_select_msg ***
 *
 * Construct a select (socket) message. It has the form: 
 *
 *         {'$socket', Socket, select, selectRef}
 *
 */
static
ERL_NIF_TERM mk_select_msg(ErlNifEnv*   env,
                           ERL_NIF_TERM sockRef,
                           ERL_NIF_TERM selectRef)
{
    return mk_socket_msg(env, sockRef, atom_select, selectRef);
}


/* *** mk_socket_msg ***
 *
 * Construct the socket message:
 *
 *         {'$socket', Socket, Tag, Info}
 *
 * Socket :: socket:socket()
 * Tag    :: atom()
 * Info   :: term()
 *
 */
static
ERL_NIF_TERM mk_socket_msg(ErlNifEnv*   env,
                           ERL_NIF_TERM sockRef,
                           ERL_NIF_TERM tag,
                           ERL_NIF_TERM info)
{
    ERL_NIF_TERM socket = mk_socket(env, sockRef);

    return MKT4(env, esock_atom_socket_tag, socket, tag, info);
}


/* *** mk_socket ***
 *
 * Simple utility function that construct the socket tuple:
 *
 *     socket:socket() :: {'$socket', SockRef :: reference()}
 */
static
ERL_NIF_TERM mk_socket(ErlNifEnv*   env,
                       ERL_NIF_TERM sockRef)
{
    return MKT2(env, esock_atom_socket_tag, sockRef);
}

#endif // #if defined(__WIN32__)

                              
/* ----------------------------------------------------------------------
 *  S e l e c t   W r a p p e r   F u n c t i o n s
 * ----------------------------------------------------------------------
 */

/* *** esock_select_read ***
 *
 * Perform a read select. When the select is triggered, a 'select'
 * message (see mk_select_msg) will be sent.
 *
 * There are two ways to handle the select message:
 * 1) Create "your own" environment and create the message using it
 *    and then pass it on to the select function.
 * 2) Or, to create the message using any available environment,
 *    and then pass a NULL pointer to the select function.
 *    This will have the effect that the select function will 
 *    create its own environment and then copy the message to it.
 * We choose the second alternative.
 */
static
int esock_select_read(ErlNifEnv*       env,
                      ErlNifEvent      event,     // The file descriptor
                      void*            obj,       // The socket descriptor object
                      const ErlNifPid* pid,       // Destination
                      ERL_NIF_TERM     sockRef,   // Socket
                      ERL_NIF_TERM     selectRef) // "ID" of the operation
{
    ERL_NIF_TERM selectMsg = mk_select_msg(env, sockRef, selectRef);

    return enif_select_read(env, event, obj, pid, selectMsg, NULL);

}


/* *** esock_select_write ***
 *
 * Perform a write select. When the select is triggered, a 'select'
 * message (see mk_select_msg) will be sent.
 * The sockRef is copied to the msgEnv when the socket message is created,
 * so no need to do that here, but the selectRef needs to be copied.
 */
static
int esock_select_write(ErlNifEnv*       env,
                       ErlNifEvent      event,     // The file descriptor
                       void*            obj,       // The socket descriptor
                       const ErlNifPid* pid,       // Destination
                       ERL_NIF_TERM     sockRef,   // Socket
                       ERL_NIF_TERM     selectRef) // "ID" of the operation
{
    ERL_NIF_TERM selectMsg = mk_select_msg(env, sockRef, selectRef);

    return enif_select_write(env, event, obj, pid, selectMsg, NULL);
}


/* *** esock_select_stop ***
 *
 * WARNING: enif_select may call esock_stop directly
 * in which case deadlock is avoided by esock_stop that checks
 * if it got a direct call and then does not lock readMtx and writeMtx.
 *
 * So readMtx and writeMtx are supposed to be locked
 * when this function is called.
 */
static
int esock_select_stop(ErlNifEnv*  env,
                      ErlNifEvent event,
                      void*       obj)
{
    return enif_select(env, event, (ERL_NIF_SELECT_STOP), obj, NULL,
                       esock_atom_undefined);
}

static
int esock_select_cancel(ErlNifEnv*             env,
                        ErlNifEvent            event,
                        enum ErlNifSelectFlags mode,
                        void*                  obj)
{
    return enif_select(env, event, (ERL_NIF_SELECT_CANCEL | mode), obj, NULL,
                       esock_atom_undefined);
}


/* ----------------------------------------------------------------------
 *  A c t i v a t e   N e x t   ( o p e r a t o r )   F u n c t i o n s
 * ----------------------------------------------------------------------
 */

/* *** activate_next_acceptor ***
 * *** activate_next_writer   ***
 * *** activate_next_reader   ***
 *
 * This functions pops the requestors queue and then selects until it 
 * manages to successfully activate a requestor or the queue is empty.
 * Return value indicates if a new requestor was activated or not.
 */

#if !defined(__WIN32__)

#define ACTIVATE_NEXT_FUNCS                                               \
    ACTIVATE_NEXT_FUNC_DECL(acceptor, read,  currentAcceptor, acceptorsQ) \
    ACTIVATE_NEXT_FUNC_DECL(writer,   write, currentWriter,   writersQ)   \
    ACTIVATE_NEXT_FUNC_DECL(reader,   read,  currentReader,   readersQ)

#define ACTIVATE_NEXT_FUNC_DECL(F, S, R, Q)                  \
    static                                                   \
    BOOLEAN_T activate_next_##F(ErlNifEnv*       env,        \
                                ESockDescriptor* descP,      \
                                ERL_NIF_TERM     sockRef)    \
    {                                                        \
        BOOLEAN_T          popped, activated;                \
        int                sres;                             \
        ERL_NIF_TERM       reason;                           \
        ESockRequestor*    reqP = &descP->R;                 \
        ESockRequestQueue* q    = &descP->Q;                 \
                                                             \
        popped = FALSE;                                      \
        do {                                                 \
                                                             \
            if (requestor_pop(q, reqP)) {                    \
                                                             \
                /* There was another one */                  \
                                                             \
                SSDBG( descP,                                           \
                       ("SOCKET",                                       \
                        "activate_next_" #F "(%T) {%d} ->"              \
                        " new (active) requestor: "                     \
                        "\r\n   pid: %T"                                \
                        "\r\n   ref: %T"                                \
                        "\r\n", sockRef, descP->sock,                   \
                        reqP->pid, reqP->ref) );                        \
                                                                        \
                /* We need to copy req ref to 'env' */                  \
                if ((sres =                                             \
                     esock_select_##S(env, descP->sock, descP,          \
                                      &reqP->pid, sockRef,              \
                                      CP_TERM(env, reqP->ref))) < 0) {  \
                                                                        \
                    /* We need to inform this process, reqP->pid,  */   \
                    /* that we failed to select, so we don't leave */   \
                    /* it hanging.                                 */   \
                    /* => send abort                               */   \
                                                                        \
                    reason = MKT2(env,                                  \
                                  esock_atom_select_failed,             \
                                  MKI(env, sres));                      \
                    esock_send_abort_msg(env, sockRef,                  \
                                         reqP->ref, reqP->env,          \
                                         reason, &reqP->pid);           \
                    reqP->env = NULL;                                   \
                                                                        \
                } else {                                                \
                                                                        \
                    /* Success: New requestor selected */               \
                    popped    = TRUE;                                   \
                    activated = TRUE;                                   \
                                                                        \
                }                                                       \
                                                                        \
            } else {                                                    \
                                                                        \
                SSDBG( descP,                                           \
                       ("SOCKET",                                       \
                        "activate_next_" #F "(%T) {%d} ->"              \
                        " no more requestors\r\n",                      \
                        sockRef, descP->sock) );                        \
                                                                        \
                popped    = TRUE;                                       \
                activated = FALSE;                                      \
            }                                                           \
                                                                        \
        } while (!popped);                                              \
                                                                        \
        SSDBG( descP,                                                   \
               ("SOCKET", "activate_next_" #F "(%T) {%d} -> "           \
                "done with %s\r\n",                                     \
                sockRef, descP->sock, B2S(activated)) );                \
                                                                        \
        return activated;                                               \
    }
ACTIVATE_NEXT_FUNCS
#undef ACTIVATE_NEXT_FUNC_DECL


#endif // if !defined(__WIN32__)


/* ----------------------------------------------------------------------
 *  R e q u e s t o r   Q u e u e   F u n c t i o n s
 * ----------------------------------------------------------------------
 *
 * Since each of these functions (search4pid, push, pop and unqueue
 * are virtually identical for acceptors, writers and readers, 
 * we make use of set of declaration macros.
 */

#if !defined(__WIN32__)

/* *** acceptor_search4pid ***
 * *** writer_search4pid   ***
 * *** reader_search4pid   ***
 *
 * Search for a pid in the requestor (acceptor, writer, or reader) queue.
 *
 */

#define REQ_SEARCH4PID_FUNCS                       \
    REQ_SEARCH4PID_FUNC_DECL(acceptor, acceptorsQ) \
    REQ_SEARCH4PID_FUNC_DECL(writer,   writersQ)   \
    REQ_SEARCH4PID_FUNC_DECL(reader,   readersQ)

#define REQ_SEARCH4PID_FUNC_DECL(F, Q)                 \
    static                                             \
    BOOLEAN_T F##_search4pid(ErlNifEnv*       env,     \
                             ESockDescriptor* descP,   \
                             ErlNifPid*       pid)     \
    {                                                  \
        return qsearch4pid(env, &descP->Q, pid);       \
    }
REQ_SEARCH4PID_FUNCS
#undef REQ_SEARCH4PID_FUNC_DECL



/* *** acceptor_push ***
 * *** writer_push   ***
 * *** reader_push   ***
 *
 * Push a requestor (acceptor, writer, or reader) onto its queue.
 * This happens when we already have a current request (of its type).
 *
 */

#define REQ_PUSH_FUNCS                       \
    REQ_PUSH_FUNC_DECL(acceptor, acceptorsQ) \
    REQ_PUSH_FUNC_DECL(writer,   writersQ)   \
    REQ_PUSH_FUNC_DECL(reader,   readersQ)

#define REQ_PUSH_FUNC_DECL(F, Q)                                       \
    static                                                             \
    ERL_NIF_TERM F##_push(ErlNifEnv*       env,                        \
                          ESockDescriptor* descP,                      \
                          ErlNifPid        pid,                        \
                          ERL_NIF_TERM     ref)                        \
    {                                                                  \
        ESockRequestQueueElement* e    = MALLOC(sizeof(ESockRequestQueueElement)); \
        ESockRequestor*           reqP = &e->data;                     \
                                                                       \
        reqP->pid = pid;                                               \
        if (MONP("reader_push -> " #F " request",                      \
                 env, descP, &pid, &reqP->mon) != 0) {                 \
            FREE(e);                                                   \
            return esock_make_error(env, atom_exmonitor);              \
        }                                                              \
        reqP->env = esock_alloc_env(#F "_push");                       \
        reqP->ref = CP_TERM(reqP->env, ref);                           \
                                                                       \
        qpush(&descP->Q, e);                                           \
                                                                       \
        return esock_make_error(env, esock_atom_eagain);               \
    }
REQ_PUSH_FUNCS
#undef REQ_PUSH_FUNC_DECL



/* *** acceptor_pop ***
 * *** writer_pop   ***
 * *** reader_pop   ***
 *
 * Pop a requestor (acceptor, writer, or reader) from its queue.
 *
 */
#define REQ_POP_FUNCS                       \
    REQ_POP_FUNC_DECL(acceptor, acceptorsQ) \
    REQ_POP_FUNC_DECL(writer,   writersQ)   \
    REQ_POP_FUNC_DECL(reader,   readersQ)

#define REQ_POP_FUNC_DECL(F, Q)                \
    static                                     \
    BOOLEAN_T F##_pop(ErlNifEnv*       env,    \
                      ESockDescriptor* descP,  \
                      ESockRequestor*  reqP)   \
    {                                          \
        return requestor_pop(&descP->Q, reqP); \
    }
REQ_POP_FUNCS
#undef REQ_POP_FUNC_DECL



/* *** acceptor_unqueue ***
 * *** writer_unqueue   ***
 * *** reader_unqueue   ***
 *
 * Remove a requestor (acceptor, writer, or reader) from its queue.
 *
 */

#define REQ_UNQUEUE_FUNCS                       \
    REQ_UNQUEUE_FUNC_DECL(acceptor, acceptorsQ) \
    REQ_UNQUEUE_FUNC_DECL(writer,   writersQ)   \
    REQ_UNQUEUE_FUNC_DECL(reader,   readersQ)

#define REQ_UNQUEUE_FUNC_DECL(F, Q)                            \
    static                                                     \
    BOOLEAN_T F##_unqueue(ErlNifEnv*       env,                \
                          ESockDescriptor* descP,              \
                          const ErlNifPid* pid)                \
    {                                                          \
        return qunqueue(env, descP, "qunqueue -> waiting " #F, \
                        &descP->Q, pid);                       \
    }
REQ_UNQUEUE_FUNCS
#undef REQ_UNQUEUE_FUNC_DECL



/* *** requestor pop ***
 *
 * Pop an requestor from its queue.
 */
static
BOOLEAN_T requestor_pop(ESockRequestQueue* q,
                        ESockRequestor*    reqP)
{
    ESockRequestQueueElement* e = qpop(q);

    esock_free_env("requestor_pop", reqP->env);

    if (e != NULL) {
        reqP->pid = e->data.pid;
        reqP->mon = e->data.mon;
        reqP->env = e->data.env;
        reqP->ref = e->data.ref;
        FREE(e);
        return TRUE;
    } else {
        /* Queue was empty */
        requestor_init(reqP);
        return FALSE;
    }
    
}

static void requestor_init(ESockRequestor* reqP) {
    enif_set_pid_undefined(&reqP->pid);
    MON_INIT(&reqP->mon);
    reqP->env = NULL;
    reqP->ref = esock_atom_undefined;
}

static int requestor_release(const char*      slogan,
                             ErlNifEnv*       env,
                             ESockDescriptor* descP,
                             ESockRequestor* reqP) {
    int res;

    enif_set_pid_undefined(&reqP->pid);
    res = DEMONP(slogan, env, descP, &reqP->mon);
    esock_free_env(slogan, reqP->env);
    reqP->env = NULL;
    reqP->ref = esock_atom_undefined;

    return res;
}


static
BOOLEAN_T qsearch4pid(ErlNifEnv*         env,
                      ESockRequestQueue* q,
                      ErlNifPid*         pid)
{
    ESockRequestQueueElement* tmp = q->first;
    
    while (tmp != NULL) {
        if (COMPARE_PIDS(&tmp->data.pid, pid) == 0)
            return TRUE;
        else
            tmp = tmp->nextP;
    }

    return FALSE;
}


static
void qpush(ESockRequestQueue*        q,
           ESockRequestQueueElement* 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
ESockRequestQueueElement* qpop(ESockRequestQueue* q)
{
    ESockRequestQueueElement* 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,
                   ESockDescriptor*   descP,
                   const char*        slogan,
                   ESockRequestQueue* q,
                   const ErlNifPid*   pid)
{
    ESockRequestQueueElement* e = q->first;
    ESockRequestQueueElement* p = NULL;

    /* Check if it was one of the waiting acceptor processes */
    while (e != NULL) {
        if (COMPARE_PIDS(&e->data.pid, pid) == 0) {

            /* 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;
                }
            }

            esock_free_env("qunqueue", e->data.env);
            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(ESockCounter* cnt, ESockCounter inc)
{
    BOOLEAN_T    wrap;
    ESockCounter max     = ESOCK_COUNTER_MAX;
    ESockCounter current = *cnt;

    if ((max - inc) >= current) {
        *cnt += inc;
        wrap  = FALSE;
    } else {
        *cnt = inc - (max - current) - 1;
        wrap = TRUE;
    }

    return (wrap);
}


static
void cnt_dec(ESockCounter* cnt, ESockCounter dec)
{
    ESockCounter 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,
                  ESockDescriptor* descP,
                  const ErlNifPid* pid,
                  ESockMonitor*    monP)
{
    int res;

    SSDBG( descP, ("SOCKET",
                   "esock_monitor {%d} [%T] %s: try monitor\r\n",
                   descP->sock, esock_self(env), slogan) );

    res = enif_monitor_process(env, descP, pid, &monP->mon);

    if (res != 0) {
        monP->isActive = FALSE;

        SSDBG( descP,
               ("SOCKET",
                "esock_monitor {%d} [%T] %s: monitor failed: %d\r\n",
                descP->sock, esock_self(env), slogan, res) );
    } else {
        monP->isActive = TRUE;

        SSDBG( descP,
               ("SOCKET",
                "esock_monitor {%d} [%T] %s: monitor ok: %T\r\n",
                descP->sock, esock_self(env), slogan,
                esock_make_monitor_term(env, monP)) );
    }

    return res;
}


static
int esock_demonitor(const char*      slogan,
                    ErlNifEnv*       env,
                    ESockDescriptor* descP,
                    ESockMonitor*    monP)
{
    int res;

    if (! monP->isActive)
        return 1;

    SSDBG( descP, ("SOCKET",
                   "esock_demonitor {%d} [%T] %s: try demonitor %T\r\n",
                   descP->sock, esock_self(env), slogan,
                   esock_make_monitor_term(env, monP)) );

    res = enif_demonitor_process(env, descP, &monP->mon);
    esock_monitor_init(monP);

    if (res != 0) {
        SSDBG( descP,
               ("SOCKET",
                "esock_demonitor {%d}[%T] %s: demonitor failed: %d\r\n",
                descP->sock, esock_self(env), slogan, res) );
    }

    return res;
}


static
void esock_monitor_init(ESockMonitor* monP)
{
    monP->isActive = FALSE;
}


static
ERL_NIF_TERM esock_make_monitor_term(ErlNifEnv* env, const ESockMonitor* monP)
{
    if (monP->isActive)
        return enif_make_monitor_term(env, &monP->mon);
    else
        return esock_atom_undefined;
}

static BOOLEAN_T esock_monitor_eq(const ESockMonitor* monP,
                                  const ErlNifMonitor* mon) {
    if (monP->isActive)
        return enif_compare_monitors(&monP->mon, mon) == 0;
    else
        return FALSE;
}

#endif // if !defined(__WIN32__)



/* ----------------------------------------------------------------------
 *  C a l l b a c k   F u n c t i o n s
 * ----------------------------------------------------------------------
 */


static void free_request_queue(ESockRequestQueue* q)
{
    while (q->first) {
        ESockRequestQueueElement* free_me = q->first;
        q->first = free_me->nextP;
        esock_free_env("dtor", free_me->data.env);
        FREE(free_me);
    }
}

/* =========================================================================
 * esock_dtor - Callback function for resource destructor
 *
 */
static
void esock_dtor(ErlNifEnv* env, void* obj)
{
#if !defined(__WIN32__)    
  ESockDescriptor* descP = (ESockDescriptor*) obj;

  SGDBG( ("SOCKET", "dtor -> try destroy read mutex\r\n") );
  MDESTROY(descP->readMtx);  descP->readMtx  = NULL;

  SGDBG( ("SOCKET", "dtor -> try destroy write mutex\r\n") );
  MDESTROY(descP->writeMtx); descP->writeMtx = NULL;

  esock_free_env("dtor reader", descP->currentReader.env);
  descP->currentReader.env = NULL;

  esock_free_env("dtor writer", descP->currentWriter.env);
  descP->currentWriter.env = NULL;

  esock_free_env("dtor acceptor", descP->currentAcceptor.env);
  descP->currentAcceptor.env = NULL;

  SGDBG( ("SOCKET", "dtor -> try free readers request queue\r\n") );
  free_request_queue(&descP->readersQ);

  SGDBG( ("SOCKET", "dtor -> try free writers request queue\r\n") );
  free_request_queue(&descP->writersQ);

  SGDBG( ("SOCKET", "dtor -> try free acceptors request queue\r\n") );
  free_request_queue(&descP->acceptorsQ);

  esock_free_env("dtor close-env", descP->closeEnv);
  descP->closeEnv = NULL;

  esock_free_env("dtor meta-env", descP->meta.env);
  descP->meta.env = NULL;

  if (! IS_CLOSED(descP)) {
      SGDBG( ("SOCKET", "dtor -> had to close socket\r\n") );
      (void) esock_close_socket(env, descP);
  }

  SGDBG( ("SOCKET", "dtor -> set state and pattern\r\n") );
  descP->readState |= ESOCK_STATE_DTOR;
  descP->writeState |= ESOCK_STATE_DTOR;
  descP->pattern = ESOCK_DESC_PATTERN_DTOR;  

  SGDBG( ("SOCKET", "dtor -> done\r\n") );
#endif
}


/* =========================================================================
 * esock_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).
 *
 */
static
void esock_stop(ErlNifEnv* env, void* obj, ErlNifEvent fd, int is_direct_call)
{
#if !defined(__WIN32__)
    ESockDescriptor* descP = (ESockDescriptor*) obj;

    /* If we are called with a direct call;
     * we already have readMtx and writeMtx
     */
    if (!is_direct_call) {
        MLOCK(descP->readMtx);
        MLOCK(descP->writeMtx);
    }
    
    SSDBG( descP, ("SOCKET", "esock_stop {%d/%d} -> when %s"
                   "\r\n   ctrlPid:      %T"
                   "\r\n   closerPid:    %T"
                   "\r\ncounters:"
                   "\r\n   writePkgCnt:  %u"
                   "\r\n   writePkgMax:  %u"
                   "\r\n   writeByteCnt: %u"
                   "\r\n   writeTries:   %u"
                   "\r\n   writeWaits:   %u"
                   "\r\n   writeFails:   %u"
                   "\r\n   readPkgCnt:   %u"
                   "\r\n   readPkgMax:   %u"
                   "\r\n   readByteCnt:  %u"
                   "\r\n   readTries:    %u"
                   "\r\n   readWaits:    %u"
                   "\r\n",
                   descP->sock, fd,
                   (is_direct_call) ? "called" : "scheduled",
                   descP->ctrlPid,
                   descP->closerPid,
                   //
                   descP->writePkgCnt,
                   descP->writePkgMax,
                   descP->writeByteCnt,
                   descP->writeTries,
                   descP->writeWaits,
                   descP->writeFails,
                   descP->readPkgCnt,
                   descP->readPkgMax,
                   descP->readByteCnt,
                   descP->readTries,
                   descP->readWaits) );

    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     *
     *           Inform waiting Closer, or close socket
     *
     * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     */

    if (enif_is_pid_undefined(&descP->closerPid)) {
        int err;

        /* We do not have a closer process
         * - we have to do an unclean (non blocking) close */

        err = esock_close_socket(env, descP);

        if (err != 0)
            esock_warning_msg("Failed closing socket for terminating "
                              "controlling process: "
                              "\r\n   Controlling Process: %T"
                              "\r\n   Descriptor:          %d"
                              "\r\n   Errno:               %d (%T)"
                              "\r\n",
                              descP->ctrlPid, descP->sock,
                              err,
                              MKA(env, erl_errno_id(err)));
    } else {

        /* We have a closer process, so this is nif_close */

        if (is_direct_call) {

            /* We do not send a message to the closer process since it will
             * be informed with the return value from nif_close,
             * but we need to free the environment here as
             * the message send in the else branch below does
             */

            esock_free_env("esock_stop - close-env", descP->closeEnv);

        } else {
            char *result;

            /* +++ send close message to the waiting closer +++ */
            result = esock_send_close_msg(env, descP, &descP->closerPid);

            if (result != NULL)
                SSDBG( descP,
                       ("SOCKET",
                        "esock_stop {%d} -> send close failed: %s"
                        "\r\n   closer: %T"
                        "\r\n", descP->sock, result,
                        descP->closerPid) );
        }

        /* Setting closeEnv = NULL is used as a marker for
         * esock_down() that this callback (esock_stop())
         * has been executed and will not close the socket
         */

        descP->closeEnv = NULL;
        descP->closeRef = esock_atom_undefined;
    }

    /* +++++++ Clear the meta option +++++++ */

    enif_clear_env(descP->meta.env);
    descP->meta.ref = esock_atom_undefined;

    SSDBG( descP,
           ("SOCKET",
            "esock_stop {%d/%d} -> done\r\n",
            descP->sock, fd) );

    if (!is_direct_call) {
        MUNLOCK(descP->writeMtx);
        MUNLOCK(descP->readMtx);
    }
#endif // if !defined(__WIN32__)
}



/* *** esock_stop_handle_current ***
 *
 * Handle current requestor (reader, writer or acceptor) during
 * socket stop.
 */
#if !defined(__WIN32__)
static
void esock_stop_handle_current(ErlNifEnv*       env,
                               const char*      role,
                               ESockDescriptor* descP,
                               ERL_NIF_TERM     sockRef,
                               ESockRequestor*  reqP)
{
    DEMONP("esock_stop_handle_current", env, descP, &reqP->mon);

    SSDBG( descP, ("SOCKET",
                   "esock_stop_handle_current {%d} ->"
                   " send abort message to current %s %T %T\r\n",
                   descP->sock, role, reqP->pid, reqP->ref) );

    if (esock_send_abort_msg(env, sockRef, reqP->ref, reqP->env,
                             atom_closed, &reqP->pid) != NULL) {

        esock_warning_msg("esock_stop_handle_current {%d}:"
                          " Failed sending abort (closed) message to"
                          " current %s %T\r\n",
                          descP->sock, role, reqP->pid);
    }

    enif_set_pid_undefined(&reqP->pid);
    reqP->env = NULL;
    reqP->ref = esock_atom_undefined;
}



/* This function traverse the queue and sends the specified
 * nif_abort message with the specified reason to each member,
 * and empty the queue.
 */
static
void inform_waiting_procs(ErlNifEnv*         env,
                          const char*        role,
                          ESockDescriptor*   descP,
                          ERL_NIF_TERM       sockRef,
                          ESockRequestQueue* q,
                          ERL_NIF_TERM       reason)
{
    ESockRequestQueueElement* currentP = q->first;
    ESockRequestQueueElement* nextP;

    SSDBG( descP,
           ("SOCKET",
            "inform_waiting_procs -> handle waiting %s(s)\r\n", role) );

    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 IT'S A FUNDAMENTAL OP...
         *
         * </KOLLA>
         */

        SSDBG( descP,
               ("SOCKET",
                "inform_waiting_procs(%T) {%d} -> "
                "send abort message to waiting %s %T\r\n",
                sockRef, descP->sock,
                role, currentP->data.pid) );

        if (esock_send_abort_msg(env,
                                 sockRef,
                                 currentP->data.ref,
                                 currentP->data.env,
                                 reason,
                                 &currentP->data.pid) != NULL) {

            esock_warning_msg("inform_waiting_procs: "
                              "Failed sending abort (%T) message to "
                              "current %s %T\r\n",
                              reason,
                              role,
                              currentP->data.pid);

        }
        currentP->data.env = NULL,

        DEMONP("inform_waiting_procs -> current 'request'",
               env, descP, &currentP->data.mon);
        nextP = currentP->nextP;
        FREE(currentP);
        currentP = nextP;
    }

    q->first = NULL;
    q->last  = NULL;
}
#endif // if !defined(__WIN32__)


/* =========================================================================
 * esock_down - Callback function for resource down (monitored processes)
 *
 */
static
void esock_down(ErlNifEnv*           env,
                void*                obj,
                const ErlNifPid*     pid,
                const ErlNifMonitor* mon)
{
#if !defined(__WIN32__)
    ESockDescriptor* descP = (ESockDescriptor*) obj;

    MLOCK(descP->readMtx);
    MLOCK(descP->writeMtx);

    SSDBG( descP, ("SOCKET", "esock_down {%d} -> entry with"
                   "\r\n   pid:   %T"
                   "\r\n   Close: %s (%s)"
                   "\r\n",
                   descP->sock, *pid,
                   B2S(IS_CLOSED(descP)),
                   B2S(IS_CLOSING(descP))) );

    if (COMPARE_PIDS(&descP->closerPid, pid) == 0) {

        /* The closer process went down
         * - it will not be able to call nif_finalize_close
         */

        if (MON_EQ(&descP->closerMon, mon)) {
            MON_INIT(&descP->closerMon);

            SSDBG( descP,
                   ("SOCKET",
                    "esock_down {%d} -> closer process exit\r\n",
                    descP->sock) );

        } else if (MON_EQ(&descP->ctrlMon, mon)) {
            MON_INIT(&descP->ctrlMon);

            SSDBG( descP,
                   ("SOCKET",
                    "esock_down {%d} -> closer controlling process exit\r\n",
                    descP->sock) );
        }

        enif_set_pid_undefined(&descP->closerPid);

        if (descP->closeEnv != NULL) {

            /* esock_stop has not been called yet
             * - clear the closer so esock_stop will close the socket */
            esock_free_env("esock_down - close-env", descP->closeEnv);
            descP->closeEnv = NULL;
            descP->closeRef = esock_atom_undefined;

        } else {
            int err;

            /* esock_stop has sent its message to the closer,
             * but the closer died before calling nif_finalize_close
             * - we have to do an unclean (non blocking) socket close here
             */

            err = esock_close_socket(env, descP);

            if (err != 0)
                esock_warning_msg("Failed closing socket for terminating "
                                  "closer process: "
                                  "\r\n   Closer Process: %T"
                                  "\r\n   Descriptor:     %d"
                                  "\r\n   Errno:          %d (%T)"
                                  "\r\n",
                                  pid, descP->sock,
                                  err,
                                  MKA(env, erl_errno_id(err)));
        }
    } else if (MON_EQ(&descP->ctrlMon, mon)) {
        MON_INIT(&descP->ctrlMon);

        /* The owner went down */

        SSDBG( descP,
               ("SOCKET",
                "esock_down {%d} -> controlling process exit\r\n",
                descP->sock) );

        if (IS_OPEN(descP)) {
            int sres;

            /* Socket not closed and no close in progress - initiate close */

            descP->closing = TRUE;
            sres = esock_do_stop(env, descP);

            if (sres < 0)
                /* XXX Should we notify the registry? */
                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:             %T"
                                  "\r\n", sres, pid, descP->sock,
                                  MON2T(env, mon));
        }

    } else if (descP->connectorP != NULL &&
               MON_EQ(&descP->connector.mon, mon)) {
        MON_INIT(&descP->connector.mon);

        SSDBG( descP,
               ("SOCKET",
                "esock_down {%d} -> connector process exit\r\n",
                descP->sock) );

        /* connectorP is only set during connection.
         * Forget all about the ongoing connection.
         * We might end up connected, but the process that initiated
         * the connection has died and will never know
         */

        requestor_release("esock_down->connector",
                          env, descP, descP->connectorP);

        descP->writeState &= ~ESOCK_STATE_CONNECTING;

        descP->connectorP = NULL;

    } else {
        ERL_NIF_TERM     sockRef;

        /* check all operation queue(s): acceptor, writer and reader.
         *
         * Is it really any point in doing this if the socket is closed?
         *
         */

        sockRef = enif_make_resource(env, descP);

        SSDBG( descP,
               ("SOCKET",
                "esock_down(%T) {%d} -> other process term\r\n",
                sockRef, descP->sock) );

        if (descP->currentReaderP != NULL)
            esock_down_reader(env, descP, sockRef, pid, mon);
        if (descP->currentAcceptorP != NULL)
            esock_down_acceptor(env, descP, sockRef, pid, mon);
        if (descP->currentWriterP != NULL)
            esock_down_writer(env, descP, sockRef, pid, mon);
    }

    MUNLOCK(descP->writeMtx);
    MUNLOCK(descP->readMtx);

    SSDBG( descP, ("SOCKET", "esock_down -> done\r\n") );

#endif // if !defined(__WIN32__)
}



/* *** esock_down_acceptor ***
 *
 * Check and then handle a downed acceptor process.
 *
 */
#if !defined(__WIN32__)
static
void esock_down_acceptor(ErlNifEnv*           env,
                         ESockDescriptor*     descP,
                         ERL_NIF_TERM         sockRef,
                         const ErlNifPid*     pid,
                         const ErlNifMonitor* mon)
{
    if (MON_EQ(&descP->currentAcceptor.mon, mon)) {
        MON_INIT(&descP->currentAcceptor.mon);
        
        SSDBG( descP,
               ("SOCKET",
                "esock_down_acceptor(%T) {%d} -> "
                "current acceptor - try activate next\r\n",
                sockRef, descP->sock) );
        
        if (!activate_next_acceptor(env, descP, sockRef)) {

            SSDBG( descP,
                   ("SOCKET",
                    "esock_down_acceptor(%T) {%d} -> no more writers\r\n",
                    sockRef, descP->sock) );

            descP->readState &= ~ESOCK_STATE_ACCEPTING;

            descP->currentAcceptorP = NULL;
        }

    } else {
        
        /* Maybe unqueue one of the waiting acceptors */
        
        SSDBG( descP,
               ("SOCKET",
                "esock_down_acceptor(%T) {%d} -> "
                "not current acceptor - maybe a waiting acceptor\r\n",
                sockRef, descP->sock) );
        
        acceptor_unqueue(env, descP, pid);
    }
}




/* *** esock_down_writer ***
 *
 * Check and then handle a downed writer process.
 *
 */
static
void esock_down_writer(ErlNifEnv*           env,
                       ESockDescriptor*     descP,
                       ERL_NIF_TERM         sockRef,
                       const ErlNifPid*     pid,
                       const ErlNifMonitor* mon)
{
    if (MON_EQ(&descP->currentWriter.mon, mon)) {
        MON_INIT(&descP->currentWriter.mon);
        
        SSDBG( descP,
               ("SOCKET",
                "esock_down_writer(%T) {%d} -> "
                "current writer - try activate next\r\n",
                sockRef, descP->sock) );
        
        if (!activate_next_writer(env, descP, sockRef)) {

            SSDBG( descP,
                   ("SOCKET",
                    "esock_down_writer(%T) {%d} -> no active writer\r\n",
                    sockRef, descP->sock) );

            descP->currentWriterP    = NULL;
        }
        
    } else {
        
        /* Maybe unqueue one of the waiting writer(s) */
        
        SSDBG( descP,
               ("SOCKET",
                "esock_down_writer(%T) {%d} -> "
                "not current writer - maybe a waiting writer\r\n",
                sockRef, descP->sock) );
        
        writer_unqueue(env, descP, pid);
    }
}




/* *** esock_down_reader ***
 *
 * Check and then handle a downed reader process.
 *
 */
static
void esock_down_reader(ErlNifEnv*           env,
                       ESockDescriptor*     descP,
                       ERL_NIF_TERM         sockRef,
                       const ErlNifPid*     pid,
                       const ErlNifMonitor* mon)
{
    if (MON_EQ(&descP->currentReader.mon, mon)) {
        MON_INIT(&descP->currentReader.mon);
        
        SSDBG( descP,
               ("SOCKET",
                "esock_down_reader(%T) {%d} -> "
                "current reader - try activate next\r\n",
                sockRef, descP->sock) );
        
        if (!activate_next_reader(env, descP, sockRef)) {

            SSDBG( descP,
                   ("SOCKET",
                    "esock_down_reader(%T) {%d} -> no more readers\r\n",
                    sockRef, descP->sock) );

            descP->currentReaderP    = NULL;
        }

    } else {
        
        /* Maybe unqueue one of the waiting reader(s) */
        
        SSDBG( descP,
               ("SOCKET",
                "esock_down_reader(%T) {%d} -> "
                "not current reader - maybe a waiting reader\r\n",
                sockRef, descP->sock) );
        
        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 esock_funcs[] =
{
    // Some utility and support functions
    {"nif_info",                0, nif_info, 0},
    {"nif_info",                1, nif_info, 0},
    {"nif_supports",            0, nif_supports, 0},
    {"nif_supports",            1, nif_supports, 0},
    {"nif_supports",            2, nif_supports, 0},
    {"nif_command",             1, nif_command, 0},

    // The proper "socket" interface
    {"nif_open",                2, nif_open, 0},
    {"nif_open",                4, nif_open, 0},
    {"nif_bind",                2, nif_bind, 0},
    {"nif_connect",             1, nif_connect, 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_close
     * is called after the close *select* has "completed".
     */
    {"nif_cancel",              3, nif_cancel, 0},
    {"nif_finalize_close",      1, nif_finalize_close, ERL_NIF_DIRTY_JOB_IO_BOUND}
};


#if !defined(__WIN32__)

static
char* extract_debug_filename(ErlNifEnv*   env,
                                 ERL_NIF_TERM map)
{
    /* See the functions above */
    ERL_NIF_TERM val;
    ErlNifBinary bin;
    char *filename;
    
    if (! GET_MAP_VAL(env, map, atom_debug_filename, &val))
        return NULL;

    if (! enif_inspect_binary(env, val, &bin))
        return NULL;

    if ((filename = MALLOC(bin.size + 1)) == NULL)
        return NULL;

    sys_memcpy(filename, bin.data, bin.size);
    filename[bin.size] = '\0';
    return filename;
}

#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)
{
    /* +++ Local atoms and error reason atoms +++ */
#define LOCAL_ATOM_DECL(A) atom_##A = MKA(env, #A)
    LOCAL_ATOMS;
    LOCAL_ERROR_REASON_ATOMS;
#undef LOCAL_ATOM_DECL

    /* Global atom(s) and error reason atom(s) */
#define GLOBAL_ATOM_DECL(A) esock_atom_##A = MKA(env, #A)
    GLOBAL_ATOMS;
    GLOBAL_ERROR_REASON_ATOMS;
#undef GLOBAL_ATOM_DECL

    esock_atom_socket_tag = MKA(env, "$socket");

#if !defined(__WIN32__)

    if (! esock_extract_pid_from_map(env, load_info,
                                     atom_registry,
                                     &data.regPid))
        return 1; // Failure - no registry pid

    data.useReg =
        esock_extract_bool_from_map(env, load_info,
                                    atom_use_registry,
                                    ESOCK_USE_SOCKET_REGISTRY);

    data.iow =
        esock_extract_bool_from_map(env, load_info,
                                    atom_iow,
                                    ESOCK_NIF_IOW_DEFAULT);

    {
        char *debug_filename;

        debug_filename = extract_debug_filename(env, load_info);
        
        if (esock_dbg_init(debug_filename)) {
            // Pick up early debug flags only if debug_filename is ok

            data.dbg =
                esock_extract_bool_from_map(env, load_info,
                                            esock_atom_debug,
                                            ESOCK_GLOBAL_DEBUG_DEFAULT);
            data.sockDbg =
                esock_extract_bool_from_map(env, load_info,
                                            atom_socket_debug,
                                            ESOCK_DEBUG_DEFAULT);
        }

        if (debug_filename != NULL)
            FREE(debug_filename);
    }

    /* +++ Global Counters +++ */
    data.cntMtx         = MCREATE("esock.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

    esocks = enif_open_resource_type_x(env,
                                       "sockets",
                                       &esockInit,
                                       ERL_NIF_RT_CREATE,
                                       NULL);
    return esocks != NULL ?
        0: // Success
        1; // Failure
}

/*
 * MODULE:  socket (the erlang API/interface module)
 * funcs:   esock_funcs (defines the API of this nif)
 * load:    on_load (load this nif)
 * upgrade: NULL (not used)
 * NULL:    THIS IS NOT USED
 * unload:  NULL (not used)
 */
ERL_NIF_INIT(prim_socket, esock_funcs, on_load, NULL, NULL, NULL)
