/*=============================================================================
                                 socket_win.c
===============================================================================
  This is the implementation of TChanSwitch and TChannel
  for a Winsock socket.
=============================================================================*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <winsock2.h>

#include "xmlrpc_config.h"
#include "xmlrpc-c/util_int.h"
#include "xmlrpc-c/string_int.h"
#include "mallocvar.h"
#include "trace.h"
#include "chanswitch.h"
#include "channel.h"
#include "socket.h"
#include "xmlrpc-c/abyss.h"

#include "socket_win.h"

#ifndef socklen_t
typedef int socklen_t;
#endif

/* =============================================================
   Provided nice error strings, NOT available in system errors.
   ============================================================= */

typedef struct tagSOCKERRS {
   int err;       // WSAGetLastError() value
   char * desc;   // description of error
} SOCKERR;

/* could/should perhaps be by the actual call,
   but for now, just one big list, with some repeats
*/

SOCKERR sSockErr[] = {
   { WSANOTINITIALISED,
     "WSANOTINITIALISED - "
     "WSAStartup must be called before using this function." },
   { WSAENETDOWN,
     "WSAENETDOWN - "
     "The network subsystem has failed." },
   { WSAEACCES,
     "WSAEACCES - "
     "Attempt to connect datagram socket to broadcast address failed "
     "because setsockopt option SO_BROADCAST is not enabled." },
   { WSAEADDRINUSE,
     "WSAEADDRINUSE - "
     "A process on the computer is already bound to the same fully-qualified "
     "address and the socket has not been marked to allow address reuse with "
     "SO_REUSEADDR. For example, the IP address and port are bound in the "
     "af_inet case). (See the SO_REUSEADDR socket option under setsockopt.)" },
   { WSAEADDRNOTAVAIL,
     "WSAEADDRNOTAVAIL - "
     "The specified address is not a valid address for this computer." },
   { WSAEFAULT,
     "WSAEFAULT - "
     "The name or namelen parameter is not a valid part of the user "
     "address space, the namelen parameter is too small, the name parameter "
     "contains an incorrect address format for the associated "
     "address family, or the first two bytes of the memory block "
     "specified by name does not match the address family associated with "
     "the socket descriptor s." },
   { WSAEINPROGRESS,
     "WSAEINPROGRESS - "
     "A blocking Windows Sockets 1.1 call is in progress, or the "
     "service provider is still processing a callback function." },
   { WSAEINVAL,
     "WSAEINVAL - "
     "The socket is already bound to an address." },
   { WSAENOBUFS,
     "WSAENOBUFS - "
     "Not enough buffers available, too many connections." },
   { WSAENOTSOCK,
     "WSAENOTSOCK - "
     "The descriptor is not a socket." },

   // setsocketopt
   { WSAENETRESET,
     "WSAENETRESET - "
     "Connection has timed out when SO_KEEPALIVE is set." },
   { WSAENOPROTOOPT,
     "WSAENOPROTOOPT - "
     "The option is unknown or the specified provider "
     "or socket is not capable of implementing it "
     "(see SO_GROUP_PRIORITY limitations)." },
   { WSAENOTCONN,
     "WSAENOTCONN - "
     "Connection has been reset when SO_KEEPALIVE is set." },

   // WSAStartup
   { WSASYSNOTREADY,
     "WSASYSNOTREADY - "
     "The underlying network subsystem is not ready for "
     "network communication." },
   { WSAVERNOTSUPPORTED,
     "WSAVERNOTSUPPORTED - "
     "The version of Windows Sockets function requested is not provided "
     "by this particular Windows Sockets implementation." },
   { WSAEINPROGRESS,
     "WSAEINPROGRESS - "
     "A blocking Windows Sockets 1.1 operation is in progress." },
   { WSAEPROCLIM,
     "WSAEPROCLIM - "
     "Limit on the number of tasks allowed by the Windows Sockets "
     "implementation has been reached." },
   { WSAEFAULT,
     "WSAEFAULT - "
     "The lpWSAData is not a valid pointer." },
   // listen
   { WSANOTINITIALISED,
     "WSANOTINITIALISED - "
     "A successful WSAStartup call must occur before using this function." },
   { WSAENETDOWN,
     "WSAENETDOWN - "
     "The network subsystem has failed." },
   { WSAEADDRINUSE,
     "WSAEADDRINUSE - "
     "The socket's local address is already in use and the socket "
     "was not marked to allow address reuse with SO_REUSEADDR.  "
     "This error usually occurs during execution of the bind function, "
     "but could be delayed until this function if the bind was to "
     "a partially wildcard address (involving ADDR_ANY) "
     "and if a specific address needs to be committed at the time "
     "of this function call." },
   { WSAEINPROGRESS,
     "WSAEINPROGRESS - "
     "A blocking Windows Sockets 1.1 call is in progress, "
     "or the service provider is still processing a callback function." },
   { WSAEINVAL,
     "WSAEINVAL - "
     "The socket has not been bound with bind." },
   { WSAEISCONN,
     "WSAEISCONN - "
     "The socket is already connected." },
   { WSAEMFILE,
     "WSAEMFILE - "
     "No more socket descriptors are available." },
   { WSAENOBUFS,
     "WSAENOBUFS - "
     "No buffer space is available." },
   { WSAENOTSOCK,
     "WSAENOTSOCK - "
     "The descriptor is not a socket." },
   { WSAEOPNOTSUPP,
     "WSAEOPNOTSUPP - "
     "The referenced socket is not of a type that has a listen operation." },

   // getpeername
   { WSANOTINITIALISED,
     "WSANOTINITIALISED - "
     "A successful WSAStartup call must occur before using this function." },
   { WSAENETDOWN,
     "WSAENETDOWN - "
     "The network subsystem has failed." },
   { WSAEFAULT,
     "WSAEFAULT - "
     "The name or the namelen parameter is not a valid part of the "
     "user address space, or the namelen parameter is too small." },
   { WSAEINPROGRESS,
     "WSAEINPROGRESS - "
     "A blocking Windows Sockets 1.1 call is in progress, "
     "or the service provider is still processing a callback function." },
   { WSAENOTCONN,
     "WSAENOTCONN - "
     "The socket is not connected." },
   { WSAENOTSOCK,
     "WSAENOTSOCK - "
     "The descriptor is not a socket." },

   // accept
   { WSANOTINITIALISED,
     "WSANOTINITIALISED - "
     "A successful WSAStartup call must occur before using this function." },
   { WSAENETDOWN,
     "WSAENETDOWN - "
     "The network subsystem has failed." },
   { WSAEFAULT,
     "WSAEFAULT - "
     "The addrlen parameter is too small or addr is not a valid part "
     "of the user address space." },
   { WSAEINTR,
     "WSAEINTR - "
     "A blocking Windows Sockets 1.1 call was canceled through "
     "WSACancelBlockingCall." },
   { WSAEINPROGRESS,
     "WSAEINPROGRESS - "
     "A blocking Windows Sockets 1.1 call is in progress, "
     "or the service provider is still processing a callback function." },
   { WSAEINVAL,
     "WSAEINVAL - "
     "The listen function was not invoked prior to accept." },
   { WSAEMFILE,
     "WSAEMFILE - "
     "The queue is nonempty upon entry to accept and "
     "there are no descriptors available." },
   { WSAENOBUFS,
     "WSAENOBUFS - "
     "No buffer space is available." },
   { WSAENOTSOCK,
     "WSAENOTSOCK - "
     "The descriptor is not a socket." },
   { WSAEOPNOTSUPP,
     "WSAEOPNOTSUPP - "
     "The referenced socket is not a type that offers connection-oriented "
     "service." },
   { WSAEWOULDBLOCK,
     "WSAEWOULDBLOCK - "
     "The socket is marked as nonblocking and no connections are present "
     "to be accepted." },

   /* must be last entry */
   { 0,            0 }
};



