/*
 * Deskflow -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2002 Chris Schoeneman
 *
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 *
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "arch/win32/ArchNetworkWinsock.h"
#include "arch/Arch.h"
#include "arch/IArchMultithread.h"
#include "arch/win32/ArchMultithreadWindows.h"
#include "arch/win32/XArchWindows.h"

#include <malloc.h>

static const int s_family[] = {
    PF_UNSPEC,
    PF_INET,
    PF_INET6,
};
static const int s_type[] = {SOCK_DGRAM, SOCK_STREAM};

static SOCKET(PASCAL FAR *accept_winsock)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
static int(PASCAL FAR *bind_winsock)(SOCKET s, const struct sockaddr FAR *addr, int namelen);
static int(PASCAL FAR *close_winsock)(SOCKET s);
static int(PASCAL FAR *connect_winsock)(SOCKET s, const struct sockaddr FAR *name, int namelen);
static int(PASCAL FAR *gethostname_winsock)(char FAR *name, int namelen);
static int(PASCAL FAR *getsockerror_winsock)(void);
static int(PASCAL FAR *getsockopt_winsock)(SOCKET s, int level, int optname, void FAR *optval, int FAR *optlen);
static u_short(PASCAL FAR *htons_winsock)(u_short v);
static char FAR *(PASCAL FAR *inet_ntoa_winsock)(struct in_addr in);
static unsigned long(PASCAL FAR *inet_addr_winsock)(const char FAR *cp);
static int(PASCAL FAR *ioctl_winsock)(SOCKET s, int cmd, void FAR *data);
static int(PASCAL FAR *listen_winsock)(SOCKET s, int backlog);
static u_short(PASCAL FAR *ntohs_winsock)(u_short v);
static int(PASCAL FAR *recv_winsock)(SOCKET s, void FAR *buf, int len, int flags);
static int(PASCAL FAR *select_winsock)(
    int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout
);
static int(PASCAL FAR *send_winsock)(SOCKET s, const void FAR *buf, int len, int flags);
static int(PASCAL FAR *setsockopt_winsock)(SOCKET s, int level, int optname, const void FAR *optval, int optlen);
static int(PASCAL FAR *shutdown_winsock)(SOCKET s, int how);
static SOCKET(PASCAL FAR *socket_winsock)(int af, int type, int protocol);
static struct hostent FAR *(PASCAL FAR *gethostbyaddr_winsock)(const char FAR *addr, int len, int type);
static struct hostent FAR *(PASCAL FAR *gethostbyname_winsock)(const char FAR *name);
static int(PASCAL FAR *WSACleanup_winsock)(void);
static int(PASCAL FAR *WSAFDIsSet_winsock)(SOCKET, fd_set FAR *fdset);
static WSAEVENT(PASCAL FAR *WSACreateEvent_winsock)(void);
static BOOL(PASCAL FAR *WSACloseEvent_winsock)(WSAEVENT);
static BOOL(PASCAL FAR *WSASetEvent_winsock)(WSAEVENT);
static BOOL(PASCAL FAR *WSAResetEvent_winsock)(WSAEVENT);
static int(PASCAL FAR *WSAEventSelect_winsock)(SOCKET, WSAEVENT, long);
static DWORD(PASCAL FAR *WSAWaitForMultipleEvents_winsock)(DWORD, const WSAEVENT FAR *, BOOL, DWORD, BOOL);
static int(PASCAL FAR *WSAEnumNetworkEvents_winsock)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS);

#undef FD_ISSET
#define FD_ISSET(fd, set) WSAFDIsSet_winsock((SOCKET)(fd), (fd_set FAR *)(set))

#define setfunc(var, name, type) var = (type)netGetProcAddress(module, #name)

static HMODULE s_networkModule = NULL;

static FARPROC netGetProcAddress(HMODULE module, LPCSTR name)
{
  FARPROC func = ::GetProcAddress(module, name);
  if (!func) {
    throw XArchNetworkSupport("");
  }
  return func;
}

ArchNetAddressImpl *ArchNetAddressImpl::alloc(size_t size)
{
  size_t totalSize = size + ADDR_HDR_SIZE;
  ArchNetAddressImpl *addr = (ArchNetAddressImpl *)malloc(totalSize);
  addr->m_len = (int)size;
  return addr;
}

//
// ArchNetworkWinsock
//

ArchNetworkWinsock::ArchNetworkWinsock() : m_mutex(NULL)
{
}

ArchNetworkWinsock::~ArchNetworkWinsock()
{
  if (s_networkModule != NULL) {
    WSACleanup_winsock();
    ::FreeLibrary(s_networkModule);

    WSACleanup_winsock = NULL;
    s_networkModule = NULL;
  }
  if (m_mutex != NULL) {
    ARCH->closeMutex(m_mutex);
  }

  EventList::iterator it;
  for (it = m_unblockEvents.begin(); it != m_unblockEvents.end(); it++) {
    delete *it;
  }
}

void ArchNetworkWinsock::init()
{
  static const char *s_library[] = {"ws2_32.dll"};

  assert(WSACleanup_winsock == NULL);
  assert(s_networkModule == NULL);

  // try each winsock library
  for (size_t i = 0; i < sizeof(s_library) / sizeof(s_library[0]); ++i) {
    try {
      initModule((HMODULE)::LoadLibrary(s_library[i]));
      m_mutex = ARCH->newMutex();
      return;
    } catch (XArchNetwork &) {
      // ignore
    }
  }

  // can't initialize any library
  throw XArchNetworkSupport("Cannot load winsock library");
}

void ArchNetworkWinsock::initModule(HMODULE module)
{
  if (module == NULL) {
    throw XArchNetworkSupport("");
  }

  // get startup function address
  int(PASCAL FAR * startup)(WORD, LPWSADATA);
  setfunc(startup, WSAStartup, int(PASCAL FAR *)(WORD, LPWSADATA));

  // startup network library
  WORD version = MAKEWORD(2 /*major*/, 2 /*minor*/);
  WSADATA data;
  int err = startup(version, &data);
  if (data.wVersion != version) {
    throw XArchNetworkSupport(new XArchEvalWinsock(err));
  }
  if (err != 0) {
    // some other initialization error
    throwError(err);
  }

  // get function addresses
  setfunc(accept_winsock, accept, SOCKET(PASCAL FAR *)(SOCKET s, struct sockaddr FAR * addr, int FAR *addrlen));
  setfunc(bind_winsock, bind, int(PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *addr, int namelen));
  setfunc(close_winsock, closesocket, int(PASCAL FAR *)(SOCKET s));
  setfunc(connect_winsock, connect, int(PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *name, int namelen));
  setfunc(gethostname_winsock, gethostname, int(PASCAL FAR *)(char FAR *name, int namelen));
  setfunc(getsockerror_winsock, WSAGetLastError, int(PASCAL FAR *)(void));
  setfunc(
      getsockopt_winsock, getsockopt,
      int(PASCAL FAR *)(SOCKET s, int level, int optname, void FAR *optval, int FAR *optlen)
  );
  setfunc(htons_winsock, htons, u_short(PASCAL FAR *)(u_short v));
  setfunc(inet_ntoa_winsock, inet_ntoa, char FAR *(PASCAL FAR *)(struct in_addr in));
  setfunc(inet_addr_winsock, inet_addr, unsigned long(PASCAL FAR *)(const char FAR *cp));
  setfunc(ioctl_winsock, ioctlsocket, int(PASCAL FAR *)(SOCKET s, int cmd, void FAR *));
  setfunc(listen_winsock, listen, int(PASCAL FAR *)(SOCKET s, int backlog));
  setfunc(ntohs_winsock, ntohs, u_short(PASCAL FAR *)(u_short v));
  setfunc(recv_winsock, recv, int(PASCAL FAR *)(SOCKET s, void FAR *buf, int len, int flags));
  setfunc(
      select_winsock, select,
      int(PASCAL FAR *)(
          int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout
      )
  );
  setfunc(send_winsock, send, int(PASCAL FAR *)(SOCKET s, const void FAR *buf, int len, int flags));
  setfunc(
      setsockopt_winsock, setsockopt,
      int(PASCAL FAR *)(SOCKET s, int level, int optname, const void FAR *optval, int optlen)
  );
  setfunc(shutdown_winsock, shutdown, int(PASCAL FAR *)(SOCKET s, int how));
  setfunc(socket_winsock, socket, SOCKET(PASCAL FAR *)(int af, int type, int protocol));
  setfunc(
      gethostbyaddr_winsock, gethostbyaddr, struct hostent FAR * (PASCAL FAR *)(const char FAR *addr, int len, int type)
  );
  setfunc(gethostbyname_winsock, gethostbyname, struct hostent FAR * (PASCAL FAR *)(const char FAR *name));
  setfunc(WSACleanup_winsock, WSACleanup, int(PASCAL FAR *)(void));
  setfunc(WSAFDIsSet_winsock, __WSAFDIsSet, int(PASCAL FAR *)(SOCKET, fd_set FAR *));
  setfunc(WSACreateEvent_winsock, WSACreateEvent, WSAEVENT(PASCAL FAR *)(void));
  setfunc(WSACloseEvent_winsock, WSACloseEvent, BOOL(PASCAL FAR *)(WSAEVENT));
  setfunc(WSASetEvent_winsock, WSASetEvent, BOOL(PASCAL FAR *)(WSAEVENT));
  setfunc(WSAResetEvent_winsock, WSAResetEvent, BOOL(PASCAL FAR *)(WSAEVENT));
  setfunc(WSAEventSelect_winsock, WSAEventSelect, int(PASCAL FAR *)(SOCKET, WSAEVENT, long));
  setfunc(
      WSAWaitForMultipleEvents_winsock, WSAWaitForMultipleEvents,
      DWORD(PASCAL FAR *)(DWORD, const WSAEVENT FAR *, BOOL, DWORD, BOOL)
  );
  setfunc(WSAEnumNetworkEvents_winsock, WSAEnumNetworkEvents, int(PASCAL FAR *)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS));

  s_networkModule = module;
}

