summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp735
1 files changed, 735 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp
new file mode 100644
index 00000000..ec35905c
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp
@@ -0,0 +1,735 @@
+/* $Id: tstClipboardMockHGCM.cpp $ */
+/** @file
+ * Shared Clipboard host service test case.
+ */
+
+/*
+ * Copyright (C) 2011-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
+ */
+
+#include "../VBoxSharedClipboardSvc-internal.h"
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/VBoxGuestLib.h>
+#ifdef RT_OS_LINUX
+# include <VBox/GuestHost/SharedClipboard-x11.h>
+#endif
+#ifdef RT_OS_WINDOWS
+# include <VBox/GuestHost/SharedClipboard-win.h>
+#endif
+
+#include <VBox/GuestHost/HGCMMock.h>
+#include <VBox/GuestHost/HGCMMockUtils.h>
+
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/utf16.h>
+
+
+/*********************************************************************************************************************************
+* Static globals *
+*********************************************************************************************************************************/
+static RTTEST g_hTest;
+
+
+/*********************************************************************************************************************************
+* Shared Clipboard testing *
+*********************************************************************************************************************************/
+struct CLIPBOARDTESTDESC;
+/** Pointer to a test description. */
+typedef CLIPBOARDTESTDESC *PTESTDESC;
+
+struct CLIPBOARDTESTCTX;
+/** Pointer to a test context. */
+typedef CLIPBOARDTESTCTX *PCLIPBOARDTESTCTX;
+
+/** Pointer a test descriptor. */
+typedef CLIPBOARDTESTDESC *PTESTDESC;
+
+typedef DECLCALLBACKTYPE(int, FNTESTSETUP,(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx));
+/** Pointer to a test setup callback. */
+typedef FNTESTSETUP *PFNTESTSETUP;
+
+typedef DECLCALLBACKTYPE(int, FNTESTEXEC,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx));
+/** Pointer to a test exec callback. */
+typedef FNTESTEXEC *PFNTESTEXEC;
+
+typedef DECLCALLBACKTYPE(int, FNTESTDESTROY,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx));
+/** Pointer to a test destroy callback. */
+typedef FNTESTDESTROY *PFNTESTDESTROY;
+
+
+/**
+ * Structure for keeping a clipboard test task.
+ */
+typedef struct CLIPBOARDTESTTASK
+{
+ SHCLFORMATS enmFmtHst;
+ SHCLFORMATS enmFmtGst;
+ /** For testing chunked reads / writes. */
+ size_t cbChunk;
+ /** Data buffer to read / write for this task.
+ * Can be NULL if not needed. */
+ void *pvData;
+ /** Size (in bytes) of \a pvData. */
+ size_t cbData;
+ /** Number of bytes read / written from / to \a pvData. */
+ size_t cbProcessed;
+} CLIPBOARDTESTTASK;
+typedef CLIPBOARDTESTTASK *PCLIPBOARDTESTTASK;
+
+/**
+ * Structure for keeping a clipboard test context.
+ */
+typedef struct CLIPBOARDTESTCTX
+{
+ /** The HGCM Mock utils context. */
+ TSTHGCMUTILSCTX HGCM;
+ /** Clipboard-specific task data. */
+ CLIPBOARDTESTTASK Task;
+ struct
+ {
+ /** The VbglR3 Shared Clipboard context to work on. */
+ VBGLR3SHCLCMDCTX CmdCtx;
+ } Guest;
+} CLIPBOARDTESTCTX;
+
+/** The one and only clipboard test context. One at a time. */
+CLIPBOARDTESTCTX g_TstCtx;
+
+/**
+ * Structure for keeping a clipboard test description.
+ */
+typedef struct CLIPBOARDTESTDESC
+{
+ /** The setup callback. */
+ PFNTESTSETUP pfnSetup;
+ /** The exec callback. */
+ PFNTESTEXEC pfnExec;
+ /** The destruction callback. */
+ PFNTESTDESTROY pfnDestroy;
+} CLIPBOARDTESTDESC;
+
+typedef struct SHCLCONTEXT
+{
+} SHCLCONTEXT;
+
+
+static int tstSetModeRc(PTSTHGCMMOCKSVC pSvc, uint32_t uMode, int rcExpected)
+{
+ VBOXHGCMSVCPARM aParms[2];
+ HGCMSvcSetU32(&aParms[0], uMode);
+ int rc2 = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, aParms);
+ RTTESTI_CHECK_MSG_RET(rcExpected == rc2, ("Expected %Rrc, got %Rrc\n", rcExpected, rc2), rc2);
+ if (RT_SUCCESS(rcExpected))
+ {
+ uint32_t const uModeRet = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG_RET(uMode == uModeRet, ("Expected mode %RU32, got %RU32\n", uMode, uModeRet), VERR_WRONG_TYPE);
+ }
+ return rc2;
+}
+
+static int tstClipboardSetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uMode)
+{
+ return tstSetModeRc(pSvc, uMode, VINF_SUCCESS);
+}
+
+static bool tstClipboardGetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uModeExpected)
+{
+ RT_NOREF(pSvc);
+ RTTESTI_CHECK_RET(ShClSvcGetMode() == uModeExpected, false);
+ return true;
+}
+
+static void tstOperationModes(void)
+{
+ struct VBOXHGCMSVCPARM parms[2];
+ uint32_t u32Mode;
+ int rc;
+
+ RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ /* Reset global variable which doesn't reset itself. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ u32Mode = ShClSvcGetMode();
+ RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode));
+
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_HOST_TO_GUEST);
+ tstSetModeRc(pSvc, 99, VERR_NOT_SUPPORTED);
+ tstClipboardGetMode(pSvc, VBOX_SHCL_MODE_OFF);
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+static void testSetTransferMode(void)
+{
+ RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ /* Invalid parameter. */
+ VBOXHGCMSVCPARM parms[2];
+ HGCMSvcSetU64(&parms[0], 99);
+ int rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+
+ /* Invalid mode. */
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS);
+
+ /* Enable transfers. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_ENABLED);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+
+ /* Disable transfers again. */
+ HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_DISABLED);
+ rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms);
+ RTTESTI_CHECK_RC(rc, VINF_SUCCESS);
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
+static void testGuestSimple(void)
+{
+ RTTestISub("Testing client (guest) API - Simple");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ /* Preparations. */
+ VBGLR3SHCLCMDCTX Ctx;
+ RT_ZERO(Ctx);
+
+ /*
+ * Multiple connects / disconnects.
+ */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID));
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx));
+ /* Report bogus guest features while connecting. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, 0xdeadbeef));
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx));
+
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID));
+
+ /*
+ * Feature tests.
+ */
+
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0x0, NULL /* pfHostFeatures */));
+ /* Report bogus features to the host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0xdeadb33f, NULL /* pfHostFeatures */));
+
+ /*
+ * Access denied tests.
+ */
+
+ /* Try reading data from host. */
+ uint8_t abData[32]; uint32_t cbIgnored;
+ RTTESTI_CHECK_RC(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT,
+ abData, sizeof(abData), &cbIgnored), VERR_ACCESS_DENIED);
+ /* Try writing data without reporting formats before (legacy). */
+ RTTESTI_CHECK_RC(VbglR3ClipboardWriteData(Ctx.idClient, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED);
+ /* Try writing data without reporting formats before. */
+ RTTESTI_CHECK_RC(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED);
+ /* Report bogus formats to the host. */
+ RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f), VERR_ACCESS_DENIED);
+ /* Report supported formats to host. */
+ RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient,
+ VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML),
+ VERR_ACCESS_DENIED);
+ /*
+ * Access allowed tests.
+ */
+ tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL);
+
+ /* Try writing data without reporting formats before. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)));
+ /* Try reading data from host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT,
+ abData, sizeof(abData), &cbIgnored));
+ /* Report bogus formats to the host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f));
+ /* Report supported formats to host. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient,
+ VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML));
+ /* Tear down. */
+ RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx));
+}
+
+static RTUTF16 tstGetRandUtf8(void)
+{
+ return RTRandU32Ex(0x20, 0x7A);
+}
+
+static char *tstGenerateUtf8StringA(uint32_t uCch)
+{
+ char * pszRand = (char *)RTMemAlloc(uCch + 1);
+ for (uint32_t i = 0; i < uCch; i++)
+ pszRand[i] = tstGetRandUtf8();
+ pszRand[uCch] = 0;
+ return pszRand;
+}
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+static RTUTF16 tstGetRandUtf16(void)
+{
+ RTUTF16 wc;
+ do
+ {
+ wc = (RTUTF16)RTRandU32Ex(1, 0xfffd);
+ } while (wc >= 0xd800 && wc <= 0xdfff);
+ return wc;
+}
+
+static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch)
+{
+ PRTUTF16 pwszRand = (PRTUTF16)RTMemAlloc((uCch + 1) * sizeof(RTUTF16));
+ for (uint32_t i = 0; i < uCch; i++)
+ pwszRand[i] = tstGetRandUtf16();
+ pwszRand[uCch] = 0;
+ return pwszRand;
+}
+#endif /* RT_OS_WINDOWS) || RT_OS_OS2 */
+
+static void testSetHeadless(void)
+{
+ RTTestISub("Testing HOST_FN_SET_HEADLESS");
+
+ PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst();
+
+ VBOXHGCMSVCPARM parms[2];
+ HGCMSvcSetU32(&parms[0], false);
+ int rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ bool fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless));
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 2, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU64(&parms[0], 99);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER);
+ HGCMSvcSetU32(&parms[0], true);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+ HGCMSvcSetU32(&parms[0], 99);
+ rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms);
+ RTTESTI_CHECK_RC_OK(rc);
+ fHeadless = ShClSvcGetHeadless();
+ RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless));
+}
+
+static void testHostCall(void)
+{
+ tstOperationModes();
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ testSetTransferMode();
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+ testSetHeadless();
+}
+
+
+/*********************************************************************************************************************************
+ * Test: Guest reading from host *
+ ********************************************************************************************************************************/
+#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS)
+/* Called from SHCLX11 thread. */
+static DECLCALLBACK(int) tstTestReadFromHost_ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, fFormats, pvUser);
+
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "tstTestReadFromHost_SvcReportFormatsCallback: fFormats=%#x\n", fFormats);
+ return VINF_SUCCESS;
+}
+
+/* Called by the backend, e.g. for X11 in the SHCLX11 thread. */
+static DECLCALLBACK(int) tstTestReadFromHost_OnClipboardReadCallback(PSHCLCONTEXT pCtx,
+ SHCLFORMAT uFmt, void **ppv, size_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, pvUser);
+
+ PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)TstHGCMUtilsTaskGetCurrent(&g_TstCtx.HGCM)->pvUser;
+
+ void *pvData = NULL;
+ size_t cbData = pTask->cbData - pTask->cbProcessed;
+ if (cbData)
+ {
+ pvData = RTMemDup((uint8_t *)pTask->pvData + pTask->cbProcessed, cbData);
+ AssertPtr(pvData);
+ }
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host reporting back %RU32 bytes of data\n", cbData);
+
+ *ppv = pvData;
+ *pcb = cbData;
+
+ return VINF_SUCCESS;
+}
+#endif /* (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */
+
+typedef struct TSTUSERMOCK
+{
+#if defined(RT_OS_LINUX)
+ SHCLX11CTX X11Ctx;
+#endif
+ PSHCLCONTEXT pCtx;
+} TSTUSERMOCK;
+typedef TSTUSERMOCK *PTSTUSERMOCK;
+
+static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszName)
+{
+#if defined(RT_OS_LINUX)
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback;
+ Callbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback;
+
+ pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT));
+ AssertPtrReturnVoid(pUsrMock->pCtx);
+
+ ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx, false);
+ ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */);
+ /* Give the clipboard time to synchronise. */
+ RTThreadSleep(500);
+#else
+ RT_NOREF(pUsrMock, pszName);
+#endif /* RT_OS_LINUX */
+}
+
+static void tstTestReadFromHost_MockDestroy(PTSTUSERMOCK pUsrMock)
+{
+#if defined(RT_OS_LINUX)
+ ShClX11ThreadStop(&pUsrMock->X11Ctx);
+ ShClX11Destroy(&pUsrMock->X11Ctx);
+ RTMemFree(pUsrMock->pCtx);
+#else
+ RT_NOREF(pUsrMock);
+#endif
+}
+
+static int tstTestReadFromHost_DoIt(PCLIPBOARDTESTCTX pCtx, PCLIPBOARDTESTTASK pTask)
+{
+ size_t cbDst = RT_MAX(_64K, pTask->cbData);
+ uint8_t *pabDst = (uint8_t *)RTMemAllocZ(cbDst);
+ AssertPtrReturn(pabDst, VERR_NO_MEMORY);
+
+ AssertPtr(pTask->pvData); /* Racing condition with host thread? */
+ Assert(pTask->cbChunk); /* Buggy test? */
+ Assert(pTask->cbChunk <= pTask->cbData); /* Ditto. */
+
+ size_t cbToRead = pTask->cbData;
+ switch (pTask->enmFmtGst)
+ {
+ case VBOX_SHCL_FMT_UNICODETEXT:
+#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */
+ cbToRead *= sizeof(RTUTF16);
+#endif
+ break;
+
+ default:
+ break;
+ }
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = &pCtx->Guest.CmdCtx;
+
+ /* Do random chunked reads. */
+ uint32_t const cChunkedReads = RTRandU32Ex(1, 16);
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "%RU32 chunked reads\n", cChunkedReads);
+ for (uint32_t i = 0; i < cChunkedReads; i++)
+ {
+ /* Note! VbglR3ClipboardReadData() currently does not support chunked reads!
+ * It in turn returns VINF_BUFFER_OVERFLOW when the supplied buffer was too small. */
+
+ uint32_t cbChunk = RTRandU32Ex(1, (uint32_t)(pTask->cbData / cChunkedReads));
+ uint32_t cbRead = 0;
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest trying to read %RU32 bytes\n", cbChunk);
+ int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, cbChunk, &cbRead);
+ if ( vrc2 == VINF_SUCCESS
+ && cbRead == 0) /* No data there yet? */
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "No data (yet) from host\n");
+ RTThreadSleep(10);
+ continue;
+ }
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying reading host clipboard data with a %RU32 buffer -> %Rrc (%RU32)\n", cbChunk, vrc2, cbRead);
+ RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_BUFFER_OVERFLOW, (g_hTest, "Got %Rrc, expected VINF_BUFFER_OVERFLOW\n", vrc2));
+ }
+
+ /* Last read: Read the data with a buffer big enough. This must succeed. */
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Reading full data (%zu)\n", pTask->cbData);
+ uint32_t cbRead = 0;
+ int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, (uint32_t)cbDst, &cbRead);
+ RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_SUCCESS, (g_hTest, "Got %Rrc, expected VINF_SUCCESS\n", vrc2));
+ RTTEST_CHECK_MSG(g_hTest, cbRead == cbToRead, (g_hTest, "Read %RU32 bytes, expected %zu\n", cbRead, cbToRead));
+
+ if (pTask->enmFmtGst == VBOX_SHCL_FMT_UNICODETEXT)
+ RTTEST_CHECK_MSG(g_hTest, RTUtf16ValidateEncoding((PRTUTF16)pabDst) == VINF_SUCCESS, (g_hTest, "Read data is not valid UTF-16\n"));
+ if (cbRead == cbToRead)
+ {
+#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */
+ PRTUTF16 pwszSrc = NULL;
+ RTTEST_CHECK(g_hTest, RT_SUCCESS(RTStrToUtf16((const char *)pTask->pvData, &pwszSrc)));
+ RTTEST_CHECK_MSG(g_hTest, memcmp(pwszSrc, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n"));
+ RTUtf16Free(pwszSrc);
+#else
+ RTTEST_CHECK_MSG(g_hTest, memcmp(pTask->pvData, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n"));
+#endif
+ }
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Read data from host:\n%.*Rhxd\n", cbRead, pabDst);
+
+ RTMemFree(pabDst);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHost_ThreadGuest(PTSTHGCMUTILSCTX pCtx, void *pvCtx)
+{
+ RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */
+
+ PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvCtx;
+ AssertPtr(pTstCtx);
+
+ RT_ZERO(pTstCtx->Guest.CmdCtx);
+ RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardConnectEx(&pTstCtx->Guest.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID));
+
+ RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */
+
+ PCLIPBOARDTESTTASK pTstTask = (PCLIPBOARDTESTTASK)pCtx->Task.pvUser;
+ AssertPtr(pTstTask);
+ tstTestReadFromHost_DoIt(pTstCtx, pTstTask);
+
+ /* Signal that the task ended. */
+ TstHGCMUtilsTaskSignal(&pCtx->Task, VINF_SUCCESS);
+
+ RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardDisconnectEx(&pTstCtx->Guest.CmdCtx));
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHost_ClientConnectedCallback(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKCLIENT pClient,
+ void *pvUser)
+{
+ RT_NOREF(pCtx, pClient);
+
+ PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvUser;
+ AssertPtr(pTstCtx); RT_NOREF(pTstCtx);
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Client %RU32 connected\n", pClient->idClient);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHostSetup(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx)
+{
+ RT_NOREF(ppvCtx);
+
+ /* Set the right clipboard mode, so that the guest can read from the host. */
+ tstClipboardSetMode(TstHgcmMockSvcInst(), VBOX_SHCL_MODE_BIDIRECTIONAL);
+
+ /* Start the host thread first, so that the guest thread can connect to it later. */
+ TSTHGCMUTILSHOSTCALLBACKS HostCallbacks;
+ RT_ZERO(HostCallbacks);
+ HostCallbacks.pfnOnClientConnected = tstTestReadFromHost_ClientConnectedCallback;
+ TstHGCMUtilsHostThreadStart(&pTstCtx->HGCM, &HostCallbacks, pTstCtx /* pvUser */);
+
+ PCLIPBOARDTESTTASK pTask = &pTstCtx->Task;
+ AssertPtr(pTask);
+ pTask->enmFmtGst = VBOX_SHCL_FMT_UNICODETEXT;
+ pTask->enmFmtHst = pTask->enmFmtGst;
+ pTask->cbChunk = RTRandU32Ex(1, 512);
+ pTask->cbData = RT_ALIGN_32(pTask->cbChunk * RTRandU32Ex(1, 16), 2);
+ Assert(pTask->cbData % sizeof(RTUTF16) == 0);
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ pTask->pvData = tstGenerateUtf8StringA(pTask->cbData);
+ pTask->cbData++; /* Add terminating zero. */
+#else
+ pTask->pvData = tstGenerateUtf16StringA((uint32_t)(pTask->cbData /* We use bytes == chars here */));
+ pTask->cbData *= sizeof(RTUTF16);
+ pTask->cbData += sizeof(RTUTF16); /* Add terminating zero. */
+#endif
+ pTask->cbProcessed = 0;
+
+ int rc = VINF_SUCCESS;
+
+#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS)
+ /* Initialize the Shared Clipboard backend callbacks. */
+ PSHCLBACKEND pBackend = ShClSvcGetBackend();
+
+ SHCLCALLBACKS ShClCallbacks;
+ RT_ZERO(ShClCallbacks);
+ ShClCallbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback;
+ ShClCallbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback;
+ ShClBackendSetCallbacks(pBackend, &ShClCallbacks);
+#elif defined (RT_OS_WINDOWS)
+ rc = SharedClipboardWinOpen(GetDesktopWindow());
+ if (RT_SUCCESS(rc))
+ {
+ rc = SharedClipboardWinDataWrite(CF_UNICODETEXT, pTask->pvData, (uint32_t)pTask->cbData);
+ SharedClipboardWinClose();
+ }
+#endif /* defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */
+
+ RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host data (%RU32):\n%.*Rhxd\n", pTask->cbData, pTask->cbData, pTask->pvData);
+ return rc;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHostExec(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)
+{
+ RT_NOREF(pvCtx);
+
+ TstHGCMUtilsGuestThreadStart(&pTstCtx->HGCM, tstTestReadFromHost_ThreadGuest, pTstCtx);
+
+ PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(&pTstCtx->HGCM);
+
+ bool fUseMock = false;
+ TSTUSERMOCK UsrMock;
+ if (fUseMock)
+ tstTestReadFromHost_MockInit(&UsrMock, "tstX11Hst");
+
+ /* Wait until the task has been finished. */
+ TstHGCMUtilsTaskWait(pTask, RT_MS_30SEC);
+
+ if (fUseMock)
+ tstTestReadFromHost_MockDestroy(&UsrMock);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstTestReadFromHostDestroy(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)
+{
+ RT_NOREF(pvCtx);
+
+ int vrc = TstHGCMUtilsGuestThreadStop(&pTstCtx->HGCM);
+ AssertRC(vrc);
+ vrc = TstHGCMUtilsHostThreadStop(&pTstCtx->HGCM);
+ AssertRC(vrc);
+
+ return vrc;
+}
+
+
+/*********************************************************************************************************************************
+ * Main *
+ ********************************************************************************************************************************/
+
+/** Test definition table. */
+CLIPBOARDTESTDESC g_aTests[] =
+{
+ /* Tests guest reading clipboard data from the host. */
+ { tstTestReadFromHostSetup, tstTestReadFromHostExec, tstTestReadFromHostDestroy }
+};
+/** Number of tests defined. */
+unsigned g_cTests = RT_ELEMENTS(g_aTests);
+
+static int tstOne(PTESTDESC pTstDesc)
+{
+ PCLIPBOARDTESTCTX pTstCtx = &g_TstCtx;
+
+ void *pvCtx;
+ int rc = pTstDesc->pfnSetup(pTstCtx, &pvCtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pTstDesc->pfnExec(pTstCtx, pvCtx);
+
+ int rc2 = pTstDesc->pfnDestroy(pTstCtx, pvCtx);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ const char *pcszExecName;
+ NOREF(argc);
+ pcszExecName = strrchr(argv[0], '/');
+ pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0];
+ RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &g_hTest);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ RTTestBanner(g_hTest);
+
+#ifndef DEBUG_andy
+ /* Don't let assertions in the host service panic (core dump) the test cases. */
+ RTAssertSetMayPanic(false);
+#endif
+
+ PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst();
+ TstHgcmMockSvcCreate(pSvc, sizeof(SHCLCLIENT));
+ TstHgcmMockSvcStart(pSvc);
+
+ /*
+ * Run the tests.
+ */
+ if (0)
+ {
+ testGuestSimple();
+ testHostCall();
+ }
+
+ RT_ZERO(g_TstCtx);
+
+ PTSTHGCMUTILSCTX pCtx = &g_TstCtx.HGCM;
+ TstHGCMUtilsCtxInit(pCtx, pSvc);
+
+ PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(pCtx);
+ TstHGCMUtilsTaskInit(pTask);
+ pTask->pvUser = &g_TstCtx.Task;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
+ tstOne(&g_aTests[i]);
+
+ TstHGCMUtilsTaskDestroy(pTask);
+
+ TstHgcmMockSvcStop(pSvc);
+ TstHgcmMockSvcDestroy(pSvc);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(g_hTest);
+}
+