static const char *
getWSAError(int const wsaErrno) {

    SOCKERR * pseP;
  
    pseP = &sSockErr[0];  // initial value
   
    while (pseP->desc) {
        if (pseP->err == wsaErrno)
            return pseP->desc;
        
        ++pseP;
    }

    return "(no description available)";
}



struct socketWin {
/*----------------------------------------------------------------------------
   The properties/state of a TSocket unique to a Unix TSocket.
-----------------------------------------------------------------------------*/
    SOCKET winsock;
    bool userSuppliedWinsock;
        /* 'socket' was supplied by the user; it belongs to him */
    HANDLE interruptEvent;
};

static
bool
connected(SOCKET const fd) {
/*----------------------------------------------------------------------------
   Return TRUE iff the socket on file descriptor 'fd' is in the connected
   state.
   If 'fd' does not identify a stream socket or we are unable to determine
   the state of the stream socket, the answer is "false".
-----------------------------------------------------------------------------*/
    bool connected;
    struct sockaddr sockaddr;
    socklen_t nameLen;
    int rc;

    nameLen = sizeof(sockaddr);

    rc = getpeername(fd, &sockaddr, &nameLen);

    if (rc == 0)
        connected = TRUE;
    else
        connected = FALSE;

    return connected;
}



void
SocketWinInit(const char ** const errorP) {

    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
 
    wVersionRequested = MAKEWORD(1, 0);
 
    err = WSAStartup(wVersionRequested, &wsaData);

    if (err != 0) {
        int const lastError = WSAGetLastError();
        xmlrpc_asprintf(errorP, "WSAStartup() faild with error %d (%s)",
                        lastError, getWSAError(lastError));
    } else
        *errorP = NULL;
}



