Index: trunk/src/VBox/ValidationKit/utils/usb/Makefile.kmk
===================================================================
--- trunk/src/VBox/ValidationKit/utils/usb/Makefile.kmk	(revision 58930)
+++ trunk/src/VBox/ValidationKit/utils/usb/Makefile.kmk	(revision 60279)
@@ -31,7 +31,18 @@
 # USB Linux test frontend.
 #
-PROGRAMS += UsbTest
-UsbTest_TEMPLATE = VBoxValidationKitR3
-UsbTest_SOURCES  = UsbTest.cpp
+ifeq ($(KBUILD_TARGET),linux)
+ PROGRAMS += UsbTest
+ UsbTest_TEMPLATE = VBoxValidationKitR3
+ UsbTest_SOURCES  = UsbTest.cpp
+endif
+
+PROGRAMS += UsbTestService
+UsbTestService_TEMPLATE = VBoxValidationKitR3
+UsbTestService_DEFS = \
+	KBUILD_TARGET=\"$(KBUILD_TARGET)\" \
+	KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\"
+UsbTestService_SOURCES = \
+	UsbTestService.cpp \
+	UsbTestServiceTcp.cpp
 
 $(evalcall def_vbox_validationkit_process_python_sources)
Index: trunk/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
===================================================================
--- trunk/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp	(revision 60279)
+++ trunk/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp	(revision 60279)
@@ -0,0 +1,1077 @@
+/* $Id$ */
+/** @file
+ * UsbTestService - Remote USB test configuration and execution server.
+ */
+
+/*
+ * Copyright (C) 2010-2016 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#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/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"
+
+
+
+/*********************************************************************************************************************************
+*   Structures and Typedefs                                                                                                      *
+*********************************************************************************************************************************/
+
+/**
+ * UTS client instance.
+ */
+typedef struct UTSCLIENT
+{
+    /** List node for new clients. */
+    RTLISTNODE             NdLst;
+    /** Transport backend specific data. */
+    PUTSTRANSPORTCLIENT    pTransportClient;
+} 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 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;
+/** 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;
+
+
+/**
+ * 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_OFFSETOF(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_OFFSETOF(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   pszDetailsFmt       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   rcReq               Status code.
+ * @param   pszOpcode           The status opcode.  Exactly 8 chars long, padd
+ *                              with space.
+ * @param   pszDetails          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')",
+                           rcOperation, szOperation, rcOperation, pPktHdr->achOpcode);
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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);
+}
+
+/**
+ * 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));
+    g_pTransport->pfnNotifyBye(pClient->pTransportClient);
+    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)
+{
+    if (pPktHdr->cb != sizeof(UTSPKTHDR))
+        return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTHDR));
+    int rc = utsReplyAck(pClient, pPktHdr);
+    if (RT_SUCCESS(rc))
+    {
+        g_pTransport->pfnNotifyHowdy(pClient->pTransportClient);
+        RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY);
+    }
+    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;
+    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);
+    /* Misc: */
+    else
+        rc = utsReplyUnknown(pClient, pPktHdr);
+
+    RTMemFree(pPktHdr);
+
+    return rc;
+}
+
+/**
+ * Destroys a client instance.
+ *
+ * @returns nothing.
+ * @param   pClient             The UTS client structure.
+ */
+static void utsClientDestroy(PUTSCLIENT pClient)
+{
+    RTMemFree(pClient);
+}
+
+/**
+ * The main thread worker serving the clients.
+ */
+static DECLCALLBACK(int) utsClientWorker(RTTHREAD hThread, void *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;
+                    rc = RTPipeRead(g_hPipeR, &bRead, 1, NULL);
+                    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 *)RTMemRealloc(papClients, (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];
+                    if (fEvts & RTPOLL_EVT_READ)
+                        rc = utsClientReqProcess(pClient);
+
+                    if (   (fEvts & RTPOLL_EVT_ERROR)
+                        || RT_FAILURE(rc))
+                    {
+                        /* Close connection and remove client from array. */
+                        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(PUTSCLIENT));
+        if (RT_LIKELY(pClient))
+        {
+            pClient->pTransportClient = pTransportClient;
+        }
+        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;
+
+    RTListInit(&g_LstClientsNew);
+
+    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);
+
+    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);
+
+    /*
+     * 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 *argv0)
+{
+    RTStrmPrintf(pStrm,
+                 "Usage: %Rbn [options]\n"
+                 "\n"
+                 "Options:\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"
+                 ,
+                 argv0,
+                 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[] =
+    {
+        { "--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_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; 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$\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();
+
+    return rcExit;
+}
+
Index: trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h
===================================================================
--- trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h	(revision 60279)
+++ trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h	(revision 60279)
@@ -0,0 +1,205 @@
+/* $Id$ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2016 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+#ifndef ___UsbTestServiceInternal_h___
+#define ___UsbTestServiceInternal_h___
+
+#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   pThis               Pointer to the transport layer descriptor.
+     * @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   pThis               Pointer to the transport layer descriptor.
+     * @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.
+     * @paramm  pClient             The transport client structure. 
+     * @param   idStart             The handle ID to start at.
+     */
+    DECLR3CALLBACKMEMBER(int, pfnPollSetAdd, (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.
+     *
+     * @paramm  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.
+     *
+     * @paramm  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).
+     *
+     * @paramm  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.
+     *
+     * @paramm  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.
+     *
+     * @paramm  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
+
Index: trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h
===================================================================
--- trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h	(revision 60279)
+++ trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h	(revision 60279)
@@ -0,0 +1,193 @@
+/* $Id$ */
+/** @file
+ * UsbTestServ - Remote USB test configuration and execution server, Protocol Header.
+ */
+
+/*
+ * Copyright (C) 2016 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+#ifndef ___UsbTestServiceProtocol_h___
+#define ___UsbTestServiceProtocol_h___
+
+#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   "
+
+/**
+ * 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. */
+    uint8_t         achHostname[68];
+} UTSPKTREQHOWDY;
+AssertCompileSizeAlignment(UTSPKTREQHOWDY, UTSPKT_ALIGNMENT);
+
+/**
+ * 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);
+
+/** 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 "
+#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);
+
+/* 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);
+
+/* No additional structure for the reply (just standard STATUS packet). */
+
+#define UTSPKT_OPCODE_GADGET_DISCONNECT "GDGTDCNT"
+
+/**
+ * The GADGET CONNECT 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);
+
+/* No additional structure for the reply (just standard STATUS packet). */
+
+RT_C_DECLS_END
+
+#endif
Index: trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp
===================================================================
--- trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp	(revision 60279)
+++ trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp	(revision 60279)
@@ -0,0 +1,480 @@
+/* $Id$ */
+/** @file
+ * UsbTestService - Remote USB test configuration and execution server, TCP/IP Transport Layer.
+ */
+
+/*
+ * Copyright (C) 2010-2016 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#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;
+/** Stop connecting attempts when set. */
+static bool                 g_fTcpStopConnecting    = false;
+
+
+
+/**
+ * Disconnects the current client and frees its data structure.
+ */
+static void utsTcpDisconnectClient(PUTSTRANSPORTCLIENT pClient)
+{
+    int rc = RTTcpServerDisconnectClient2(pClient->hTcpClient);
+    AssertRCSuccess(rc);
+    if (pClient->pbTcpStashed)
+        RTMemFree(pClient->pbTcpStashed);
+    RTMemFree(pClient);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnWaitForConnect}
+ */
+static 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);
+}
+
+/**
+ * @interface_method_impl{UTSTRANSPORT,pfnNotifyHowdy}
+ */
+static DECLCALLBACK(void) utsTcpNotifyHowdy(PUTSTRANSPORTCLIENT pClient)
+{
+    /* nothing to do here */
+}
+
+/**
+ * @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,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,
+    /* .pfnRecvPkt        = */ utsTcpRecvPkt,
+    /* .pfnSendPkt        = */ utsTcpSendPkt,
+    /* .pfnBabble         = */ utsTcpBabble,
+    /* .pfnNotifyHowdy    = */ utsTcpNotifyHowdy,
+    /* .pfnNotifyBye      = */ utsTcpNotifyBye,
+    /* .pfnNotifyReboot   = */ utsTcpNotifyReboot,
+    /* .u32EndMarker      = */ UINT32_C(0x12345678)
+};
