// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#define LARGE_BUFFER_THRESHOLD 65536 // bytes
#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts

///////////////////////////////////////////////////////////////////////////////////////
// RemoteMsiSession //
//////////////////////
//
// Allows accessing MSI APIs from another process using named pipes.
//
class RemoteMsiSession
{
public:

    // This enumeration MUST stay in sync with the
    // managed equivalent in RemotableNativeMethods.cs!
    enum RequestId
    {
        EndSession = 0,
        MsiCloseHandle,
        MsiCreateRecord,
        MsiDatabaseGetPrimaryKeys,
        MsiDatabaseIsTablePersistent,
        MsiDatabaseOpenView,
        MsiDoAction,
        MsiEnumComponentCosts,
        MsiEvaluateCondition,
        MsiFormatRecord,
        MsiGetActiveDatabase,
        MsiGetComponentState,
        MsiGetFeatureCost,
        MsiGetFeatureState,
        MsiGetFeatureValidStates,
        MsiGetLanguage,
        MsiGetLastErrorRecord,
        MsiGetMode,
        MsiGetProperty,
        MsiGetSourcePath,
        MsiGetSummaryInformation,
        MsiGetTargetPath,
        MsiProcessMessage,
        MsiRecordClearData,
        MsiRecordDataSize,
        MsiRecordGetFieldCount,
        MsiRecordGetInteger,
        MsiRecordGetString,
        MsiRecordIsNull,
        MsiRecordReadStream,
        MsiRecordSetInteger,
        MsiRecordSetStream,
        MsiRecordSetString,
        MsiSequence,
        MsiSetComponentState,
        MsiSetFeatureAttributes,
        MsiSetFeatureState,
        MsiSetInstallLevel,
        MsiSetMode,
        MsiSetProperty,
        MsiSetTargetPath,
        MsiSummaryInfoGetProperty,
        MsiVerifyDiskSpace,
        MsiViewExecute,
        MsiViewFetch,
        MsiViewGetError,
        MsiViewGetColumnInfo,
        MsiViewModify,
    };

    static const int MAX_REQUEST_FIELDS = 4;

    // Used to pass data back and forth for remote API calls,
    // including in & out params & return values.
    // Only strings and ints are supported.
    struct RequestData
    {
        struct
        {
            VARENUM  vt;
            union {
              int    iValue;
              UINT   uiValue;
              DWORD  cchValue;
              LPWSTR szValue;
              BYTE*  sValue;
              DWORD  cbValue;
            };
        } fields[MAX_REQUEST_FIELDS];
    };

public:

