/* $Id: VBoxGuestControlSvc.cpp $ */
/** @file
 * Guest Control Service: Controlling the guest.
 */

/*
 * Copyright (C) 2011-2019 Oracle Corporation
 *
 * 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.
 */

/** @page pg_svc_guest_control   Guest Control HGCM Service
 *
 * This service acts as a proxy for handling and buffering host message requests
 * and clients on the guest. It tries to be as transparent as possible to let
 * the guest (client) and host side do their protocol handling as desired.
 *
 * The following terms are used:
 * - Host:   A host process (e.g. VBoxManage or another tool utilizing the Main API)
 *           which wants to control something on the guest.
 * - Client: A client (e.g. VBoxService) running inside the guest OS waiting for
 *           new host messages to perform. There can be multiple clients connected
 *           to this service. A client is represented by its unique HGCM client ID.
 * - Context ID: An (almost) unique ID automatically generated on the host (Main API)
 *               to not only distinguish clients but individual requests. Because
 *               the host does not know anything about connected clients it needs
 *               an indicator which it can refer to later. This context ID gets
 *               internally bound by the service to a client which actually processes
 *               the message in order to have a relationship between client<->context ID(s).
 *
 * The host can trigger messages which get buffered by the service (with full HGCM
 * parameter info). As soon as a client connects (or is ready to do some new work)
 * it gets a buffered host message  to process it. This message then will be immediately
 * removed from the message list. If there are ready clients but no new messages to be
 * processed, these clients will be set into a deferred state (that is being blocked
 * to return until a new host message is available).
 *
 * If a client needs to inform the host that something happened, it can send a
 * message to a low level HGCM callback registered in Main. This callback contains
 * the actual data as well as the context ID to let the host do the next necessary
 * steps for this context. This context ID makes it possible to wait for an event
 * inside the host's Main API function (like starting a process on the guest and
 * wait for getting its PID returned by the client) as well as cancelling blocking
 * host calls in order the client terminated/crashed (HGCM detects disconnected
 * clients and reports it to this service's callback).
 *
 * Starting at VBox 4.2 the context ID itself consists of a session ID, an object
 * ID (for example a process or file ID) and a count. This is necessary to not break
 * compatibility between older hosts and to manage guest session on the host.
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#define LOG_GROUP LOG_GROUP_GUEST_CONTROL
#include <VBox/HostServices/GuestControlSvc.h>
#include <VBox/GuestHost/GuestControl.h> /** @todo r=bird: Why two headers??? */

#include <VBox/err.h>
#include <VBox/log.h>
#include <VBox/AssertGuest.h>
#include <VBox/VMMDev.h>
#include <VBox/vmm/ssm.h>
#include <iprt/assert.h>
#include <iprt/cpp/autores.h>
#include <iprt/cpp/utils.h>
#include <iprt/mem.h>
#include <iprt/list.h>
#include <iprt/req.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#include <iprt/time.h>

#include <map>
#include <new>      /* for std::nothrow*/


using namespace guestControl;


/**
 * Structure for maintaining a request.
 */
typedef struct ClientRequest
{
    /** The call handle */
    VBOXHGCMCALLHANDLE mHandle;
    /** Number of parameters */
    uint32_t mNumParms;
    /** The call parameters */
    VBOXHGCMSVCPARM *mParms;
    /** The default constructor. */
    ClientRequest(void)
        : mHandle(0), mNumParms(0), mParms(NULL)
    {}
} ClientRequest;

/**
 * Structure for holding a buffered host message which has
 * not been processed yet.
 */
typedef struct HostMsg
{
    /** Entry on the ClientState::m_HostMsgList list. */
    RTLISTNODE      m_ListEntry;
    union
    {
        /** The top two twomost bits are exploited for message destination.
         * See VBOX_GUESTCTRL_DST_XXX.  */
        uint64_t    m_idContextAndDst;
        /** The context ID this message belongs to (extracted from the first parameter). */
        uint32_t    m_idContext;
    };
    /** Dynamic structure for holding the HGCM parms */
    uint32_t mType;
    /** Number of HGCM parameters. */
    uint32_t mParmCount;
    /** Array of HGCM parameters. */
    PVBOXHGCMSVCPARM mpParms;
    /** Set if we detected the message skipping hack from r121400. */
    bool m_f60BetaHackInPlay;

    HostMsg()
        : m_idContextAndDst(0)
        , mType(UINT32_MAX)
        , mParmCount(0)
        , mpParms(NULL)
        , m_f60BetaHackInPlay(false)
    {
        RTListInit(&m_ListEntry);
    }

    /**
     * Releases the host message, properly deleting it if no further references.
     */
    void Delete(void)
    {
        LogFlowThisFunc(("[Msg %RU32 (%s)] destroying\n", mType, GstCtrlHostMsgtoStr((eHostMsg)mType)));
        if (mpParms)
        {
            for (uint32_t i = 0; i < mParmCount; i++)
                if (mpParms[i].type == VBOX_HGCM_SVC_PARM_PTR)
                {
                    RTMemFree(mpParms[i].u.pointer.addr);
                    mpParms[i].u.pointer.addr = NULL;
                }
            RTMemFree(mpParms);
            mpParms = NULL;
        }
        mParmCount = 0;
        delete this;
    }


    /**
     * Initializes the message.
     *
     * The specified parameters are copied and any buffers referenced by it
     * duplicated as well.
     *
     * @returns VBox status code.
     * @param   idMsg       The host message number, eHostMsg.
     * @param   cParms      Number of parameters in the HGCM request.
     * @param   paParms     Array of parameters.
     */
    int Init(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
    {
        LogFlowThisFunc(("[Msg %RU32 (%s)] Allocating cParms=%RU32, paParms=%p\n",
                         idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), cParms, paParms));
        Assert(mpParms == NULL);
        Assert(mParmCount == 0);
        Assert(RTListIsEmpty(&m_ListEntry));

        /*
         * Fend of bad stuff.
         */
        AssertReturn(cParms > 0, VERR_WRONG_PARAMETER_COUNT); /* At least one parameter (context ID) must be present. */
        AssertReturn(cParms < VMMDEV_MAX_HGCM_PARMS, VERR_WRONG_PARAMETER_COUNT);
        AssertPtrReturn(paParms, VERR_INVALID_POINTER);

        /*
         * The first parameter is the context ID and the message destination mask.
         */
        if (paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT)
        {
            m_idContextAndDst = paParms[0].u.uint64;
            AssertReturn(m_idContextAndDst & VBOX_GUESTCTRL_DST_BOTH, VERR_INTERNAL_ERROR_3);
        }
        else if (paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT)
        {
            AssertMsgFailed(("idMsg=%u %s - caller must set dst!\n", idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg)));
            m_idContextAndDst = paParms[0].u.uint32 | VBOX_GUESTCTRL_DST_BOTH;
        }
        else
            AssertFailedReturn(VERR_WRONG_PARAMETER_TYPE);

        /*
         * Just make a copy of the parameters and any buffers.
         */
        mType   = idMsg;
        mParmCount = cParms;
        mpParms    = (VBOXHGCMSVCPARM *)RTMemAllocZ(sizeof(VBOXHGCMSVCPARM) * mParmCount);
        AssertReturn(mpParms, VERR_NO_MEMORY);

        for (uint32_t i = 0; i < cParms; i++)
        {
            mpParms[i].type = paParms[i].type;
            switch (paParms[i].type)
            {
                case VBOX_HGCM_SVC_PARM_32BIT:
                    mpParms[i].u.uint32 = paParms[i].u.uint32;
                    break;

                case VBOX_HGCM_SVC_PARM_64BIT:
                    mpParms[i].u.uint64 = paParms[i].u.uint64;
                    break;

                case VBOX_HGCM_SVC_PARM_PTR:
                    mpParms[i].u.pointer.size = paParms[i].u.pointer.size;
                    if (mpParms[i].u.pointer.size > 0)
                    {
                        mpParms[i].u.pointer.addr = RTMemDup(paParms[i].u.pointer.addr, mpParms[i].u.pointer.size);
                        AssertReturn(mpParms[i].u.pointer.addr, VERR_NO_MEMORY);
                    }
                    /* else: structure is zeroed by allocator. */
                    break;

                default:
                    AssertMsgFailedReturn(("idMsg=%u (%s) parameter #%u: type=%u\n",
                                           idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), i, paParms[i].type),
                                          VERR_WRONG_PARAMETER_TYPE);
            }
        }

        /*
         * Morph the first parameter back to 32-bit.
         */
        mpParms[0].type     = VBOX_HGCM_SVC_PARM_32BIT;
        mpParms[0].u.uint32 = (uint32_t)paParms[0].u.uint64;

        return VINF_SUCCESS;
    }


    /**
     * Sets the GUEST_MSG_PEEK_WAIT GUEST_MSG_PEEK_NOWAIT return parameters.
     *
     * @param   paDstParms  The peek parameter vector.
     * @param   cDstParms   The number of peek parameters (at least two).
     * @remarks ASSUMES the parameters has been cleared by clientMsgPeek.
     */
    inline void setPeekReturn(PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms)
    {
        Assert(cDstParms >= 2);
        if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT)
            paDstParms[0].u.uint32 = mType;
        else
            paDstParms[0].u.uint64 = mType;
        paDstParms[1].u.uint32 = mParmCount;

        uint32_t i = RT_MIN(cDstParms, mParmCount + 2);
        while (i-- > 2)
            switch (mpParms[i - 2].type)
            {
                case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break;
                case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break;
                case VBOX_HGCM_SVC_PARM_PTR:   paDstParms[i].u.uint32 = mpParms[i - 2].u.pointer.size; break;
            }
    }


    /** @name Support for old-style (GUEST_MSG_WAIT) operation.
     * @{
     */

    /**
     * Worker for Assign() that opies data from the buffered HGCM request to the
     * current HGCM request.
     *
     * @returns VBox status code.
     * @param   paDstParms              Array of parameters of HGCM request to fill the data into.
     * @param   cDstParms               Number of parameters the HGCM request can handle.
     */
    int CopyTo(VBOXHGCMSVCPARM paDstParms[], uint32_t cDstParms) const
    {
        LogFlowThisFunc(("[Msg %RU32] mParmCount=%RU32, m_idContext=%RU32 (Session %RU32)\n",
                         mType, mParmCount, m_idContext, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(m_idContext)));

        int rc = VINF_SUCCESS;
        if (cDstParms != mParmCount)
        {
            LogFlowFunc(("Parameter count does not match (got %RU32, expected %RU32)\n",
                         cDstParms, mParmCount));
            rc = VERR_INVALID_PARAMETER;
        }

        if (RT_SUCCESS(rc))
        {
            for (uint32_t i = 0; i < mParmCount; i++)
            {
                if (paDstParms[i].type != mpParms[i].type)
                {
                    LogFunc(("Parameter %RU32 type mismatch (got %RU32, expected %RU32)\n", i, paDstParms[i].type, mpParms[i].type));
                    rc = VERR_INVALID_PARAMETER;
                }
                else
                {
                    switch (mpParms[i].type)
                    {
                        case VBOX_HGCM_SVC_PARM_32BIT:
#ifdef DEBUG_andy
                            LogFlowFunc(("\tmpParms[%RU32] = %RU32 (uint32_t)\n",
                                         i, mpParms[i].u.uint32));
#endif
                            paDstParms[i].u.uint32 = mpParms[i].u.uint32;
                            break;

                        case VBOX_HGCM_SVC_PARM_64BIT:
#ifdef DEBUG_andy
                            LogFlowFunc(("\tmpParms[%RU32] = %RU64 (uint64_t)\n",
                                         i, mpParms[i].u.uint64));
#endif
                            paDstParms[i].u.uint64 = mpParms[i].u.uint64;
                            break;

                        case VBOX_HGCM_SVC_PARM_PTR:
                        {
#ifdef DEBUG_andy
                            LogFlowFunc(("\tmpParms[%RU32] = %p (ptr), size = %RU32\n",
                                         i, mpParms[i].u.pointer.addr, mpParms[i].u.pointer.size));
#endif
                            if (!mpParms[i].u.pointer.size)
                                continue; /* Only copy buffer if there actually is something to copy. */

                            if (!paDstParms[i].u.pointer.addr)
                                rc = VERR_INVALID_PARAMETER;
                            else if (paDstParms[i].u.pointer.size < mpParms[i].u.pointer.size)
                                rc = VERR_BUFFER_OVERFLOW;
                            else
                                memcpy(paDstParms[i].u.pointer.addr,
                                       mpParms[i].u.pointer.addr,
                                       mpParms[i].u.pointer.size);
                            break;
                        }

                        default:
                            LogFunc(("Parameter %RU32 of type %RU32 is not supported yet\n", i, mpParms[i].type));
                            rc = VERR_NOT_SUPPORTED;
                            break;
                    }
                }

                if (RT_FAILURE(rc))
                {
                    LogFunc(("Parameter %RU32 invalid (%Rrc), refusing\n", i, rc));
                    break;
                }
            }
        }

        LogFlowFunc(("Returned with rc=%Rrc\n", rc));
        return rc;
    }

    int Assign(const ClientRequest *pReq)
    {
        AssertPtrReturn(pReq, VERR_INVALID_POINTER);

        int rc;

        LogFlowThisFunc(("[Msg %RU32] mParmCount=%RU32, mpParms=%p\n", mType, mParmCount, mpParms));

        /* Does the current host message need more parameter space which
         * the client does not provide yet? */
        if (mParmCount > pReq->mNumParms)
        {
            LogFlowThisFunc(("[Msg %RU32] Requires %RU32 parms, only got %RU32 from client\n",
                             mType, mParmCount, pReq->mNumParms));
            /*
             * So this call apparently failed because the guest wanted to peek
             * how much parameters it has to supply in order to successfully retrieve
             * this message. Let's tell him so!
             */
            rc = VERR_TOO_MUCH_DATA;
        }
        else
        {
            rc = CopyTo(pReq->mParms, pReq->mNumParms);

            /*
             * Has there been enough parameter space but the wrong parameter types
             * were submitted -- maybe the client was just asking for the next upcoming
             * host message?
             *
             * Note: To keep this compatible to older clients we return VERR_TOO_MUCH_DATA
             *       in every case.
             */
            if (RT_FAILURE(rc))
                rc = VERR_TOO_MUCH_DATA;
        }

        return rc;
    }

    int Peek(const ClientRequest *pReq)
    {
        AssertPtrReturn(pReq, VERR_INVALID_POINTER);

        LogFlowThisFunc(("[Msg %RU32] mParmCount=%RU32, mpParms=%p\n", mType, mParmCount, mpParms));

        if (pReq->mNumParms >= 2)
        {
            HGCMSvcSetU32(&pReq->mParms[0], mType);   /* Message ID */
            HGCMSvcSetU32(&pReq->mParms[1], mParmCount); /* Required parameters for message */
        }
        else
            LogFlowThisFunc(("Warning: Client has not (yet) submitted enough parameters (%RU32, must be at least 2) to at least peak for the next message\n",
                             pReq->mNumParms));

        /*
         * Always return VERR_TOO_MUCH_DATA data here to
         * keep it compatible with older clients and to
         * have correct accounting (mHostRc + mHostMsgTries).
         */
        return VERR_TOO_MUCH_DATA;
    }

    /** @} */
} HostMsg;