ArchSocket ArchNetworkWinsock::newSocket(EAddressFamily family, ESocketType type)
{
  // create socket
  SOCKET fd = socket_winsock(s_family[family], s_type[type], 0);
  if (fd == INVALID_SOCKET) {
    throwError(getsockerror_winsock());
  }
  try {
    setBlockingOnSocket(fd, false);
    BOOL flag = 0;
    int size = sizeof(flag);
    if (setsockopt_winsock(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, size) == SOCKET_ERROR) {
      throwError(getsockerror_winsock());
    }
  } catch (...) {
    close_winsock(fd);
    throw;
  }

  // allocate socket object
  ArchSocketImpl *socket = new ArchSocketImpl;
  socket->m_socket = fd;
  socket->m_refCount = 1;
  socket->m_event = WSACreateEvent_winsock();
  socket->m_pollWrite = true;
  return socket;
}

ArchSocket ArchNetworkWinsock::copySocket(ArchSocket s)
{
  assert(s != NULL);

  // ref the socket and return it
  ARCH->lockMutex(m_mutex);
  ++s->m_refCount;
  ARCH->unlockMutex(m_mutex);
  return s;
}

void ArchNetworkWinsock::closeSocket(ArchSocket s)
{
  assert(s != NULL);

  // unref the socket and note if it should be released
  ARCH->lockMutex(m_mutex);
  const bool doClose = (--s->m_refCount == 0);
  ARCH->unlockMutex(m_mutex);

  // close the socket if necessary
  if (doClose) {
    if (close_winsock(s->m_socket) == SOCKET_ERROR) {
      // close failed.  restore the last ref and throw.
      int err = getsockerror_winsock();
      ARCH->lockMutex(m_mutex);
      ++s->m_refCount;
      ARCH->unlockMutex(m_mutex);
      throwError(err);
    }
    WSACloseEvent_winsock(s->m_event);
    delete s;
  }
}

