/* $Id: localipc-win.cpp $ */
/** @file
 * IPRT - Local IPC, Windows Implementation Using Named Pipes.
 */

/*
 * Copyright (C) 2008-2020 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_LOCALIPC
/*
 * We have to force NT 5.0 here because of
 * ConvertStringSecurityDescriptorToSecurityDescriptor. Note that because of
 * FILE_FLAG_FIRST_PIPE_INSTANCE this code actually requires W2K SP2+.
 */
#ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0500 /* for ConvertStringSecurityDescriptorToSecurityDescriptor */
#elif _WIN32_WINNT < 0x0500
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x0500
#endif
#define UNICODE    /* For the SDDL_ strings. */
#include <iprt/win/windows.h>
#include <sddl.h>

#include "internal/iprt.h"
#include <iprt/localipc.h>

#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/critsect.h>
#include <iprt/ctype.h>
#include <iprt/err.h>
#include <iprt/ldr.h>
#include <iprt/log.h>
#include <iprt/mem.h>
#include <iprt/param.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#include <iprt/time.h>
#include <iprt/utf16.h>

#include "internal/magics.h"
#include "internal-r3-win.h"



/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** Pipe prefix string. */
#define RTLOCALIPC_WIN_PREFIX   L"\\\\.\\pipe\\IPRT-"

/** DACL for block all network access and local users other than the creator/owner.
 *
 * ACE format: (ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid)
 *
 * Note! FILE_GENERIC_WRITE (SDDL_FILE_WRITE) is evil here because it includes
 *       the FILE_CREATE_PIPE_INSTANCE(=FILE_APPEND_DATA) flag. Thus the hardcoded
 *       value 0x0012019b in the client ACE. The server-side still needs
 *       setting FILE_CREATE_PIPE_INSTANCE although.
 *       It expands to:
 *          0x00000001 - FILE_READ_DATA
 *          0x00000008 - FILE_READ_EA
 *          0x00000080 - FILE_READ_ATTRIBUTES
 *          0x00020000 - READ_CONTROL
 *          0x00100000 - SYNCHRONIZE
 *          0x00000002 - FILE_WRITE_DATA
 *          0x00000010 - FILE_WRITE_EA
 *          0x00000100 - FILE_WRITE_ATTRIBUTES
 *       =  0x0012019b (client)
 *       + (only for server):
 *          0x00000004 - FILE_CREATE_PIPE_INSTANCE
 *       =  0x0012019f
 *
 * @todo Triple check this!
 * @todo EVERYONE -> AUTHENTICATED USERS or something more appropriate?
 * @todo Have trouble allowing the owner FILE_CREATE_PIPE_INSTANCE access, so for now I'm hacking
 *       it just to get progress - the service runs as local system.
 *       The CREATOR OWNER and PERSONAL SELF works (the former is only involved in inheriting
 *       it seems, which is why it won't work. The latter I've no idea about. Perhaps the solution
 *       is to go the annoying route of OpenProcessToken, QueryTokenInformation,
 *          ConvertSidToStringSid and then use the result... Suggestions are very welcome
 */
#define RTLOCALIPC_WIN_SDDL_BASE \
        SDDL_DACL SDDL_DELIMINATOR \
        SDDL_ACE_BEGIN SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK SDDL_ACE_END \
        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL   L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END

#define RTLOCALIPC_WIN_SDDL_SERVER \
        RTLOCALIPC_WIN_SDDL_BASE \
        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019f"   L";;;" SDDL_EVERYONE SDDL_ACE_END

#define RTLOCALIPC_WIN_SDDL_CLIENT \
        RTLOCALIPC_WIN_SDDL_BASE \
        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b"   L";;;" SDDL_EVERYONE SDDL_ACE_END

//        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_PERSONAL_SELF SDDL_ACE_END \
//        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";CIOI;" SDDL_GENERIC_ALL L";;;" SDDL_CREATOR_OWNER SDDL_ACE_END
//        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b"    L";;;" SDDL_EVERYONE SDDL_ACE_END
//        SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * Local IPC service instance, Windows.
 */
typedef struct RTLOCALIPCSERVERINT
{
    /** The magic (RTLOCALIPCSERVER_MAGIC). */
    uint32_t u32Magic;
    /** The creation flags. */
    uint32_t fFlags;
    /** Critical section protecting the structure. */
    RTCRITSECT CritSect;
    /** The number of references to the instance.
     * @remarks The reference counting isn't race proof. */
    uint32_t volatile cRefs;
    /** Indicates that there is a pending cancel request. */
    bool volatile fCancelled;
    /** The named pipe handle. */
    HANDLE hNmPipe;
    /** The handle to the event object we're using for overlapped I/O. */
    HANDLE hEvent;
    /** The overlapped I/O structure. */
    OVERLAPPED OverlappedIO;
    /** The full pipe name (variable length). */
    RTUTF16 wszName[1];
} RTLOCALIPCSERVERINT;
/** Pointer to a local IPC server instance (Windows). */
typedef RTLOCALIPCSERVERINT *PRTLOCALIPCSERVERINT;


/**
 * Local IPC session instance, Windows.
 *
 * This is a named pipe and we should probably merge the pipe code with this to
 * save work and code duplication.
 */
typedef struct RTLOCALIPCSESSIONINT
{
    /** The magic (RTLOCALIPCSESSION_MAGIC). */
    uint32_t            u32Magic;
    /** Critical section protecting the structure. */
    RTCRITSECT          CritSect;
    /** The number of references to the instance.
     * @remarks The reference counting isn't race proof. */
    uint32_t volatile   cRefs;
    /** Set if the zero byte read that the poll code using is pending. */
    bool                fZeroByteRead;
    /** Indicates that there is a pending cancel request. */
    bool volatile       fCancelled;
    /** Set if this is the server side, clear if the client. */
    bool                fServerSide;
    /** The named pipe handle. */
    HANDLE              hNmPipe;
    struct
    {
        RTTHREAD        hActiveThread;
        /** The handle to the event object we're using for overlapped I/O. */
        HANDLE          hEvent;
        /** The overlapped I/O structure. */
        OVERLAPPED      OverlappedIO;
    }
    /** Overlapped reads. */
                        Read,
    /** Overlapped writes. */
                        Write;
#if 0 /* Non-blocking writes are not yet supported. */
    /** Bounce buffer for writes. */
    uint8_t            *pbBounceBuf;
    /** Amount of used buffer space. */
    size_t              cbBounceBufUsed;
    /** Amount of allocated buffer space. */
    size_t              cbBounceBufAlloc;
#endif
    /** Buffer for the zero byte read.
     * Used in RTLocalIpcSessionWaitForData(). */
    uint8_t             abBuf[8];
} RTLOCALIPCSESSIONINT;
/** Pointer to a local IPC session instance (Windows). */
typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT;