/**
 * Per-client structure used for book keeping/state tracking a
 * certain host message.
 */
typedef struct ClientContext
{
    /* Pointer to list node of this message. */
    HostMsg *mpHostMsg;
    /** The standard constructor. */
    ClientContext(void) : mpHostMsg(NULL) {}
    /** Internal constrcutor. */
    ClientContext(HostMsg *pHostMsg) : mpHostMsg(pHostMsg) {}
} ClientContext;
typedef std::map< uint32_t, ClientContext > ClientContextMap;

/**
 * Structure for holding a connected guest client state.
 */
typedef struct ClientState
{
    PVBOXHGCMSVCHELPERS     m_pSvcHelpers;
    /** Host message list to process (HostMsg). */
    RTLISTANCHOR            m_HostMsgList;
    /** The HGCM client ID. */
    uint32_t                m_idClient;
    /** The session ID for this client, UINT32_MAX if not set or master. */
    uint32_t                m_idSession;
    /** Set if master. */
    bool                    m_fIsMaster;
    /** Set if restored (needed for shutting legacy mode assert on non-masters). */
    bool                    m_fRestored;

    /** Set if we've got a pending wait cancel. */
    bool                    m_fPendingCancel;
    /** Pending client call (GUEST_MSG_PEEK_WAIT or GUEST_MSG_WAIT), zero if none pending.
     *
     * This means the client waits for a new host message to reply and won't return
     * from the waiting call until a new host message is available. */
    guestControl::eGuestMsg m_enmPendingMsg;
    /** Pending peek/wait request details. */
    ClientRequest           m_PendingReq;


    ClientState(void)
        : m_pSvcHelpers(NULL)
        , m_idClient(0)
        , m_idSession(UINT32_MAX)
        , m_fIsMaster(false)
        , m_fRestored(false)
        , m_fPendingCancel(false)
        , m_enmPendingMsg((guestControl::eGuestMsg)0)
        , mHostMsgRc(VINF_SUCCESS)
        , mHostMsgTries(0)
        , mPeekCount(0)
    {
        RTListInit(&m_HostMsgList);
    }

    ClientState(PVBOXHGCMSVCHELPERS pSvcHelpers, uint32_t idClient)
        : m_pSvcHelpers(pSvcHelpers)
        , m_idClient(idClient)
        , m_idSession(UINT32_MAX)
        , m_fIsMaster(false)
        , m_fRestored(false)
        , m_fPendingCancel(false)
        , m_enmPendingMsg((guestControl::eGuestMsg)0)
        , mHostMsgRc(VINF_SUCCESS)
        , mHostMsgTries(0)
        , mPeekCount(0)
    {
        RTListInit(&m_HostMsgList);
    }

    /**
     * Used by for Service::hostProcessMessage().
     */
    void EnqueueMessage(HostMsg *pHostMsg)
    {
        AssertPtr(pHostMsg);
        RTListAppend(&m_HostMsgList, &pHostMsg->m_ListEntry);
    }

    /**
     * Used by for Service::hostProcessMessage().
     *
     * @note This wakes up both GUEST_MSG_WAIT and GUEST_MSG_PEEK_WAIT sleepers.
     */
    int Wakeup(void)
    {
        int rc = VINF_NO_CHANGE;

        if (m_enmPendingMsg != 0)
        {
            LogFlowFunc(("[Client %RU32] Waking up ...\n", m_idClient));

            rc = VINF_SUCCESS;

            HostMsg *pFirstMsg = RTListGetFirstCpp(&m_HostMsgList, HostMsg, m_ListEntry);
            if (pFirstMsg)
            {
                LogFlowThisFunc(("[Client %RU32] Current host message is %RU32 (CID=%#RX32, cParms=%RU32)\n",
                                 m_idClient, pFirstMsg->mType, pFirstMsg->m_idContext, pFirstMsg->mParmCount));

                if (m_enmPendingMsg == GUEST_MSG_PEEK_WAIT)
                {
                    pFirstMsg->setPeekReturn(m_PendingReq.mParms, m_PendingReq.mNumParms);
                    rc = m_pSvcHelpers->pfnCallComplete(m_PendingReq.mHandle, VINF_SUCCESS);

                    m_PendingReq.mHandle   = NULL;
                    m_PendingReq.mParms    = NULL;
                    m_PendingReq.mNumParms = 0;
                    m_enmPendingMsg         = (guestControl::eGuestMsg)0;
                }
                else if (m_enmPendingMsg == GUEST_MSG_WAIT)
                    rc = OldRun(&m_PendingReq, pFirstMsg);
                else
                    AssertMsgFailed(("m_enmIsPending=%d\n", m_enmPendingMsg));
            }
            else
                AssertMsgFailed(("Waking up client ID=%RU32 with no host message in queue is a bad idea\n", m_idClient));

            return rc;
        }

        return VINF_NO_CHANGE;
    }

    /**
     * Used by Service::call() to handle GUEST_MSG_CANCEL.
     *
     * @note This cancels both GUEST_MSG_WAIT and GUEST_MSG_PEEK_WAIT sleepers.
     */
    int CancelWaiting()
    {
        LogFlowFunc(("[Client %RU32] Cancelling waiting thread, isPending=%d, pendingNumParms=%RU32, m_idSession=%x\n",
                     m_idClient, m_enmPendingMsg, m_PendingReq.mNumParms, m_idSession));

        /*
         * The PEEK call is simple: At least two parameters, all set to zero before sleeping.
         */
        int rcComplete;
        if (m_enmPendingMsg == GUEST_MSG_PEEK_WAIT)
        {
            HGCMSvcSetU32(&m_PendingReq.mParms[0], HOST_MSG_CANCEL_PENDING_WAITS);
            rcComplete = VINF_TRY_AGAIN;
        }
        /*
         * The GUEST_MSG_WAIT call is complicated, though we're generally here
         * to wake up someone who is peeking and have two parameters.  If there
         * aren't two parameters, fail the call.
         */
        else if (m_enmPendingMsg != 0)
        {
            Assert(m_enmPendingMsg == GUEST_MSG_WAIT);
            if (m_PendingReq.mNumParms > 0)
                HGCMSvcSetU32(&m_PendingReq.mParms[0], HOST_MSG_CANCEL_PENDING_WAITS);
            if (m_PendingReq.mNumParms > 1)
                HGCMSvcSetU32(&m_PendingReq.mParms[1], 0);
            rcComplete = m_PendingReq.mNumParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN;
        }
        /*
         * If nobody is waiting, flag the next wait call as cancelled.
         */
        else
        {
            m_fPendingCancel = true;
            return VINF_SUCCESS;
        }

        m_pSvcHelpers->pfnCallComplete(m_PendingReq.mHandle, rcComplete);

        m_PendingReq.mHandle   = NULL;
        m_PendingReq.mParms    = NULL;
        m_PendingReq.mNumParms = 0;
        m_enmPendingMsg            = (guestControl::eGuestMsg)0;
        m_fPendingCancel      = false;
        return VINF_SUCCESS;
    }


    /** @name The GUEST_MSG_WAIT state and helpers.
     *
     * @note Don't try understand this, it is certificable!
     *
     * @{
     */

    /** Last (most recent) rc after handling the host message. */
    int mHostMsgRc;
    /** How many GUEST_MSG_WAIT calls the client has issued to retrieve one message.
     *
     * This is used as a heuristic to remove a message that the client appears not
     * to be able to successfully retrieve.  */
    uint32_t mHostMsgTries;
    /** Number of times we've peeked at a pending message.
     *
     * This is necessary for being compatible with older Guest Additions.  In case
     * there are messages which only have two (2) parameters and therefore would fit
     * into the GUEST_MSG_WAIT reply immediately, we now can make sure that the
     * client first gets back the GUEST_MSG_WAIT results first.
     */
    uint32_t mPeekCount;

    /**
     * Ditches the first host message and crazy GUEST_MSG_WAIT state.
     *
     * @note Only used by GUEST_MSG_WAIT scenarios.
     */
    void OldDitchFirstHostMsg()
    {
        HostMsg *pFirstMsg = RTListGetFirstCpp(&m_HostMsgList, HostMsg, m_ListEntry);
        Assert(pFirstMsg);
        RTListNodeRemove(&pFirstMsg->m_ListEntry);
        pFirstMsg->Delete();

        /* Reset state else. */
        mHostMsgRc    = VINF_SUCCESS;
        mHostMsgTries = 0;
        mPeekCount    = 0;
    }

    /**
     * Used by Wakeup() and OldRunCurrent().
     *
     * @note Only used by GUEST_MSG_WAIT scenarios.
     */
    int OldRun(ClientRequest const *pReq, HostMsg *pHostMsg)
    {
        AssertPtrReturn(pReq, VERR_INVALID_POINTER);
        AssertPtrReturn(pHostMsg, VERR_INVALID_POINTER);
        Assert(RTListNodeIsFirst(&m_HostMsgList, &pHostMsg->m_ListEntry));

        LogFlowFunc(("[Client %RU32] pReq=%p, mHostMsgRc=%Rrc, mHostMsgTries=%RU32, mPeekCount=%RU32\n",
                      m_idClient, pReq, mHostMsgRc, mHostMsgTries, mPeekCount));

        int rc = mHostMsgRc = OldSendReply(pReq, pHostMsg);

        LogFlowThisFunc(("[Client %RU32] Processing host message %RU32 ended with rc=%Rrc\n",
                         m_idClient, pHostMsg->mType, mHostMsgRc));

        bool fRemove = false;
        if (RT_FAILURE(rc))
        {
            mHostMsgTries++;

            /*
             * If the client understood the message but supplied too little buffer space
             * don't send this message again and drop it after 6 unsuccessful attempts.
             *
             * Note: Due to legacy reasons this the retry counter has to be even because on
             *       every peek there will be the actual message retrieval from the client side.
             *       To not get the actual message if the client actually only wants to peek for
             *       the next message, there needs to be two rounds per try, e.g. 3 rounds = 6 tries.
             */
            /** @todo Fix the mess stated above. GUEST_MSG_WAIT should be become GUEST_MSG_PEEK, *only*
             *        (and every time) returning the next upcoming host message (if any, blocking). Then
             *        it's up to the client what to do next, either peeking again or getting the actual
             *        host message via an own GUEST_ type message.
             */
            if (   rc == VERR_TOO_MUCH_DATA
                || rc == VERR_CANCELLED)
            {
                if (mHostMsgTries == 6)
                    fRemove = true;
            }
            /* Client did not understand the message or something else weird happened. Try again one
             * more time and drop it if it didn't get handled then. */
            else if (mHostMsgTries > 1)
                fRemove = true;
        }
        else
            fRemove = true; /* Everything went fine, remove it. */

        LogFlowThisFunc(("[Client %RU32] Tried host message %RU32 for %RU32 times, (last result=%Rrc, fRemove=%RTbool)\n",
                         m_idClient, pHostMsg->mType, mHostMsgTries, rc, fRemove));

        if (fRemove)
        {
            Assert(RTListNodeIsFirst(&m_HostMsgList, &pHostMsg->m_ListEntry));
            OldDitchFirstHostMsg();
        }

        LogFlowFunc(("[Client %RU32] Returned with rc=%Rrc\n", m_idClient, rc));
        return rc;
    }

    /**
     * @note Only used by GUEST_MSG_WAIT scenarios.
     */
    int OldRunCurrent(const ClientRequest *pReq)
    {
        AssertPtrReturn(pReq, VERR_INVALID_POINTER);

        /*
         * If the host message list is empty, the request must wait for one to be posted.
         */
        HostMsg *pFirstMsg = RTListGetFirstCpp(&m_HostMsgList, HostMsg, m_ListEntry);
        if (!pFirstMsg)
        {
            if (!m_fPendingCancel)
            {
                /* Go to sleep. */
                ASSERT_GUEST_RETURN(m_enmPendingMsg == 0, VERR_WRONG_ORDER);
                m_PendingReq = *pReq;
                m_enmPendingMsg  = GUEST_MSG_WAIT;
                LogFlowFunc(("[Client %RU32] Is now in pending mode\n", m_idClient));
                return VINF_HGCM_ASYNC_EXECUTE;
            }

            /* Wait was cancelled. */
            m_fPendingCancel = false;
            if (pReq->mNumParms > 0)
                HGCMSvcSetU32(&pReq->mParms[0], HOST_MSG_CANCEL_PENDING_WAITS);
            if (pReq->mNumParms > 1)
                HGCMSvcSetU32(&pReq->mParms[1], 0);
            return pReq->mNumParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN;
        }

        /*
         * Return first host message.
         */
        return OldRun(pReq, pFirstMsg);
    }

    /**
     * Internal worker for OldRun().
     * @note Only used for GUEST_MSG_WAIT.
     */
    int OldSendReply(ClientRequest const *pReq,
                     HostMsg         *pHostMsg)
    {
        AssertPtrReturn(pReq, VERR_INVALID_POINTER);
        AssertPtrReturn(pHostMsg, VERR_INVALID_POINTER);

        /* In case of VERR_CANCELLED. */
        uint32_t const cSavedPeeks = mPeekCount;

        int rc;
        /* If the client is in pending mode, always send back
         * the peek result first. */
        if (m_enmPendingMsg)
        {
            Assert(m_enmPendingMsg == GUEST_MSG_WAIT);
            rc = pHostMsg->Peek(pReq);
            mPeekCount++;
        }
        else
        {
            /* If this is the very first peek, make sure to *always* give back the peeking answer
             * instead of the actual message, even if this message would fit into the current
             * connection buffer. */
            if (!mPeekCount)
            {
                rc = pHostMsg->Peek(pReq);
                mPeekCount++;
            }
            else
            {
                /* Try assigning the host message to the client and store the
                 * result code for later use. */
                rc = pHostMsg->Assign(pReq);
                if (RT_FAILURE(rc)) /* If something failed, let the client peek (again). */
                {
                    rc = pHostMsg->Peek(pReq);
                    mPeekCount++;
                }
                else
                    mPeekCount = 0;
            }
        }

        /* Reset pending status. */
        m_enmPendingMsg = (guestControl::eGuestMsg)0;

        /* In any case the client did something, so complete
         * the pending call with the result we just got. */
        AssertPtr(m_pSvcHelpers);
        int rc2 = m_pSvcHelpers->pfnCallComplete(pReq->mHandle, rc);

        /* Rollback in case the guest cancelled the call. */
        if (rc2 == VERR_CANCELLED && RT_SUCCESS(rc))
        {
            mPeekCount = cSavedPeeks;
            rc = VERR_CANCELLED;
        }

        LogFlowThisFunc(("[Client %RU32] Message %RU32 ended with %Rrc (mPeekCount=%RU32, pReq=%p)\n",
                         m_idClient, pHostMsg->mType, rc, mPeekCount, pReq));
        return rc;
    }

    /** @} */
} ClientState;
typedef std::map< uint32_t, ClientState *> ClientStateMap;

