Index: trunk/src/VBox/HostServices/testcase/TstHGCMMock.cpp
===================================================================
--- trunk/src/VBox/HostServices/testcase/TstHGCMMock.cpp	(revision 98576)
+++ trunk/src/VBox/HostServices/testcase/TstHGCMMock.cpp	(revision 98576)
@@ -0,0 +1,654 @@
+/* $Id$ */
+/** @file
+ * TstHGCMMock.cpp - Mocking framework for testing HGCM-based host services.
+ *
+ * The goal is to run host service + Vbgl code as unmodified as possible as
+ * part of testcases to gain test coverage which otherwise wouldn't possible
+ * for heavily user-centric features like Shared Clipboard or drag'n drop (DnD).
+ *
+ * Currently, though, it's only the service that runs unmodified, the
+ * testcases does custom Vbgl work.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#include <VBox/GuestHost/HGCMMock.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+#include <iprt/test.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/hgcmsvc.h>
+
+
+/*********************************************************************************************************************************
+*   Global Variables                                                                                                             *
+*********************************************************************************************************************************/
+/** Static HGCM service to mock. */
+static TSTHGCMMOCKSVC g_tstHgcmSvc;
+
+
+/*********************************************************************************************************************************
+*   Internal functions                                                                                                           *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes a HGCM mock client.
+ *
+ * @return VBox status code.
+ * @param  pClient              Client instance to initialize.
+ * @param  idClient             HGCM client ID to assign.
+ * @param  cbClient             Size (in bytes) of service-specific (opaque) client data to allocate.
+ */
+static int tstHgcmMockClientInit(PTSTHGCMMOCKCLIENT pClient, uint32_t idClient, size_t cbClient)
+{
+    RT_BZERO(pClient, sizeof(TSTHGCMMOCKCLIENT));
+
+    pClient->idClient = idClient;
+    if (cbClient)
+    {
+        pClient->pvClient = RTMemAllocZ(cbClient);
+        AssertPtrReturn(pClient->pvClient, VERR_NO_MEMORY);
+        pClient->cbClient = cbClient;
+    }
+
+    return RTSemEventCreate(&pClient->hEvent);
+}
+
+/**
+ * Destroys a HGCM mock client.
+ *
+ * @return VBox status code.
+ * @param  pClient              Client instance to destroy.
+ */
+static int tstHgcmMockClientDestroy(PTSTHGCMMOCKCLIENT pClient)
+{
+    int rc = RTSemEventDestroy(pClient->hEvent);
+    if (RT_SUCCESS(rc))
+    {
+        if (pClient->pvClient)
+        {
+            Assert(pClient->cbClient);
+            RTMemFree(pClient->pvClient);
+            pClient->pvClient = NULL;
+            pClient->cbClient = 0;
+        }
+
+        pClient->hEvent = NIL_RTSEMEVENT;
+    }
+
+    return rc;
+}
+
+/** @copydoc VBOXHGCMSVCFNTABLE::pfnConnect */
+static DECLCALLBACK(int) tstHgcmMockSvcConnect(PTSTHGCMMOCKSVC pSvc, void *pvService, uint32_t *pidClient)
+{
+    RT_NOREF(pvService);
+
+    PTSTHGCMMOCKFN pFn = (PTSTHGCMMOCKFN)RTMemAllocZ(sizeof(TSTHGCMMOCKFN));
+    AssertPtrReturn(pFn, VERR_NO_MEMORY);
+
+    PTSTHGCMMOCKCLIENT pClient = &pSvc->aHgcmClient[pSvc->uNextClientId];
+
+    int rc = tstHgcmMockClientInit(pClient, pSvc->uNextClientId, pSvc->fnTable.cbClient);
+    if (RT_FAILURE(rc))
+        return rc;
+
+    pFn->enmType = TSTHGCMMOCKFNTYPE_CONNECT;
+    pFn->pClient = pClient;
+
+    RTListAppend(&pSvc->lstCall, &pFn->Node);
+    pFn = NULL; /* Thread takes ownership now. */
+
+    int rc2 = RTSemEventSignal(pSvc->hEventQueue);
+    AssertRCReturn(rc2, rc2);
+    rc2 = RTSemEventWait(pClient->hEvent, RT_MS_30SEC);
+    AssertRCReturn(rc2, rc2);
+
+    ASMAtomicIncU32(&pSvc->uNextClientId);
+
+    rc2 = RTSemEventSignal(pSvc->hEventConnect);
+    AssertRCReturn(rc2, rc2);
+
+    *pidClient = pClient->idClient;
+
+    return VINF_SUCCESS;
+}
+
+/** @copydoc VBOXHGCMSVCFNTABLE::pfnDisconnect */
+static DECLCALLBACK(int) tstHgcmMockSvcDisconnect(PTSTHGCMMOCKSVC pSvc, void *pvService, uint32_t idClient)
+{
+    RT_NOREF(pvService);
+
+    PTSTHGCMMOCKCLIENT pClient = &pSvc->aHgcmClient[idClient];
+
+    PTSTHGCMMOCKFN pFn = (PTSTHGCMMOCKFN)RTMemAllocZ(sizeof(TSTHGCMMOCKFN));
+    AssertPtrReturn(pFn, VERR_NO_MEMORY);
+
+    pFn->enmType = TSTHGCMMOCKFNTYPE_DISCONNECT;
+    pFn->pClient = pClient;
+
+    RTListAppend(&pSvc->lstCall, &pFn->Node);
+    pFn = NULL; /* Thread takes ownership now. */
+
+    int rc2 = RTSemEventSignal(pSvc->hEventQueue);
+    AssertRCReturn(rc2, rc2);
+
+    rc2 = RTSemEventWait(pClient->hEvent, RT_MS_30SEC);
+    AssertRCReturn(rc2, rc2);
+
+    return tstHgcmMockClientDestroy(pClient);
+}
+
+/** @copydoc VBOXHGCMSVCFNTABLE::pfnCall */
+static DECLCALLBACK(int) tstHgcmMockSvcCall(PTSTHGCMMOCKSVC pSvc, void *pvService, VBOXHGCMCALLHANDLE callHandle, uint32_t idClient, void *pvClient,
+                                            int32_t function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
+{
+    RT_NOREF(pvService, pvClient);
+
+    PTSTHGCMMOCKCLIENT pClient = &pSvc->aHgcmClient[idClient];
+
+    PTSTHGCMMOCKFN pFn = (PTSTHGCMMOCKFN)RTMemAllocZ(sizeof(TSTHGCMMOCKFN));
+    AssertPtrReturn(pFn, VERR_NO_MEMORY);
+
+    const size_t cbParms = cParms * sizeof(VBOXHGCMSVCPARM);
+
+    pFn->enmType         = TSTHGCMMOCKFNTYPE_CALL;
+    pFn->pClient         = pClient;
+
+    pFn->u.Call.hCall    = callHandle;
+    pFn->u.Call.iFunc    = function;
+    PVBOXHGCMSVCPARM const paParmsCopy = (PVBOXHGCMSVCPARM)RTMemDup(paParms, cbParms);
+    pFn->u.Call.pParms   = paParmsCopy;
+    AssertPtrReturn(pFn->u.Call.pParms, VERR_NO_MEMORY);
+    pFn->u.Call.cParms   = cParms;
+
+    RTListAppend(&pSvc->lstCall, &pFn->Node);
+
+    int rc2 = RTSemEventSignal(pSvc->hEventQueue);
+    AssertRCReturn(rc2, rc2);
+
+    rc2 = RTSemEventWait(pClient->hEvent, RT_INDEFINITE_WAIT);
+    AssertRCReturn(rc2, rc2);
+
+    memcpy(paParms, paParmsCopy, cbParms);
+    /** @todo  paParmsCopy is leaked, right? Doesn't appear to be a
+     *         use-after-free here. (pFn is freeded though) */
+
+    return VINF_SUCCESS; /** @todo Return host call rc */
+}
+
+/** @copydoc VBOXHGCMSVCFNTABLE::pfnHostCall
+ * @note Public for also being able to test host calls via testcases. */
+int TstHgcmMockSvcHostCall(PTSTHGCMMOCKSVC pSvc, void *pvService, int32_t function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
+{
+    RT_NOREF(pvService);
+    AssertReturn(pSvc->cHostCallers == 0, VERR_WRONG_ORDER); /* Only one host call at a time. */
+
+    pSvc->cHostCallers++;
+
+    PTSTHGCMMOCKFN pFn = (PTSTHGCMMOCKFN)RTMemAllocZ(sizeof(TSTHGCMMOCKFN));
+    AssertPtrReturn(pFn, VERR_INVALID_POINTER);
+
+    pFn->enmType           = TSTHGCMMOCKFNTYPE_HOST_CALL;
+    pFn->u.HostCall.iFunc  = function;
+    if (cParms)
+    {
+        pFn->u.HostCall.pParms = (PVBOXHGCMSVCPARM)RTMemDup(paParms, cParms * sizeof(VBOXHGCMSVCPARM));
+        AssertPtrReturn(pFn->u.HostCall.pParms, VERR_NO_MEMORY);
+        pFn->u.HostCall.cParms = cParms;
+    }
+
+    RTListAppend(&pSvc->lstCall, &pFn->Node);
+    pFn = NULL; /* Thread takes ownership now. */
+
+    int rc2 = RTSemEventSignal(pSvc->hEventQueue);
+    AssertRC(rc2);
+
+    rc2 = RTSemEventWait(pSvc->hEventHostCall, RT_INDEFINITE_WAIT);
+    AssertRCReturn(rc2, rc2);
+
+    Assert(pSvc->cHostCallers);
+    pSvc->cHostCallers--;
+
+    return pSvc->rcHostCall;
+}
+
+/**
+ * Call completion callback for guest calls.
+ *
+ * @return VBox status code.
+ * @param  callHandle           Call handle to complete.
+ * @param  rc                   Return code to return to the caller.
+ */
+static DECLCALLBACK(int) tstHgcmMockSvcCallComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc)
+{
+    PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+    size_t i = 0;
+    for (; RT_ELEMENTS(pSvc->aHgcmClient); i++)
+    {
+        PTSTHGCMMOCKCLIENT pClient = &pSvc->aHgcmClient[i];
+        if (&pClient->hCall == callHandle) /* Slow, but works for now. */
+        {
+            if (rc == VINF_HGCM_ASYNC_EXECUTE)
+            {
+                Assert(pClient->fAsyncExec == false);
+            }
+            else /* Complete call + notify client. */
+            {
+                callHandle->rc = rc;
+
+                int rc2 = RTSemEventSignal(pClient->hEvent);
+                AssertRCReturn(rc2, rc2);
+            }
+
+            return VINF_SUCCESS;
+        }
+    }
+
+    return VERR_NOT_FOUND;
+}
+
+/**
+ * Main thread of HGCM mock service.
+ *
+ * @return VBox status code.
+ * @param  hThread              Thread handle.
+ * @param  pvUser               User-supplied data of type PTSTHGCMMOCKSVC.
+ */
+static DECLCALLBACK(int) tstHgcmMockSvcThread(RTTHREAD hThread, void *pvUser)
+{
+    RT_NOREF(hThread);
+    PTSTHGCMMOCKSVC pSvc = (PTSTHGCMMOCKSVC)pvUser;
+
+    pSvc->uNextClientId      = 0;
+
+    pSvc->fnTable.cbSize     = sizeof(pSvc->fnTable);
+    pSvc->fnTable.u32Version = VBOX_HGCM_SVC_VERSION;
+
+    RT_ZERO(pSvc->fnHelpers);
+    pSvc->fnHelpers.pfnCallComplete = tstHgcmMockSvcCallComplete;
+    pSvc->fnTable.pHelpers          = &pSvc->fnHelpers;
+
+    int rc = VBoxHGCMSvcLoad(&pSvc->fnTable);
+    if (RT_SUCCESS(rc))
+    {
+        RTThreadUserSignal(hThread);
+
+        for (;;)
+        {
+            rc = RTSemEventWait(pSvc->hEventQueue, 10 /* ms */);
+            if (ASMAtomicReadBool(&pSvc->fShutdown))
+            {
+                rc = VINF_SUCCESS;
+                break;
+            }
+            if (rc == VERR_TIMEOUT)
+                continue;
+
+            PTSTHGCMMOCKFN pFn = RTListGetFirst(&pSvc->lstCall, TSTHGCMMOCKFN, Node);
+            if (pFn)
+            {
+                switch (pFn->enmType)
+                {
+                    case TSTHGCMMOCKFNTYPE_CONNECT:
+                    {
+                        rc = pSvc->fnTable.pfnConnect(pSvc->fnTable.pvService,
+                                                      pFn->pClient->idClient, pFn->pClient->pvClient,
+                                                      VMMDEV_REQUESTOR_USR_NOT_GIVEN /* fRequestor */, false /* fRestoring */);
+
+                        int rc2 = RTSemEventSignal(pFn->pClient->hEvent);
+                        AssertRC(rc2);
+                        break;
+                    }
+
+                    case TSTHGCMMOCKFNTYPE_DISCONNECT:
+                    {
+                        rc = pSvc->fnTable.pfnDisconnect(pSvc->fnTable.pvService,
+                                                         pFn->pClient->idClient, pFn->pClient->pvClient);
+
+                        int rc2 = RTSemEventSignal(pFn->pClient->hEvent);
+                        AssertRC(rc2);
+                        break;
+                    }
+
+                    case TSTHGCMMOCKFNTYPE_CALL:
+                    {
+                        pSvc->fnTable.pfnCall(pSvc->fnTable.pvService, pFn->u.Call.hCall, pFn->pClient->idClient,
+                                              pFn->pClient->pvClient, pFn->u.Call.iFunc, pFn->u.Call.cParms,
+                                              pFn->u.Call.pParms, RTTimeNanoTS());
+
+                        /* Note: Call will be completed in the call completion callback. */
+                        break;
+                    }
+
+                    case TSTHGCMMOCKFNTYPE_HOST_CALL:
+                    {
+                        pSvc->rcHostCall = pSvc->fnTable.pfnHostCall(pSvc->fnTable.pvService, pFn->u.HostCall.iFunc,
+                                                                     pFn->u.HostCall.cParms, pFn->u.HostCall.pParms);
+
+                        int rc2 = RTSemEventSignal(pSvc->hEventHostCall);
+                        AssertRC(rc2);
+                        break;
+                    }
+
+                    default:
+                        AssertFailed();
+                        break;
+                }
+                RTListNodeRemove(&pFn->Node);
+                RTMemFree(pFn);  /* Note! caller frees the parameters? Or at least it uses them again. */
+            }
+        }
+    }
+
+    return rc;
+}
+
+
+/*********************************************************************************************************************************
+*   Public functions                                                                                                             *
+*********************************************************************************************************************************/
+
+/**
+ * Returns the pointer to the HGCM mock service instance.
+ *
+ * @return Pointer to HGCM mock service instance.
+ */
+PTSTHGCMMOCKSVC TstHgcmMockSvcInst(void)
+{
+    return &g_tstHgcmSvc;
+}
+
+/**
+ * Waits for a HGCM mock client to connect, extended version.
+ *
+ * @return Pointer to connected client, or NULL if ran into timeout.
+ * @param  pSvc                 HGCM mock service instance.
+ * @param  msTimeout            Timeout (in ms) to wait for connection.
+ */
+PTSTHGCMMOCKCLIENT TstHgcmMockSvcWaitForConnectEx(PTSTHGCMMOCKSVC pSvc, RTMSINTERVAL msTimeout)
+{
+    int rc = RTSemEventWait(pSvc->hEventConnect, msTimeout);
+    if (RT_SUCCESS(rc))
+    {
+        Assert(pSvc->uNextClientId);
+        return &pSvc->aHgcmClient[pSvc->uNextClientId - 1];
+    }
+    return NULL;
+}
+
+/**
+ * Waits for a HGCM mock client to connect.
+ *
+ * @return Pointer to connected client, or NULL if waiting for connection was aborted.
+ * @param  pSvc                 HGCM mock service instance.
+ */
+PTSTHGCMMOCKCLIENT TstHgcmMockSvcWaitForConnect(PTSTHGCMMOCKSVC pSvc)
+{
+    return TstHgcmMockSvcWaitForConnectEx(pSvc, RT_MS_30SEC);
+}
+
+/**
+ * Creates a HGCM mock service instance.
+ *
+ * @return VBox status code.
+ * @param  pSvc                 HGCM mock service instance to create.
+ */
+int TstHgcmMockSvcCreate(PTSTHGCMMOCKSVC pSvc)
+{
+    RT_ZERO(pSvc->aHgcmClient);
+    pSvc->fShutdown = false;
+    int rc = RTSemEventCreate(&pSvc->hEventQueue);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTSemEventCreate(&pSvc->hEventHostCall);
+        if (RT_SUCCESS(rc))
+        {
+            rc = RTSemEventCreate(&pSvc->hEventConnect);
+            if (RT_SUCCESS(rc))
+            {
+                RTListInit(&pSvc->lstCall);
+            }
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Destroys a HGCM mock service instance.
+ *
+ * @return VBox status code.
+ * @param  pSvc                 HGCM mock service instance to destroy.
+ */
+int TstHgcmMockSvcDestroy(PTSTHGCMMOCKSVC pSvc)
+{
+    int rc = RTSemEventDestroy(pSvc->hEventQueue);
+    if (RT_SUCCESS(rc))
+    {
+        rc = RTSemEventDestroy(pSvc->hEventHostCall);
+        if (RT_SUCCESS(rc))
+            RTSemEventDestroy(pSvc->hEventConnect);
+    }
+    return rc;
+}
+
+/**
+ * Starts a HGCM mock service instance.
+ *
+ * @return VBox status code.
+ * @param  pSvc                 HGCM mock service instance to start.
+ */
+int TstHgcmMockSvcStart(PTSTHGCMMOCKSVC pSvc)
+{
+    int rc = RTThreadCreate(&pSvc->hThread, tstHgcmMockSvcThread, pSvc, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
+                            "MockSvc");
+    if (RT_SUCCESS(rc))
+        rc = RTThreadUserWait(pSvc->hThread, RT_MS_30SEC);
+
+    return rc;
+}
+
+/**
+ * Stops a HGCM mock service instance.
+ *
+ * @return VBox status code.
+ * @param  pSvc                 HGCM mock service instance to stop.
+ */
+int TstHgcmMockSvcStop(PTSTHGCMMOCKSVC pSvc)
+{
+    ASMAtomicWriteBool(&pSvc->fShutdown, true);
+
+    int rcThread;
+    int rc = RTThreadWait(pSvc->hThread, RT_MS_30SEC, &rcThread);
+    if (RT_SUCCESS(rc))
+        rc = rcThread;
+    if (RT_SUCCESS(rc))
+        pSvc->hThread = NIL_RTTHREAD;
+
+    return rc;
+}
+
+
+/*********************************************************************************************************************************
+*   VbglR3 stubs                                                                                                                 *
+*********************************************************************************************************************************/
+
+/**
+ * Connects to an HGCM mock service.
+ *
+ * @returns VBox status code
+ * @param   pszServiceName  Name of the host service.
+ * @param   pidClient       Where to put the client ID on success. The client ID
+ *                          must be passed to all the other calls to the service.
+ */
+VBGLR3DECL(int) VbglR3HGCMConnect(const char *pszServiceName, HGCMCLIENTID *pidClient)
+{
+    RT_NOREF(pszServiceName);
+
+    PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+    return tstHgcmMockSvcConnect(pSvc, pSvc->fnTable.pvService, pidClient);
+}
+
+/**
+ * Disconnect from an HGCM mock service.
+ *
+ * @returns VBox status code.
+ * @param   idClient        The client id returned by VbglR3HGCMConnect().
+ */
+VBGLR3DECL(int) VbglR3HGCMDisconnect(HGCMCLIENTID idClient)
+{
+    PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+    return tstHgcmMockSvcDisconnect(pSvc, pSvc->fnTable.pvService, idClient);
+}
+
+/**
+ * Query the session ID of the mocked VM.
+ *
+ * @returns IPRT status code.
+ * @param   pu64IdSession       Session id (out).
+ */
+VBGLR3DECL(int) VbglR3GetSessionId(uint64_t *pu64IdSession)
+{
+    if (pu64IdSession)
+        *pu64IdSession = 42;
+    return VINF_SUCCESS;
+}
+
+/**
+ * Makes a fully prepared HGCM call to an HGCM mock service.
+ *
+ * @returns VBox status code.
+ * @param   pInfo           Fully prepared HGCM call info.
+ * @param   cbInfo          Size of the info.  This may sometimes be larger than
+ *                          what the parameter count indicates because of
+ *                          parameter changes between versions and such.
+ */
+VBGLR3DECL(int) VbglR3HGCMCall(PVBGLIOCHGCMCALL pInfo, size_t cbInfo)
+{
+    RT_NOREF(cbInfo);
+
+    AssertMsg(pInfo->Hdr.cbIn  == cbInfo, ("cbIn=%#x cbInfo=%#zx\n", pInfo->Hdr.cbIn, cbInfo));
+    AssertMsg(pInfo->Hdr.cbOut == cbInfo, ("cbOut=%#x cbInfo=%#zx\n", pInfo->Hdr.cbOut, cbInfo));
+    Assert(sizeof(*pInfo) + pInfo->cParms * sizeof(HGCMFunctionParameter) <= cbInfo);
+
+    HGCMFunctionParameter *offSrcParms = VBGL_HGCM_GET_CALL_PARMS(pInfo);
+    PVBOXHGCMSVCPARM       paDstParms  = (PVBOXHGCMSVCPARM)RTMemAlloc(pInfo->cParms * sizeof(VBOXHGCMSVCPARM));
+
+    uint16_t i = 0;
+    for (; i < pInfo->cParms; i++)
+    {
+        switch (offSrcParms->type)
+        {
+            case VMMDevHGCMParmType_32bit:
+            {
+                paDstParms[i].type     = VBOX_HGCM_SVC_PARM_32BIT;
+                paDstParms[i].u.uint32 = offSrcParms->u.value32;
+                break;
+            }
+
+            case VMMDevHGCMParmType_64bit:
+            {
+                paDstParms[i].type     = VBOX_HGCM_SVC_PARM_64BIT;
+                paDstParms[i].u.uint64 = offSrcParms->u.value64;
+                break;
+            }
+
+            case VMMDevHGCMParmType_LinAddr:
+            {
+                paDstParms[i].type           = VBOX_HGCM_SVC_PARM_PTR;
+                paDstParms[i].u.pointer.addr = (void *)offSrcParms->u.LinAddr.uAddr;
+                paDstParms[i].u.pointer.size = offSrcParms->u.LinAddr.cb;
+                break;
+            }
+
+            default:
+                AssertFailed();
+                break;
+        }
+
+        offSrcParms++;
+    }
+
+    PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst();
+
+    int rc2 = tstHgcmMockSvcCall(pSvc, pSvc->fnTable.pvService, &pSvc->aHgcmClient[pInfo->u32ClientID].hCall,
+                                 pInfo->u32ClientID, pSvc->aHgcmClient[pInfo->u32ClientID].pvClient,
+                                 pInfo->u32Function, pInfo->cParms, paDstParms);
+    if (RT_SUCCESS(rc2))
+    {
+        offSrcParms = VBGL_HGCM_GET_CALL_PARMS(pInfo);
+
+        for (i = 0; i < pInfo->cParms; i++)
+        {
+            paDstParms[i].type = offSrcParms->type;
+            switch (paDstParms[i].type)
+            {
+                case VMMDevHGCMParmType_32bit:
+                    offSrcParms->u.value32 = paDstParms[i].u.uint32;
+                    break;
+
+                case VMMDevHGCMParmType_64bit:
+                    offSrcParms->u.value64 = paDstParms[i].u.uint64;
+                    break;
+
+                case VMMDevHGCMParmType_LinAddr:
+                {
+                    offSrcParms->u.LinAddr.cb = paDstParms[i].u.pointer.size;
+                    break;
+                }
+
+                default:
+                    AssertFailed();
+                    break;
+            }
+
+            offSrcParms++;
+        }
+    }
+
+    RTMemFree(paDstParms);
+
+    if (RT_SUCCESS(rc2))
+        rc2 = pSvc->aHgcmClient[pInfo->u32ClientID].hCall.rc;
+
+    return rc2;
+}
+
Index: trunk/src/VBox/HostServices/testcase/TstHGCMMockUtils.cpp
===================================================================
--- trunk/src/VBox/HostServices/testcase/TstHGCMMockUtils.cpp	(revision 98576)
+++ trunk/src/VBox/HostServices/testcase/TstHGCMMockUtils.cpp	(revision 98576)
@@ -0,0 +1,291 @@
+/* $Id$ */
+/** @file
+ * TstHGCMMockUtils.cpp - Utility functions for the HGCM Mocking framework.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#include <VBox/GuestHost/HGCMMockUtils.h>
+
+#include <iprt/err.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/types.h>
+
+
+/*********************************************************************************************************************************
+*   Context                                                                                                                      *
+*********************************************************************************************************************************/
+/**
+ * Initializes a HGCM Mock utils context.
+ *
+ * @param   pCtx                Context to intiialize.
+ * @param   pSvc                HGCM Mock service instance to use.
+ */
+void TstHGCMUtilsCtxInit(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKSVC pSvc)
+{
+    RT_BZERO(pCtx, sizeof(TSTHGCMUTILSCTX));
+
+    pCtx->pSvc = pSvc;
+}
+
+
+/*********************************************************************************************************************************
+*   Tasks                                                                                                                        *
+*********************************************************************************************************************************/
+/**
+ * Returns the current task of a HGCM Mock utils context.
+ *
+ * @returns Current task of a HGCM Mock utils context. NULL if no current task found.
+ * @param   pCtx                HGCM Mock utils context.
+ */
+PTSTHGCMUTILSTASK TstHGCMUtilsTaskGetCurrent(PTSTHGCMUTILSCTX pCtx)
+{
+    /* Currently we only support one task at a time. */
+    return &pCtx->Task;
+}
+
+/**
+ * Initializes a HGCM Mock utils task.
+ *
+ * @returns VBox status code.
+ * @param   pTask               Task to initialize.
+ */
+int TstHGCMUtilsTaskInit(PTSTHGCMUTILSTASK pTask)
+{
+    pTask->pvUser = NULL;
+    pTask->rcCompleted = pTask->rcExpected = VERR_IPE_UNINITIALIZED_STATUS;
+    return RTSemEventCreate(&pTask->hEvent);
+}
+
+/**
+ * Destroys a HGCM Mock utils task.
+ *
+ * @returns VBox status code.
+ * @param   pTask               Task to destroy.
+ */
+void TstHGCMUtilsTaskDestroy(PTSTHGCMUTILSTASK pTask)
+{
+    RTSemEventDestroy(pTask->hEvent);
+}
+
+/**
+ * Waits for a HGCM Mock utils task to complete.
+ *
+ * @returns VBox status code.
+ * @param   pTask               Task to wait for.
+ * @param   msTimeout           Timeout (in ms) to wait.
+ */
+int TstHGCMUtilsTaskWait(PTSTHGCMUTILSTASK pTask, RTMSINTERVAL msTimeout)
+{
+    return RTSemEventWait(pTask->hEvent, msTimeout);
+}
+
+/**
+ * Returns if the HGCM Mock utils task has been completed successfully.
+ *
+ * @returns \c true if successful, \c false if not.
+ * @param   pTask               Task to check.
+ */
+bool TstHGCMUtilsTaskOk(PTSTHGCMUTILSTASK pTask)
+{
+    return pTask->rcCompleted == pTask->rcExpected;
+}
+
+/**
+ * Returns if the HGCM Mock utils task has been completed (failed or succeeded).
+ *
+ * @returns \c true if completed, \c false if (still) running.
+ * @param   pTask               Task to check.
+ */
+bool TstHGCMUtilsTaskCompleted(PTSTHGCMUTILSTASK pTask)
+{
+    return pTask->rcCompleted != VERR_IPE_UNINITIALIZED_STATUS;
+}
+
+/**
+ * Signals a HGCM Mock utils task to complete its operation.
+ *
+ * @param   pTask               Task to complete.
+ * @param   rc                  Task result to set for completion.
+ */
+void TstHGCMUtilsTaskSignal(PTSTHGCMUTILSTASK pTask, int rc)
+{
+    AssertMsg(pTask->rcCompleted == VERR_IPE_UNINITIALIZED_STATUS, ("Task already completed\n"));
+    pTask->rcCompleted = rc;
+    int rc2 = RTSemEventSignal(pTask->hEvent);
+    AssertRC(rc2);
+}
+
+
+/*********************************************************************************************************************************
+ * Threading                                                                                                                     *
+ ********************************************************************************************************************************/
+
+/**
+ * Thread worker for the guest side thread.
+ *
+ * @returns VBox status code.
+ * @param   hThread             Thread handle.
+ * @param   pvUser              Pointer of type PTSTHGCMUTILSCTX.
+ *
+ * @note    Runs in the guest thread.
+ */
+static DECLCALLBACK(int) tstHGCMUtilsGuestThread(RTTHREAD hThread, void *pvUser)
+{
+    RT_NOREF(hThread);
+    PTSTHGCMUTILSCTX pCtx = (PTSTHGCMUTILSCTX)pvUser;
+    AssertPtr(pCtx);
+
+    RTThreadUserSignal(hThread);
+
+    if (pCtx->Guest.pfnThread)
+        return pCtx->Guest.pfnThread(pCtx, pCtx->Guest.pvUser);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Starts the guest side thread.
+ *
+ * @returns VBox status code.
+ * @param   pCtx                HGCM Mock utils context to start guest thread for.
+ * @param   pFnThread           Pointer to custom thread worker function to call within the guest side thread.
+ * @param   pvUser              User-supplied pointer to guest thread context data. Optional and can be NULL.
+ */
+int TstHGCMUtilsGuestThreadStart(PTSTHGCMUTILSCTX pCtx, PFNTSTHGCMUTILSTHREAD pFnThread, void *pvUser)
+{
+    pCtx->Guest.pfnThread = pFnThread;
+    pCtx->Guest.pvUser    = pvUser;
+
+    int rc = RTThreadCreate(&pCtx->Guest.hThread, tstHGCMUtilsGuestThread, pCtx, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
+                            "tstShClGst");
+    if (RT_SUCCESS(rc))
+        rc = RTThreadUserWait(pCtx->Guest.hThread, RT_MS_30SEC);
+
+    return rc;
+}
+
+/**
+ * Stops the guest side thread.
+ *
+ * @returns VBox status code.
+ * @param   pCtx                HGCM Mock utils context to stop guest thread for.
+ */
+int TstHGCMUtilsGuestThreadStop(PTSTHGCMUTILSCTX pCtx)
+{
+    ASMAtomicWriteBool(&pCtx->Guest.fShutdown, true);
+
+    int rcThread;
+    int rc = RTThreadWait(pCtx->Guest.hThread, RT_MS_30SEC, &rcThread);
+    if (RT_SUCCESS(rc))
+        rc = rcThread;
+    if (RT_SUCCESS(rc))
+        pCtx->Guest.hThread = NIL_RTTHREAD;
+
+    return rc;
+}
+
+/**
+ * Thread worker function for the host side HGCM service.
+ *
+ * @returns VBox status code.
+ * @param   hThread             Thread handle.
+ * @param   pvUser              Pointer of type PTSTHGCMUTILSCTX.
+ *
+ * @note    Runs in the host service thread.
+ */
+static DECLCALLBACK(int) tstHGCMUtilsHostThreadWorker(RTTHREAD hThread, void *pvUser)
+{
+    RT_NOREF(hThread);
+    PTSTHGCMUTILSCTX pCtx = (PTSTHGCMUTILSCTX)pvUser;
+    AssertPtr(pCtx);
+
+    int rc = VINF_SUCCESS;
+
+    RTThreadUserSignal(hThread);
+
+    PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst();
+
+    for (;;)
+    {
+        if (ASMAtomicReadBool(&pCtx->Host.fShutdown))
+            break;
+
+        /* Wait for a new (mock) HGCM client to connect. */
+        PTSTHGCMMOCKCLIENT pMockClient = TstHgcmMockSvcWaitForConnectEx(pSvc, 100 /* ms */);
+        if (pMockClient) /* Might be NULL when timed out. */
+        {
+            if (pCtx->Host.Callbacks.pfnOnClientConnected)
+                /* ignore rc */ pCtx->Host.Callbacks.pfnOnClientConnected(pCtx, pMockClient, pCtx->Host.pvUser);
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Starts the host side thread.
+ *
+ * @returns VBox status code.
+ * @param   pCtx                HGCM Mock utils context to start host thread for.
+ * @param   pCallbacks          Pointer to host callback table to use.
+ * @param   pvUser              User-supplied pointer to reach into the host thread callbacks.
+ */
+int TstHGCMUtilsHostThreadStart(PTSTHGCMUTILSCTX pCtx, PTSTHGCMUTILSHOSTCALLBACKS pCallbacks, void *pvUser)
+{
+    memcpy(&pCtx->Host.Callbacks, pCallbacks, sizeof(TSTHGCMUTILSHOSTCALLBACKS));
+    pCtx->Host.pvUser = pvUser;
+
+    int rc = RTThreadCreate(&pCtx->Host.hThread, tstHGCMUtilsHostThreadWorker, pCtx, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
+                            "tstShClHst");
+    if (RT_SUCCESS(rc))
+        rc = RTThreadUserWait(pCtx->Host.hThread, RT_MS_30SEC);
+
+    return rc;
+}
+
+/**
+ * Stops the host side thread.
+ *
+ * @returns VBox status code.
+ * @param   pCtx                HGCM Mock utils context to stop host thread for.
+ */
+int TstHGCMUtilsHostThreadStop(PTSTHGCMUTILSCTX pCtx)
+{
+    ASMAtomicWriteBool(&pCtx->Host.fShutdown, true);
+
+    int rcThread;
+    int rc = RTThreadWait(pCtx->Host.hThread, RT_MS_30SEC, &rcThread);
+    if (RT_SUCCESS(rc))
+        rc = rcThread;
+    if (RT_SUCCESS(rc))
+        pCtx->Host.hThread = NIL_RTTHREAD;
+
+    return rc;
+}
+