void ArchNetworkWinsock::closeSocketForRead(ArchSocket s)
{
  assert(s != NULL);

  if (shutdown_winsock(s->m_socket, SD_RECEIVE) == SOCKET_ERROR) {
    if (getsockerror_winsock() != WSAENOTCONN) {
      throwError(getsockerror_winsock());
    }
  }
}

void ArchNetworkWinsock::closeSocketForWrite(ArchSocket s)
{
  assert(s != NULL);

  if (shutdown_winsock(s->m_socket, SD_SEND) == SOCKET_ERROR) {
    if (getsockerror_winsock() != WSAENOTCONN) {
      throwError(getsockerror_winsock());
    }
  }
}

void ArchNetworkWinsock::bindSocket(ArchSocket s, ArchNetAddress addr)
{
  assert(s != NULL);
  assert(addr != NULL);

  if (bind_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }
}

void ArchNetworkWinsock::listenOnSocket(ArchSocket s)
{
  assert(s != NULL);

  // hardcoding backlog
  if (listen_winsock(s->m_socket, 3) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }
}

ArchSocket ArchNetworkWinsock::acceptSocket(ArchSocket s, ArchNetAddress *const addr)
{
  assert(s != NULL);

  // create new socket and temporary address
  ArchSocketImpl *socket = new ArchSocketImpl;
  ArchNetAddress tmp = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in6));

  // accept on socket
  SOCKET fd = accept_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, tmp), &tmp->m_len);
  if (fd == INVALID_SOCKET) {
    int err = getsockerror_winsock();
    delete socket;
    free(tmp);
    if (addr) {
      *addr = NULL;
    }
    if (err == WSAEWOULDBLOCK) {
      return NULL;
    }
    throwError(err);
  }

  try {
    setBlockingOnSocket(fd, false);
  } catch (...) {
    close_winsock(fd);
    delete socket;
    free(tmp);
    if (addr) {
      *addr = NULL;
    }
    throw;
  }

  // initialize socket
  socket->m_socket = fd;
  socket->m_refCount = 1;
  socket->m_event = WSACreateEvent_winsock();
  socket->m_pollWrite = true;

  // copy address if requested
  if (addr != NULL) {
    *addr = ARCH->copyAddr(tmp);
  }

  free(tmp);
  return socket;
}

