/*
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <qse/si/nwio.h>
#include <qse/cmn/time.h>
#include "../cmn/mem-prv.h"

#if defined(_WIN32)
#	include <winsock2.h>
#	include <ws2tcpip.h> /* sockaddr_in6 */
#	include <windows.h>
#	define  USE_SELECT
#elif defined(__OS2__)
#	if defined(TCPV40HDRS)
#		define  BSD_SELECT
#	endif
#	include <types.h>
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <sys/ioctl.h>
#	include <nerrno.h>
#	if defined(TCPV40HDRS)
#		define  USE_SELECT
#		include <sys/select.h>
#	else
#		include <unistd.h>
#	endif
#elif defined(__DOS__)
#	include <tcp.h>
#	include <sys/ioctl.h>
#	define memset QSE_MEMSET /* FD_SET hardcodes memset() */
#	define select select_s
#	define USE_SELECT
	/* SO_RCVTIMEO doesn't work or i don't know how to get it to work. */
#	undef SO_RCVTIMEO
#	undef SO_SNDTIMEO
#elif defined(HAVE_T_CONNECT) && !defined(HAVE_CONNECT) && defined(HAVE_TIUSER_H)
#	include "../cmn/syscall.h"
#	include <tiuser.h>
#	include <sys/socket.h>
#	include <netinet/in.h>
#	define USE_TLI
#	define USE_SELECT


	extern int t_accept(int, int, struct t_call *);
	extern void *t_alloc(int, int, int);
	extern int t_bind(int, struct t_bind *, struct t_bind *);
	extern int t_close(int);
	extern int t_connect(int, struct t_call *, struct t_call *);
	extern int t_listen(int, struct t_call *);
	extern int t_open(const char *, int, struct t_info *);
	extern int t_errno;
	extern int t_snd(int fd, char*  buf, unsigned int nbytes, int flags);
	extern int t_rcv(int fd, char*  buf, unsigned int nbytes, int* flags);
#elif defined(HAVE_POLL_H)
#	include "../cmn/syscall.h"
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <poll.h>
#	define  USE_POLL
#else
#	include "../cmn/syscall.h"
#	include <sys/socket.h>
#	include <netinet/in.h>
#	define  USE_SELECT
#endif

enum
{
	STATUS_UDP_CONNECT    = (1 << 0),
	STATUS_TMOUT_R_PRESET = (1 << 1),
	STATUS_TMOUT_W_PRESET = (1 << 2)
};

static qse_ssize_t socket_output (
	qse_tio_t* tio, qse_tio_cmd_t cmd, void* buf, qse_size_t size);
static qse_ssize_t socket_input (
	qse_tio_t* tio, qse_tio_cmd_t cmd, void* buf, qse_size_t size);

#define TMOUT_ENABLED(tmout) (tmout.sec >= 0 && tmout.nsec >= 0)

#if defined(_WIN32)
static qse_nwio_errnum_t skerr_to_errnum (DWORD e)
{
	switch (e)
	{
		case WSA_NOT_ENOUGH_MEMORY:
			return QSE_NWIO_ENOMEM;

		case WSA_INVALID_PARAMETER:
		case WSA_INVALID_HANDLE:
			return QSE_NWIO_EINVAL;

		case WSAEACCES:
			return QSE_NWIO_EACCES;

		case WSAEINTR:
			return QSE_NWIO_EINTR;

		case WSAECONNREFUSED:
		case WSAENETUNREACH:
		case WSAEHOSTUNREACH:
		case WSAEHOSTDOWN:
			return QSE_NWIO_ECONN;

		default:
			return QSE_NWIO_ESYSERR;
	}
}
#elif defined(__OS2__)
static qse_nwio_errnum_t skerr_to_errnum (int e)
{
	switch (e)
	{
	#if defined(SOCENOMEM)
		case SOCENOMEM:
			return QSE_NWIO_ENOMEM;
	#endif

		case SOCEINVAL:
			return QSE_NWIO_EINVAL;

		case SOCEACCES:
			return QSE_NWIO_EACCES;

	#if defined(SOCENOENT)
		case SOCENOENT:
			return QSE_NWIO_ENOENT;
	#endif

	#if defined(SOCEXIST)
		case SOCEEXIST:
			return QSE_NWIO_EEXIST;
	#endif
	
		case SOCEINTR:
			return QSE_NWIO_EINTR;

		case SOCEPIPE:
			return QSE_NWIO_EPIPE;

		case SOCECONNREFUSED:
		case SOCENETUNREACH:
		case SOCEHOSTUNREACH:
		case SOCEHOSTDOWN:
			return QSE_NWIO_ECONN;

		default:
			return QSE_NWIO_ESYSERR;
	}
}

#else

static qse_nwio_errnum_t skerr_to_errnum (int e)
{
	switch (e)
	{
		case ENOMEM:
			return QSE_NWIO_ENOMEM;

		case EINVAL:
			return QSE_NWIO_EINVAL;

		case EACCES:
			return QSE_NWIO_EACCES;

		case ENOENT:
			return QSE_NWIO_ENOENT;

		case EEXIST:
			return QSE_NWIO_EEXIST;

		case EINTR:
			return QSE_NWIO_EINTR;

		case EPIPE:
			return QSE_NWIO_EPIPE;

#if defined(EAGAIN) || defined(EWOULDBLOCK)

	#if defined(EAGAIN) && defined(EWOULDBLOCK)
		case EAGAIN:
		#if (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
		#endif
	#elif defined(EAGAIN)
		case EAGAIN:
	#else
		case EWOULDBLOCK;
	#endif
			return QSE_NWIO_EAGAIN;
#endif

#if defined(ECONNREFUSED) || defined(ENETUNREACH) || defined(EHOSTUNREACH) || defined(EHOSTDOWN)
	#if defined(ECONNREFUSED) 
		case ECONNREFUSED:
	#endif
	#if defined(ENETUNREACH) 
		case ENETUNREACH:
	#endif
	#if defined(EHOSTUNREACH) 
		case EHOSTUNREACH:
	#endif
	#if defined(EHOSTDOWN) 
		case EHOSTDOWN:
	#endif
			return QSE_NWIO_ECONN;
#endif

		default:
			return QSE_NWIO_ESYSERR;
	}
}