/**
 * Prepared session (GUEST_SESSION_PREPARE).
 */
typedef struct GstCtrlPreparedSession
{
    /** List entry. */
    RTLISTNODE  ListEntry;
    /** The session ID.   */
    uint32_t    idSession;
    /** The key size. */
    uint32_t    cbKey;
    /** The key bytes. */
    uint8_t     abKey[RT_FLEXIBLE_ARRAY];
} GstCtrlPreparedSession;


/**
 * Class containing the shared information service functionality.
 */
class GstCtrlService : public RTCNonCopyable
{

private:

    /** Type definition for use in callback functions. */
    typedef GstCtrlService SELF;
    /** HGCM helper functions. */
    PVBOXHGCMSVCHELPERS     mpHelpers;
    /** Callback function supplied by the host for notification of updates to properties. */
    PFNHGCMSVCEXT           mpfnHostCallback;
    /** User data pointer to be supplied to the host callback function. */
    void                   *mpvHostData;
    /** Map containing all connected clients, key is HGCM client ID. */
    ClientStateMap          m_ClientStateMap;
    /** Session ID -> client state. */
    ClientStateMap          m_SessionIdMap;
    /** The current master client, NULL if none. */
    ClientState            *m_pMasterClient;
    /** The master HGCM client ID, UINT32_MAX if none. */
    uint32_t                m_idMasterClient;
    /** Set if we're in legacy mode (pre 6.0). */
    bool                    m_fLegacyMode;
    /** Number of prepared sessions. */
    uint32_t                m_cPreparedSessions;
    /** List of prepared session (GstCtrlPreparedSession). */
    RTLISTANCHOR            m_PreparedSessions;

public:
    explicit GstCtrlService(PVBOXHGCMSVCHELPERS pHelpers)
        : mpHelpers(pHelpers)
        , mpfnHostCallback(NULL)
        , mpvHostData(NULL)
        , m_pMasterClient(NULL)
        , m_idMasterClient(UINT32_MAX)
        , m_fLegacyMode(true)
        , m_cPreparedSessions(0)
    {
        RTListInit(&m_PreparedSessions);
    }

    static DECLCALLBACK(int)  svcUnload(void *pvService);
    static DECLCALLBACK(int)  svcConnect(void *pvService, uint32_t idClient, void *pvClient,
                                         uint32_t fRequestor, bool fRestoring);
    static DECLCALLBACK(int)  svcDisconnect(void *pvService, uint32_t idClient, void *pvClient);
    static DECLCALLBACK(void) svcCall(void *pvService, VBOXHGCMCALLHANDLE hCall, uint32_t idClient, void *pvClient,
                                      uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival);
    static DECLCALLBACK(int)  svcHostCall(void *pvService, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    static DECLCALLBACK(int)  svcSaveState(void *pvService, uint32_t idClient, void *pvClient, PSSMHANDLE pSSM);
    static DECLCALLBACK(int)  svcLoadState(void *pvService, uint32_t idClient, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion);
    static DECLCALLBACK(int)  svcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension);

private:
    int clientMakeMeMaster(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms);
    int clientMsgPeek(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fWait);
    int clientMsgGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientMsgCancel(ClientState *pClient, uint32_t cParms);
    int clientMsgSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientSessionPrepare(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientSessionCancelPrepared(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientSessionAccept(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientSessionCloseOther(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientToMain(ClientState *pClient, uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);

    int clientMsgOldGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientMsgOldFilterSet(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int clientMsgOldSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms);

    int hostCallback(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);
    int hostProcessMessage(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]);

    DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(GstCtrlService);
};


/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnUnload,
 *  Simply deletes the GstCtrlService object}
 */
/*static*/ DECLCALLBACK(int)
GstCtrlService::svcUnload(void *pvService)
{
    AssertLogRelReturn(VALID_PTR(pvService), VERR_INVALID_PARAMETER);
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    delete pThis;

    return VINF_SUCCESS;
}



/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnConnect,
 *  Initializes the state for a new client.}
 */
/*static*/ DECLCALLBACK(int)
GstCtrlService::svcConnect(void *pvService, uint32_t idClient, void *pvClient, uint32_t fRequestor, bool fRestoring)
{
    LogFlowFunc(("[Client %RU32] Connected\n", idClient));

    RT_NOREF(fRestoring, pvClient);
    AssertLogRelReturn(VALID_PTR(pvService), VERR_INVALID_PARAMETER);
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    AssertMsg(pThis->m_ClientStateMap.find(idClient) == pThis->m_ClientStateMap.end(),
              ("Client with ID=%RU32 already connected when it should not\n", idClient));

    /*
     * Create client state.
     */
    ClientState *pClient = NULL;
    try
    {
        pClient = new (pvClient) ClientState(pThis->mpHelpers, idClient);
        pThis->m_ClientStateMap[idClient] = pClient;
    }
    catch (std::bad_alloc &)
    {
        if (pClient)
            pClient->~ClientState();
        return VERR_NO_MEMORY;
    }

    /*
     * For legacy compatibility reasons we have to pick a master client at some
     * point, so if the /dev/vboxguest requirements checks out we pick the first
     * one through the door.
     */
/** @todo make picking the master more dynamic/flexible? */
    if (   pThis->m_fLegacyMode
        && pThis->m_idMasterClient == UINT32_MAX)
    {
        if (   fRequestor == VMMDEV_REQUESTOR_LEGACY
            || !(fRequestor & VMMDEV_REQUESTOR_USER_DEVICE))
        {
            LogFunc(("Picking %u as master for now.\n", idClient));
            pThis->m_pMasterClient  = pClient;
            pThis->m_idMasterClient = idClient;
            pClient->m_fIsMaster = true;
        }
    }

    return VINF_SUCCESS;
}


/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnConnect,
 *  Handles a client which disconnected.}
 *
 * This functiond does some internal cleanup as well as sends notifications to
 * the host so that the host can do the same (if required).
 */