bool ArchNetworkWinsock::connectSocket(ArchSocket s, ArchNetAddress addr)
{
  assert(s != NULL);
  assert(addr != NULL);

  if (connect_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == SOCKET_ERROR) {
    if (getsockerror_winsock() == WSAEISCONN) {
      return true;
    }
    if (getsockerror_winsock() == WSAEWOULDBLOCK) {
      return false;
    }
    throwError(getsockerror_winsock());
  }
  return true;
}

int ArchNetworkWinsock::pollSocket(PollEntry pe[], int num, double timeout)
{
  int i;
  DWORD n;

  // prepare sockets and wait list
  bool canWrite = false;
  WSAEVENT *events = (WSAEVENT *)alloca((num + 1) * sizeof(WSAEVENT));
  for (i = 0, n = 0; i < num; ++i) {
    // reset return flags
    pe[i].m_revents = 0;

    // set invalid flag if socket is bogus then go to next socket
    if (pe[i].m_socket == NULL) {
      pe[i].m_revents |= kPOLLNVAL;
      continue;
    }

    // select desired events
    long socketEvents = 0;
    if ((pe[i].m_events & kPOLLIN) != 0) {
      socketEvents |= FD_READ | FD_ACCEPT | FD_CLOSE;
    }
    if ((pe[i].m_events & kPOLLOUT) != 0) {
      socketEvents |= FD_WRITE | FD_CONNECT | FD_CLOSE;

      // if m_pollWrite is false then we assume the socket is
      // writable.  winsock doesn't signal writability except
      // when the state changes from unwritable.
      if (!pe[i].m_socket->m_pollWrite) {
        canWrite = true;
        pe[i].m_revents |= kPOLLOUT;
      }
    }

    // if no events then ignore socket
    if (socketEvents == 0) {
      continue;
    }

    // select socket for desired events
    WSAEventSelect_winsock(pe[i].m_socket->m_socket, pe[i].m_socket->m_event, socketEvents);

    // add socket event to wait list
    events[n++] = pe[i].m_socket->m_event;
  }

  // if no sockets then return immediately
  if (n == 0) {
    return 0;
  }

  // add the unblock event
  ArchMultithreadWindows *mt = ArchMultithreadWindows::getInstance();
  ArchThread thread = mt->newCurrentThread();
  WSAEVENT *unblockEvent = (WSAEVENT *)mt->getNetworkDataForThread(thread);
  ARCH->closeThread(thread);
  if (unblockEvent == NULL) {
    unblockEvent = new WSAEVENT;
    m_unblockEvents.push_back(unblockEvent);
    *unblockEvent = WSACreateEvent_winsock();
    mt->setNetworkDataForCurrentThread(unblockEvent);
  }
  events[n++] = *unblockEvent;

  // prepare timeout
  DWORD t = (timeout < 0.0) ? INFINITE : (DWORD)(1000.0 * timeout);
  if (canWrite) {
    // if we know we can write then don't block
    t = 0;
  }

  // wait
  DWORD result = WSAWaitForMultipleEvents_winsock(n, events, FALSE, t, FALSE);

  // reset the unblock event
  WSAResetEvent_winsock(*unblockEvent);

  // handle results
  if (result == WSA_WAIT_FAILED) {
    if (getsockerror_winsock() == WSAEINTR) {
      // interrupted system call
      ARCH->testCancelThread();
      return 0;
    }
    throwError(getsockerror_winsock());
  }
  if (result == WSA_WAIT_TIMEOUT && !canWrite) {
    return 0;
  }
  if (result == WSA_WAIT_EVENT_0 + n - 1) {
    // the unblock event was signalled
    return 0;
  }
  for (i = 0, n = 0; i < num; ++i) {
    // skip events we didn't check
    if (pe[i].m_socket == NULL || (pe[i].m_events & (kPOLLIN | kPOLLOUT)) == 0) {
      continue;
    }

    // get events
    WSANETWORKEVENTS info;
    if (WSAEnumNetworkEvents_winsock(pe[i].m_socket->m_socket, pe[i].m_socket->m_event, &info) == SOCKET_ERROR) {
      continue;
    }
    if ((info.lNetworkEvents & FD_READ) != 0) {
      pe[i].m_revents |= kPOLLIN;
    }
    if ((info.lNetworkEvents & FD_ACCEPT) != 0) {
      pe[i].m_revents |= kPOLLIN;
    }
    if ((info.lNetworkEvents & FD_WRITE) != 0) {
      pe[i].m_revents |= kPOLLOUT;

      // socket is now writable so don't bothing polling for
      // writable until it becomes unwritable.
      pe[i].m_socket->m_pollWrite = false;
    }
    if ((info.lNetworkEvents & FD_CONNECT) != 0) {
      if (info.iErrorCode[FD_CONNECT_BIT] != 0) {
        pe[i].m_revents |= kPOLLERR;
      } else {
        pe[i].m_revents |= kPOLLOUT;
        pe[i].m_socket->m_pollWrite = false;
      }
    }
    if ((info.lNetworkEvents & FD_CLOSE) != 0) {
      if (info.iErrorCode[FD_CLOSE_BIT] != 0) {
        pe[i].m_revents |= kPOLLERR;
      } else {
        if ((pe[i].m_events & kPOLLIN) != 0) {
          pe[i].m_revents |= kPOLLIN;
        }
        if ((pe[i].m_events & kPOLLOUT) != 0) {
          pe[i].m_revents |= kPOLLOUT;
        }
      }
    }
    if (pe[i].m_revents != 0) {
      ++n;
    }
  }

  return (int)n;
}