#if defined(USE_TLI)
static qse_nwio_errnum_t tlierr_to_errnum (int te, int se)
{
	switch (te)
	{
		/* TODO: add more t_error conversion */

		case TACCES:
			return QSE_NWIO_EACCES;

		case TSYSERR:
			return skerr_to_errnum (se);

		default:
			return QSE_NWIO_ESYSERR;
	}
}
#endif


#endif

static qse_nwio_errnum_t tio_errnum_to_nwio_errnum (qse_tio_t* tio)
{
	switch (tio->errnum)
	{
		case QSE_TIO_ENOMEM:
			return QSE_NWIO_ENOMEM;
		case QSE_TIO_EINVAL:
			return QSE_NWIO_EINVAL;
		case QSE_TIO_EACCES:
			return QSE_NWIO_EACCES;
		case QSE_TIO_ENOENT:
			return QSE_NWIO_ENOENT;
		case QSE_TIO_EILSEQ:
			return QSE_NWIO_EILSEQ;
		case QSE_TIO_EICSEQ:
			return QSE_NWIO_EICSEQ;
		case QSE_TIO_EILCHR:
			return QSE_NWIO_EILCHR;
		default:
			return QSE_NWIO_EOTHER;
	}
}

static int wait_for_data (qse_nwio_t* nwio, const qse_ntime_t* tmout, int what)
{
	int xret;

#if defined(USE_POLL)
	struct pollfd pfd;

	pfd.fd = nwio->handle;
	pfd.revents = 0;
	switch (what)
	{
		case 0:
			pfd.events = POLLIN;
			break;

		case 1:
			pfd.events = POLLOUT;
			break;

		case 2:
			pfd.events = POLLIN | POLLOUT;
			break;

		default:
			nwio->errnum = QSE_NWIO_EINVAL;
			return -1;
	}

	xret = poll (&pfd, 1, QSE_SECNSEC_TO_MSEC(tmout->sec, tmout->nsec));
	if (xret <= -1)
	{
		nwio->errnum = skerr_to_errnum (errno);
		return -1;
	}
	else if (xret == 0)
	{
		nwio->errnum = QSE_NWIO_ETMOUT;
		return -1;
	}

	return 0;
#elif defined(USE_SELECT)
	fd_set fds[2];
	struct timeval tv;

	FD_ZERO (&fds[0]);
	FD_ZERO (&fds[1]);

	switch (what)
	{
		case 0: /* read */
		case 1: /* write */
			FD_SET (nwio->handle, &fds[what]);
			break;

		case 2: /* both */
			FD_SET (nwio->handle, &fds[0]);
			FD_SET (nwio->handle, &fds[1]);
			break;

		default:
			nwio->errnum = QSE_NWIO_EINVAL;
			return -1;
	}

	tv.tv_sec = tmout->sec;
	tv.tv_usec = QSE_NSEC_TO_USEC (tmout->nsec);

	xret = select (nwio->handle + 1, &fds[0], &fds[1], QSE_NULL, &tv);
	#if defined(_WIN32)
	if (xret == SOCKET_ERROR)
	#else
	if (xret <= -1)
	#endif
	{
	#if defined(_WIN32)
		nwio->errnum = skerr_to_errnum (WSAGetLastError());
	#elif defined(__OS2__)
		nwio->errnum = skerr_to_errnum (sock_errno());
	#else
		nwio->errnum = skerr_to_errnum (errno);
	#endif
		return -1;
	}
	else if (xret == 0)
	{
		nwio->errnum = QSE_NWIO_ETMOUT;
		return -1;
	}
	return 0;

#elif defined(__OS2__)
	int count[2] = { 0, 0 };
	long tmout_msecs; 
	
	count[what]++;

	tmout_msecs = QSE_SECNSEC_TO_MSEC (tmout->sec, tmout->nsec);
	xret = os2_select (&nwio->handle, count[0], count[1], 0, tmout_msecs);
	if (xret <= -1)
	{
		nwio->errnum = skerr_to_errnum (sock_errno());
		return -1;
	}
	else if (xret == 0)
	{
		nwio->errnum = QSE_NWIO_ETMOUT;
		return -1;
	}
	return 0;

#else
	nwio->errnum = QSE_NWIO_ENOIMPL;
	return -1;
#endif

}

qse_nwio_t* qse_nwio_open (
	qse_mmgr_t* mmgr, qse_size_t xtnsize, const qse_nwad_t* nwad, 
	int flags, const qse_nwio_tmout_t* tmout)
{
	qse_nwio_t* nwio;

	nwio = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(qse_nwio_t) + xtnsize);
	if (nwio == QSE_NULL) return QSE_NULL;

	if (qse_nwio_init (nwio, mmgr, nwad, flags, tmout) <= -1)
	{
		QSE_MMGR_FREE (mmgr, nwio);
		return QSE_NULL;
	}

	QSE_MEMSET (nwio + 1, 0, xtnsize);
	return nwio;
}

void qse_nwio_close (qse_nwio_t* nwio)
{
	qse_nwio_fini (nwio);
	QSE_MMGR_FREE (nwio->mmgr, nwio);
}

