/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "system.h"

#include <osl/pipe.h>
#include <osl/diagnose.h>
#include <osl/thread.h>
#include <osl/mutex.h>
#include <osl/conditn.h>
#include <osl/interlck.h>
#include <osl/process.h>
#include <rtl/alloc.h>
#include <sal/log.hxx>
#include <o3tl/char16_t2wchar_t.hxx>

#include <cassert>
#include <string.h>

#define PIPESYSTEM      "\\\\.\\pipe\\"
#define PIPEPREFIX      "OSL_PIPE_"

struct oslPipeImpl
{
    oslInterlockedCount  m_Reference;
    HANDLE               m_File;
    HANDLE               m_NamedObject;
    PSECURITY_ATTRIBUTES m_Security;
    HANDLE               m_ReadEvent;
    HANDLE               m_WriteEvent;
    HANDLE               m_AcceptEvent;
    rtl_uString*         m_Name;
    oslPipeError         m_Error;
    bool                 m_bClosed;
};

static oslPipe osl_createPipeImpl(void)
{
    oslPipe pPipe;

    pPipe = static_cast< oslPipe >(rtl_allocateZeroMemory(sizeof(struct oslPipeImpl)));

    pPipe->m_bClosed = false;
    pPipe->m_Reference = 0;
    pPipe->m_Name = nullptr;
    pPipe->m_File = INVALID_HANDLE_VALUE;
    pPipe->m_NamedObject = nullptr;

    pPipe->m_ReadEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    pPipe->m_WriteEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    pPipe->m_AcceptEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);

    return pPipe;
}

static void osl_destroyPipeImpl(oslPipe pPipe)
{
    if (!pPipe)
        return;

    if (pPipe->m_NamedObject)
        CloseHandle(pPipe->m_NamedObject);

    if (pPipe->m_Security)
    {
        free(pPipe->m_Security->lpSecurityDescriptor);
        free(pPipe->m_Security);
    }

    CloseHandle(pPipe->m_ReadEvent);
    CloseHandle(pPipe->m_WriteEvent);
    CloseHandle(pPipe->m_AcceptEvent);

    if (pPipe->m_Name)
        rtl_uString_release(pPipe->m_Name);

    free(pPipe);
}

oslPipe SAL_CALL osl_createPipe(rtl_uString *strPipeName, oslPipeOptions Options,
                       oslSecurity Security)
{
    rtl_uString* name = nullptr;
    rtl_uString* path = nullptr;
    rtl_uString* temp = nullptr;
    oslPipe pPipe;

    PSECURITY_ATTRIBUTES pSecAttr = nullptr;

    rtl_uString_newFromAscii(&path, PIPESYSTEM);
    rtl_uString_newFromAscii(&name, PIPEPREFIX);

    if (Security)
    {
        rtl_uString *Ident = nullptr;
        rtl_uString *Delim = nullptr;

        OSL_VERIFY(osl_getUserIdent(Security, &Ident));
        rtl_uString_newFromAscii(&Delim, "_");

        rtl_uString_newConcat(&temp, name, Ident);
        rtl_uString_newConcat(&name, temp, Delim);

        rtl_uString_release(Ident);
        rtl_uString_release(Delim);
    }
    else
    {
        if (Options & osl_Pipe_CREATE)
        {
            PSECURITY_DESCRIPTOR pSecDesc;

            pSecDesc = static_cast< PSECURITY_DESCRIPTOR >(malloc(SECURITY_DESCRIPTOR_MIN_LENGTH));
            assert(pSecDesc); // Don't handle OOM conditions

            /* add a NULL disc. ACL to the security descriptor */
            OSL_VERIFY(InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION));
            OSL_VERIFY(SetSecurityDescriptorDacl(pSecDesc, TRUE, nullptr, FALSE));

            pSecAttr = static_cast< PSECURITY_ATTRIBUTES >(malloc(sizeof(SECURITY_ATTRIBUTES)));
            assert(pSecAttr); // Don't handle OOM conditions
            pSecAttr->nLength = sizeof(SECURITY_ATTRIBUTES);
            pSecAttr->lpSecurityDescriptor = pSecDesc;
            pSecAttr->bInheritHandle = TRUE;
        }
    }

    rtl_uString_assign(&temp, name);
    rtl_uString_newConcat(&name, temp, strPipeName);

    /* alloc memory */
    pPipe = osl_createPipeImpl();

    assert(pPipe);  // if osl_createPipeImpl() cannot init. a new pipe, this is a failure

    osl_atomic_increment(&(pPipe->m_Reference));

    /* build system pipe name */
    rtl_uString_assign(&temp, path);
    rtl_uString_newConcat(&path, temp, name);
    rtl_uString_release(temp);
    temp = nullptr;

    if (Options & osl_Pipe_CREATE)
    {
        SetLastError(ERROR_SUCCESS);

        pPipe->m_NamedObject = CreateMutexW(nullptr, FALSE, o3tl::toW(name->buffer));

        if (pPipe->m_NamedObject)
        {
            if (GetLastError() != ERROR_ALREADY_EXISTS)
            {
                pPipe->m_Security = pSecAttr;
                rtl_uString_assign(&pPipe->m_Name, name);

                /* try to open system pipe */
                pPipe->m_File = CreateNamedPipeW(
                    o3tl::toW(path->buffer),
                    PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                    PIPE_WAIT | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
                    PIPE_UNLIMITED_INSTANCES,
                    4096, 4096,
                    NMPWAIT_WAIT_FOREVER,
                    pPipe->m_Security);

                if (pPipe->m_File != INVALID_HANDLE_VALUE)
                {
                    rtl_uString_release( name );
                    rtl_uString_release( path );

                    return pPipe;
                }
            }
            else
            {
                CloseHandle(pPipe->m_NamedObject);
                pPipe->m_NamedObject = nullptr;
            }
        }
    }
    else
    {
        bool bPipeAvailable;

        do
        {
            /* free instance should be available first */
            bPipeAvailable = WaitNamedPipeW(o3tl::toW(path->buffer), NMPWAIT_WAIT_FOREVER);

            /* first try to open system pipe */
            if (bPipeAvailable)
            {
                pPipe->m_File = CreateFileW(
                    o3tl::toW(path->buffer),
                    GENERIC_READ|GENERIC_WRITE,
                    FILE_SHARE_READ | FILE_SHARE_WRITE,
                    nullptr,
                    OPEN_EXISTING,
                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                    nullptr);

                if (pPipe->m_File != INVALID_HANDLE_VALUE)
                {
                    // We got it !
                    rtl_uString_release(name);
                    rtl_uString_release(path);

                    // We should try to transfer our privilege to become foreground process
                    // to the other process, so that it may show popups (otherwise, they might
                    // be blocked by SPI_GETFOREGROUNDLOCKTIMEOUT setting -
                    // see SystemParametersInfo function at MSDN
                    ULONG ServerProcessId = 0;
                    if (GetNamedPipeServerProcessId(pPipe->m_File, &ServerProcessId))
                        AllowSetForegroundWindow(ServerProcessId);

                    return pPipe;
                }
                else
                {
                    // Pipe instance maybe caught by another client -> try again
                }
            }
        } while (bPipeAvailable);
    }

    /* if we reach here something went wrong */
    osl_destroyPipeImpl(pPipe);

    return nullptr;
}