void ArchNetworkWinsock::unblockPollSocket(ArchThread thread)
{
  // set the unblock event
  ArchMultithreadWindows *mt = ArchMultithreadWindows::getInstance();
  WSAEVENT *unblockEvent = (WSAEVENT *)mt->getNetworkDataForThread(thread);
  if (unblockEvent != NULL) {
    WSASetEvent_winsock(*unblockEvent);
  }
}

size_t ArchNetworkWinsock::readSocket(ArchSocket s, void *buf, size_t len)
{
  assert(s != NULL);

  int n = recv_winsock(s->m_socket, buf, (int)len, 0);
  if (n == SOCKET_ERROR) {
    int err = getsockerror_winsock();
    if (err == WSAEINTR || err == WSAEWOULDBLOCK) {
      return 0;
    }
    throwError(err);
  }
  return static_cast<size_t>(n);
}

size_t ArchNetworkWinsock::writeSocket(ArchSocket s, const void *buf, size_t len)
{
  assert(s != NULL);

  int n = send_winsock(s->m_socket, buf, (int)len, 0);
  if (n == SOCKET_ERROR) {
    int err = getsockerror_winsock();
    if (err == WSAEINTR) {
      return 0;
    }
    if (err == WSAEWOULDBLOCK) {
      s->m_pollWrite = true;
      return 0;
    }
    throwError(err);
  }
  return static_cast<size_t>(n);
}