/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession);


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
static bool volatile g_fResolvedApis = false;
/** advapi32.dll API ConvertStringSecurityDescriptorToSecurityDescriptorW. */
static decltype(ConvertStringSecurityDescriptorToSecurityDescriptorW) *g_pfnSSDLToSecDescW = NULL;


/**
 * Builds and allocates the security descriptor required for securing the local pipe.
 *
 * @return  IPRT status code.
 * @param   ppDesc              Where to store the allocated security descriptor on success.
 *                              Must be free'd using LocalFree().
 * @param   fServer             Whether it's for a server or client instance.
 */
static int rtLocalIpcServerWinAllocSecurityDescriptior(PSECURITY_DESCRIPTOR *ppDesc, bool fServer)
{
    /*
     * Resolve the API the first time around.
     */
    if (!g_fResolvedApis)
    {
        g_pfnSSDLToSecDescW = (decltype(g_pfnSSDLToSecDescW))RTLdrGetSystemSymbol("advapi32.dll", "ConvertStringSecurityDescriptorToSecurityDescriptorW");
        ASMCompilerBarrier();
        g_fResolvedApis = true;
    }

    int rc;
    PSECURITY_DESCRIPTOR pSecDesc = NULL;
    if (g_pfnSSDLToSecDescW)
    {
        /*
         * We'll create a security descriptor from a SDDL that denies
         * access to network clients (this is local IPC after all), it
         * makes some further restrictions to prevent non-authenticated
         * users from screwing around.
         */
        PCRTUTF16 pwszSDDL = fServer ? RTLOCALIPC_WIN_SDDL_SERVER : RTLOCALIPC_WIN_SDDL_CLIENT;
        if (g_pfnSSDLToSecDescW(pwszSDDL, SDDL_REVISION_1, &pSecDesc, NULL))
        {
            AssertPtr(pSecDesc);
            *ppDesc = pSecDesc;
            return VINF_SUCCESS;
        }

        rc = RTErrConvertFromWin32(GetLastError());
    }
    else
    {
        /* Windows OSes < W2K SP2 not supported for now, bail out. */
        /** @todo Implement me! */
        rc = VERR_NOT_SUPPORTED;
    }
    return rc;
}


/**
 * Creates a named pipe instance.
 *
 * This is used by both RTLocalIpcServerCreate and RTLocalIpcServerListen.
 *
 * @return  IPRT status code.
 * @param   phNmPipe        Where to store the named pipe handle on success.
 *                          This will be set to INVALID_HANDLE_VALUE on failure.
 * @param   pwszPipeName    The named pipe name, full, UTF-16 encoded.
 * @param   fFirst          Set on the first call (from RTLocalIpcServerCreate),
 *                          otherwise clear. Governs the
 *                          FILE_FLAG_FIRST_PIPE_INSTANCE flag.
 */
static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, bool fFirst)
{
    *phNmPipe = INVALID_HANDLE_VALUE;

    PSECURITY_DESCRIPTOR pSecDesc;
    int rc = rtLocalIpcServerWinAllocSecurityDescriptior(&pSecDesc, fFirst /* Server? */);
    if (RT_SUCCESS(rc))
    {
        SECURITY_ATTRIBUTES SecAttrs;
        SecAttrs.nLength              = sizeof(SECURITY_ATTRIBUTES);
        SecAttrs.lpSecurityDescriptor = pSecDesc;
        SecAttrs.bInheritHandle       = FALSE;

        DWORD fOpenMode = PIPE_ACCESS_DUPLEX
                        | PIPE_WAIT
                        | FILE_FLAG_OVERLAPPED;
        if (   fFirst
            && (   g_enmWinVer >= kRTWinOSType_XP
                || (   g_enmWinVer == kRTWinOSType_2K
                    && g_WinOsInfoEx.wServicePackMajor >= 2) ) )
            fOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE; /* Introduced with W2K SP2 */

        HANDLE hNmPipe = CreateNamedPipeW(pwszPipeName,                  /* lpName */
                                          fOpenMode,                     /* dwOpenMode */
                                          PIPE_TYPE_BYTE,                /* dwPipeMode */
                                          PIPE_UNLIMITED_INSTANCES,      /* nMaxInstances */
                                          PAGE_SIZE,                     /* nOutBufferSize (advisory) */
                                          PAGE_SIZE,                     /* nInBufferSize (ditto) */
                                          30*1000,                       /* nDefaultTimeOut = 30 sec */
                                          &SecAttrs);                    /* lpSecurityAttributes */
        LocalFree(pSecDesc);
        if (hNmPipe != INVALID_HANDLE_VALUE)
            *phNmPipe = hNmPipe;
        else
            rc = RTErrConvertFromWin32(GetLastError());
    }

    return rc;
}


/**
 * Validates the user specified name.
 *
 * @returns IPRT status code.
 * @param   pszName         The name to validate.
 * @param   pcwcFullName    Where to return the UTF-16 length of the full name.
 * @param   fNative         Whether it's a native name or a portable name.
 */
static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative)
{
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertReturn(*pszName, VERR_INVALID_NAME);

    if (!fNative)
    {
        size_t cwcName = RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1;
        for (;;)
        {
            char ch = *pszName++;
            if (!ch)
                break;
            AssertReturn(!RT_C_IS_CNTRL(ch), VERR_INVALID_NAME);
            AssertReturn((unsigned)ch < 0x80, VERR_INVALID_NAME);
            AssertReturn(ch != '\\', VERR_INVALID_NAME);
            AssertReturn(ch != '/', VERR_INVALID_NAME);
            cwcName++;
        }
        *pcwcFullName = cwcName;
    }
    else
    {
        int rc = RTStrCalcUtf16LenEx(pszName, RTSTR_MAX, pcwcFullName);
        AssertRCReturn(rc, rc);
    }

    return VINF_SUCCESS;
}


/**
 * Constructs the full pipe name as UTF-16.
 *
 * @returns IPRT status code.
 * @param   pszName         The user supplied name.  ASSUMES reasonable length
 *                          for now, so no long path prefixing needed.
 * @param   pwszFullName    The output buffer.
 * @param   cwcFullName     The output buffer size excluding the terminator.
 * @param   fNative         Whether the user supplied name is a native or
 *                          portable one.
 */
