/* $Id: DrvTCP.cpp $ */
/** @file
 * TCP socket driver implementing the IStream interface.
 */

/*
 * Copyright (C) 2006-2015 Oracle Corporation.
 *
 * This file was contributed by <NAME> (derived from DrvNamedPipe)
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_TCP
#include <VBox/vmm/pdmdrv.h>
#include <iprt/assert.h>
#include <iprt/file.h>
#include <iprt/stream.h>
#include <iprt/alloc.h>
#include <iprt/string.h>
#include <iprt/semaphore.h>
#include <iprt/uuid.h>
#include <stdlib.h>

#include "VBoxDD.h"

#ifdef RT_OS_WINDOWS
# include <ws2tcpip.h>
#else /* !RT_OS_WINDOWS */
# include <errno.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <netdb.h>
# ifndef SHUT_RDWR /* OS/2 */
#  define SHUT_RDWR 3
# endif
#endif /* !RT_OS_WINDOWS */

#ifndef SHUT_RDWR
# ifdef SD_BOTH
#  define SHUT_RDWR SD_BOTH
# else
#  define SHUT_RDWR 2
# endif
#endif

/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
/** Converts a pointer to DRVTCP::IMedia to a PDRVTCP. */
#define PDMISTREAM_2_DRVTCP(pInterface) ( (PDRVTCP)((uintptr_t)pInterface - RT_OFFSETOF(DRVTCP, IStream)) )

/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * TCP driver instance data.
 *
 * @implements PDMISTREAM
 */
typedef struct DRVTCP
{
    /** The stream interface. */
    PDMISTREAM          IStream;
    /** Pointer to the driver instance. */
    PPDMDRVINS          pDrvIns;
    /** Pointer to the TCP server address:port or port only. (Freed by MM) */
    char                *pszLocation;
    /** Flag whether VirtualBox represents the server or client side. */
    bool                fIsServer;

    /** Socket handle of the TCP socket for server. */
    int                 TCPServer;
    /** Socket handle of the TCP socket connection. */
    int                 TCPConnection;

    /** Thread for listening for new connections. */
    RTTHREAD            ListenThread;
    /** Flag to signal listening thread to shut down. */
    bool volatile       fShutdown;
} DRVTCP, *PDRVTCP;


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/