void ArchNetworkWinsock::throwErrorOnSocket(ArchSocket s)
{
  assert(s != NULL);

  // get the error from the socket layer
  int err = 0;
  int size = sizeof(err);
  if (getsockopt_winsock(s->m_socket, SOL_SOCKET, SO_ERROR, &err, &size) == SOCKET_ERROR) {
    err = getsockerror_winsock();
  }

  // throw if there's an error
  if (err != 0) {
    throwError(err);
  }
}

void ArchNetworkWinsock::setBlockingOnSocket(SOCKET s, bool blocking)
{
  assert(s != 0);

  int flag = blocking ? 0 : 1;
  if (ioctl_winsock(s, FIONBIO, &flag) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }
}

bool ArchNetworkWinsock::setNoDelayOnSocket(ArchSocket s, bool noDelay)
{
  assert(s != NULL);

  // get old state
  BOOL oflag;
  int size = sizeof(oflag);
  if (getsockopt_winsock(s->m_socket, IPPROTO_TCP, TCP_NODELAY, &oflag, &size) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }

  // set new state
  BOOL flag = noDelay ? 1 : 0;
  size = sizeof(flag);
  if (setsockopt_winsock(s->m_socket, IPPROTO_TCP, TCP_NODELAY, &flag, size) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }

  return (oflag != 0);
}

bool ArchNetworkWinsock::setReuseAddrOnSocket(ArchSocket s, bool reuse)
{
  assert(s != NULL);

  // get old state
  BOOL oflag;
  int size = sizeof(oflag);
  if (getsockopt_winsock(s->m_socket, SOL_SOCKET, SO_REUSEADDR, &oflag, &size) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }

  // set new state
  BOOL flag = reuse ? 1 : 0;
  size = sizeof(flag);
  if (setsockopt_winsock(s->m_socket, SOL_SOCKET, SO_REUSEADDR, &flag, size) == SOCKET_ERROR) {
    throwError(getsockerror_winsock());
  }

  return (oflag != 0);
}

std::string ArchNetworkWinsock::getHostName()
{
  char name[256];
  if (gethostname_winsock(name, sizeof(name)) == -1) {
    name[0] = '\0';
  } else {
    name[sizeof(name) - 1] = '\0';
  }
  return name;
}

ArchNetAddress ArchNetworkWinsock::newAnyAddr(EAddressFamily family)
{
  ArchNetAddressImpl *addr = NULL;
  switch (family) {
  case kINET: {
    addr = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in));
    struct sockaddr_in *ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
    ipAddr->sin_family = AF_INET;
    ipAddr->sin_port = 0;
    ipAddr->sin_addr.s_addr = INADDR_ANY;
    break;
  }

  case kINET6: {
    addr = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in6));
    struct sockaddr_in6 *ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
    ipAddr->sin6_family = AF_INET6;
    ipAddr->sin6_port = 0;
    memcpy(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any));
    break;
  }

  default:
    assert(0 && "invalid family");
  }
  return addr;
}

