/*
 *  WINGs WMConnection function library
 *
 *  Copyright (c) 1999-2003 Dan Pascu
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * TODO:
 * - decide if we want to support connections with external sockets, else
 *   clean up the structure of the unneeded members.
 * - decide what to do with all wwarning() calls that are still there.
 *
 */

#include "wconfig.h"

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#ifdef __FreeBSD__
#include <sys/signal.h>
#endif

#include "WINGs.h"

/* Some older systems does not define this (linux libc5, maybe others too) */
#ifndef SHUT_RDWR
# define SHUT_RDWR              2
#endif

/* For SunOS */
#ifndef SA_RESTART
# define SA_RESTART             0
#endif

/* For Solaris */
#ifndef INADDR_NONE
# define INADDR_NONE           -1
#endif

/* Stuff for setting the sockets into non-blocking mode. */
/*
#ifdef	__POSIX_SOURCE
# define NONBLOCK_OPT           O_NONBLOCK
#else
# define NONBLOCK_OPT           FNDELAY
#endif
*/

#define NONBLOCK_OPT            O_NONBLOCK

#define NETBUF_SIZE             4096

#define DEF_TIMEOUT             600	/* 600 seconds == 10 minutes */

int WCErrorCode = 0;

static Bool SigInitialized = False;

static unsigned int DefaultTimeout = DEF_TIMEOUT;
static unsigned int OpenTimeout = DEF_TIMEOUT;

typedef struct TimeoutData {
	unsigned timeout;
	WMHandlerID *handler;
} TimeoutData;

typedef struct W_Connection {
	int sock;		/* the socket we speak through */

	struct {
		WMHandlerID *read;	/* the input read handler */
		WMHandlerID *write;	/* the input write handler */
		WMHandlerID *exception;	/* the input exception handler */
	} handler;

	ConnectionDelegate *delegate;	/* client delegates */
	void *clientData;	/* client data */
	unsigned int uflags;	/* flags for the client */

	WMArray *outputQueue;
	unsigned bufPos;

	TimeoutData sendTimeout;
	TimeoutData openTimeout;

	WMConnectionState state;
	WMConnectionTimeoutState timeoutState;

	char *address;
	char *service;
	char *protocol;

	Bool closeOnRelease;
	Bool shutdownOnClose;
	Bool wasNonBlocking;
	Bool isNonBlocking;

} W_Connection;

static void clearOutputQueue(WMConnection * cPtr)
{
	cPtr->bufPos = 0;
	WMEmptyArray(cPtr->outputQueue);
}

static void openTimeout(void *cdata)
{
	WMConnection *cPtr = (WMConnection *) cdata;

	cPtr->openTimeout.handler = NULL;
	if (cPtr->handler.write) {
		WMDeleteInputHandler(cPtr->handler.write);
		cPtr->handler.write = NULL;
	}
	if (cPtr->state != WCConnected) {
		cPtr->state = WCTimedOut;
		cPtr->timeoutState = WCTWhileOpening;
		if (cPtr->delegate && cPtr->delegate->didTimeout) {
			(*cPtr->delegate->didTimeout) (cPtr->delegate, cPtr);
		} else {
			WMCloseConnection(cPtr);
			cPtr->state = WCTimedOut;	/* the above set state to WCClosed */
		}
	}
}

static void sendTimeout(void *cdata)
{
	WMConnection *cPtr = (WMConnection *) cdata;

	cPtr->sendTimeout.handler = NULL;
	if (cPtr->handler.write) {
		WMDeleteInputHandler(cPtr->handler.write);
		cPtr->handler.write = NULL;
	}
	if (WMGetArrayItemCount(cPtr->outputQueue) > 0) {
		clearOutputQueue(cPtr);
		cPtr->state = WCTimedOut;
		cPtr->timeoutState = WCTWhileSending;
		/* // should we close it no matter what (after calling the didTimeout
		   // delegate)? -Dan */
		if (cPtr->delegate && cPtr->delegate->didTimeout) {
			(*cPtr->delegate->didTimeout) (cPtr->delegate, cPtr);
		} else {
			WMCloseConnection(cPtr);
			cPtr->state = WCTimedOut;	/* the above set state to WCClosed */
		}
	}
}