static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, bool fNative)
{
    if (!fNative)
    {
        static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX;
        Assert(cwcFullName * sizeof(RTUTF16) > sizeof(s_wszPrefix));
        memcpy(pwszFullName, s_wszPrefix, sizeof(s_wszPrefix));
        cwcFullName  -= RT_ELEMENTS(s_wszPrefix) - 1;
        pwszFullName += RT_ELEMENTS(s_wszPrefix) - 1;
    }
    return RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszFullName, cwcFullName + 1, NULL);
}


RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags)
{
    /*
     * Validate parameters.
     */
    AssertPtrReturn(phServer, VERR_INVALID_POINTER);
    *phServer = NIL_RTLOCALIPCSERVER;
    AssertReturn(!(fFlags & ~RTLOCALIPC_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
    size_t cwcFullName;
    int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME));
    if (RT_SUCCESS(rc))
    {
        /*
         * Allocate and initialize the instance data.
         */
        size_t cbThis = RT_UOFFSETOF_DYN(RTLOCALIPCSERVERINT, wszName[cwcFullName + 1]);
        PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)RTMemAllocVar(cbThis);
        AssertReturn(pThis, VERR_NO_MEMORY);

        pThis->u32Magic   = RTLOCALIPCSERVER_MAGIC;
        pThis->cRefs      = 1; /* the one we return */
        pThis->fCancelled = false;

        rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME));
        if (RT_SUCCESS(rc))
        {
            rc = RTCritSectInit(&pThis->CritSect);
            if (RT_SUCCESS(rc))
            {
                pThis->hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/,
                                            FALSE /*bInitialState*/, NULL /*lpName*/);
                if (pThis->hEvent != NULL)
                {
                    RT_ZERO(pThis->OverlappedIO);
                    pThis->OverlappedIO.Internal = STATUS_PENDING;
                    pThis->OverlappedIO.hEvent   = pThis->hEvent;

                    rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, true /* fFirst */);
                    if (RT_SUCCESS(rc))
                    {
                        *phServer = pThis;
                        return VINF_SUCCESS;
                    }

                    BOOL fRc = CloseHandle(pThis->hEvent);
                    AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
                }
                else
                    rc = RTErrConvertFromWin32(GetLastError());

                int rc2 = RTCritSectDelete(&pThis->CritSect);
                AssertRC(rc2);
            }
        }
        RTMemFree(pThis);
    }
    return rc;
}


/**
 * Retains a reference to the server instance.
 *
 * @returns
 * @param   pThis               The server instance.
 */
DECLINLINE(void) rtLocalIpcServerRetain(PRTLOCALIPCSERVERINT pThis)
{
    uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2 && cRefs); NOREF(cRefs);
}


/**
 * Call when the reference count reaches 0.
 *
 * Caller owns the critsect.
 *
 * @returns VINF_OBJECT_DESTROYED
 * @param   pThis       The instance to destroy.
 */
DECL_NO_INLINE(static, int) rtLocalIpcServerWinDestroy(PRTLOCALIPCSERVERINT pThis)
{
    Assert(pThis->u32Magic == ~RTLOCALIPCSERVER_MAGIC);
    pThis->u32Magic = ~RTLOCALIPCSERVER_MAGIC;

    BOOL fRc = CloseHandle(pThis->hNmPipe);
    AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
    pThis->hNmPipe = INVALID_HANDLE_VALUE;

    fRc = CloseHandle(pThis->hEvent);
    AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
    pThis->hEvent = NULL;

    RTCritSectLeave(&pThis->CritSect);
    RTCritSectDelete(&pThis->CritSect);

    RTMemFree(pThis);
    return VINF_OBJECT_DESTROYED;
}


/**
 * Server instance destructor.
 *
 * @returns VINF_OBJECT_DESTROYED
 * @param   pThis               The server instance.
 */
DECL_NO_INLINE(static, int) rtLocalIpcServerDtor(PRTLOCALIPCSERVERINT pThis)
{
    RTCritSectEnter(&pThis->CritSect);
    return rtLocalIpcServerWinDestroy(pThis);
}


/**
 * Releases a reference to the server instance.
 *
 * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed.
 * @param   pThis               The server instance.
 */
DECLINLINE(int) rtLocalIpcServerRelease(PRTLOCALIPCSERVERINT pThis)
{
    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2);
    if (!cRefs)
        return rtLocalIpcServerDtor(pThis);
    return VINF_SUCCESS;
}


/**
 * Releases a reference to the server instance and leaves the critsect.
 *
 * @returns VINF_SUCCESS if only release, VINF_OBJECT_DESTROYED if destroyed.
 * @param   pThis               The server instance.
 */
DECLINLINE(int) rtLocalIpcServerReleaseAndUnlock(PRTLOCALIPCSERVERINT pThis)
{
    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2);
    if (!cRefs)
        return rtLocalIpcServerWinDestroy(pThis);
    return RTCritSectLeave(&pThis->CritSect);
}



RTDECL(int) RTLocalIpcServerDestroy(RTLOCALIPCSERVER hServer)
{
    /*
     * Validate input.
     */
    if (hServer == NIL_RTLOCALIPCSERVER)
        return VINF_SUCCESS;
    PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE);

    /*
     * Cancel any thread currently busy using the server,
     * leaving the cleanup to it.
     */
    AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, ~RTLOCALIPCSERVER_MAGIC, RTLOCALIPCSERVER_MAGIC), VERR_WRONG_ORDER);

    RTCritSectEnter(&pThis->CritSect);

    /* Cancel everything. */
    ASMAtomicUoWriteBool(&pThis->fCancelled, true);
    if (pThis->cRefs > 1)
    {
        BOOL fRc = SetEvent(pThis->hEvent);
        AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
    }

    return rtLocalIpcServerReleaseAndUnlock(pThis);
}


RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION phClientSession)
{
    /*
     * Validate input.
     */
    PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE);
    AssertPtrReturn(phClientSession, VERR_INVALID_POINTER);

    /*
     * Enter the critsect before inspecting the object further.
     */
    int rc = RTCritSectEnter(&pThis->CritSect);
    AssertRCReturn(rc, rc);

    rtLocalIpcServerRetain(pThis);
    if (!pThis->fCancelled)
    {
        ResetEvent(pThis->hEvent);

        RTCritSectLeave(&pThis->CritSect);

        /*
         * Try connect a client. We need to use overlapped I/O here because
         * of the cancellation done by RTLocalIpcServerCancel and RTLocalIpcServerDestroy.
         */
        SetLastError(NO_ERROR);
        BOOL fRc = ConnectNamedPipe(pThis->hNmPipe, &pThis->OverlappedIO);
        DWORD dwErr = fRc ? NO_ERROR : GetLastError();
        if (    !fRc
            &&  dwErr == ERROR_IO_PENDING)
        {
            WaitForSingleObject(pThis->hEvent, INFINITE);
            DWORD dwIgnored;
            fRc = GetOverlappedResult(pThis->hNmPipe, &pThis->OverlappedIO, &dwIgnored, FALSE /* bWait*/);
            dwErr = fRc ? NO_ERROR : GetLastError();
        }

        RTCritSectEnter(&pThis->CritSect);
        if (   !pThis->fCancelled /* Event signalled but not cancelled? */
            && pThis->u32Magic == RTLOCALIPCSERVER_MAGIC)
        {
            /*
             * Still alive, some error or an actual client.
             *
             * If it's the latter we'll have to create a new pipe instance that
             * replaces the current one for the server. The current pipe instance
             * will be assigned to the client session.
             */
            if (   fRc
                || dwErr == ERROR_PIPE_CONNECTED)
            {
                HANDLE hNmPipe;
                rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, false /* fFirst */);
                if (RT_SUCCESS(rc))
                {
                    HANDLE hNmPipeSession = pThis->hNmPipe; /* consumed */
                    pThis->hNmPipe = hNmPipe;
                    rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession);
                }
                else
                {
                    /*
                     * We failed to create a new instance for the server, disconnect
                     * the client and fail. Don't try service the client here.
                     */
                    fRc = DisconnectNamedPipe(pThis->hNmPipe);
                    AssertMsg(fRc, ("%d\n", GetLastError()));
                }
            }
            else
                rc = RTErrConvertFromWin32(dwErr);
        }
        else
        {
            /*
             * Cancelled.
             *
             * Cancel the overlapped io if it didn't complete (must be done
             * in the this thread) or disconnect the client.
             */
            Assert(pThis->fCancelled);
            if (    fRc
                ||  dwErr == ERROR_PIPE_CONNECTED)
                fRc = DisconnectNamedPipe(pThis->hNmPipe);
            else if (dwErr == ERROR_IO_PENDING)
                fRc = CancelIo(pThis->hNmPipe);
            else
                fRc = TRUE;
            AssertMsg(fRc, ("%d\n", GetLastError()));
            rc = VERR_CANCELLED;
        }
    }
    else
    {
        /*pThis->fCancelled = false; - Terrible interface idea. Add API to clear fCancelled if ever required. */
        rc = VERR_CANCELLED;
    }
    rtLocalIpcServerReleaseAndUnlock(pThis);
    return rc;
}


RTDECL(int) RTLocalIpcServerCancel(RTLOCALIPCSERVER hServer)
{
    /*
     * Validate input.
     */
    PRTLOCALIPCSERVERINT pThis = (PRTLOCALIPCSERVERINT)hServer;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSERVER_MAGIC, VERR_INVALID_HANDLE);

    /*
     * Enter the critical section, then set the cancellation flag
     * and signal the event (to wake up anyone in/at WaitForSingleObject).
     */
    rtLocalIpcServerRetain(pThis);
    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        ASMAtomicUoWriteBool(&pThis->fCancelled, true);

        BOOL fRc = SetEvent(pThis->hEvent);
        if (fRc)
            rc = VINF_SUCCESS;
        else
        {
            DWORD dwErr = GetLastError();
            AssertMsgFailed(("dwErr=%u\n", dwErr));
            rc = RTErrConvertFromWin32(dwErr);
        }

        rtLocalIpcServerReleaseAndUnlock(pThis);
    }
    else
        rtLocalIpcServerRelease(pThis);
    return rc;
}


/**
 * Create a session instance for a new server client or a client connect.
 *
 * @returns IPRT status code.
 *
 * @param   ppSession       Where to store the session handle on success.
 * @param   hNmPipeSession  The named pipe handle if server calling,
 *                          INVALID_HANDLE_VALUE if client connect.  This will
 *                          be consumed by this session, meaning on failure to
 *                          create the session it will be closed.
 */
static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession)
{
    AssertPtr(ppSession);

    /*
     * Allocate and initialize the session instance data.
     */
    int rc;
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pThis));
    if (pThis)
    {
        pThis->u32Magic         = RTLOCALIPCSESSION_MAGIC;
        pThis->cRefs            = 1; /* our ref */
        pThis->fCancelled       = false;
        pThis->fZeroByteRead    = false;
        pThis->fServerSide      = hNmPipeSession != INVALID_HANDLE_VALUE;
        pThis->hNmPipe          = hNmPipeSession;
#if 0 /* Non-blocking writes are not yet supported. */
        pThis->pbBounceBuf      = NULL;
        pThis->cbBounceBufAlloc = 0;
        pThis->cbBounceBufUsed  = 0;
#endif
        rc = RTCritSectInit(&pThis->CritSect);
        if (RT_SUCCESS(rc))
        {
            pThis->Read.hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/,
                                             FALSE /*bInitialState*/, NULL /*lpName*/);
            if (pThis->Read.hEvent != NULL)
            {
                pThis->Read.OverlappedIO.Internal = STATUS_PENDING;
                pThis->Read.OverlappedIO.hEvent   = pThis->Read.hEvent;
                pThis->Read.hActiveThread         = NIL_RTTHREAD;

                pThis->Write.hEvent = CreateEvent(NULL /*lpEventAttributes*/, TRUE /*bManualReset*/,
                                                  FALSE /*bInitialState*/, NULL /*lpName*/);
                if (pThis->Write.hEvent != NULL)
                {
                    pThis->Write.OverlappedIO.Internal = STATUS_PENDING;
                    pThis->Write.OverlappedIO.hEvent   = pThis->Write.hEvent;
                    pThis->Write.hActiveThread         = NIL_RTTHREAD;

                    *ppSession = pThis;
                    return VINF_SUCCESS;
                }

                CloseHandle(pThis->Read.hEvent);
            }

            /* bail out */
            rc = RTErrConvertFromWin32(GetLastError());
            RTCritSectDelete(&pThis->CritSect);
        }
        RTMemFree(pThis);
    }
    else
        rc = VERR_NO_MEMORY;

    if (hNmPipeSession != INVALID_HANDLE_VALUE)
    {
        BOOL fRc = CloseHandle(hNmPipeSession);
        AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
    }
    return rc;
}


RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *pszName, uint32_t fFlags)
{
    /*
     * Validate input.
     */
    AssertPtrReturn(phSession, VERR_INVALID_POINTER);
    AssertReturn(!(fFlags & ~RTLOCALIPC_C_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);

    size_t cwcFullName;
    int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME));
    if (RT_SUCCESS(rc))
    {
        /*
         * Create a session (shared with server client session creation).
         */
        PRTLOCALIPCSESSIONINT pThis;
        rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE);
        if (RT_SUCCESS(rc))
        {
            /*
             * Try open the pipe.
             */
            PSECURITY_DESCRIPTOR pSecDesc;
            rc = rtLocalIpcServerWinAllocSecurityDescriptior(&pSecDesc, false /*fServer*/);
            if (RT_SUCCESS(rc))
            {
                PRTUTF16 pwszFullName = RTUtf16Alloc((cwcFullName + 1) * sizeof(RTUTF16));
                if (pwszFullName)
                    rc = rtLocalIpcWinConstructName(pszName, pwszFullName, cwcFullName,
                                                    RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME));
                else
                    rc = VERR_NO_UTF16_MEMORY;
                if (RT_SUCCESS(rc))
                {
                    SECURITY_ATTRIBUTES SecAttrs;
                    SecAttrs.nLength              = sizeof(SECURITY_ATTRIBUTES);
                    SecAttrs.lpSecurityDescriptor = pSecDesc;
                    SecAttrs.bInheritHandle       = FALSE;

                    HANDLE hPipe = CreateFileW(pwszFullName,
                                               GENERIC_READ | GENERIC_WRITE,
                                               0 /*no sharing*/,
                                               &SecAttrs,
                                               OPEN_EXISTING,
                                               FILE_FLAG_OVERLAPPED
                                               /* Needed in order to prevent the server to impersonate with this thread's
                                                * security context. See #9773. */
                                               | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS,
                                               NULL /*no template handle*/);
                    if (hPipe != INVALID_HANDLE_VALUE)
                    {
                        pThis->hNmPipe = hPipe;

                        LocalFree(pSecDesc);
                        RTUtf16Free(pwszFullName);

                        /*
                         * We're done!
                         */
                        *phSession = pThis;
                        return VINF_SUCCESS;
                    }

                    rc = RTErrConvertFromWin32(GetLastError());
                }

                RTUtf16Free(pwszFullName);
                LocalFree(pSecDesc);
            }

            /* destroy the session handle. */
            CloseHandle(pThis->Read.hEvent);
            CloseHandle(pThis->Write.hEvent);
            RTCritSectDelete(&pThis->CritSect);

            RTMemFree(pThis);
        }
    }
    return rc;
}


/**
 * Cancells all pending I/O operations, forcing the methods to return with
 * VERR_CANCELLED (unless they've got actual data to return).
 *
 * Used by RTLocalIpcSessionCancel and RTLocalIpcSessionClose.
 *
 * @returns IPRT status code.
 * @param   pThis               The client session instance.
 */
static int rtLocalIpcWinCancel(PRTLOCALIPCSESSIONINT pThis)
{
    ASMAtomicUoWriteBool(&pThis->fCancelled, true);

    /*
     * Call CancelIo since this call cancels both read and write oriented operations.
     */
    if (   pThis->fZeroByteRead
        || pThis->Read.hActiveThread != NIL_RTTHREAD
        || pThis->Write.hActiveThread != NIL_RTTHREAD)
        CancelIo(pThis->hNmPipe);

    /*
     * Set both event semaphores.
     */
    BOOL fRc = SetEvent(pThis->Read.hEvent);
    AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
    fRc = SetEvent(pThis->Write.hEvent);
    AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);

    return VINF_SUCCESS;
}


/**
 * Retains a reference to the session instance.
 *
 * @param   pThis               The client session instance.
 */
DECLINLINE(void) rtLocalIpcSessionRetain(PRTLOCALIPCSESSIONINT pThis)
{
    uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2 && cRefs); NOREF(cRefs);
}


RTDECL(uint32_t) RTLocalIpcSessionRetain(RTLOCALIPCSESSION hSession)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, UINT32_MAX);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX);

    uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2 && cRefs);
    return cRefs;
}


/**
 * Call when the reference count reaches 0.
 *
 * Caller owns the critsect.
 *
 * @returns VINF_OBJECT_DESTROYED
 * @param   pThis       The instance to destroy.
 */
DECL_NO_INLINE(static, int) rtLocalIpcSessionWinDestroy(PRTLOCALIPCSESSIONINT pThis)
{
    BOOL fRc = CloseHandle(pThis->hNmPipe);
    AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc);
    pThis->hNmPipe = INVALID_HANDLE_VALUE;

    fRc = CloseHandle(pThis->Write.hEvent);
    AssertMsg(fRc, ("%d\n", GetLastError()));
    pThis->Write.hEvent = NULL;

    fRc = CloseHandle(pThis->Read.hEvent);
    AssertMsg(fRc, ("%d\n", GetLastError()));
    pThis->Read.hEvent = NULL;

    int rc2 = RTCritSectLeave(&pThis->CritSect); AssertRC(rc2);
    RTCritSectDelete(&pThis->CritSect);

    RTMemFree(pThis);
    return VINF_OBJECT_DESTROYED;
}


/**
 * Releases a reference to the session instance and unlock it.
 *
 * @returns VINF_SUCCESS or VINF_OBJECT_DESTROYED as appropriate.
 * @param   pThis               The session instance.
 */
DECLINLINE(int) rtLocalIpcSessionReleaseAndUnlock(PRTLOCALIPCSESSIONINT pThis)
{
    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2);
    if (!cRefs)
        return rtLocalIpcSessionWinDestroy(pThis);

    int rc2 = RTCritSectLeave(&pThis->CritSect); AssertRC(rc2);
    Log(("rtLocalIpcSessionReleaseAndUnlock: %u refs left\n", cRefs));
    return VINF_SUCCESS;
}


RTDECL(uint32_t) RTLocalIpcSessionRelease(RTLOCALIPCSESSION hSession)
{
    if (hSession == NIL_RTLOCALIPCSESSION)
        return 0;

    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, UINT32_MAX);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, UINT32_MAX);

    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    Assert(cRefs < UINT32_MAX / 2);
    if (cRefs)
        Log(("RTLocalIpcSessionRelease: %u refs left\n", cRefs));
    else
    {
        RTCritSectEnter(&pThis->CritSect);
        rtLocalIpcSessionWinDestroy(pThis);
    }
    return cRefs;
}