void
SocketWinTerm(void) {
    
    WSACleanup();
}



/*=============================================================================
      TChannel
=============================================================================*/

static ChannelDestroyImpl channelDestroy;

static void
channelDestroy(TChannel * const channelP) {

    struct socketWin * const socketWinP = channelP->implP;

    if (!socketWinP->userSuppliedWinsock)
        closesocket(socketWinP->winsock);

    CloseHandle(socketWinP->interruptEvent);

    free(socketWinP);
}



static ChannelWriteImpl channelWrite;

static void
channelWrite(TChannel *            const channelP,
             const unsigned char * const buffer,
             uint32_t              const len,
             bool *          const failedP) {

    struct socketWin * const socketWinP = channelP->implP;

    size_t bytesLeft;
    bool error;

    assert(sizeof(size_t) >= sizeof(len));

    for (bytesLeft = len, error = FALSE;
         bytesLeft > 0 && !error;
        ) {
        size_t const maxSend = (size_t)(-1) >> 1;

        int rc;
        
        rc = send(socketWinP->winsock, &buffer[len-bytesLeft],
                  MIN(maxSend, bytesLeft), 0);

        if (rc <= 0)
            /* 0 means connection closed; < 0 means severe error */
            error = TRUE;
        else
            bytesLeft -= rc;
    }
    *failedP = error;
}



static ChannelReadImpl channelRead;

static void
channelRead(TChannel *   const channelP, 
            unsigned char * const buffer, 
            uint32_t     const bufferSize,
            uint32_t *   const bytesReceivedP,
            bool * const failedP) {

    struct socketWin * const socketWinP = channelP->implP;

    int rc;
    rc = recv(socketWinP->winsock, buffer, bufferSize, 0);

    if (rc < 0) {
        *failedP = TRUE;
    } else {
        *failedP = FALSE;
        *bytesReceivedP = rc;
    }
}



static ChannelWaitImpl channelWait;

static void
channelWait(TChannel * const channelP,
            bool       const waitForRead,
            bool       const waitForWrite,
            uint32_t   const timems,
            bool *     const readyToReadP,
            bool *     const readyToWriteP,
            bool *     const failedP) {

    struct socketWin * const socketWinP = channelP->implP;

    fd_set rfds, wfds;
    TIMEVAL tv;
    bool failed, readRdy, writeRdy, timedOut;

    FD_ZERO(&rfds);
    FD_ZERO(&wfds);

    if (waitForRead)
        FD_SET(socketWinP->winsock, &rfds);

    if (waitForWrite)
        FD_SET(socketWinP->winsock, &wfds);

    tv.tv_sec  = timems / 1000;
    tv.tv_usec = timems % 1000;
 
    for (failed = FALSE, readRdy = FALSE, writeRdy = FALSE, timedOut = FALSE;
         !failed && !readRdy && !writeRdy && !timedOut;
        ) {

        int rc;

        rc = select(socketWinP->winsock + 1, &rfds, &wfds, NULL,
                    (timems == TIME_INFINITE ? NULL : &tv));

        switch(rc) {   
        case 0:
            timedOut = TRUE;
            break;
        case -1:  /* socket error */
            if (WSAGetLastError() != WSAEINTR)
                failed = TRUE;
        break;
        default:
            if (FD_ISSET(socketWinP->winsock, &rfds))
                readRdy = TRUE;
            if (FD_ISSET(socketWinP->winsock, &wfds))
                writeRdy = TRUE;
        }
    }

    if (failedP)
        *failedP       = failed;
    if (readyToReadP)
        *readyToReadP  = readRdy;
    if (readyToWriteP)
        *readyToWriteP = writeRdy;
}