/*static*/ DECLCALLBACK(int)
GstCtrlService::svcDisconnect(void *pvService, uint32_t idClient, void *pvClient)
{
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    ClientState *pClient = reinterpret_cast<ClientState *>(pvClient);
    AssertPtrReturn(pClient, VERR_INVALID_POINTER);
    LogFlowFunc(("[Client %RU32] Disconnected (%zu clients total)\n", idClient, pThis->m_ClientStateMap.size()));

    /*
     * Cancel all pending host messages, replying with GUEST_DISCONNECTED if final recipient.
     */
    HostMsg *pCurMsg, *pNextMsg;
    RTListForEachSafeCpp(&pClient->m_HostMsgList, pCurMsg, pNextMsg, HostMsg, m_ListEntry)
    {
        RTListNodeRemove(&pCurMsg->m_ListEntry);

        VBOXHGCMSVCPARM Parm;
        HGCMSvcSetU32(&Parm, pCurMsg->m_idContext);
        int rc2 = pThis->hostCallback(GUEST_MSG_DISCONNECTED, 1, &Parm);
        LogFlowFunc(("Cancelled host message %u (%s) with idContext=%#x -> %Rrc\n",
                     pCurMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pCurMsg->mType), pCurMsg->m_idContext, rc2));
        RT_NOREF(rc2);

        pCurMsg->Delete();
    }

    /*
     * Delete the client state.
     */
    pThis->m_ClientStateMap.erase(idClient);
    if (pClient->m_idSession != UINT32_MAX)
        pThis->m_SessionIdMap.erase(pClient->m_idSession);
    pClient->~ClientState();

    /*
     * If it's the master disconnecting, we need to reset related globals.
     */
    if (idClient == pThis->m_idMasterClient)
    {
        pThis->m_pMasterClient  = NULL;
        pThis->m_idMasterClient = UINT32_MAX;

        GstCtrlPreparedSession *pCur, *pNext;
        RTListForEachSafe(&pThis->m_PreparedSessions, pCur, pNext, GstCtrlPreparedSession, ListEntry)
        {
            RTListNodeRemove(&pCur->ListEntry);
            RTMemFree(pCur);
        }
        pThis->m_cPreparedSessions = 0;
    }
    else
        Assert(pClient != pThis->m_pMasterClient);

    if (pThis->m_ClientStateMap.empty())
        pThis->m_fLegacyMode = true;

    return VINF_SUCCESS;
}


/**
 * A client asks for the next message to process.
 *
 * This either fills in a pending host message into the client's parameter space
 * or defers the guest call until we have something from the host.
 *
 * @returns VBox status code.
 * @param   pClient         The client state.
 * @param   hCall           The client's call handle.
 * @param   cParms          Number of parameters.
 * @param   paParms         Array of parameters.
 */
int GstCtrlService::clientMsgOldGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    ASSERT_GUEST(pClient->m_idSession != UINT32_MAX || pClient->m_fIsMaster || pClient->m_fRestored);

    /* Use the current (inbound) connection. */
    ClientRequest thisCon;
    thisCon.mHandle   = hCall;
    thisCon.mNumParms = cParms;
    thisCon.mParms    = paParms;

    return pClient->OldRunCurrent(&thisCon);
}


/**
 * Implements GUEST_MAKE_ME_MASTER.
 *
 * @returns VBox status code.
 * @retval  VINF_HGCM_ASYNC_EXECUTE on success (we complete the message here).
 * @retval  VERR_ACCESS_DENIED if not using main VBoxGuest device not
 * @retval  VERR_RESOURCE_BUSY if there is already a master.
 * @retval  VERR_VERSION_MISMATCH if VBoxGuest didn't supply requestor info.
 * @retval  VERR_WRONG_PARAMETER_COUNT
 *
 * @param   pClient     The client state.
 * @param   hCall       The client's call handle.
 * @param   cParms      Number of parameters.
 */
int GstCtrlService::clientMakeMeMaster(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms)
{
    /*
     * Validate the request.
     */
    ASSERT_GUEST_RETURN(cParms == 0, VERR_WRONG_PARAMETER_COUNT);

    uint32_t fRequestor = mpHelpers->pfnGetRequestor(hCall);
    ASSERT_GUEST_LOGREL_MSG_RETURN(fRequestor != VMMDEV_REQUESTOR_LEGACY,
                                   ("Outdated VBoxGuest w/o requestor support. Please update!\n"),
                                   VERR_VERSION_MISMATCH);
    ASSERT_GUEST_LOGREL_MSG_RETURN(!(fRequestor & VMMDEV_REQUESTOR_USER_DEVICE), ("fRequestor=%#x\n", fRequestor),
                                   VERR_ACCESS_DENIED);

    /*
     * Do the work.
     */
    ASSERT_GUEST_MSG_RETURN(m_idMasterClient == pClient->m_idClient || m_idMasterClient == UINT32_MAX,
                            ("Already have master session %RU32, refusing %RU32.\n", m_idMasterClient, pClient->m_idClient),
                            VERR_RESOURCE_BUSY);
    int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
    if (RT_SUCCESS(rc))
    {
        m_pMasterClient  = pClient;
        m_idMasterClient = pClient->m_idClient;
        m_fLegacyMode    = false;
        pClient->m_fIsMaster = true;
        Log(("[Client %RU32] is master.\n", pClient->m_idClient));
    }
    else
        LogFunc(("pfnCallComplete -> %Rrc\n", rc));

    return VINF_HGCM_ASYNC_EXECUTE;
}

/**
 * Implements GUEST_MSG_PEEK_WAIT and GUEST_MSG_PEEK_NOWAIT.
 *
 * @returns VBox status code.
 * @retval  VINF_SUCCESS if a message was pending and is being returned.
 * @retval  VERR_TRY_AGAIN if no message pending and not blocking.
 * @retval  VERR_RESOURCE_BUSY if another read already made a waiting call.
 * @retval  VINF_HGCM_ASYNC_EXECUTE if message wait is pending.
 *
 * @param   pClient     The client state.
 * @param   hCall       The client's call handle.
 * @param   cParms      Number of parameters.
 * @param   paParms     Array of parameters.
 * @param   fWait       Set if we should wait for a message, clear if to return
 *                      immediately.
 */
int GstCtrlService::clientMsgPeek(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fWait)
{
    /*
     * Validate the request.
     */
    ASSERT_GUEST_MSG_RETURN(cParms >= 2, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT);

    uint64_t idRestoreCheck = 0;
    uint32_t i              = 0;
    if (paParms[i].type == VBOX_HGCM_SVC_PARM_64BIT)
    {
        idRestoreCheck = paParms[0].u.uint64;
        paParms[0].u.uint64 = 0;
        i++;
    }
    for (; i < cParms; i++)
    {
        ASSERT_GUEST_MSG_RETURN(paParms[i].type == VBOX_HGCM_SVC_PARM_32BIT, ("#%u type=%u\n", i, paParms[i].type),
                                VERR_WRONG_PARAMETER_TYPE);
        paParms[i].u.uint32 = 0;
    }

    /*
     * Check restore session ID.
     */
    if (idRestoreCheck != 0)
    {
        uint64_t idRestore = mpHelpers->pfnGetVMMDevSessionId(mpHelpers);
        if (idRestoreCheck != idRestore)
        {
            paParms[0].u.uint64 = idRestore;
            LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_XXXX -> VERR_VM_RESTORED (%#RX64 -> %#RX64)\n",
                         pClient->m_idClient, idRestoreCheck, idRestore));
            return VERR_VM_RESTORED;
        }
        Assert(!mpHelpers->pfnIsCallRestored(hCall));
    }

    /*
     * Return information about the first message if one is pending in the list.
     */
    HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry);
    if (pFirstMsg)
    {
        pFirstMsg->setPeekReturn(paParms, cParms);
        LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_XXXX -> VINF_SUCCESS (idMsg=%u (%s), cParms=%u)\n",
                     pClient->m_idClient, pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType), pFirstMsg->mParmCount));
        return VINF_SUCCESS;
    }

    /*
     * If we cannot wait, fail the call.
     */
    if (!fWait)
    {
        LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT -> VERR_TRY_AGAIN\n", pClient->m_idClient));
        return VERR_TRY_AGAIN;
    }

    /*
     * Wait for the host to queue a message for this client.
     */
    ASSERT_GUEST_MSG_RETURN(pClient->m_enmPendingMsg == 0, ("Already pending! (idClient=%RU32)\n", pClient->m_idClient),
                            VERR_RESOURCE_BUSY);
    pClient->m_PendingReq.mHandle   = hCall;
    pClient->m_PendingReq.mNumParms = cParms;
    pClient->m_PendingReq.mParms    = paParms;
    pClient->m_enmPendingMsg         = GUEST_MSG_PEEK_WAIT;
    LogFlowFunc(("[Client %RU32] Is now in pending mode...\n", pClient->m_idClient));
    return VINF_HGCM_ASYNC_EXECUTE;
}

/**
 * Implements GUEST_MSG_GET.
 *
 * @returns VBox status code.
 * @retval  VINF_SUCCESS if message retrieved and removed from the pending queue.
 * @retval  VERR_TRY_AGAIN if no message pending.
 * @retval  VERR_BUFFER_OVERFLOW if a parmeter buffer is too small.  The buffer
 *          size was updated to reflect the required size, though this isn't yet
 *          forwarded to the guest.  (The guest is better of using peek with
 *          parameter count + 2 parameters to get the sizes.)
 * @retval  VERR_MISMATCH if the incoming message ID does not match the pending.
 * @retval  VINF_HGCM_ASYNC_EXECUTE if message was completed already.
 *
 * @param   pClient     The client state.
 * @param   hCall       The client's call handle.
 * @param   cParms      Number of parameters.
 * @param   paParms     Array of parameters.
 */
