/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** 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.
*/
#include "sockets.h"
#include "vl.h"
#include <fcntl.h>
#include "android_debug.h"

/* QSOCKET_CALL is used to deal with the fact that EINTR happens pretty
 * easily in QEMU since we use SIGALRM to implement periodic timers
 */
#ifdef _WIN32
#  define  QSOCKET_CALL(_ret,_cmd)   \
    do { _ret = (_cmd); } while ( _ret < 0 && WSAGetLastError() == WSAEINTR )
#else
#  define  QSOCKET_CALL(_ret,_cmd)   \
    do { _ret = (_cmd); } while ( _ret < 0 && errno == EINTR )
#endif

#ifdef _WIN32
const char*  socket_strerr(void)
{
	int   err = WSAGetLastError();
	switch (err) {
		case WSA_INVALID_HANDLE:
			return "invalid handle";
		case WSA_NOT_ENOUGH_MEMORY:
			return "not enough memory";
		case WSA_INVALID_PARAMETER:
			return "invalid parameter";
		case WSA_OPERATION_ABORTED:
			return "operation aborted";
		case WSA_IO_INCOMPLETE:
			return "incomplete i/o";
		case WSA_IO_PENDING:
			return "pending i/o";
		case WSAEINTR:
			return "interrupted";
		case WSAEBADF:
			return "bad file descriptor";
		case WSAEACCES:
			return "permission denied";
		case WSAEFAULT:
			return "bad address";
		case WSAEINVAL:
			return "invalid argument";
		case WSAEMFILE:
			return "too many opened files";
		case WSAEWOULDBLOCK:
			return "resource temporarily unavailable";
		case WSAEINPROGRESS:
			return "operation in progress";
		case WSAEALREADY:
			return "operation already in progress";
		case WSAENOTSOCK:
			return "socket operation not on socket";
		case WSAEDESTADDRREQ:
			return "destination address required";
		case WSAEMSGSIZE:
			return "message too long";
		case WSAEPROTOTYPE:
			return "wrong protocol for socket type";
		case WSAENOPROTOOPT:
			return "bad option for protocol";
		case WSAEPROTONOSUPPORT:
			return "protocol not supported";
	    case WSAEADDRINUSE:
			return "address already in use";
	    case WSAEADDRNOTAVAIL:
			return "address not available";
	    case WSAENETDOWN:
			return "network is down";
	    case WSAENETUNREACH:
			return "network unreachable";
	    case WSAENETRESET:
			return "network dropped connection on reset";
	    case WSAECONNABORTED:
			return "connection aborted";
	    case WSAECONNRESET:
			return "connection reset by peer";
	    case WSAENOBUFS:
			return "no buffer space available";
	    case WSAETIMEDOUT:
			return "connection timed out";
	    case WSAECONNREFUSED:
			return "connection refused";
	    case WSAEHOSTDOWN:
			return "host is down";
	    case WSAEHOSTUNREACH:
			return "no route to host";
		default:
			return "unknown/TODO";
	}
}
#endif

int socket_get_type(int fd)
{
    int  opt    = -1;
    int  optlen = sizeof(opt);
    getsockopt(fd, SOL_SOCKET, SO_TYPE, (void*)&opt, (void*)&optlen );
    return opt;
}

int socket_set_nonblock(int fd)
{
#ifdef _WIN32
    unsigned long opt = 1;
    return ioctlsocket(fd, FIONBIO, &opt);
#else
    return fcntl(fd, F_SETFL, O_NONBLOCK);
#endif
}

int socket_set_blocking(int fd)
{
#ifdef _WIN32
    unsigned long opt = 0;
    return ioctlsocket(fd, FIONBIO, &opt);
#else
    return fcntl(fd, F_SETFL, O_NONBLOCK);
#endif
}


int socket_set_xreuseaddr(int  fd)
{
#ifdef _WIN32
   /* on Windows, SO_REUSEADDR is used to indicate that several programs can
    * bind to the same port. this is completely different from the Unix
    * semantics. instead of SO_EXCLUSIVEADDR to ensure that explicitely prevent
    * this.
    */
    BOOL  flag = 1;
    return setsockopt( fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (const char*)&flag, sizeof(flag) );
#else
    int  flag = 1;
    return setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&flag, sizeof(flag) );
#endif
}