ArchNetAddress ArchNetworkWinsock::copyAddr(ArchNetAddress addr)
{
  assert(addr != NULL);

  ArchNetAddressImpl *copy = ArchNetAddressImpl::alloc(addr->m_len);
  memcpy(TYPED_ADDR(void, copy), TYPED_ADDR(void, addr), addr->m_len);
  return copy;
}

std::vector<ArchNetAddress> ArchNetworkWinsock::nameToAddr(const std::string &name)
{
  // allocate address
  std::vector<ArchNetAddressImpl *> addresses;

  struct addrinfo hints;
  struct addrinfo *pResult;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  int ret = -1;

  ARCH->lockMutex(m_mutex);
  if ((ret = getaddrinfo(name.c_str(), NULL, &hints, &pResult)) != 0) {
    ARCH->unlockMutex(m_mutex);
    throwNameError(ret);
  }

  for (auto address = pResult; address != nullptr; address = address->ai_next) {
    addresses.push_back(new ArchNetAddressImpl);
    if (address->ai_family == AF_INET) {
      addresses.back()->m_len = (socklen_t)sizeof(struct sockaddr_in);
    } else {
      addresses.back()->m_len = (socklen_t)sizeof(struct sockaddr_in6);
    }

    memcpy(&addresses.back()->m_addr, address->ai_addr, addresses.back()->m_len);
  }

  freeaddrinfo(pResult);
  ARCH->unlockMutex(m_mutex);
  return addresses;
}

void ArchNetworkWinsock::closeAddr(ArchNetAddress addr)
{
  assert(addr != NULL);

  free(addr);
}

std::string ArchNetworkWinsock::addrToName(ArchNetAddress addr)
{
  assert(addr != NULL);

  char host[1024];
  char service[20];
  int ret =
      getnameinfo(TYPED_ADDR(struct sockaddr, addr), addr->m_len, host, sizeof(host), service, sizeof(service), 0);

  if (ret != NULL) {
    throwNameError(ret);
  }

  // return (primary) name
  std::string name = host;
  return name;
}

std::string ArchNetworkWinsock::addrToString(ArchNetAddress addr)
{
  assert(addr != NULL);

  switch (getAddrFamily(addr)) {
  case kINET: {
    struct sockaddr_in *ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
    return inet_ntoa_winsock(ipAddr->sin_addr);
  }

  case kINET6: {
    char strAddr[INET6_ADDRSTRLEN];
    struct sockaddr_in6 *ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
    inet_ntop(AF_INET6, &ipAddr->sin6_addr, strAddr, INET6_ADDRSTRLEN);
    return strAddr;
  }

  default:
    assert(0 && "unknown address family");
    return "";
  }
}

IArchNetwork::EAddressFamily ArchNetworkWinsock::getAddrFamily(ArchNetAddress addr)
{
  assert(addr != NULL);

  switch (addr->m_addr.ss_family) {
  case AF_INET:
    return kINET;

  case AF_INET6:
    return kINET6;

  default:
    return kUNKNOWN;
  }
}

void ArchNetworkWinsock::setAddrPort(ArchNetAddress addr, int port)
{
  assert(addr != NULL);

  switch (getAddrFamily(addr)) {
  case kINET: {
    struct sockaddr_in *ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
    ipAddr->sin_port = htons_winsock(static_cast<u_short>(port));
    break;
  }

  case kINET6: {
    struct sockaddr_in6 *ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
    ipAddr->sin6_port = htons_winsock(static_cast<u_short>(port));
    break;
  }

  default:
    assert(0 && "unknown address family");
    break;
  }
}

int ArchNetworkWinsock::getAddrPort(ArchNetAddress addr)
{
  assert(addr != NULL);

  switch (getAddrFamily(addr)) {
  case kINET: {
    struct sockaddr_in *ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
    return ntohs_winsock(ipAddr->sin_port);
  }

  case kINET6: {
    struct sockaddr_in6 *ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
    return ntohs_winsock(ipAddr->sin6_port);
  }

  default:
    assert(0 && "unknown address family");
    return 0;
  }
}