/** @copydoc PDMISTREAM::pfnRead */
static DECLCALLBACK(int) drvTCPRead(PPDMISTREAM pInterface, void *pvBuf, size_t *pcbRead)
{
    int rc = VINF_SUCCESS;
    PDRVTCP pThis = PDMISTREAM_2_DRVTCP(pInterface);
    LogFlow(("%s: pvBuf=%p *pcbRead=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbRead, pThis->pszLocation));

    Assert(pvBuf);

    if (pThis->TCPConnection != -1)
    {
        ssize_t cbReallyRead;
        unsigned cbBuf = (unsigned)*pcbRead;
        cbReallyRead = recv(pThis->TCPConnection, (char *)pvBuf, cbBuf, 0);
        if (cbReallyRead == 0)
        {
            int tmp = pThis->TCPConnection;
            pThis->TCPConnection = -1;
#ifdef RT_OS_WINDOWS
            closesocket(tmp);
#else
            close(tmp);
#endif
        }
        else if (cbReallyRead == -1)
        {
            cbReallyRead = 0;
            rc = RTErrConvertFromErrno(errno);
        }
        *pcbRead = cbReallyRead;
    }
    else
    {
        RTThreadSleep(100);
        *pcbRead = 0;
    }

    LogFlow(("%s: *pcbRead=%zu returns %Rrc\n", __FUNCTION__, *pcbRead, rc));
    return rc;
}


/** @copydoc PDMISTREAM::pfnWrite */
static DECLCALLBACK(int) drvTCPWrite(PPDMISTREAM pInterface, const void *pvBuf, size_t *pcbWrite)
{
    int rc = VINF_SUCCESS;
    PDRVTCP pThis = PDMISTREAM_2_DRVTCP(pInterface);
    LogFlow(("%s: pvBuf=%p *pcbWrite=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbWrite, pThis->pszLocation));

    Assert(pvBuf);
    if (pThis->TCPConnection != -1)
    {
        ssize_t cbWritten;
        unsigned cbBuf = (unsigned)*pcbWrite;
        cbWritten = send(pThis->TCPConnection, (const char *)pvBuf, cbBuf, 0);
        if (cbWritten == 0)
        {
            int tmp = pThis->TCPConnection;
            pThis->TCPConnection = -1;
#ifdef RT_OS_WINDOWS
            closesocket(tmp);
#else
            close(tmp);
#endif
        }
        else if (cbWritten == -1)
        {
            cbWritten = 0;
            rc = RTErrConvertFromErrno(errno);
        }
        *pcbWrite = cbWritten;
    }

    LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
    return rc;
}


/**
 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
 */
static DECLCALLBACK(void *) drvTCPQueryInterface(PPDMIBASE pInterface, const char *pszIID)
{
    PPDMDRVINS      pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
    PDRVTCP         pThis   = PDMINS_2_DATA(pDrvIns, PDRVTCP);
    PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
    PDMIBASE_RETURN_INTERFACE(pszIID, PDMISTREAM, &pThis->IStream);
    return NULL;
}


/* -=-=-=-=- listen thread -=-=-=-=- */

/**
 * Receive thread loop.
 *
 * @returns 0 on success.
 * @param   ThreadSelf  Thread handle to this thread.
 * @param   pvUser      User argument.
 */
static DECLCALLBACK(int) drvTCPListenLoop(RTTHREAD ThreadSelf, void *pvUser)
{
    PDRVTCP pThis = (PDRVTCP)pvUser;
    int     rc = VINF_SUCCESS;

    while (RT_LIKELY(!pThis->fShutdown))
    {
        if (listen(pThis->TCPServer, 0) == -1)
        {
            rc = RTErrConvertFromErrno(errno);
            LogRel(("DrvTCP%d: listen failed, rc=%Rrc\n", pThis->pDrvIns->iInstance, rc));
            break;
        }
        int s = accept(pThis->TCPServer, NULL, NULL);
        if (s == -1)
        {
            rc = RTErrConvertFromErrno(errno);
            LogRel(("DrvTCP%d: accept failed, rc=%Rrc\n", pThis->pDrvIns->iInstance, rc));
            break;
        }
        if (pThis->TCPConnection != -1)
        {
            LogRel(("DrvTCP%d: only single connection supported\n", pThis->pDrvIns->iInstance));
#ifdef RT_OS_WINDOWS
            closesocket(s);
#else
            close(s);
#endif
        }
        else
            pThis->TCPConnection = s;
    }

    return VINF_SUCCESS;
}

/* -=-=-=-=- PDMDRVREG -=-=-=-=- */

/**
 * Common worker for drvTCPPowerOff and drvTCPDestructor.
 *
 * @param   pThis               The instance data.
 */
static void drvTCPShutdownListener(PDRVTCP pThis)
{
    /*
     * Signal shutdown of the listener thread.
     */
    pThis->fShutdown = true;
    if (    pThis->fIsServer
        &&  pThis->TCPServer != -1)
    {
        int rc = shutdown(pThis->TCPServer, SHUT_RDWR);
        AssertRC(rc == 0); NOREF(rc);

#ifdef RT_OS_WINDOWS
        rc = closesocket(pThis->TCPServer);
#else
        rc = close(pThis->TCPServer);
#endif
        AssertRC(rc == 0);
        pThis->TCPServer = -1;
    }
}


/**
 * Power off a TCP socket stream driver instance.
 *
 * This does most of the destruction work, to avoid ordering dependencies.
 *
 * @param   pDrvIns     The driver instance data.
 */
static DECLCALLBACK(void) drvTCPPowerOff(PPDMDRVINS pDrvIns)
{
    PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
    LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));

    drvTCPShutdownListener(pThis);
}


/**
 * Destruct a TCP socket stream driver instance.
 *
 * Most VM resources are freed by the VM. This callback is provided so that
 * any non-VM resources can be freed correctly.
 *
 * @param   pDrvIns     The driver instance data.
 */
static DECLCALLBACK(void) drvTCPDestruct(PPDMDRVINS pDrvIns)
{
    PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
    LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
    PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);

    drvTCPShutdownListener(pThis);

    /*
     * While the thread exits, clean up as much as we can.
     */

    Assert(pThis->TCPServer == -1);
    if (pThis->TCPConnection != -1)
    {
        int rc = shutdown(pThis->TCPConnection, SHUT_RDWR);
        AssertRC(rc == 0); NOREF(rc);

#ifdef RT_OS_WINDOWS
        rc = closesocket(pThis->TCPConnection);
#else
        rc = close(pThis->TCPConnection);
#endif
        Assert(rc == 0);
        pThis->TCPConnection = -1;
    }
    if (   pThis->fIsServer
        && pThis->pszLocation)
        RTFileDelete(pThis->pszLocation);


    MMR3HeapFree(pThis->pszLocation);
    pThis->pszLocation = NULL;

    /*
     * Wait for the thread.
     */
    if (pThis->ListenThread != NIL_RTTHREAD)
    {
        int rc = RTThreadWait(pThis->ListenThread, 30000, NULL);
        if (RT_SUCCESS(rc))
            pThis->ListenThread = NIL_RTTHREAD;
        else
            LogRel(("DrvTCP%d: listen thread did not terminate (%Rrc)\n", pDrvIns->iInstance, rc));
    }

}


/**
 * Construct a TCP socket stream driver instance.
 *
 * @copydoc FNPDMDRVCONSTRUCT
 */
static DECLCALLBACK(int) drvTCPConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
{
    PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
    PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);

    /*
     * Init the static parts.
     */
    pThis->pDrvIns                      = pDrvIns;
    pThis->pszLocation                  = NULL;
    pThis->fIsServer                    = false;

    pThis->TCPServer                    = -1;
    pThis->TCPConnection                = -1;

    pThis->ListenThread                 = NIL_RTTHREAD;
    pThis->fShutdown                    = false;
    /* IBase */
    pDrvIns->IBase.pfnQueryInterface    = drvTCPQueryInterface;
    /* IStream */
    pThis->IStream.pfnRead              = drvTCPRead;
    pThis->IStream.pfnWrite             = drvTCPWrite;

    /*
     * Validate and read the configuration.
     */
    PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Location|IsServer", "");

    int rc = CFGMR3QueryStringAlloc(pCfg, "Location", &pThis->pszLocation);
    if (RT_FAILURE(rc))
        return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
                                   N_("Configuration error: querying \"Location\" resulted in %Rrc"), rc);
    rc = CFGMR3QueryBool(pCfg, "IsServer", &pThis->fIsServer);
    if (RT_FAILURE(rc))
        return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
                                   N_("Configuration error: querying \"IsServer\" resulted in %Rrc"), rc);

    /*
     * Create/Open the socket.
     */
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s == -1)
        return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
                                   N_("DrvTCP#%d failed to create socket"), pDrvIns->iInstance);

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

    if (pThis->fIsServer)
    {
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(/*PORT*/ atoi(pThis->pszLocation));

        /* Bind address to the telnet socket. */
        pThis->TCPServer = s;
        RTFileDelete(pThis->pszLocation);
        if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
            return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
                                       N_("DrvTCP#%d failed to bind to socket %s"),
                                       pDrvIns->iInstance, pThis->pszLocation);
        rc = RTThreadCreate(&pThis->ListenThread, drvTCPListenLoop, (void *)pThis, 0,
                            RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DrvTCPStream");
        if (RT_FAILURE(rc))
            return PDMDrvHlpVMSetError(pDrvIns, rc,  RT_SRC_POS,
                                       N_("DrvTCP#%d failed to create listening thread"), pDrvIns->iInstance);
    }
    else
    {
        char *token;
        const char *delim = ":";
        struct hostent *server;
        token = strtok(pThis->pszLocation, delim);
        if(token) {
            server = gethostbyname(token);
            memmove((char *)&addr.sin_addr.s_addr,
                    (char *)server->h_addr,
                     server->h_length);
        }
        token = strtok(NULL, delim);
        if(token) {
            addr.sin_port = htons(/*PORT*/ atoi(token));
        }

        /* Connect to the telnet socket. */
        pThis->TCPConnection = s;
        if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
            return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
                                       N_("DrvTCP#%d failed to connect to socket %s"),
                                       pDrvIns->iInstance, pThis->pszLocation);
    }

    LogRel(("DrvTCP: %s, %s\n", pThis->pszLocation, pThis->fIsServer ? "server" : "client"));
    return VINF_SUCCESS;
}


/**
 * TCP stream driver registration record.
 */
const PDMDRVREG g_DrvTCP =
{
    /* u32Version */
    PDM_DRVREG_VERSION,
    /* szName */
    "TCP",
    /* szRCMod */
    "",
    /* szR0Mod */
    "",
    /* pszDescription */
    "TCP serial stream driver.",
    /* fFlags */
    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
    /* fClass. */
    PDM_DRVREG_CLASS_STREAM,
    /* cMaxInstances */
    ~0U,
    /* cbInstance */
    sizeof(DRVTCP),
    /* pfnConstruct */
    drvTCPConstruct,
    /* pfnDestruct */
    drvTCPDestruct,
    /* pfnRelocate */
    NULL,
    /* pfnIOCtl */
    NULL,
    /* pfnPowerOn */
    NULL,
    /* pfnReset */
    NULL,
    /* pfnSuspend */
    NULL,
    /* pfnResume */
    NULL,
    /* pfnAttach */
    NULL,
    /* pfnDetach */
    NULL,
    /* pfnPowerOff */
    drvTCPPowerOff,
    /* pfnSoftReset */
    NULL,
    /* u32EndVersion */
    PDM_DRVREG_VERSION
};