static ChannelInterruptImpl channelInterrupt;

static void
channelInterrupt(TChannel * const channelP) {
/*----------------------------------------------------------------------------
  Interrupt any waiting that a thread might be doing in channelWait()
  now or in the future.

  Actually, this is just a no-op because we don't yet know how to
  accomplish that.  (But we could probably do it the same way
  chanSwitchInterrupt() works -- no one has needed it enough yet to do that
  work).
-----------------------------------------------------------------------------*/

}



void
ChannelWinGetPeerName(TChannel *           const channelP,
                      struct sockaddr_in * const inAddrP,
                      const char **        const errorP) {

    struct socketWin * const socketWinP = channelP->implP;

    socklen_t addrlen;
    int rc;
    struct sockaddr sockAddr;

    addrlen = sizeof(sockAddr);
    
    rc = getpeername(socketWinP->winsock, &sockAddr, &addrlen);

    if (rc != 0) {
        int const lastError = WSAGetLastError();
        xmlrpc_asprintf(errorP, "getpeername() failed.  WSA error = %d (%s)",
                        lastError, getWSAError(lastError));
    } else {
        if (addrlen != sizeof(sockAddr))
            xmlrpc_asprintf(errorP, "getpeername() returned a socket address "
                            "of the wrong size: %u.  Expected %u",
                            addrlen, sizeof(sockAddr));
        else {
            if (sockAddr.sa_family != AF_INET)
                xmlrpc_asprintf(errorP,
                                "Socket does not use the Inet (IP) address "
                                "family.  Instead it uses family %d",
                                sockAddr.sa_family);
            else {
                *inAddrP = *(struct sockaddr_in *)&sockAddr;

                *errorP = NULL;
            }
        }
    }
}



static ChannelFormatPeerInfoImpl channelFormatPeerInfo;

static void
channelFormatPeerInfo(TChannel *    const channelP,
                      const char ** const peerStringP) {

    struct socketWin * const socketWinP = channelP->implP;

    struct sockaddr sockaddr;
    socklen_t sockaddrLen;
    int rc;

    sockaddrLen = sizeof(sockaddr);
    
    rc = getpeername(socketWinP->winsock, &sockaddr, &sockaddrLen);
    
    if (rc != 0) {
        int const lastError = WSAGetLastError();
        xmlrpc_asprintf(peerStringP, "?? getpeername() failed.  "
                        "WSAERROR %d (%s)",
                        lastError, getWSAError(lastError));
    } else {
        switch (sockaddr.sa_family) {
        case AF_INET: {
            struct sockaddr_in * const sockaddrInP =
                (struct sockaddr_in *) &sockaddr;
            if (sockaddrLen < sizeof(*sockaddrInP))
                xmlrpc_asprintf(peerStringP, "??? getpeername() returned "
                                "the wrong size");
            else {
                unsigned char * const ipaddr = (unsigned char *)
                    &sockaddrInP->sin_addr.s_addr;
                xmlrpc_asprintf(peerStringP, "%u.%u.%u.%u:%hu",
                                ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3],
                                sockaddrInP->sin_port);
            }
        } break;
        default:
            xmlrpc_asprintf(peerStringP, "??? AF=%u", sockaddr.sa_family);
        }
    }
}



static struct TChannelVtbl const channelVtbl = {
    &channelDestroy,
    &channelWrite,
    &channelRead,
    &channelWait,
    &channelInterrupt,
    &channelFormatPeerInfo,
};



