summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/utils/usb
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/ValidationKit/utils/usb/Makefile.kmk74
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTest.cpp667
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestService.cpp1658
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp211
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h546
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp462
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp470
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp179
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h132
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp263
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h118
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h226
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp448
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h105
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp120
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h373
-rw-r--r--src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp512
17 files changed, 6564 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/usb/Makefile.kmk b/src/VBox/ValidationKit/utils/usb/Makefile.kmk
new file mode 100644
index 00000000..e8dc1923
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/Makefile.kmk
@@ -0,0 +1,74 @@
+# $Id: Makefile.kmk $
+## @file
+# VirtualBox Validation Kit - USB test helpers.
+#
+
+#
+# Copyright (C) 2014-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>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox 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.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# USB Linux test frontend.
+#
+ifeq ($(KBUILD_TARGET),linux)
+ PROGRAMS += UsbTest
+ UsbTest_TEMPLATE = VBoxValidationKitR3
+ UsbTest_SOURCES = UsbTest.cpp
+endif
+
+PROGRAMS += UsbTestService
+UsbTestService_TEMPLATE = VBoxValidationKitR3
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ UsbTestService_DEFS = \
+ KBUILD_TARGET="$(KBUILD_TARGET)" \
+ KBUILD_TARGET_ARCH="$(KBUILD_TARGET_ARCH)"
+else
+ UsbTestService_DEFS = \
+ KBUILD_TARGET=\"$(KBUILD_TARGET)\" \
+ KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\"
+endif
+UsbTestService_SOURCES = \
+ UsbTestService.cpp \
+ UsbTestServiceGadgetCfg.cpp \
+ UsbTestServiceGadgetClassTest.cpp \
+ UsbTestServiceGadgetHost.cpp \
+ UsbTestServiceGadgetHostUsbIp.cpp \
+ UsbTestServiceGadget.cpp \
+ UsbTestServiceProtocol.cpp \
+ UsbTestServiceTcp.cpp
+UsbTestService_SOURCES.linux = \
+ UsbTestServicePlatform-linux.cpp
+
+$(evalcall def_vbox_validationkit_process_python_sources)
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp
new file mode 100644
index 00000000..809e3fe1
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp
@@ -0,0 +1,667 @@
+/* $Id: UsbTest.cpp $ */
+/** @file
+ * UsbTest - User frontend for the Linux usbtest USB test and benchmarking module.
+ * Integrates with our test framework for nice outputs.
+ */
+
+/*
+ * Copyright (C) 2014-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/path.h>
+#include <iprt/param.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include <iprt/linux/sysfs.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * USB test request data.
+ * There is no public header with this information so we define it ourself here.
+ */
+typedef struct USBTESTPARMS
+{
+ /** Specifies the test to run. */
+ uint32_t idxTest;
+ /** How many iterations the test should be executed. */
+ uint32_t cIterations;
+ /** Size of the data packets. */
+ uint32_t cbData;
+ /** Size of */
+ uint32_t cbVariation;
+ /** Length of the S/G list for the test. */
+ uint32_t cSgLength;
+ /** Returned time data after completing the test. */
+ struct timeval TimeTest;
+} USBTESTPARAMS;
+/** Pointer to a test parameter structure. */
+typedef USBTESTPARAMS *PUSBTESTPARAMS;
+
+/**
+ * USB device descriptor. Used to search for the test device based
+ * on the vendor and product id.
+ */
+#pragma pack(1)
+typedef struct USBDEVDESC
+{
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t bcdUSB;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bMaxPacketSize0;
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+ uint8_t iManufacturer;
+ uint8_t iProduct;
+ uint8_t iSerialNumber;
+ uint8_t bNumConfigurations;
+} USBDEVDESC;
+#pragma pack()
+
+#define USBTEST_REQUEST _IOWR('U', 100, USBTESTPARMS)
+
+/**
+ * Callback to set up the test parameters for a specific test.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code
+ * otherwise indicating the kind of error.
+ * @param idxTest The test index.
+ * @param pszTest Test name.
+ * @param pParams The USB test parameters to set up.
+ */
+typedef DECLCALLBACKTYPE(int, FNUSBTESTPARAMSSETUP,(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams));
+/** Pointer to a USB test parameters setup callback. */
+typedef FNUSBTESTPARAMSSETUP *PFNUSBTESTPARAMSSETUP;
+
+/**
+ * USB test descriptor.
+ */
+typedef struct USBTESTDESC
+{
+ /** (Sort of) Descriptive test name. */
+ const char *pszName;
+ /** Flag whether the test is excluded. */
+ bool fExcluded;
+ /** The parameter setup callback. */
+ PFNUSBTESTPARAMSSETUP pfnParamsSetup;
+} USBTESTDESC;
+/** Pointer a USB test descriptor. */
+typedef USBTESTDESC *PUSBTESTDESC;
+
+/**
+ * USB speed values.
+ */
+typedef enum USBTESTSPEED
+{
+ USBTESTSPEED_ANY = 0,
+ USBTESTSPEED_UNKNOWN,
+ USBTESTSPEED_LOW,
+ USBTESTSPEED_FULL,
+ USBTESTSPEED_HIGH,
+ USBTESTSPEED_SUPER
+} USBTESTSPEED;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Some forward method declarations. */
+static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams);
+static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams);
+
+/** Command line parameters */
+static const RTGETOPTDEF g_aCmdOptions[] =
+{
+ {"--device", 'd', RTGETOPT_REQ_STRING },
+ {"--help", 'h', RTGETOPT_REQ_NOTHING},
+ {"--exclude", 'e', RTGETOPT_REQ_UINT32},
+ {"--exclude-all", 'a', RTGETOPT_REQ_NOTHING},
+ {"--include", 'i', RTGETOPT_REQ_UINT32},
+ {"--expected-speed", 's', RTGETOPT_REQ_STRING }
+};
+
+static USBTESTDESC g_aTests[] =
+{
+ /* pszTest fExcluded pfnParamsSetup */
+ {"NOP", false, usbTestParamsSetupReadWrite},
+ {"Non-queued Bulk write", false, usbTestParamsSetupReadWrite},
+ {"Non-queued Bulk read", false, usbTestParamsSetupReadWrite},
+ {"Non-queued Bulk write variabe size", false, usbTestParamsSetupReadWrite},
+ {"Non-queued Bulk read variabe size", false, usbTestParamsSetupReadWrite},
+ {"Queued Bulk write", false, usbTestParamsSetupReadWrite},
+ {"Queued Bulk read", false, usbTestParamsSetupReadWrite},
+ {"Queued Bulk write variabe size", false, usbTestParamsSetupReadWrite},
+ {"Queued Bulk read variabe size", false, usbTestParamsSetupReadWrite},
+ {"Chapter 9 Control Test", false, usbTestParamsSetupReadWrite},
+ {"Queued control messaging", false, usbTestParamsSetupReadWrite},
+ {"Unlink reads", false, usbTestParamsSetupReadWrite},
+ {"Unlink writes", false, usbTestParamsSetupReadWrite},
+ {"Set/Clear halts", false, usbTestParamsSetupReadWrite},
+ {"Control writes", false, usbTestParamsSetupControlWrites},
+ {"Isochronous write", false, usbTestParamsSetupReadWrite},
+ {"Isochronous read", false, usbTestParamsSetupReadWrite},
+ {"Bulk write unaligned (DMA)", false, usbTestParamsSetupReadWrite},
+ {"Bulk read unaligned (DMA)", false, usbTestParamsSetupReadWrite},
+ {"Bulk write unaligned (no DMA)", false, usbTestParamsSetupReadWrite},
+ {"Bulk read unaligned (no DMA)", false, usbTestParamsSetupReadWrite},
+ {"Control writes unaligned", false, usbTestParamsSetupControlWrites},
+ {"Isochronous write unaligned", false, usbTestParamsSetupReadWrite},
+ {"Isochronous read unaligned", false, usbTestParamsSetupReadWrite},
+ {"Unlink queued Bulk", false, usbTestParamsSetupReadWrite}
+};
+
+/** The test handle. */
+static RTTEST g_hTest;
+/** The expected device speed. */
+static USBTESTSPEED g_enmSpeed = USBTESTSPEED_ANY;
+
+/**
+ * Setup callback for basic read/write (bulk, isochronous) tests.
+ *
+ * @copydoc FNUSBTESTPARAMSSETUP
+ */
+static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams)
+{
+ NOREF(idxTest);
+ NOREF(pszTest);
+
+ pParams->cIterations = 1000;
+ pParams->cbData = 512;
+ pParams->cbVariation = 512;
+ pParams->cSgLength = 32;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Setup callback for the control writes test.
+ *
+ * @copydoc FNUSBTESTPARAMSSETUP
+ */
+static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams)
+{
+ NOREF(idxTest);
+ NOREF(pszTest);
+
+ pParams->cIterations = 1000;
+ pParams->cbData = 512;
+ /*
+ * Must be smaller than cbData or the parameter check in the usbtest module fails,
+ * no idea yet why it must be this.
+ */
+ pParams->cbVariation = 256;
+ pParams->cSgLength = 32;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Shows tool usage text.
+ */
+static void usbTestUsage(PRTSTREAM pStrm)
+{
+ char szExec[RTPATH_MAX];
+ RTStrmPrintf(pStrm, "usage: %s [options]\n",
+ RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec))));
+ RTStrmPrintf(pStrm, "\n");
+ RTStrmPrintf(pStrm, "options: \n");
+
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++)
+ {
+ const char *pszHelp;
+ switch (g_aCmdOptions[i].iShort)
+ {
+ case 'h':
+ pszHelp = "Displays this help and exit";
+ break;
+ case 'd':
+ pszHelp = "Use the specified test device";
+ break;
+ case 'e':
+ pszHelp = "Exclude the given test id from the list";
+ break;
+ case 'a':
+ pszHelp = "Exclude all tests from the list (useful to enable single tests later with --include)";
+ break;
+ case 'i':
+ pszHelp = "Include the given test id in the list";
+ break;
+ case 's':
+ pszHelp = "The device speed to expect";
+ break;
+ default:
+ pszHelp = "Option undocumented";
+ break;
+ }
+ char szOpt[256];
+ RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort);
+ RTStrmPrintf(pStrm, " %-30s%s\n", szOpt, pszHelp);
+ }
+}
+
+/**
+ * Searches for a USB test device and returns the bus and device ID and the device speed.
+ */
+static int usbTestDeviceQueryBusAndDevId(uint16_t *pu16BusId, uint16_t *pu16DevId, USBTESTSPEED *penmSpeed)
+{
+ bool fFound = false;
+
+#define USBTEST_USB_DEV_SYSFS "/sys/bus/usb/devices/"
+
+ RTDIR hDirUsb = NULL;
+ int rc = RTDirOpen(&hDirUsb, USBTEST_USB_DEV_SYSFS);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTDIRENTRY DirUsbBus;
+ rc = RTDirRead(hDirUsb, &DirUsbBus, NULL);
+ if ( RT_SUCCESS(rc)
+ && RTStrNCmp(DirUsbBus.szName, "usb", 3)
+ && RTLinuxSysFsExists(USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName))
+ {
+ int64_t idVendor = 0;
+ int64_t idProduct = 0;
+ int64_t iBusId = 0;
+ int64_t iDevId = 0;
+ char aszSpeed[20];
+
+ rc = RTLinuxSysFsReadIntFile(16, &idVendor, USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsReadIntFile(16, &idProduct, USBTEST_USB_DEV_SYSFS "%s/idProduct", DirUsbBus.szName);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsReadIntFile(16, &iBusId, USBTEST_USB_DEV_SYSFS "%s/busnum", DirUsbBus.szName);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsReadIntFile(16, &iDevId, USBTEST_USB_DEV_SYSFS "%s/devnum", DirUsbBus.szName);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsReadStrFile(&aszSpeed[0], sizeof(aszSpeed), NULL, USBTEST_USB_DEV_SYSFS "%s/speed", DirUsbBus.szName);
+
+ if ( RT_SUCCESS(rc)
+ && idVendor == 0x0525
+ && idProduct == 0xa4a0)
+ {
+ if (penmSpeed)
+ {
+ /* Parse the speed. */
+ if (!RTStrCmp(&aszSpeed[0], "1.5"))
+ *penmSpeed = USBTESTSPEED_LOW;
+ else if (!RTStrCmp(&aszSpeed[0], "12"))
+ *penmSpeed = USBTESTSPEED_FULL;
+ else if (!RTStrCmp(&aszSpeed[0], "480"))
+ *penmSpeed = USBTESTSPEED_HIGH;
+ else if ( !RTStrCmp(&aszSpeed[0], "5000")
+ || !RTStrCmp(&aszSpeed[0], "10000"))
+ *penmSpeed = USBTESTSPEED_SUPER;
+ else
+ *penmSpeed = USBTESTSPEED_UNKNOWN;
+ }
+
+ if (pu16BusId)
+ *pu16BusId = (uint16_t)iBusId;
+ if (pu16DevId)
+ *pu16DevId = (uint16_t)iDevId;
+ fFound = true;
+ break;
+ }
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+
+ } while ( RT_SUCCESS(rc)
+ && !fFound);
+
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+
+ RTDirClose(hDirUsb);
+ }
+
+ if (RT_SUCCESS(rc) && !fFound)
+ rc = VERR_NOT_FOUND;
+
+ return rc;
+}
+
+/**
+ * Search for a USB test device and return the device path.
+ *
+ * @returns Path to the USB test device or NULL if none was found.
+ */
+static char *usbTestFindDevice(void)
+{
+ /*
+ * Very crude and quick way to search for the correct test device.
+ * Assumption is that the path looks like /dev/bus/usb/%3d/%3d.
+ */
+ char *pszDevPath = NULL;
+
+ RTDIR hDirUsb = NULL;
+ int rc = RTDirOpen(&hDirUsb, "/dev/bus/usb");
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTDIRENTRY DirUsbBus;
+ rc = RTDirRead(hDirUsb, &DirUsbBus, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ char aszPath[RTPATH_MAX + 1];
+ RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), "/dev/bus/usb/%s", DirUsbBus.szName);
+
+ RTDIR hDirUsbBus = NULL;
+ rc = RTDirOpen(&hDirUsbBus, &aszPath[0]);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTDIRENTRY DirUsbDev;
+ rc = RTDirRead(hDirUsbBus, &DirUsbDev, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ char aszPathDev[RTPATH_MAX + 1];
+ RTStrPrintf(&aszPathDev[0], RT_ELEMENTS(aszPathDev), "/dev/bus/usb/%s/%s",
+ DirUsbBus.szName, DirUsbDev.szName);
+
+ RTFILE hFileDev;
+ rc = RTFileOpen(&hFileDev, aszPathDev, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ USBDEVDESC DevDesc;
+
+ rc = RTFileRead(hFileDev, &DevDesc, sizeof(DevDesc), NULL);
+ RTFileClose(hFileDev);
+
+ if ( RT_SUCCESS(rc)
+ && DevDesc.idVendor == 0x0525
+ && DevDesc.idProduct == 0xa4a0)
+ pszDevPath = RTStrDup(aszPathDev);
+ }
+
+ rc = VINF_SUCCESS;
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+
+ } while ( RT_SUCCESS(rc)
+ && !pszDevPath);
+
+ rc = VINF_SUCCESS;
+ RTDirClose(hDirUsbBus);
+ }
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ } while ( RT_SUCCESS(rc)
+ && !pszDevPath);
+
+ RTDirClose(hDirUsb);
+ }
+
+ return pszDevPath;
+}
+
+static int usbTestIoctl(int iDevFd, int iInterface, PUSBTESTPARAMS pParams)
+{
+ struct usbdevfs_ioctl IoCtlData;
+
+ IoCtlData.ifno = iInterface;
+ IoCtlData.ioctl_code = (int)USBTEST_REQUEST;
+ IoCtlData.data = pParams;
+ return ioctl(iDevFd, USBDEVFS_IOCTL, &IoCtlData);
+}
+
+/**
+ * Test execution worker.
+ *
+ * @param pszDevice The device to use for testing.
+ */
+static void usbTestExec(const char *pszDevice)
+{
+ int iDevFd;
+
+ RTTestSub(g_hTest, "Opening device");
+ iDevFd = open(pszDevice, O_RDWR);
+ if (iDevFd != -1)
+ {
+ USBTESTPARAMS Params;
+
+ RTTestPassed(g_hTest, "Opening device successful\n");
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
+ {
+ RTTestSub(g_hTest, g_aTests[i].pszName);
+
+ if (g_aTests[i].fExcluded)
+ {
+ RTTestSkipped(g_hTest, "Excluded from list");
+ continue;
+ }
+
+ int rc = g_aTests[i].pfnParamsSetup(i, g_aTests[i].pszName, &Params);
+ if (RT_SUCCESS(rc))
+ {
+ Params.idxTest = i;
+
+ /* Assume the test interface has the number 0 for now. */
+ int rcPosix = usbTestIoctl(iDevFd, 0, &Params);
+ if (rcPosix < 0 && errno == EOPNOTSUPP)
+ {
+ RTTestSkipped(g_hTest, "Not supported");
+ continue;
+ }
+
+ if (rcPosix < 0)
+ {
+ /*
+ * The error status code of the unlink testcase is
+ * offset by 2000 for the sync and 1000 for the sync code path
+ * (see drivers/usb/misc/usbtest.c in the Linux kernel sources).
+ *
+ * Adjust to the actual status code so converting doesn't assert.
+ */
+ int iTmpErrno = errno;
+ if (iTmpErrno >= 2000)
+ iTmpErrno -= 2000;
+ else if (iTmpErrno >= 1000)
+ iTmpErrno -= 1000;
+ RTTestFailed(g_hTest, "Test failed with %Rrc\n", RTErrConvertFromErrno(iTmpErrno));
+ }
+ else
+ {
+ uint64_t u64Ns = Params.TimeTest.tv_sec * RT_NS_1SEC + Params.TimeTest.tv_usec * RT_NS_1US;
+ RTTestValue(g_hTest, "Runtime", u64Ns, RTTESTUNIT_NS);
+ }
+ }
+ else
+ RTTestFailed(g_hTest, "Setting up test parameters failed with %Rrc\n", rc);
+ RTTestSubDone(g_hTest);
+ }
+
+ close(iDevFd);
+ }
+ else
+ RTTestFailed(g_hTest, "Opening device failed with %Rrc\n", RTErrConvertFromErrno(errno));
+
+}
+
+int main(int argc, char *argv[])
+{
+ /*
+ * Init IPRT and globals.
+ */
+ int rc = RTTestInitAndCreate("UsbTest", &g_hTest);
+ if (rc)
+ return rc;
+
+ /*
+ * Default values.
+ */
+ const char *pszDevice = NULL;
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */);
+ while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (rc)
+ {
+ case 'h':
+ usbTestUsage(g_pStdOut);
+ return RTEXITCODE_SUCCESS;
+ case 'd':
+ pszDevice = ValueUnion.psz;
+ break;
+ case 's':
+ if (!RTStrICmp(ValueUnion.psz, "Low"))
+ g_enmSpeed = USBTESTSPEED_LOW;
+ else if (!RTStrICmp(ValueUnion.psz, "Full"))
+ g_enmSpeed = USBTESTSPEED_FULL;
+ else if (!RTStrICmp(ValueUnion.psz, "High"))
+ g_enmSpeed = USBTESTSPEED_HIGH;
+ else if (!RTStrICmp(ValueUnion.psz, "Super"))
+ g_enmSpeed = USBTESTSPEED_SUPER;
+ else
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid speed passed to --expected-speed\n");
+ RTTestErrorInc(g_hTest);
+ return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion);
+ }
+ break;
+ case 'e':
+ if (ValueUnion.u32 < RT_ELEMENTS(g_aTests))
+ g_aTests[ValueUnion.u32].fExcluded = true;
+ else
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --exclude\n");
+ RTTestErrorInc(g_hTest);
+ return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion);
+ }
+ break;
+ case 'a':
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
+ g_aTests[i].fExcluded = true;
+ break;
+ case 'i':
+ if (ValueUnion.u32 < RT_ELEMENTS(g_aTests))
+ g_aTests[ValueUnion.u32].fExcluded = false;
+ else
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --include\n");
+ RTTestErrorInc(g_hTest);
+ return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion);
+ }
+ break;
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Start testing.
+ */
+ RTTestBanner(g_hTest);
+
+ /* Find the first test device if none was given. */
+ if (!pszDevice)
+ {
+ RTTestSub(g_hTest, "Detecting device");
+ pszDevice = usbTestFindDevice();
+ if (!pszDevice)
+ RTTestFailed(g_hTest, "Failed to find suitable device\n");
+
+ RTTestSubDone(g_hTest);
+ }
+
+ if (pszDevice)
+ {
+ /* First check that the requested speed matches. */
+ if (g_enmSpeed != USBTESTSPEED_ANY)
+ {
+ RTTestSub(g_hTest, "Checking correct device speed");
+
+ USBTESTSPEED enmSpeed = USBTESTSPEED_UNKNOWN;
+ rc = usbTestDeviceQueryBusAndDevId(NULL, NULL, &enmSpeed);
+ if (RT_SUCCESS(rc))
+ {
+ if (enmSpeed == g_enmSpeed)
+ RTTestPassed(g_hTest, "Reported device speed matches requested speed\n");
+ else
+ RTTestFailed(g_hTest, "Reported device speed doesn'match requested speed (%u vs %u)\n",
+ enmSpeed, g_enmSpeed);
+ }
+ else
+ RTTestFailed(g_hTest, "Failed to query device speed with rc=%Rrc\n", rc);
+
+ RTTestSubDone(g_hTest);
+ }
+ usbTestExec(pszDevice);
+ }
+
+ RTEXITCODE rcExit = RTTestSummaryAndDestroy(g_hTest);
+ return rcExit;
+}
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
new file mode 100644
index 00000000..0912ff51
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
@@ -0,0 +1,1658 @@
+/* $Id: UsbTestService.cpp $ */
+/** @file
+ * UsbTestService - Remote USB test configuration and execution server.
+ */
+
+/*
+ * Copyright (C) 2010-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/crc.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/initterm.h>
+#include <iprt/json.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include "UsbTestServiceInternal.h"
+#include "UsbTestServiceGadget.h"
+#include "UsbTestServicePlatform.h"
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+#define UTS_USBIP_PORT_FIRST 3240
+#define UTS_USBIP_PORT_LAST 3340
+
+/**
+ * UTS client state.
+ */
+typedef enum UTSCLIENTSTATE
+{
+ /** Invalid client state. */
+ UTSCLIENTSTATE_INVALID = 0,
+ /** Client is initialising, only the HOWDY and BYE packets are allowed. */
+ UTSCLIENTSTATE_INITIALISING,
+ /** Client is in fully cuntional state and ready to process all requests. */
+ UTSCLIENTSTATE_READY,
+ /** Client is destroying. */
+ UTSCLIENTSTATE_DESTROYING,
+ /** 32bit hack. */
+ UTSCLIENTSTATE_32BIT_HACK = 0x7fffffff
+} UTSCLIENTSTATE;
+
+/**
+ * UTS client instance.
+ */
+typedef struct UTSCLIENT
+{
+ /** List node for new clients. */
+ RTLISTNODE NdLst;
+ /** The current client state. */
+ UTSCLIENTSTATE enmState;
+ /** Transport backend specific data. */
+ PUTSTRANSPORTCLIENT pTransportClient;
+ /** Client hostname. */
+ char *pszHostname;
+ /** Gadget host handle. */
+ UTSGADGETHOST hGadgetHost;
+ /** Handle fo the current configured gadget. */
+ UTSGADGET hGadget;
+} UTSCLIENT;
+/** Pointer to a UTS client instance. */
+typedef UTSCLIENT *PUTSCLIENT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Transport layers.
+ */
+static const PCUTSTRANSPORT g_apTransports[] =
+{
+ &g_TcpTransport,
+ //&g_SerialTransport,
+ //&g_FileSysTransport,
+ //&g_GuestPropTransport,
+ //&g_TestDevTransport,
+};
+
+/** The select transport layer. */
+static PCUTSTRANSPORT g_pTransport;
+/** The config path. */
+static char g_szCfgPath[RTPATH_MAX];
+/** The scratch path. */
+static char g_szScratchPath[RTPATH_MAX];
+/** The default scratch path. */
+static char g_szDefScratchPath[RTPATH_MAX];
+/** The CD/DVD-ROM path. */
+static char g_szCdRomPath[RTPATH_MAX];
+/** The default CD/DVD-ROM path. */
+static char g_szDefCdRomPath[RTPATH_MAX];
+/** The operating system short name. */
+static char g_szOsShortName[16];
+/** The CPU architecture short name. */
+static char g_szArchShortName[16];
+/** The combined "OS.arch" name. */
+static char g_szOsDotArchShortName[32];
+/** The combined "OS/arch" name. */
+static char g_szOsSlashArchShortName[32];
+/** The executable suffix. */
+static char g_szExeSuff[8];
+/** The shell script suffix. */
+static char g_szScriptSuff[8];
+/** Whether to display the output of the child process or not. */
+static bool g_fDisplayOutput = true;
+/** Whether to terminate or not.
+ * @todo implement signals and stuff. */
+static bool volatile g_fTerminate = false;
+/** Configuration AST. */
+static RTJSONVAL g_hCfgJson = NIL_RTJSONVAL;
+/** Pipe for communicating with the serving thread about new clients. - read end */
+static RTPIPE g_hPipeR;
+/** Pipe for communicating with the serving thread about new clients. - write end */
+static RTPIPE g_hPipeW;
+/** Thread serving connected clients. */
+static RTTHREAD g_hThreadServing;
+/** Critical section protecting the list of new clients. */
+static RTCRITSECT g_CritSectClients;
+/** List of new clients waiting to be picked up by the client worker thread. */
+static RTLISTANCHOR g_LstClientsNew;
+/** First USB/IP port we can use. */
+static uint16_t g_uUsbIpPortFirst = UTS_USBIP_PORT_FIRST;
+/** Last USB/IP port we can use. */
+static uint16_t g_uUsbIpPortLast = UTS_USBIP_PORT_LAST;
+/** Next free port. */
+static uint16_t g_uUsbIpPortNext = UTS_USBIP_PORT_FIRST;
+
+
+
+/**
+ * Returns the string represenation of the given state.
+ */
+static const char *utsClientStateStringify(UTSCLIENTSTATE enmState)
+{
+ switch (enmState)
+ {
+ case UTSCLIENTSTATE_INVALID:
+ return "INVALID";
+ case UTSCLIENTSTATE_INITIALISING:
+ return "INITIALISING";
+ case UTSCLIENTSTATE_READY:
+ return "READY";
+ case UTSCLIENTSTATE_DESTROYING:
+ return "DESTROYING";
+ case UTSCLIENTSTATE_32BIT_HACK:
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Unknown state %#x\n", enmState));
+ return "UNKNOWN";
+}
+
+/**
+ * Calculates the checksum value, zero any padding space and send the packet.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPkt The packet to send. Must point to a correctly
+ * aligned buffer.
+ */
+static int utsSendPkt(PUTSCLIENT pClient, PUTSPKTHDR pPkt)
+{
+ Assert(pPkt->cb >= sizeof(*pPkt));
+ pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode));
+ if (pPkt->cb != RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT))
+ memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT) - pPkt->cb);
+
+ Log(("utsSendPkt: cb=%#x opcode=%.8s\n", pPkt->cb, pPkt->achOpcode));
+ Log2(("%.*Rhxd\n", RT_MIN(pPkt->cb, 256), pPkt));
+ int rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt);
+ while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !g_fTerminate)
+ rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt);
+ if (RT_FAILURE(rc))
+ Log(("utsSendPkt: rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Sends a babble reply and disconnects the client (if applicable).
+ *
+ * @param pClient The UTS client structure.
+ * @param pszOpcode The BABBLE opcode.
+ */
+static void utsReplyBabble(PUTSCLIENT pClient, const char *pszOpcode)
+{
+ UTSPKTHDR Reply;
+ Reply.cb = sizeof(Reply);
+ Reply.uCrc32 = 0;
+ memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode));
+
+ g_pTransport->pfnBabble(pClient->pTransportClient, &Reply, 20*1000);
+}
+
+/**
+ * Receive and validate a packet.
+ *
+ * Will send bable responses to malformed packets that results in a error status
+ * code.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param ppPktHdr Where to return the packet on success. Free
+ * with RTMemFree.
+ * @param fAutoRetryOnFailure Whether to retry on error.
+ */
+static int utsRecvPkt(PUTSCLIENT pClient, PPUTSPKTHDR ppPktHdr, bool fAutoRetryOnFailure)
+{
+ for (;;)
+ {
+ PUTSPKTHDR pPktHdr;
+ int rc = g_pTransport->pfnRecvPkt(pClient->pTransportClient, &pPktHdr);
+ if (RT_SUCCESS(rc))
+ {
+ /* validate the packet. */
+ if ( pPktHdr->cb >= sizeof(UTSPKTHDR)
+ && pPktHdr->cb < UTSPKT_MAX_SIZE)
+ {
+ Log2(("utsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n"
+ "%.*Rhxd\n",
+ pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode, RT_MIN(pPktHdr->cb, 256), pPktHdr));
+ uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0
+ ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode))
+ : 0;
+ if (pPktHdr->uCrc32 == uCrc32Calc)
+ {
+ AssertCompileMemberSize(UTSPKTHDR, achOpcode, 8);
+ if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0])
+ && RT_C_IS_UPPER(pPktHdr->achOpcode[1])
+ && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ')
+ )
+ {
+ Log(("utsRecvPkt: cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode));
+ *ppPktHdr = pPktHdr;
+ return rc;
+ }
+
+ rc = VERR_IO_BAD_COMMAND;
+ }
+ else
+ {
+ Log(("utsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n",
+ pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc));
+ rc = VERR_IO_CRC;
+ }
+ }
+ else
+ rc = VERR_IO_BAD_LENGTH;
+
+ /* Send babble reply and disconnect the client if the transport is
+ connection oriented. */
+ if (rc == VERR_IO_BAD_LENGTH)
+ utsReplyBabble(pClient, "BABBLE L");
+ else if (rc == VERR_IO_CRC)
+ utsReplyBabble(pClient, "BABBLE C");
+ else if (rc == VERR_IO_BAD_COMMAND)
+ utsReplyBabble(pClient, "BABBLE O");
+ else
+ utsReplyBabble(pClient, "BABBLE ");
+ RTMemFree(pPktHdr);
+ }
+
+ /* Try again or return failure? */
+ if ( g_fTerminate
+ || rc != VERR_INTERRUPTED
+ || !fAutoRetryOnFailure
+ )
+ {
+ Log(("utsRecvPkt: rc=%Rrc\n", rc));
+ return rc;
+ }
+ }
+}
+
+/**
+ * Make a simple reply, only status opcode.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pReply The reply packet.
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ * @param cbExtra Bytes in addition to the header.
+ */
+static int utsReplyInternal(PUTSCLIENT pClient, PUTSPKTSTS pReply, const char *pszOpcode, size_t cbExtra)
+{
+ /* copy the opcode, don't be too strict in case of a padding screw up. */
+ size_t cchOpcode = strlen(pszOpcode);
+ if (RT_LIKELY(cchOpcode == sizeof(pReply->Hdr.achOpcode)))
+ memcpy(pReply->Hdr.achOpcode, pszOpcode, sizeof(pReply->Hdr.achOpcode));
+ else
+ {
+ Assert(cchOpcode == sizeof(pReply->Hdr.achOpcode));
+ while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ')
+ cchOpcode--;
+ AssertMsgReturn(cchOpcode < sizeof(pReply->Hdr.achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4);
+ memcpy(pReply->Hdr.achOpcode, pszOpcode, cchOpcode);
+ memset(&pReply->Hdr.achOpcode[cchOpcode], ' ', sizeof(pReply->Hdr.achOpcode) - cchOpcode);
+ }
+
+ pReply->Hdr.cb = (uint32_t)sizeof(UTSPKTSTS) + (uint32_t)cbExtra;
+ pReply->Hdr.uCrc32 = 0;
+
+ return utsSendPkt(pClient, &pReply->Hdr);
+}
+
+/**
+ * Make a simple reply, only status opcode.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The original packet (for future use).
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ */
+static int utsReplySimple(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode)
+{
+ UTSPKTSTS Pkt;
+
+ RT_ZERO(Pkt);
+ Pkt.rcReq = VINF_SUCCESS;
+ Pkt.cchStsMsg = 0;
+ NOREF(pPktHdr);
+ return utsReplyInternal(pClient, &Pkt, pszOpcode, 0);
+}
+
+/**
+ * Acknowledges a packet with success.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The original packet (for future use).
+ */
+static int utsReplyAck(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ return utsReplySimple(pClient, pPktHdr, "ACK ");
+}
+
+/**
+ * Replies with a failure.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The original packet (for future use).
+ * @param rcReq Status code.
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ * @param rcReq The status code of the request.
+ * @param pszDetailFmt Longer description of the problem (format string).
+ * @param va Format arguments.
+ */
+static int utsReplyFailureV(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, va_list va)
+{
+ NOREF(pPktHdr);
+ union
+ {
+ UTSPKTSTS Hdr;
+ char ach[256];
+ } uPkt;
+
+ RT_ZERO(uPkt);
+ size_t cchDetail = RTStrPrintfV(&uPkt.ach[sizeof(UTSPKTSTS)],
+ sizeof(uPkt) - sizeof(UTSPKTSTS),
+ pszDetailFmt, va);
+ uPkt.Hdr.rcReq = rcReq;
+ uPkt.Hdr.cchStsMsg = cchDetail;
+ return utsReplyInternal(pClient, &uPkt.Hdr, pszOpcode, cchDetail + 1);
+}
+
+/**
+ * Replies with a failure.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The original packet (for future use).
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ * @param rcReq Status code.
+ * @param pszDetailFmt Longer description of the problem (format string).
+ * @param ... Format arguments.
+ */
+static int utsReplyFailure(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, ...)
+{
+ va_list va;
+ va_start(va, pszDetailFmt);
+ int rc = utsReplyFailureV(pClient, pPktHdr, pszOpcode, rcReq, pszDetailFmt, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Replies according to the return code.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet to reply to.
+ * @param rcOperation The status code to report.
+ * @param pszOperationFmt The operation that failed. Typically giving the
+ * function call with important arguments.
+ * @param ... Arguments to the format string.
+ */
+static int utsReplyRC(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...)
+{
+ if (RT_SUCCESS(rcOperation))
+ return utsReplyAck(pClient, pPktHdr);
+
+ char szOperation[128];
+ va_list va;
+ va_start(va, pszOperationFmt);
+ RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va);
+ va_end(va);
+
+ return utsReplyFailure(pClient, pPktHdr, "FAILED ", rcOperation, "%s failed with rc=%Rrc (opcode '%.8s')",
+ szOperation, rcOperation, pPktHdr->achOpcode);
+}
+
+#if 0 /* unused */
+/**
+ * Signal a bad packet minum size.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet to reply to.
+ * @param cbMin The minimum size.
+ */
+static int utsReplyBadMinSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cbMin)
+{
+ return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at least %zu bytes, got %u (opcode '%.8s')",
+ cbMin, pPktHdr->cb, pPktHdr->achOpcode);
+}
+#endif
+
+/**
+ * Signal a bad packet exact size.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet to reply to.
+ * @param cb The wanted size.
+ */
+static int utsReplyBadSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cb)
+{
+ return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at %zu bytes, got %u (opcode '%.8s')",
+ cb, pPktHdr->cb, pPktHdr->achOpcode);
+}
+
+#if 0 /* unused */
+/**
+ * Deals with a command that isn't implemented yet.
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet which opcode isn't implemented.
+ */
+static int utsReplyNotImplemented(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ return utsReplyFailure(pClient, pPktHdr, "NOT IMPL", VERR_NOT_IMPLEMENTED, "Opcode '%.8s' is not implemented", pPktHdr->achOpcode);
+}
+#endif
+
+/**
+ * Deals with a unknown command.
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet to reply to.
+ */
+static int utsReplyUnknown(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ return utsReplyFailure(pClient, pPktHdr, "UNKNOWN ", VERR_NOT_FOUND, "Opcode '%.8s' is not known", pPktHdr->achOpcode);
+}
+
+/**
+ * Deals with a command which contains an unterminated string.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet containing the unterminated string.
+ */
+static int utsReplyBadStrTermination(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ return utsReplyFailure(pClient, pPktHdr, "BAD TERM", VERR_INVALID_PARAMETER, "Opcode '%.8s' contains an unterminated string", pPktHdr->achOpcode);
+}
+
+/**
+ * Deals with a command sent in an invalid client state.
+ *
+ * @returns IPRT status code of the send.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The packet containing the unterminated string.
+ */
+static int utsReplyInvalidState(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ return utsReplyFailure(pClient, pPktHdr, "INVSTATE", VERR_INVALID_STATE, "Opcode '%.8s' is not supported at client state '%s",
+ pPktHdr->achOpcode, utsClientStateStringify(pClient->enmState));
+}
+
+/**
+ * Parses an unsigned integer from the given value string.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given maximum.
+ * @param pszVal The value string.
+ * @param uMax The maximum value.
+ * @param pu64 Where to store the parsed number on success.
+ */
+static int utsDoGadgetCreateCfgParseUInt(const char *pszVal, uint64_t uMax, uint64_t *pu64)
+{
+ int rc = RTStrToUInt64Ex(pszVal, NULL, 0, pu64);
+ if (RT_SUCCESS(rc))
+ {
+ if (*pu64 > uMax)
+ rc = VERR_OUT_OF_RANGE;
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a signed integer from the given value string.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given range.
+ * @param pszVal The value string.
+ * @param iMin The minimum value.
+ * @param iMax The maximum value.
+ * @param pi64 Where to store the parsed number on success.
+ */
+static int utsDoGadgetCreateCfgParseInt(const char *pszVal, int64_t iMin, int64_t iMax, int64_t *pi64)
+{
+ int rc = RTStrToInt64Ex(pszVal, NULL, 0, pi64);
+ if (RT_SUCCESS(rc))
+ {
+ if ( *pi64 < iMin
+ || *pi64 > iMax)
+ rc = VERR_OUT_OF_RANGE;
+ }
+
+ return rc;
+}
+
+/**
+ * Parses the given config item and fills in the value according to the given type.
+ *
+ * @returns IPRT status code.
+ * @param pCfgItem The config item to parse.
+ * @param u32Type The config type.
+ * @param pszVal The value encoded as a string.
+ */
+static int utsDoGadgetCreateCfgParseItem(PUTSGADGETCFGITEM pCfgItem, uint32_t u32Type,
+ const char *pszVal)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (u32Type)
+ {
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_BOOLEAN;
+ if ( RTStrICmp(pszVal, "enabled")
+ || RTStrICmp(pszVal, "1")
+ || RTStrICmp(pszVal, "true"))
+ pCfgItem->Val.u.f = true;
+ else if ( RTStrICmp(pszVal, "disabled")
+ || RTStrICmp(pszVal, "0")
+ || RTStrICmp(pszVal, "false"))
+ pCfgItem->Val.u.f = false;
+ else
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_STRING:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_STRING;
+ pCfgItem->Val.u.psz = RTStrDup(pszVal);
+ if (!pCfgItem->Val.u.psz)
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT8;
+
+ uint64_t u64;
+ rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT8_MAX, &u64);
+ if (RT_SUCCESS(rc))
+ pCfgItem->Val.u.u8 = (uint8_t)u64;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT16;
+
+ uint64_t u64;
+ rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT16_MAX, &u64);
+ if (RT_SUCCESS(rc))
+ pCfgItem->Val.u.u16 = (uint16_t)u64;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT32;
+
+ uint64_t u64;
+ rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT32_MAX, &u64);
+ if (RT_SUCCESS(rc))
+ pCfgItem->Val.u.u32 = (uint32_t)u64;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT64;
+ rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT64_MAX, &pCfgItem->Val.u.u64);
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_INT8:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT8;
+
+ int64_t i64;
+ rc = utsDoGadgetCreateCfgParseInt(pszVal, INT8_MIN, INT8_MAX, &i64);
+ if (RT_SUCCESS(rc))
+ pCfgItem->Val.u.i8 = (int8_t)i64;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_INT16:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT16;
+
+ int64_t i64;
+ rc = utsDoGadgetCreateCfgParseInt(pszVal, INT16_MIN, INT16_MAX, &i64);
+ if (RT_SUCCESS(rc))
+ pCfgItem->Val.u.i16 = (int16_t)i64;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_INT32:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT32;
+
+ int64_t i64;
+ rc = utsDoGadgetCreateCfgParseInt(pszVal, INT32_MIN, INT32_MAX, &i64);
+ if (RT_SUCCESS(rc))
+ pCfgItem->Val.u.i32 = (int32_t)i64;
+ break;
+ }
+ case UTSPKT_GDGT_CFG_ITEM_TYPE_INT64:
+ {
+ pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT64;
+ rc = utsDoGadgetCreateCfgParseInt(pszVal, INT64_MIN, INT64_MAX, &pCfgItem->Val.u.i64);
+ break;
+ }
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+/**
+ * Creates the configuration from the given GADGET CREATE packet.
+ *
+ * @returns IPRT status code.
+ * @param pCfgItem The first config item header in the request packet.
+ * @param cCfgItems Number of config items in the packet to parse.
+ * @param cbPkt Number of bytes left in the packet for the config data.
+ * @param paCfg The array of configuration items to fill.
+ */
+static int utsDoGadgetCreateFillCfg(PUTSPKTREQGDGTCTORCFGITEM pCfgItem, unsigned cCfgItems,
+ size_t cbPkt, PUTSGADGETCFGITEM paCfg)
+{
+ int rc = VINF_SUCCESS;
+ unsigned idxCfg = 0;
+
+ while ( RT_SUCCESS(rc)
+ && cCfgItems
+ && cbPkt)
+ {
+ if (cbPkt >= sizeof(UTSPKTREQGDGTCTORCFGITEM))
+ {
+ cbPkt -= sizeof(UTSPKTREQGDGTCTORCFGITEM);
+ if (pCfgItem->u32KeySize + pCfgItem->u32ValSize >= cbPkt)
+ {
+ const char *pszKey = (const char *)(pCfgItem + 1);
+ const char *pszVal = pszKey + pCfgItem->u32KeySize;
+
+ /* Validate termination. */
+ if ( *(pszKey + pCfgItem->u32KeySize - 1) != '\0'
+ || *(pszVal + pCfgItem->u32ValSize - 1) != '\0')
+ rc = VERR_INVALID_PARAMETER;
+ else
+ {
+ paCfg[idxCfg].pszKey = RTStrDup(pszKey);
+
+ rc = utsDoGadgetCreateCfgParseItem(&paCfg[idxCfg], pCfgItem->u32Type, pszVal);
+ if (RT_SUCCESS(rc))
+ {
+ cbPkt -= pCfgItem->u32KeySize + pCfgItem->u32ValSize;
+ cCfgItems--;
+ idxCfg++;
+ pCfgItem = (PUTSPKTREQGDGTCTORCFGITEM)(pszVal + pCfgItem->u32ValSize);
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+/**
+ * Verifies and acknowledges a "BYE" request.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The howdy packet.
+ */
+static int utsDoBye(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ int rc;
+ if (pPktHdr->cb == sizeof(UTSPKTHDR))
+ rc = utsReplyAck(pClient, pPktHdr);
+ else
+ rc = utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTHDR));
+ return rc;
+}
+
+/**
+ * Verifies and acknowledges a "HOWDY" request.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The howdy packet.
+ */
+static int utsDoHowdy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pPktHdr->cb != sizeof(UTSPKTREQHOWDY))
+ return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQHOWDY));
+
+ if (pClient->enmState != UTSCLIENTSTATE_INITIALISING)
+ return utsReplyInvalidState(pClient, pPktHdr);
+
+ PUTSPKTREQHOWDY pReq = (PUTSPKTREQHOWDY)pPktHdr;
+
+ if (pReq->uVersion != UTS_PROTOCOL_VS)
+ return utsReplyRC(pClient, pPktHdr, VERR_VERSION_MISMATCH, "The given version %#x is not supported", pReq->uVersion);
+
+ /* Verify hostname string. */
+ if (pReq->cchHostname >= sizeof(pReq->achHostname))
+ return utsReplyBadSize(pClient, pPktHdr, sizeof(pReq->achHostname) - 1);
+
+ if (pReq->achHostname[pReq->cchHostname] != '\0')
+ return utsReplyBadStrTermination(pClient, pPktHdr);
+
+ /* Extract string. */
+ pClient->pszHostname = RTStrDup(&pReq->achHostname[0]);
+ if (!pClient->pszHostname)
+ return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for the hostname string");
+
+ if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_PHYSICAL)
+ return utsReplyRC(pClient, pPktHdr, VERR_NOT_SUPPORTED, "Physical connections are not yet supported");
+
+ if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_USBIP)
+ {
+ /* Set up the USB/IP server, find an unused port we can start the server on. */
+ UTSGADGETCFGITEM aCfg[2];
+
+ uint16_t uPort = g_uUsbIpPortNext;
+
+ if (g_uUsbIpPortNext == g_uUsbIpPortLast)
+ g_uUsbIpPortNext = g_uUsbIpPortFirst;
+ else
+ g_uUsbIpPortNext++;
+
+ aCfg[0].pszKey = "UsbIp/Port";
+ aCfg[0].Val.enmType = UTSGADGETCFGTYPE_UINT16;
+ aCfg[0].Val.u.u16 = uPort;
+ aCfg[1].pszKey = NULL;
+
+ rc = utsGadgetHostCreate(UTSGADGETHOSTTYPE_USBIP, &aCfg[0], &pClient->hGadgetHost);
+ if (RT_SUCCESS(rc))
+ {
+ /* Send the reply with the configured USB/IP port. */
+ UTSPKTREPHOWDY Rep;
+
+ RT_ZERO(Rep);
+
+ Rep.uVersion = UTS_PROTOCOL_VS;
+ Rep.fUsbConn = UTSPKT_HOWDY_CONN_F_USBIP;
+ Rep.uUsbIpPort = uPort;
+ Rep.cUsbIpDevices = 1;
+ Rep.cPhysicalDevices = 0;
+
+ rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS));
+ if (RT_SUCCESS(rc))
+ {
+ g_pTransport->pfnNotifyHowdy(pClient->pTransportClient);
+ pClient->enmState = UTSCLIENTSTATE_READY;
+ RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY);
+ }
+ }
+ else
+ return utsReplyRC(pClient, pPktHdr, rc, "Creating the USB/IP gadget host failed");
+ }
+ else
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "No access method requested");
+
+ return rc;
+}
+
+/**
+ * Verifies and processes a "GADGET CREATE" request.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The gadget create packet.
+ */
+static int utsDoGadgetCreate(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pPktHdr->cb < sizeof(UTSPKTREQGDGTCTOR))
+ return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCTOR));
+
+ if ( pClient->enmState != UTSCLIENTSTATE_READY
+ || pClient->hGadgetHost == NIL_UTSGADGETHOST)
+ return utsReplyInvalidState(pClient, pPktHdr);
+
+ PUTSPKTREQGDGTCTOR pReq = (PUTSPKTREQGDGTCTOR)pPktHdr;
+
+ if (pReq->u32GdgtType != UTSPKT_GDGT_CREATE_TYPE_TEST)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget type is not supported");
+
+ if (pReq->u32GdgtAccess != UTSPKT_GDGT_CREATE_ACCESS_USBIP)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget access method is not supported");
+
+ PUTSGADGETCFGITEM paCfg = NULL;
+ if (pReq->u32CfgItems > 0)
+ {
+ paCfg = (PUTSGADGETCFGITEM)RTMemAllocZ((pReq->u32CfgItems + 1) * sizeof(UTSGADGETCFGITEM));
+ if (RT_UNLIKELY(!paCfg))
+ return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for configration items");
+
+ rc = utsDoGadgetCreateFillCfg((PUTSPKTREQGDGTCTORCFGITEM)(pReq + 1), pReq->u32CfgItems,
+ pPktHdr->cb - sizeof(UTSPKTREQGDGTCTOR), paCfg);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(paCfg);
+ return utsReplyRC(pClient, pPktHdr, rc, "Failed to parse configuration");
+ }
+ }
+
+ rc = utsGadgetCreate(pClient->hGadgetHost, UTSGADGETCLASS_TEST, paCfg, &pClient->hGadget);
+ if (RT_SUCCESS(rc))
+ {
+ UTSPKTREPGDGTCTOR Rep;
+ RT_ZERO(Rep);
+
+ Rep.idGadget = 0;
+ Rep.u32BusId = utsGadgetGetBusId(pClient->hGadget);
+ Rep.u32DevId = utsGadgetGetDevId(pClient->hGadget);
+ rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS));
+ }
+ else
+ rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to create gadget with %Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Verifies and processes a "GADGET DESTROY" request.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The gadget destroy packet.
+ */
+static int utsDoGadgetDestroy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDTOR))
+ return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDTOR));
+
+ if ( pClient->enmState != UTSCLIENTSTATE_READY
+ || pClient->hGadgetHost == NIL_UTSGADGETHOST)
+ return utsReplyInvalidState(pClient, pPktHdr);
+
+ PUTSPKTREQGDGTDTOR pReq = (PUTSPKTREQGDGTDTOR)pPktHdr;
+
+ if (pReq->idGadget != 0)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid");
+ if (pClient->hGadget == NIL_UTSGADGET)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up");
+
+ utsGadgetRelease(pClient->hGadget);
+ pClient->hGadget = NIL_UTSGADGET;
+
+ return utsReplyAck(pClient, pPktHdr);
+}
+
+/**
+ * Verifies and processes a "GADGET CONNECT" request.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The gadget connect packet.
+ */
+static int utsDoGadgetConnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb != sizeof(UTSPKTREQGDGTCNCT))
+ return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCNCT));
+
+ if ( pClient->enmState != UTSCLIENTSTATE_READY
+ || pClient->hGadgetHost == NIL_UTSGADGETHOST)
+ return utsReplyInvalidState(pClient, pPktHdr);
+
+ PUTSPKTREQGDGTCNCT pReq = (PUTSPKTREQGDGTCNCT)pPktHdr;
+
+ if (pReq->idGadget != 0)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid");
+ if (pClient->hGadget == NIL_UTSGADGET)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up");
+
+ int rc = utsGadgetConnect(pClient->hGadget);
+ if (RT_SUCCESS(rc))
+ rc = utsReplyAck(pClient, pPktHdr);
+ else
+ rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to connect the gadget");
+
+ return rc;
+}
+
+/**
+ * Verifies and processes a "GADGET DISCONNECT" request.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure.
+ * @param pPktHdr The gadget disconnect packet.
+ */
+static int utsDoGadgetDisconnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDCNT))
+ return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDCNT));
+
+ if ( pClient->enmState != UTSCLIENTSTATE_READY
+ || pClient->hGadgetHost == NIL_UTSGADGETHOST)
+ return utsReplyInvalidState(pClient, pPktHdr);
+
+ PUTSPKTREQGDGTDCNT pReq = (PUTSPKTREQGDGTDCNT)pPktHdr;
+
+ if (pReq->idGadget != 0)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid");
+ if (pClient->hGadget == NIL_UTSGADGET)
+ return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up");
+
+ int rc = utsGadgetDisconnect(pClient->hGadget);
+ if (RT_SUCCESS(rc))
+ rc = utsReplyAck(pClient, pPktHdr);
+ else
+ rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to disconnect the gadget");
+
+ return rc;
+}
+
+/**
+ * Main request processing routine for each client.
+ *
+ * @returns IPRT status code.
+ * @param pClient The UTS client structure sending the request.
+ */
+static int utsClientReqProcess(PUTSCLIENT pClient)
+{
+ /*
+ * Read client command packet and process it.
+ */
+ PUTSPKTHDR pPktHdr = NULL;
+ int rc = utsRecvPkt(pClient, &pPktHdr, true /*fAutoRetryOnFailure*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do a string switch on the opcode bit.
+ */
+ /* Connection: */
+ if ( utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_HOWDY))
+ rc = utsDoHowdy(pClient, pPktHdr);
+ else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_BYE))
+ rc = utsDoBye(pClient, pPktHdr);
+ /* Gadget API. */
+ else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CREATE))
+ rc = utsDoGadgetCreate(pClient, pPktHdr);
+ else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DESTROY))
+ rc = utsDoGadgetDestroy(pClient, pPktHdr);
+ else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CONNECT))
+ rc = utsDoGadgetConnect(pClient, pPktHdr);
+ else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DISCONNECT))
+ rc = utsDoGadgetDisconnect(pClient, pPktHdr);
+ /* Misc: */
+ else
+ rc = utsReplyUnknown(pClient, pPktHdr);
+
+ RTMemFree(pPktHdr);
+
+ return rc;
+}
+
+/**
+ * Destroys a client instance.
+ *
+ * @param pClient The UTS client structure.
+ */
+static void utsClientDestroy(PUTSCLIENT pClient)
+{
+ if (pClient->pszHostname)
+ RTStrFree(pClient->pszHostname);
+ if (pClient->hGadget != NIL_UTSGADGET)
+ utsGadgetRelease(pClient->hGadget);
+ if (pClient->hGadgetHost != NIL_UTSGADGETHOST)
+ utsGadgetHostRelease(pClient->hGadgetHost);
+ RTMemFree(pClient);
+}
+
+/**
+ * The main thread worker serving the clients.
+ */
+static DECLCALLBACK(int) utsClientWorker(RTTHREAD hThread, void *pvUser)
+{
+ RT_NOREF2(hThread, pvUser);
+ unsigned cClientsMax = 0;
+ unsigned cClientsCur = 0;
+ PUTSCLIENT *papClients = NULL;
+ RTPOLLSET hPollSet;
+
+ int rc = RTPollSetCreate(&hPollSet);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Add the pipe to the poll set. */
+ rc = RTPollSetAddPipe(hPollSet, g_hPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 0);
+ if (RT_SUCCESS(rc))
+ {
+ while (!g_fTerminate)
+ {
+ uint32_t fEvts;
+ uint32_t uId;
+ rc = RTPoll(hPollSet, RT_INDEFINITE_WAIT, &fEvts, &uId);
+ if (RT_SUCCESS(rc))
+ {
+ if (uId == 0)
+ {
+ if (fEvts & RTPOLL_EVT_ERROR)
+ break;
+
+ /* We got woken up because of a new client. */
+ Assert(fEvts & RTPOLL_EVT_READ);
+
+ uint8_t bRead;
+ size_t cbRead = 0;
+ rc = RTPipeRead(g_hPipeR, &bRead, 1, &cbRead);
+ AssertRC(rc);
+
+ RTCritSectEnter(&g_CritSectClients);
+ /* Walk the list and add all new clients. */
+ PUTSCLIENT pIt, pItNext;
+ RTListForEachSafe(&g_LstClientsNew, pIt, pItNext, UTSCLIENT, NdLst)
+ {
+ RTListNodeRemove(&pIt->NdLst);
+ Assert(cClientsCur <= cClientsMax);
+ if (cClientsCur == cClientsMax)
+ {
+ /* Realloc to accommodate for the new clients. */
+ PUTSCLIENT *papClientsNew = (PUTSCLIENT *)RTMemReallocZ(papClients, cClientsMax * sizeof(PUTSCLIENT), (cClientsMax + 10) * sizeof(PUTSCLIENT));
+ if (RT_LIKELY(papClientsNew))
+ {
+ cClientsMax += 10;
+ papClients = papClientsNew;
+ }
+ }
+
+ if (cClientsCur < cClientsMax)
+ {
+ /* Find a free slot in the client array. */
+ unsigned idxSlt = 0;
+ while ( idxSlt < cClientsMax
+ && papClients[idxSlt] != NULL)
+ idxSlt++;
+
+ rc = g_pTransport->pfnPollSetAdd(hPollSet, pIt->pTransportClient, idxSlt + 1);
+ if (RT_SUCCESS(rc))
+ {
+ cClientsCur++;
+ papClients[idxSlt] = pIt;
+ }
+ else
+ {
+ g_pTransport->pfnNotifyBye(pIt->pTransportClient);
+ utsClientDestroy(pIt);
+ }
+ }
+ else
+ {
+ g_pTransport->pfnNotifyBye(pIt->pTransportClient);
+ utsClientDestroy(pIt);
+ }
+ }
+ RTCritSectLeave(&g_CritSectClients);
+ }
+ else
+ {
+ /* Client sends a request, pick the right client and process it. */
+ PUTSCLIENT pClient = papClients[uId - 1];
+ AssertPtr(pClient);
+ if (fEvts & RTPOLL_EVT_READ)
+ rc = utsClientReqProcess(pClient);
+
+ if ( (fEvts & RTPOLL_EVT_ERROR)
+ || RT_FAILURE(rc))
+ {
+ /* Close connection and remove client from array. */
+ rc = g_pTransport->pfnPollSetRemove(hPollSet, pClient->pTransportClient, uId);
+ AssertRC(rc);
+
+ g_pTransport->pfnNotifyBye(pClient->pTransportClient);
+ papClients[uId - 1] = NULL;
+ cClientsCur--;
+ utsClientDestroy(pClient);
+ }
+ }
+ }
+ }
+ }
+
+ RTPollSetDestroy(hPollSet);
+
+ return rc;
+}
+
+/**
+ * The main loop.
+ *
+ * @returns exit code.
+ */
+static RTEXITCODE utsMainLoop(void)
+{
+ RTEXITCODE enmExitCode = RTEXITCODE_SUCCESS;
+ while (!g_fTerminate)
+ {
+ /*
+ * Wait for new connection and spin off a new thread
+ * for every new client.
+ */
+ PUTSTRANSPORTCLIENT pTransportClient;
+ int rc = g_pTransport->pfnWaitForConnect(&pTransportClient);
+ if (RT_FAILURE(rc))
+ continue;
+
+ /*
+ * New connection, create new client structure and spin of
+ * the request handling thread.
+ */
+ PUTSCLIENT pClient = (PUTSCLIENT)RTMemAllocZ(sizeof(UTSCLIENT));
+ if (RT_LIKELY(pClient))
+ {
+ pClient->enmState = UTSCLIENTSTATE_INITIALISING;
+ pClient->pTransportClient = pTransportClient;
+ pClient->pszHostname = NULL;
+ pClient->hGadgetHost = NIL_UTSGADGETHOST;
+ pClient->hGadget = NIL_UTSGADGET;
+
+ /* Add client to the new list and inform the worker thread. */
+ RTCritSectEnter(&g_CritSectClients);
+ RTListAppend(&g_LstClientsNew, &pClient->NdLst);
+ RTCritSectLeave(&g_CritSectClients);
+
+ size_t cbWritten = 0;
+ rc = RTPipeWrite(g_hPipeW, "", 1, &cbWritten);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to inform worker thread of a new client");
+ }
+ else
+ {
+ RTMsgError("Creating new client structure failed with out of memory error\n");
+ g_pTransport->pfnNotifyBye(pTransportClient);
+ }
+
+
+ }
+
+ return enmExitCode;
+}
+
+/**
+ * Initializes the global UTS state.
+ *
+ * @returns IPRT status code.
+ */
+static int utsInit(void)
+{
+ int rc = VINF_SUCCESS;
+ PRTERRINFO pErrInfo = NULL;
+
+ RTListInit(&g_LstClientsNew);
+
+ rc = RTJsonParseFromFile(&g_hCfgJson, g_szCfgPath, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = utsPlatformInit();
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&g_CritSectClients);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPipeCreate(&g_hPipeR, &g_hPipeW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin off the thread serving connections. */
+ rc = RTThreadCreate(&g_hThreadServing, utsClientWorker, NULL, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE,
+ "USBTSTSRV");
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ else
+ RTMsgError("Creating the client worker thread failed with %Rrc\n", rc);
+
+ RTPipeClose(g_hPipeR);
+ RTPipeClose(g_hPipeW);
+ }
+ else
+ RTMsgError("Creating communications pipe failed with %Rrc\n", rc);
+
+ RTCritSectDelete(&g_CritSectClients);
+ }
+ else
+ RTMsgError("Creating global critical section failed with %Rrc\n", rc);
+
+ RTJsonValueRelease(g_hCfgJson);
+ }
+ else
+ RTMsgError("Initializing the platform failed with %Rrc\n", rc);
+ }
+ else
+ {
+ if (RTErrInfoIsSet(pErrInfo))
+ {
+ RTMsgError("Failed to parse config with detailed error: %s (%Rrc)\n",
+ pErrInfo->pszMsg, pErrInfo->rc);
+ RTErrInfoFree(pErrInfo);
+ }
+ else
+ RTMsgError("Failed to parse config with unknown error (%Rrc)\n", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Determines the default configuration.
+ */
+static void utsSetDefaults(void)
+{
+ /*
+ * OS and ARCH.
+ */
+ AssertCompile(sizeof(KBUILD_TARGET) <= sizeof(g_szOsShortName));
+ strcpy(g_szOsShortName, KBUILD_TARGET);
+
+ AssertCompile(sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szArchShortName));
+ strcpy(g_szArchShortName, KBUILD_TARGET_ARCH);
+
+ AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName));
+ strcpy(g_szOsDotArchShortName, KBUILD_TARGET);
+ g_szOsDotArchShortName[sizeof(KBUILD_TARGET) - 1] = '.';
+ strcpy(&g_szOsDotArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH);
+
+ AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName));
+ strcpy(g_szOsSlashArchShortName, KBUILD_TARGET);
+ g_szOsSlashArchShortName[sizeof(KBUILD_TARGET) - 1] = '/';
+ strcpy(&g_szOsSlashArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH);
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ strcpy(g_szExeSuff, ".exe");
+ strcpy(g_szScriptSuff, ".cmd");
+#else
+ strcpy(g_szExeSuff, "");
+ strcpy(g_szScriptSuff, ".sh");
+#endif
+
+ /*
+ * The CD/DVD-ROM location.
+ */
+ /** @todo do a better job here :-) */
+#ifdef RT_OS_WINDOWS
+ strcpy(g_szDefCdRomPath, "D:/");
+#elif defined(RT_OS_OS2)
+ strcpy(g_szDefCdRomPath, "D:/");
+#else
+ if (RTDirExists("/media"))
+ strcpy(g_szDefCdRomPath, "/media/cdrom");
+ else
+ strcpy(g_szDefCdRomPath, "/mnt/cdrom");
+#endif
+ strcpy(g_szCdRomPath, g_szDefCdRomPath);
+
+ /*
+ * Temporary directory.
+ */
+ int rc = RTPathTemp(g_szDefScratchPath, sizeof(g_szDefScratchPath));
+ if (RT_SUCCESS(rc))
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(RT_OS_DOS)
+ rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXX.tmp");
+#else
+ rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXXXXXXX.tmp");
+#endif
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("RTPathTemp/Append failed when constructing scratch path: %Rrc\n", rc);
+ strcpy(g_szDefScratchPath, "/tmp/uts-XXXX.tmp");
+ }
+ strcpy(g_szScratchPath, g_szDefScratchPath);
+
+ /*
+ * Config file location.
+ */
+ /** @todo Improve */
+#if !defined(RT_OS_WINDOWS)
+ strcpy(g_szCfgPath, "/etc/uts.conf");
+#else
+ strcpy(g_szCfgPath, "");
+#endif
+
+ /*
+ * The default transporter is the first one.
+ */
+ g_pTransport = g_apTransports[0];
+}
+
+/**
+ * Prints the usage.
+ *
+ * @param pStrm Where to print it.
+ * @param pszArgv0 The program name (argv[0]).
+ */
+static void utsUsage(PRTSTREAM pStrm, const char *pszArgv0)
+{
+ RTStrmPrintf(pStrm,
+ "Usage: %Rbn [options]\n"
+ "\n"
+ "Options:\n"
+ " --config <path>\n"
+ " Where to load the config from\n"
+ " --cdrom <path>\n"
+ " Where the CD/DVD-ROM will be mounted.\n"
+ " Default: %s\n"
+ " --scratch <path>\n"
+ " Where to put scratch files.\n"
+ " Default: %s \n"
+ ,
+ pszArgv0,
+ g_szDefCdRomPath,
+ g_szDefScratchPath);
+ RTStrmPrintf(pStrm,
+ " --transport <name>\n"
+ " Use the specified transport layer, one of the following:\n");
+ for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++)
+ RTStrmPrintf(pStrm, " %s - %s\n", g_apTransports[i]->szName, g_apTransports[i]->pszDesc);
+ RTStrmPrintf(pStrm, " Default: %s\n", g_pTransport->szName);
+ RTStrmPrintf(pStrm,
+ " --display-output, --no-display-output\n"
+ " Display the output and the result of all child processes.\n");
+ RTStrmPrintf(pStrm,
+ " --foreground\n"
+ " Don't daemonize, run in the foreground.\n");
+ RTStrmPrintf(pStrm,
+ " --help, -h, -?\n"
+ " Display this message and exit.\n"
+ " --version, -V\n"
+ " Display the version and exit.\n");
+
+ for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++)
+ if (g_apTransports[i]->cOpts)
+ {
+ RTStrmPrintf(pStrm,
+ "\n"
+ "Options for %s:\n", g_apTransports[i]->szName);
+ g_apTransports[i]->pfnUsage(g_pStdOut);
+ }
+}
+
+/**
+ * Parses the arguments.
+ *
+ * @returns Exit code. Exit if this is non-zero or @a *pfExit is set.
+ * @param argc The number of arguments.
+ * @param argv The argument vector.
+ * @param pfExit For indicating exit when the exit code is zero.
+ */
+static RTEXITCODE utsParseArgv(int argc, char **argv, bool *pfExit)
+{
+ *pfExit = false;
+
+ /*
+ * Storage for locally handled options.
+ */
+ bool fDaemonize = true;
+ bool fDaemonized = false;
+
+ /*
+ * Combine the base and transport layer option arrays.
+ */
+ static const RTGETOPTDEF s_aBaseOptions[] =
+ {
+ { "--config", 'C', RTGETOPT_REQ_STRING },
+ { "--transport", 't', RTGETOPT_REQ_STRING },
+ { "--cdrom", 'c', RTGETOPT_REQ_STRING },
+ { "--scratch", 's', RTGETOPT_REQ_STRING },
+ { "--display-output", 'd', RTGETOPT_REQ_NOTHING },
+ { "--no-display-output",'D', RTGETOPT_REQ_NOTHING },
+ { "--foreground", 'f', RTGETOPT_REQ_NOTHING },
+ { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING },
+ };
+
+ size_t cOptions = RT_ELEMENTS(s_aBaseOptions);
+ for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++)
+ cOptions += g_apTransports[i]->cOpts;
+
+ PRTGETOPTDEF paOptions = (PRTGETOPTDEF)alloca(cOptions * sizeof(RTGETOPTDEF));
+ if (!paOptions)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "alloca failed\n");
+
+ memcpy(paOptions, s_aBaseOptions, sizeof(s_aBaseOptions));
+ cOptions = RT_ELEMENTS(s_aBaseOptions);
+ for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++)
+ {
+ memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF));
+ cOptions += g_apTransports[i]->cOpts;
+ }
+
+ /*
+ * Parse the arguments.
+ */
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, paOptions, cOptions, 1, 0 /* fFlags */);
+ AssertRC(rc);
+
+ int ch;
+ RTGETOPTUNION Val;
+ while ((ch = RTGetOpt(&GetState, &Val)))
+ {
+ switch (ch)
+ {
+ case 'C':
+ rc = RTStrCopy(g_szCfgPath, sizeof(g_szCfgPath), Val.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Config file path is path too long (%Rrc)\n", rc);
+ break;
+
+ case 'c':
+ rc = RTStrCopy(g_szCdRomPath, sizeof(g_szCdRomPath), Val.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "CD/DVD-ROM is path too long (%Rrc)\n", rc);
+ break;
+
+ case 'd':
+ g_fDisplayOutput = true;
+ break;
+
+ case 'D':
+ g_fDisplayOutput = false;
+ break;
+
+ case 'f':
+ fDaemonize = false;
+ break;
+
+ case 'h':
+ utsUsage(g_pStdOut, argv[0]);
+ *pfExit = true;
+ return RTEXITCODE_SUCCESS;
+
+ case 's':
+ rc = RTStrCopy(g_szScratchPath, sizeof(g_szScratchPath), Val.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "scratch path is too long (%Rrc)\n", rc);
+ break;
+
+ case 't':
+ {
+ PCUTSTRANSPORT pTransport = NULL;
+ for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++)
+ if (!strcmp(g_apTransports[i]->szName, Val.psz))
+ {
+ pTransport = g_apTransports[i];
+ break;
+ }
+ if (!pTransport)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown transport layer name '%s'\n", Val.psz);
+ g_pTransport = pTransport;
+ break;
+ }
+
+ case 'V':
+ RTPrintf("$Revision: 157380 $\n");
+ *pfExit = true;
+ return RTEXITCODE_SUCCESS;
+
+ case 'Z':
+ fDaemonized = true;
+ fDaemonize = false;
+ break;
+
+ default:
+ {
+ rc = VERR_TRY_AGAIN;
+ for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++)
+ if (g_apTransports[i]->cOpts)
+ {
+ rc = g_apTransports[i]->pfnOption(ch, &Val);
+ if (RT_SUCCESS(rc))
+ break;
+ if (rc != VERR_TRY_AGAIN)
+ {
+ *pfExit = true;
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+ if (rc == VERR_TRY_AGAIN)
+ {
+ *pfExit = true;
+ return RTGetOptPrintError(ch, &Val);
+ }
+ break;
+ }
+ }
+ }
+
+ /*
+ * Daemonize ourselves if asked to.
+ */
+ if (fDaemonize && !*pfExit)
+ {
+ rc = RTProcDaemonize(argv, "--daemonized");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc);
+ *pfExit = true;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Initialize the runtime.
+ */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Determine defaults and parse the arguments.
+ */
+ utsSetDefaults();
+ bool fExit;
+ RTEXITCODE rcExit = utsParseArgv(argc, argv, &fExit);
+ if (rcExit != RTEXITCODE_SUCCESS || fExit)
+ return rcExit;
+
+ /*
+ * Initialize global state.
+ */
+ rc = utsInit();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Initialize the transport layer.
+ */
+ rc = g_pTransport->pfnInit();
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Ok, start working
+ */
+ rcExit = utsMainLoop();
+
+ /*
+ * Cleanup.
+ */
+ g_pTransport->pfnTerm();
+
+ utsPlatformTerm();
+
+ return rcExit;
+}
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp
new file mode 100644
index 00000000..05b4c569
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp
@@ -0,0 +1,211 @@
+/* $Id: UsbTestServiceGadget.cpp $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "UsbTestServiceGadgetInternal.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Internal UTS gadget host instance data.
+ */
+typedef struct UTSGADGETINT
+{
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Pointer to the gadget class callback table. */
+ PCUTSGADGETCLASSIF pClassIf;
+ /** The gadget host handle. */
+ UTSGADGETHOST hGadgetHost;
+ /** Class specific instance data - variable in size. */
+ uint8_t abClassInst[1];
+} UTSGADGETINT;
+/** Pointer to the internal gadget host instance data. */
+typedef UTSGADGETINT *PUTSGADGETINT;
+
+
+/*********************************************************************************************************************************
+* Global variables *
+*********************************************************************************************************************************/
+
+/** Known gadget host interfaces. */
+static const PCUTSGADGETCLASSIF g_apUtsGadgetClass[] =
+{
+ &g_UtsGadgetClassTest
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Destroys a gadget instance.
+ *
+ * @param pThis The gadget instance.
+ */
+static void utsGadgetDestroy(PUTSGADGETINT pThis)
+{
+ pThis->pClassIf->pfnTerm((PUTSGADGETCLASSINT)&pThis->abClassInst[0]);
+ RTMemFree(pThis);
+}
+
+
+DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass,
+ PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget)
+{
+ int rc = VINF_SUCCESS;
+ PCUTSGADGETCLASSIF pClassIf = NULL;
+
+ /* Get the interface. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetClass); i++)
+ {
+ if (g_apUtsGadgetClass[i]->enmClass == enmClass)
+ {
+ pClassIf = g_apUtsGadgetClass[i];
+ break;
+ }
+ }
+
+ if (RT_LIKELY(pClassIf))
+ {
+ PUTSGADGETINT pThis = (PUTSGADGETINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETINT, abClassInst[pClassIf->cbClass]));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->cRefs = 1;
+ pThis->hGadgetHost = hGadgetHost;
+ pThis->pClassIf = pClassIf;
+ rc = pClassIf->pfnInit((PUTSGADGETCLASSINT)&pThis->abClassInst[0], paCfg);
+ if (RT_SUCCESS(rc))
+ {
+ /* Connect the gadget to the host. */
+ rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, pThis);
+ if (RT_SUCCESS(rc))
+ *phGadget = pThis;
+ }
+ else
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+
+DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget)
+{
+ PUTSGADGETINT pThis = hGadget;
+
+ AssertPtrReturn(pThis, 0);
+
+ return ASMAtomicIncU32(&pThis->cRefs);
+}
+
+
+DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget)
+{
+ PUTSGADGETINT pThis = hGadget;
+
+ AssertPtrReturn(pThis, 0);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ if (!cRefs)
+ utsGadgetDestroy(pThis);
+
+ return cRefs;
+}
+
+
+DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget)
+{
+ PUTSGADGETINT pThis = hGadget;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ return pThis->pClassIf->pfnGetBusId((PUTSGADGETCLASSINT)&pThis->abClassInst[0]);
+}
+
+
+DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget)
+{
+ PUTSGADGETINT pThis = hGadget;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ return 1; /** @todo Current assumption which is true on Linux with dummy_hcd. */
+}
+
+
+DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget)
+{
+ PUTSGADGETINT pThis = hGadget;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ int rc = pThis->pClassIf->pfnConnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, hGadget);
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget)
+{
+ PUTSGADGETINT pThis = hGadget;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ int rc = utsGadgetHostGadgetDisconnect(pThis->hGadgetHost, hGadget);
+ if (RT_SUCCESS(rc))
+ rc = pThis->pClassIf->pfnDisconnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]);
+
+ return rc;
+}
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h
new file mode 100644
index 00000000..19d2603a
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h
@@ -0,0 +1,546 @@
+/* $Id: UsbTestServiceGadget.h $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Gadget API.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h
+#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+
+RT_C_DECLS_BEGIN
+
+/** Opaque gadget host handle. */
+typedef struct UTSGADGETHOSTINT *UTSGADGETHOST;
+/** Pointer to a gadget host handle. */
+typedef UTSGADGETHOST *PUTSGADGETHOST;
+
+/** NIL gadget host handle. */
+#define NIL_UTSGADGETHOST ((UTSGADGETHOST)0)
+
+/** Opaque USB gadget handle. */
+typedef struct UTSGADGETINT *UTSGADGET;
+/** Pointer to a USB gadget handle. */
+typedef UTSGADGET *PUTSGADET;
+
+/** NIL gadget handle. */
+#define NIL_UTSGADGET ((UTSGADGET)0)
+
+/**
+ * Gadget/Gadget host configuration item type.
+ */
+typedef enum UTSGADGETCFGTYPE
+{
+ /** Don't use! */
+ UTSGADGETCFGTYPE_INVALID = 0,
+ /** Boolean type. */
+ UTSGADGETCFGTYPE_BOOLEAN,
+ /** UTF-8 string. */
+ UTSGADGETCFGTYPE_STRING,
+ /** Unsigned 8bit integer. */
+ UTSGADGETCFGTYPE_UINT8,
+ /** Unsigned 16bit integer. */
+ UTSGADGETCFGTYPE_UINT16,
+ /** Unsigned 32bit integer. */
+ UTSGADGETCFGTYPE_UINT32,
+ /** Unsigned 64bit integer. */
+ UTSGADGETCFGTYPE_UINT64,
+ /** Signed 8bit integer. */
+ UTSGADGETCFGTYPE_INT8,
+ /** Signed 16bit integer. */
+ UTSGADGETCFGTYPE_INT16,
+ /** Signed 32bit integer. */
+ UTSGADGETCFGTYPE_INT32,
+ /** Signed 64bit integer. */
+ UTSGADGETCFGTYPE_INT64,
+ /** 32bit hack. */
+ UTSGADGETCFGTYPE_32BIT_HACK = 0x7fffffff
+} UTSGADGETCFGTYPE;
+
+/**
+ * Gadget configuration value.
+ */
+typedef struct UTSGADGETCFGVAL
+{
+ /** Value type */
+ UTSGADGETCFGTYPE enmType;
+ /** Value based on the type. */
+ union
+ {
+ bool f;
+ const char *psz;
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ int8_t i8;
+ int16_t i16;
+ int32_t i32;
+ int64_t i64;
+ } u;
+} UTSGADGETCFGVAL;
+/** Pointer to a gadget configuration value. */
+typedef UTSGADGETCFGVAL *PUTSGADGETCFGVAL;
+/** Pointer to a const gadget configuration value. */
+typedef const UTSGADGETCFGVAL *PCUTSGADGETCFGVAL;
+
+/**
+ * Gadget configuration item.
+ */
+typedef struct UTSGADGETCFGITEM
+{
+ /** Item key. */
+ const char *pszKey;
+ /** Item value. */
+ UTSGADGETCFGVAL Val;
+} UTSGADGETCFGITEM;
+/** Pointer to a gadget configuration item. */
+typedef UTSGADGETCFGITEM *PUTSGADGETCFGITEM;
+/** Pointer to a const gadget configuration item. */
+typedef const UTSGADGETCFGITEM *PCUTSGADGETCFGITEM;
+
+/**
+ * Type for the gadget host.
+ */
+typedef enum UTSGADGETHOSTTYPE
+{
+ /** Invalid type, don't use. */
+ UTSGADGETHOSTTYPE_INVALID = 0,
+ /** USB/IP host, gadgets are exported using a USB/IP server. */
+ UTSGADGETHOSTTYPE_USBIP,
+ /** Physical connection using a device or OTG port. */
+ UTSGADGETHOSTTYPE_PHYSICAL,
+ /** 32bit hack. */
+ UTSGADGETHOSTTYPE_32BIT_HACK = 0x7fffffff
+} UTSGADGETHOSTTYPE;
+
+/**
+ * USB gadget class.
+ */
+typedef enum UTSGADGETCLASS
+{
+ /** Invalid class, don't use. */
+ UTSGADGETCLASS_INVALID = 0,
+ /** Special test device class. */
+ UTSGADGETCLASS_TEST,
+ /** MSD device. */
+ UTSGADGETCLASS_MSD,
+ /** 32bit hack. */
+ UTSGADGETCLASS_32BIT_HACK = 0x7fffffff
+} UTSGADGETCLASS;
+
+/**
+ * Queries the value of a given boolean key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pf Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ bool *pf);
+
+/**
+ * Queries the value of a given boolean key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pf Where to store the value on success.
+ * @param fDef The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ bool *pf, bool fDef);
+
+/**
+ * Queries the string value of a given key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param ppszVal Where to store the pointer to the string on success,
+ * must be freed with RTStrFree().
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ char **ppszVal);
+
+/**
+ * Queries the string value of a given key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param ppszVal Where to store the pointer to the string on success,
+ * must be freed with RTStrFree().
+ * @param pszDef The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ char **ppszVal, const char *pszDef);
+
+/**
+ * Queries the value of a given uint8_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu8 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint8_t *pu8);
+
+/**
+ * Queries the value of a given uint8_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu8 Where to store the value on success.
+ * @param u8Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint8_t *pu8, uint8_t u8Def);
+
+/**
+ * Queries the value of a given uint16_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu16 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pu16);
+
+/**
+ * Queries the value of a given uint16_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu16 Where to store the value on success.
+ * @param u16Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pu16, uint16_t u16Def);
+
+/**
+ * Queries the value of a given uint32_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu32 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pu32);
+
+/**
+ * Queries the value of a given uint32_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu32 Where to store the value on success.
+ * @param u32Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pu32, uint32_t u32Def);
+
+/**
+ * Queries the value of a given uint64_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu64 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pu64);
+
+/**
+ * Queries the value of a given uint64_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pu64 Where to store the value on success.
+ * @param u64Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pu64, uint64_t u64Def);
+
+/**
+ * Queries the value of a given int8_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi8 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ int8_t *pi8);
+
+/**
+ * Queries the value of a given int8_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi8 Where to store the value on success.
+ * @param i8Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ int8_t *pi8, uint8_t i8Def);
+
+/**
+ * Queries the value of a given int16_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi16 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pi16);
+
+/**
+ * Queries the value of a given int16_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi16 Where to store the value on success.
+ * @param i16Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pi16, uint16_t i16Def);
+
+/**
+ * Queries the value of a given int32_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi32 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pi32);
+
+/**
+ * Queries the value of a given int32_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi32 Where to store the value on success.
+ * @param i32Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pi32, uint32_t i32Def);
+
+/**
+ * Queries the value of a given int64_t key from the given configuration array.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi64 Where to store the value on success.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pi64);
+
+/**
+ * Queries the value of a given int64_t key from the given configuration array,
+ * setting a default if not found.
+ *
+ * @returns IPRT status code.
+ * @param paCfg The configuration items.
+ * @param pszKey The key query the value for.
+ * @param pi64 Where to store the value on success.
+ * @param i64Def The default value to assign if the key is not found.
+ */
+DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pi64, uint64_t i64Def);
+
+/**
+ * Creates a new USB gadget host.
+ *
+ * @returns IPRT status code.
+ * @param enmType The host type.
+ * @param paCfg Additional configuration parameters - optional.
+ * The array must be terminated with a NULL entry.
+ * @param phGadgetHost Where to store the handle to the gadget host on success.
+ */
+DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg,
+ PUTSGADGETHOST phGadgetHost);
+
+/**
+ * Retains the given gadget host handle.
+ *
+ * @returns New reference count.
+ * @param hGadgetHost The gadget host handle to retain.
+ */
+DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost);
+
+/**
+ * Releases the given gadget host handle, destroying it if the reference
+ * count reaches 0.
+ *
+ * @returns New reference count.
+ * @param hGadgetHost The gadget host handle to release.
+ */
+DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost);
+
+/**
+ * Returns the current config of the given gadget host.
+ *
+ * @returns Pointer to a constant array of configuration items for the given gadget host.
+ * @param hGadgetHost The gadget host handle.
+ */
+DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetHostGetCfg(UTSGADGETHOST hGadgetHost);
+
+/**
+ * Connects the given gadget to the host.
+ *
+ * @returns IPRT status code.
+ * @param hGadgetHost The gadget host handle.
+ * @param hGadget The gadget handle.
+ */
+DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget);
+
+/**
+ * Disconnects the given gadget from the host.
+ *
+ * @returns IPRT status code.
+ * @param hGadgetHost The gadget host handle.
+ * @param hGadget The gadget handle.
+ */
+DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget);
+
+/**
+ * Creates a new USB gadget based the class.
+ *
+ * @returns IPRT status code.
+ * @param hGadgetHost The gadget host the gadget is part of.
+ * @param enmClass The gadget class.
+ * @param paCfg Array of optional configuration items for the gadget.
+ * @param phGadget Where to store the gadget handle on success.
+ */
+DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass,
+ PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget);
+
+/**
+ * Retains the given gadget handle.
+ *
+ * @returns New reference count.
+ * @param hGadget The gadget handle to retain.
+ */
+DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget);
+
+/**
+ * Releases the given gadget handle, destroying it if the reference
+ * count reaches 0.
+ *
+ * @returns New reference count.
+ * @param hGadget The gadget handle to destroy.
+ */
+DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget);
+
+/**
+ * Returns the current config of the given gadget.
+ *
+ * @returns Pointer to a constant array of configuration items for the given gadget.
+ * @param hGadget The gadget handle.
+ */
+DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetGetCfg(UTSGADGET hGadget);
+
+/**
+ * Returns the path of the given gadget from which it can be accessed.
+ *
+ * @returns Access path.
+ * @param hGadget The gadget handle.
+ */
+DECLHIDDEN(const char *) utsGadgetGetAccessPath(UTSGADGET hGadget);
+
+/**
+ * Returns the bus ID the gadget is on.
+ *
+ * @returns Bus ID of the gadget.
+ * @param hGadget The gadget handle.
+ */
+DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget);
+
+/**
+ * Returns the device ID of the gagdet.
+ *
+ * @returns Device ID of the gadget.
+ * @param hGadget The gadget handle.
+ */
+DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget);
+
+/**
+ * Mark the gadget as connected to the host. Depending
+ * on the host type it will be appear as physically attached
+ * or will appear in the exported USB device list.
+ *
+ * @returns IPRT status code.
+ * @param hGadget The gadget handle to connect.
+ */
+DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget);
+
+/**
+ * Mark the gadget as disconnected from the host.
+ *
+ * @returns IPRT status code.
+ * @param hGadget The gadget handle to disconnect.
+ */
+DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h */
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp
new file mode 100644
index 00000000..7ae631a5
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp
@@ -0,0 +1,462 @@
+/* $Id: UsbTestServiceGadgetCfg.cpp $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, USB gadget Cfg API.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cdefs.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "UsbTestServiceGadget.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Returns the gadget configuration item matching the given key.
+ *
+ * @returns Pointer to the configuration item on success or NULL if not found.
+ * @param paCfg The configuration item array.
+ * @param pszKey The key to look for.
+ */
+static PCUTSGADGETCFGITEM utsGadgetCfgGetItemFromKey(PCUTSGADGETCFGITEM paCfg, const char *pszKey)
+{
+ while ( paCfg
+ && paCfg->pszKey)
+ {
+ if (!RTStrCmp(paCfg->pszKey, pszKey))
+ return paCfg;
+
+ paCfg++;
+ }
+ return NULL;
+}
+
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ bool *pf)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN)
+ {
+ *pf = pCfgItem->Val.u.f;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ bool *pf, bool fDef)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN)
+ {
+ *pf = pCfgItem ? pCfgItem->Val.u.f : fDef;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ char **ppszVal)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING)
+ {
+ *ppszVal = RTStrDup(pCfgItem->Val.u.psz);
+ if (*ppszVal)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ char **ppszVal, const char *pszDef)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING)
+ {
+ *ppszVal = RTStrDup(pCfgItem ? pCfgItem->Val.u.psz : pszDef);
+ if (*ppszVal)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint8_t *pu8)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8)
+ {
+ *pu8 = pCfgItem->Val.u.u8;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint8_t *pu8, uint8_t u8Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8)
+ {
+ *pu8 = pCfgItem ? pCfgItem->Val.u.u8 : u8Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pu16)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16)
+ {
+ *pu16 = pCfgItem->Val.u.u16;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pu16, uint16_t u16Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16)
+ {
+ *pu16 = pCfgItem ? pCfgItem->Val.u.u16 : u16Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pu32)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32)
+ {
+ *pu32 = pCfgItem->Val.u.u32;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pu32, uint32_t u32Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32)
+ {
+ *pu32 = pCfgItem ? pCfgItem->Val.u.u32 : u32Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pu64)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64)
+ {
+ *pu64 = pCfgItem->Val.u.u64;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pu64, uint64_t u64Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64)
+ {
+ *pu64 = pCfgItem ? pCfgItem->Val.u.u64 : u64Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ int8_t *pi8)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8)
+ {
+ *pi8 = pCfgItem->Val.u.i8;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ int8_t *pi8, uint8_t i8Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8)
+ {
+ *pi8 = pCfgItem ? pCfgItem->Val.u.i8 : i8Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pi16)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16)
+ {
+ *pi16 = pCfgItem->Val.u.i16;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint16_t *pi16, uint16_t i16Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16)
+ {
+ *pi16 = pCfgItem ? pCfgItem->Val.u.i16 : i16Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pi32)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32)
+ {
+ *pi32 = pCfgItem->Val.u.i32;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint32_t *pi32, uint32_t i32Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32)
+ {
+ *pi32 = pCfgItem ? pCfgItem->Val.u.i32 : i32Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pi64)
+{
+ int rc = VERR_NOT_FOUND;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if (pCfgItem)
+ {
+ if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64)
+ {
+ *pi64 = pCfgItem->Val.u.i64;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey,
+ uint64_t *pi64, uint64_t i64Def)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey);
+
+ if ( !pCfgItem
+ || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64)
+ {
+ *pi64 = pCfgItem ? pCfgItem->Val.u.i64 : i64Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp
new file mode 100644
index 00000000..6a2f3626
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp
@@ -0,0 +1,470 @@
+/* $Id: UsbTestServiceGadgetClassTest.cpp $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, USB gadget class
+ * for the test device.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/env.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+#include <iprt/thread.h>
+
+#include <iprt/linux/sysfs.h>
+
+#include "UsbTestServiceGadgetInternal.h"
+#include "UsbTestServicePlatform.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Default configfs mount point. */
+#define UTS_GADGET_CLASS_CONFIGFS_MNT_DEF "/sys/kernel/config/usb_gadget"
+/** Gadget template name */
+#define UTS_GADGET_TEMPLATE_NAME "gadget_test"
+
+/** Default vendor ID which is recognized by the usbtest driver. */
+#define UTS_GADGET_TEST_VENDOR_ID_DEF UINT16_C(0x0525)
+/** Default product ID which is recognized by the usbtest driver. */
+#define UTS_GADGET_TEST_PRODUCT_ID_DEF UINT16_C(0xa4a0)
+/** Default device class. */
+#define UTS_GADGET_TEST_DEVICE_CLASS_DEF UINT8_C(0xff)
+/** Default serial number string. */
+#define UTS_GADGET_TEST_SERIALNUMBER_DEF "0123456789"
+/** Default manufacturer string. */
+#define UTS_GADGET_TEST_MANUFACTURER_DEF "Oracle Inc."
+/** Default product string. */
+#define UTS_GADGET_TEST_PRODUCT_DEF "USB test device"
+
+/**
+ * Internal UTS gadget host instance data.
+ */
+typedef struct UTSGADGETCLASSINT
+{
+ /** Gadget template path. */
+ char *pszGadgetPath;
+ /** The UDC this gadget is connected to. */
+ char *pszUdc;
+ /** Bus identifier for the used UDC. */
+ uint32_t uBusId;
+ /** Device identifier. */
+ uint32_t uDevId;
+} UTSGADGETCLASSINT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Number of already created gadgets, used for the template name. */
+static volatile uint32_t g_cGadgets = 0;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Creates a new directory pointed to by the given format string.
+ *
+ * @returns IPRT status code.
+ * @param pszFormat The format string.
+ * @param va The arguments.
+ */
+static int utsGadgetClassTestDirCreateV(const char *pszFormat, va_list va)
+{
+ int rc = VINF_SUCCESS;
+ char aszPath[RTPATH_MAX + 1];
+
+ size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va);
+ if (cbStr <= sizeof(aszPath) - 1)
+ rc = RTDirCreateFullPath(aszPath, 0700);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ return rc;
+}
+
+
+/**
+ * Creates a new directory pointed to by the given format string.
+ *
+ * @returns IPRT status code.
+ * @param pszFormat The format string.
+ * @param ... The arguments.
+ */
+static int utsGadgetClassTestDirCreate(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = utsGadgetClassTestDirCreateV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Removes a directory pointed to by the given format string.
+ *
+ * @returns IPRT status code.
+ * @param pszFormat The format string.
+ * @param va The arguments.
+ */
+static int utsGadgetClassTestDirRemoveV(const char *pszFormat, va_list va)
+{
+ int rc = VINF_SUCCESS;
+ char aszPath[RTPATH_MAX + 1];
+
+ size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va);
+ if (cbStr <= sizeof(aszPath) - 1)
+ rc = RTDirRemove(aszPath);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ return rc;
+}
+
+
+/**
+ * Removes a directory pointed to by the given format string.
+ *
+ * @returns IPRT status code.
+ * @param pszFormat The format string.
+ * @param ... The arguments.
+ */
+static int utsGadgetClassTestDirRemove(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = utsGadgetClassTestDirRemoveV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Links the given function to the given config.
+ *
+ * @returns IPRT status code.
+ * @param pClass The gadget class instance data.
+ * @param pszFunc The function to link.
+ * @param pszCfg The configuration which the function will be part of.
+ */
+static int utsGadgetClassTestLinkFuncToCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg)
+{
+ int rc = VINF_SUCCESS;
+ char aszPathFunc[RTPATH_MAX + 1];
+ char aszPathCfg[RTPATH_MAX + 1];
+
+ size_t cbStr = RTStrPrintf(&aszPathFunc[0], sizeof(aszPathFunc), "%s/functions/%s",
+ pClass->pszGadgetPath, pszFunc);
+ if (cbStr <= sizeof(aszPathFunc) - 1)
+ {
+ cbStr = RTStrPrintf(&aszPathCfg[0], sizeof(aszPathCfg), "%s/configs/%s/%s",
+ pClass->pszGadgetPath, pszCfg, pszFunc);
+ if (cbStr <= sizeof(aszPathCfg) - 1)
+ rc = RTSymlinkCreate(&aszPathCfg[0], &aszPathFunc[0], RTSYMLINKTYPE_DIR, 0);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ return rc;
+}
+
+
+/**
+ * Unlinks the given function from the given configuration.
+ *
+ * @returns IPRT status code.
+ * @param pClass The gadget class instance data.
+ * @param pszFunc The function to unlink.
+ * @param pszCfg The configuration which the function is currently part of.
+ */
+static int utsGadgetClassTestUnlinkFuncFromCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg)
+{
+ int rc = VINF_SUCCESS;
+ char aszPath[RTPATH_MAX + 1];
+ size_t cbStr = RTStrPrintf(&aszPath[0], sizeof(aszPath), "%s/configs/%s/%s",
+ pClass->pszGadgetPath, pszCfg, pszFunc);
+ if (cbStr <= sizeof(aszPath) - 1)
+ rc = RTSymlinkDelete(&aszPath[0], 0);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ return rc;
+}
+
+
+/**
+ * Cleans up any leftover configurations from the gadget class.
+ *
+ * @param pClass The gadget class instance data.
+ */
+static void utsGadgetClassTestCleanup(PUTSGADGETCLASSINT pClass)
+{
+ /* Unbind the gadget from the currently assigned UDC first. */
+ int rc = RTLinuxSysFsWriteStrFile("", 0, NULL, "%s/UDC", pClass->pszGadgetPath);
+ AssertRC(rc);
+
+ /* Delete the symlinks, ignore any errors. */
+ utsGadgetClassTestUnlinkFuncFromCfg(pClass, "Loopback.0", "c.2");
+ utsGadgetClassTestUnlinkFuncFromCfg(pClass, "SourceSink.0", "c.1");
+
+ /* Delete configuration strings and then the configuration directories. */
+ utsGadgetClassTestDirRemove("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath);
+ utsGadgetClassTestDirRemove("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath);
+
+ utsGadgetClassTestDirRemove("%s/configs/c.2", pClass->pszGadgetPath);
+ utsGadgetClassTestDirRemove("%s/configs/c.1", pClass->pszGadgetPath);
+
+ /* Delete the functions. */
+ utsGadgetClassTestDirRemove("%s/functions/Loopback.0", pClass->pszGadgetPath);
+ utsGadgetClassTestDirRemove("%s/functions/SourceSink.0", pClass->pszGadgetPath);
+
+ /* Delete the english strings. */
+ utsGadgetClassTestDirRemove("%s/strings/0x409", pClass->pszGadgetPath);
+
+ /* Finally delete the gadget template. */
+ utsGadgetClassTestDirRemove(pClass->pszGadgetPath);
+
+ /* Release the UDC. */
+ if (pClass->pszUdc)
+ {
+ rc = utsPlatformLnxReleaseUDC(pClass->pszUdc);
+ AssertRC(rc);
+ RTStrFree(pClass->pszUdc);
+ }
+}
+
+/**
+ * @interface_method_impl{UTSGADGETCLASSIF,pfnInit}
+ */
+static DECLCALLBACK(int) utsGadgetClassTestInit(PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg)
+{
+ int rc = VINF_SUCCESS;
+
+ if (RTLinuxSysFsExists(UTS_GADGET_CLASS_CONFIGFS_MNT_DEF))
+ {
+ /* Create the gadget template */
+ unsigned idx = ASMAtomicIncU32(&g_cGadgets);
+
+ int rcStr = RTStrAPrintf(&pClass->pszGadgetPath, "%s/%s%u", UTS_GADGET_CLASS_CONFIGFS_MNT_DEF,
+ UTS_GADGET_TEMPLATE_NAME, idx);
+ if (rcStr == -1)
+ return VERR_NO_STR_MEMORY;
+
+ rc = utsGadgetClassTestDirCreate(pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ {
+ uint16_t idVendor = 0;
+ uint16_t idProduct = 0;
+ uint8_t bDeviceClass = 0;
+ char *pszSerial = NULL;
+ char *pszManufacturer = NULL;
+ char *pszProduct = NULL;
+ bool fSuperSpeed = false;
+
+ /* Get basic device config. */
+ rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idVendor", &idVendor, UTS_GADGET_TEST_VENDOR_ID_DEF);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idProduct", &idProduct, UTS_GADGET_TEST_PRODUCT_ID_DEF);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetCfgQueryU8Def(paCfg, "Gadget/bDeviceClass", &bDeviceClass, UTS_GADGET_TEST_DEVICE_CLASS_DEF);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/SerialNumber", &pszSerial, UTS_GADGET_TEST_SERIALNUMBER_DEF);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Manufacturer", &pszManufacturer, UTS_GADGET_TEST_MANUFACTURER_DEF);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Product", &pszProduct, UTS_GADGET_TEST_PRODUCT_DEF);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetCfgQueryBoolDef(paCfg, "Gadget/SuperSpeed", &fSuperSpeed, false);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Write basic attributes. */
+ rc = RTLinuxSysFsWriteU16File(16, idVendor, "%s/idVendor", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteU16File(16, idProduct, "%s/idProduct", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteU16File(16, bDeviceClass, "%s/bDeviceClass", pClass->pszGadgetPath);
+
+ /* Create english language strings. */
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/strings/0x409", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteStrFile(pszSerial, 0, NULL, "%s/strings/0x409/serialnumber", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteStrFile(pszManufacturer, 0, NULL, "%s/strings/0x409/manufacturer", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteStrFile(pszProduct, 0, NULL, "%s/strings/0x409/product", pClass->pszGadgetPath);
+
+ /* Create the gadget functions. */
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/functions/SourceSink.0", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/functions/Loopback.0", pClass->pszGadgetPath);
+
+ /* Create the device configs. */
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/configs/c.1", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/configs/c.2", pClass->pszGadgetPath);
+
+ /* Write configuration strings. */
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestDirCreate("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteStrFile("source and sink data", 0, NULL, "%s/configs/c.1/strings/0x409/configuration", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteStrFile("loop input to output", 0, NULL, "%s/configs/c.2/strings/0x409/configuration", pClass->pszGadgetPath);
+
+ /* Link the functions into the configurations. */
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestLinkFuncToCfg(pClass, "SourceSink.0", "c.1");
+ if (RT_SUCCESS(rc))
+ rc = utsGadgetClassTestLinkFuncToCfg(pClass, "Loopback.0", "c.2");
+
+ /* Finally enable the gadget by attaching it to a UDC. */
+ if (RT_SUCCESS(rc))
+ {
+ pClass->pszUdc = NULL;
+
+ rc = utsPlatformLnxAcquireUDC(fSuperSpeed, &pClass->pszUdc, &pClass->uBusId);
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsWriteStrFile(pClass->pszUdc, 0, NULL, "%s/UDC", pClass->pszGadgetPath);
+ if (RT_SUCCESS(rc))
+ RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */
+ }
+ }
+
+ if (pszSerial)
+ RTStrFree(pszSerial);
+ if (pszManufacturer)
+ RTStrFree(pszManufacturer);
+ if (pszProduct)
+ RTStrFree(pszProduct);
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ utsGadgetClassTestCleanup(pClass);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETCLASSIF,pfnTerm}
+ */
+static DECLCALLBACK(void) utsGadgetClassTestTerm(PUTSGADGETCLASSINT pClass)
+{
+ utsGadgetClassTestCleanup(pClass);
+
+ if (pClass->pszGadgetPath)
+ RTStrFree(pClass->pszGadgetPath);
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETCLASSIF,pfnGetBusId}
+ */
+static DECLCALLBACK(uint32_t) utsGadgetClassTestGetBusId(PUTSGADGETCLASSINT pClass)
+{
+ return pClass->uBusId;
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETCLASSIF,pfnConnect}
+ */
+static DECLCALLBACK(int) utsGadgetClassTestConnect(PUTSGADGETCLASSINT pClass)
+{
+ int rc = RTLinuxSysFsWriteStrFile("connect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc);
+ if (RT_SUCCESS(rc))
+ RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETCLASSIF,pfnDisconnect}
+ */
+static DECLCALLBACK(int) utsGadgetClassTestDisconnect(PUTSGADGETCLASSINT pClass)
+{
+ return RTLinuxSysFsWriteStrFile("disconnect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc);}
+
+
+
+/**
+ * The gadget host interface callback table.
+ */
+const UTSGADGETCLASSIF g_UtsGadgetClassTest =
+{
+ /** enmType */
+ UTSGADGETCLASS_TEST,
+ /** pszDesc */
+ "UTS test device gadget class",
+ /** cbIf */
+ sizeof(UTSGADGETCLASSINT),
+ /** pfnInit */
+ utsGadgetClassTestInit,
+ /** pfnTerm */
+ utsGadgetClassTestTerm,
+ /** pfnGetBusId */
+ utsGadgetClassTestGetBusId,
+ /** pfnConnect */
+ utsGadgetClassTestConnect,
+ /** pfnDisconnect. */
+ utsGadgetClassTestDisconnect
+};
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp
new file mode 100644
index 00000000..1f8cc53b
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp
@@ -0,0 +1,179 @@
+/* $Id: UsbTestServiceGadgetHost.cpp $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "UsbTestServiceGadget.h"
+#include "UsbTestServiceGadgetHostInternal.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Internal UTS gadget host instance data.
+ */
+typedef struct UTSGADGETHOSTINT
+{
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Pointer to the gadget host callback table. */
+ PCUTSGADGETHOSTIF pHstIf;
+ /** Interface specific instance data - variable in size. */
+ uint8_t abIfInst[1];
+} UTSGADGETHOSTINT;
+/** Pointer to the internal gadget host instance data. */
+typedef UTSGADGETHOSTINT *PUTSGADGETHOSTINT;
+
+
+/*********************************************************************************************************************************
+* Global variables *
+*********************************************************************************************************************************/
+
+/** Known gadget host interfaces. */
+static const PCUTSGADGETHOSTIF g_apUtsGadgetHostIf[] =
+{
+ &g_UtsGadgetHostIfUsbIp,
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Destroys a gadget host instance.
+ *
+ * @param pThis The gadget host instance.
+ */
+static void utsGadgetHostDestroy(PUTSGADGETHOSTINT pThis)
+{
+ /** @todo Remove all gadgets. */
+ pThis->pHstIf->pfnTerm((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0]);
+ RTMemFree(pThis);
+}
+
+
+DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg,
+ PUTSGADGETHOST phGadgetHost)
+{
+ int rc = VINF_SUCCESS;
+ PCUTSGADGETHOSTIF pIf = NULL;
+
+ /* Get the interface. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetHostIf); i++)
+ {
+ if (g_apUtsGadgetHostIf[i]->enmType == enmType)
+ {
+ pIf = g_apUtsGadgetHostIf[i];
+ break;
+ }
+ }
+
+ if (RT_LIKELY(pIf))
+ {
+ PUTSGADGETHOSTINT pThis = (PUTSGADGETHOSTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETHOSTINT, abIfInst[pIf->cbIf]));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->cRefs = 1;
+ pThis->pHstIf = pIf;
+ rc = pIf->pfnInit((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], paCfg);
+ if (RT_SUCCESS(rc))
+ *phGadgetHost = pThis;
+ else
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+
+DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost)
+{
+ PUTSGADGETHOSTINT pThis = hGadgetHost;
+
+ AssertPtrReturn(pThis, 0);
+
+ return ASMAtomicIncU32(&pThis->cRefs);
+}
+
+
+DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost)
+{
+ PUTSGADGETHOSTINT pThis = hGadgetHost;
+
+ AssertPtrReturn(pThis, 0);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ if (!cRefs)
+ utsGadgetHostDestroy(pThis);
+
+ return cRefs;
+}
+
+
+DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget)
+{
+ PUTSGADGETHOSTINT pThis = hGadgetHost;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ return pThis->pHstIf->pfnGadgetConnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget);
+}
+
+
+DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget)
+{
+ PUTSGADGETHOSTINT pThis = hGadgetHost;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ return pThis->pHstIf->pfnGadgetDisconnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget);
+}
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h
new file mode 100644
index 00000000..6ae11fd0
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h
@@ -0,0 +1,132 @@
+/* $Id: UsbTestServiceGadgetHostInternal.h $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Gadget API.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h
+#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+
+#include "UsbTestServiceGadget.h"
+
+RT_C_DECLS_BEGIN
+
+/** Pointer to an opaque type dependent gadget host instance data. */
+typedef struct UTSGADGETHOSTTYPEINT *PUTSGADGETHOSTTYPEINT;
+/** Pointer to a gadget host instance pointer. */
+typedef PUTSGADGETHOSTTYPEINT *PPUTSGADETHOSTTYPEINT;
+
+/**
+ * Gadget host interface.
+ */
+typedef struct UTSGADGETHOSTIF
+{
+ /** The gadget host type implemented. */
+ UTSGADGETHOSTTYPE enmType;
+ /** Description. */
+ const char *pszDesc;
+ /** Size of the interface specific instance data. */
+ size_t cbIf;
+
+ /**
+ * Initializes the gadget host interface.
+ *
+ * @returns IPRT status code.
+ * @param pIf The interface specific instance data.
+ * @param paCfg The configuration of the interface.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg));
+
+ /**
+ * Terminates the gadget host interface.
+ *
+ * @param pIf The interface specific instance data.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETHOSTTYPEINT pIf));
+
+ /**
+ * Adds the given gadget to the host interface.
+ *
+ * @returns IPRT status code.
+ * @param pIf The interface specific instance data.
+ * @param hGadget The gadget handle to add.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGadgetAdd, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget));
+
+ /**
+ * Removes the given gadget from the host interface.
+ *
+ * @returns IPRT status code.
+ * @param pIf The interface specific instance data.
+ * @param hGadget The gadget handle to remove.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGadgetRemove, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget));
+
+ /**
+ * Connects the given gadget to the host interface so it appears as connected to the client
+ * of the gadget host.
+ *
+ * @returns IPRT status code.
+ * @param pIf The interface specific instance data.
+ * @param hGadget The gadget handle to add.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGadgetConnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget));
+
+ /**
+ * Disconnects the given gadget from the host interface so it appears as disconnected to the client
+ * of the gadget host.
+ *
+ * @returns IPRT status code.
+ * @param pIf The interface specific instance data.
+ * @param hGadget The gadget handle to add.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnGadgetDisconnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget));
+
+} UTSGADGETHOSTIF;
+/** Pointer to a gadget host callback table. */
+typedef UTSGADGETHOSTIF *PUTSGADGETHOSTIF;
+/** Pointer to a const gadget host callback table. */
+typedef const struct UTSGADGETHOSTIF *PCUTSGADGETHOSTIF;
+
+extern UTSGADGETHOSTIF const g_UtsGadgetHostIfUsbIp;
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h */
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp
new file mode 100644
index 00000000..a0f183ca
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp
@@ -0,0 +1,263 @@
+/* $Id: UsbTestServiceGadgetHostUsbIp.cpp $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, USB gadget host interface
+ * for USB/IP.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/env.h>
+#include <iprt/mem.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include "UsbTestServiceGadgetHostInternal.h"
+#include "UsbTestServicePlatform.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Internal UTS gadget host instance data.
+ */
+typedef struct UTSGADGETHOSTTYPEINT
+{
+ /** Handle to the USB/IP daemon process. */
+ RTPROCESS hProcUsbIp;
+} UTSGADGETHOSTTYPEINT;
+
+/** Default port of the USB/IP server. */
+#define UTS_GADGET_HOST_USBIP_PORT_DEF 3240
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Worker for binding/unbinding the given gadget from the USB/IP server.
+ *
+ * @returns IPRT status code.
+ * @param pThis The gadget host instance.
+ * @param hGadget The gadget handle.
+ * @param fBind Flag whether to do a bind or unbind.
+ */
+static int usbGadgetHostUsbIpBindUnbind(PUTSGADGETHOSTTYPEINT pThis, UTSGADGET hGadget, bool fBind)
+{
+ RT_NOREF1(pThis);
+ uint32_t uBusId, uDevId;
+ char aszBus[32];
+
+ uBusId = utsGadgetGetBusId(hGadget);
+ uDevId = utsGadgetGetDevId(hGadget);
+
+ /* Create the busid argument string. */
+ size_t cbRet = RTStrPrintf(&aszBus[0], RT_ELEMENTS(aszBus), "%u-%u", uBusId, uDevId);
+ if (cbRet == RT_ELEMENTS(aszBus))
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Bind to the USB/IP server. */
+ RTPROCESS hProcUsbIp = NIL_RTPROCESS;
+ const char *apszArgv[5];
+
+ apszArgv[0] = "usbip";
+ apszArgv[1] = fBind ? "bind" : "unbind";
+ apszArgv[2] = "-b";
+ apszArgv[3] = &aszBus[0];
+ apszArgv[4] = NULL;
+
+ int rc = RTProcCreate("usbip", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcUsbIp);
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS ProcSts;
+ rc = RTProcWait(hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts);
+ if (RT_SUCCESS(rc))
+ {
+ /* Evaluate the process status. */
+ if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL
+ || ProcSts.iStatus != 0)
+ rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{UTSGADGETHOSTIF,pfnInit}
+ */
+static DECLCALLBACK(int) utsGadgetHostUsbIpInit(PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg)
+{
+ int rc = VINF_SUCCESS;
+ uint16_t uPort = 0;
+
+ pIf->hProcUsbIp = NIL_RTPROCESS;
+
+ rc = utsGadgetCfgQueryU16Def(paCfg, "UsbIp/Port", &uPort, UTS_GADGET_HOST_USBIP_PORT_DEF);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make sure the kernel drivers are loaded. */
+ rc = utsPlatformModuleLoad("usbip-core", NULL, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = utsPlatformModuleLoad("usbip-host", NULL, 0);
+ if (RT_SUCCESS(rc))
+ {
+ char aszPort[10];
+ char aszPidFile[64];
+ const char *apszArgv[6];
+
+ RTStrPrintf(aszPort, RT_ELEMENTS(aszPort), "%u", uPort);
+ RTStrPrintf(aszPidFile, RT_ELEMENTS(aszPidFile), "/var/run/usbipd-%u.pid", uPort);
+ /* Start the USB/IP server process. */
+ apszArgv[0] = "usbipd";
+ apszArgv[1] = "--tcp-port";
+ apszArgv[2] = aszPort;
+ apszArgv[3] = "--pid";
+ apszArgv[4] = aszPidFile;
+ apszArgv[5] = NULL;
+ rc = RTProcCreate("usbipd", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &pIf->hProcUsbIp);
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for a bit to make sure the server started up successfully. */
+ uint64_t tsStart = RTTimeMilliTS();
+ do
+ {
+ RTPROCSTATUS ProcSts;
+ rc = RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_NOBLOCK, &ProcSts);
+ if (rc != VERR_PROCESS_RUNNING)
+ {
+ rc = VERR_INVALID_HANDLE;
+ break;
+ }
+ RTThreadSleep(1);
+ rc = VINF_SUCCESS;
+ } while (RTTimeMilliTS() - tsStart < 2 * 1000); /* 2 seconds. */
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETHOSTIF,pfnTerm}
+ */
+static DECLCALLBACK(void) utsGadgetHostUsbIpTerm(PUTSGADGETHOSTTYPEINT pIf)
+{
+ /* Kill the process and wait for it to terminate. */
+ RTProcTerminate(pIf->hProcUsbIp);
+
+ RTPROCSTATUS ProcSts;
+ RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts);
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetAdd}
+ */
+static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetAdd(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)
+{
+ /* Nothing to do so far. */
+ RT_NOREF2(pIf, hGadget);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetRemove}
+ */
+static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetRemove(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)
+{
+ /* Nothing to do so far. */
+ RT_NOREF2(pIf, hGadget);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetConnect}
+ */
+static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetConnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)
+{
+ return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, true /* fBind */);
+}
+
+
+/**
+ * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetDisconnect}
+ */
+static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetDisconnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)
+{
+ return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, false /* fBind */);
+}
+
+
+
+/**
+ * The gadget host interface callback table.
+ */
+const UTSGADGETHOSTIF g_UtsGadgetHostIfUsbIp =
+{
+ /** enmType */
+ UTSGADGETHOSTTYPE_USBIP,
+ /** pszDesc */
+ "UTS USB/IP gadget host",
+ /** cbIf */
+ sizeof(UTSGADGETHOSTTYPEINT),
+ /** pfnInit */
+ utsGadgetHostUsbIpInit,
+ /** pfnTerm */
+ utsGadgetHostUsbIpTerm,
+ /** pfnGadgetAdd */
+ utsGadgetHostUsbIpGadgetAdd,
+ /** pfnGadgetRemove */
+ utsGadgetHostUsbIpGadgetRemove,
+ /** pfnGadgetConnect */
+ utsGadgetHostUsbIpGadgetConnect,
+ /** pfnGadgetDisconnect */
+ utsGadgetHostUsbIpGadgetDisconnect
+};
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h
new file mode 100644
index 00000000..dce2719f
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h
@@ -0,0 +1,118 @@
+/* $Id: UsbTestServiceGadgetInternal.h $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Interal gadget interfaces.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h
+#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+
+#include "UsbTestServiceGadget.h"
+
+RT_C_DECLS_BEGIN
+
+/** Pointer to an opaque type dependent gadget host instance data. */
+typedef struct UTSGADGETCLASSINT *PUTSGADGETCLASSINT;
+/** Pointer to a gadget host instance pointer. */
+typedef PUTSGADGETCLASSINT *PPUTSGADGETCLASSINT;
+
+/**
+ * Gadget class interface.
+ */
+typedef struct UTSGADGETCLASSIF
+{
+ /** The gadget class type implemented. */
+ UTSGADGETCLASS enmClass;
+ /** Description. */
+ const char *pszDesc;
+ /** Size of the class specific instance data. */
+ size_t cbClass;
+
+ /**
+ * Initializes the gadget class instance.
+ *
+ * @returns IPRT status code.
+ * @param pClass The interface specific instance data.
+ * @param paCfg The configuration of the interface.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg));
+
+ /**
+ * Terminates the gadget class instance.
+ *
+ * @param pClass The interface specific instance data.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETCLASSINT pClass));
+
+ /**
+ * Returns the bus ID of the class instance.
+ *
+ * @returns Bus ID.
+ * @param pClass The interface specific instance data.
+ */
+ DECLR3CALLBACKMEMBER(uint32_t, pfnGetBusId, (PUTSGADGETCLASSINT pClass));
+
+ /**
+ * Connects the gadget.
+ *
+ * @returns IPRT status code.
+ * @param pClass The interface specific instance data.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnConnect, (PUTSGADGETCLASSINT pClass));
+
+ /**
+ * Disconnect the gadget.
+ *
+ * @returns IPRT status code.
+ * @param pClass The interface specific instance data.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnDisconnect, (PUTSGADGETCLASSINT pClass));
+
+} UTSGADGETCLASSIF;
+/** Pointer to a gadget class callback table. */
+typedef UTSGADGETCLASSIF *PUTSGADGETCLASSIF;
+/** Pointer to a const gadget host callback table. */
+typedef const struct UTSGADGETCLASSIF *PCUTSGADGETCLASSIF;
+
+extern UTSGADGETCLASSIF const g_UtsGadgetClassTest;
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h */
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h
new file mode 100644
index 00000000..71e2d961
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h
@@ -0,0 +1,226 @@
+/* $Id: UsbTestServiceInternal.h $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h
+#define VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/getopt.h>
+#include <iprt/stream.h>
+
+#include "UsbTestServiceProtocol.h"
+
+RT_C_DECLS_BEGIN
+
+/** Opaque UTS transport layer specific client data. */
+typedef struct UTSTRANSPORTCLIENT *PUTSTRANSPORTCLIENT;
+typedef PUTSTRANSPORTCLIENT *PPUTSTRANSPORTCLIENT;
+
+/**
+ * Transport layer descriptor.
+ */
+typedef struct UTSTRANSPORT
+{
+ /** The name. */
+ char szName[16];
+ /** The description. */
+ const char *pszDesc;
+ /** Pointer to an array of options. */
+ PCRTGETOPTDEF paOpts;
+ /** The number of options in the array. */
+ size_t cOpts;
+
+ /**
+ * Print the usage information for this transport layer.
+ *
+ * @param pStream The stream to print the usage info to.
+ *
+ * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnUsage, (PRTSTREAM pStream));
+
+ /**
+ * Handle an option.
+ *
+ * When encountering an options that is not part of the base options, we'll call
+ * this method for each transport layer until one handles it.
+ *
+ * @retval VINF_SUCCESS if handled.
+ * @retval VERR_TRY_AGAIN if not handled.
+ * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status.
+ *
+ * @param ch The short option value.
+ * @param pVal Pointer to the value union.
+ *
+ * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnOption, (int ch, PCRTGETOPTUNION pVal));
+
+ /**
+ * Initializes the transport layer.
+ *
+ * @returns IPRT status code. On errors, the transport layer shall call
+ * RTMsgError to display the error details to the user.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnInit, (void));
+
+ /**
+ * Terminate the transport layer, closing and freeing resources.
+ *
+ * On errors, the transport layer shall call RTMsgError to display the error
+ * details to the user.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnTerm, (void));
+
+ /**
+ * Waits for a new client to connect and returns the client specific data on
+ * success.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnWaitForConnect, (PPUTSTRANSPORTCLIENT ppClientNew));
+
+ /**
+ * Polls for incoming packets.
+ *
+ * @returns true if there are pending packets, false if there isn't.
+ * @param pClient The client to poll for data.
+ */
+ DECLR3CALLBACKMEMBER(bool, pfnPollIn, (PUTSTRANSPORTCLIENT pClient));
+
+ /**
+ * Adds any pollable handles to the poll set.
+ *
+ * @returns IPRT status code.
+ * @param hPollSet The poll set to add them to.
+ * @param pClient The transport client structure.
+ * @param idStart The handle ID to start at.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnPollSetAdd, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart));
+
+ /**
+ * Removes the given client frmo the given pollset.
+ *
+ * @returns IPRT status code.
+ * @param hPollSet The poll set to remove from.
+ * @param pClient The transport client structure.
+ * @param idStart The handle ID to remove.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnPollSetRemove, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart));
+
+ /**
+ * Receives an incoming packet.
+ *
+ * This will block until the data becomes available or we're interrupted by a
+ * signal or something.
+ *
+ * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED,
+ * the current operation will be aborted when applicable. When
+ * interrupted, the transport layer will store the data until the next
+ * receive call.
+ *
+ * @param pClient The transport client structure.
+ * @param ppPktHdr Where to return the pointer to the packet we've
+ * read. This is allocated from the heap using
+ * RTMemAlloc (w/ UTSPKT_ALIGNMENT) and must be
+ * free by calling RTMemFree.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnRecvPkt, (PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr));
+
+ /**
+ * Sends an outgoing packet.
+ *
+ * This will block until the data has been written.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_INTERRUPTED if interrupted before anything was sent.
+ *
+ * @param pClient The transport client structure.
+ * @param pPktHdr The packet to send. The size is given by
+ * aligning the size in the header by
+ * UTSPKT_ALIGNMENT.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSendPkt, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr));
+
+ /**
+ * Sends a babble packet and disconnects the client (if applicable).
+ *
+ * @param pClient The transport client structure.
+ * @param pPktHdr The packet to send. The size is given by
+ * aligning the size in the header by
+ * UTSPKT_ALIGNMENT.
+ * @param cMsSendTimeout The send timeout measured in milliseconds.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnBabble, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout));
+
+ /**
+ * Notification about a client HOWDY.
+ *
+ * @param pClient The transport client structure.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy, (PUTSTRANSPORTCLIENT pClient));
+
+ /**
+ * Notification about a client BYE.
+ *
+ * For connection oriented transport layers, it would be good to disconnect the
+ * client at this point.
+ *
+ * @param pClient The transport client structure.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnNotifyBye, (PUTSTRANSPORTCLIENT pClient));
+
+ /**
+ * Notification about a REBOOT or SHUTDOWN.
+ *
+ * For connection oriented transport layers, stop listening for and
+ * accepting at this point.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnNotifyReboot, (void));
+
+ /** Non-zero end marker. */
+ uint32_t u32EndMarker;
+} UTSTRANSPORT;
+/** Pointer to a const transport layer descriptor. */
+typedef const struct UTSTRANSPORT *PCUTSTRANSPORT;
+
+
+extern UTSTRANSPORT const g_TcpTransport;
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h */
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp
new file mode 100644
index 00000000..0f422479
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp
@@ -0,0 +1,448 @@
+/* $Id: UsbTestServicePlatform-linux.cpp $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Platform
+ * specific helpers - Linux version.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+
+#include <iprt/linux/sysfs.h>
+
+#include "UsbTestServicePlatform.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Where the dummy_hcd.* and dummy_udc.* entries are stored. */
+#define UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/sys/devices/platform"
+
+/**
+ * A USB bus provided by the dummy HCD.
+ */
+typedef struct UTSPLATFORMLNXDUMMYHCDBUS
+{
+ /** The bus ID on the host the dummy HCD is serving. */
+ uint32_t uBusId;
+ /** Flag whether this is a super speed bus. */
+ bool fSuperSpeed;
+} UTSPLATFORMLNXDUMMYHCDBUS;
+/** Pointer to a Dummy HCD bus. */
+typedef UTSPLATFORMLNXDUMMYHCDBUS *PUTSPLATFORMLNXDUMMYHCDBUS;
+
+/**
+ * A dummy UDC descriptor.
+ */
+typedef struct UTSPLATFORMLNXDUMMYHCD
+{
+ /** Index of the dummy hcd entry. */
+ uint32_t idxDummyHcd;
+ /** Name for the dummy HCD. */
+ const char *pszHcdName;
+ /** Name for the accompanying dummy HCD. */
+ const char *pszUdcName;
+ /** Flag whether this HCD is free for use. */
+ bool fAvailable;
+ /** Flag whether this HCD contains a super speed capable bus. */
+ bool fSuperSpeed;
+ /** Number of busses this HCD instance serves. */
+ unsigned cBusses;
+ /** Bus structures the HCD serves.*/
+ PUTSPLATFORMLNXDUMMYHCDBUS paBusses;
+} UTSPLATFORMLNXDUMMYHCD;
+/** Pointer to a dummy HCD entry. */
+typedef UTSPLATFORMLNXDUMMYHCD *PUTSPLATFORMLNXDUMMYHCD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Array of dummy HCD entries. */
+static PUTSPLATFORMLNXDUMMYHCD g_paDummyHcd = NULL;
+/** Number of Dummy hCD entries in the array. */
+static unsigned g_cDummyHcd = 0;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Queries the assigned busses for the given dummy HCD instance.
+ *
+ * @returns IPRT status code.
+ * @param pHcd The dummy HCD bus instance.
+ * @param pszHcdName The base HCD name.
+ */
+static int utsPlatformLnxDummyHcdQueryBusses(PUTSPLATFORMLNXDUMMYHCD pHcd, const char *pszHcdName)
+{
+ int rc = VINF_SUCCESS;
+ char aszPath[RTPATH_MAX + 1];
+ unsigned idxBusCur = 0;
+ unsigned idxBusMax = 0;
+
+ size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/usb*",
+ pszHcdName, pHcd->idxDummyHcd);
+ if (cchPath == RT_ELEMENTS(aszPath))
+ return VERR_BUFFER_OVERFLOW;
+
+ RTDIR hDir = NULL;
+ rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTDIRENTRY DirFolderContent;
+ rc = RTDirRead(hDir, &DirFolderContent, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uBusId = 0;
+
+ /* Extract the bus number - it is after "usb", i.e. "usb9" indicates a bus ID of 9. */
+ rc = RTStrToUInt32Ex(&DirFolderContent.szName[3], NULL, 10, &uBusId);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check whether this is a super speed bus. */
+ int64_t iSpeed = 0;
+ bool fSuperSpeed = false;
+ rc = RTLinuxSysFsReadIntFile(10, &iSpeed, UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/%s/speed",
+ pszHcdName, pHcd->idxDummyHcd, DirFolderContent.szName);
+ if ( RT_SUCCESS(rc)
+ && (iSpeed == 5000 || iSpeed == 10000))
+ {
+ fSuperSpeed = true;
+ pHcd->fSuperSpeed = true;
+ }
+
+ /* Add to array of available busses for this HCD. */
+ if (idxBusCur == idxBusMax)
+ {
+ size_t cbNew = (idxBusMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCDBUS);
+ PUTSPLATFORMLNXDUMMYHCDBUS pNew = (PUTSPLATFORMLNXDUMMYHCDBUS)RTMemRealloc(pHcd->paBusses, cbNew);
+ if (pNew)
+ {
+ idxBusMax += 10;
+ pHcd->paBusses = pNew;
+ }
+ }
+
+ if (idxBusCur < idxBusMax)
+ {
+ pHcd->paBusses[idxBusCur].uBusId = uBusId;
+ pHcd->paBusses[idxBusCur].fSuperSpeed = fSuperSpeed;
+ idxBusCur++;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ } while (RT_SUCCESS(rc));
+
+ pHcd->cBusses = idxBusCur;
+
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+
+ RTDirClose(hDir);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Scans all available HCDs with the given name.
+ *
+ * @returns IPRT status code.
+ * @param pszHcdName The base HCD name.
+ * @param pszUdcName The base UDC name.
+ */
+static int utsPlatformLnxHcdScanByName(const char *pszHcdName, const char *pszUdcName)
+{
+ char aszPath[RTPATH_MAX + 1];
+ size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath),
+ UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.*", pszHcdName);
+ if (cchPath == RT_ELEMENTS(aszPath))
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Enumerate the available HCD and their bus numbers. */
+ RTDIR hDir = NULL;
+ int rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned idxHcdCur = g_cDummyHcd;
+ unsigned idxHcdMax = g_cDummyHcd;
+
+ do
+ {
+ RTDIRENTRY DirFolderContent;
+ rc = RTDirRead(hDir, &DirFolderContent, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the HCD index and assigned bus number form the sysfs entries,
+ * Any error here is silently ignored and results in the HCD not being
+ * added to the list of available controllers.
+ */
+ const char *pszIdx = RTStrStr(DirFolderContent.szName, ".");
+ if (pszIdx)
+ {
+ /* Skip the separator and convert number to index. */
+ pszIdx++;
+
+ uint32_t idxHcd = 0;
+ rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add to array of available HCDs. */
+ if (idxHcdCur == idxHcdMax)
+ {
+ size_t cbNew = (idxHcdMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCD);
+ PUTSPLATFORMLNXDUMMYHCD pNew = (PUTSPLATFORMLNXDUMMYHCD)RTMemRealloc(g_paDummyHcd, cbNew);
+ if (pNew)
+ {
+ idxHcdMax += 10;
+ g_paDummyHcd = pNew;
+ }
+ }
+
+ if (idxHcdCur < idxHcdMax)
+ {
+ g_paDummyHcd[idxHcdCur].idxDummyHcd = idxHcd;
+ g_paDummyHcd[idxHcdCur].pszHcdName = pszHcdName;
+ g_paDummyHcd[idxHcdCur].pszUdcName = pszUdcName;
+ g_paDummyHcd[idxHcdCur].fAvailable = true;
+ g_paDummyHcd[idxHcdCur].fSuperSpeed = false;
+ g_paDummyHcd[idxHcdCur].cBusses = 0;
+ g_paDummyHcd[idxHcdCur].paBusses = NULL;
+ rc = utsPlatformLnxDummyHcdQueryBusses(&g_paDummyHcd[idxHcdCur], pszHcdName);
+ if (RT_SUCCESS(rc))
+ idxHcdCur++;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ } while (RT_SUCCESS(rc));
+
+ g_cDummyHcd = idxHcdCur;
+
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+
+ RTDirClose(hDir);
+ }
+
+ return rc;
+}
+
+DECLHIDDEN(int) utsPlatformInit(void)
+{
+ /* Load the modules required for setting up USB/IP testing. */
+ int rc = utsPlatformModuleLoad("libcomposite", NULL, 0);
+ if (RT_SUCCESS(rc))
+ {
+ const char *apszArg[] = { "num=20" }; /** @todo Make configurable from config. */
+ rc = utsPlatformModuleLoad("dummy_hcd", &apszArg[0], RT_ELEMENTS(apszArg));
+ if (RT_SUCCESS(rc))
+ rc = utsPlatformModuleLoad("dummy_hcd_ss", &apszArg[0], RT_ELEMENTS(apszArg));
+ if (RT_SUCCESS(rc))
+ rc = utsPlatformLnxHcdScanByName("dummy_hcd", "dummy_udc");
+ if (RT_SUCCESS(rc))
+ rc = utsPlatformLnxHcdScanByName("dummy_hcd_ss", "dummy_udc_ss");
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) utsPlatformTerm(void)
+{
+ /* Unload dummy HCD. */
+ utsPlatformModuleUnload("dummy_hcd");
+ utsPlatformModuleUnload("dummy_hcd_ss");
+
+ RTMemFree(g_paDummyHcd);
+}
+
+
+DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv,
+ unsigned cArgv)
+{
+ RTPROCESS hProcModprobe = NIL_RTPROCESS;
+ const char **papszArgs = (const char **)RTMemAllocZ((3 + cArgv) * sizeof(const char *));
+ if (RT_UNLIKELY(!papszArgs))
+ return VERR_NO_MEMORY;
+
+ papszArgs[0] = "modprobe";
+ papszArgs[1] = pszModule;
+
+ unsigned idx;
+ for (idx = 0; idx < cArgv; idx++)
+ papszArgs[2+idx] = papszArgv[idx];
+ papszArgs[2+idx] = NULL;
+
+ int rc = RTProcCreate("modprobe", papszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe);
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS ProcSts;
+ rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts);
+ if (RT_SUCCESS(rc))
+ {
+ /* Evaluate the process status. */
+ if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL
+ || ProcSts.iStatus != 0)
+ rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */
+ }
+ }
+
+ RTMemFree(papszArgs);
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule)
+{
+ RTPROCESS hProcModprobe = NIL_RTPROCESS;
+ const char *apszArgv[3];
+
+ apszArgv[0] = "rmmod";
+ apszArgv[1] = pszModule;
+ apszArgv[2] = NULL;
+
+ int rc = RTProcCreate("rmmod", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe);
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS ProcSts;
+ rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts);
+ if (RT_SUCCESS(rc))
+ {
+ /* Evaluate the process status. */
+ if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL
+ || ProcSts.iStatus != 0)
+ rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */
+ }
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId)
+{
+ int rc = VERR_NOT_FOUND;
+
+ for (unsigned i = 0; i < g_cDummyHcd; i++)
+ {
+ PUTSPLATFORMLNXDUMMYHCD pHcd = &g_paDummyHcd[i];
+
+ /*
+ * We can't use a super speed capable UDC for gadgets with lower speeds
+ * because they hardcode the maximum speed to SuperSpeed most of the time
+ * which will make it unusable for lower speeds.
+ */
+ if ( pHcd->fAvailable
+ && pHcd->fSuperSpeed == fSuperSpeed)
+ {
+ /* Check all assigned busses for a speed match. */
+ for (unsigned idxBus = 0; idxBus < pHcd->cBusses; idxBus++)
+ {
+ if (pHcd->paBusses[idxBus].fSuperSpeed == fSuperSpeed)
+ {
+ rc = VINF_SUCCESS;
+ int cbRet = RTStrAPrintf(ppszUdc, "%s.%u", pHcd->pszUdcName, pHcd->idxDummyHcd);
+ if (cbRet == -1)
+ rc = VERR_NO_STR_MEMORY;
+ *puBusId = pHcd->paBusses[idxBus].uBusId;
+ pHcd->fAvailable = false;
+ break;
+ }
+ }
+
+ if (rc != VERR_NOT_FOUND)
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ const char *pszIdx = RTStrStr(pszUdc, ".");
+ if (pszIdx)
+ {
+ size_t cchUdcName = pszIdx - pszUdc;
+ pszIdx++;
+ uint32_t idxHcd = 0;
+ rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_NOT_FOUND;
+
+ for (unsigned i = 0; i < g_cDummyHcd; i++)
+ {
+ if ( g_paDummyHcd[i].idxDummyHcd == idxHcd
+ && !RTStrNCmp(g_paDummyHcd[i].pszUdcName, pszUdc, cchUdcName))
+ {
+ AssertReturn(!g_paDummyHcd[i].fAvailable, VERR_INVALID_PARAMETER);
+ g_paDummyHcd[i].fAvailable = true;
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h
new file mode 100644
index 00000000..cf5eed26
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h
@@ -0,0 +1,105 @@
+/* $Id: UsbTestServicePlatform.h $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Platform specific helpers.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h
+#define VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Initializes the platform specific structures for UTS.
+ *
+ * @returns IPRT status code.
+ */
+DECLHIDDEN(int) utsPlatformInit(void);
+
+/**
+ * Frees all platform specific structures for UTS.
+ */
+DECLHIDDEN(void) utsPlatformTerm(void);
+
+/**
+ * Loads the specified kernel module on the platform.
+ *
+ * @returns IPRT status code.
+ * @param pszModule The module to load.
+ * @param papszArgv Array of arguments to pass to the module.
+ * @param cArgv Number of argument array entries.
+ */
+DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv,
+ unsigned cArgv);
+
+/**
+ * Unloads the specified kernel module on the platform.
+ *
+ * @returns IPRT status code.
+ * @param pszModule The module to unload.
+ */
+DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule);
+
+#ifdef RT_OS_LINUX
+
+/**
+ * Acquires a free UDC to attach a gadget to.
+ *
+ * @returns IPRT status code.
+ * @param fSuperSpeed Flag whether a super speed bus is required.
+ * @param ppszUdc Where to store the pointer to the name of the UDC on success.
+ * Free with RTStrFree().
+ * @param puBusId Where to store the bus ID the UDC is attached to on the host side.
+ */
+DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId);
+
+/**
+ * Releases the given UDC for other use.
+ *
+ * @returns IPRT status code.
+ * @param pszUdc The UDC to release.
+ */
+DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc);
+
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h */
+
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp
new file mode 100644
index 00000000..a32e8a3e
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp
@@ -0,0 +1,120 @@
+/* $Id: UsbTestServiceProtocol.cpp $ */
+/** @file
+ * UsbTestService - Remote USB test configuration and execution server, Protocol helpers.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+
+#include "UsbTestServiceProtocol.h"
+
+
+
+/**
+ * Converts a UTS packet header from host to network byte order.
+ *
+ * @param pPktHdr The packet header to convert.
+ */
+DECLINLINE(void) utsProtocolPktHdrH2N(PUTSPKTHDR pPktHdr)
+{
+ pPktHdr->cb = RT_H2N_U32(pPktHdr->cb);
+ pPktHdr->uCrc32 = RT_H2N_U32(pPktHdr->uCrc32);
+}
+
+
+/**
+ * Converts a UTS packet header from network to host byte order.
+ *
+ * @param pPktHdr The packet header to convert.
+ */
+DECLINLINE(void) utsProtocolPktHdrN2H(PUTSPKTHDR pPktHdr)
+{
+ pPktHdr->cb = RT_N2H_U32(pPktHdr->cb);
+ pPktHdr->uCrc32 = RT_N2H_U32(pPktHdr->uCrc32);
+}
+
+
+/**
+ * Converts a UTS status header from host to network byte order.
+ *
+ * @param pPktHdr The packet header to convert.
+ */
+DECLINLINE(void) utsProtocolStsHdrH2N(PUTSPKTSTS pPktHdr)
+{
+ utsProtocolPktHdrH2N(&pPktHdr->Hdr);
+ pPktHdr->rcReq = RT_H2N_U32(pPktHdr->rcReq);
+ pPktHdr->cchStsMsg = RT_H2N_U32(pPktHdr->cchStsMsg);
+}
+
+
+/**
+ * Converts a UTS status header from network to host byte order.
+ *
+ * @param pPktHdr The packet header to convert.
+ */
+DECLINLINE(void) utsProtocolStsHdrN2H(PUTSPKTSTS pPktHdr)
+{
+ utsProtocolPktHdrN2H(&pPktHdr->Hdr);
+ pPktHdr->rcReq = RT_N2H_U32(pPktHdr->rcReq);
+ pPktHdr->cchStsMsg = RT_N2H_U32(pPktHdr->cchStsMsg);
+}
+
+
+DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr)
+{
+ utsProtocolPktHdrH2N(pPktHdr);
+}
+
+
+DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr)
+{
+ RT_NOREF1(pPktHdr);
+}
+
+
+DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTSTS pPktHdr)
+{
+ RT_NOREF1(pPktHdr);
+}
+
+
+DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTSTS pPktHdr)
+{
+ RT_NOREF1(pPktHdr);
+}
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h
new file mode 100644
index 00000000..5378493b
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h
@@ -0,0 +1,373 @@
+/* $Id: UsbTestServiceProtocol.h $ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Protocol Header.
+ */
+
+/*
+ * Copyright (C) 2016-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h
+#define VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Common Packet header (for requests and replies).
+ */
+typedef struct UTSPKTHDR
+{
+ /** The unpadded packet length. This include this header. */
+ uint32_t cb;
+ /** The CRC-32 for the packet starting from the opcode field. 0 if the packet
+ * hasn't been CRCed. */
+ uint32_t uCrc32;
+ /** Packet opcode, an unterminated ASCII string. */
+ uint8_t achOpcode[8];
+} UTSPKTHDR;
+AssertCompileSize(UTSPKTHDR, 16);
+/** Pointer to a packet header. */
+typedef UTSPKTHDR *PUTSPKTHDR;
+/** Pointer to a packet header. */
+typedef UTSPKTHDR const *PCUTSPKTHDR;
+/** Pointer to a packet header pointer. */
+typedef PUTSPKTHDR *PPUTSPKTHDR;
+
+/** Packet alignment. */
+#define UTSPKT_ALIGNMENT 16
+/** Max packet size. */
+#define UTSPKT_MAX_SIZE _256K
+
+/**
+ * Status packet.
+ */
+typedef struct UTSPKTSTS
+{
+ /** Embedded common packet header. */
+ UTSPKTHDR Hdr;
+ /** The IPRT status code of the request. */
+ int32_t rcReq;
+ /** Size of the optional status message following this structure -
+ * only for errors. */
+ uint32_t cchStsMsg;
+ /** Padding - reserved. */
+ uint8_t au8Padding[8];
+} UTSPKTSTS;
+AssertCompileSizeAlignment(UTSPKTSTS, UTSPKT_ALIGNMENT);
+/** Pointer to a status packet header. */
+typedef UTSPKTSTS *PUTSPKTSTS;
+
+#define UTSPKT_OPCODE_HOWDY "HOWDY "
+
+/** 32bit protocol version consisting of a 16bit major and 16bit minor part. */
+#define UTS_PROTOCOL_VS (UTS_PROTOCOL_VS_MAJOR | UTS_PROTOCOL_VS_MINOR)
+/** The major version part of the protocol version. */
+#define UTS_PROTOCOL_VS_MAJOR (1 << 16)
+/** The minor version part of the protocol version. */
+#define UTS_PROTOCOL_VS_MINOR (0)
+
+/**
+ * The HOWDY request structure.
+ */
+typedef struct UTSPKTREQHOWDY
+{
+ /** Embedded packet header. */
+ UTSPKTHDR Hdr;
+ /** Version of the protocol the client wants to use. */
+ uint32_t uVersion;
+ /** Mask of USB device connections the client wants to use. */
+ uint32_t fUsbConn;
+ /** The number of characters for the hostname. */
+ uint32_t cchHostname;
+ /** The client host name as terminated ASCII string. */
+ char achHostname[68];
+} UTSPKTREQHOWDY;
+AssertCompileSizeAlignment(UTSPKTREQHOWDY, UTSPKT_ALIGNMENT);
+/** Pointer to a HOWDY request structure. */
+typedef UTSPKTREQHOWDY *PUTSPKTREQHOWDY;
+
+/**
+ * The HOWDY reply structure.
+ */
+typedef struct UTSPKTREPHOWDY
+{
+ /** Status packet. */
+ UTSPKTSTS Sts;
+ /** Version to use for the established connection. */
+ uint32_t uVersion;
+ /** Mask of supported USB device connections for this connection. */
+ uint32_t fUsbConn;
+ /** Port number the USB/IP server is listening on if
+ * the client requested USB/IP support and the server can
+ * deliver it. */
+ uint32_t uUsbIpPort;
+ /** Maximum number of devices supported over USB/IP
+ * at the same time. */
+ uint32_t cUsbIpDevices;
+ /** Maximum number of physical devices supported for this client
+ * if a physical connection is present. */
+ uint32_t cPhysicalDevices;
+ /** Padding - reserved. */
+ uint8_t au8Padding[12];
+} UTSPKTREPHOWDY;
+AssertCompileSizeAlignment(UTSPKTREPHOWDY, UTSPKT_ALIGNMENT);
+/** Pointer to a HOWDY reply structure. */
+typedef UTSPKTREPHOWDY *PUTSPKTREPHOWDY;
+
+/** Connections over USB/IP are supported. */
+#define UTSPKT_HOWDY_CONN_F_USBIP RT_BIT_32(0)
+/** The server has a physical connection available to the client
+ * which can be used for testing. */
+#define UTSPKT_HOWDY_CONN_F_PHYSICAL RT_BIT_32(1)
+
+
+#define UTSPKT_OPCODE_BYE "BYE "
+
+/* No additional structures for BYE. */
+
+#define UTSPKT_OPCODE_GADGET_CREATE "GDGTCRT "
+
+/**
+ * The GADGET CREATE request structure.
+ */
+typedef struct UTSPKTREQGDGTCTOR
+{
+ /** Embedded packet header. */
+ UTSPKTHDR Hdr;
+ /** Gadget type. */
+ uint32_t u32GdgtType;
+ /** Access methods. */
+ uint32_t u32GdgtAccess;
+ /** Number of config items - following this structure. */
+ uint32_t u32CfgItems;
+ /** Reserved. */
+ uint32_t u32Rsvd0;
+} UTSPKTREQGDGTCTOR;
+AssertCompileSizeAlignment(UTSPKTREQGDGTCTOR, UTSPKT_ALIGNMENT);
+/** Pointer to a GADGET CREATE structure. */
+typedef UTSPKTREQGDGTCTOR *PUTSPKTREQGDGTCTOR;
+
+/** Gadget type - Test device. */
+#define UTSPKT_GDGT_CREATE_TYPE_TEST UINT32_C(0x1)
+
+/** Gadget acess method - USB/IP. */
+#define UTSPKT_GDGT_CREATE_ACCESS_USBIP UINT32_C(0x1)
+
+/**
+ * Configuration item.
+ */
+typedef struct UTSPKTREQGDGTCTORCFGITEM
+{
+ /** Size of the key incuding termination in bytes. */
+ uint32_t u32KeySize;
+ /** Item type. */
+ uint32_t u32Type;
+ /** Size of the value string including termination in bytes. */
+ uint32_t u32ValSize;
+ /** Reserved. */
+ uint32_t u32Rsvd0;
+} UTSPKTREQGDGTCTORCFGITEM;
+AssertCompileSizeAlignment(UTSPKTREQGDGTCTORCFGITEM, UTSPKT_ALIGNMENT);
+/** Pointer to a configuration item. */
+typedef UTSPKTREQGDGTCTORCFGITEM *PUTSPKTREQGDGTCTORCFGITEM;
+
+/** Boolean configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN UINT32_C(1)
+/** String configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_STRING UINT32_C(2)
+/** Unsigned 8-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8 UINT32_C(3)
+/** Unsigned 16-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16 UINT32_C(4)
+/** Unsigned 32-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32 UINT32_C(5)
+/** Unsigned 64-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64 UINT32_C(6)
+/** Signed 8-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT8 UINT32_C(7)
+/** Signed 16-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT16 UINT32_C(8)
+/** Signed 32-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT32 UINT32_C(9)
+/** Signed 64-bit integer configuration item type. */
+#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT64 UINT32_C(10)
+
+/**
+ * The GADGET CREATE reply structure.
+ */
+typedef struct UTSPKTREPGDGTCTOR
+{
+ /** Status packet. */
+ UTSPKTSTS Sts;
+ /** The gadget ID on success. */
+ uint32_t idGadget;
+ /** Bus ID the gadget is attached to */
+ uint32_t u32BusId;
+ /** Device ID of the gadget on the bus. */
+ uint32_t u32DevId;
+ /** Padding - reserved. */
+ uint8_t au8Padding[4];
+} UTSPKTREPGDGTCTOR;
+AssertCompileSizeAlignment(UTSPKTREPGDGTCTOR, UTSPKT_ALIGNMENT);
+/** Pointer to a GADGET CREATE structure. */
+typedef UTSPKTREPGDGTCTOR *PUTSPKTREPGDGTCTOR;
+
+
+#define UTSPKT_OPCODE_GADGET_DESTROY "GDGTDTOR"
+
+/**
+ * The GADGET DESTROY request structure.
+ */
+typedef struct UTSPKTREQGDGTDTOR
+{
+ /** Embedded packet header. */
+ UTSPKTHDR Hdr;
+ /** Gadget ID as returned from the GADGET CREATE request on success. */
+ uint32_t idGadget;
+ /** Padding - reserved. */
+ uint8_t au8Padding[12];
+} UTSPKTREQGDGTDTOR;
+AssertCompileSizeAlignment(UTSPKTREQGDGTDTOR, UTSPKT_ALIGNMENT);
+/** Pointer to a GADGET DESTROY structure. */
+typedef UTSPKTREQGDGTDTOR *PUTSPKTREQGDGTDTOR;
+
+/* No additional structure for the reply (just standard STATUS packet). */
+
+#define UTSPKT_OPCODE_GADGET_CONNECT "GDGTCNCT"
+
+/**
+ * The GADGET CONNECT request structure.
+ */
+typedef struct UTSPKTREQGDGTCNCT
+{
+ /** Embedded packet header. */
+ UTSPKTHDR Hdr;
+ /** Gadget ID as returned from the GADGET CREATE request on success. */
+ uint32_t idGadget;
+ /** Padding - reserved. */
+ uint8_t au8Padding[12];
+} UTSPKTREQGDGTCNCT;
+AssertCompileSizeAlignment(UTSPKTREQGDGTCNCT, UTSPKT_ALIGNMENT);
+/** Pointer to a GADGET CONNECT request structure. */
+typedef UTSPKTREQGDGTCNCT *PUTSPKTREQGDGTCNCT;
+
+/* No additional structure for the reply (just standard STATUS packet). */
+
+#define UTSPKT_OPCODE_GADGET_DISCONNECT "GDGTDCNT"
+
+/**
+ * The GADGET DISCONNECT request structure.
+ */
+typedef struct UTSPKTREQGDGTDCNT
+{
+ /** Embedded packet header. */
+ UTSPKTHDR Hdr;
+ /** Gadget ID as returned from the GADGET CREATE request on success. */
+ uint32_t idGadget;
+ /** Padding - reserved. */
+ uint8_t au8Padding[12];
+} UTSPKTREQGDGTDCNT;
+AssertCompileSizeAlignment(UTSPKTREQGDGTDCNT, UTSPKT_ALIGNMENT);
+/** Pointer to a GADGET CONNECT request structure. */
+typedef UTSPKTREQGDGTDCNT *PUTSPKTREQGDGTDCNT;
+
+/* No additional structure for the reply (just standard STATUS packet). */
+
+/**
+ * Checks if the two opcodes match.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pPktHdr The packet header.
+ * @param pszOpcode2 The opcode we're comparing with. Does not have
+ * to be the whole 8 chars long.
+ */
+DECLINLINE(bool) utsIsSameOpcode(PCUTSPKTHDR pPktHdr, const char *pszOpcode2)
+{
+ if (pPktHdr->achOpcode[0] != pszOpcode2[0])
+ return false;
+ if (pPktHdr->achOpcode[1] != pszOpcode2[1])
+ return false;
+
+ unsigned i = 2;
+ while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode)
+ && pszOpcode2[i] != '\0')
+ {
+ if (pPktHdr->achOpcode[i] != pszOpcode2[i])
+ break;
+ i++;
+ }
+
+ if ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode)
+ && pszOpcode2[i] == '\0')
+ {
+ while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode)
+ && pPktHdr->achOpcode[i] == ' ')
+ i++;
+ }
+
+ return i == RT_SIZEOFMEMB(UTSPKTHDR, achOpcode);
+}
+
+/**
+ * Converts a UTS request packet from host to network byte ordering.
+ *
+ * @param pPktHdr The packet to convert.
+ */
+DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr);
+
+/**
+ * Converts a UTS request packet from network to host byte ordering.
+ *
+ * @param pPktHdr The packet to convert.
+ */
+DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr);
+
+/**
+ * Converts a UTS reply packet from host to network byte ordering.
+ *
+ * @param pPktHdr The packet to convert.
+ */
+DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTHDR pPktHdr);
+
+/**
+ * Converts a UTS reply packet from network to host byte ordering.
+ *
+ * @param pPktHdr The packet to convert.
+ */
+DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTHDR pPktHdr);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h */
diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp
new file mode 100644
index 00000000..4d9013f7
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp
@@ -0,0 +1,512 @@
+/* $Id: UsbTestServiceTcp.cpp $ */
+/** @file
+ * UsbTestService - Remote USB test configuration and execution server, TCP/IP Transport Layer.
+ */
+
+/*
+ * Copyright (C) 2010-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>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include "UsbTestServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The default server port. */
+#define UTS_TCP_DEF_BIND_PORT 6042
+/** The default server bind address. */
+#define UTS_TCP_DEF_BIND_ADDRESS ""
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * TCP specific client data.
+ */
+typedef struct UTSTRANSPORTCLIENT
+{
+ /** Socket of the current client. */
+ RTSOCKET hTcpClient;
+ /** The size of the stashed data. */
+ size_t cbTcpStashed;
+ /** The size of the stashed data allocation. */
+ size_t cbTcpStashedAlloced;
+ /** The stashed data. */
+ uint8_t *pbTcpStashed;
+} UTSTRANSPORTCLIENT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** @name TCP Parameters
+ * @{ */
+/** The addresses to bind to. Empty string means any. */
+static char g_szTcpBindAddr[256] = UTS_TCP_DEF_BIND_ADDRESS;
+/** The TCP port to listen to. */
+static uint32_t g_uTcpBindPort = UTS_TCP_DEF_BIND_PORT;
+/** @} */
+
+/** Pointer to the TCP server instance. */
+static PRTTCPSERVER g_pTcpServer = NULL;
+#if 0 /* unused */
+/** Stop connecting attempts when set. */
+static bool g_fTcpStopConnecting = false;
+#endif
+
+
+
+/**
+ * Disconnects the current client and frees all stashed data.
+ */
+static void utsTcpDisconnectClient(PUTSTRANSPORTCLIENT pClient)
+{
+ if (pClient->hTcpClient != NIL_RTSOCKET)
+ {
+ int rc = RTTcpServerDisconnectClient2(pClient->hTcpClient);
+ pClient->hTcpClient = NIL_RTSOCKET;
+ AssertRCSuccess(rc);
+ }
+
+ if (pClient->pbTcpStashed)
+ {
+ RTMemFree(pClient->pbTcpStashed);
+ pClient->pbTcpStashed = NULL;
+ }
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnWaitForConnect}
+ */
+static DECLCALLBACK(int) utsTcpWaitForConnect(PPUTSTRANSPORTCLIENT ppClientNew)
+{
+ int rc;
+ RTSOCKET hClientNew;
+
+ rc = RTTcpServerListen2(g_pTcpServer, &hClientNew);
+ Log(("utsTcpWaitForConnect: RTTcpServerListen2 -> %Rrc\n", rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ PUTSTRANSPORTCLIENT pClient = (PUTSTRANSPORTCLIENT)RTMemAllocZ(sizeof(UTSTRANSPORTCLIENT));
+ if (RT_LIKELY(pClient))
+ {
+ pClient->hTcpClient = hClientNew;
+ pClient->cbTcpStashed = 0;
+ pClient->cbTcpStashedAlloced = 0;
+ pClient->pbTcpStashed = NULL;
+ *ppClientNew = pClient;
+ }
+ else
+ {
+ RTTcpServerDisconnectClient2(hClientNew);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnNotifyReboot}
+ */
+static DECLCALLBACK(void) utsTcpNotifyReboot(void)
+{
+ Log(("utsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer));
+ if (g_pTcpServer)
+ {
+ int rc = RTTcpServerDestroy(g_pTcpServer);
+ if (RT_FAILURE(rc))
+ RTMsgInfo("RTTcpServerDestroy failed in utsTcpNotifyReboot: %Rrc", rc);
+ g_pTcpServer = NULL;
+ }
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnNotifyBye}
+ */
+static DECLCALLBACK(void) utsTcpNotifyBye(PUTSTRANSPORTCLIENT pClient)
+{
+ Log(("utsTcpNotifyBye: utsTcpDisconnectClient %RTsock\n", pClient->hTcpClient));
+ utsTcpDisconnectClient(pClient);
+ RTMemFree(pClient);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnNotifyHowdy}
+ */
+static DECLCALLBACK(void) utsTcpNotifyHowdy(PUTSTRANSPORTCLIENT pClient)
+{
+ /* nothing to do here */
+ RT_NOREF1(pClient);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnBabble}
+ */
+static DECLCALLBACK(void) utsTcpBabble(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)
+{
+ /*
+ * Try send the babble reply.
+ */
+ NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */
+ int rc;
+ size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT);
+ do rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend);
+ while (rc == VERR_INTERRUPTED);
+
+ /*
+ * Disconnect the client.
+ */
+ Log(("utsTcpBabble: utsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", pClient->hTcpClient, rc));
+ utsTcpDisconnectClient(pClient);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnSendPkt}
+ */
+static DECLCALLBACK(int) utsTcpSendPkt(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr)
+{
+ Assert(pPktHdr->cb >= sizeof(UTSPKTHDR));
+
+ /*
+ * Write it.
+ */
+ size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT);
+ int rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_INTERRUPTED)
+ {
+ /* assume fatal connection error. */
+ Log(("RTTcpWrite -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient));
+ utsTcpDisconnectClient(pClient);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnRecvPkt}
+ */
+static DECLCALLBACK(int) utsTcpRecvPkt(PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr)
+{
+ int rc = VINF_SUCCESS;
+ *ppPktHdr = NULL;
+
+ /*
+ * Read state.
+ */
+ size_t offData = 0;
+ size_t cbData = 0;
+ size_t cbDataAlloced;
+ uint8_t *pbData = NULL;
+
+ /*
+ * Any stashed data?
+ */
+ if (pClient->cbTcpStashedAlloced)
+ {
+ offData = pClient->cbTcpStashed;
+ cbDataAlloced = pClient->cbTcpStashedAlloced;
+ pbData = pClient->pbTcpStashed;
+
+ pClient->cbTcpStashed = 0;
+ pClient->cbTcpStashedAlloced = 0;
+ pClient->pbTcpStashed = NULL;
+ }
+ else
+ {
+ cbDataAlloced = RT_ALIGN_Z(64, UTSPKT_ALIGNMENT);
+ pbData = (uint8_t *)RTMemAlloc(cbDataAlloced);
+ if (!pbData)
+ return VERR_NO_MEMORY;
+ }
+
+ /*
+ * Read and valid the length.
+ */
+ while (offData < sizeof(uint32_t))
+ {
+ size_t cbRead;
+ rc = RTTcpRead(pClient->hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ if (cbRead == 0)
+ {
+ Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc));
+ rc = VERR_NET_NOT_CONNECTED;
+ break;
+ }
+ offData += cbRead;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ ASMCompilerBarrier(); /* paranoia^3 */
+ cbData = *(uint32_t volatile *)pbData;
+ if (cbData >= sizeof(UTSPKTHDR) && cbData <= UTSPKT_MAX_SIZE)
+ {
+ /*
+ * Align the length and reallocate the return packet it necessary.
+ */
+ cbData = RT_ALIGN_Z(cbData, UTSPKT_ALIGNMENT);
+ if (cbData > cbDataAlloced)
+ {
+ void *pvNew = RTMemRealloc(pbData, cbData);
+ if (pvNew)
+ {
+ pbData = (uint8_t *)pvNew;
+ cbDataAlloced = cbData;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read the remainder of the data.
+ */
+ while (offData < cbData)
+ {
+ size_t cbRead;
+ rc = RTTcpRead(pClient->hTcpClient, pbData + offData, cbData - offData, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ if (cbRead == 0)
+ {
+ Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc));
+ rc = VERR_NET_NOT_CONNECTED;
+ break;
+ }
+ offData += cbRead;
+ }
+ }
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ if (RT_SUCCESS(rc))
+ *ppPktHdr = (PUTSPKTHDR)pbData;
+ else
+ {
+ /*
+ * Deal with errors.
+ */
+ if (rc == VERR_INTERRUPTED)
+ {
+ /* stash it away for the next call. */
+ pClient->cbTcpStashed = cbData;
+ pClient->cbTcpStashedAlloced = cbDataAlloced;
+ pClient->pbTcpStashed = pbData;
+ }
+ else
+ {
+ RTMemFree(pbData);
+
+ /* assume fatal connection error. */
+ Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient));
+ utsTcpDisconnectClient(pClient);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnPollSetAdd}
+ */
+static DECLCALLBACK(int) utsTcpPollSetAdd(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)
+{
+ return RTPollSetAddSocket(hPollSet, pClient->hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnPollSetRemove}
+ */
+static DECLCALLBACK(int) utsTcpPollSetRemove(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)
+{
+ RT_NOREF1(pClient);
+ return RTPollSetRemove(hPollSet, idStart);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnPollIn}
+ */
+static DECLCALLBACK(bool) utsTcpPollIn(PUTSTRANSPORTCLIENT pClient)
+{
+ int rc = RTTcpSelectOne(pClient->hTcpClient, 0/*cMillies*/);
+ return RT_SUCCESS(rc);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnTerm}
+ */
+static DECLCALLBACK(void) utsTcpTerm(void)
+{
+ /* Shut down the server (will wake up thread). */
+ if (g_pTcpServer)
+ {
+ Log(("utsTcpTerm: Destroying server...\n"));
+ int rc = RTTcpServerDestroy(g_pTcpServer);
+ if (RT_FAILURE(rc))
+ RTMsgInfo("RTTcpServerDestroy failed in utsTcpTerm: %Rrc", rc);
+ g_pTcpServer = NULL;
+ }
+
+ Log(("utsTcpTerm: done\n"));
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnInit}
+ */
+static DECLCALLBACK(int) utsTcpInit(void)
+{
+ int rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NET_DOWN)
+ {
+ RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n",
+ g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc);
+ uint64_t StartMs = RTTimeMilliTS();
+ do
+ {
+ RTThreadSleep(1000);
+ rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer);
+ } while ( rc == VERR_NET_DOWN
+ && RTTimeMilliTS() - StartMs < 20000);
+ if (RT_SUCCESS(rc))
+ RTMsgInfo("RTTcpServerCreateEx succceeded.\n");
+ }
+ if (RT_FAILURE(rc))
+ {
+ g_pTcpServer = NULL;
+ RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n",
+ g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc);
+ }
+ }
+
+ return rc;
+}
+
+/** Options */
+enum UTSTCPOPT
+{
+ UTSTCPOPT_BIND_ADDRESS = 1000,
+ UTSTCPOPT_BIND_PORT
+};
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnOption}
+ */
+static DECLCALLBACK(int) utsTcpOption(int ch, PCRTGETOPTUNION pVal)
+{
+ int rc;
+
+ switch (ch)
+ {
+ case UTSTCPOPT_BIND_ADDRESS:
+ rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc);
+ return VINF_SUCCESS;
+
+ case UTSTCPOPT_BIND_PORT:
+ g_uTcpBindPort = pVal->u16 == 0 ? UTS_TCP_DEF_BIND_PORT : pVal->u16;
+ return VINF_SUCCESS;
+ }
+ return VERR_TRY_AGAIN;
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnUsage}
+ */
+DECLCALLBACK(void) utsTcpUsage(PRTSTREAM pStream)
+{
+ RTStrmPrintf(pStream,
+ " --tcp-bind-address <address>\n"
+ " The address(es) to listen to TCP connection on. Empty string\n"
+ " means any address, this is the default.\n"
+ " --tcp-bind-port <port>\n"
+ " The port to listen to TCP connections on.\n"
+ " Default: %u\n"
+ , UTS_TCP_DEF_BIND_PORT);
+}
+
+/** Command line options for the TCP/IP transport layer. */
+static const RTGETOPTDEF g_TcpOpts[] =
+{
+ { "--tcp-bind-address", UTSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING },
+ { "--tcp-bind-port", UTSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 }
+};
+
+/** TCP/IP transport layer. */
+const UTSTRANSPORT g_TcpTransport =
+{
+ /* .szName = */ "tcp",
+ /* .pszDesc = */ "TCP/IP",
+ /* .cOpts = */ &g_TcpOpts[0],
+ /* .paOpts = */ RT_ELEMENTS(g_TcpOpts),
+ /* .pfnUsage = */ utsTcpUsage,
+ /* .pfnOption = */ utsTcpOption,
+ /* .pfnInit = */ utsTcpInit,
+ /* .pfnTerm = */ utsTcpTerm,
+ /* .pfnWaitForConnect = */ utsTcpWaitForConnect,
+ /* .pfnPollIn = */ utsTcpPollIn,
+ /* .pfnPollSetAdd = */ utsTcpPollSetAdd,
+ /* .pfnPollSetRemove = */ utsTcpPollSetRemove,
+ /* .pfnRecvPkt = */ utsTcpRecvPkt,
+ /* .pfnSendPkt = */ utsTcpSendPkt,
+ /* .pfnBabble = */ utsTcpBabble,
+ /* .pfnNotifyHowdy = */ utsTcpNotifyHowdy,
+ /* .pfnNotifyBye = */ utsTcpNotifyBye,
+ /* .pfnNotifyReboot = */ utsTcpNotifyReboot,
+ /* .u32EndMarker = */ UINT32_C(0x12345678)
+};