bool ArchNetworkWinsock::isAnyAddr(ArchNetAddress addr)
{
  assert(addr != NULL);

  switch (getAddrFamily(addr)) {
  case kINET: {
    struct sockaddr_in *ipAddr = TYPED_ADDR(struct sockaddr_in, addr);
    return (addr->m_len == sizeof(struct sockaddr_in) && ipAddr->sin_addr.s_addr == INADDR_ANY);
  }

  case kINET6: {
    struct sockaddr_in6 *ipAddr = TYPED_ADDR(struct sockaddr_in6, addr);
    return (
        addr->m_len == sizeof(struct sockaddr_in) && memcmp(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0
    );
  }

  default:
    assert(0 && "unknown address family");
    return true;
  }
}

bool ArchNetworkWinsock::isEqualAddr(ArchNetAddress a, ArchNetAddress b)
{
  return (a == b || (a->m_len == b->m_len && memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0));
}

void ArchNetworkWinsock::throwError(int err)
{
  switch (err) {
  case WSAEACCES:
    throw XArchNetworkAccess(new XArchEvalWinsock(err));

  case WSAEMFILE:
  case WSAENOBUFS:
  case WSAENETDOWN:
    throw XArchNetworkResource(new XArchEvalWinsock(err));

  case WSAEPROTOTYPE:
  case WSAEPROTONOSUPPORT:
  case WSAEAFNOSUPPORT:
  case WSAEPFNOSUPPORT:
  case WSAESOCKTNOSUPPORT:
  case WSAEINVAL:
  case WSAENOPROTOOPT:
  case WSAEOPNOTSUPP:
  case WSAESHUTDOWN:
  case WSANOTINITIALISED:
  case WSAVERNOTSUPPORTED:
  case WSASYSNOTREADY:
    throw XArchNetworkSupport(new XArchEvalWinsock(err));

  case WSAEADDRNOTAVAIL:
    throw XArchNetworkNoAddress(new XArchEvalWinsock(err));

  case WSAEADDRINUSE:
    throw XArchNetworkAddressInUse(new XArchEvalWinsock(err));

  case WSAEHOSTUNREACH:
  case WSAENETUNREACH:
    throw XArchNetworkNoRoute(new XArchEvalWinsock(err));

  case WSAENOTCONN:
    throw XArchNetworkNotConnected(new XArchEvalWinsock(err));

  case WSAEDISCON:
    throw XArchNetworkShutdown(new XArchEvalWinsock(err));

  case WSAENETRESET:
  case WSAECONNABORTED:
  case WSAECONNRESET:
    throw XArchNetworkDisconnected(new XArchEvalWinsock(err));

  case WSAECONNREFUSED:
    throw XArchNetworkConnectionRefused(new XArchEvalWinsock(err));

  case WSAEHOSTDOWN:
  case WSAETIMEDOUT:
    throw XArchNetworkTimedOut(new XArchEvalWinsock(err));

  case WSAHOST_NOT_FOUND:
    throw XArchNetworkNameUnknown(new XArchEvalWinsock(err));

  case WSANO_DATA:
    throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err));

  case WSANO_RECOVERY:
    throw XArchNetworkNameFailure(new XArchEvalWinsock(err));

  case WSATRY_AGAIN:
    throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err));

  default:
    throw XArchNetwork(new XArchEvalWinsock(err));
  }
}

void ArchNetworkWinsock::throwNameError(int err)
{
  switch (err) {
  case WSAHOST_NOT_FOUND:
    throw XArchNetworkNameUnknown(new XArchEvalWinsock(err));

  case WSANO_DATA:
    throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err));

  case WSANO_RECOVERY:
    throw XArchNetworkNameFailure(new XArchEvalWinsock(err));

  case WSATRY_AGAIN:
    throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err));

  default:
    throw XArchNetworkName(new XArchEvalWinsock(err));
  }
}