static void inputHandler(int fd, int mask, void *clientData)
{
	WMConnection *cPtr = (WMConnection *) clientData;

	if (cPtr->state == WCClosed || cPtr->state == WCDied)
		return;

	if ((mask & WIWriteMask)) {
		int result;

		if (cPtr->state == WCInProgress) {
			Bool failed;
			socklen_t len = sizeof(result);

			WCErrorCode = 0;
			if (getsockopt(cPtr->sock, SOL_SOCKET, SO_ERROR,
				       (void *)&result, &len) == 0 && result != 0) {
				cPtr->state = WCFailed;
				WCErrorCode = result;
				failed = True;
				/* should call wsyserrorwithcode(result, ...) here? */
			} else {
				cPtr->state = WCConnected;
				failed = False;
			}

			if (cPtr->handler.write) {
				WMDeleteInputHandler(cPtr->handler.write);
				cPtr->handler.write = NULL;
			}

			if (cPtr->openTimeout.handler) {
				WMDeleteTimerHandler(cPtr->openTimeout.handler);
				cPtr->openTimeout.handler = NULL;
			}

			if (cPtr->delegate && cPtr->delegate->didInitialize)
				(*cPtr->delegate->didInitialize) (cPtr->delegate, cPtr);

			/* we use failed and not cPtr->state here, because cPtr may be
			 * destroyed by the delegate called above if the connection failed
			 */
			if (failed)
				return;
		} else if (cPtr->state == WCConnected) {
			result = WMFlushConnection(cPtr);
			if (result > 0 && cPtr->delegate && cPtr->delegate->canResumeSending) {
				(*cPtr->delegate->canResumeSending) (cPtr->delegate, cPtr);
			}
		}
	}

	if (!cPtr->delegate)
		return;

	/* if the connection died, may get destroyed in the delegate, so retain */
	wretain(cPtr);

	if ((mask & WIReadMask) && cPtr->delegate->didReceiveInput)
		(*cPtr->delegate->didReceiveInput) (cPtr->delegate, cPtr);

	if ((mask & WIExceptMask) && cPtr->delegate->didCatchException)
		(*cPtr->delegate->didCatchException) (cPtr->delegate, cPtr);

	wrelease(cPtr);
}

static Bool setSocketNonBlocking(int sock, Bool flag)
{
	int state;
	Bool isNonBlock;

	state = fcntl(sock, F_GETFL, 0);

	if (state < 0) {
		/* set WCErrorCode here? -Dan */
		return False;
	}

	isNonBlock = (state & NONBLOCK_OPT) != 0;

	if (flag) {
		if (isNonBlock)
			return True;
		state |= NONBLOCK_OPT;
	} else {
		if (!isNonBlock)
			return True;
		state &= ~NONBLOCK_OPT;
	}

	if (fcntl(sock, F_SETFL, state) < 0) {
		/* set WCErrorCode here? -Dan */
		return False;
	}

	return True;
}

static void setConnectionAddress(WMConnection * cPtr, struct sockaddr_in *socketaddr)
{
	wassertr(cPtr->address == NULL);

	cPtr->address = wstrdup(inet_ntoa(socketaddr->sin_addr));
	cPtr->service = wmalloc(16);
	sprintf(cPtr->service, "%hu", ntohs(socketaddr->sin_port));
	cPtr->protocol = wstrdup("tcp");
}

static struct sockaddr_in *getSocketAddress(char *name, char *service, char *protocol)
{
	static struct sockaddr_in socketaddr;
	struct servent *sp;

	if (!protocol || protocol[0] == 0)
		protocol = "tcp";

	memset(&socketaddr, 0, sizeof(struct sockaddr_in));
	socketaddr.sin_family = AF_INET;