static void
makeChannelFromWinsock(SOCKET        const winsock,
                       TChannel **   const channelPP,
                       const char ** const errorP) {

    struct socketWin * socketWinP;

    MALLOCVAR(socketWinP);
    
    if (socketWinP == NULL)
        xmlrpc_asprintf(errorP, "Unable to allocate memory for Windows "
                        "socket descriptor");
    else {
        TChannel * channelP;
        
        socketWinP->winsock = winsock;
        socketWinP->userSuppliedWinsock = TRUE;
        socketWinP->interruptEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

        ChannelCreate(&channelVtbl, socketWinP, &channelP);
        
        if (channelP == NULL)
            xmlrpc_asprintf(errorP, "Unable to allocate memory for "
                            "channel descriptor.");
        else {
            *channelPP = channelP;
            *errorP = NULL;
        }
        if (*errorP) {
            CloseHandle(socketWinP->interruptEvent);
            free(socketWinP);
        }
    }
}



static void
makeChannelInfo(struct abyss_win_chaninfo ** const channelInfoPP,
                struct sockaddr              const peerAddr,
                socklen_t                    const peerAddrLen,
                const char **                const errorP) {

    struct abyss_win_chaninfo * channelInfoP;

    MALLOCVAR(channelInfoP);
    
    if (channelInfoP == NULL)
        xmlrpc_asprintf(errorP, "Unable to allocate memory");
    else {
        channelInfoP->peerAddrLen = peerAddrLen;
        channelInfoP->peerAddr    = peerAddr;
        
        *channelInfoPP = channelInfoP;

        *errorP = NULL;
    }
}



void
ChannelWinCreateWinsock(SOCKET                       const fd,
                        TChannel **                  const channelPP,
                        struct abyss_win_chaninfo ** const channelInfoPP,
                        const char **                const errorP) {

    struct sockaddr peerAddr;
    socklen_t peerAddrLen;
    int rc;

    peerAddrLen = sizeof(peerAddr);

    rc = getpeername(fd, &peerAddr, &peerAddrLen);

    if (rc != 0) {
        int const lastError = WSAGetLastError();
        if (lastError == WSAENOTCONN) {
            /* NOTE: This specific string 'not in connected' is
               required by one of the rpctest suite items, in abyss.c
               (line 186), hence the separation of the error messages
               in this case ...
            */
            xmlrpc_asprintf(errorP, "Socket on file descriptor %d "
                            "is not in connected state. WSAERROR = %d (%s)",
                            fd, lastError, getWSAError(lastError));
        } else
            xmlrpc_asprintf(errorP, "getpeername() failed. WSAERROR = %d (%s)",
                        lastError, getWSAError(lastError));
    } else {
        makeChannelInfo(channelInfoPP, peerAddr, peerAddrLen, errorP);
        if (!*errorP) {
            makeChannelFromWinsock(fd, channelPP, errorP);

            if (*errorP)
                free(*channelInfoPP);
        }
    }
}


/*=============================================================================
      TChanSwitch
=============================================================================*/

static SwitchDestroyImpl chanSwitchDestroy;

void
chanSwitchDestroy(TChanSwitch * const chanSwitchP) {

    struct socketWin * const socketWinP = chanSwitchP->implP;

    if (!socketWinP->userSuppliedWinsock)
        closesocket(socketWinP->winsock);

    CloseHandle(socketWinP->interruptEvent);

    free(socketWinP);
}



static SwitchListenImpl chanSwitchListen;

static void
chanSwitchListen(TChanSwitch * const chanSwitchP,
                 uint32_t      const backlog,
                 const char ** const errorP) {

    struct socketWin * const socketWinP = chanSwitchP->implP;

    int32_t const minus1 = -1;

    int rc;

    /* Disable the Nagle algorithm to make persistant connections faster */

    setsockopt(socketWinP->winsock, IPPROTO_TCP, TCP_NODELAY,
               (const char *)&minus1, sizeof(minus1));

    rc = listen(socketWinP->winsock, backlog);

    if (rc != 0) {
        int const lastError = WSAGetLastError();
        xmlrpc_asprintf(errorP, "setsockopt() failed with WSAERROR %d (%s)",
                        lastError, getWSAError(lastError));
    } else
        *errorP = NULL;
}