static int preset_tmout (qse_nwio_t* nwio)
{
#if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)
	#if defined(_WIN32)
	DWORD tv;
	#elif defined(__OS2__)
	long	tv;
	#else
	struct timeval tv;
	#endif

	if (TMOUT_ENABLED(nwio->tmout.r))
	{
	#if defined(_WIN32) || defined(__OS2__)
		tv = QSE_SEC_TO_MSEC(nwio->tmout.r.sec) + QSE_NSEC_TO_MSEC (nwio->tmout.r.nsec);
	#else
		tv.tv_sec = nwio->tmout.r.sec;
		tv.tv_usec = QSE_NSEC_TO_USEC (nwio->tmout.r.nsec);
	#endif

		if (setsockopt (nwio->handle, SOL_SOCKET, SO_RCVTIMEO, (void*)&tv, QSE_SIZEOF(tv)) <= -1)
		{
	#if defined(_WIN32)
			nwio->errnum = skerr_to_errnum (WSAGetLastError());
	#elif defined(__OS2__)
			nwio->errnum = skerr_to_errnum (sock_errno());
	#else
			nwio->errnum = skerr_to_errnum (errno);
	#endif
			return -1; /* tried to set but failed */
		}

		nwio->status |= STATUS_TMOUT_R_PRESET;
	}

	if (TMOUT_ENABLED(nwio->tmout.w))
	{
	#if defined(_WIN32) || defined(__OS2__)
		tv = QSE_SEC_TO_MSEC(nwio->tmout.w.sec) + QSE_NSEC_TO_MSEC (nwio->tmout.w.nsec);
	#else
		tv.tv_sec = nwio->tmout.w.sec;
		tv.tv_usec = QSE_NSEC_TO_USEC (nwio->tmout.w.nsec);
	#endif
		if (setsockopt (nwio->handle, SOL_SOCKET, SO_SNDTIMEO, (void*)&tv, QSE_SIZEOF(tv)) <= -1)
		{
	#if defined(_WIN32)
			nwio->errnum = skerr_to_errnum (WSAGetLastError());
	#elif defined(__OS2__)
			nwio->errnum = skerr_to_errnum (sock_errno());
	#else
			nwio->errnum = skerr_to_errnum (errno);
	#endif
			return -1; /* tried to set but failed */
		}

		nwio->status |= STATUS_TMOUT_W_PRESET;
	}

	return 1; /* set successfully - don't need a multiplexer */
#endif

	return 0; /* no measn to set it */
}

int qse_nwio_init (
	qse_nwio_t* nwio, qse_mmgr_t* mmgr, const qse_nwad_t* nwad, 
	int flags, const qse_nwio_tmout_t* tmout)
{
	qse_skad_t addr;
	qse_sck_len_t addrlen;
	int family, type, tmp;

	QSE_MEMSET (nwio, 0, QSE_SIZEOF(*nwio));
	nwio->mmgr = mmgr;
	nwio->flags = flags;
	nwio->errnum = QSE_NWIO_ENOERR;
	if (tmout) nwio->tmout = *tmout;
	else
	{
		nwio->tmout.r.sec = -1;
		nwio->tmout.w.sec = -1;
		nwio->tmout.c.sec = -1;
		nwio->tmout.a.sec = -1;
	}

	tmp = qse_nwadtoskad (nwad, &addr);
	if (tmp <= -1) 
	{
		nwio->errnum = QSE_NWIO_EINVAL;
		return -1;
	}
	addrlen = tmp;

#if defined(SOCK_STREAM) && defined(SOCK_DGRAM)
	if (flags & QSE_NWIO_TCP) type = SOCK_STREAM;
	else if (flags & QSE_NWIO_UDP) type = SOCK_DGRAM;
	else
#endif
	{
		nwio->errnum = QSE_NWIO_EINVAL;
		return -1;
	}

	family = qse_skadfamily (&addr);

#if defined(_WIN32)
	nwio->handle = socket (family, type, 0);
	if (nwio->handle == INVALID_SOCKET)
	{
		nwio->errnum = skerr_to_errnum (WSAGetLastError());
		goto oops;
	}

	if ((flags & QSE_NWIO_TCP) && (flags & QSE_NWIO_KEEPALIVE))
	{
		int optval = 1;
		setsockopt (nwio->handle, SOL_SOCKET, SO_KEEPALIVE, (void*)&optval, QSE_SIZEOF(optval));
	}

	if (flags & QSE_NWIO_PASSIVE)
	{
		qse_nwio_hnd_t handle;

		if (flags & QSE_NWIO_REUSEADDR)
		{
			int optval = 1;
			setsockopt (nwio->handle, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, QSE_SIZEOF(optval));
		}

		if (bind (nwio->handle, (struct sockaddr*)&addr, addrlen) == SOCKET_ERROR)
		{
			nwio->errnum = skerr_to_errnum (WSAGetLastError());
			goto oops;
		}

		if (flags & QSE_NWIO_TCP)
		{
			if (listen (nwio->handle, 10) == SOCKET_ERROR)
			{
				nwio->errnum = skerr_to_errnum (WSAGetLastError());
				goto oops;
			}

			if (TMOUT_ENABLED(nwio->tmout.a) &&
			    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) goto oops;

			handle = accept (nwio->handle, (struct sockaddr*)&addr, &addrlen);
			if (handle == INVALID_SOCKET)
			{
				nwio->errnum = skerr_to_errnum (WSAGetLastError());
				goto oops;
			}

			closesocket (nwio->handle);
			nwio->handle = handle;
		}
		else if (flags & QSE_NWIO_UDP)
		{
			nwio->status |= STATUS_UDP_CONNECT;
		}
	}
	else
	{
		int xret;

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			unsigned long cmd = 1;

			if (ioctlsocket(nwio->handle, FIONBIO, &cmd) == SOCKET_ERROR) 
			{
				nwio->errnum = skerr_to_errnum (WSAGetLastError());
				goto oops;
			}
		}

		xret = connect (nwio->handle, (struct sockaddr*)&addr, addrlen);

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			unsigned long cmd = 0;
			
			if ((xret == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) ||
			    ioctlsocket (nwio->handle, FIONBIO, &cmd) == SOCKET_ERROR)
			{
				nwio->errnum = skerr_to_errnum (WSAGetLastError());
				goto oops;
			}

			if (wait_for_data (nwio, &nwio->tmout.c, 1) <= -1) goto oops;
			else 
			{
				int xlen;
				DWORD xerr;

				xlen = QSE_SIZEOF(xerr);
				if (getsockopt (nwio->handle, SOL_SOCKET, SO_ERROR, (char*)&xerr, &xlen) == SOCKET_ERROR)
				{
					nwio->errnum = skerr_to_errnum (WSAGetLastError());
					goto oops;
				}
				else if (xerr != 0)
				{
					nwio->errnum = skerr_to_errnum (xerr);
					goto oops;
				}
			}
		}
		else
		{
			if (xret == SOCKET_ERROR)
			{
				nwio->errnum = skerr_to_errnum (WSAGetLastError());
				goto oops;
			}
		}
	}

