/**
 * Reads a string from the socket.
 *
 * @returns VBox status code.
 *
 * @param   pState      The teleporter state structure.
 * @param   pszBuf      The output buffer.
 * @param   cchBuf      The size of the output buffer.
 *
 */
static int teleporterTcpReadLine(TeleporterState *pState, char *pszBuf, size_t cchBuf)
{
    char       *pszStart = pszBuf;
    RTSOCKET    Sock     = pState->mhSocket;

    AssertReturn(cchBuf > 1, VERR_INTERNAL_ERROR);
    *pszBuf = '\0';

    /* dead simple approach. */
    for (;;)
    {
        char ch;
        int rc = RTTcpRead(Sock, &ch, sizeof(ch), NULL);
        if (RT_FAILURE(rc))
        {
            LogRel(("Teleporter: RTTcpRead -> %Rrc while reading string ('%s')\n", rc, pszStart));
            return rc;
        }
        if (    ch == '\n'
                ||  ch == '\0')
            return VINF_SUCCESS;
        if (cchBuf <= 1)
        {
            LogRel(("Teleporter: String buffer overflow: '%s'\n", pszStart));
            return VERR_BUFFER_OVERFLOW;
        }
        *pszBuf++ = ch;
        *pszBuf = '\0';
        cchBuf--;
    }
}
/**
 * @interface_method_impl{TXSTRANSPORT,pfnRecvPkt}
 */
static DECLCALLBACK(int) txsTcpRecvPkt(PPTXSPKTHDR ppPktHdr)
{
    int rc = VINF_SUCCESS;
    *ppPktHdr = NULL;

    /*
     * Do we have to wait for a client to connect?
     */
    RTSOCKET hTcpClient = g_hTcpClient;
    if (hTcpClient == NIL_RTSOCKET)
    {
        rc = txsTcpConnect();
        if (RT_FAILURE(rc))
            return rc;
        hTcpClient = g_hTcpClient; Assert(hTcpClient != NIL_RTSOCKET);
    }

    /*
     * Read state.
     */
    size_t      offData       = 0;
    size_t      cbData        = 0;
    size_t      cbDataAlloced;
    uint8_t    *pbData        = NULL;

    /*
     * Any stashed data?
     */
    if (g_cbTcpStashedAlloced)
    {
        offData               = g_cbTcpStashed;
        cbDataAlloced         = g_cbTcpStashedAlloced;
        pbData                = g_pbTcpStashed;

        g_cbTcpStashed        = 0;
        g_cbTcpStashedAlloced = 0;
        g_pbTcpStashed        = NULL;
    }
    else
    {
        cbDataAlloced = RT_ALIGN_Z(64,  TXSPKT_ALIGNMENT);
        pbData = (uint8_t *)RTMemAlloc(cbDataAlloced);
        if (!pbData)
            return VERR_NO_MEMORY;
    }

    /*
     * Read and valid the length.
     */
    while (offData < sizeof(uint32_t))
    {
        size_t cbRead;
        rc = RTTcpRead(hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead);
        if (RT_FAILURE(rc))
            break;
        if (cbRead == 0)
        {
            Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc));
            rc = VERR_NET_NOT_CONNECTED;
            break;
        }
        offData += cbRead;
    }
    if (RT_SUCCESS(rc))
    {
        ASMCompilerBarrier(); /* paranoia^3 */
        cbData = *(uint32_t volatile *)pbData;
        if (cbData >= sizeof(TXSPKTHDR) && cbData <= TXSPKT_MAX_SIZE)
        {
            /*
             * Align the length and reallocate the return packet it necessary.
             */
            cbData = RT_ALIGN_Z(cbData, TXSPKT_ALIGNMENT);
            if (cbData > cbDataAlloced)
            {
                void *pvNew = RTMemRealloc(pbData, cbData);
                if (pvNew)
                {
                    pbData = (uint8_t *)pvNew;
                    cbDataAlloced = cbData;
                }
                else
                    rc = VERR_NO_MEMORY;
            }
            if (RT_SUCCESS(rc))
            {
                /*
                 * Read the remainder of the data.
                 */
                while (offData < cbData)
                {
                    size_t cbRead;
                    rc = RTTcpRead(hTcpClient, pbData + offData, cbData - offData, &cbRead);
                    if (RT_FAILURE(rc))
                        break;
                    if (cbRead == 0)
                    {
                        Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc));
                        rc = VERR_NET_NOT_CONNECTED;
                        break;
                    }
                    offData += cbRead;
                }
            }
        }
        else
            rc = VERR_NET_PROTOCOL_ERROR;
    }
    if (RT_SUCCESS(rc))
        *ppPktHdr = (PTXSPKTHDR)pbData;
    else
    {
        /*
         * Deal with errors.
         */
        if (rc == VERR_INTERRUPTED)
        {
            /* stash it away for the next call. */
            g_cbTcpStashed        = cbData;
            g_cbTcpStashedAlloced = cbDataAlloced;
            g_pbTcpStashed        = pbData;
        }
        else
        {
            RTMemFree(pbData);

            /* assume fatal connection error. */
            Log(("txsTcpRecvPkt: RTTcpRead -> %Rrc -> txsTcpDisconnectClient(%RTsock)\n", rc, g_hTcpClient));
            txsTcpDisconnectClient();
        }
    }

    return rc;
}
/**
 * Do the teleporter.
 *
 * @returns VBox status code.
 * @param   pState              The teleporter state.
 */