	/*
	 * If we were given a hostname, we use any address for that host.
	 * Otherwise we expect the given name to be an address unless it is
	 * NULL (any address).
	 */
	if (name && name[0] != 0) {
		WMHost *host = WMGetHostWithName(name);

		if (!host)
			return NULL;	/* name is not a hostname nor a number and dot adr */

		name = WMGetHostAddress(host);
#ifndef	HAVE_INET_ATON
		if ((socketaddr.sin_addr.s_addr = inet_addr(name)) == INADDR_NONE) {
#else
		if (inet_aton(name, &socketaddr.sin_addr) == 0) {
#endif
			WMReleaseHost(host);
			return NULL;
		}
		WMReleaseHost(host);
	} else {
		socketaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	}

	if (!service || service[0] == 0) {
		socketaddr.sin_port = 0;
	} else if ((sp = getservbyname(service, protocol)) == 0) {
		char *endptr;
		unsigned portNumber;

		portNumber = strtoul(service, &endptr, 10);

		if (service[0] != 0 && *endptr == 0 && portNumber < 65536) {
			socketaddr.sin_port = htons(portNumber);
		} else {
			return NULL;
		}
	} else {
		socketaddr.sin_port = sp->s_port;
	}

	return &socketaddr;
}

static void dummyHandler(int signum)
{
}

static WMConnection *createConnectionWithSocket(int sock, Bool closeOnRelease)
{
	WMConnection *cPtr;
	struct sigaction sig_action;

	cPtr = wmalloc(sizeof(WMConnection));
	wretain(cPtr);
	memset(cPtr, 0, sizeof(WMConnection));

	fcntl(sock, F_SETFD, FD_CLOEXEC);	/* by default close on exec */

	cPtr->sock = sock;
	cPtr->openTimeout.timeout = OpenTimeout;
	cPtr->openTimeout.handler = NULL;
	cPtr->sendTimeout.timeout = DefaultTimeout;
	cPtr->sendTimeout.handler = NULL;
	cPtr->closeOnRelease = closeOnRelease;
	cPtr->shutdownOnClose = 1;
	cPtr->outputQueue = WMCreateArrayWithDestructor(16, (WMFreeDataProc *) WMReleaseData);
	cPtr->state = WCNotConnected;
	cPtr->timeoutState = WCTNone;

	/* ignore dead pipe */
	if (!SigInitialized) {
		/* Because POSIX mandates that only signal with handlers are reset
		 * accross an exec*(), we do not want to propagate ignoring SIGPIPEs
		 * to children. Hence the dummy handler. Philippe Troin <phil@fifi.org>
		 */
		sig_action.sa_handler = &dummyHandler;
		sig_action.sa_flags = SA_RESTART;
		sigaction(SIGPIPE, &sig_action, NULL);
		SigInitialized = True;
	}

	return cPtr;
}

#if 0
WMConnection *WMCreateConnectionWithSocket(int sock, Bool closeOnRelease)
{
	WMConnection *cPtr;
	struct sockaddr_in clientname;
	int size;
	int n;

	cPtr = createConnectionWithSocket(sock, closeOnRelease);
	cPtr->wasNonBlocking = WMIsConnectionNonBlocking(cPtr);
	cPtr->isNonBlocking = cPtr->wasNonBlocking;

	/* some way to find out if it is connected, and binded. can't find
	   if it listens though!!!
	 */

	size = sizeof(clientname);
	n = getpeername(sock, (struct sockaddr *)&clientname, &size);
	if (n == 0) {
		/* Since we have a peer, it means we are connected */
		cPtr->state = WCConnected;
	} else {
		size = sizeof(clientname);
		n = getsockname(sock, (struct sockaddr *)&clientname, &size);
		if (n == 0) {
			/* We don't have a peer, but we are binded to an address.
			 * Assume we are listening on it (we don't know that for sure!)
			 */
			cPtr->state = WCListening;
		} else {
			cPtr->state = WCNotConnected;
		}
	}

	return cPtr;
}
#endif

/*
 * host     is the name on which we want to listen for incoming connections,
 *          and it must be a name of this host, or NULL if we want to listen
 *          on any incoming address.
 * service  is either a service name as present in /etc/services, or the port
 *          number we want to listen on. If NULL, a random port between
 *          1024 and 65535 will be assigned to us.
 * protocol is one of "tcp" or "udp". If NULL, "tcp" will be used by default.
 *          currently only "tcp" is supported.
 */
WMConnection *WMCreateConnectionAsServerAtAddress(char *host, char *service, char *protocol)
{
	WMConnection *cPtr;
	struct sockaddr_in *socketaddr;
	socklen_t size;
	int sock, on;

	WCErrorCode = 0;

	if ((socketaddr = getSocketAddress(host, service, protocol)) == NULL) {
		wwarning(_("Bad address-service-protocol combination"));
		return NULL;
	}

	/* Create the actual socket */
	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		WCErrorCode = errno;
		return NULL;
	}