    // This value is set from the single data parameter in the EndSession request.
    // It saves the exit code of the out-of-proc custom action.
    int ExitCode;

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession constructor
    //
    // Creates a new remote session instance, for use either by the server
    // or client process.
    //
    // szName  - Identifies the session instance being remoted. The server and
    //         the client must use the same name. The name should be unique
    //         enough to avoid conflicting with other instances on the system.
    //
    // fServer - True if the calling process is the server process, false if the
    //         calling process is the client process.
    //
    RemoteMsiSession(const wchar_t* szName, bool fServer=true)
        : m_fServer(fServer),
          m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"),
          m_szPipeName(NULL),
          m_hPipe(NULL),
          m_fConnecting(false),
          m_fConnected(false),
          m_hReceiveThread(NULL),
          m_hReceiveStopEvent(NULL),
          m_pBufReceive(NULL),
          m_cbBufReceive(0),
          m_pBufSend(NULL),
          m_cbBufSend(0),
          ExitCode(ERROR_INSTALL_FAILURE)
    {   
        SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED));
        m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession destructor
    //
    // Closes any open handles and frees any allocated memory.
    //
    ~RemoteMsiSession()
    {
        WaitExitCode();
        if (m_hPipe != NULL)
        {
            CloseHandle(m_hPipe);
            m_hPipe = NULL;
        }
        if (m_overlapped.hEvent != NULL)
        {
            CloseHandle(m_overlapped.hEvent);
            m_overlapped.hEvent = NULL;
        }
        if (m_szPipeName != NULL)
        {
            delete[] m_szPipeName;
            m_szPipeName = NULL;
        }
        if (m_pBufReceive != NULL)
        {
            SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
            delete[] m_pBufReceive;
            m_pBufReceive = NULL;
        }
        if (m_pBufSend != NULL)
        {
            SecureZeroMemory(m_pBufSend, m_cbBufSend);
            delete[] m_pBufSend;
            m_pBufSend = NULL;
        }
        m_fConnecting = false;
        m_fConnected = false;
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession::WaitExitCode()
    //
    // Waits for the server processing thread to complete.
    //
    void WaitExitCode()
    {
        if (m_hReceiveThread != NULL)
        {
            SetEvent(m_hReceiveStopEvent);
            WaitForSingleObject(m_hReceiveThread, INFINITE);
            CloseHandle(m_hReceiveThread);
            m_hReceiveThread = NULL;
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession::Connect()
    //
    // Connects the inter-process communication channel.
    // (Currently implemented as a named pipe.)
    //
    // This method must be called first by the server process, then by the client
    // process. The method does not block; the server will asynchronously wait
    // for the client process to make the connection.
    //
    // Returns: 0 on success, Win32 error code on failure.
    //
    virtual DWORD Connect()
    {
        const wchar_t* szPipePrefix = L"\\\\.\\pipe\\";
        size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1;
        m_szPipeName = new wchar_t[cchPipeNameBuf];

        if (m_szPipeName == NULL)
        {
            return ERROR_OUTOFMEMORY;
        }
        else
        {
            wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix);
            wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName);

            if (m_fServer)
            {
                return this->ConnectPipeServer();
            }
            else
            {
                return this->ConnectPipeClient();
            }
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession::IsConnected()
    //
    // Checks if the server process and client process are currently connected.
    //
    virtual bool IsConnected() const
    {
        return m_fConnected;
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession::ProcessRequests()
    //
    // For use by the service process. Watches for requests in the input buffer and calls
    // the callback for each one.
    //
    // This method does not block; it spawns a separate thread to do the work.
    //
    // Returns: 0 on success, Win32 error code on failure.
    //
    virtual DWORD ProcessRequests()
    {
        return this->StartProcessingReqests();
    }

    /////////////////////////////////////////////////////////////////////////////////////
    // RemoteMsiSession::SendRequest()
    //
    // For use by the client process. Sends a request to the server and
    // synchronously waits on a response, up to the timeout value.
    //
    // id         - ID code of the MSI API call being requested.
    //
    // pRequest   - Pointer to a data structure containing request parameters.
    //
    // ppResponse - [OUT] Pointer to a location that receives the response parameters.
    //
    // Returns: 0 on success, Win32 error code on failure.
    // Returns WAIT_TIMEOUT if no response was received in time.
    //
    virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse)
    {
        if (m_fServer)
        {
            return ERROR_INVALID_OPERATION;
        }

        if (!m_fConnected)
        {
            *ppResponse = NULL;
            return 0;
        }

        DWORD dwRet = this->SendRequest(id, pRequest);
        if (dwRet != 0)
        {
            return dwRet;
        }

        if (id != EndSession)
        {
            static RequestData response;
            if (ppResponse != NULL)
            {
                *ppResponse = &response;
            }

            return this->ReceiveResponse(id, &response);
        }
        else
        {
            CloseHandle(m_hPipe);
            m_hPipe = NULL;
            m_fConnected = false;
            return 0;
        }
    }

private:

    //
    // Do not allow assignment.
    //
    RemoteMsiSession& operator=(const RemoteMsiSession&);

    //
    // Called only by the server process.
    // Create a new thread to handle receiving requests.
    //
    DWORD StartProcessingReqests()
    {
        if (!m_fServer || m_hReceiveStopEvent != NULL)
        {
            return ERROR_INVALID_OPERATION;
        }

        DWORD dwRet = 0;

        m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        if (m_hReceiveStopEvent == NULL)
        {
            dwRet = GetLastError();
        }
        else
        {
            if (m_hReceiveThread != NULL)
            {
                CloseHandle(m_hReceiveThread);
            }

            m_hReceiveThread = CreateThread(NULL, 0,
                RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL);
            
            if (m_hReceiveThread == NULL)
            {
                dwRet = GetLastError();
                CloseHandle(m_hReceiveStopEvent);
                m_hReceiveStopEvent = NULL;
            }
        }

        return dwRet;
    }

    //
    // Called only by the watcher process.
    // First verify the connection is complete. Then continually read and parse messages,
    // invoke the callback, and send the replies.
    //
    static DWORD WINAPI ProcessRequestsThreadStatic(void* pv)
    {
        return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread();
    }

    DWORD ProcessRequestsThread()
    {
        DWORD dwRet;

        dwRet = CompleteConnection();
        if (dwRet != 0)
        {
            if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0;
        }

        while (m_fConnected)
        {
            RequestId id;
            RequestData req;
            dwRet = ReceiveRequest(&id, &req);
            if (dwRet != 0)
            {
                if (dwRet == ERROR_OPERATION_ABORTED ||
                    dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA)
                {
                    dwRet = 0;
                }
            }
            else
            {
                RequestData resp;
                ProcessRequest(id, &req, &resp);

                if (id == EndSession)
                {
                    break;
                }

                dwRet = SendResponse(id, &resp);
                if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA)
                {
                    dwRet = 0;
                }
            }
        }

        CloseHandle(m_hReceiveStopEvent);
        m_hReceiveStopEvent = NULL;
        return dwRet;
    }

    //
    // Called only by the server process's receive thread.
    // Read one request into a RequestData object.
    //
    DWORD ReceiveRequest(RequestId* pId, RequestData* pReq)
    {
        DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId));

        if (dwRet == 0)
        {
            dwRet = this->ReadRequestData(pReq);
        }

        return dwRet;
    }

    //
    // Called by the server process's receive thread or the client's request call
    // to read the response. Read data from the pipe, allowing interruption by the
    // stop event if on the server.
    //
    DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead)
    {
        DWORD dwRet = 0;
        DWORD dwTotalBytesRead = 0;

        while (dwRet == 0 && dwTotalBytesRead < cbRead)
        {
            DWORD dwBytesReadThisTime;
            ResetEvent(m_overlapped.hEvent);
            if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped))
            {
                dwRet = GetLastError();
                if (dwRet == ERROR_IO_PENDING)
                {
                    if (m_fServer)
                    {
                        HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
                        dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
                    }
                    else
                    {
                        dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
                    }

                    if (dwRet == WAIT_OBJECT_0)
                    {
                        if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE))
                        {
                            dwRet = GetLastError();
                        }
                    }
                    else if (dwRet == WAIT_FAILED)
                    {
                        dwRet = GetLastError();
                    }
                    else
                    {
                        dwRet = ERROR_OPERATION_ABORTED;
                    }
                }
            }

            dwTotalBytesRead += dwBytesReadThisTime;
        }

        if (dwRet != 0)
        {
            if (m_fServer)
            {
                CancelIo(m_hPipe);
                DisconnectNamedPipe(m_hPipe);
            }
            else
            {
                CloseHandle(m_hPipe);
                m_hPipe = NULL;
            }
            m_fConnected = false;
        }

        return dwRet;
    }

    //
    // Called only by the server process.
    // Given a request, invoke the MSI API and return the response.
    // This is implemented in RemoteMsi.cpp.
    //
    void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp);

    //
    // Called only by the client process.
    // Send request data over the pipe.
    //
    DWORD SendRequest(RequestId id, const RequestData* pRequest)
    {
        DWORD dwRet = WriteRequestData(id, pRequest);
        
        if (dwRet != 0)
        {
            m_fConnected = false;
            CloseHandle(m_hPipe);
            m_hPipe = NULL;
        }

        return dwRet;
    }

    //
    // Called only by the server process.
    // Just send a response over the pipe.
    //
    DWORD SendResponse(RequestId id, const RequestData* pResp)
    {
        DWORD dwRet = WriteRequestData(id, pResp);

        if (dwRet != 0)
        {
            DisconnectNamedPipe(m_hPipe);
            m_fConnected = false;
        }

        return dwRet;
    }

    //
    // Called either by the client or server process.
    // Writes data to the pipe for a request or response.
    //
    DWORD WriteRequestData(RequestId id, const RequestData* pReq)
    {
        DWORD dwRet = 0;

        RequestData req = *pReq; // Make a copy because the const data can't be changed.

        dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId));
        if (dwRet != 0)
        {
            return dwRet;
        }

        BYTE* sValues[MAX_REQUEST_FIELDS] = {0};
        for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
        {
            if (req.fields[i].vt == VT_LPWSTR)
            {
                sValues[i] = (BYTE*) req.fields[i].szValue;
                req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue);
            }
            else if (req.fields[i].vt == VT_STREAM)
            {
                sValues[i] = req.fields[i].sValue;
                req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue;
            }
        }

        dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData));
        if (dwRet != 0)
        {
            return dwRet;
        }

        for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
        {
            if (sValues[i] != NULL)
            {
                DWORD cbValue;
                if (req.fields[i].vt == VT_LPWSTR)
                {
                    cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR);
                }
                else
                {
                    cbValue = req.fields[i].cbValue;
                }

                dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue);
                if (dwRet != 0)
                {
                    break;
                }
            }
        }

        return dwRet;
    }

    //
    // Called when writing a request or response. Writes data to
    // the pipe, allowing interruption by the stop event if on the server.
    //
    DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite)
    {
        DWORD dwRet = 0;
        DWORD dwTotalBytesWritten = 0;

        while (dwRet == 0 && dwTotalBytesWritten < cbWrite)
        {
            DWORD dwBytesWrittenThisTime;
            ResetEvent(m_overlapped.hEvent);
            if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped))
            {
                dwRet = GetLastError();
                if (dwRet == ERROR_IO_PENDING)
                {
                    if (m_fServer)
                    {
                        HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
                        dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
                    }
                    else
                    {
                        dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
                    }

                    if (dwRet == WAIT_OBJECT_0)
                    {
                        if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE))
                        {
                            dwRet = GetLastError();
                        }
                    }
                    else if (dwRet == WAIT_FAILED)
                    {
                        dwRet = GetLastError();
                    }
                    else
                    {
                        dwRet = ERROR_OPERATION_ABORTED;
                    }
                }
            }

            dwTotalBytesWritten += dwBytesWrittenThisTime;
        }

        return dwRet;
    }

    //
    // Called either by the client or server process.
    // Reads data from the pipe for a request or response.
    //
    DWORD ReadRequestData(RequestData* pReq)
    {
        DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData));

        if (dwRet == 0)
        {
            DWORD cbData = 0;
            for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
            {
                if (pReq->fields[i].vt == VT_LPWSTR)
                {
                    cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
                }
                else if (pReq->fields[i].vt == VT_STREAM)
                {
                    cbData += pReq->fields[i].cbValue;
                }
            }
            
            if (cbData > 0)
            {
                if (!CheckRequestDataBuf(cbData))
                {
                    return ERROR_OUTOFMEMORY;
                }

                dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData);
                if (dwRet == 0)
                {
                    DWORD dwOffset = 0;
                    for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
                    {
                        if (pReq->fields[i].vt == VT_LPWSTR)
                        {
                            LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset);
                            dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
                            pReq->fields[i].szValue = szTemp;
                        }
                        else if (pReq->fields[i].vt == VT_STREAM)
                        {
                            BYTE* sTemp = m_pBufReceive + dwOffset;
                            dwOffset += pReq->fields[i].cbValue;
                            pReq->fields[i].sValue = sTemp;
                        }
                    }
                }
            }
        }

        return dwRet;
    }

    //
    // Called only by the client process.
    // Wait for a response on the pipe. If no response is received before the timeout,
    // then give up and close the connection.
    //
    DWORD ReceiveResponse(RequestId id, RequestData* pResp)
    {
        RequestId responseId;
        DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId));
        if (dwRet == 0 && responseId != id)
        {
            dwRet = ERROR_OPERATION_ABORTED;
        }

        if (dwRet == 0)
        {
            dwRet = this->ReadRequestData(pResp);
        }

        return dwRet;
    }

    //
    // Called only by the server process's receive thread.
    // Try to complete and verify an asynchronous connection operation.
    //
    DWORD CompleteConnection()
    {
        DWORD dwRet = 0;
        if (m_fConnecting)
        {
            HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
            DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);

            if (dwWaitRes == WAIT_OBJECT_0)
            {
                m_fConnecting = false;

                DWORD dwUnused;
                if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE))
                {
                    m_fConnected = true;
                }
                else
                {
                    dwRet = GetLastError();
                }
            }
            else if (dwWaitRes == WAIT_FAILED)
            {
                CancelIo(m_hPipe);
                dwRet = GetLastError();
            }
            else
            {
                CancelIo(m_hPipe);
                dwRet = ERROR_OPERATION_ABORTED;
            }
        }
        return dwRet;
    }

    //
    // Called only by the server process.
    // Creates a named pipe instance and begins asynchronously waiting
    // for a connection from the client process.
    //
    DWORD ConnectPipeServer()
    {
        DWORD dwRet = 0;
        const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes
        m_hPipe = CreateNamedPipe(
            m_szPipeName,
            PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
            1, BUFSIZE, BUFSIZE, 0, NULL);
        if (m_hPipe == INVALID_HANDLE_VALUE)
        {
            m_hPipe = NULL;
            dwRet = GetLastError();
        }
        else if (ConnectNamedPipe(m_hPipe, &m_overlapped))
        {
            m_fConnected = true;
        }
        else
        {
            dwRet = GetLastError();

            if (dwRet == ERROR_PIPE_BUSY)
            {
                // All pipe instances are busy, so wait for a maximum of 20 seconds 
                dwRet = 0;
                if (WaitNamedPipe(m_szPipeName, 20000))
                {
                    m_fConnected = true;
                }
                else
                {
                    dwRet = GetLastError();
                }
            }
            
            if (dwRet == ERROR_IO_PENDING)
            {
                dwRet = 0;
                m_fConnecting = true;
            }
        }
        return dwRet;
    }

    //
    // Called only by the client process.
    // Attemps to open a connection to an existing named pipe instance
    // which should have already been created by the server process.
    //
    DWORD ConnectPipeClient()
    {
        DWORD dwRet = 0;
        m_hPipe = CreateFile(
            m_szPipeName, GENERIC_READ | GENERIC_WRITE, 
            0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
        if (m_hPipe != INVALID_HANDLE_VALUE)
        {
            m_fConnected = true;
        }
        else
        {
            m_hPipe = NULL;
            dwRet = GetLastError();
        }
        return dwRet;
    }

    //
    // Ensures that the request buffer is large enough to hold a request,
    // reallocating the buffer if necessary.
    // It will also reduce the buffer size if the previous allocation was very large.
    //
    BOOL CheckRequestDataBuf(DWORD cbBuf)
    {
        if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive))
        {
            if (m_pBufReceive != NULL)
            {
                SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
                delete[] m_pBufReceive;
            }
            m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf);
            m_pBufReceive = new BYTE[m_cbBufReceive];
            if (m_pBufReceive == NULL)
            {
                m_cbBufReceive = 0;
            }
        }
        return m_pBufReceive != NULL;
    }

private:

    // Name of this instance. 
    const wchar_t* m_szName;

    // "\\.\pipe\name"
    wchar_t* m_szPipeName;
    
    // Handle to the pipe instance.
    HANDLE m_hPipe;

    // Handle to the thread that receives requests.
    HANDLE m_hReceiveThread;

    // Handle to the event used to signal the receive thread to exit.
    HANDLE m_hReceiveStopEvent;

    // All pipe I/O is done in overlapped mode to avoid unintentional blocking.
    OVERLAPPED m_overlapped;
    
    // Dynamically-resized buffer for receiving requests.
    BYTE* m_pBufReceive;

    // Current size of the receive request buffer.
    DWORD m_cbBufReceive;

    // Dynamically-resized buffer for sending requests.
    wchar_t* m_pBufSend;

    // Current size of the send request buffer.
    DWORD m_cbBufSend;

    // True if this is the server process, false if this is the client process.
    const bool m_fServer;

    // True if an asynchronous connection operation is currently in progress.
    bool m_fConnecting;

    // True if the pipe is currently connected.
    bool m_fConnected;
};