static void
createChannelForAccept(int             const acceptedWinsock,
                       struct sockaddr const peerAddr,
                       TChannel **     const channelPP,
                       void **         const channelInfoPP,
                       const char **   const errorP) {

    struct abyss_win_chaninfo * channelInfoP;
    makeChannelInfo(&channelInfoP, peerAddr, sizeof(peerAddr), errorP);
    if (!*errorP) {
        struct socketWin * acceptedSocketP;

        MALLOCVAR(acceptedSocketP);

        if (!acceptedSocketP)
            xmlrpc_asprintf(errorP, "Unable to allocate memory");
        else {
            TChannel * channelP;

            acceptedSocketP->winsock             = acceptedWinsock;
            acceptedSocketP->userSuppliedWinsock = FALSE;
            acceptedSocketP->interruptEvent      =
                CreateEvent(NULL, FALSE, FALSE, NULL);

            ChannelCreate(&channelVtbl, acceptedSocketP, &channelP);
            if (!channelP)
                xmlrpc_asprintf(errorP,
                                "Failed to create TChannel object.");
            else {
                *errorP        = NULL;
                *channelPP     = channelP;
                *channelInfoPP = channelInfoP;
            }
            if (*errorP) {
                CloseHandle(acceptedSocketP->interruptEvent);
                free(acceptedSocketP);
            }
        }
    }
}



static SwitchAcceptImpl  chanSwitchAccept;

static void
chanSwitchAccept(TChanSwitch * const chanSwitchP,
                 TChannel **   const channelPP,
                 void **       const channelInfoPP,
                 const char ** const errorP) {
/*----------------------------------------------------------------------------
   Accept a connection via the channel switch *chanSwitchP.  Return as
   *channelPP the channel for the accepted connection.

   If no connection is waiting at *chanSwitchP, wait until one is.

   If we receive a signal while waiting, return immediately with
   *channelPP == NULL.
-----------------------------------------------------------------------------*/
    struct socketWin * const listenSocketP = chanSwitchP->implP;
    HANDLE acceptEvent = WSACreateEvent();
    bool interrupted;
    TChannel * channelP;

    interrupted = FALSE; /* Haven't been interrupted yet */
    channelP    = NULL;  /* No connection yet */
    *errorP     = NULL;  /* No error yet */

    WSAEventSelect(listenSocketP->winsock, acceptEvent,
                   FD_ACCEPT | FD_CLOSE | FD_READ);

    while (!channelP && !*errorP && !interrupted) {
        HANDLE interrupts[2] = {acceptEvent, listenSocketP->interruptEvent};
        int rc;
        struct sockaddr peerAddr;
        socklen_t size = sizeof(peerAddr);

        rc = WaitForMultipleObjects(2, interrupts, FALSE, INFINITE);
        if (WAIT_OBJECT_0 + 1 == rc) {
            interrupted = TRUE;
            continue;
        };

        rc = accept(listenSocketP->winsock, &peerAddr, &size);

        if (rc >= 0) {
            int const acceptedWinsock = rc;

            createChannelForAccept(acceptedWinsock, peerAddr,
                                   &channelP, channelInfoPP, errorP);

            if (*errorP)
                closesocket(acceptedWinsock);
        } else {
            int const lastError = WSAGetLastError();

            if (lastError == WSAEINTR)
                interrupted = TRUE;
            else
                xmlrpc_asprintf(errorP,
                                "accept() failed, WSA error = %d (%s)",
                                lastError, getWSAError(lastError));
        }
    }
    *channelPP = channelP;
    CloseHandle(acceptEvent);
}



static SwitchInterruptImpl chanSwitchInterrupt;

static void
chanSwitchInterrupt(TChanSwitch * const chanSwitchP) {
/*----------------------------------------------------------------------------
  Interrupt any waiting that a thread might be doing in chanSwitchAccept()
  now or in the future.
-----------------------------------------------------------------------------*/
    struct socketWin * const listenSocketP = chanSwitchP->implP;

    SetEvent(listenSocketP->interruptEvent);
}