HRESULT
Console::teleporterSrc(TeleporterStateSrc *pState)
{
    AutoCaller autoCaller(this);
    if (FAILED(autoCaller.rc())) return autoCaller.rc();

    /*
     * Wait for Console::Teleport to change the state.
     */
    {
        AutoWriteLock autoLock(this COMMA_LOCKVAL_SRC_POS);
    }

    BOOL fCanceled = TRUE;
    HRESULT hrc = pState->mptrProgress->COMGETTER(Canceled)(&fCanceled);
    if (FAILED(hrc))
        return hrc;
    if (fCanceled)
        return setError(E_FAIL, tr("canceled"));

    /*
     * Try connect to the destination machine, disable Nagle.
     * (Note. The caller cleans up mhSocket, so we can return without worries.)
     */
    int vrc = RTTcpClientConnect(pState->mstrHostname.c_str(), pState->muPort, &pState->mhSocket);
    if (RT_FAILURE(vrc))
        return setError(E_FAIL, tr("Failed to connect to port %u on '%s': %Rrc"),
                        pState->muPort, pState->mstrHostname.c_str(), vrc);
    vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/);
    AssertRC(vrc);

    /* Read and check the welcome message. */
    char szLine[RT_MAX(128, sizeof(g_szWelcome))];
    RT_ZERO(szLine);
    vrc = RTTcpRead(pState->mhSocket, szLine, sizeof(g_szWelcome) - 1, NULL);
    if (RT_FAILURE(vrc))
        return setError(E_FAIL, tr("Failed to read welcome message: %Rrc"), vrc);
    if (strcmp(szLine, g_szWelcome))
        return setError(E_FAIL, tr("Unexpected welcome %.*Rhxs"), sizeof(g_szWelcome) - 1, szLine);

    /* password */
    pState->mstrPassword.append('\n');
    vrc = RTTcpWrite(pState->mhSocket, pState->mstrPassword.c_str(), pState->mstrPassword.length());
    if (RT_FAILURE(vrc))
        return setError(E_FAIL, tr("Failed to send password: %Rrc"), vrc);

    /* ACK */
    hrc = teleporterSrcReadACK(pState, "password", tr("Invalid password"));
    if (FAILED(hrc))
        return hrc;

    /*
     * Start loading the state.
     *
     * Note! The saved state includes vital configuration data which will be
     *       verified against the VM config on the other end.  This is all done
     *       in the first pass, so we should fail pretty promptly on misconfig.
     */
    hrc = teleporterSrcSubmitCommand(pState, "load");
    if (FAILED(hrc))
        return hrc;

    RTSocketRetain(pState->mhSocket);
    void *pvUser = static_cast<void *>(static_cast<TeleporterState *>(pState));
    vrc = VMR3Teleport(VMR3GetVM(pState->mpUVM),
                       pState->mcMsMaxDowntime,
                       &g_teleporterTcpOps,         pvUser,
                       teleporterProgressCallback,  pvUser,
                       &pState->mfSuspendedByUs);
    RTSocketRelease(pState->mhSocket);
    if (RT_FAILURE(vrc))
    {
        if (   vrc == VERR_SSM_CANCELLED
                && RT_SUCCESS(RTTcpSelectOne(pState->mhSocket, 1)))
        {
            hrc = teleporterSrcReadACK(pState, "load-complete");
            if (FAILED(hrc))
                return hrc;
        }
        return setError(E_FAIL, tr("VMR3Teleport -> %Rrc"), vrc);
    }

    hrc = teleporterSrcReadACK(pState, "load-complete");
    if (FAILED(hrc))
        return hrc;

    /*
     * We're at the point of no return.
     */
    if (!pState->mptrProgress->notifyPointOfNoReturn())
    {
        teleporterSrcSubmitCommand(pState, "cancel", false /*fWaitForAck*/);
        return E_FAIL;
    }

    /*
     * Hand over any media which we might be sharing.
     *
     * Note! This is only important on localhost teleportations.
     */
    /** @todo Maybe we should only do this if it's a local teleportation... */
    hrc = mControl->UnlockMedia();
    if (FAILED(hrc))
        return hrc;
    pState->mfUnlockedMedia = true;

    hrc = teleporterSrcSubmitCommand(pState, "lock-media");
    if (FAILED(hrc))
        return hrc;

    /*
     * The FINAL step is giving the target instructions how to proceed with the VM.
     */
    if (    vrc == VINF_SSM_LIVE_SUSPENDED
            ||  pState->menmOldMachineState == MachineState_Paused)
        hrc = teleporterSrcSubmitCommand(pState, "hand-over-paused");
    else
        hrc = teleporterSrcSubmitCommand(pState, "hand-over-resume");
    if (FAILED(hrc))
        return hrc;

    /*
     * teleporterSrcThreadWrapper will do the automatic power off because it
     * has to release the AutoVMCaller.
     */
    return S_OK;
}
/**
 * @copydoc SSMSTRMOPS::pfnRead
 */