#elif defined(__OS2__)
	nwio->handle = socket (family, type, 0);
	if (nwio->handle <= -1)
	{
		nwio->errnum = skerr_to_errnum (sock_errno());
		goto oops;
	}

	if ((flags & QSE_NWIO_TCP) && (flags & QSE_NWIO_KEEPALIVE))
	{
		int optval = 1;
		setsockopt (nwio->handle, SOL_SOCKET, SO_KEEPALIVE, (void*)&optval, QSE_SIZEOF(optval));
	}

	if (flags & QSE_NWIO_PASSIVE)
	{
		qse_nwio_hnd_t handle;

		if (flags & QSE_NWIO_REUSEADDR)
		{
			int optval = 1;
			setsockopt (nwio->handle, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, QSE_SIZEOF(optval));
		}

		if (bind (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
		{
			nwio->errnum = skerr_to_errnum (sock_errno());
			goto oops;
		}

		if (flags & QSE_NWIO_TCP)
		{
			if (listen (nwio->handle, 10) <= -1)
			{
				nwio->errnum = skerr_to_errnum (sock_errno());
				goto oops;
			}

			if (TMOUT_ENABLED(nwio->tmout.a) &&
			    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) goto oops;

			handle = accept (nwio->handle, (struct sockaddr*)&addr, &addrlen);
			if (handle <= -1)
			{
				nwio->errnum = skerr_to_errnum (sock_errno());
				goto oops;
			}

			soclose (nwio->handle);
			nwio->handle = handle;
		}
		else if (flags & QSE_NWIO_UDP)
		{
			nwio->status |= STATUS_UDP_CONNECT;
		}
	}
	else
	{
		int xret;

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			int noblk = 1;

			if (ioctl (nwio->handle, FIONBIO, (void*)&noblk, QSE_SIZEOF(noblk)) <= -1)
			{
				nwio->errnum = skerr_to_errnum (sock_errno());
				goto oops;
			}
		}

		xret = connect (nwio->handle, (struct sockaddr*)&addr, addrlen);

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			int noblk = 0;
			
			if ((xret <= -1 && sock_errno() != SOCEINPROGRESS) ||
			    ioctl (nwio->handle, FIONBIO, (void*)&noblk, QSE_SIZEOF(noblk)) <= -1)
			{
				nwio->errnum = skerr_to_errnum (sock_errno());
				goto oops;
			}

			if (wait_for_data (nwio, &nwio->tmout.c, 1) <= -1) goto oops;
			else 
			{
				int xlen, xerr;

				xlen = QSE_SIZEOF(xerr);
				if (getsockopt (nwio->handle, SOL_SOCKET, SO_ERROR, (char*)&xerr, &xlen) <= -1)
				{
					nwio->errnum = skerr_to_errnum (sock_errno());
					goto oops;
				}
				else if (xerr != 0)
				{
					nwio->errnum = skerr_to_errnum (xerr);
					goto oops;
				}
			}
		}
		else
		{
			if (xret <= -1)
			{
				nwio->errnum = skerr_to_errnum (sock_errno());
				goto oops;
			}
		}
	}

#elif defined(__DOS__)

	nwio->handle = socket (family, type, 0);
	if (nwio->handle <= -1)
	{
		nwio->errnum = skerr_to_errnum (errno);
		goto oops;
	}

	if ((flags & QSE_NWIO_TCP) && (flags & QSE_NWIO_KEEPALIVE))
	{
		int optval = 1;
		setsockopt (nwio->handle, SOL_SOCKET, SO_KEEPALIVE, (void*)&optval, QSE_SIZEOF(optval));
	}

	if (flags & QSE_NWIO_PASSIVE)
	{
		qse_nwio_hnd_t handle;

	#if defined(SO_REUSEADDR)
		if (flags & QSE_NWIO_REUSEADDR)
		{
			int optval = 1;
			setsockopt (nwio->handle, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, QSE_SIZEOF(optval));
		}
	#endif

		if (bind (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
		{
			nwio->errnum = skerr_to_errnum (errno);
			goto oops;
		}

		if (flags & QSE_NWIO_TCP)
		{
			if (listen (nwio->handle, 10) <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}

			if (TMOUT_ENABLED(nwio->tmout.a) &&
			    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) goto oops;

			handle = accept (nwio->handle, (struct sockaddr*)&addr, &addrlen);
			if (handle <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}

			close_s (nwio->handle);
			nwio->handle = handle;
		}
		else if (flags & QSE_NWIO_UDP)
		{
			nwio->status |= STATUS_UDP_CONNECT;
		}
	}
	else
	{
		int xret;

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			int cmd = 1;

			if (ioctlsocket(nwio->handle, FIONBIO, (char*)&cmd) == SOCKET_ERROR) 
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}
		}

		xret = connect (nwio->handle, (struct sockaddr*)&addr, addrlen);

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			int cmd = 0;

			if ((xret == SOCKET_ERROR && errno != EWOULDBLOCK) ||
			    ioctlsocket (nwio->handle, FIONBIO, (char*)&cmd) == SOCKET_ERROR)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}

			if (wait_for_data (nwio, &nwio->tmout.c, 1) <= -1) goto oops;
			else 
			{
				int xlen, xerr;

				xlen = QSE_SIZEOF(xerr);
				if (getsockopt (nwio->handle, SOL_SOCKET, SO_ERROR, (char*)&xerr, &xlen) <= -1)
				{
					nwio->errnum = skerr_to_errnum (errno);
					goto oops;
				}
				else if (xerr != 0)
				{
					nwio->errnum = skerr_to_errnum (xerr);
					goto oops;
				}
			}
		}
		else
		{
			if (xret <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}
		}
	}