	/*
	 * Set socket options. We try to make the port reusable and have it
	 * close as fast as possible without waiting in unnecessary wait states
	 * on close.
	 */
	on = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));

	if (bind(sock, (struct sockaddr *)socketaddr, sizeof(*socketaddr)) < 0) {
		WCErrorCode = errno;
		close(sock);
		return NULL;
	}

	if (listen(sock, 10) < 0) {
		WCErrorCode = errno;
		close(sock);
		return NULL;
	}

	/* Find out what is the address/service/protocol we get */
	/* In case some of address/service/protocol were NULL */
	size = sizeof(*socketaddr);
	if (getsockname(sock, (struct sockaddr *)socketaddr, &size) < 0) {
		WCErrorCode = errno;
		close(sock);
		return NULL;
	}

	cPtr = createConnectionWithSocket(sock, True);
	cPtr->state = WCListening;
	WMSetConnectionNonBlocking(cPtr, True);

	setConnectionAddress(cPtr, socketaddr);

	return cPtr;
}

WMConnection *WMCreateConnectionToAddress(char *host, char *service, char *protocol)
{
	WMConnection *cPtr;
	struct sockaddr_in *socketaddr;
	int sock;

	WCErrorCode = 0;

	wassertrv(service != NULL && service[0] != 0, NULL);

	if (host == NULL || host[0] == 0)
		host = "localhost";

	if ((socketaddr = getSocketAddress(host, service, protocol)) == NULL) {
		wwarning(_("Bad address-service-protocol combination"));
		return NULL;
	}

	/* Create the actual socket */
	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		WCErrorCode = errno;
		return NULL;
	}
	/* make socket blocking while we connect. */
	setSocketNonBlocking(sock, False);
	if (connect(sock, (struct sockaddr *)socketaddr, sizeof(*socketaddr)) < 0) {
		WCErrorCode = errno;
		close(sock);
		return NULL;
	}

	cPtr = createConnectionWithSocket(sock, True);
	cPtr->state = WCConnected;
	WMSetConnectionNonBlocking(cPtr, True);
	setConnectionAddress(cPtr, socketaddr);

	return cPtr;
}

WMConnection *WMCreateConnectionToAddressAndNotify(char *host, char *service, char *protocol)
{
	WMConnection *cPtr;
	struct sockaddr_in *socketaddr;
	int sock;
	Bool isNonBlocking;

	WCErrorCode = 0;

	wassertrv(service != NULL && service[0] != 0, NULL);

	if (host == NULL || host[0] == 0)
		host = "localhost";

	if ((socketaddr = getSocketAddress(host, service, protocol)) == NULL) {
		wwarning(_("Bad address-service-protocol combination"));
		return NULL;
	}

	/* Create the actual socket */
	sock = socket(PF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
		WCErrorCode = errno;
		return NULL;
	}
	isNonBlocking = setSocketNonBlocking(sock, True);
	if (connect(sock, (struct sockaddr *)socketaddr, sizeof(*socketaddr)) < 0) {
		if (errno != EINPROGRESS) {
			WCErrorCode = errno;
			close(sock);
			return NULL;
		}
	}

	cPtr = createConnectionWithSocket(sock, True);
	cPtr->state = WCInProgress;
	cPtr->isNonBlocking = isNonBlocking;

	cPtr->handler.write = WMAddInputHandler(cPtr->sock, WIWriteMask, inputHandler, cPtr);

	cPtr->openTimeout.handler = WMAddTimerHandler(cPtr->openTimeout.timeout * 1000, openTimeout, cPtr);

	setConnectionAddress(cPtr, socketaddr);

	return cPtr;
}