static DECLCALLBACK(int) teleporterTcpOpRead(void *pvUser, uint64_t offStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
{
    TeleporterState *pState = (TeleporterState *)pvUser;
    AssertReturn(!pState->mfIsSource, VERR_INVALID_HANDLE);

    for (;;)
    {
        int rc;

        /*
         * Check for various conditions and may have been signalled.
         */
        if (pState->mfEndOfStream)
            return VERR_EOF;
        if (pState->mfStopReading)
            return VERR_EOF;
        if (pState->mfIOError)
            return VERR_IO_GEN_FAILURE;

        /*
         * If there is no more data in the current block, read the next
         * block header.
         */
        if (!pState->mcbReadBlock)
        {
            rc = teleporterTcpReadSelect(pState);
            if (RT_FAILURE(rc))
                return rc;
            TELEPORTERTCPHDR Hdr;
            rc = RTTcpRead(pState->mhSocket, &Hdr, sizeof(Hdr), NULL);
            if (RT_FAILURE(rc))
            {
                pState->mfIOError = true;
                LogRel(("Teleporter/TCP: Header read error: %Rrc\n", rc));
                return rc;
            }

            if (RT_UNLIKELY(   Hdr.u32Magic != TELEPORTERTCPHDR_MAGIC
                               || Hdr.cb > TELEPORTERTCPHDR_MAX_SIZE
                               || Hdr.cb == 0))
            {
                if (    Hdr.u32Magic == TELEPORTERTCPHDR_MAGIC
                        &&  (   Hdr.cb == 0
                                || Hdr.cb == UINT32_MAX)
                   )
                {
                    pState->mfEndOfStream = true;
                    pState->mcbReadBlock  = 0;
                    return Hdr.cb ? VERR_SSM_CANCELLED : VERR_EOF;
                }
                pState->mfIOError = true;
                LogRel(("Teleporter/TCP: Invalid block: u32Magic=%#x cb=%#x\n", Hdr.u32Magic, Hdr.cb));
                return VERR_IO_GEN_FAILURE;
            }

            pState->mcbReadBlock = Hdr.cb;
            if (pState->mfStopReading)
                return VERR_EOF;
        }

        /*
         * Read more data.
         */
        rc = teleporterTcpReadSelect(pState);
        if (RT_FAILURE(rc))
            return rc;
        uint32_t cb = (uint32_t)RT_MIN(pState->mcbReadBlock, cbToRead);
        rc = RTTcpRead(pState->mhSocket, pvBuf, cb, pcbRead);
        if (RT_FAILURE(rc))
        {
            pState->mfIOError = true;
            LogRel(("Teleporter/TCP: Data read error: %Rrc (cb=%#x)\n", rc, cb));
            return rc;
        }
        if (pcbRead)
        {
            cb = (uint32_t)*pcbRead;
            pState->moffStream   += cb;
            pState->mcbReadBlock -= cb;
            return VINF_SUCCESS;
        }
        pState->moffStream   += cb;
        pState->mcbReadBlock -= cb;
        if (cbToRead == cb)
            return VINF_SUCCESS;

        /* Advance to the next block. */
        cbToRead -= cb;
        pvBuf = (uint8_t *)pvBuf + cb;
    }
}
Console::teleporterTrgServeConnection(RTSOCKET Sock, void *pvUser)
{
    TeleporterStateTrg *pState = (TeleporterStateTrg *)pvUser;
    pState->mhSocket = Sock;

    /*
     * Disable Nagle and say hello.
     */
    int vrc = RTTcpSetSendCoalescing(pState->mhSocket, false /*fEnable*/);
    AssertRC(vrc);
    vrc = RTTcpWrite(Sock, g_szWelcome, sizeof(g_szWelcome) - 1);
    if (RT_FAILURE(vrc))
    {
        LogRel(("Teleporter: Failed to write welcome message: %Rrc\n", vrc));
        return VINF_SUCCESS;
    }

    /*
     * Password (includes '\n', see teleporterTrg).
     */
    const char *pszPassword = pState->mstrPassword.c_str();
    unsigned    off = 0;
    while (pszPassword[off])
    {
        char ch;
        vrc = RTTcpRead(Sock, &ch, sizeof(ch), NULL);
        if (    RT_FAILURE(vrc)
                ||  pszPassword[off] != ch)
        {
            if (RT_FAILURE(vrc))
                LogRel(("Teleporter: Password read failure (off=%u): %Rrc\n", off, vrc));
            else
                LogRel(("Teleporter: Invalid password (off=%u)\n", off));
            teleporterTcpWriteNACK(pState, VERR_AUTHENTICATION_FAILURE);
            return VINF_SUCCESS;
        }
        off++;
    }
    vrc = teleporterTcpWriteACK(pState);
    if (RT_FAILURE(vrc))
        return VINF_SUCCESS;

    /*
     * Update the progress bar, with peer name if available.
     */
    HRESULT     hrc;
    RTNETADDR   Addr;
    vrc = RTTcpGetPeerAddress(Sock, &Addr);
    if (RT_SUCCESS(vrc))
    {
        LogRel(("Teleporter: Incoming VM from %RTnaddr!\n", &Addr));
        hrc = pState->mptrProgress->SetNextOperation(BstrFmt(tr("Teleporting VM from %RTnaddr"), &Addr).raw(), 8);
    }
    else
    {
        LogRel(("Teleporter: Incoming VM!\n"));
        hrc = pState->mptrProgress->SetNextOperation(Bstr(tr("Teleporting VM")).raw(), 8);
    }
    AssertMsg(SUCCEEDED(hrc) || hrc == E_FAIL, ("%Rhrc\n", hrc));

    /*
     * Stop the server and cancel the timeout timer.
     *
     * Note! After this point we must return VERR_TCP_SERVER_STOP, while prior
     *       to it we must not return that value!
     */
    RTTcpServerShutdown(pState->mhServer);
    RTTimerLRDestroy(*pState->mphTimerLR);
    *pState->mphTimerLR = NIL_RTTIMERLR;

    /*
     * Command processing loop.
     */
    bool fDone = false;
    for (;;)
    {
        char szCmd[128];
        vrc = teleporterTcpReadLine(pState, szCmd, sizeof(szCmd));
        if (RT_FAILURE(vrc))
            break;

        if (!strcmp(szCmd, "load"))
        {
            vrc = teleporterTcpWriteACK(pState);
            if (RT_FAILURE(vrc))
                break;

            int vrc2 = VMR3AtErrorRegisterU(pState->mpUVM,
                                            Console::genericVMSetErrorCallback, &pState->mErrorText);
            AssertRC(vrc2);
            RTSocketRetain(pState->mhSocket); /* For concurrent access by I/O thread and EMT. */
            pState->moffStream = 0;

            void *pvUser2 = static_cast<void *>(static_cast<TeleporterState *>(pState));
            vrc = VMR3LoadFromStream(VMR3GetVM(pState->mpUVM),
                                     &g_teleporterTcpOps, pvUser2,
                                     teleporterProgressCallback, pvUser2);

            RTSocketRelease(pState->mhSocket);
            vrc2 = VMR3AtErrorDeregister(VMR3GetVM(pState->mpUVM), Console::genericVMSetErrorCallback, &pState->mErrorText);
            AssertRC(vrc2);

            if (RT_FAILURE(vrc))
            {
                LogRel(("Teleporter: VMR3LoadFromStream -> %Rrc\n", vrc));
                teleporterTcpWriteNACK(pState, vrc, pState->mErrorText.c_str());
                break;
            }

            /* The EOS might not have been read, make sure it is. */
            pState->mfStopReading = false;
            size_t cbRead;
            vrc = teleporterTcpOpRead(pvUser2, pState->moffStream, szCmd, 1, &cbRead);
            if (vrc != VERR_EOF)
            {
                LogRel(("Teleporter: Draining teleporterTcpOpRead -> %Rrc\n", vrc));
                teleporterTcpWriteNACK(pState, vrc);
                break;
            }

            vrc = teleporterTcpWriteACK(pState);
        }
        else if (!strcmp(szCmd, "cancel"))
        {
            /* Don't ACK this. */
            LogRel(("Teleporter: Received cancel command.\n"));
            vrc = VERR_SSM_CANCELLED;
        }
        else if (!strcmp(szCmd, "lock-media"))
        {
            hrc = pState->mpControl->LockMedia();
            if (SUCCEEDED(hrc))
            {
                pState->mfLockedMedia = true;
                vrc = teleporterTcpWriteACK(pState);
            }
            else
            {
                vrc = VERR_FILE_LOCK_FAILED;
                teleporterTcpWriteNACK(pState, vrc);
            }
        }
        else if (   !strcmp(szCmd, "hand-over-resume")
                    || !strcmp(szCmd, "hand-over-paused"))
        {
            /*
             * Point of no return.
             *
             * Note! Since we cannot tell whether a VMR3Resume failure is
             *       destructive for the source or not, we have little choice
             *       but to ACK it first and take any failures locally.
             *
             *       Ideally, we should try resume it first and then ACK (or
             *       NACK) the request since this would reduce latency and
             *       make it possible to recover from some VMR3Resume failures.
             */
            if (   pState->mptrProgress->notifyPointOfNoReturn()
                    && pState->mfLockedMedia)
            {
                vrc = teleporterTcpWriteACK(pState);
                if (RT_SUCCESS(vrc))
                {
                    if (!strcmp(szCmd, "hand-over-resume"))
                        vrc = VMR3Resume(VMR3GetVM(pState->mpUVM));
                    else
                        pState->mptrConsole->setMachineState(MachineState_Paused);
                    fDone = true;
                    break;
                }
            }
            else
            {
                vrc = pState->mfLockedMedia ? VERR_WRONG_ORDER : VERR_SSM_CANCELLED;
                teleporterTcpWriteNACK(pState, vrc);
            }
        }
        else
        {
            LogRel(("Teleporter: Unknown command '%s' (%.*Rhxs)\n", szCmd, strlen(szCmd), szCmd));
            vrc = VERR_NOT_IMPLEMENTED;
            teleporterTcpWriteNACK(pState, vrc);
        }

        if (RT_FAILURE(vrc))
            break;
    }

    if (RT_SUCCESS(vrc) && !fDone)
        vrc = VERR_WRONG_ORDER;
    if (RT_FAILURE(vrc))
        teleporterTrgUnlockMedia(pState);

    pState->mRc = vrc;
    pState->mhSocket = NIL_RTSOCKET;
    LogFlowFunc(("returns mRc=%Rrc\n", vrc));
    return VERR_TCP_SERVER_STOP;
}