static struct TChanSwitchVtbl const chanSwitchVtbl = {
    &chanSwitchDestroy,
    &chanSwitchListen,
    &chanSwitchAccept,
    &chanSwitchInterrupt,
};



static void
setSocketOptions(SOCKET        const fd,
                 const char ** const errorP) {

    int32_t const n = 1;

    int rc;

    rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&n, sizeof(n));

    if (rc != 0) {
        int const lastError = WSAGetLastError();
        xmlrpc_asprintf(errorP, "Failed to set socket options.  "
                        "setsockopt() failed with WSAERROR %d (%s)",
                        lastError, getWSAError(lastError));
    } else
        *errorP = NULL;
}



void
bindSocketToPort(SOCKET           const winsock,
                 struct in_addr * const addrP,
                 uint16_t         const portNumber,
                 const char **    const errorP) {
    
    struct sockaddr_in name;
    int rc;

    ZeroMemory(&name, sizeof(name));
    name.sin_family = AF_INET;
    name.sin_port   = htons(portNumber);
    if (addrP)
        name.sin_addr = *addrP;

    rc = bind(winsock, (struct sockaddr *)&name, sizeof(name));

    if (rc != 0) {
        int const lastError = WSAGetLastError();
        xmlrpc_asprintf(errorP, "Unable to bind socket to port number %u.  "
                        "bind() failed with WSAERROR %i (%s)",
                        portNumber, lastError, getWSAError(lastError));
    } else
        *errorP = NULL;
}



void
ChanSwitchWinCreate(uint16_t       const portNumber,
                    TChanSwitch ** const chanSwitchPP,
                    const char **  const errorP) {
/*----------------------------------------------------------------------------
   Create a Winsock-based channel switch.

   Set the socket's local address so that a subsequent "listen" will listen
   on all IP addresses, port number 'portNumber'.
-----------------------------------------------------------------------------*/
    struct socketWin * socketWinP;

    MALLOCVAR(socketWinP);

    if (!socketWinP)
        xmlrpc_asprintf(errorP, "Unable to allocate memory for Windows socket "
                        "descriptor structure.");
    else {
        SOCKET winsock;

        winsock = socket(AF_INET, SOCK_STREAM, 0);

        if (winsock == 0 || winsock == INVALID_SOCKET) {
            int const lastError = WSAGetLastError();
            xmlrpc_asprintf(errorP, "socket() failed with WSAERROR %d (%s)",
                            lastError, getWSAError(lastError));
        } else {
            socketWinP->winsock = winsock;
            socketWinP->userSuppliedWinsock = FALSE;
            socketWinP->interruptEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
            
            setSocketOptions(socketWinP->winsock, errorP);
            if (!*errorP) {
                bindSocketToPort(socketWinP->winsock, NULL, portNumber,
                                 errorP);
                if (!*errorP)
                    ChanSwitchCreate(&chanSwitchVtbl, socketWinP,
                                     chanSwitchPP);
            }

            if (*errorP) {
                CloseHandle(socketWinP->interruptEvent);
                closesocket(winsock);
            }
        }
        if (*errorP)
            free(socketWinP);
    }
}



void
ChanSwitchWinCreateWinsock(SOCKET         const winsock,
                           TChanSwitch ** const chanSwitchPP,
                           const char **  const errorP) {

    struct socketWin * socketWinP;

    if (connected(winsock))
        xmlrpc_asprintf(errorP, "Socket is in connected state.");
    else {
        MALLOCVAR(socketWinP);

        if (socketWinP == NULL)
            xmlrpc_asprintf(errorP, "unable to allocate memory for Windows "
                            "socket descriptor.");
        else {
            TChanSwitch * chanSwitchP;

            socketWinP->winsock = winsock;
            socketWinP->userSuppliedWinsock = TRUE;
            socketWinP->interruptEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

            ChanSwitchCreate(&chanSwitchVtbl, socketWinP, &chanSwitchP);

            if (chanSwitchP == NULL)
                xmlrpc_asprintf(errorP, "Unable to allocate memory for "
                                "channel switch descriptor");
            else {
                *chanSwitchPP = chanSwitchP;
                *errorP = NULL;
            }
            if (*errorP) {
                CloseHandle(socketWinP->interruptEvent);
                free(socketWinP);
            }
        }
    }
}