RTDECL(int) RTLocalIpcSessionClose(RTLOCALIPCSESSION hSession)
{
    /*
     * Validate input.
     */
    if (hSession == NIL_RTLOCALIPCSESSION)
        return VINF_SUCCESS;
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);

    /*
     * Invalidate the instance, cancel all outstanding I/O and drop our reference.
     */
    RTCritSectEnter(&pThis->CritSect);
    rtLocalIpcWinCancel(pThis);
    return rtLocalIpcSessionReleaseAndUnlock(pThis);
}


/**
 * Handles WaitForSingleObject return value when waiting for a zero byte read.
 *
 * The zero byte read is started by the RTLocalIpcSessionWaitForData method and
 * left pending when the function times out.  This saves us the problem of
 * CancelIo messing with all active I/O operations and the trouble of restarting
 * the zero byte read the next time the method is called.  However should
 * RTLocalIpcSessionRead be called after a failed RTLocalIpcSessionWaitForData
 * call, the zero byte read will still be pending and it must wait for it to
 * complete before the OVERLAPPEDIO structure can be reused.
 *
 * Thus, both functions will do WaitForSingleObject and share this routine to
 * handle the outcome.
 *
 * @returns IPRT status code.
 * @param   pThis               The session instance.
 * @param   rcWait              The WaitForSingleObject return code.
 */
static int rtLocalIpcWinGetZeroReadResult(PRTLOCALIPCSESSIONINT pThis, DWORD rcWait)
{
    int rc;
    DWORD cbRead = 42;
    if (rcWait == WAIT_OBJECT_0)
    {
        if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, !pThis->fCancelled /*fWait*/))
        {
            Assert(cbRead == 0);
            rc = VINF_SUCCESS;
            pThis->fZeroByteRead = false;
        }
        else if (pThis->fCancelled)
            rc = VERR_CANCELLED;
        else
            rc = RTErrConvertFromWin32(GetLastError());
    }
    else
    {
        /* We try get the result here too, just in case we're lucky, but no waiting. */
        DWORD dwErr = GetLastError();
        if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, FALSE /*fWait*/))
        {
            Assert(cbRead == 0);
            rc = VINF_SUCCESS;
            pThis->fZeroByteRead = false;
        }
        else if (rcWait == WAIT_TIMEOUT)
            rc = VERR_TIMEOUT;
        else if (rcWait == WAIT_ABANDONED)
            rc = VERR_INVALID_HANDLE;
        else
            rc = RTErrConvertFromWin32(dwErr);
    }
    return rc;
}


RTDECL(int) RTLocalIpcSessionRead(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);
    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
    /* pcbRead is optional. */

    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        rtLocalIpcSessionRetain(pThis);
        if (pThis->Read.hActiveThread == NIL_RTTHREAD)
        {
            pThis->Read.hActiveThread = RTThreadSelf();

            size_t cbTotalRead = 0;
            while (cbToRead > 0)
            {
                DWORD cbRead = 0;
                if (!pThis->fCancelled)
                {
                    /*
                     * Wait for pending zero byte read, if necessary.
                     * Note! It cannot easily be cancelled due to concurrent current writes.
                     */
                    if (!pThis->fZeroByteRead)
                    { /* likely */ }
                    else
                    {
                        RTCritSectLeave(&pThis->CritSect);
                        DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, RT_MS_1MIN);
                        RTCritSectEnter(&pThis->CritSect);

                        rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait);
                        if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT)
                            continue;
                        break;
                    }

                    /*
                     * Kick of a an overlapped read.  It should return immediately if
                     * there is bytes in the buffer.  If not, we'll cancel it and see
                     * what we get back.
                     */
                    rc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(rc == TRUE);
                    RTCritSectLeave(&pThis->CritSect);

                    if (ReadFile(pThis->hNmPipe, pvBuf,
                                 cbToRead <= ~(DWORD)0 ? (DWORD)cbToRead : ~(DWORD)0,
                                 &cbRead, &pThis->Read.OverlappedIO))
                    {
                        RTCritSectEnter(&pThis->CritSect);
                        rc = VINF_SUCCESS;
                    }
                    else if (GetLastError() == ERROR_IO_PENDING)
                    {
                        WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, INFINITE);

                        RTCritSectEnter(&pThis->CritSect);
                        if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, TRUE /*fWait*/))
                            rc = VINF_SUCCESS;
                        else
                        {
                            if (pThis->fCancelled)
                                rc = VERR_CANCELLED;
                            else
                                rc = RTErrConvertFromWin32(GetLastError());
                            break;
                        }
                    }
                    else
                    {
                        rc = RTErrConvertFromWin32(GetLastError());
                        AssertMsgFailedBreak(("%Rrc\n", rc));
                    }
                }
                else
                {
                    rc = VERR_CANCELLED;
                    break;
                }

                /* Advance. */
                cbToRead    -= cbRead;
                cbTotalRead += cbRead;
                pvBuf     = (uint8_t *)pvBuf + cbRead;
            }

            if (pcbRead)
            {
                *pcbRead = cbTotalRead;
                if (   RT_FAILURE(rc)
                    && cbTotalRead
                    && rc != VERR_INVALID_POINTER)
                    rc = VINF_SUCCESS;
            }

            pThis->Read.hActiveThread = NIL_RTTHREAD;
        }
        else
            rc = VERR_WRONG_ORDER;
        rtLocalIpcSessionReleaseAndUnlock(pThis);
    }

    return rc;
}