int GstCtrlService::clientMsgGet(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate the request.
     *
     * The weird first parameter logic is due to GUEST_MSG_WAIT compatibility
     * (don't want to rewrite all the message structures).
     */
    uint32_t const idMsgExpected = cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT ? paParms[0].u.uint32
                                 : cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT ? paParms[0].u.uint64
                                 : UINT32_MAX;

    /*
     * Return information about the first message if one is pending in the list.
     */
    HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry);
    if (pFirstMsg)
    {

        ASSERT_GUEST_MSG_RETURN(pFirstMsg->mType == idMsgExpected || idMsgExpected == UINT32_MAX,
                                ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n",
                                 pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType), pFirstMsg->mParmCount,
                                 idMsgExpected, GstCtrlHostMsgtoStr((eHostMsg)idMsgExpected), cParms),
                                VERR_MISMATCH);
        ASSERT_GUEST_MSG_RETURN(pFirstMsg->mParmCount == cParms,
                                ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n",
                                 pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType), pFirstMsg->mParmCount,
                                 idMsgExpected, GstCtrlHostMsgtoStr((eHostMsg)idMsgExpected), cParms),
                                VERR_WRONG_PARAMETER_COUNT);

        /* Check the parameter types. */
        for (uint32_t i = 0; i < cParms; i++)
            ASSERT_GUEST_MSG_RETURN(pFirstMsg->mpParms[i].type == paParms[i].type,
                                    ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->mpParms[i].type,
                                     paParms[i].type, pFirstMsg->mType, GstCtrlHostMsgtoStr((eHostMsg)pFirstMsg->mType)),
                                    VERR_WRONG_PARAMETER_TYPE);

        /*
         * Copy out the parameters.
         *
         * No assertions on buffer overflows, and keep going till the end so we can
         * communicate all the required buffer sizes.
         */
        int rc = VINF_SUCCESS;
        for (uint32_t i = 0; i < cParms; i++)
            switch (pFirstMsg->mpParms[i].type)
            {
                case VBOX_HGCM_SVC_PARM_32BIT:
                    paParms[i].u.uint32 = pFirstMsg->mpParms[i].u.uint32;
                    break;

                case VBOX_HGCM_SVC_PARM_64BIT:
                    paParms[i].u.uint64 = pFirstMsg->mpParms[i].u.uint64;
                    break;

                case VBOX_HGCM_SVC_PARM_PTR:
                {
                    uint32_t const cbSrc = pFirstMsg->mpParms[i].u.pointer.size;
                    uint32_t const cbDst = paParms[i].u.pointer.size;
                    paParms[i].u.pointer.size = cbSrc; /** @todo Check if this is safe in other layers...
                                                        * Update: Safe, yes, but VMMDevHGCM doesn't pass it along. */
                    if (cbSrc <= cbDst)
                        memcpy(paParms[i].u.pointer.addr, pFirstMsg->mpParms[i].u.pointer.addr, cbSrc);
                    else
                        rc = VERR_BUFFER_OVERFLOW;
                    break;
                }

                default:
                    AssertMsgFailed(("#%u: %u\n", i, pFirstMsg->mpParms[i].type));
                    rc = VERR_INTERNAL_ERROR;
                    break;
            }
        if (RT_SUCCESS(rc))
        {
            /*
             * Complete the message and remove the pending message unless the
             * guest raced us and cancelled this call in the meantime.
             */
            AssertPtr(mpHelpers);
            rc = mpHelpers->pfnCallComplete(hCall, rc);
            if (rc != VERR_CANCELLED)
            {
                RTListNodeRemove(&pFirstMsg->m_ListEntry);
                pFirstMsg->Delete();
            }
            else
                LogFunc(("pfnCallComplete -> %Rrc\n", rc));
            return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
        }
        return rc;
    }

    paParms[0].u.uint32 = 0;
    paParms[1].u.uint32 = 0;
    LogFlowFunc(("[Client %RU32] GUEST_MSG_GET -> VERR_TRY_AGAIN\n", pClient->m_idClient));
    return VERR_TRY_AGAIN;
}

/**
 * Implements GUEST_MSG_CANCEL.
 *
 * @returns VBox status code.
 * @retval  VINF_SUCCESS if cancelled any calls.
 * @retval  VWRN_NOT_FOUND if no callers.
 * @retval  VINF_HGCM_ASYNC_EXECUTE if message wait is pending.
 *
 * @param   pClient     The client state.
 * @param   cParms      Number of parameters.
 */
int GstCtrlService::clientMsgCancel(ClientState *pClient, uint32_t cParms)
{
    /*
     * Validate the request.
     */
    ASSERT_GUEST_MSG_RETURN(cParms == 0, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT);

    /*
     * Execute.
     */
    if (pClient->m_enmPendingMsg != 0)
    {
        pClient->CancelWaiting();
        return VINF_SUCCESS;
    }
    return VWRN_NOT_FOUND;
}


/**
 * Implements GUEST_MSG_SKIP.
 *
 * @returns VBox status code.
 * @retval  VINF_HGCM_ASYNC_EXECUTE on success as we complete the message.
 * @retval  VERR_NOT_FOUND if no message pending.
 *
 * @param   pClient     The client state.
 * @param   hCall       The call handle for completing it.
 * @param   cParms      Number of parameters.
 * @param   paParms     The parameters.
 */
int GstCtrlService::clientMsgSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate the call.
     */
    ASSERT_GUEST_RETURN(cParms <= 2, VERR_WRONG_PARAMETER_COUNT);

    int32_t rcSkip = VERR_NOT_SUPPORTED;
    if (cParms >= 1)
    {
        ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
        rcSkip = (int32_t)paParms[0].u.uint32;
    }

    uint32_t idMsg = UINT32_MAX;
    if (cParms >= 2)
    {
        ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
        idMsg = paParms[1].u.uint32;
    }

    /*
     * Do the job.
     */
    HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry);
    if (pFirstMsg)
    {
        if (   pFirstMsg->mType == idMsg
            || idMsg == UINT32_MAX)
        {
            int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
            if (RT_SUCCESS(rc))
            {
                /*
                 * Remove the message from the queue.
                 */
                Assert(RTListNodeIsFirst(&pClient->m_HostMsgList, &pFirstMsg->m_ListEntry) );
                RTListNodeRemove(&pFirstMsg->m_ListEntry);

                /*
                 * Compose a reply to the host service.
                 */
                VBOXHGCMSVCPARM aReplyParams[5];
                HGCMSvcSetU32(&aReplyParams[0], pFirstMsg->m_idContext);
                switch (pFirstMsg->mType)
                {
                    case HOST_MSG_EXEC_CMD:
                        HGCMSvcSetU32(&aReplyParams[1], 0);              /* pid */
                        HGCMSvcSetU32(&aReplyParams[2], PROC_STS_ERROR); /* status */
                        HGCMSvcSetU32(&aReplyParams[3], rcSkip);         /* flags / whatever */
                        HGCMSvcSetPv(&aReplyParams[4], NULL, 0);         /* data buffer */
                        hostCallback(GUEST_MSG_EXEC_STATUS, 5, aReplyParams);
                        break;

                    case HOST_MSG_SESSION_CREATE:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_SESSION_NOTIFYTYPE_ERROR);    /* type */
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                            /* result */
                        hostCallback(GUEST_MSG_SESSION_NOTIFY, 3, aReplyParams);
                        break;

                    case HOST_MSG_EXEC_SET_INPUT:
                        HGCMSvcSetU32(&aReplyParams[1], pFirstMsg->mParmCount >= 2 ? pFirstMsg->mpParms[1].u.uint32 : 0);
                        HGCMSvcSetU32(&aReplyParams[2], INPUT_STS_ERROR);   /* status */
                        HGCMSvcSetU32(&aReplyParams[3], rcSkip);            /* flags / whatever */
                        HGCMSvcSetU32(&aReplyParams[4], 0);                 /* bytes consumed */
                        hostCallback(GUEST_MSG_EXEC_INPUT_STATUS, 5, aReplyParams);
                        break;

                    case HOST_MSG_FILE_OPEN:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_OPEN); /* type*/
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                     /* rc */
                        HGCMSvcSetU32(&aReplyParams[3], VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pFirstMsg->m_idContext)); /* handle */
                        hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams);
                        break;
                    case HOST_MSG_FILE_CLOSE:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_ERROR); /* type*/
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                      /* rc */
                        hostCallback(GUEST_MSG_FILE_NOTIFY, 3, aReplyParams);
                        break;
                    case HOST_MSG_FILE_READ:
                    case HOST_MSG_FILE_READ_AT:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_READ);  /* type */
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                      /* rc */
                        HGCMSvcSetPv(&aReplyParams[3], NULL, 0);                      /* data buffer */
                        hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams);
                        break;
                    case HOST_MSG_FILE_WRITE:
                    case HOST_MSG_FILE_WRITE_AT:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_WRITE); /* type */
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                      /* rc */
                        HGCMSvcSetU32(&aReplyParams[3], 0);                           /* bytes written */
                        hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams);
                        break;
                    case HOST_MSG_FILE_SEEK:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_SEEK);  /* type */
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                      /* rc */
                        HGCMSvcSetU64(&aReplyParams[3], 0);                           /* actual */
                        hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams);
                        break;
                    case HOST_MSG_FILE_TELL:
                        HGCMSvcSetU32(&aReplyParams[1], GUEST_FILE_NOTIFYTYPE_TELL);  /* type */
                        HGCMSvcSetU32(&aReplyParams[2], rcSkip);                      /* rc */
                        HGCMSvcSetU64(&aReplyParams[3], 0);                           /* actual */
                        hostCallback(GUEST_MSG_FILE_NOTIFY, 4, aReplyParams);
                        break;

                    case HOST_MSG_EXEC_GET_OUTPUT: /** @todo This can't be right/work. */
                    case HOST_MSG_EXEC_TERMINATE:  /** @todo This can't be right/work. */
                    case HOST_MSG_EXEC_WAIT_FOR:   /** @todo This can't be right/work. */
                    case HOST_MSG_PATH_USER_DOCUMENTS:
                    case HOST_MSG_PATH_USER_HOME:
                    case HOST_MSG_PATH_RENAME:
                    case HOST_MSG_DIR_REMOVE:
                    default:
                        HGCMSvcSetU32(&aReplyParams[1], pFirstMsg->mType);
                        HGCMSvcSetU32(&aReplyParams[2], (uint32_t)rcSkip);
                        HGCMSvcSetPv(&aReplyParams[3], NULL, 0);
                        hostCallback(GUEST_MSG_REPLY, 4, aReplyParams);
                        break;
                }

                /*
                 * Free the message.
                 */
                pFirstMsg->Delete();
            }
            else
                LogFunc(("pfnCallComplete -> %Rrc\n", rc));
            return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
        }
        LogFunc(("Warning: GUEST_MSG_SKIP mismatch! Found %u, caller expected %u!\n", pFirstMsg->mType, idMsg));
        return VERR_MISMATCH;
    }
    return VERR_NOT_FOUND;
}


/**
 * Implements GUEST_SESSION_PREPARE.
 *
 * @returns VBox status code.
 * @retval  VINF_HGCM_ASYNC_EXECUTE on success as we complete the message.
 * @retval  VERR_OUT_OF_RESOURCES if too many pending sessions hanging around.
 * @retval  VERR_OUT_OF_RANGE if the session ID outside the allowed range.
 * @retval  VERR_BUFFER_OVERFLOW if key too large.
 * @retval  VERR_BUFFER_UNDERFLOW if key too small.
 * @retval  VERR_ACCESS_DENIED if not master or in legacy mode.
 * @retval  VERR_DUPLICATE if the session ID has been prepared already.
 *
 * @param   pClient     The client state.
 * @param   hCall       The call handle for completing it.
 * @param   cParms      Number of parameters.
 * @param   paParms     The parameters.
 */
