From f8fe689a81f906d1b91bb3220acde2a4ecb14c5b Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Mon, 6 May 2024 05:01:46 +0200
Subject: Adding upstream version 6.0.4-dfsg.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 src/VBox/Runtime/testcase/tstRTLocalIpc.cpp | 960 ++++++++++++++++++++++++++++
 1 file changed, 960 insertions(+)
 create mode 100644 src/VBox/Runtime/testcase/tstRTLocalIpc.cpp

(limited to 'src/VBox/Runtime/testcase/tstRTLocalIpc.cpp')

diff --git a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp
new file mode 100644
index 00000000..a24c5459
--- /dev/null
+++ b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp
@@ -0,0 +1,960 @@
+/* $Id: tstRTLocalIpc.cpp $ */
+/** @file
+ * IPRT Testcase - RTLocalIpc API.
+ */
+
+/*
+ * Copyright (C) 2013-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.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#include <iprt/localipc.h>
+
+#include <iprt/asm.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+
+/*********************************************************************************************************************************
+*   Global Variables                                                                                                             *
+*********************************************************************************************************************************/
+/** The test instance.*/
+static RTTEST g_hTest;
+
+
+
+static void testBasics(void)
+{
+    RTTestISub("Basics");
+
+    /* Server-side. */
+    RTTESTI_CHECK_RC(RTLocalIpcServerCreate(NULL, NULL, 0), VERR_INVALID_POINTER);
+    RTLOCALIPCSERVER hIpcServer;
+    int rc;
+    RTTESTI_CHECK_RC(rc = RTLocalIpcServerCreate(&hIpcServer, NULL, 0), VERR_INVALID_POINTER);
+    if (RT_SUCCESS(rc)) RTLocalIpcServerDestroy(hIpcServer);
+    RTTESTI_CHECK_RC(rc = RTLocalIpcServerCreate(&hIpcServer, "", 0), VERR_INVALID_NAME);
+    if (RT_SUCCESS(rc)) RTLocalIpcServerDestroy(hIpcServer);
+    RTTESTI_CHECK_RC(rc = RTLocalIpcServerCreate(&hIpcServer, "BasicTest", 1234 /* Invalid flags */), VERR_INVALID_FLAGS);
+    if (RT_SUCCESS(rc)) RTLocalIpcServerDestroy(hIpcServer);
+
+    RTTESTI_CHECK_RC(RTLocalIpcServerCancel(NULL), VERR_INVALID_HANDLE);
+    RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(NULL), VINF_SUCCESS);
+
+    /* Basic server creation / destruction. */
+    RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "BasicTest", 0), VINF_SUCCESS);
+    RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+    RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+    /* Client-side (per session). */
+    RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(NULL, NULL, 0), VERR_INVALID_POINTER);
+    RTLOCALIPCSESSION hIpcSession;
+    RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(&hIpcSession, NULL, 0), VERR_INVALID_POINTER);
+    if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+    RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(&hIpcSession, "", 0), VERR_INVALID_NAME);
+    if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+    RTTESTI_CHECK_RC(RTLocalIpcSessionConnect(&hIpcSession, "BasicTest", 1234 /* Invalid flags */), VERR_INVALID_FLAGS);
+    if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+
+    RTTESTI_CHECK_RC(RTLocalIpcSessionCancel(NULL), VERR_INVALID_HANDLE);
+    RTTESTI_CHECK_RC(RTLocalIpcSessionClose(NULL), VINF_SUCCESS);
+
+    /* Basic client creation / destruction. */
+    RTTESTI_CHECK_RC_RETV(rc = RTLocalIpcSessionConnect(&hIpcSession, "BasicTest", 0), VERR_FILE_NOT_FOUND);
+    if (RT_SUCCESS(rc)) RTLocalIpcSessionClose(hIpcSession);
+    //RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VERR_INVALID_HANDLE);  - accessing freed memory, bad idea.
+    //RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VERR_INVALID_HANDLE); - accessing freed memory, bad idea.
+}
+
+
+
+/*********************************************************************************************************************************
+*                                                                                                                                *
+*   testSessionConnection - Connecting.                                                                                          *
+*                                                                                                                                *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser)
+{
+    RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+    RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+
+    int rc;
+    for (;;)
+    {
+        RTLOCALIPCSESSION hIpcSession;
+        rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+        if (RT_SUCCESS(rc))
+        {
+            RTThreadSleep(8); /* windows output fudge (purely esthetical) */
+            RTTestIPrintf(RTTESTLVL_INFO, "testServerListenThread: Got new client connection.\n");
+            RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+        }
+        else
+        {
+            RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+            break;
+        }
+    }
+    return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionConnectionChild(RTTHREAD hSelf, void *pvUser)
+{
+    RTLOCALIPCSESSION hClientSession;
+    RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+    RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionConnection",0 /* Flags */),
+                        VINF_SUCCESS, rcCheck);
+    RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionClose(hClientSession),
+                        VINF_OBJECT_DESTROYED, rcCheck);
+
+    return VINF_SUCCESS;
+}
+
+
+static void testSessionConnection(const char *pszExecPath)
+{
+    RTTestISub(!pszExecPath ? "Connect from thread" : "Connect from child");
+
+    /*
+     * Create the test server.
+     */
+    RTLOCALIPCSERVER hIpcServer;
+    RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionConnection", 0), VINF_SUCCESS);
+
+    /*
+     * Create worker thread that listens and closes incoming connections until
+     * cancelled.
+     */
+    int rc;
+    RTTHREAD hListenThread;
+    RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenThread, hIpcServer, 0 /* Stack */,
+                                            RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-1"));
+    if (RT_SUCCESS(rc))
+    {
+        RTThreadUserWait(hListenThread, 32);
+
+        /*
+         * Two variations here: Client connects from thread or a child process.
+         */
+        if (pszExecPath)
+        {
+            RTPROCESS hClientProc;
+            const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionConnectionChild", NULL };
+            RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+            if (RT_SUCCESS(rc))
+            {
+                RTPROCSTATUS ProcStatus;
+                RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+                if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+                    RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+            }
+        }
+        else
+        {
+            RTTHREAD hClientThread;
+            RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionConnectionChild, NULL,
+                                                    0 /* Stack */, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-1"));
+            if (RT_SUCCESS(rc))
+            {
+                int rcThread;
+                RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+                if (RT_SUCCESS(rc))
+                    RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+            }
+        }
+
+
+        /*
+         * Terminate the server thread.
+         */
+        //RTTestIPrintf(RTTESTLVL_INFO, "Child terminated, waiting for server thread ...\n");
+        RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+        int rcThread;
+        RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, 30 * 1000 /* 30s timeout */, &rcThread), VINF_SUCCESS);
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+    }
+
+    RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+}
+
+
+
+/*********************************************************************************************************************************
+*                                                                                                                                *
+*   testSessionWait - RTLocalIpcSessionWaitForData.                                                                              *
+*                                                                                                                                *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(int) testSessionWaitThread(RTTHREAD hSelf, void *pvUser)
+{
+    RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+    int rc;
+    for (;;)
+    {
+        RTLOCALIPCSESSION hIpcSession;
+        rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+        if (RT_SUCCESS(rc))
+        {
+            RTTestIPrintf(RTTESTLVL_INFO, "testSessionWaitThread: Got new client connection.\n");
+
+            /* Wait for the client to trigger a disconnect by writing us something. */
+            RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hIpcSession, RT_MS_1MIN), VINF_SUCCESS);
+
+            size_t cbRead;
+            char szCmd[64];
+            RT_ZERO(szCmd);
+            RTTESTI_CHECK_RC(rc = RTLocalIpcSessionReadNB(hIpcSession, szCmd, sizeof(szCmd) - 1, &cbRead), VINF_SUCCESS);
+            if (RT_SUCCESS(rc) && (cbRead != sizeof("disconnect") - 1 || strcmp(szCmd, "disconnect")) )
+                RTTestIFailed("cbRead=%zu, expected %zu; szCmd='%s', expected 'disconnect'\n",
+                              cbRead, sizeof("disconnect") - 1, szCmd);
+
+            RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+            RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+        }
+        else
+        {
+            RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+            break;
+        }
+    }
+    RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+    return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionWaitChild(RTTHREAD hSelf, void *pvUser)
+{
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+    RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+    RTLOCALIPCSESSION hClientSession;
+    RTTESTI_CHECK_RC_RET(RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionWait", 0 /*fFlags*/),
+                         VINF_SUCCESS, rcCheck);
+
+    /*
+     * The server side won't write anything.  It will close the connection
+     * as soon as we write something.
+     */
+    RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, 0 /*cMsTimeout*/), VERR_TIMEOUT);
+    RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, 8 /*cMsTimeout*/), VERR_TIMEOUT);
+    uint8_t abBuf[4];
+    size_t cbRead = _4M-1;
+    RTTESTI_CHECK_RC(RTLocalIpcSessionReadNB(hClientSession, abBuf, sizeof(abBuf), &cbRead), VINF_TRY_AGAIN);
+    RTTESTI_CHECK(cbRead == 0);
+
+    /* Trigger server disconnect. */
+    int rc;
+    RTTESTI_CHECK_RC(rc = RTLocalIpcSessionWrite(hClientSession, RT_STR_TUPLE("disconnect")), VINF_SUCCESS);
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * When we wait now, we should get an broken pipe error as
+         * the server has close its end.
+         */
+        RTTESTI_CHECK_RC(rc = RTLocalIpcSessionWaitForData(hClientSession, RT_MS_1MIN), VERR_BROKEN_PIPE);
+        RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, 0), VERR_BROKEN_PIPE);
+        RTTESTI_CHECK_RC(RTLocalIpcSessionWaitForData(hClientSession, RT_MS_1SEC), VERR_BROKEN_PIPE);
+
+        bool fMayPanic = RTAssertSetMayPanic(false);
+        bool fQuiet    = RTAssertSetQuiet(true);
+
+        RTTESTI_CHECK_RC(RTLocalIpcSessionWrite(hClientSession, RT_STR_TUPLE("broken")), VERR_BROKEN_PIPE);
+        RTTESTI_CHECK_RC(RTLocalIpcSessionRead(hClientSession, abBuf, sizeof(abBuf), NULL), VERR_BROKEN_PIPE);
+        cbRead = _4M-1;
+        RTTESTI_CHECK_RC(RTLocalIpcSessionRead(hClientSession, abBuf, sizeof(abBuf), &cbRead), VERR_BROKEN_PIPE);
+        cbRead = _1G/2;
+        RTTESTI_CHECK_RC(RTLocalIpcSessionReadNB(hClientSession, abBuf, sizeof(abBuf), &cbRead), VERR_BROKEN_PIPE);
+
+        RTAssertSetMayPanic(fMayPanic);
+        RTAssertSetQuiet(fQuiet);
+    }
+
+    RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED);
+
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * @note This is identical to testSessionData with a couple of string and
+ *       function pointers replaced.
+ */
+static void testSessionWait(const char *pszExecPath)
+{
+    RTTestISub(!pszExecPath ? "Wait for data in thread" : "Wait for data in child");
+
+    /*
+     * Create the test server.
+     */
+    RTLOCALIPCSERVER hIpcServer;
+    RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionWait", 0), VINF_SUCCESS);
+
+    /*
+     * Create worker thread that listens and processes incoming connections
+     * until cancelled.
+     */
+    int rc;
+    RTTHREAD hListenThread;
+    RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testSessionWaitThread, hIpcServer, 0 /* Stack */,
+                                            RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-2"));
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Create a client process or thread and connects to the server.
+         * It will perform the wait-for-data test.
+         */
+        RTPROCESS hClientProc = NIL_RTPROCESS;
+        RTTHREAD  hClientThread = NIL_RTTHREAD;
+        if (pszExecPath)
+        {
+            const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionWaitChild", NULL };
+            RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+        }
+        else
+            RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionWaitChild, g_hTest, 0 /*cbStack*/,
+                                                    RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-2"));
+
+        /*
+         * Wait for the server thread to indicate that it has processed one
+         * connection, then shut it all down.
+         */
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN / 2));
+
+        RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+        int rcThread;
+        RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread), VINF_SUCCESS);
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+
+        RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+        /*
+         * Check that client ran successfully.
+         */
+        if (pszExecPath)
+        {
+            if (hClientProc != NIL_RTPROCESS)
+            {
+                RTPROCSTATUS ProcStatus;
+                RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+                if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+                    RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+            }
+        }
+        else if (hClientThread != NIL_RTTHREAD)
+        {
+            RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+            if (RT_SUCCESS(rc))
+                RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+        }
+    }
+}
+
+
+
+/*********************************************************************************************************************************
+*                                                                                                                                *
+*   testSessionData - Data transfer integrity.                                                                                   *
+*                                                                                                                                *
+*********************************************************************************************************************************/
+
+/** The max message size. */
+#define MAX_DATA_MSG_SIZE   _1M
+
+static int testSessionDataReadMessages(RTLOCALIPCSESSION hIpcSession, uint32_t cRounds)
+{
+    /*
+     * Message scratch buffer.  Search message starts with a uint32_t word
+     * that indicates the message length.  The remaining words are set to
+     * the message number.
+     */
+    uint32_t *pau32ScratchBuf = (uint32_t *)RTMemAlloc(MAX_DATA_MSG_SIZE);
+    RTTESTI_CHECK_RET(pau32ScratchBuf != NULL, VERR_NO_MEMORY);
+
+    int rc = VINF_SUCCESS;
+    for (uint32_t iRound = 0; iRound < cRounds && rc == VINF_SUCCESS; iRound++)
+    {
+        /* Read the message length. */
+        uint32_t cbMsg;
+        RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, &cbMsg, sizeof(cbMsg), NULL), VINF_SUCCESS);
+        if (cbMsg >= sizeof(cbMsg) && cbMsg <= MAX_DATA_MSG_SIZE)
+        {
+            pau32ScratchBuf[0] = cbMsg;
+
+            /* Read the message body. */
+            uint32_t cbLeft = cbMsg - sizeof(uint32_t);
+            uint8_t *pbCur  = (uint8_t *)&pau32ScratchBuf[1];
+            while (cbLeft > 0)
+            {
+                uint32_t cbCur = RTRandU32Ex(1, cbLeft + cbLeft / 4);
+                cbCur = RT_MIN(cbCur, cbLeft);
+                if ((iRound % 3) == 1)
+                {
+                    size_t cbRead = _1G;
+                    RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, pbCur, cbCur, &cbRead), VINF_SUCCESS);
+                    RTTESTI_CHECK(cbCur >= cbRead);
+                    cbCur = (uint32_t)cbRead;
+                }
+                else
+                    RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, pbCur, cbCur, NULL), VINF_SUCCESS);
+                pbCur  += cbCur;
+                cbLeft -= cbCur;
+            }
+
+            /* Check the message body. */
+            if (RT_SUCCESS(rc))
+            {
+                uint32_t offLast = cbMsg & (sizeof(uint32_t) - 1);
+                if (offLast)
+                    memcpy((uint8_t *)pau32ScratchBuf + cbMsg, (uint8_t const *)&iRound + offLast, sizeof(uint32_t) - offLast);
+
+                ASMCompilerBarrier(); /* Guard against theoretical alias issues in the above code.  */
+
+                uint32_t cWords = RT_ALIGN_32(cbMsg, sizeof(uint32_t)) / sizeof(uint32_t);
+                for (uint32_t iWord = 1; iWord < cWords; iWord++)
+                    if (pau32ScratchBuf[iWord] != iRound)
+                    {
+                        RTTestIFailed("Message body word #%u mismatch: %#x, expected %#x", iWord, pau32ScratchBuf[iWord], iRound);
+                        break;
+                    }
+            }
+        }
+        else
+        {
+            RTTestIFailed("cbMsg=%#x is out of range", cbMsg);
+            rc = VERR_OUT_OF_RANGE;
+        }
+    }
+
+    RTMemFree(pau32ScratchBuf);
+    return rc;
+}
+
+
+static int testSessionDataWriteMessages(RTLOCALIPCSESSION hIpcSession, uint32_t cRounds)
+{
+    /*
+     * Message scratch buffer.  Search message starts with a uint32_t word
+     * that indicates the message length.  The remaining words are set to
+     * the message number.
+     */
+    uint32_t   cbScratchBuf = RTRandU32Ex(64, MAX_DATA_MSG_SIZE);
+    cbScratchBuf = RT_ALIGN_32(cbScratchBuf, sizeof(uint32_t));
+
+    uint32_t *pau32ScratchBuf = (uint32_t *)RTMemAlloc(cbScratchBuf);
+    RTTESTI_CHECK_RET(pau32ScratchBuf != NULL, VERR_NO_MEMORY);
+
+    size_t cbSent = 0;
+    int rc = VINF_SUCCESS;
+    for (uint32_t iRound = 0; iRound < cRounds && rc == VINF_SUCCESS; iRound++)
+    {
+        /* Construct the message. */
+        uint32_t cbMsg  = RTRandU32Ex(sizeof(uint32_t), cbScratchBuf);
+        uint32_t cWords = RT_ALIGN_32(cbMsg, sizeof(uint32_t)) / sizeof(uint32_t);
+
+        uint32_t iWord  = 0;
+        pau32ScratchBuf[iWord++] = cbMsg;
+        while (iWord < cWords)
+            pau32ScratchBuf[iWord++] = iRound;
+
+        /* Send it. */
+        uint32_t cbLeft = cbMsg;
+        uint8_t const *pbCur = (uint8_t *)pau32ScratchBuf;
+        while (cbLeft > 0)
+        {
+            uint32_t cbCur = RT_MIN(iRound + 1, cbLeft);
+            RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hIpcSession, pbCur, cbCur), VINF_SUCCESS);
+            pbCur  += cbCur;
+            cbSent += cbCur;
+            cbLeft -= cbCur;
+        }
+    }
+
+    RTTestIPrintf(RTTESTLVL_ALWAYS, "Sent %'zu bytes over %u rounds.\n", cbSent, cRounds);
+    RTMemFree(pau32ScratchBuf);
+    return rc;
+}
+
+
+static DECLCALLBACK(int) testSessionDataThread(RTTHREAD hSelf, void *pvUser)
+{
+    RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+    int rc;
+    for (;;)
+    {
+        RTLOCALIPCSESSION hIpcSession;
+        rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+        if (RT_SUCCESS(rc))
+        {
+            RTTestIPrintf(RTTESTLVL_INFO, "testSessionDataThread: Got new client connection\n");
+
+            /* The server is the initator. First message sets the number of rounds. */
+            uint32_t cRounds = RTRandU32Ex(32, _1K);
+            RTTESTI_CHECK_RC(rc = RTLocalIpcSessionWrite(hIpcSession, &cRounds, sizeof(cRounds)), VINF_SUCCESS);
+            if (RT_SUCCESS(rc))
+            {
+                rc = testSessionDataWriteMessages(hIpcSession, cRounds);
+                if (RT_SUCCESS(rc))
+                    rc = testSessionDataReadMessages(hIpcSession, cRounds);
+            }
+
+            RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+            RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+        }
+        else
+        {
+            RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+            break;
+        }
+    }
+    RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+    return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionDataChild(RTTHREAD hSelf, void *pvUser)
+{
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+    RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+    /*
+     * Connect.
+     */
+    RTLOCALIPCSESSION hClientSession;
+    RTTESTI_CHECK_RC_RET(RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionData", 0 /*fFlags*/),
+                         VINF_SUCCESS, rcCheck);
+
+    /*
+     * The server first sends us a rounds count.
+     */
+    int rc;
+    uint32_t cRounds = 0;
+    RTTESTI_CHECK_RC(rc = RTLocalIpcSessionRead(hClientSession, &cRounds, sizeof(cRounds), NULL), VINF_SUCCESS);
+    if (RT_SUCCESS(rc))
+    {
+        if (cRounds >= 32 && cRounds <= _1K)
+        {
+            rc = testSessionDataReadMessages(hClientSession, cRounds);
+            if (RT_SUCCESS(rc))
+                rc = testSessionDataWriteMessages(hClientSession, cRounds);
+        }
+        else
+            RTTestIFailed("cRounds=%#x is out of range", cRounds);
+    }
+
+    RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED);
+
+    return rc;
+}
+
+
+/**
+ * @note This is identical to testSessionWait with a couple of strings, function
+ *       pointers, and timeouts replaced.
+ */
+static void testSessionData(const char *pszExecPath)
+{
+    RTTestISub(!pszExecPath ? "Data exchange with thread" : "Data exchange with child");
+
+    /*
+     * Create the test server.
+     */
+    RTLOCALIPCSERVER hIpcServer;
+    RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionData", 0), VINF_SUCCESS);
+
+    /*
+     * Create worker thread that listens and processes incoming connections
+     * until cancelled.
+     */
+    int rc;
+    RTTHREAD hListenThread;
+    RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testSessionDataThread, hIpcServer, 0 /* Stack */,
+                                            RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-3"));
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Create a client thread or process.
+         */
+        RTPROCESS hClientProc   = NIL_RTPROCESS;
+        RTTHREAD  hClientThread = NIL_RTTHREAD;
+        if (pszExecPath)
+        {
+            const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionDataChild", NULL };
+            RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+        }
+        else
+            RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionDataChild, g_hTest, 0 /*cbStack*/,
+                                                    RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-2"));
+
+        /*
+         * Wait for the server thread to indicate that it has processed one
+         * connection, then shut it all down.
+         */
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN * 3));
+
+        RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+        int rcThread;
+        RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread), VINF_SUCCESS);
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+
+        RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+        /*
+         * Check that client ran successfully.
+         */
+        if (pszExecPath)
+        {
+            if (hClientProc != NIL_RTPROCESS)
+            {
+                RTPROCSTATUS ProcStatus;
+                RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+                if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+                    RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+            }
+        }
+        else if (hClientThread != NIL_RTTHREAD)
+        {
+            RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+            if (RT_SUCCESS(rc))
+                RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+        }
+    }
+}
+
+
+/*********************************************************************************************************************************
+*                                                                                                                                *
+*   testSessionPerf - Performance measurements.                                                                                  *
+*                                                                                                                                *
+*********************************************************************************************************************************/
+
+#define IPC_PERF_LAST_MSG   UINT32_C(0x7fffeeee)
+#define IPC_PERF_MSG_REPLY(uMsg)    ((uMsg) | RT_BIT_32(31))
+
+
+static DECLCALLBACK(int) testSessionPerfThread(RTTHREAD hSelf, void *pvUser)
+{
+    RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+
+    int rc;
+    for (;;)
+    {
+        RTLOCALIPCSESSION hIpcSession;
+        rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession);
+        if (RT_SUCCESS(rc))
+        {
+            RTTestIPrintf(RTTESTLVL_INFO, "testSessionPerfThread: Got new client connection\n");
+
+            /* The server is the initator, so we start sending messages. */
+            uint64_t cNsElapsed = _4G;
+            uint64_t nsStart    = RTTimeNanoTS();
+            uint32_t cMessages  = 0;
+            for (;; )
+            {
+                uint32_t uMsg = cMessages;
+                RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hIpcSession, &uMsg, sizeof(uMsg)), VINF_SUCCESS);
+                uMsg = UINT32_MAX;
+                RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hIpcSession, &uMsg, sizeof(uMsg), NULL), VINF_SUCCESS);
+                if (uMsg == IPC_PERF_MSG_REPLY(cMessages))
+                { /* likely */ }
+                else
+                {
+                    RTTestIFailed("uMsg=%#x expected %#x", uMsg, IPC_PERF_MSG_REPLY(cMessages));
+                    rc = VERR_OUT_OF_RANGE;
+                    break;
+                }
+
+                /* next */
+                cMessages++;
+                if (cMessages & _16K)
+                { /* likely */ }
+                else
+                {
+                    cNsElapsed = RTTimeNanoTS() - nsStart;
+                    if (cNsElapsed > 2*RT_NS_1SEC_64)
+                    {
+                        uMsg = IPC_PERF_LAST_MSG;
+                        RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hIpcSession, &uMsg, sizeof(uMsg)), VINF_SUCCESS);
+                        break;
+                    }
+                }
+            }
+            if (RT_SUCCESS(rc))
+            {
+                RTThreadSleep(8); /* windows output fudge (purely esthetical) */
+                RTTestIValue("roundtrip", cNsElapsed / cMessages, RTTESTUNIT_NS_PER_ROUND_TRIP);
+                RTTestIValue("roundtrips", RT_NS_1SEC / (cNsElapsed / cMessages), RTTESTUNIT_OCCURRENCES_PER_SEC);
+            }
+
+            RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED);
+            RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+        }
+        else
+        {
+            RTTESTI_CHECK_RC(rc, VERR_CANCELLED);
+            break;
+        }
+    }
+    RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf));
+    return rc;
+}
+
+
+/**
+ * Used both as a thread procedure and child process worker.
+ */
+static DECLCALLBACK(int) tstRTLocalIpcSessionPerfChild(RTTHREAD hSelf, void *pvUser)
+{
+    RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck);
+    RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser);
+
+    /*
+     * Connect.
+     */
+    RTLOCALIPCSESSION hClientSession;
+    RTTESTI_CHECK_RC_RET(RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionPerf", 0 /*fFlags*/),
+                         VINF_SUCCESS, rcCheck);
+
+    /*
+     * Process messages.  Server does all the timing and stuff.
+     */
+    int rc = VINF_SUCCESS;
+    for (uint32_t cMessages = 0; ; cMessages++)
+    {
+        /* Read the next message from the server. */
+        uint32_t uMsg = UINT32_MAX;
+        RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionRead(hClientSession, &uMsg, sizeof(uMsg), NULL), VINF_SUCCESS);
+        if (uMsg == cMessages)
+        {
+            uMsg = IPC_PERF_MSG_REPLY(uMsg);
+            RTTESTI_CHECK_RC_BREAK(rc = RTLocalIpcSessionWrite(hClientSession, &uMsg, sizeof(uMsg)), VINF_SUCCESS);
+        }
+        else if (uMsg == IPC_PERF_LAST_MSG)
+            break;
+        else
+        {
+            RTTestIFailed("uMsg=%#x expected %#x", uMsg, cMessages);
+            rc = VERR_OUT_OF_RANGE;
+            break;
+        }
+    }
+
+    RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED);
+    return rc;
+}
+
+
+/**
+ * @note This is identical to testSessionWait with a couple of string and
+ *       function pointers replaced.
+ */
+static void testSessionPerf(const char *pszExecPath)
+{
+    RTTestISub(!pszExecPath ? "Thread performance" : "Child performance");
+
+    /*
+     * Create the test server.
+     */
+    RTLOCALIPCSERVER hIpcServer;
+    RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionPerf", 0), VINF_SUCCESS);
+
+    /*
+     * Create worker thread that listens and processes incoming connections
+     * until cancelled.
+     */
+    int rc;
+    RTTHREAD hListenThread;
+    RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testSessionPerfThread, hIpcServer, 0 /* Stack */,
+                                            RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-3"));
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Create a client thread or process.
+         */
+        RTPROCESS hClientProc   = NIL_RTPROCESS;
+        RTTHREAD  hClientThread = NIL_RTTHREAD;
+        if (pszExecPath)
+        {
+            const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionPerfChild", NULL };
+            RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc));
+        }
+        else
+            RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionPerfChild, g_hTest, 0 /*cbStack*/,
+                                                    RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-2"));
+
+        /*
+         * Wait for the server thread to indicate that it has processed one
+         * connection, then shut it all down.
+         */
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN / 2));
+
+        RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS);
+        int rcThread;
+        RTTESTI_CHECK_RC(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread), VINF_SUCCESS);
+        if (RT_SUCCESS(rc))
+            RTTESTI_CHECK_RC(rcThread, VERR_CANCELLED);
+
+        RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED);
+
+        /*
+         * Check that client ran successfully.
+         */
+        if (pszExecPath)
+        {
+            if (hClientProc != NIL_RTPROCESS)
+            {
+                RTPROCSTATUS ProcStatus;
+                RTTESTI_CHECK_RC_OK(rc = RTProcWait(hClientProc, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus));
+                if (RT_SUCCESS(rc) && (ProcStatus.enmReason != RTPROCEXITREASON_NORMAL || ProcStatus.iStatus != 0))
+                    RTTestIFailed("Chiled exited with enmReason=%d iStatus=%d", ProcStatus.enmReason, ProcStatus.iStatus);
+            }
+        }
+        else if (hClientThread != NIL_RTTHREAD)
+        {
+            RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hClientThread, RT_MS_1MIN / 2, &rcThread));
+            if (RT_SUCCESS(rc))
+                RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS);
+        }
+    }
+}
+
+
+int main(int argc, char **argv)
+{
+    int rc = RTR3InitExe(argc, &argv, 0);
+    if (RT_FAILURE(rc))
+        return RTMsgInitFailure(rc);
+
+    /*
+     * Main process.
+     */
+    if (argc == 1)
+    {
+        rc = RTTestCreate("tstRTLocalIpc", &g_hTest);
+        if (RT_FAILURE(rc))
+            return RTEXITCODE_FAILURE;
+        RTTestBanner(g_hTest);
+
+        /* Basics first. */
+        bool fMayPanic = RTAssertSetMayPanic(false);
+        bool fQuiet    = RTAssertSetQuiet(true);
+        testBasics();
+        RTAssertSetMayPanic(fMayPanic);
+        RTAssertSetQuiet(fQuiet);
+
+        /* Do real tests if the basics are fine. */
+        char szExecPath[RTPATH_MAX];
+        if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)))
+        {
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionConnection(NULL);
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionConnection(szExecPath);
+
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionWait(NULL);
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionWait(szExecPath);
+
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionData(NULL);
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionData(szExecPath);
+
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionPerf(NULL);
+            if (RTTestErrorCount(g_hTest) == 0)
+                testSessionPerf(szExecPath);
+        }
+        else
+            RTTestIFailed("RTProcGetExecutablePath failed");
+    }
+    /*
+     * Child process.
+     */
+    else if (   argc == 3
+             && !strcmp(argv[1], "child"))
+    {
+        rc = RTTestCreateChild(argv[2], &g_hTest);
+        if (RT_FAILURE(rc))
+            return RTEXITCODE_FAILURE;
+
+        if (!strcmp(argv[2], "tstRTLocalIpcSessionConnectionChild"))
+            tstRTLocalIpcSessionConnectionChild(RTThreadSelf(), g_hTest);
+        else if (!strcmp(argv[2], "tstRTLocalIpcSessionWaitChild"))
+            tstRTLocalIpcSessionWaitChild(RTThreadSelf(), g_hTest);
+        else if (!strcmp(argv[2], "tstRTLocalIpcSessionDataChild"))
+            tstRTLocalIpcSessionDataChild(RTThreadSelf(), g_hTest);
+        else if (!strcmp(argv[2], "tstRTLocalIpcSessionPerfChild"))
+            tstRTLocalIpcSessionPerfChild(RTThreadSelf(), g_hTest);
+        else
+            RTTestIFailed("Unknown child function '%s'", argv[2]);
+    }
+    /*
+     * Invalid parameters.
+     */
+    else
+        return RTEXITCODE_SYNTAX;
+
+    /*
+     * Summary.
+     */
+    return RTTestSummaryAndDestroy(g_hTest);
+}
+
-- 
cgit v1.2.3