#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#if defined(HAVE_UNIX_SOCKET)
#if defined(HAVE_STDDEF_H)
#include <stddef.h>
#endif
#include <sys/un.h>
#endif
#if defined(HAVE_POLL)
#include <poll.h>
#else /* !HAVE_POLL */
#include <sys/time.h>
#endif /* !HAVE_POLL */
#if defined(WIN32)
#include <winsock.h>
#endif /* WIN32 */

#ifdef HAVE_SIGNAL
#include <signal.h>
#endif

#include <iiimp.h>

#include "stream.h"

/* function declarations */

static IIIMF_status
stream_socket_read(
    IIIMF_stream_private private,
    void *buf,
    size_t nbyte
);

static IIIMF_status
stream_socket_write(
    IIIMF_stream_private private,
    const void* buf,
    size_t nbyte
);

/* enum and structure */

enum IIIMF_STREAM_SOCKET_FLAGS {
	IIIMF_STREAM_SOCKET_LISTEN,
	IIIMF_STREAM_SOCKET_OPEN
};

typedef struct IIIMF_stream_socket_private IIIMF_stream_socket_private;
struct IIIMF_stream_socket_private {
    int flags;
    int fd;
    int timeout;
};

static IIIMF_stream_socket_private*
create_sockpriv(
    int flags,
    int fd,
    int timeout
)
{
    IIIMF_stream_socket_private *p;

#ifdef HAVE_SO_NOSIGPIPE
    int optval = 1;
#endif
    p = (IIIMF_stream_socket_private*) malloc(sizeof(*p));
    if (!p) return NULL;
    p->timeout = timeout;
    p->flags = flags;
    p->fd = fd;
#ifdef HAVE_SO_NOSIGPIPE
    /* need error handling ? */
    setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof (int));
#endif
    return p;
}

static void
delete_sockpriv(
    IIIMF_stream_socket_private *psockpriv
)
{
    free(psockpriv);
}

#if defined(HAVE_UNIX_SOCKET)
static IIIMF_status
create_socket_stream_unix(
    const char *node,
    const char *service,
    int timeout,
    IIIMF_stream ** stream_ret
)
{
    int fd;
    int fd_flag;
    int r;
    size_t size;
    struct sockaddr_un sun_addr;

    fd = -1;

    fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1) {
	return IIIMF_STATUS_STREAM;
    }

    sun_addr.sun_family = AF_UNIX;
    if ((NULL == service) || ('\0' == (*service))) {
	strncpy(sun_addr.sun_path, node, sizeof(sun_addr.sun_path));
    } else {
	snprintf(sun_addr.sun_path, sizeof(sun_addr.sun_path), "%s/%s",
		 node, service);
    }

    size = (offsetof(struct sockaddr_un, sun_path)
             + strlen (sun_addr.sun_path) + 1);

    r = connect(fd, (struct sockaddr *)(&sun_addr), size);

    if (r < 0) {
	(void)close(fd);
	return IIIMF_STATUS_STREAM;
    }

    fd_flag = fcntl(fd, F_GETFD);
    fd_flag |= FD_CLOEXEC;
    (void)fcntl(fd, F_SETFD, fd_flag);
    {
	IIIMF_status st;
	IIIMF_stream *stream;
	IIIMF_stream_socket_private *sockpriv;

	sockpriv = create_sockpriv(IIIMF_STREAM_SOCKET_OPEN, fd,
				   timeout);

	if (!sockpriv) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}

	st = iiimf_create_stream(stream_socket_read, stream_socket_write,
				 sockpriv, timeout, &stream);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	*stream_ret = stream;
    }

    return IIIMF_STATUS_SUCCESS;

}
#endif

