/*
 * PROJECT:     ReactOS Automatic Testing Utility
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Class that manages an unidirectional anonymous byte stream pipe
 * COPYRIGHT:   Copyright 2015 Thomas Faber (thomas.faber@reactos.org)
 *              Copyright 2019 Colin Finck (colin@reactos.org)
 */

#include "precomp.h"

LONG CPipe::m_lPipeCount = 0;


/**
 * Constructs a CPipe object and initializes read and write handles.
 */
CPipe::CPipe()
{
    SECURITY_ATTRIBUTES SecurityAttributes;

    SecurityAttributes.nLength = sizeof(SecurityAttributes);
    SecurityAttributes.bInheritHandle = TRUE;
    SecurityAttributes.lpSecurityDescriptor = NULL;

    // Construct a unique pipe name.
    WCHAR wszPipeName[MAX_PATH];
    InterlockedIncrement(&m_lPipeCount);
    swprintf(wszPipeName, L"\\\\.\\pipe\\TestmanPipe%ld", m_lPipeCount);

    // Create a named pipe with the default settings, but overlapped (asynchronous) operations.
    // Latter feature is why we can't simply use CreatePipe.
    const DWORD dwDefaultBufferSize = 4096;
    const DWORD dwDefaultTimeoutMilliseconds = 120000;

    m_hReadPipe = CreateNamedPipeW(wszPipeName,
        PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
        1,
        dwDefaultBufferSize,
        dwDefaultBufferSize,
        dwDefaultTimeoutMilliseconds,
        &SecurityAttributes);
    if (m_hReadPipe == INVALID_HANDLE_VALUE)
    {
        FATAL("CreateNamedPipe failed\n");
    }

    // Use CreateFileW to get the write handle to the pipe.
    // Writing is done synchronously, so no FILE_FLAG_OVERLAPPED here!
    m_hWritePipe = CreateFileW(wszPipeName,
        GENERIC_WRITE,
        0,
        &SecurityAttributes,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (m_hWritePipe == INVALID_HANDLE_VALUE)
    {
        FATAL("CreateFileW failed\n");
    }

    // Prepare the OVERLAPPED structure for reading.
    ZeroMemory(&m_ReadOverlapped, sizeof(m_ReadOverlapped));
    m_ReadOverlapped.hEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
    if (!m_ReadOverlapped.hEvent)
    {
        FATAL("CreateEvent failed\n");
    }
}

/**
 * Destructs a CPipe object and closes all open handles.
 */
CPipe::~CPipe()
{
    if (m_hReadPipe)
        CloseHandle(m_hReadPipe);
    if (m_hWritePipe)
        CloseHandle(m_hWritePipe);
}

/**
 * Closes a CPipe's read pipe, leaving the write pipe unchanged.
 */
void
CPipe::CloseReadPipe()
{
    if (!m_hReadPipe)
        FATAL("Trying to close already closed read pipe\n");
    CloseHandle(m_hReadPipe);
    m_hReadPipe = NULL;
}

/**
 * Closes a CPipe's write pipe, leaving the read pipe unchanged.
 */
void
CPipe::CloseWritePipe()
{
    if (!m_hWritePipe)
        FATAL("Trying to close already closed write pipe\n");
    CloseHandle(m_hWritePipe);
    m_hWritePipe = NULL;
}

/**
 * Reads data from a pipe without advancing the read offset and/or retrieves information about available data.
 *
 * This function must not be called after CloseReadPipe.
 *
 * @param Buffer
 * An optional buffer to read pipe data into.
 *
 * @param BufferSize
 * The size of the buffer specified in Buffer, or 0 if no read should be performed.
 *
 * @param BytesRead
 * On return, the number of bytes actually read from the pipe into Buffer.
 *
 * @param TotalBytesAvailable
 * On return, the total number of bytes available to read from the pipe.
 *
 * @return
 * True on success, false on failure; call GetLastError for error information.
 *
 * @see PeekNamedPipe
 */
bool
CPipe::Peek(PVOID Buffer, DWORD BufferSize, PDWORD BytesRead, PDWORD TotalBytesAvailable)
{
    if (!m_hReadPipe)
        FATAL("Trying to peek from a closed read pipe\n");

    return PeekNamedPipe(m_hReadPipe, Buffer, BufferSize, BytesRead, TotalBytesAvailable, NULL);
}

/**
 * Reads data from the read pipe, advancing the read offset accordingly.
 *
 * This function must not be called after CloseReadPipe.
 *
 * @param Buffer
 * Buffer to read pipe data into.
 *
 * @param NumberOfBytesToRead
 * The number of bytes to read into Buffer.
 *
 * @param NumberOfBytesRead
 * On return, the number of bytes actually read from the pipe into Buffer.
 *
 * @return
 * Returns a Win32 error code. Expected error codes include:
 *   - ERROR_SUCCESS:     The read has completed successfully.
 *   - WAIT_TIMEOUT:      The given timeout has elapsed before any data was read.
 *   - ERROR_BROKEN_PIPE: The other end of the pipe has been closed.
 *
 * @see ReadFile
 */
DWORD
CPipe::Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead, DWORD TimeoutMilliseconds)
{
    if (!m_hReadPipe)
    {
        FATAL("Trying to read from a closed read pipe\n");
    }

    if (ReadFile(m_hReadPipe, Buffer, NumberOfBytesToRead, NumberOfBytesRead, &m_ReadOverlapped))
    {
        // The asynchronous read request could be satisfied immediately.
        return ERROR_SUCCESS;
    }

    DWORD dwLastError = GetLastError();
    if (dwLastError == ERROR_IO_PENDING)
    {
        // The asynchronous read request could not be satisfied immediately, so wait for it with the given timeout.
        DWORD dwWaitResult = WaitForSingleObject(m_ReadOverlapped.hEvent, TimeoutMilliseconds);
        if (dwWaitResult == WAIT_OBJECT_0)
        {
            // Fill NumberOfBytesRead.
            if (GetOverlappedResult(m_hReadPipe, &m_ReadOverlapped, NumberOfBytesRead, FALSE))
            {
                // We successfully read NumberOfBytesRead bytes.
                return ERROR_SUCCESS;
            }

            dwLastError = GetLastError();
            if (dwLastError == ERROR_BROKEN_PIPE)
            {
                // The other end of the pipe has been closed.
                return ERROR_BROKEN_PIPE;
            }
            else
            {
                // An unexpected error.
                FATAL("GetOverlappedResult failed\n");
            }
        }
        else
        {
            // This may be WAIT_TIMEOUT or an unexpected error.
            return dwWaitResult;
        }
    }
    else
    {
        // This may be ERROR_BROKEN_PIPE or an unexpected error.
        return dwLastError;
    }
}

/**
 * Writes data to the write pipe.
 *
 * This function must not be called after CloseWritePipe.
 *
 * @param Buffer
 * Buffer containing the data to write.
 *
 * @param NumberOfBytesToWrite
 * The number of bytes to write to the pipe from Buffer.
 *
 * @param NumberOfBytesWritten
 * On return, the number of bytes actually written to the pipe.
 *
 * @return
 * True on success, false on failure; call GetLastError for error information.
 *
 * @see WriteFile
 */
bool
CPipe::Write(LPCVOID Buffer, DWORD NumberOfBytesToWrite, PDWORD NumberOfBytesWritten)
{
    if (!m_hWritePipe)
        FATAL("Trying to write to a closed write pipe\n");

    return WriteFile(m_hWritePipe, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, NULL);
}