int GstCtrlService::clientSessionPrepare(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate parameters.
     */
    ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT);
    ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const idSession = paParms[0].u.uint32;
    ASSERT_GUEST_RETURN(idSession >= 1, VERR_OUT_OF_RANGE);
    ASSERT_GUEST_RETURN(idSession <= 0xfff0, VERR_OUT_OF_RANGE);

    ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const cbKey = paParms[1].u.pointer.size;
    void const    *pvKey = paParms[1].u.pointer.addr;
    ASSERT_GUEST_RETURN(cbKey >= 64, VERR_BUFFER_UNDERFLOW);
    ASSERT_GUEST_RETURN(cbKey <= _16K, VERR_BUFFER_OVERFLOW);

    ASSERT_GUEST_RETURN(pClient->m_fIsMaster, VERR_ACCESS_DENIED);
    ASSERT_GUEST_RETURN(!m_fLegacyMode, VERR_ACCESS_DENIED);
    Assert(m_idMasterClient == pClient->m_idClient);
    Assert(m_pMasterClient == pClient);

    /* Now that we know it's the master, we can check for session ID duplicates. */
    GstCtrlPreparedSession *pCur;
    RTListForEach(&m_PreparedSessions, pCur, GstCtrlPreparedSession, ListEntry)
    {
        ASSERT_GUEST_RETURN(pCur->idSession != idSession, VERR_DUPLICATE);
    }

    /*
     * Make a copy of the session ID and key.
     */
    ASSERT_GUEST_RETURN(m_cPreparedSessions < 128, VERR_OUT_OF_RESOURCES);

    GstCtrlPreparedSession *pPrepped = (GstCtrlPreparedSession *)RTMemAlloc(RT_UOFFSETOF_DYN(GstCtrlPreparedSession, abKey[cbKey]));
    AssertReturn(pPrepped, VERR_NO_MEMORY);
    pPrepped->idSession = idSession;
    pPrepped->cbKey     = cbKey;
    memcpy(pPrepped->abKey, pvKey, cbKey);

    RTListAppend(&m_PreparedSessions, &pPrepped->ListEntry);
    m_cPreparedSessions++;

    /*
     * Try complete the message.
     */
    int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
    if (RT_SUCCESS(rc))
        LogFlow(("Prepared %u with a %#x byte key (%u pending).\n", idSession, cbKey, m_cPreparedSessions));
    else
    {
        LogFunc(("pfnCallComplete -> %Rrc\n", rc));
        RTListNodeRemove(&pPrepped->ListEntry);
        RTMemFree(pPrepped);
        m_cPreparedSessions--;
    }
    return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
}


/**
 * Implements GUEST_SESSION_CANCEL_PREPARED.
 *
 * @returns VBox status code.
 * @retval  VINF_HGCM_ASYNC_EXECUTE on success as we complete the message.
 * @retval  VWRN_NOT_FOUND if no session with the specified ID.
 * @retval  VERR_ACCESS_DENIED if not master or in legacy mode.
 *
 * @param   pClient     The client state.
 * @param   cParms      Number of parameters.
 * @param   paParms     The parameters.
 */
int GstCtrlService::clientSessionCancelPrepared(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate parameters.
     */
    ASSERT_GUEST_RETURN(cParms == 1, VERR_WRONG_PARAMETER_COUNT);
    ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const idSession = paParms[0].u.uint32;

    ASSERT_GUEST_RETURN(pClient->m_fIsMaster, VERR_ACCESS_DENIED);
    ASSERT_GUEST_RETURN(!m_fLegacyMode, VERR_ACCESS_DENIED);
    Assert(m_idMasterClient == pClient->m_idClient);
    Assert(m_pMasterClient == pClient);

    /*
     * Do the work.
     */
    int rc = VWRN_NOT_FOUND;
    if (idSession == UINT32_MAX)
    {
        GstCtrlPreparedSession *pCur, *pNext;
        RTListForEachSafe(&m_PreparedSessions, pCur, pNext, GstCtrlPreparedSession, ListEntry)
        {
            RTListNodeRemove(&pCur->ListEntry);
            RTMemFree(pCur);
            rc = VINF_SUCCESS;
        }
        m_cPreparedSessions = 0;
    }
    else
    {
        GstCtrlPreparedSession *pCur, *pNext;
        RTListForEachSafe(&m_PreparedSessions, pCur, pNext, GstCtrlPreparedSession, ListEntry)
        {
            if (pCur->idSession == idSession)
            {
                RTListNodeRemove(&pCur->ListEntry);
                RTMemFree(pCur);
                m_cPreparedSessions -= 1;
                rc = VINF_SUCCESS;
                break;
            }
        }
    }
    return VINF_SUCCESS;
}


/**
 * Implements GUEST_SESSION_ACCEPT.
 *
 * @returns VBox status code.
 * @retval  VINF_HGCM_ASYNC_EXECUTE on success as we complete the message.
 * @retval  VERR_NOT_FOUND if the specified session ID wasn't found.
 * @retval  VERR_MISMATCH if the key didn't match.
 * @retval  VERR_ACCESS_DENIED if we're in legacy mode or is master.
 * @retval  VERR_RESOURCE_BUSY if the client is already associated with a
 *          session.
 *
 * @param   pClient     The client state.
 * @param   hCall       The call handle for completing it.
 * @param   cParms      Number of parameters.
 * @param   paParms     The parameters.
 */
int GstCtrlService::clientSessionAccept(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate parameters.
     */
    ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT);
    ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const idSession = paParms[0].u.uint32;
    ASSERT_GUEST_RETURN(idSession >= 1, VERR_OUT_OF_RANGE);
    ASSERT_GUEST_RETURN(idSession <= 0xfff0, VERR_OUT_OF_RANGE);

    ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const cbKey = paParms[1].u.pointer.size;
    void const    *pvKey = paParms[1].u.pointer.addr;
    ASSERT_GUEST_RETURN(cbKey >= 64, VERR_BUFFER_UNDERFLOW);
    ASSERT_GUEST_RETURN(cbKey <= _16K, VERR_BUFFER_OVERFLOW);

    ASSERT_GUEST_RETURN(!pClient->m_fIsMaster, VERR_ACCESS_DENIED);
    ASSERT_GUEST_RETURN(!m_fLegacyMode, VERR_ACCESS_DENIED);
    Assert(m_idMasterClient != pClient->m_idClient);
    Assert(m_pMasterClient != pClient);
    ASSERT_GUEST_RETURN(pClient->m_idSession == UINT32_MAX, VERR_RESOURCE_BUSY);

    /*
     * Look for the specified session and match the key to it.
     */
    GstCtrlPreparedSession *pCur;
    RTListForEach(&m_PreparedSessions, pCur, GstCtrlPreparedSession, ListEntry)
    {
        if (pCur->idSession == idSession)
        {
            if (   pCur->cbKey == cbKey
                && memcmp(pCur->abKey, pvKey, cbKey) == 0)
            {
                /*
                 * We've got a match.
                 * Try insert it into the sessio ID map and complete the request.
                 */
                try
                {
                    m_SessionIdMap[idSession] = pClient;
                }
                catch (std::bad_alloc &)
                {
                    LogFunc(("Out of memory!\n"));
                    return VERR_NO_MEMORY;
                }

                int rc = mpHelpers->pfnCallComplete(hCall, VINF_SUCCESS);
                if (RT_SUCCESS(rc))
                {
                    pClient->m_idSession = idSession;

                    RTListNodeRemove(&pCur->ListEntry);
                    RTMemFree(pCur);
                    m_cPreparedSessions -= 1;
                    Log(("[Client %RU32] accepted session id %u.\n", pClient->m_idClient, idSession));
                }
                else
                {
                    LogFunc(("pfnCallComplete -> %Rrc\n", rc));
                    m_SessionIdMap.erase(idSession);
                }
                return VINF_HGCM_ASYNC_EXECUTE; /* The caller must not complete it. */
            }
            LogFunc(("Key mismatch for %u!\n", pClient->m_idClient));
            return VERR_MISMATCH;
        }
    }

    LogFunc(("No client prepared for %u!\n", pClient->m_idClient));
    return VERR_NOT_FOUND;
}


/**
 * Client asks another client (guest) session to close.
 *
 * @returns VBox status code.
 * @param   pClient         The client state.
 * @param   cParms          Number of parameters.
 * @param   paParms         Array of parameters.
 */
int GstCtrlService::clientSessionCloseOther(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate input.
     */
    ASSERT_GUEST_RETURN(cParms == 2, VERR_WRONG_PARAMETER_COUNT);
    ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const idContext = paParms[0].u.uint32;

    ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t const fFlags = paParms[1].u.uint32;

    ASSERT_GUEST_RETURN(pClient->m_fIsMaster || (m_fLegacyMode && pClient->m_idSession == UINT32_MAX), VERR_ACCESS_DENIED);

    /*
     * Forward the message to the destiation.
     * Since we modify the first parameter, we must make a copy of the parameters.
     */
    VBOXHGCMSVCPARM aParms[2];
    HGCMSvcSetU64(&aParms[0], idContext | VBOX_GUESTCTRL_DST_SESSION);
    HGCMSvcSetU32(&aParms[1], fFlags);
    int rc = hostProcessMessage(HOST_MSG_SESSION_CLOSE, RT_ELEMENTS(aParms), aParms);

    LogFlowFunc(("Closing guest context ID=%RU32 (from client ID=%RU32) returned with rc=%Rrc\n", idContext, pClient->m_idClient, rc));
    return rc;
}


/**
 * For compatiblity with old additions only - filtering / set session ID.
 *
 * @return  VBox status code.
 * @param   pClient     The client state.
 * @param   cParms      Number of parameters.
 * @param   paParms     Array of parameters.
 */