static void removeAllHandlers(WMConnection * cPtr)
{
	if (cPtr->handler.read)
		WMDeleteInputHandler(cPtr->handler.read);
	if (cPtr->handler.write)
		WMDeleteInputHandler(cPtr->handler.write);
	if (cPtr->handler.exception)
		WMDeleteInputHandler(cPtr->handler.exception);
	if (cPtr->openTimeout.handler)
		WMDeleteTimerHandler(cPtr->openTimeout.handler);
	if (cPtr->sendTimeout.handler)
		WMDeleteTimerHandler(cPtr->sendTimeout.handler);

	cPtr->handler.read = NULL;
	cPtr->handler.write = NULL;
	cPtr->handler.exception = NULL;
	cPtr->openTimeout.handler = NULL;
	cPtr->sendTimeout.handler = NULL;
}

void WMDestroyConnection(WMConnection * cPtr)
{
	if (cPtr->closeOnRelease && cPtr->sock >= 0) {
		if (cPtr->shutdownOnClose) {
			shutdown(cPtr->sock, SHUT_RDWR);
		}
		close(cPtr->sock);
	}

	removeAllHandlers(cPtr);
	WMFreeArray(cPtr->outputQueue);	/* will also free the items with the destructor */

	if (cPtr->address) {
		wfree(cPtr->address);
		wfree(cPtr->service);
		wfree(cPtr->protocol);
	}

	wrelease(cPtr);
}

void WMCloseConnection(WMConnection * cPtr)
{
	if (cPtr->sock >= 0) {
		if (cPtr->shutdownOnClose) {
			shutdown(cPtr->sock, SHUT_RDWR);
		}
		close(cPtr->sock);
		cPtr->sock = -1;
	}

	removeAllHandlers(cPtr);
	clearOutputQueue(cPtr);

	cPtr->state = WCClosed;
}

WMConnection *WMAcceptConnection(WMConnection * listener)
{
	struct sockaddr_in clientname;
	socklen_t size;
	int newSock;
	WMConnection *newConnection;

	WCErrorCode = 0;
	wassertrv(listener && listener->state == WCListening, NULL);

	size = sizeof(clientname);
	newSock = accept(listener->sock, (struct sockaddr *)&clientname, &size);
	if (newSock < 0) {
		WCErrorCode = ((errno != EAGAIN && errno != EWOULDBLOCK) ? errno : 0);
		return NULL;
	}

	newConnection = createConnectionWithSocket(newSock, True);
	WMSetConnectionNonBlocking(newConnection, True);
	newConnection->state = WCConnected;
	setConnectionAddress(newConnection, &clientname);

	return newConnection;
}

char *WMGetConnectionAddress(WMConnection * cPtr)
{
	return cPtr->address;
}

char *WMGetConnectionService(WMConnection * cPtr)
{
	return cPtr->service;
}

char *WMGetConnectionProtocol(WMConnection * cPtr)
{
	return cPtr->protocol;
}

int WMGetConnectionSocket(WMConnection * cPtr)
{
	return cPtr->sock;
}

WMConnectionState WMGetConnectionState(WMConnection * cPtr)
{
	return cPtr->state;
}

WMConnectionTimeoutState WMGetConnectionTimeoutState(WMConnection * cPtr)
{
	return cPtr->timeoutState;
}

Bool WMEnqueueConnectionData(WMConnection * cPtr, WMData * data)
{
	wassertrv(cPtr->state != WCNotConnected && cPtr->state != WCListening, False);
	wassertrv(cPtr->state != WCInProgress && cPtr->state != WCFailed, False);

	if (cPtr->state != WCConnected)
		return False;

	WMAddToArray(cPtr->outputQueue, WMRetainData(data));
	return True;
}

/*
 * Return value:
 * -1 - not connected or connection died while sending
 *  0 - couldn't send the data (or part of it). data is saved in a queue
 *      and will be sent when possible. after it is sent the canResumeSending
 *      callback will be called.
 *  1 - data was succesfully sent
 */