int socket_set_oobinline(int  fd)
{
#ifdef _WIN32
    BOOL   flag = 1;
#else
    int    flag = 1;
#endif
    /* enable low-latency */
    return setsockopt( fd, SOL_SOCKET, SO_OOBINLINE, (const char*)&flag, sizeof(flag) );
}


int  socket_set_lowlatency(int  fd)
{
#ifdef _WIN32
    BOOL   flag = 1;
#else
    int    flag = 1;
#endif
    /* enable low-latency */
    return setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&flag, sizeof(flag) );
}


#ifdef _WIN32
#include <stdlib.h>

static void socket_cleanup(void)
{
    WSACleanup();
}

int socket_init(void)
{
    WSADATA Data;
    int ret, err;

    ret = WSAStartup(MAKEWORD(2,2), &Data);
    if (ret != 0) {
        err = WSAGetLastError();
        return -1;
    }
    atexit(socket_cleanup);
    return 0;
}

#else /* !_WIN32 */

int socket_init(void)
{
   return 0;   /* nothing to do on Unix */
}

#endif /* !_WIN32 */

#ifdef _WIN32

static void
socket_close_handler( void*  _fd )
{
    int   fd = (int)_fd;
    int   ret;
    char  buff[64];

    /* we want to drain the read side of the socket before closing it */
    do {
        ret = recv( fd, buff, sizeof(buff), 0 );
    } while (ret < 0 && socket_errno == EINTR);

    if (ret < 0 && socket_errno == EWOULDBLOCK)
        return;

    qemu_set_fd_handler( fd, NULL, NULL, NULL );
    closesocket( fd );
}

void
socket_close( int  fd )
{
    shutdown( fd, SD_BOTH );
    /* we want to drain the socket before closing it */
    qemu_set_fd_handler( fd, socket_close_handler, NULL, (void*)fd );
}

#else /* !_WIN32 */

#include <unistd.h>

void
socket_close( int  fd )
{
    shutdown( fd, SHUT_RDWR );
    close( fd );
}

#endif /* !_WIN32 */


static int
socket_bind_server( int  s, const struct sockaddr*  addr, socklen_t  addrlen, int  type )
{
    int   ret;

    socket_set_xreuseaddr(s);

    QSOCKET_CALL(ret, bind(s, addr, addrlen));
    if ( ret < 0 ) {
        dprint("could not bind server socket: %s", socket_errstr());
        socket_close(s);
        return -1;
    }

    if (type == SOCK_STREAM) {
        QSOCKET_CALL( ret, listen(s, 4) );
        if ( ret < 0 ) {
            dprint("could not listen server socket: %s", socket_errstr());
            socket_close(s);
            return -1;
        }
    }
    return  s;
}


static int
socket_connect_client( int  s, const struct sockaddr*  addr, socklen_t  addrlen )
{
    int  ret;

    QSOCKET_CALL(ret, connect(s, addr, addrlen));
    if ( ret < 0 ) {
        dprint( "could not connect client socket: %s\n", socket_errstr() );
        socket_close(s);
        return -1;
    }

    socket_set_nonblock( s );
    return s;
}


static int
socket_in_server( int  address, int  port, int  type )
{
    struct sockaddr_in  addr;
    int                 s;

    memset( &addr, 0, sizeof(addr) );
    addr.sin_family      = AF_INET;
    addr.sin_port        = htons(port);
    addr.sin_addr.s_addr = htonl(address);

    s = socket(PF_INET, type, 0);
    if (s < 0) return -1;

    return socket_bind_server( s, (struct sockaddr*) &addr, sizeof(addr), type );
}


static int
socket_in_client( struct sockaddr_in*  addr, int  type )
{
    int  s;

    s = socket(addr->sin_family, type, 0);
    if (s < 0) return -1;

    return socket_connect_client( s, (struct sockaddr*) addr, sizeof(*addr) );
}


int
socket_loopback_server( int  port, int  type )
{
    return socket_in_server( INADDR_LOOPBACK, port, type );
}

int
socket_loopback_client( int  port, int  type )
{
    struct sockaddr_in  addr;
    memset( &addr, 0, sizeof(addr) );
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(port);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    return socket_in_client( &addr, type );
}


int
socket_network_client( const char*  host, int  port, int  type )
{
    struct hostent*     hp;
    struct sockaddr_in  addr;

    hp = gethostbyname(host);
    if (hp == 0) return -1;

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = hp->h_addrtype;
    addr.sin_port   = htons(port);
    memcpy( &addr.sin_addr, hp->h_addr, hp->h_length );

    return socket_in_client( &addr, type );
}


