/**
 * @interface_method_impl{TXSTRANSPORT,pfnPollIn}
 */
static DECLCALLBACK(bool) txsTcpPollIn(void)
{
    RTSOCKET hTcpClient = g_hTcpClient;
    if (hTcpClient == NIL_RTSOCKET)
        return false;
    int rc = RTTcpSelectOne(hTcpClient, 0/*cMillies*/);
    return RT_SUCCESS(rc);
}
/**
 * Selects and poll for close condition.
 *
 * We can use a relatively high poll timeout here since it's only used to get
 * us out of error paths.  In the normal cause of events, we'll get a
 * end-of-stream header.
 *
 * @returns VBox status code.
 *
 * @param   pState          The teleporter state data.
 */
static int teleporterTcpReadSelect(TeleporterState *pState)
{
    int rc;
    do
    {
        rc = RTTcpSelectOne(pState->mhSocket, 1000);
        if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
        {
            pState->mfIOError = true;
            LogRel(("Teleporter/TCP: Header select error: %Rrc\n", rc));
            break;
        }
        if (pState->mfStopReading)
        {
            rc = VERR_EOF;
            break;
        }
    } while (rc == VERR_TIMEOUT);
    return rc;
}
/**
 * @copydoc SSMSTRMOPS::pfnIsOk
 */
static DECLCALLBACK(int) teleporterTcpOpIsOk(void *pvUser)
{
    TeleporterState *pState = (TeleporterState *)pvUser;

    if (pState->mfIsSource)
    {
        /* Poll for incoming NACKs and errors from the other side */
        int rc = RTTcpSelectOne(pState->mhSocket, 0);
        if (rc != VERR_TIMEOUT)
        {
            if (RT_SUCCESS(rc))
            {
                LogRel(("Teleporter/TCP: Incoming data detect by IsOk, assuming it is a cancellation NACK.\n"));
                rc = VERR_SSM_CANCELLED;
            }
            else
                LogRel(("Teleporter/TCP: RTTcpSelectOne -> %Rrc (IsOk).\n", rc));
            return rc;
        }
    }

    return VINF_SUCCESS;
}
/**
 * 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;
}