/*
 * synergy -- mouse and keyboard sharing utility
 * Copyright (C) 2002 Chris Schoeneman, Nick Bolton, Sorin Sbarnea
 * 
 * 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 COPYING 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 "CArchNetworkWinsock.h"
#include "CArch.h"
#include "CArchMultithreadWindows.h"
#include "IArchMultithread.h"
#include "XArchWindows.h"
#include <malloc.h>

static const int s_family[] = {
	PF_UNSPEC,
	PF_INET
};
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;
}

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


//
// CArchNetworkWinsock
//

CArchNetworkWinsock::CArchNetworkWinsock()
{
	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 {
			init((HMODULE)::LoadLibrary(s_library[i]));
			m_mutex = ARCH->newMutex();
			return;
		}
		catch (XArchNetwork&) {
			// ignore
		}
	}

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

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

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

void
CArchNetworkWinsock::init(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*/, 0 /*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;
}

CArchSocket
CArchNetworkWinsock::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);
	}
	catch (...) {
		close_winsock(fd);
		throw;
	}

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

CArchSocket
CArchNetworkWinsock::copySocket(CArchSocket s)
{
	assert(s != NULL);

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

void
CArchNetworkWinsock::closeSocket(CArchSocket 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
CArchNetworkWinsock::closeSocketForRead(CArchSocket s)
{
	assert(s != NULL);

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

void
CArchNetworkWinsock::closeSocketForWrite(CArchSocket s)
{
	assert(s != NULL);

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

void
CArchNetworkWinsock::bindSocket(CArchSocket s, CArchNetAddress addr)
{
	assert(s    != NULL);
	assert(addr != NULL);

	if (bind_winsock(s->m_socket, &addr->m_addr, addr->m_len) == SOCKET_ERROR) {
		throwError(getsockerror_winsock());
	}
}

void
CArchNetworkWinsock::listenOnSocket(CArchSocket s)
{
	assert(s != NULL);

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

CArchSocket
CArchNetworkWinsock::acceptSocket(CArchSocket s, CArchNetAddress* addr)
{
	assert(s != NULL);

	// create new socket and temporary address
	CArchSocketImpl* socket = new CArchSocketImpl;
	CArchNetAddress tmp = CArchNetAddressImpl::alloc(sizeof(struct sockaddr));

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

	try {
		setBlockingOnSocket(fd, false);
	}
	catch (...) {
		close_winsock(fd);
		delete socket;
		free(tmp);
		*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
CArchNetworkWinsock::connectSocket(CArchSocket s, CArchNetAddress addr)
{
	assert(s    != NULL);
	assert(addr != NULL);

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

int
CArchNetworkWinsock::pollSocket(CPollEntry 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
	CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance();
	CArchThread thread     = mt->newCurrentThread();
	WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread);
	ARCH->closeThread(thread);
	if (unblockEvent == NULL) {
		unblockEvent  = new WSAEVENT;
		*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
CArchNetworkWinsock::unblockPollSocket(CArchThread thread)
{
	// set the unblock event
	CArchMultithreadWindows* mt = CArchMultithreadWindows::getInstance();
	WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread);
	if (unblockEvent != NULL) {
		WSASetEvent_winsock(*unblockEvent);
	}
}

size_t
CArchNetworkWinsock::readSocket(CArchSocket 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
CArchNetworkWinsock::writeSocket(CArchSocket 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
CArchNetworkWinsock::throwErrorOnSocket(CArchSocket 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
CArchNetworkWinsock::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
CArchNetworkWinsock::setNoDelayOnSocket(CArchSocket 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
CArchNetworkWinsock::setReuseAddrOnSocket(CArchSocket 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
CArchNetworkWinsock::getHostName()
{
	char name[256];
	if (gethostname_winsock(name, sizeof(name)) == -1) {
		name[0] = '\0';
	}
	else {
		name[sizeof(name) - 1] = '\0';
	}
	return name;
}

CArchNetAddress
CArchNetworkWinsock::newAnyAddr(EAddressFamily family)
{
	CArchNetAddressImpl* addr = NULL;
	switch (family) {
	case kINET: {
		addr = CArchNetAddressImpl::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;
	}

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

CArchNetAddress
CArchNetworkWinsock::copyAddr(CArchNetAddress addr)
{
	assert(addr != NULL);

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

CArchNetAddress
CArchNetworkWinsock::nameToAddr(const std::string& name)
{
	// allocate address
	CArchNetAddressImpl* addr = NULL;

	// try to convert assuming an IPv4 dot notation address
	struct sockaddr_in inaddr;
	memset(&inaddr, 0, sizeof(inaddr));
	inaddr.sin_family      = AF_INET;
	inaddr.sin_port        = 0;
	inaddr.sin_addr.s_addr = inet_addr_winsock(name.c_str());
	if (inaddr.sin_addr.s_addr != INADDR_NONE) {
		// it's a dot notation address
		addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in));
		memcpy(TYPED_ADDR(void, addr), &inaddr, addr->m_len);
	}

	else {
		// address lookup
		struct hostent* info = gethostbyname_winsock(name.c_str());
		if (info == NULL) {
			throwNameError(getsockerror_winsock());
		}

		// copy over address (only IPv4 currently supported)
		if (info->h_addrtype == AF_INET) {
			addr = CArchNetAddressImpl::alloc(sizeof(struct sockaddr_in));
			memcpy(&inaddr.sin_addr, info->h_addr_list[0],
								sizeof(inaddr.sin_addr));
			memcpy(TYPED_ADDR(void, addr), &inaddr, addr->m_len);
		}
		else {
			throw XArchNetworkNameUnsupported(
					"The requested name is valid but "
					"does not have a supported address family");
		}
	}

	return addr;
}

void
CArchNetworkWinsock::closeAddr(CArchNetAddress addr)
{
	assert(addr != NULL);

	free(addr);
}

std::string
CArchNetworkWinsock::addrToName(CArchNetAddress addr)
{
	assert(addr != NULL);

	// name lookup
	struct hostent* info = gethostbyaddr_winsock(
							reinterpret_cast<const char FAR*>(&addr->m_addr),
							addr->m_len, addr->m_addr.sa_family);
	if (info == NULL) {
		throwNameError(getsockerror_winsock());
	}

	// return (primary) name
	return info->h_name;
}

std::string
CArchNetworkWinsock::addrToString(CArchNetAddress addr)
{
	assert(addr != NULL);

	switch (getAddrFamily(addr)) {
	case kINET: {
		struct sockaddr_in* ipAddr =
			reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
		return inet_ntoa_winsock(ipAddr->sin_addr);
	}

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

IArchNetwork::EAddressFamily
CArchNetworkWinsock::getAddrFamily(CArchNetAddress addr)
{
	assert(addr != NULL);

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

	default:
		return kUNKNOWN;
	}
}

void
CArchNetworkWinsock::setAddrPort(CArchNetAddress addr, int port)
{
	assert(addr != NULL);

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

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

int
CArchNetworkWinsock::getAddrPort(CArchNetAddress addr)
{
	assert(addr != NULL);

	switch (getAddrFamily(addr)) {
	case kINET: {
		struct sockaddr_in* ipAddr =
			reinterpret_cast<struct sockaddr_in*>(&addr->m_addr);
		return ntohs_winsock(ipAddr->sin_port);
	}

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

bool
CArchNetworkWinsock::isAnyAddr(CArchNetAddress addr)
{
	assert(addr != NULL);

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

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

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

void
CArchNetworkWinsock::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
CArchNetworkWinsock::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));
	}
}