int GstCtrlService::clientMsgOldFilterSet(ClientState *pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Validate input and access.
     */
    ASSERT_GUEST_RETURN(cParms == 4, VERR_WRONG_PARAMETER_COUNT);
    ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t uValue = paParms[0].u.uint32;
    ASSERT_GUEST_RETURN(paParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t fMaskAdd = paParms[1].u.uint32;
    ASSERT_GUEST_RETURN(paParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE);
    uint32_t fMaskRemove = paParms[2].u.uint32;
    ASSERT_GUEST_RETURN(paParms[3].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* flags, unused */

    /*
     * We have a bunch of expectations here:
     *  - Never called in non-legacy mode.
     *  - Only called once per session.
     *  - Never called by the master session.
     *  - Clients that doesn't wish for any messages passes all zeros.
     *  - All other calls has a unique session ID.
     */
    ASSERT_GUEST_LOGREL_RETURN(m_fLegacyMode, VERR_WRONG_ORDER);
    ASSERT_GUEST_LOGREL_MSG_RETURN(pClient->m_idSession == UINT32_MAX, ("m_idSession=%#x\n", pClient->m_idSession),
                                   VERR_WRONG_ORDER);
    ASSERT_GUEST_LOGREL_RETURN(!pClient->m_fIsMaster, VERR_WRONG_ORDER);

    if (uValue == 0)
    {
        ASSERT_GUEST_LOGREL(fMaskAdd == 0);
        ASSERT_GUEST_LOGREL(fMaskRemove == 0);
        /* Nothing to do, already muted (UINT32_MAX). */
    }
    else
    {
        ASSERT_GUEST_LOGREL(fMaskAdd == UINT32_C(0xf8000000));
        ASSERT_GUEST_LOGREL(fMaskRemove == 0);

        uint32_t idSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uValue);
        ASSERT_GUEST_LOGREL_MSG_RETURN(idSession > 0, ("idSession=%u (%#x)\n", idSession, uValue), VERR_OUT_OF_RANGE);

        ClientStateMap::iterator ItConflict = m_SessionIdMap.find(idSession);
        ASSERT_GUEST_LOGREL_MSG_RETURN(ItConflict == m_SessionIdMap.end(),
                                       ("idSession=%u uValue=%#x idClient=%u; conflicting with client %u\n",
                                        idSession, uValue, pClient->m_idClient, ItConflict->second->m_idClient),
                                       VERR_DUPLICATE);

        /* Commit it. */
        try
        {
            m_SessionIdMap[idSession] = pClient;
        }
        catch (std::bad_alloc &)
        {
            LogFunc(("Out of memory\n"));
            return VERR_NO_MEMORY;
        }
        pClient->m_idSession = idSession;
    }
    return VINF_SUCCESS;
}


/**
 * For compatibility with old additions only - skip the current message w/o
 * calling main code.
 *
 * Please note that we don't care if the caller cancelled the request, because
 * old additions code didn't give damn about VERR_INTERRUPT.
 *
 * @return  VBox status code.
 * @param   pClient     The client state.
 * @param   hCall       The call handle for completing it.
 * @param   cParms      Number of parameters.
 */
int GstCtrlService::clientMsgOldSkip(ClientState *pClient, VBOXHGCMCALLHANDLE hCall, uint32_t cParms)
{
    /*
     * Validate input and access.
     */
    ASSERT_GUEST_RETURN(cParms == 1, VERR_WRONG_PARAMETER_COUNT);

    /*
     * Execute the request.
     *
     * Note! As it turns out the old and new skip should be mostly the same.  The
     *       pre-6.0 GAs (up to BETA3) has a hack which tries to issue a
     *       VERR_NOT_SUPPORTED reply to unknown host requests, however the 5.2.x
     *       and earlier GAs doesn't.  We need old skip behavior only for the 6.0
     *       beta GAs, nothing else.
     *       So, we have to track whether they issued a MSG_REPLY or not.  Wonderful.
     */
    HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry);
    if (pFirstMsg)
    {
        uint32_t const idMsg             = pFirstMsg->mType;
        bool const     f60BetaHackInPlay = pFirstMsg->m_f60BetaHackInPlay;
        int            rc;
        if (!f60BetaHackInPlay)
            rc = clientMsgSkip(pClient, hCall, 0, NULL);
        else
        {
            RTListNodeRemove(&pFirstMsg->m_ListEntry);
            pFirstMsg->Delete();
            rc = VINF_SUCCESS;
        }

        /* Reset legacy message wait/get state: */
        if (RT_SUCCESS(rc))
        {
            pClient->mHostMsgRc    = VINF_SUCCESS;
            pClient->mHostMsgTries = 0;
            pClient->mPeekCount    = 0;
        }

        LogFlowFunc(("[Client %RU32] Legacy message skipping: Skipped %u (%s)%s!\n",
                     pClient->m_idClient, idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), f60BetaHackInPlay ? " hack style" : ""));
        NOREF(idMsg);
        return rc;
    }
    LogFlowFunc(("[Client %RU32] Legacy message skipping: No messages pending!\n", pClient->m_idClient));
    return VINF_SUCCESS;
}


/**
 * Forwards client call to the Main API.
 *
 * This is typically notifications and replys.
 *
 * @returns VBox status code.
 * @param   pClient         The client state.
 * @param   idMsg           Message ID that occured.
 * @param   cParms          Number of parameters.
 * @param   paParms         Array of parameters.
 */
int GstCtrlService::clientToMain(ClientState *pClient, uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * Do input validation.  This class of messages all have a 32-bit context ID as
     * the first parameter, so make sure it is there and appropriate for the caller.
     */
    ASSERT_GUEST_RETURN(cParms >= 1, VERR_WRONG_PARAMETER_COUNT);
    ASSERT_GUEST_RETURN(paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_COUNT);
    uint32_t const idContext = paParms[0].u.uint32;
    uint32_t const idSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(idContext);

    ASSERT_GUEST_MSG_RETURN(   pClient->m_idSession == idSession
                            || pClient->m_fIsMaster
                            || (   m_fLegacyMode                                /* (see bugref:9313#c16) */
                                && pClient->m_idSession == UINT32_MAX
                                && (   idMsg == GUEST_MSG_EXEC_STATUS
                                    || idMsg == GUEST_MSG_SESSION_NOTIFY)),
                            ("idSession=%u (CID=%#x) m_idSession=%u idClient=%u idMsg=%u (%s)\n", idSession, idContext,
                             pClient->m_idSession, pClient->m_idClient, idMsg, GstCtrlGuestMsgToStr((eGuestMsg)idMsg)),
                            VERR_ACCESS_DENIED);

    /*
     * It seems okay, so make the call.
     */
    return hostCallback(idMsg, cParms, paParms);
}


/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnCall}
 *
 * @note    All functions which do not involve an unreasonable delay will be
 *          handled synchronously.  If needed, we will add a request handler
 *          thread in future for those which do.
 * @thread  HGCM
 */
/*static*/ DECLCALLBACK(void)
GstCtrlService::svcCall(void *pvService, VBOXHGCMCALLHANDLE hCall, uint32_t idClient, void *pvClient,
                        uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival)
{
    LogFlowFunc(("[Client %RU32] u32Function=%RU32 (%s), cParms=%RU32, paParms=0x%p\n",
                 idClient, u32Function, GstCtrlGuestMsgToStr((eGuestMsg)u32Function), cParms, paParms));
    RT_NOREF(tsArrival, idClient);

    /*
     * Convert opaque pointers to typed ones.
     */
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertReturnVoidStmt(pThis, pThis->mpHelpers->pfnCallComplete(hCall, VERR_INTERNAL_ERROR_5));
    ClientState *pClient = reinterpret_cast<ClientState *>(pvClient);
    AssertReturnVoidStmt(pClient, pThis->mpHelpers->pfnCallComplete(hCall, VERR_INVALID_CLIENT_ID));
    Assert(pClient->m_idClient == idClient);

    /*
     * Do the dispatching.
     */
    int rc;
    switch (u32Function)
    {
        case GUEST_MSG_MAKE_ME_MASTER:
            LogFlowFunc(("[Client %RU32] GUEST_MAKE_ME_MASTER\n", idClient));
            rc = pThis->clientMakeMeMaster(pClient, hCall, cParms);
            break;
        case GUEST_MSG_PEEK_NOWAIT:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_NOWAIT\n", idClient));
            rc = pThis->clientMsgPeek(pClient, hCall, cParms, paParms, false /*fWait*/);
            break;
        case GUEST_MSG_PEEK_WAIT:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_PEEK_WAIT\n", idClient));
            rc = pThis->clientMsgPeek(pClient, hCall, cParms, paParms, true /*fWait*/);
            break;
        case GUEST_MSG_GET:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_GET\n", idClient));
            rc = pThis->clientMsgGet(pClient, hCall, cParms, paParms);
            break;
        case GUEST_MSG_CANCEL:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_CANCEL\n", idClient));
            rc = pThis->clientMsgCancel(pClient, cParms);
            break;
        case GUEST_MSG_SKIP:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_SKIP\n", idClient));
            rc = pThis->clientMsgSkip(pClient, hCall, cParms, paParms);
            break;
        case GUEST_MSG_SESSION_PREPARE:
            LogFlowFunc(("[Client %RU32] GUEST_SESSION_PREPARE\n", idClient));
            rc = pThis->clientSessionPrepare(pClient, hCall, cParms, paParms);
            break;
        case GUEST_MSG_SESSION_CANCEL_PREPARED:
            LogFlowFunc(("[Client %RU32] GUEST_SESSION_CANCEL_PREPARED\n", idClient));
            rc = pThis->clientSessionCancelPrepared(pClient, cParms, paParms);
            break;
        case GUEST_MSG_SESSION_ACCEPT:
            LogFlowFunc(("[Client %RU32] GUEST_SESSION_ACCEPT\n", idClient));
            rc = pThis->clientSessionAccept(pClient, hCall, cParms, paParms);
            break;
        case GUEST_MSG_SESSION_CLOSE:
            LogFlowFunc(("[Client %RU32] GUEST_SESSION_CLOSE\n", idClient));
            rc = pThis->clientSessionCloseOther(pClient, cParms, paParms);
            break;

        /*
         * Stuff the goes to various main objects:
         */
        case GUEST_MSG_REPLY:
            if (cParms >= 3 && paParms[2].u.uint32 == (uint32_t)VERR_NOT_SUPPORTED)
            {
                HostMsg *pFirstMsg = RTListGetFirstCpp(&pClient->m_HostMsgList, HostMsg, m_ListEntry);
                if (pFirstMsg && pFirstMsg->m_idContext == paParms[0].u.uint32)
                    pFirstMsg->m_f60BetaHackInPlay = true;
            }
            RT_FALL_THROUGH();
        case GUEST_MSG_PROGRESS_UPDATE:
        case GUEST_MSG_SESSION_NOTIFY:
        case GUEST_MSG_EXEC_OUTPUT:
        case GUEST_MSG_EXEC_STATUS:
        case GUEST_MSG_EXEC_INPUT_STATUS:
        case GUEST_MSG_EXEC_IO_NOTIFY:
        case GUEST_MSG_DIR_NOTIFY:
        case GUEST_MSG_FILE_NOTIFY:
            LogFlowFunc(("[Client %RU32] %s\n", idClient, GstCtrlGuestMsgToStr((eGuestMsg)u32Function)));
            rc = pThis->clientToMain(pClient, u32Function /* Msg */, cParms, paParms);
            Assert(rc != VINF_HGCM_ASYNC_EXECUTE);
            break;

        /*
         * The remaining messages are here for compatibility with older Guest Additions:
         */
        case GUEST_MSG_WAIT:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_WAIT\n", idClient));
            pThis->clientMsgOldGet(pClient, hCall, cParms, paParms);
            rc = VINF_HGCM_ASYNC_EXECUTE;
            break;

        case GUEST_MSG_SKIP_OLD:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_SKIP_OLD\n", idClient));
            rc = pThis->clientMsgOldSkip(pClient, hCall, cParms);
            break;

        case GUEST_MSG_FILTER_SET:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_FILTER_SET\n", idClient));
            rc = pThis->clientMsgOldFilterSet(pClient, cParms, paParms);
            break;

        case GUEST_MSG_FILTER_UNSET:
            LogFlowFunc(("[Client %RU32] GUEST_MSG_FILTER_UNSET\n", idClient));
            rc = VERR_NOT_IMPLEMENTED;
            break;

        /*
         * Anything else shall return invalid function.
         * Note! We used to return VINF_SUCCESS for these.  See bugref:9313
         *       and Guest::i_notifyCtrlDispatcher().
         */
        default:
            ASSERT_GUEST_MSG_FAILED(("u32Function=%RU32 (%#x)\n", u32Function, u32Function));
            rc = VERR_INVALID_FUNCTION;
            break;
    }

    if (rc != VINF_HGCM_ASYNC_EXECUTE)
    {
        /* Tell the client that the call is complete (unblocks waiting). */
        LogFlowFunc(("[Client %RU32] Calling pfnCallComplete w/ rc=%Rrc\n", idClient, rc));
        AssertPtr(pThis->mpHelpers);
        pThis->mpHelpers->pfnCallComplete(hCall, rc);
    }
}