#elif defined(USE_TLI)

	{

		static const qse_mchar_t* dev_path[2][2] = 
		{
			{ "/dev/tcp", "/dev/inet/tcp" },
			{ "/dev/udp", "/dev/inet/tcp" }
		};
		int dev_id;

		if (flags & QSE_NWIO_TCP) dev_id = 0;
		else 
		{
			QSE_ASSERT (flags & QSE_NWIO_UDP);
			dev_id = 1;
		}

		nwio->handle = t_open (dev_path[dev_id][0], O_RDWR, QSE_NULL);
		if (nwio->handle <= -1)
		{
			nwio->handle = t_open (dev_path[dev_id][1], O_RDWR, QSE_NULL);
			if (nwio->handle <= -1)
			{
				nwio->errnum = tlierr_to_errnum (t_errno, errno);
				goto oops;
			}
		}

		if (flags & QSE_NWIO_PASSIVE)
		{
			/* TODO: */
			nwio->errnum = QSE_NWIO_ENOIMPL;
			goto oops;
		}
		else
		{
			struct t_call call; /* for connecting */
			struct t_bind req, ret; /* for binding */
			qse_skad_t reqaddr, retaddr;
			qse_nwad_t reqnwad;

			/*
			call = t_alloc (nwio->handle, T_CALL, T_ADDR);
			if (!call)
			{
				nwio->errnum = tlierr_to_errnum (t_errno, errno);
				goto oops;
			}*/

			qse_clearnwad (&reqnwad, nwad->type);
			qse_nwadtoskad (&reqnwad, &reqaddr);

			QSE_MEMSET (&ret, 0, QSE_SIZEOF(req));
			req.addr.maxlen = addrlen;
			req.addr.len = addrlen;
			req.addr.buf = &reqaddr;

			QSE_MEMSET (&ret, 0, QSE_SIZEOF(ret));
			ret.addr.maxlen = addrlen;
			ret.addr.len = addrlen;
			ret.addr.buf = &retaddr;

			if (t_bind (nwio->handle, &req, &ret) <= -1)
			{
				nwio->errnum = tlierr_to_errnum (t_errno, errno);
				goto oops;
			}

/* TODO: should i use t_alloc() and t_free for call, ret, req? */
			QSE_MEMSET (&call, 0, QSE_SIZEOF(call));
			call.addr.maxlen = addrlen;
			call.addr.len = addrlen;
			call.addr.buf = &addr;

			if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
			{
				int orgfl;

				orgfl = fcntl (nwio->handle, F_GETFL, 0);
				if (orgfl <= -1 || fcntl (nwio->handle, F_SETFL, orgfl | O_NONBLOCK) <= -1)
				{
					nwio->errnum = skerr_to_errnum (errno);
					goto oops;
				}

				if (t_connect (nwio->handle, &call, 0) <= -1)
				{
					if (t_errno != TNODATA)
					{
						nwio->errnum = tlierr_to_errnum (t_errno, errno);
						goto oops;
					}

			/* TODO: this doesn't seem to work wel... REDO THE WORK */
					if (wait_for_data (nwio, &nwio->tmout.c, 0) <= -1) goto oops;

					if (t_rcvconnect (nwio->handle, QSE_NULL) <= -1)
					{
						nwio->errnum = tlierr_to_errnum (t_errno, errno);
						goto oops;
					}
				}

				if (fcntl (nwio->handle, F_SETFL, orgfl) <= -1) 
				{
					nwio->errnum = skerr_to_errnum (errno);
					goto oops;
				}
			}
			else
			{
				if (t_connect (nwio->handle, &call, 0) <= -1)
				{
					nwio->errnum = tlierr_to_errnum (t_errno, errno);
					goto oops;
				}
			}
		}
	}