IIIMF_status
iiimf_connect_socket_stream(
    const char *	node,
    const char *	service,
    int                 timeout,
    IIIMF_stream ** stream_ret
)
{
    int			fd;
    int			fd_flag;

#if defined(HAVE_GETADDRINFO)
    int			e;
    struct addrinfo *	res;
    struct addrinfo *	aip;
    struct addrinfo	hints;

#if defined(HAVE_UNIX_SOCKET)
    if (*node == '/') {
	return create_socket_stream_unix(node, service, timeout, stream_ret);
    }
#endif
    fd = -1;

    (void)memset(&hints, 0, sizeof (hints));
    hints.ai_flags = AI_CANONNAME;
    hints.ai_socktype = SOCK_STREAM;

    e = getaddrinfo(node, service, &hints, &res);
    if (0 != e) {
	return -1;
    }

    for (aip = res; NULL != aip; aip = aip->ai_next) {
	fd = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
	if (-1 == fd) {
	    break;
	}

	if (-1 == connect(fd, aip->ai_addr, aip->ai_addrlen)) {
	    (void)close(fd);
	    fd = -1;
	    continue;
	} else {
	    break;
	}
    }

    freeaddrinfo(res);

    if (-1 == fd) return IIIMF_STATUS_STREAM;

#else /* !HAVE_GETADDRINFO */

#if defined(WIN32)
    unsigned short int	port_number;
#else /* !WIN32 */
    int			port_number;
#endif /* !WIN32 */
    int			r;
    int			optval;
    unsigned long	in_addr;
    struct hostent *	hostp;
    struct sockaddr_in	addr;
    struct servent *	srvp;

#if defined(HAVE_UNIX_SOCKET)
    if (*node == '/') {
	return create_socket_stream_unix(node, service, timeout, stream_ret);
    }
#endif
#if defined(WIN32)
    start_winsock(1, 1);
#endif

    fd = socket(PF_INET, SOCK_STREAM, 0);
    if (fd < 0 ) return IIIMF_STATUS_STREAM;

    port_number = 0;

    (void)memset((char *)(&addr), 0, sizeof (addr));
    addr.sin_family = PF_INET;

    if (0 < strlen(service)) {
	srvp = getservbyname(service, "tcp");
	if (NULL != srvp) {
	    port_number = srvp->s_port;
	}
	(void)endservent();
	if (0 == port_number) {
	    port_number = atoi(service);
	}
    }
    if (0 == port_number) {
	port_number = 9010;
    }
    addr.sin_port = htons(port_number);
    in_addr = inet_addr(node);
    if ((unsigned long)(-1) != in_addr) {
	(void)memcpy(&addr.sin_addr, &in_addr, sizeof (in_addr));
    } else {
	hostp = gethostbyname(node);
	if (NULL == hostp) {
#if defined(WIN32)
	    (void)sock_close(fd);
#else /* !WIN32 */
	    (void)close(fd);
#endif /* !WIN32 */
	    return IIIMF_STATUS_STREAM;
	}

	if (0 < hostp->h_length) {
	    (void)memcpy(&addr.sin_addr, hostp->h_addr,
			 hostp->h_length);
	} else {
#if defined(WIN32)
	    sock_close(fd);
#else /* !WIN32 */
	    (void)close(fd);
#endif /* !WIN32 */
	    return IIIMF_STATUS_STREAM;
	}
    }

    r = connect(fd, (struct sockaddr *)(&addr), sizeof (addr));
#if defined(WIN32)
    if (SOCKET_ERROR == r) {
	sock_close(fd);
	return IIIMF_STATUS_STREAM;
    }
#else /* !WIN32 */
    if (r < 0) {
	(void)close(fd);
	return IIIMF_STATUS_STREAM;
    }
#endif /* !WIN32 */
#endif /* !HAVE_GETADDRINFO */

    fd_flag = fcntl(fd, F_GETFD);
    fd_flag |= FD_CLOEXEC;
    (void)fcntl(fd, F_SETFD, fd_flag);

    {
	IIIMF_status st;
	IIIMF_stream *stream;
	IIIMF_stream_socket_private *sockpriv;

	sockpriv = create_sockpriv(IIIMF_STREAM_SOCKET_OPEN, fd,
				   timeout);
	if (!sockpriv) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}

	st = iiimf_create_stream(stream_socket_read, stream_socket_write,
				 sockpriv, timeout, &stream);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	*stream_ret = stream;
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_listen_socket_stream(
    const char *	node,
    const char *	service,
    int                 timeout,
    IIIMF_stream ** stream_ret
)
{
    int			fd;
    int			fd_flag;
    int			optval;
    int			r = 0;

#if defined(HAVE_GETADDRINFO)
    int			e;
    struct addrinfo *	a;
    struct addrinfo *	aip;
    struct addrinfo	hints;

    fd = -1;

    (void)memset(&hints, 0, sizeof (hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_socktype = SOCK_STREAM;

    e = getaddrinfo(NULL, service, &hints, &aip);
    if (0 != e) return IIIMF_STATUS_STREAM;

    for (a = aip; NULL != a; a = a->ai_next) {
	fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
	if (-1 != fd) {
	    r = bind(fd, aip->ai_addr, sizeof (struct sockaddr));
	    if (-1 != r) break;
	    (void)close(fd);
	    fd = -1;
	}
    }
    freeaddrinfo(aip);
    if ((-1 == fd) || (-1 == r)) return IIIMF_STATUS_STREAM;
    r = listen(fd, 5);
    if (-1 == r) return IIIMF_STATUS_STREAM;
#else /* !HAVE_GETADDRINFO */

#if defined(WIN32)
    unsigned short int	port_number;
#else /* !WIN32 */
    int			port_number;
#endif /* !WIN32 */
#if defined(PF_INET6) && defined(ENABLE_IPV6)
    struct sockaddr_in6	addr;
#else /* !PF_INET6 && ENABLE_IPV6 */
    struct sockaddr_in	addr;
#endif /* !PF_INET6 && ENABLE_IPV6 */
    struct servent *	srvp;

#if defined(WIN32)
    start_winsock(1, 1);
#endif

#if defined(PF_INET6) && defined(ENABLE_IPV6)
    fd = socket(PF_INET6, SOCK_STREAM, 0);
#else /* !PF_INET6 && ENABLE_IPV6 */
    fd = socket(PF_INET, SOCK_STREAM, 0);
#endif /* !PF_INET6 && ENABLE_IPV6 */

    if (fd < 0 ) return IIIMF_STATUS_STREAM;

    port_number = 0;

    if (0 < strlen(service)) {
	srvp = getservbyname(service, "tcp");
	if (NULL != srvp) {
	    port_number = srvp->s_port;
	}
	(void)endservent();
	if (0 == port_number) {
	    port_number = atoi(service);
	}
    }
    if (0 == port_number) {
	port_number = 9010;
    }

    (void)memset((char *)(&addr), 0, sizeof (addr));
#if defined(PF_INET6) && defined(ENABLE_IPV6)
    addr.sin6_family = PF_INET6;
    addr.sin6_flowinfo = 0;
    addr.sin6_port = htons(port_number);
    addr.sin6_addr = in6addr_any;
#else /* !PF_INET6 && ENABLE_IPV6 */
    addr.sin_family = PF_INET;
    addr.sin_port = htons(port_number);
#endif /* !PF_INET6 && ENABLE_IPV6 */

    if ((bind(fd, (struct sockaddr *)(&addr), sizeof (addr)) < 0) ||
	(listen(fd, 5) < 0) ) {
#if defined(WIN32)
	sock_close(fd);
#else /* !WIN32 */
	(void)close(fd);
#endif /* !WIN32 */
	return IIIMF_STATUS_STREAM;
    }
#endif /* !HAVE_GETADDRINFO */

    optval = 1;
    r = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (int));
    if (-1 == r) {
#if defined(WIN32)
	sock_close(fd);
#else /* !WIN32 */
	(void)close(fd);
#endif /* !WIN32 */
	return IIIMF_STATUS_STREAM;
    }

    fd_flag = fcntl(fd, F_GETFD);
    fd_flag |= FD_CLOEXEC;
    (void)fcntl(fd, F_SETFD, fd_flag);

    {
	IIIMF_status st;
	IIIMF_stream *stream;
	IIIMF_stream_socket_private *sockpriv;

	sockpriv = create_sockpriv(IIIMF_STREAM_SOCKET_LISTEN, fd, timeout);
	if (!sockpriv) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}

	st = iiimf_create_stream(NULL, NULL, sockpriv, timeout, &stream);
	if (st != IIIMF_STATUS_SUCCESS) return st;
	*stream_ret = stream;
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_accept_socket_stream(
    IIIMF_stream *  stream,
    IIIMF_stream ** stream_ret
)
{
    IIIMF_stream_socket_private *sockpriv = (IIIMF_stream_socket_private*) stream->private_data;
    IIIMF_status	status;
    struct sockaddr	address;
    socklen_t		address_len;
    int			fd;
#if defined(HAVE_POLL)
    struct pollfd	fds[1];
    int			poll_ret;
#else /* !HAVE_POLL */
    fd_set		fdvar;
    struct timeval	timeout;
    int			select_ret;

    timeout.tv_sec = sockpriv->timeout / 1000;
    timeout.tv_usec = sockpriv->timeout % 1000;
#endif /* !HAVE_POLL */

    if (sockpriv->flags != IIIMF_STREAM_SOCKET_LISTEN)
	return IIIMF_STATUS_ARGUMENT;

    if (0 <= sockpriv->timeout) {
#if defined(HAVE_POLL)
	fds[0].fd = sockpriv->fd;
	fds[0].events = POLLIN;
	poll_ret = poll(fds, 1, sockpriv->timeout);
	if (0 == poll_ret) {
	    return IIIMF_STATUS_TIMEOUT;
	} else if (-1 == poll_ret) {
	    return IIIMF_STATUS_STREAM;
	}
#else /* !HAVE_POLL */
	do {
	    FD_ZERO(&fdvar);
	    FD_SET(sockpriv->fd, &fdvar);
	    select_ret = select(sockpriv->fd + 1, NULL,
				&fdvar, NULL, &timeout);
	} while ((-1 == select_ret) && (EINTR == errno));
	if (-1 == select_ret) return IIIMF_STATUS_TIMEOUT;
#endif /* !HAVE_POLL */
    }
    address_len = (sizeof (address));
    fd = accept(sockpriv->fd, &address, &address_len);
    if (-1 == fd) return IIIMF_STATUS_STREAM;

    {
	IIIMF_stream *stream_new;
	IIIMF_stream_socket_private *sockpriv_new;

	sockpriv_new = create_sockpriv(IIIMF_STREAM_SOCKET_OPEN, fd,
				       sockpriv->timeout);
	if (!sockpriv_new) {
	    close(fd);
	    return IIIMF_STATUS_MALLOC;
	}
	status = iiimf_create_stream(stream_socket_read, stream_socket_write,
				     sockpriv_new, sockpriv->timeout, &stream_new);
	if (status != IIIMF_STATUS_SUCCESS) return status;
	*stream_ret = stream_new;
    }

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_delete_socket_stream(
    IIIMF_stream *  stream
)
{
    IIIMF_stream_socket_private *sockpriv = (IIIMF_stream_socket_private*) stream->private_data;
    
    if (sockpriv->fd > 0) close(sockpriv->fd);
    delete_sockpriv(sockpriv);
    iiimf_stream_delete(stream);
    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
stream_socket_read(
    IIIMF_stream_private        private,
    void *			buf,
    size_t			nbyte)
{
    IIIMF_stream_socket_private *sockpriv = (IIIMF_stream_socket_private*) private;
    char *		p;
    ssize_t		n;
    ssize_t		r;

#if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
    RETSIGTYPE (*old_sighandler)(int);
#endif

#if defined(HAVE_POLL)
    struct pollfd	fds[1];
    int			poll_ret;
#else /* !HAVE_POLL */
    fd_set		fdvar;
    struct timeval	timeout;
    int			select_ret;

    timeout.tv_sec = sockpriv->timeout / 1000;
    timeout.tv_usec = sockpriv->timeout % 1000;
#endif /* !HAVE_POLL */

#if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
    /* block SIGPIPE */
    old_sighandler = signal(SIGPIPE, SIG_IGN);
#endif

    if (sockpriv->fd < 0) return IIIMF_STATUS_STREAM_RECEIVE;
    for (p = buf, n = nbyte; 0 < n; p += r, n -= r) {
	if (0 <= sockpriv->timeout) {
#if defined(HAVE_POLL)
	    fds[0].fd = sockpriv->fd;
	    fds[0].events = POLLIN;
	    poll_ret = poll(fds, 1, sockpriv->timeout);
	    if (0 == poll_ret) {
		return IIIMF_STATUS_TIMEOUT;
	    } else if (-1 == poll_ret) {
		return IIIMF_STATUS_STREAM;
	    }
#else /* !HAVE_POLL */
	    do {
		FD_ZERO(&fdvar);
		FD_SET(sockpriv->fd, &fdvar);
		select_ret = select(sockpriv->fd + 1, NULL,
				    &fdvar, NULL, &timeout);
	    } while ((-1 == select_ret) && (EINTR == errno));
#endif /* !HAVE_POLL */
	}

#ifdef HAVE_MSG_NOSIGNAL
        r = recv(sockpriv->fd, p, n, MSG_NOSIGNAL);
#else
	r = recv(sockpriv->fd, p, n, 0);
#endif
	if (r == 0) {
	    return IIIMF_STATUS_CONNECTION_CLOSED;
	}
	if (r < 0) {
	    if (EINTR == errno) {
		continue;
	    } else {
#if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
                /* restore old handler */
                signal(SIGPIPE, old_sighandler);
#endif
                if (EPIPE == errno) {
                    sockpriv->fd = -1;
                    return IIIMF_STATUS_CONNECTION_CLOSED;
                }
		return IIIMF_STATUS_STREAM_RECEIVE;
	    }
	}
    }

#if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
    /* restore old handler */
    signal(SIGPIPE, old_sighandler);
#endif

    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
stream_socket_write(
    IIIMF_stream_private        private,
    const void *		buf,
    size_t			nbyte)
{
    IIIMF_stream_socket_private *sockpriv = (IIIMF_stream_socket_private*) private;
    const char *	p;
    ssize_t		n;
    ssize_t		r;

#if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
    RETSIGTYPE (*old_sighandler)(int);
    /* block SIGPIPE */
    old_sighandler = signal(SIGPIPE, SIG_IGN);
#endif

    if (sockpriv->fd < 0) return IIIMF_STATUS_STREAM_SEND;

    for (p = buf, n = nbyte; 0 < n; p += r, n -= r) {
        #ifdef HAVE_MSG_NOSIGNAL
        r = send(sockpriv->fd, p, n, MSG_NOSIGNAL);
        #else
        r = send(sockpriv->fd, p, n, 0);
        #endif
	if (r < 0) {
            #if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
            /* restore old handler */
            signal(SIGPIPE, old_sighandler);
            #endif
            if (EPIPE == errno) {
                sockpriv->fd = -1;
                return IIIMF_STATUS_CONNECTION_CLOSED;
            }
            return IIIMF_STATUS_STREAM_SEND;
        }
    }

#if !defined(HAVE_SO_NOSIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SIGNAL)
    /* restore old handler */
    signal(SIGPIPE, old_sighandler);
#endif

    return IIIMF_STATUS_SUCCESS;
}

#if defined(WIN32)
static int
start_winsock(int major, int minor)
{
    WORD	wVersionRequested;
    WSADATA	wsaData;
    int	r;

    wVersionRequested = MAKEWORD(major, minor);

    r = WSAStartup(wVersionRequested, &wsaData);

    if (0 != r) return -1;

#ifdef CHK_WSOCK_VERSION
    if ((1 != LOBYTE(wnsaData.wVersion)) ||
	(1 != HIBYTE(wsaData.wVersion))) {
	/* wrong version */
	return 1;
    }
#endif /* CHK_WSOCK_VERSION */

	return 0;
}


static int
end_winsock()
{
    return WSACleanup();
}


static int
sock_close(int fd)
{
    int r;
    if (INVALID_SOCKET != fd) {
	r = closesocket(fd);
	end_winsock();
	fd = INVALID_SOCKET;
	return r;
    }
    return 0;
}
#endif /* WIN32 */


/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