/**
 * Notifies the host (using low-level HGCM callbacks) about an event
 * which was sent from the client.
 *
 * @returns VBox status code.
 * @param   u32Function     Message ID that occured.
 * @param   cParms          Number of parameters.
 * @param   paParms         Array of parameters.
 */
int GstCtrlService::hostCallback(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    LogFlowFunc(("u32Function=%RU32 (%s), cParms=%ld, paParms=%p\n",
                 u32Function, GstCtrlGuestMsgToStr((eGuestMsg)u32Function), cParms, paParms));

    int rc;
    if (mpfnHostCallback)
    {
        VBOXGUESTCTRLHOSTCALLBACK data = { cParms, paParms };
        rc = mpfnHostCallback(mpvHostData, u32Function, &data, sizeof(data));
    }
    else
        rc = VERR_NOT_SUPPORTED;

    LogFlowFunc(("Returning rc=%Rrc\n", rc));
    return rc;
}


/**
 * Processes a message received from the host side and re-routes it to
 * a connect client on the guest.
 *
 * @returns VBox status code.
 * @param   idMsg   Message ID to process.
 * @param   cParms      Number of parameters.
 * @param   paParms     Array of parameters.
 */
int GstCtrlService::hostProcessMessage(uint32_t idMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    /*
     * If no client is connected at all we don't buffer any host messages
     * and immediately return an error to the host.  This avoids the host
     * waiting for a response from the guest side in case VBoxService on
     * the guest is not running/system is messed up somehow.
     */
    if (m_ClientStateMap.empty())
    {
        LogFlow(("GstCtrlService::hostProcessMessage: VERR_NOT_FOUND!\n"));
        return VERR_NOT_FOUND;
    }

    /*
     * Create a host message for each destination.
     * Note! There is currently only one scenario in which we send a host
     *       message to two recipients.
     */
    HostMsg *pHostMsg = new (std::nothrow) HostMsg();
    AssertReturn(pHostMsg, VERR_NO_MEMORY);
    int rc = pHostMsg->Init(idMsg, cParms, paParms);
    if (RT_SUCCESS(rc))
    {
        uint64_t const fDestinations = pHostMsg->m_idContextAndDst & VBOX_GUESTCTRL_DST_BOTH;
        HostMsg       *pHostMsg2     = NULL;
        if (fDestinations != VBOX_GUESTCTRL_DST_BOTH)
        { /* likely */ }
        else
        {
            pHostMsg2 = new (std::nothrow) HostMsg();
            if (pHostMsg2)
                rc = pHostMsg2->Init(idMsg, cParms, paParms);
            else
                rc = VERR_NO_MEMORY;
        }
        if (RT_SUCCESS(rc))
        {
            LogFlowFunc(("Handling host message m_idContextAndDst=%#RX64, idMsg=%RU32, cParms=%RU32, paParms=%p, cClients=%zu\n",
                         pHostMsg->m_idContextAndDst, idMsg, cParms, paParms, m_ClientStateMap.size()));

            /*
             * Find the message destination and post it to the client.  If the
             * session ID doesn't match any particular client it goes to the master.
             */
            AssertMsg(!m_ClientStateMap.empty(), ("Client state map is empty when it should not be!\n"));

            /* Dispatch to the session. */
            if (fDestinations & VBOX_GUESTCTRL_DST_SESSION)
            {
                uint32_t const idSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pHostMsg->m_idContext);
                ClientStateMap::iterator It = m_SessionIdMap.find(idSession);
                if (It != m_SessionIdMap.end())
                {
                    ClientState *pClient = It->second;
                    Assert(pClient->m_idSession == idSession);
                    RTListAppend(&pClient->m_HostMsgList, &pHostMsg->m_ListEntry);
                    pHostMsg  = pHostMsg2;
                    pHostMsg2 = NULL;

                    int rc2 = pClient->Wakeup();
                    LogFlowFunc(("Woke up client ID=%RU32 -> rc=%Rrc\n", pClient->m_idClient, rc2));
                    RT_NOREF(rc2);
                    rc = VINF_SUCCESS;
                }
                else
                {
                    LogFunc(("No client with session ID %u was found! (idMsg=%d %s)\n",
                             idSession, idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg)));
                    rc = !(fDestinations & VBOX_GUESTCTRL_DST_ROOT_SVC) ? VERR_NOT_FOUND : VWRN_NOT_FOUND;
                }
            }

            /* Does the message go to the root service? */
            if (   (fDestinations & VBOX_GUESTCTRL_DST_ROOT_SVC)
                && RT_SUCCESS(rc))
            {
                Assert(pHostMsg);
                if (m_pMasterClient)
                {
                    RTListAppend(&m_pMasterClient->m_HostMsgList, &pHostMsg->m_ListEntry);
                    pHostMsg = NULL;

                    int rc2 = m_pMasterClient->Wakeup();
                    LogFlowFunc(("Woke up client ID=%RU32 (master) -> rc=%Rrc\n", m_pMasterClient->m_idClient, rc2));
                    NOREF(rc2);
                }
                else
                    rc = VERR_NOT_FOUND;
            }
        }

        /* Drop unset messages. */
        if (pHostMsg2)
            pHostMsg2->Delete();
    }
    if (pHostMsg)
        pHostMsg->Delete();

    if (RT_FAILURE(rc))
        LogFunc(("Failed %Rrc (idMsg=%u, cParms=%u)\n", rc, idMsg, cParms));
    return rc;
}


/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnHostCall,
 *  Wraps to the hostProcessMessage() member function.}
 */
/*static*/ DECLCALLBACK(int)
GstCtrlService::svcHostCall(void *pvService, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
    AssertLogRelReturn(VALID_PTR(pvService), VERR_INVALID_PARAMETER);
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    LogFlowFunc(("u32Function=%RU32, cParms=%RU32, paParms=0x%p\n", u32Function, cParms, paParms));
    AssertReturn(u32Function != HOST_MSG_CANCEL_PENDING_WAITS, VERR_INVALID_FUNCTION);
    return pThis->hostProcessMessage(u32Function, cParms, paParms);
}




/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnSaveState}
 */
/*static*/ DECLCALLBACK(int)
GstCtrlService::svcSaveState(void *pvService, uint32_t idClient, void *pvClient, PSSMHANDLE pSSM)
{
    RT_NOREF(pvClient);
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    /* Note! We don't need to save the idSession here because it's only used
             for sessions and the sessions are not persistent across a state
             save/restore.  The Main objects aren't there.  Clients shuts down.
             Only the root service survives, so remember who that is and its mode. */

    SSMR3PutU32(pSSM, 1);
    SSMR3PutBool(pSSM, pThis->m_fLegacyMode);
    return SSMR3PutBool(pSSM, idClient == pThis->m_idMasterClient);
}


/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnLoadState}
 */
/*static*/ DECLCALLBACK(int)
GstCtrlService::svcLoadState(void *pvService, uint32_t idClient, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion)
{
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    ClientState *pClient = reinterpret_cast<ClientState *>(pvClient);
    AssertReturn(pClient, VERR_INVALID_CLIENT_ID);
    Assert(pClient->m_idClient == idClient);

    if (uVersion >= HGCM_SAVED_STATE_VERSION)
    {
        uint32_t uSubVersion;
        int rc = SSMR3GetU32(pSSM, &uSubVersion);
        AssertRCReturn(rc, rc);
        if (uSubVersion != 1)
            return SSMR3SetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS,
                                     "sub version %u, expected 1\n", uSubVersion);
        bool fLegacyMode;
        rc = SSMR3GetBool(pSSM, &fLegacyMode);
        AssertRCReturn(rc, rc);
        pThis->m_fLegacyMode = fLegacyMode;

        bool fIsMaster;
        rc = SSMR3GetBool(pSSM, &fIsMaster);
        AssertRCReturn(rc, rc);

        pClient->m_fIsMaster = fIsMaster;
        if (fIsMaster)
        {
            pThis->m_pMasterClient  = pClient;
            pThis->m_idMasterClient = idClient;
        }
    }
    else
    {
        /*
         * For old saved states we have to guess at who should be the master.
         * Given how HGCMService::CreateAndConnectClient and associates manage
         * and saves the client, the first client connecting will be restored
         * first.  The only time this might go wrong if the there are zombie
         * VBoxService session processes in the restored guest, and I don't
         * we need to care too much about that scenario.
         *
         * Given how HGCM first re-connects the clients before this function
         * gets called, there isn't anything we need to do here it turns out. :-)
         */
    }
    pClient->m_fRestored = true;
    return VINF_SUCCESS;
}


/**
 * @interface_method_impl{VBOXHGCMSVCFNTABLE,pfnRegisterExtension,
 *  Installs a host callback for notifications of property changes.}
 */
/*static*/ DECLCALLBACK(int) GstCtrlService::svcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension)
{
    SELF *pThis = reinterpret_cast<SELF *>(pvService);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrNullReturn(pfnExtension, VERR_INVALID_POINTER);

    pThis->mpfnHostCallback = pfnExtension;
    pThis->mpvHostData      = pvExtension;
    return VINF_SUCCESS;
}


/**
 * @copydoc VBOXHGCMSVCLOAD
 */
extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable)
{
    int rc = VINF_SUCCESS;

    LogFlowFunc(("pTable=%p\n", pTable));

    if (!VALID_PTR(pTable))
    {
        rc = VERR_INVALID_PARAMETER;
    }
    else
    {
        LogFlowFunc(("pTable->cbSize=%d, pTable->u32Version=0x%08X\n", pTable->cbSize, pTable->u32Version));

        if (   pTable->cbSize != sizeof (VBOXHGCMSVCFNTABLE)
            || pTable->u32Version != VBOX_HGCM_SVC_VERSION)
        {
            rc = VERR_VERSION_MISMATCH;
        }
        else
        {
            GstCtrlService *pService = NULL;
            /* No exceptions may propagate outside. */
            try
            {
                pService = new GstCtrlService(pTable->pHelpers);
            }
            catch (int rcThrown)
            {
                rc = rcThrown;
            }
            catch(std::bad_alloc &)
            {
                rc = VERR_NO_MEMORY;
            }

            if (RT_SUCCESS(rc))
            {
                /*
                 * We don't need an additional client data area on the host,
                 * because we're a class which can have members for that :-).
                 */
                pTable->cbClient = sizeof(ClientState);

                /* Register functions. */
                pTable->pfnUnload               = GstCtrlService::svcUnload;
                pTable->pfnConnect              = GstCtrlService::svcConnect;
                pTable->pfnDisconnect           = GstCtrlService::svcDisconnect;
                pTable->pfnCall                 = GstCtrlService::svcCall;
                pTable->pfnHostCall             = GstCtrlService::svcHostCall;
                pTable->pfnSaveState            = GstCtrlService::svcSaveState;
                pTable->pfnLoadState            = GstCtrlService::svcLoadState;
                pTable->pfnRegisterExtension    = GstCtrlService::svcRegisterExtension;
                pTable->pfnNotify               = NULL;

                /* Service specific initialization. */
                pTable->pvService = pService;
            }
            else
            {
                if (pService)
                {
                    delete pService;
                    pService = NULL;
                }
            }
        }
    }

    LogFlowFunc(("Returning %Rrc\n", rc));
    return rc;
}