void SAL_CALL osl_acquirePipe(oslPipe pPipe)
{
    osl_atomic_increment(&(pPipe->m_Reference));
}

void SAL_CALL osl_releasePipe(oslPipe pPipe)
{
    if (!pPipe)
        return;

    if (osl_atomic_decrement(&(pPipe->m_Reference)) == 0)
    {
        if (!pPipe->m_bClosed)
            osl_closePipe(pPipe);

        osl_destroyPipeImpl(pPipe);
    }
}

void SAL_CALL osl_closePipe(oslPipe pPipe)
{
    if (pPipe && !pPipe->m_bClosed)
    {
        pPipe->m_bClosed = true;
        /* if we have a system pipe close it */
        if (pPipe->m_File != INVALID_HANDLE_VALUE)
        {
            DisconnectNamedPipe(pPipe->m_File);
            CloseHandle(pPipe->m_File);
        }
    }
}

oslPipe SAL_CALL osl_acceptPipe(oslPipe pPipe)
{
    oslPipe pAcceptedPipe = nullptr;

    OVERLAPPED os = {};

    DWORD nBytesTransferred;
    rtl_uString* path = nullptr;
    rtl_uString* temp = nullptr;

    SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_acceptPipe: invalid pipe");
    if (!pPipe)
        return nullptr;

    SAL_WARN_IF(pPipe->m_File == INVALID_HANDLE_VALUE, "sal.osl.pipe", "osl_acceptPipe: invalid handle");

    os.hEvent = pPipe->m_AcceptEvent;
    ResetEvent(pPipe->m_AcceptEvent);

    if (!ConnectNamedPipe(pPipe->m_File, &os))
    {
        switch (GetLastError())
        {
            case ERROR_PIPE_CONNECTED:  // Client already connected to pipe
            case ERROR_NO_DATA:         // Client was connected but has already closed pipe end
                                        // should only appear in nonblocking mode but in fact does
                                        // in blocking asynchronous mode.
                break;
            case ERROR_PIPE_LISTENING:  // Only for nonblocking mode but see ERROR_NO_DATA
            case ERROR_IO_PENDING:      // This is normal if not client is connected yet
            case ERROR_MORE_DATA:       // Should not happen
                // blocking call to accept
                if( !GetOverlappedResult(pPipe->m_File, &os, &nBytesTransferred, TRUE))
                {
                    // Possible error could be that between ConnectNamedPipe and
                    // GetOverlappedResult a connect took place.

                    switch (GetLastError())
                    {
                    case ERROR_PIPE_CONNECTED:  // Pipe was already connected
                    case ERROR_NO_DATA:         // Pipe was connected but client has already closed -> ver fast client ;-)
                        break;                  // Everything's fine !!!
                    default:
                        // Something went wrong
                        return nullptr;
                    }
                }
                break;
            default:                    // All other error say that somethings going wrong.
                return nullptr;
        }
    }

    pAcceptedPipe = osl_createPipeImpl();
    assert(pAcceptedPipe);  // should never be the case that an oslPipe cannot be initialized

    osl_atomic_increment(&(pAcceptedPipe->m_Reference));
    rtl_uString_assign(&pAcceptedPipe->m_Name, pPipe->m_Name);
    pAcceptedPipe->m_File = pPipe->m_File;

    rtl_uString_newFromAscii(&temp, PIPESYSTEM);
    rtl_uString_newConcat(&path, temp, pPipe->m_Name);
    rtl_uString_release(temp);

    // prepare for next accept
    pPipe->m_File =
        CreateNamedPipeW(o3tl::toW(path->buffer),
            PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
            PIPE_WAIT | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
            PIPE_UNLIMITED_INSTANCES,
            4096, 4096,
            NMPWAIT_WAIT_FOREVER,
            pAcceptedPipe->m_Security);
    rtl_uString_release(path);

    return pAcceptedPipe;
}