#else
	#if defined(SOCK_CLOEXEC)
	nwio->handle = socket (family, type | SOCK_CLOEXEC, 0);
	#else
	nwio->handle = socket (family, type, 0);
	#endif
	if (nwio->handle <= -1)
	{
		nwio->errnum = skerr_to_errnum (errno);
		goto oops;
	}

	#if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC)
	{ 
		int tmp = fcntl (nwio->handle, F_GETFD);
		if (tmp >= 0) fcntl (nwio->handle, F_SETFD, tmp | FD_CLOEXEC);
	}
	#endif

	if ((flags & QSE_NWIO_TCP) && (flags & QSE_NWIO_KEEPALIVE))
	{
		int optval = 1;
		setsockopt (nwio->handle, SOL_SOCKET, SO_KEEPALIVE, (void*)&optval, QSE_SIZEOF(optval));
	}

	if (flags & QSE_NWIO_PASSIVE)
	{
		qse_nwio_hnd_t handle;

	#if defined(SO_REUSEADDR)
		if (flags & QSE_NWIO_REUSEADDR)
		{
			int optval = 1;
			setsockopt (nwio->handle, SOL_SOCKET, SO_REUSEADDR, (void*)&optval, QSE_SIZEOF(optval));
		}
	#endif

		if (bind (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
		{
			nwio->errnum = skerr_to_errnum (errno);
			goto oops;
		}

		if (flags & QSE_NWIO_TCP)
		{
			if (listen (nwio->handle, 10) <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}

			if (TMOUT_ENABLED(nwio->tmout.a) &&
			    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) goto oops;

			handle = accept (nwio->handle, (struct sockaddr*)&addr, &addrlen);
			if (handle <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}

			qse_close_sck (nwio->handle); /* close the listening socket */
			nwio->handle = handle; /* set the handle to the accepted socket */
		}
		else if (flags & QSE_NWIO_UDP)
		{
			nwio->status |= STATUS_UDP_CONNECT;
		}
	}
	else
	{
		int xret;

		if (TMOUT_ENABLED(nwio->tmout.c) && (flags & QSE_NWIO_TCP))
		{
			int orgfl;

			orgfl = fcntl (nwio->handle, F_GETFL, 0);
			if (orgfl <= -1 || fcntl (nwio->handle, F_SETFL, orgfl | O_NONBLOCK) <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}
		
			xret = connect (nwio->handle, (struct sockaddr*)&addr, addrlen);
		
			if ((xret <= -1 && errno != EINPROGRESS) ||
			    fcntl (nwio->handle, F_SETFL, orgfl) <= -1) 
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}

			if (wait_for_data (nwio, &nwio->tmout.c, 1) <= -1) goto oops;
			else 
			{
				qse_sck_len_t xlen;
				xlen = QSE_SIZEOF(xret);
				if (getsockopt (nwio->handle, SOL_SOCKET, SO_ERROR, (char*)&xret, &xlen) <= -1)
				{
					nwio->errnum = skerr_to_errnum (errno);
					goto oops;
				}
				else if (xret != 0)
				{
					nwio->errnum = skerr_to_errnum (xret);
					goto oops;
				}
			}
		}
		else
		{
			xret = connect (nwio->handle, (struct sockaddr*)&addr, addrlen);
			if (xret <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				goto oops;
			}
		}
	}
#endif

	if (flags & QSE_NWIO_TEXT)
	{
		int topt = 0;

		if (flags & QSE_NWIO_IGNOREMBWCERR) topt |= QSE_TIO_IGNOREMBWCERR;
		if (flags & QSE_NWIO_NOAUTOFLUSH) topt |= QSE_TIO_NOAUTOFLUSH;

		nwio->tio = qse_tio_open (mmgr, QSE_SIZEOF(qse_nwio_t*), topt);
		if (nwio->tio == QSE_NULL)
		{
			nwio->errnum = QSE_NWIO_ENOMEM;
			goto oops;
		}

		/* store the back-reference to nwio in the extension area.*/
		*(qse_nwio_t**)QSE_XTN(nwio->tio) = nwio;

		if (qse_tio_attachin (nwio->tio, socket_input, QSE_NULL, 4096) <= -1 ||
		    qse_tio_attachout (nwio->tio, socket_output, QSE_NULL, 4096) <= -1)
		{
			if (nwio->errnum == QSE_NWIO_ENOERR) 
				nwio->errnum = tio_errnum_to_nwio_errnum (nwio->tio);
			goto oops;
		}
	}

	preset_tmout (nwio);
	return 0;

oops:
	if (nwio->tio) 
	{
		qse_tio_close (nwio->tio);
		nwio->tio = QSE_NULL;
	}

	if (qse_is_sck_valid(nwio->handle)) qse_close_sck (nwio->handle);
	return -1;
}

void qse_nwio_fini (qse_nwio_t* nwio)
{
	/*if (qse_nwio_flush (nwio) <= -1) return -1;*/
	qse_nwio_flush (nwio);
	if (nwio->tio)
	{
		qse_tio_close (nwio->tio);
		nwio->tio = QSE_NULL;
	}

	qse_close_sck (nwio->handle);
}

qse_mmgr_t* qse_nwio_getmmgr (qse_nwio_t* nwio)
{
	return nwio->mmgr;
}

void* qse_nwio_getxtn (qse_nwio_t* nwio)
{
	return QSE_XTN (nwio);
}

qse_nwio_errnum_t qse_nwio_geterrnum (const qse_nwio_t* nwio)
{
	return nwio->errnum;
}

qse_cmgr_t* qse_nwio_getcmgr (qse_nwio_t* nwio)
{
	return nwio->tio? qse_tio_getcmgr (nwio->tio): QSE_NULL;
}

void qse_nwio_setcmgr (qse_nwio_t* nwio, qse_cmgr_t* cmgr)
{
	if (nwio->tio) qse_tio_setcmgr (nwio->tio, cmgr);
}

qse_nwio_hnd_t qse_nwio_gethnd (const qse_nwio_t* nwio)
{
	return nwio->handle;
}

qse_ssize_t qse_nwio_flush (qse_nwio_t* nwio)
{
	qse_ssize_t n;

	if (nwio->tio)
	{
		nwio->errnum = QSE_NWIO_ENOERR;
		n = qse_tio_flush (nwio->tio);
		if (n <= -1 && nwio->errnum == QSE_NWIO_ENOERR) 
			nwio->errnum = tio_errnum_to_nwio_errnum (nwio->tio);
	}
	else n = 0;

	return n;
}

void qse_nwio_drain (qse_nwio_t* nwio)
{
	if (nwio->tio) qse_tio_drain (nwio->tio);
}

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