RTDECL(int) RTLocalIpcSessionReadNB(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, size_t *pcbRead)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);
    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
    AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
    *pcbRead = 0;

    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        rtLocalIpcSessionRetain(pThis);
        if (pThis->Read.hActiveThread == NIL_RTTHREAD)
        {
            pThis->Read.hActiveThread = RTThreadSelf();

            for (;;)
            {
                DWORD cbRead = 0;
                if (!pThis->fCancelled)
                {
                    /*
                     * Wait for pending zero byte read, if necessary.
                     * Note! It cannot easily be cancelled due to concurrent current writes.
                     */
                    if (!pThis->fZeroByteRead)
                    { /* likely */ }
                    else
                    {
                        RTCritSectLeave(&pThis->CritSect);
                        DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, 0);
                        RTCritSectEnter(&pThis->CritSect);

                        rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait);
                        if (RT_SUCCESS(rc))
                            continue;

                        if (rc == VERR_TIMEOUT)
                            rc = VINF_TRY_AGAIN;
                        break;
                    }

                    /*
                     * Figure out how much we can read (cannot try and cancel here
                     * like in the anonymous pipe code).
                     */
                    DWORD cbAvailable;
                    if (PeekNamedPipe(pThis->hNmPipe, NULL, 0, NULL, &cbAvailable, NULL))
                    {
                        if (cbAvailable == 0 || cbToRead == 0)
                        {
                            *pcbRead = 0;
                            rc = VINF_TRY_AGAIN;
                            break;
                        }
                    }
                    else
                    {
                        rc = RTErrConvertFromWin32(GetLastError());
                        break;
                    }
                    if (cbAvailable > cbToRead)
                        cbAvailable = (DWORD)cbToRead;

                    /*
                     * Kick of a an overlapped read.  It should return immediately, so we
                     * don't really need to leave the critsect here.
                     */
                    rc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(rc == TRUE);
                    if (ReadFile(pThis->hNmPipe, pvBuf, cbAvailable, &cbRead, &pThis->Read.OverlappedIO))
                    {
                        *pcbRead = cbRead;
                        rc = VINF_SUCCESS;
                    }
                    else if (GetLastError() == ERROR_IO_PENDING)
                    {
                        DWORD rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, 0);
                        if (rcWait == WAIT_TIMEOUT)
                        {
                            RTCritSectLeave(&pThis->CritSect);
                            rcWait = WaitForSingleObject(pThis->Read.OverlappedIO.hEvent, INFINITE);
                            RTCritSectEnter(&pThis->CritSect);
                        }
                        if (GetOverlappedResult(pThis->hNmPipe, &pThis->Read.OverlappedIO, &cbRead, TRUE /*fWait*/))
                        {
                            *pcbRead = cbRead;
                            rc = VINF_SUCCESS;
                        }
                        else
                        {
                            if (pThis->fCancelled)
                                rc = VERR_CANCELLED;
                            else
                                rc = RTErrConvertFromWin32(GetLastError());
                        }
                    }
                    else
                    {
                        rc = RTErrConvertFromWin32(GetLastError());
                        AssertMsgFailedBreak(("%Rrc\n", rc));
                    }
                }
                else
                    rc = VERR_CANCELLED;
                break;
            }

            pThis->Read.hActiveThread = NIL_RTTHREAD;
        }
        else
            rc = VERR_WRONG_ORDER;
        rtLocalIpcSessionReleaseAndUnlock(pThis);
    }

    return rc;
}


#if 0 /* Non-blocking writes are not yet supported. */
/**
 * Common worker for handling I/O completion.
 *
 * This is used by RTLocalIpcSessionClose and RTLocalIpcSessionWrite.
 *
 * @returns IPRT status code.
 * @param   pThis               The pipe instance handle.
 */
static int rtLocalIpcSessionWriteCheckCompletion(PRTLOCALIPCSESSIONINT pThis)
{
    int rc;
    DWORD rcWait = WaitForSingleObject(pThis->OverlappedIO.hEvent, 0);
    if (rcWait == WAIT_OBJECT_0)
    {
        DWORD cbWritten = 0;
        if (GetOverlappedResult(pThis->hNmPipe, &pThis->OverlappedIO, &cbWritten, TRUE))
        {
            for (;;)
            {
                if (cbWritten >= pThis->cbBounceBufUsed)
                {
                    pThis->fIOPending = false;
                    rc = VINF_SUCCESS;
                    break;
                }

                /* resubmit the remainder of the buffer - can this actually happen? */
                memmove(&pThis->pbBounceBuf[0], &pThis->pbBounceBuf[cbWritten], pThis->cbBounceBufUsed - cbWritten);
                rc = ResetEvent(pThis->OverlappedIO.hEvent); Assert(rc == TRUE);
                if (!WriteFile(pThis->hNmPipe, pThis->pbBounceBuf, (DWORD)pThis->cbBounceBufUsed,
                               &cbWritten, &pThis->OverlappedIO))
                {
                    DWORD dwErr = GetLastError();
                    if (dwErr == ERROR_IO_PENDING)
                        rc = VINF_TRY_AGAIN;
                    else
                    {
                        pThis->fIOPending = false;
                        if (dwErr == ERROR_NO_DATA)
                            rc = VERR_BROKEN_PIPE;
                        else
                            rc = RTErrConvertFromWin32(dwErr);
                    }
                    break;
                }
                Assert(cbWritten > 0);
            }
        }
        else
        {
            pThis->fIOPending = false;
            rc = RTErrConvertFromWin32(GetLastError());
        }
    }
    else if (rcWait == WAIT_TIMEOUT)
        rc = VINF_TRY_AGAIN;
    else
    {
        pThis->fIOPending = false;
        if (rcWait == WAIT_ABANDONED)
            rc = VERR_INVALID_HANDLE;
        else
            rc = RTErrConvertFromWin32(GetLastError());
    }
    return rc;
}
#endif


RTDECL(int) RTLocalIpcSessionWrite(RTLOCALIPCSESSION hSession, const void *pvBuf, size_t cbToWrite)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);
    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
    AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);

    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        rtLocalIpcSessionRetain(pThis);
        if (pThis->Write.hActiveThread == NIL_RTTHREAD)
        {
            pThis->Write.hActiveThread = RTThreadSelf();

            /*
             * Try write everything. No bounce buffering necessary.
             */
            size_t cbTotalWritten = 0;
            while (cbToWrite > 0)
            {
                DWORD cbWritten = 0;
                if (!pThis->fCancelled)
                {
                    BOOL fRc = ResetEvent(pThis->Write.OverlappedIO.hEvent); Assert(fRc == TRUE);
                    RTCritSectLeave(&pThis->CritSect);

                    DWORD const cbToWriteInThisIteration = cbToWrite <= ~(DWORD)0 ? (DWORD)cbToWrite : ~(DWORD)0;
                    fRc = WriteFile(pThis->hNmPipe, pvBuf, cbToWriteInThisIteration, &cbWritten, &pThis->Write.OverlappedIO);
                    if (fRc)
                        rc = VINF_SUCCESS;
                    else
                    {
                        DWORD dwErr = GetLastError();
                        if (dwErr == ERROR_IO_PENDING)
                        {
                            DWORD rcWait = WaitForSingleObject(pThis->Write.OverlappedIO.hEvent, INFINITE);
                            if (rcWait == WAIT_OBJECT_0)
                            {
                                if (GetOverlappedResult(pThis->hNmPipe, &pThis->Write.OverlappedIO, &cbWritten, TRUE /*fWait*/))
                                    rc = VINF_SUCCESS;
                                else
                                    rc = RTErrConvertFromWin32(GetLastError());
                            }
                            else if (rcWait == WAIT_TIMEOUT)
                                rc = VERR_TIMEOUT;
                            else if (rcWait == WAIT_ABANDONED)
                                rc = VERR_INVALID_HANDLE;
                            else
                                rc = RTErrConvertFromWin32(GetLastError());
                        }
                        else if (dwErr == ERROR_NO_DATA)
                            rc = VERR_BROKEN_PIPE;
                        else
                            rc = RTErrConvertFromWin32(dwErr);
                    }

                    if (cbWritten > cbToWriteInThisIteration) /* paranoia^3 */
                        cbWritten = cbToWriteInThisIteration;

                    RTCritSectEnter(&pThis->CritSect);
                    if (RT_FAILURE(rc))
                        break;
                }
                else
                {
                    rc = VERR_CANCELLED;
                    break;
                }

                /* Advance. */
                pvBuf           = (char const *)pvBuf + cbWritten;
                cbTotalWritten += cbWritten;
                cbToWrite      -= cbWritten;
            }

            pThis->Write.hActiveThread = NIL_RTTHREAD;
        }
        else
            rc = VERR_WRONG_ORDER;
        rtLocalIpcSessionReleaseAndUnlock(pThis);
    }

    return rc;
}