int WMSendConnectionData(WMConnection * cPtr, WMData * data)
{
	int bytes, pos, len;
	TimeoutData *tPtr = &cPtr->sendTimeout;
	const unsigned char *dataBytes;

	wassertrv(cPtr->state != WCNotConnected && cPtr->state != WCListening, -1);
	wassertrv(cPtr->state != WCInProgress && cPtr->state != WCFailed, -1);

	if (cPtr->state != WCConnected)
		return -1;

	/* If we have no data just flush the queue, else try to send data */
	if (data && WMGetDataLength(data) > 0) {
		WMAddToArray(cPtr->outputQueue, WMRetainData(data));
		/* If there already was something in queue, and also a write input
		 * handler is established, it means we were unable to send, so
		 * return and let the write handler notify us when we can send.
		 */
		if (WMGetArrayItemCount(cPtr->outputQueue) > 1 && cPtr->handler.write)
			return 0;
	}

	while (WMGetArrayItemCount(cPtr->outputQueue) > 0) {
		data = WMGetFromArray(cPtr->outputQueue, 0);
		dataBytes = (const unsigned char *)WMDataBytes(data);
		len = WMGetDataLength(data);
		pos = cPtr->bufPos;	/* where we're left last time */
		while (pos < len) {
 again:
			bytes = write(cPtr->sock, dataBytes + pos, len - pos);
			if (bytes < 0) {
				switch (errno) {
				case EINTR:
					goto again;
				case EWOULDBLOCK:
					/* save the position where we're left and add a timeout */
					cPtr->bufPos = pos;
					if (!tPtr->handler) {
						tPtr->handler = WMAddTimerHandler(tPtr->timeout * 1000,
										  sendTimeout, cPtr);
					}
					if (!cPtr->handler.write) {
						cPtr->handler.write =
						    WMAddInputHandler(cPtr->sock, WIWriteMask, inputHandler, cPtr);
					}
					return 0;
				default:
					WCErrorCode = errno;
					cPtr->state = WCDied;
					removeAllHandlers(cPtr);
					if (cPtr->delegate && cPtr->delegate->didDie)
						(*cPtr->delegate->didDie) (cPtr->delegate, cPtr);
					return -1;
				}
			}
			pos += bytes;
		}
		WMDeleteFromArray(cPtr->outputQueue, 0);
		cPtr->bufPos = 0;
		if (tPtr->handler) {
			WMDeleteTimerHandler(tPtr->handler);
			tPtr->handler = NULL;
		}
		/*if (cPtr->handler.write) {
		   WMDeleteInputHandler(cPtr->handler.write);
		   cPtr->handler.write = NULL;
		   } */
	}

	if (cPtr->handler.write) {
		WMDeleteInputHandler(cPtr->handler.write);
		cPtr->handler.write = NULL;
	}

	return 1;
}

/*
 * WMGetConnectionAvailableData(connection):
 *
 * will return a WMData structure containing the available data on the
 * specified connection. If connection is non-blocking (default) and no data
 * is available when this function is called, an empty WMData is returned.
 *
 * If an error occurs while reading or the other side closed connection,
 * it will return NULL.
 * Also trying to read from an already died or closed connection is
 * considered to be an error condition, and will return NULL.
 */
WMData *WMGetConnectionAvailableData(WMConnection * cPtr)
{
	char buffer[NETBUF_SIZE];
	int nbytes;
	WMData *aData;

	wassertrv(cPtr->state != WCNotConnected && cPtr->state != WCListening, NULL);
	wassertrv(cPtr->state != WCInProgress && cPtr->state != WCFailed, NULL);

	if (cPtr->state != WCConnected)
		return NULL;

	aData = NULL;

 again:
	nbytes = read(cPtr->sock, buffer, NETBUF_SIZE);
	if (nbytes < 0) {
		switch (errno) {
		case EINTR:
			goto again;
		case EWOULDBLOCK:
			aData = WMCreateDataWithCapacity(0);
			break;
		default:
			WCErrorCode = errno;
			cPtr->state = WCDied;
			removeAllHandlers(cPtr);
			if (cPtr->delegate && cPtr->delegate->didDie)
				(*cPtr->delegate->didDie) (cPtr->delegate, cPtr);
			break;
		}
	} else if (nbytes == 0) {	/* the other side has closed connection */
		cPtr->state = WCClosed;
		removeAllHandlers(cPtr);
		if (cPtr->delegate && cPtr->delegate->didDie)
			(*cPtr->delegate->didDie) (cPtr->delegate, cPtr);
	} else {
		aData = WMCreateDataWithBytes(buffer, nbytes);
	}

	return aData;
}