static qse_ssize_t nwio_read (qse_nwio_t* nwio, void* buf, qse_size_t size)
{
#if defined(_WIN32)
	int count;
#elif defined(__OS2__)
	int n;
#elif defined(__DOS__)
	int n;
#else
	qse_ssize_t n;
#endif

#if defined(_WIN32)
	if (size > (QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int)))
		size = QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int);

	if (nwio->status & STATUS_UDP_CONNECT)
	{
		qse_skad_t addr;
		int addrlen;

		addrlen = QSE_SIZEOF(addr);

		if (TMOUT_ENABLED(nwio->tmout.a) &&
		    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) return -1;

		count = recvfrom (
			nwio->handle, buf, size, 0, 
			(struct sockaddr*)&addr, &addrlen);
		if (count == SOCKET_ERROR) 
		{
			nwio->errnum = skerr_to_errnum (WSAGetLastError());
		}
		else if (count >= 1)
		{
			/* for udp, it just creates a stream with the
			 * first sender */
			if (connect (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
			{
				nwio->errnum = skerr_to_errnum (WSAGetLastError());
				return -1;
			}
			nwio->status &= ~STATUS_UDP_CONNECT;
		}
	}
	else
	{
		if (!(nwio->status & STATUS_TMOUT_R_PRESET) && 
		    TMOUT_ENABLED(nwio->tmout.r) &&
		    wait_for_data (nwio, &nwio->tmout.r, 0) <= -1) return -1;

		count = recv (nwio->handle, buf, size, 0);
		if (count == SOCKET_ERROR) nwio->errnum = skerr_to_errnum (WSAGetLastError());
	}

	return count;

#elif defined(__OS2__)
	if (size > (QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int)))
		size = QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int);

	if (nwio->status & STATUS_UDP_CONNECT)
	{
		qse_skad_t addr;
		int addrlen;

		addrlen = QSE_SIZEOF(addr);

		if (TMOUT_ENABLED(nwio->tmout.a) &&
		    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) return -1;

		n = recvfrom (
			nwio->handle, buf, size, 0, 
			(struct sockaddr*)&addr, &addrlen);
		if (n <= -1) 
		{
			nwio->errnum = skerr_to_errnum (sock_errno());
		}
		else if (n >= 1)
		{
			/* for udp, it just creates a stream with the
			 * first sender */
			if (connect (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
			{
				nwio->errnum = skerr_to_errnum (sock_errno());
				return -1;
			}
			nwio->status &= ~STATUS_UDP_CONNECT;
		}
	}
	else
	{
		if (!(nwio->status & STATUS_TMOUT_R_PRESET) && 
		    TMOUT_ENABLED(nwio->tmout.r) &&
		    wait_for_data (nwio, &nwio->tmout.r, 0) <= -1) return -1;

		n = recv (nwio->handle, buf, size, 0);
		if (n <= -1) nwio->errnum = skerr_to_errnum (sock_errno());
	}

	return n;

#elif defined(USE_TLI)

	if (size > (QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int)))
		size = QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int);

reread:
	if (nwio->status & STATUS_UDP_CONNECT)
	{
		qse_skad_t addr;
		qse_sck_len_t addrlen;

		addrlen = QSE_SIZEOF(addr);

		/* it's similar to accept for tcp because i'm expecting 
		 * the first sender and call connect() to it below just
		 * like the 'nc' utility does.
		 * so i treat this recvfrom() as if it is accept(). 
		 */
		if (TMOUT_ENABLED(nwio->tmout.a) &&
		    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) return -1;

		/* TODO: */
		nwio->errnum = QSE_NWIO_ENOIMPL;
		return -1;
		/*
		n = recvfrom (
			nwio->handle, buf, size, 0, 
			(struct sockaddr*)&addr, &addrlen);
 		*/

		if (n <= -1) 
		{
			if (t_errno == TSYSERR && errno == EINTR)
			{
				if (nwio->flags & QSE_NWIO_READNORETRY) 
					nwio->errnum = QSE_NWIO_EINTR;
				else goto reread;
			}
			else
			{
				nwio->errnum = tlierr_to_errnum (t_errno, errno);
			}
		}
		else if (n >= 1)
		{
			/* TODO: */
			nwio->errnum = QSE_NWIO_ENOIMPL;
			return -1;

			/* for udp, it just creates a stream with the
			 * first sender */
			/*
			if (t_connect (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				return -1;
			}
			nwio->status &= ~STATUS_UDP_CONNECT;
 			*/
		}
	}
	else
	{
		int flags;

		if (!(nwio->status & STATUS_TMOUT_R_PRESET) && 
		    TMOUT_ENABLED(nwio->tmout.r) &&
		    wait_for_data (nwio, &nwio->tmout.r, 0) <= -1) 
		{
			return -1;
		}

		n = t_rcv (nwio->handle, buf, size, &flags);
		if (n <= -1) 
		{
			if (t_errno == TSYSERR && errno == EINTR)
			{
				if (nwio->flags & QSE_NWIO_READNORETRY) 
					nwio->errnum = QSE_NWIO_EINTR;
				else goto reread;
			}
			else
			{
				nwio->errnum = tlierr_to_errnum (t_errno, errno);
			}
		}
	}

	return n;

#else

	if (size > QSE_TYPE_MAX(qse_ssize_t)) size = QSE_TYPE_MAX(qse_ssize_t);

reread:
	if (nwio->status & STATUS_UDP_CONNECT)
	{
		qse_skad_t addr;
		qse_sck_len_t addrlen;

		addrlen = QSE_SIZEOF(addr);

		/* it's similar to accept for tcp because i'm expecting 
		 * the first sender and call connect() to it below just
		 * like the 'nc' utility does.
		 * so i treat this recvfrom() as if it is accept(). 
		 */
		if (TMOUT_ENABLED(nwio->tmout.a) &&
		    wait_for_data (nwio, &nwio->tmout.a, 0) <= -1) return -1;

		n = recvfrom (
			nwio->handle, buf, size, 0, 
			(struct sockaddr*)&addr, &addrlen);
		if (n <= -1) 
		{
			if (errno == EINTR)
			{
				if (nwio->flags & QSE_NWIO_READNORETRY) 
					nwio->errnum = QSE_NWIO_EINTR;
				else goto reread;
			}
			else
			{
				nwio->errnum = skerr_to_errnum (errno);
			}
		}
		else if (n >= 1)
		{
			/* for udp, it just creates a stream with the
			 * first sender */
			if (connect (nwio->handle, (struct sockaddr*)&addr, addrlen) <= -1)
			{
				nwio->errnum = skerr_to_errnum (errno);
				return -1;
			}
			nwio->status &= ~STATUS_UDP_CONNECT;
		}
	}
	else
	{
		if (!(nwio->status & STATUS_TMOUT_R_PRESET) && 
		    TMOUT_ENABLED(nwio->tmout.r) &&
		    wait_for_data (nwio, &nwio->tmout.r, 0) <= -1) return -1;

		n = recv (nwio->handle, buf, size, 0);
		if (n <= -1) 
		{
			if (errno == EINTR)
			{
				if (nwio->flags & QSE_NWIO_READNORETRY) 
					nwio->errnum = QSE_NWIO_EINTR;
				else goto reread;
			}
			else
			{
				nwio->errnum = skerr_to_errnum (errno);
			}
		}
	}

	return n;