RTDECL(int) RTLocalIpcSessionFlush(RTLOCALIPCSESSION hSession)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);

    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        if (pThis->Write.hActiveThread == NIL_RTTHREAD)
        {
            /* No flushing on Windows needed since RTLocalIpcSessionWrite will block until
             * all data was written (or an error occurred). */
            /** @todo r=bird: above comment is misinformed.
             *        Implement this as soon as we want an explicit asynchronous version of
             *        RTLocalIpcSessionWrite on Windows. */
            rc = VINF_SUCCESS;
        }
        else
            rc = VERR_WRONG_ORDER;
        RTCritSectLeave(&pThis->CritSect);
    }
    return rc;
}


RTDECL(int) RTLocalIpcSessionWaitForData(RTLOCALIPCSESSION hSession, uint32_t cMillies)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);

    uint64_t const msStart = RTTimeMilliTS();

    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        rtLocalIpcSessionRetain(pThis);
        if (pThis->Read.hActiveThread == NIL_RTTHREAD)
        {
            pThis->Read.hActiveThread = RTThreadSelf();

            /*
             * Wait loop.
             */
            for (unsigned iLoop = 0;; iLoop++)
            {
                /*
                 * Check for cancellation before we continue.
                 */
                if (!pThis->fCancelled)
                { /* likely */ }
                else
                {
                    rc = VERR_CANCELLED;
                    break;
                }

                /*
                 * Prep something we can wait on.
                 */
                HANDLE hWait = INVALID_HANDLE_VALUE;
                if (pThis->fZeroByteRead)
                    hWait = pThis->Read.OverlappedIO.hEvent;
                else
                {
                    /* Peek at the pipe buffer and see how many bytes it contains. */
                    DWORD cbAvailable;
                    if (   PeekNamedPipe(pThis->hNmPipe, NULL, 0, NULL, &cbAvailable, NULL)
                        && cbAvailable)
                    {
                        rc = VINF_SUCCESS;
                        break;
                    }

                    /* Start a zero byte read operation that we can wait on. */
                    if (cMillies == 0)
                    {
                        rc = VERR_TIMEOUT;
                        break;
                    }
                    BOOL fRc = ResetEvent(pThis->Read.OverlappedIO.hEvent); Assert(fRc == TRUE); NOREF(fRc);
                    DWORD cbRead = 0;
                    if (ReadFile(pThis->hNmPipe, pThis->abBuf, 0 /*cbToRead*/, &cbRead, &pThis->Read.OverlappedIO))
                    {
                        rc = VINF_SUCCESS;
                        if (iLoop > 10)
                            RTThreadYield();
                    }
                    else if (GetLastError() == ERROR_IO_PENDING)
                    {
                        pThis->fZeroByteRead = true;
                        hWait = pThis->Read.OverlappedIO.hEvent;
                    }
                    else
                        rc = RTErrConvertFromWin32(GetLastError());
                    if (RT_FAILURE(rc))
                        break;
                }

                /*
                 * Check for timeout.
                 */
                DWORD cMsMaxWait = INFINITE; /* (MSC maybe used uninitialized) */
                if (cMillies == RT_INDEFINITE_WAIT)
                    cMsMaxWait = INFINITE;
                else if (   hWait != INVALID_HANDLE_VALUE
                         || iLoop > 10)
                {
                    uint64_t cMsElapsed = RTTimeMilliTS() - msStart;
                    if (cMsElapsed <= cMillies)
                        cMsMaxWait = cMillies - (uint32_t)cMsElapsed;
                    else if (iLoop == 0)
                        cMsMaxWait = cMillies ? 1 : 0;
                    else
                    {
                        rc = VERR_TIMEOUT;
                        break;
                    }
                }

                /*
                 * Wait and collect the result.
                 */
                if (hWait != INVALID_HANDLE_VALUE)
                {
                    RTCritSectLeave(&pThis->CritSect);

                    DWORD rcWait = WaitForSingleObject(hWait, cMsMaxWait);

                    int rc2 = RTCritSectEnter(&pThis->CritSect);
                    AssertRC(rc2);

                    rc = rtLocalIpcWinGetZeroReadResult(pThis, rcWait);
                    break;
                }
            }

            pThis->Read.hActiveThread = NIL_RTTHREAD;
        }

        rtLocalIpcSessionReleaseAndUnlock(pThis);
    }

    return rc;
}


RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession)
{
    PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE);

    /*
     * Enter the critical section, then set the cancellation flag
     * and signal the event (to wake up anyone in/at WaitForSingleObject).
     */
    int rc = RTCritSectEnter(&pThis->CritSect);
    if (RT_SUCCESS(rc))
    {
        rtLocalIpcSessionRetain(pThis);
        rc = rtLocalIpcWinCancel(pThis);
        rtLocalIpcSessionReleaseAndUnlock(pThis);
    }

    return rc;
}


RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess)
{
    RT_NOREF_PV(hSession); RT_NOREF_PV(pProcess);
    return VERR_NOT_SUPPORTED;
}


RTDECL(int) RTLocalIpcSessionQueryUserId(RTLOCALIPCSESSION hSession, PRTUID pUid)
{
    RT_NOREF_PV(hSession); RT_NOREF_PV(pUid);
    return VERR_NOT_SUPPORTED;
}


RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGid)
{
    RT_NOREF_PV(hSession); RT_NOREF_PV(pGid);
    return VERR_NOT_SUPPORTED;
}