int
socket_anyaddr_server( int  port, int  type )
{
    return socket_in_server( INADDR_ANY, port, type );
}

int
socket_accept_any( int  server_fd )
{
    int  fd;

    QSOCKET_CALL(fd, accept( server_fd, NULL, 0 ));
    if (fd < 0) {
        dprint( "could not accept client connection from fd %d: %s",
                server_fd, socket_errstr() );
        return -1;
    }

    /* set to non-blocking */
    socket_set_nonblock( fd );
    return fd;
}


#ifndef _WIN32

#include <sys/un.h>

static int
socket_unix_prepare_address( struct sockaddr_un*  addr, const char*  name )
{
    size_t  namelen = strlen(name);
    size_t  offset  = offsetof(struct sockaddr_un, sun_path);

    if (offset + namelen + 1 > sizeof(*addr)) {
        fprintf(stderr, "unix socket path too long\n");
        return -1;
    }
    memset( addr, 0, sizeof(*addr) );
    addr->sun_family = AF_LOCAL;
    memcpy( addr->sun_path, name, namelen+1 );
    return offset + namelen + 1;
}

int
socket_unix_server( const char*  name, int  type )
{
    struct sockaddr_un  addr;
    int                 addrlen;
    int                 s, ret;

    do {
        s = socket(AF_LOCAL, type, 0);
    } while (s < 0 && socket_errno == EINTR);
    if (s < 0) return -1;

    addrlen = socket_unix_prepare_address( &addr, name );
    if (addrlen < 0) {
        socket_close(s);
        return -1;
    }

    do {
        ret = unlink( addr.sun_path );
    } while (ret < 0 && errno == EINTR);

    return socket_bind_server( s, (struct sockaddr*) &addr, (socklen_t)addrlen, type );
}

int
socket_unix_client( const char*  name, int  type )
{
    struct sockaddr_un  addr;
    int                 addrlen;
    int                 s;

    do {
        s = socket(AF_LOCAL, type, 0);
    } while (s < 0 && socket_errno == EINTR);
    if (s < 0) return -1;

    addrlen = socket_unix_prepare_address( &addr, name );
    if (addrlen < 0) {
        socket_close(s);
        return -1;
    }

    return socket_connect_client( s, (struct sockaddr*) &addr, (socklen_t)addrlen );
}
#endif



int
socket_pair(int *fd1, int *fd2)
{
#ifndef _WIN32
    int   fds[2];
    int   ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);

    if (!ret) {
        socket_set_nonblock(fds[0]);
        socket_set_nonblock(fds[1]);
        *fd1 = fds[0];
        *fd2 = fds[1];
    }
    return ret;
#else /* _WIN32 */
    /* on Windows, select() only works with network sockets, which
     * means we absolutely cannot use Win32 PIPEs to implement
     * socket pairs with the current event loop implementation.
     * We're going to do like Cygwin: create a random pair
     * of localhost TCP sockets and connect them together
     */
    int                 s0, s1, s2, port;
    struct sockaddr_in  sockin;
    socklen_t           len;

    /* first, create the 'server' socket.
     * a port number of 0 means 'any port between 1024 and 5000.
     * see Winsock bind() documentation for details */
    s0 = socket_loopback_server( 0, SOCK_STREAM );
    if (s0 < 0)
        return -1;

    /* now connect a client socket to it, we first need to
     * extract the server socket's port number */
    len = sizeof sockin;
    if (getsockname(s0, (struct sockaddr*) &sockin, &len) < 0) {
        closesocket (s0);
        return -1;
    }

    port = ntohs(sockin.sin_port);
    s2   = socket_loopback_client( port, SOCK_STREAM );
    if (s2 < 0) {
        closesocket(s0);
        return -1;
    }

    /* we need to accept the connection on the server socket
     * this will create the second socket for the pair
     */
    len = sizeof sockin;
    s1  = accept(s0, (struct sockaddr*) &sockin, &len);
    if (s1 == INVALID_SOCKET) {
        closesocket (s0);
        closesocket (s2);
        return -1;
    }
    socket_set_nonblock(s1);

    /* close server socket */
    closesocket(s0);
    *fd1 = s1;
    *fd2 = s2;
    return 0;
#endif /* _WIN32 */
}