#endif
}

qse_ssize_t qse_nwio_read (qse_nwio_t* nwio, void* buf, qse_size_t size)
{
	if (nwio->tio == QSE_NULL) 
		return nwio_read (nwio, buf, size);
	else
	{
		qse_ssize_t n;

		nwio->errnum = QSE_NWIO_ENOERR;
		n = qse_tio_read (nwio->tio, buf, size);
		if (n <= -1 && nwio->errnum == QSE_NWIO_ENOERR) 
			nwio->errnum = tio_errnum_to_nwio_errnum (nwio->tio);

		return n;
	}
}

static qse_ssize_t nwio_write (qse_nwio_t* nwio, const void* data, qse_size_t size)
{
#if defined(_WIN32)
	int count;
#elif defined(__OS2__)
	int n;
#elif defined(__DOS__)
	int n;
#else
	qse_ssize_t n;
#endif

#if defined(_WIN32)

	if (size > (QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int)))
		size = QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int);

	if (!(nwio->status & STATUS_TMOUT_W_PRESET) && 
	    TMOUT_ENABLED(nwio->tmout.w) &&
	    wait_for_data (nwio, &nwio->tmout.w, 1) <= -1) return -1;

	count = send (nwio->handle, data, size, 0);
	if (count == SOCKET_ERROR) nwio->errnum = skerr_to_errnum (WSAGetLastError());
	return count;

#elif defined(__OS2__)

	if (size > (QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int)))
		size = QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int);

	if (!(nwio->status & STATUS_TMOUT_W_PRESET) && 
	    TMOUT_ENABLED(nwio->tmout.w) &&
	    wait_for_data (nwio, &nwio->tmout.w, 1) <= -1) return -1;

	n = send (nwio->handle, data, size, 0);
	if (n <= -1) nwio->errnum = skerr_to_errnum (sock_errno());
	return n;

#elif defined(USE_TLI)

	if (size > (QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int)))
		size = QSE_TYPE_MAX(qse_ssize_t) & QSE_TYPE_MAX(int);

rewrite:
	if (!(nwio->status & STATUS_TMOUT_W_PRESET) && 
	    TMOUT_ENABLED(nwio->tmout.w) &&
	    wait_for_data (nwio, &nwio->tmout.w, 1) <= -1) return -1;

	n = t_snd (nwio->handle, data, size, 0);
	if (n <= -1) 
	{
		if (errno == EINTR)
		{
			if (nwio->flags & QSE_NWIO_WRITENORETRY)
				nwio->errnum = QSE_NWIO_EINTR;
			else goto rewrite;
		}
		else
		{
			nwio->errnum = tlierr_to_errnum (t_errno, errno);
		}
	}
	return n;

#else

	if (size > QSE_TYPE_MAX(qse_ssize_t)) size = QSE_TYPE_MAX(qse_ssize_t);

rewrite:
	if (!(nwio->status & STATUS_TMOUT_W_PRESET) && 
	    TMOUT_ENABLED(nwio->tmout.w) &&
	    wait_for_data (nwio, &nwio->tmout.w, 1) <= -1) return -1;

	n = send (nwio->handle, data, size, 0);
	if (n <= -1) 
	{
		if (errno == EINTR)
		{
			if (nwio->flags & QSE_NWIO_WRITENORETRY)
				nwio->errnum = QSE_NWIO_EINTR;
			else goto rewrite;
		}
		else
		{
			nwio->errnum = skerr_to_errnum (errno);
		}
	}
	return n;

#endif
}

qse_ssize_t qse_nwio_write (qse_nwio_t* nwio, const void* data, qse_size_t size)
{
	if (nwio->tio == QSE_NULL)
	{
		return nwio_write(nwio, data, size);
	}
	else
	{
		qse_ssize_t n;

		nwio->errnum = QSE_NWIO_ENOERR;
		n = qse_tio_write(nwio->tio, data, size);
		if (n <= -1 && nwio->errnum == QSE_NWIO_ENOERR) 
			nwio->errnum = tio_errnum_to_nwio_errnum (nwio->tio);

		return n;
	}
}


qse_ssize_t qse_nwio_writebytes (qse_nwio_t* nwio, const void* data, qse_size_t size)
{
	if (nwio->tio == QSE_NULL)
		return nwio_write(nwio, data, size);
	else
		return qse_tio_writembs(nwio->tio, data, size);
}


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

static qse_ssize_t socket_input (
	qse_tio_t* tio, qse_tio_cmd_t cmd, void* buf, qse_size_t size)
{
	if (cmd == QSE_TIO_DATA) 
	{
		qse_nwio_t* nwio;

		nwio = *(qse_nwio_t**)QSE_XTN(tio);
		QSE_ASSERT (nwio != QSE_NULL);

		return nwio_read (nwio, buf, size);
	}

	return 0;
}

static qse_ssize_t socket_output (
	qse_tio_t* tio, qse_tio_cmd_t cmd, void* buf, qse_size_t size)
{
	if (cmd == QSE_TIO_DATA) 
	{
		qse_nwio_t* nwio;

		nwio = *(qse_nwio_t**)QSE_XTN(tio);
		QSE_ASSERT (nwio != QSE_NULL);

		return nwio_write (nwio, buf, size);
	}

	return 0;
}