sal_Int32 SAL_CALL osl_receivePipe(oslPipe pPipe,
                        void* pBuffer,
                        sal_Int32 BytesToRead)
{
    DWORD nBytes;
    OVERLAPPED os = {};

    assert(pPipe);

    os.hEvent = pPipe->m_ReadEvent;

    ResetEvent(pPipe->m_ReadEvent);

    if (!ReadFile(pPipe->m_File, pBuffer, BytesToRead, &nBytes, &os) &&
        ((GetLastError() != ERROR_IO_PENDING) ||
         !GetOverlappedResult(pPipe->m_File, &os, &nBytes, TRUE)))
    {
        DWORD lastError = GetLastError();

        if (lastError == ERROR_MORE_DATA)
        {
            nBytes = BytesToRead;
        }
        else
        {
            if (lastError == ERROR_PIPE_NOT_CONNECTED)
                nBytes = 0;
            else
                nBytes = DWORD(-1);

            pPipe->m_Error = osl_Pipe_E_ConnectionAbort;
        }
    }

    return nBytes;
}

sal_Int32 SAL_CALL osl_sendPipe(oslPipe pPipe,
                       const void* pBuffer,
                       sal_Int32 BytesToSend)
{
    DWORD nBytes;
    OVERLAPPED os = {};

    assert(pPipe);

    os.hEvent = pPipe->m_WriteEvent;
    ResetEvent(pPipe->m_WriteEvent);

    if (!WriteFile(pPipe->m_File, pBuffer, BytesToSend, &nBytes, &os) &&
        ((GetLastError() != ERROR_IO_PENDING) ||
          !GetOverlappedResult(pPipe->m_File, &os, &nBytes, TRUE)))
    {
        if (GetLastError() == ERROR_PIPE_NOT_CONNECTED)
            nBytes = 0;
        else
            nBytes = DWORD(-1);

        pPipe->m_Error = osl_Pipe_E_ConnectionAbort;
    }

    return nBytes;
}

sal_Int32 SAL_CALL osl_writePipe(oslPipe pPipe, const void *pBuffer , sal_Int32 n)
{
    /* loop until all desired bytes were send or an error occurred */
    sal_Int32 BytesSend = 0;
    sal_Int32 BytesToSend = n;

    SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_writePipe: invalid pipe");
    while (BytesToSend > 0)
    {
        sal_Int32 RetVal;

        RetVal= osl_sendPipe(pPipe, pBuffer, BytesToSend);

        /* error occurred? */
        if (RetVal <= 0)
            break;

        BytesToSend -= RetVal;
        BytesSend += RetVal;
        pBuffer= static_cast< char const* >(pBuffer) + RetVal;
    }

    return BytesSend;
}

sal_Int32 SAL_CALL osl_readPipe(oslPipe pPipe, void *pBuffer, sal_Int32 n)
{
    /* loop until all desired bytes were read or an error occurred */
    sal_Int32 BytesRead = 0;
    sal_Int32 BytesToRead = n;

    SAL_WARN_IF(!pPipe, "sal.osl.pipe", "osl_readPipe: invalid pipe");
    while (BytesToRead > 0)
    {
        sal_Int32 RetVal;
        RetVal= osl_receivePipe(pPipe, pBuffer, BytesToRead);

        /* error occurred? */
        if(RetVal <= 0)
            break;

        BytesToRead -= RetVal;
        BytesRead += RetVal;
        pBuffer= static_cast< char* >(pBuffer) + RetVal;
    }
    return BytesRead;
}

oslPipeError SAL_CALL osl_getLastPipeError(oslPipe pPipe)
{
    oslPipeError Error;

    if (pPipe)
    {
        Error = pPipe->m_Error;
        pPipe->m_Error = osl_Pipe_E_None;
    }
    else
    {
        Error = osl_Pipe_E_NotFound;
    }

    return Error;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