void WMSetConnectionDelegate(WMConnection * cPtr, ConnectionDelegate * delegate)
{
	wassertr(cPtr->sock >= 0);
	/* Don't try to set the delegate multiple times */
	wassertr(cPtr->delegate == NULL);

	cPtr->delegate = delegate;
	if (delegate && delegate->didReceiveInput && !cPtr->handler.read)
		cPtr->handler.read = WMAddInputHandler(cPtr->sock, WIReadMask, inputHandler, cPtr);
	if (delegate && delegate->didCatchException && !cPtr->handler.exception)
		cPtr->handler.exception = WMAddInputHandler(cPtr->sock, WIExceptMask, inputHandler, cPtr);
}

#if 0
Bool WMIsConnectionNonBlocking(WMConnection * cPtr)
{
#if 1
	int state;

	state = fcntl(cPtr->sock, F_GETFL, 0);

	if (state < 0) {
		/* If we can't use fcntl on socket, this probably also means we could
		 * not use fcntl to set non-blocking mode, and since a socket defaults
		 * to blocking when created, return False as the best assumption */
		return False;
	}

	return ((state & NONBLOCK_OPT) != 0);
#else
	return cPtr->isNonBlocking;
#endif
}
#endif

Bool WMSetConnectionNonBlocking(WMConnection * cPtr, Bool flag)
{
	wassertrv(cPtr != NULL && cPtr->sock >= 0, False);

	flag = ((flag == 0) ? 0 : 1);

	if (cPtr->isNonBlocking == flag)
		return True;

	if (setSocketNonBlocking(cPtr->sock, flag) == True) {
		cPtr->isNonBlocking = flag;
		return True;
	}

	return False;
}

Bool WMSetConnectionCloseOnExec(WMConnection * cPtr, Bool flag)
{
	wassertrv(cPtr != NULL && cPtr->sock >= 0, False);

	if (fcntl(cPtr->sock, F_SETFD, ((flag == 0) ? 0 : FD_CLOEXEC)) < 0) {
		return False;
	}

	return True;
}

void WMSetConnectionShutdownOnClose(WMConnection * cPtr, Bool flag)
{
	cPtr->shutdownOnClose = ((flag == 0) ? 0 : 1);
}

void *WMGetConnectionClientData(WMConnection * cPtr)
{
	return cPtr->clientData;
}

void WMSetConnectionClientData(WMConnection * cPtr, void *data)
{
	cPtr->clientData = data;
}

unsigned int WMGetConnectionFlags(WMConnection * cPtr)
{
	return cPtr->uflags;
}

void WMSetConnectionFlags(WMConnection * cPtr, unsigned int flags)
{
	cPtr->uflags = flags;
}

WMArray *WMGetConnectionUnsentData(WMConnection * cPtr)
{
	return cPtr->outputQueue;
}

void WMSetConnectionDefaultTimeout(unsigned int timeout)
{
	if (timeout == 0) {
		DefaultTimeout = DEF_TIMEOUT;
	} else {
		DefaultTimeout = timeout;
	}
}

void WMSetConnectionOpenTimeout(unsigned int timeout)
{
	if (timeout == 0) {
		OpenTimeout = DefaultTimeout;
	} else {
		OpenTimeout = timeout;
	}
}

void WMSetConnectionSendTimeout(WMConnection * cPtr, unsigned int timeout)
{
	if (timeout == 0) {
		cPtr->sendTimeout.timeout = DefaultTimeout;
	} else {
		cPtr->sendTimeout.timeout = timeout;
	}
}
