/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#	include "pipeimpl.h"

#ifndef _INC_MALLOC
#	include <malloc.h>
#endif

#ifndef _INC_TCHAR
#	ifdef UNICODE
#		define _UNICODE
#	endif
#	include <tchar.h>
#endif

const TCHAR PIPE_NAME_PREFIX_MAPPING[] = TEXT("PIPE_FILE_MAPPING_");
const TCHAR PIPE_NAME_PREFIX_SYNCHRONIZE[] = TEXT("PIPE_SYNCHRONIZE_MUTEX_");
const TCHAR PIPE_NAME_PREFIX_CONNECTION[] = TEXT("PIPE_CONNECTION_SEMAPHORE_");

const DWORD PIPE_BUFFER_SIZE = 4096;


//============================================================================
//	PipeData
//============================================================================

struct PipeData
{
    DWORD	dwProcessId;
    HANDLE	hReadPipe;
    HANDLE	hWritePipe;
};

//============================================================================
//	Pipe
//============================================================================

#ifdef UNICODE
#define Pipe		PipeW
#define ClientPipe	ClientPipeW
#define ServerPipe	ServerPipeW
#else
#define Pipe		PipeA
#define ClientPipe	ClientPipeA
#define ServerPipe	ServerPipeA
#endif

class Pipe
{
protected:
    HANDLE	m_hReadPipe;	// Handle to use for reading
    HANDLE	m_hWritePipe;	// Handle to use for writing

    Pipe( HANDLE hReadPipe, HANDLE hWritePipe );

    static HANDLE CreatePipeDataMutex( LPCTSTR lpName, BOOL bInitialOwner );
    static HANDLE CreatePipeDataMapping( LPCTSTR lpName );
    static HANDLE OpenPipeDataMapping( LPCTSTR lpName );
    static HANDLE CreatePipeConnectionSemaphore( LPCTSTR lpName, LONG lInitialCount, LONG lMaximumcount );

public:
    Pipe( const Pipe& );
    const Pipe& operator = ( const Pipe& );
    virtual ~Pipe();

    virtual bool Close();
    virtual bool Write( LPCVOID lpBuffer, DWORD dwBytesToWrite, LPDWORD lpBytesWritten, bool bWait = true );
    virtual bool Read( LPVOID lpBuffer, DWORD dwBytesToRead, LPDWORD lpBytesRead, bool bWait = true );

    virtual Pipe *AcceptConnection()
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return NULL;
    }

    void * operator new( size_t nBytes )
    {
        return HeapAlloc( GetProcessHeap(), 0, nBytes );
    }

    void operator delete( void *ptr )
    {
        HeapFree( GetProcessHeap(), 0, ptr );
    }

    bool is() const
    {
        return (FALSE != HeapValidate( GetProcessHeap(), 0, this ));
    }

};

//============================================================================
//	ClientPipe
//============================================================================

class ClientPipe : public Pipe
{
protected:
    ClientPipe( HANDLE hReadPipe, HANDLE hWritePipe );
public:
    static ClientPipe* Create( LPCTSTR lpName );
};

//============================================================================
//	ServerPipe
//============================================================================

class ServerPipe : public Pipe
{
protected:
    HANDLE	m_hMapping;
    HANDLE	m_hSynchronize;
    LPTSTR	m_lpName;

    ServerPipe( LPCTSTR lpName, HANDLE hMapping, HANDLE hSynchronize, HANDLE hReadPipe, HANDLE hWritePipe );
public:
    virtual ~ServerPipe();

    static ServerPipe *Create( LPCTSTR lpName );

    virtual Pipe *AcceptConnection();
};

HANDLE	Pipe::CreatePipeDataMapping( LPCTSTR lpName )
{
    HANDLE	hMapping = NULL;
    LPTSTR	lpMappingName = (LPTSTR)alloca( _tcslen(lpName) * sizeof(TCHAR) + sizeof(PIPE_NAME_PREFIX_MAPPING) );

    if ( lpMappingName )
    {
        _tcscpy( lpMappingName, PIPE_NAME_PREFIX_MAPPING );
        _tcscat( lpMappingName, lpName );

        LPTSTR	lpMappingFileName = (LPTSTR)alloca( MAX_PATH * sizeof(TCHAR) );

        if ( lpMappingFileName )
        {
            DWORD	nChars = GetTempPath( MAX_PATH, lpMappingFileName );

            if ( MAX_PATH + _tcslen(lpName) < nChars + 1 )
            {
                lpMappingFileName = (LPTSTR)alloca( (nChars + 1 + _tcslen(lpName)) * sizeof(TCHAR) );
                if ( lpMappingFileName )
                    nChars = GetTempPath( nChars, lpMappingFileName );
                else
                {
                    nChars = 0;
                    SetLastError( ERROR_NOT_ENOUGH_MEMORY );
                }
            }

            if ( nChars )
            {
                _tcscat( lpMappingFileName, lpMappingName );

                HANDLE hFile = CreateFile( 
                    lpMappingFileName, 
                    GENERIC_READ | GENERIC_WRITE, 
                    FILE_SHARE_READ | FILE_SHARE_WRITE, 
                    NULL, 
                    OPEN_ALWAYS, 
                    FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, 
                    NULL );

                if ( IsValidHandle(hFile) )
                {
                    hMapping = CreateFileMapping(
                        (HANDLE)hFile,
                        (LPSECURITY_ATTRIBUTES)NULL,
                        PAGE_READWRITE,
                        0,
                        sizeof(PipeData),
                        lpMappingName );

                    CloseHandle( hFile );
                }
            }
        }
        else
            SetLastError( ERROR_NOT_ENOUGH_MEMORY );
    }

    return hMapping;
}

HANDLE	Pipe::OpenPipeDataMapping( LPCTSTR lpName )
{
    HANDLE	hMapping = NULL;
    LPTSTR	lpMappingName = (LPTSTR)alloca( _tcslen(lpName) * sizeof(TCHAR) + sizeof(PIPE_NAME_PREFIX_MAPPING) );

    if ( lpMappingName )
    {
        _tcscpy( lpMappingName, PIPE_NAME_PREFIX_MAPPING );
        _tcscat( lpMappingName, lpName );

        hMapping = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, lpMappingName );
    }

    return hMapping;
}

HANDLE	Pipe::CreatePipeDataMutex( LPCTSTR lpName, BOOL bInitialOwner )
{
    HANDLE	hMutex = NULL;
    LPTSTR	lpMutexName = (LPTSTR)alloca( _tcslen(lpName) * sizeof(TCHAR) + sizeof(PIPE_NAME_PREFIX_SYNCHRONIZE) );

    if ( lpMutexName )
    {
        _tcscpy( lpMutexName, PIPE_NAME_PREFIX_SYNCHRONIZE );
        _tcscat( lpMutexName, lpName );

        hMutex = CreateMutex( NULL, bInitialOwner, lpMutexName );
    }

    return hMutex;
}

HANDLE Pipe::CreatePipeConnectionSemaphore( LPCTSTR lpName, LONG lInitialCount, LONG lMaximumCount )
{
    HANDLE	hSemaphore = NULL;
    LPTSTR	lpSemaphoreName = (LPTSTR)alloca( _tcslen(lpName) * sizeof(TCHAR) + sizeof(PIPE_NAME_PREFIX_CONNECTION) );

    if ( lpSemaphoreName )
    {
        _tcscpy( lpSemaphoreName, PIPE_NAME_PREFIX_CONNECTION );
        _tcscat( lpSemaphoreName, lpName );

        hSemaphore = CreateSemaphore( NULL, lInitialCount, lMaximumCount, lpSemaphoreName );
    }

    return hSemaphore;
}


//----------------------------------------------------------------------------
//	Pipe copy ctor
//----------------------------------------------------------------------------

Pipe::Pipe( const Pipe& rPipe ) :
m_hReadPipe( INVALID_HANDLE_VALUE ),
m_hWritePipe( INVALID_HANDLE_VALUE )
{
    DuplicateHandle( 
        GetCurrentProcess(),
        rPipe.m_hReadPipe,
        GetCurrentProcess(),
        &m_hReadPipe,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );

    DuplicateHandle( 
        GetCurrentProcess(),
        rPipe.m_hWritePipe,
        GetCurrentProcess(),
        &m_hWritePipe,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );
}

//----------------------------------------------------------------------------
//	Pipe assignment operator
//----------------------------------------------------------------------------

const Pipe& Pipe::operator = ( const Pipe& rPipe )
{
    Close();

    DuplicateHandle( 
        GetCurrentProcess(),
        rPipe.m_hReadPipe,
        GetCurrentProcess(),
        &m_hReadPipe,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );

    DuplicateHandle( 
        GetCurrentProcess(),
        rPipe.m_hWritePipe,
        GetCurrentProcess(),
        &m_hWritePipe,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );
    
    return *this;
}

//----------------------------------------------------------------------------
//	Pipe ctor
//----------------------------------------------------------------------------

Pipe::Pipe( HANDLE hReadPipe, HANDLE hWritePipe ) :
m_hReadPipe( INVALID_HANDLE_VALUE ),
m_hWritePipe( INVALID_HANDLE_VALUE )
{
    DuplicateHandle( 
        GetCurrentProcess(),
        hReadPipe,
        GetCurrentProcess(),
        &m_hReadPipe,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );

    DuplicateHandle( 
        GetCurrentProcess(),
        hWritePipe,
        GetCurrentProcess(),
        &m_hWritePipe,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );
}

//----------------------------------------------------------------------------
//	Pipe dtor
//----------------------------------------------------------------------------

Pipe::~Pipe()
{
    Close();
}

//----------------------------------------------------------------------------
//	Pipe Close
//----------------------------------------------------------------------------

bool Pipe::Close()
{
    bool	fSuccess = false;	// Assume failure

    if ( IsValidHandle(m_hReadPipe) )
    {
        CloseHandle( m_hReadPipe );
        m_hReadPipe = INVALID_HANDLE_VALUE;
    }

    if ( IsValidHandle(m_hWritePipe) )
    {
        CloseHandle( m_hWritePipe );
        m_hWritePipe = INVALID_HANDLE_VALUE;
    }

    return fSuccess;
}

//----------------------------------------------------------------------------
//	Pipe Write
//----------------------------------------------------------------------------

bool Pipe::Write( LPCVOID lpBuffer, DWORD dwBytesToWrite, LPDWORD lpBytesWritten, bool bWait )
{
    DWORD	dwBytesAvailable = 0;
    BOOL	fSuccess = TRUE;

    if ( !bWait )
        fSuccess = PeekNamedPipe( m_hReadPipe, NULL, 0, NULL, &dwBytesAvailable, NULL );

    if ( fSuccess )
    {
        if ( !bWait && dwBytesToWrite > PIPE_BUFFER_SIZE - dwBytesAvailable )
            dwBytesToWrite = PIPE_BUFFER_SIZE - dwBytesAvailable ;

        return !!WriteFile( m_hWritePipe, lpBuffer, dwBytesToWrite, lpBytesWritten, NULL );
    }

    return false;
}

//----------------------------------------------------------------------------
//	Pipe Read
//----------------------------------------------------------------------------

bool Pipe::Read( LPVOID lpBuffer, DWORD dwBytesToRead, LPDWORD lpBytesRead, bool bWait )
{
    DWORD	dwBytesAvailable = 0;
    BOOL	fSuccess = TRUE;

    if ( !bWait )
        fSuccess = PeekNamedPipe( m_hReadPipe, NULL, 0, NULL, &dwBytesAvailable, NULL );

    if ( fSuccess )
    {
        if ( bWait || dwBytesAvailable )
            return !!ReadFile( m_hReadPipe, lpBuffer, dwBytesToRead, lpBytesRead, NULL );
        else
        {
            *lpBytesRead = 0;
            return true;
        }
    }

    return false;
}



//----------------------------------------------------------------------------
//	Client pipe dtor
//----------------------------------------------------------------------------

ClientPipe::ClientPipe( HANDLE hReadPipe, HANDLE hWritePipe ) : Pipe( hReadPipe, hWritePipe )
{
}

//----------------------------------------------------------------------------
//	Client pipe creation
//----------------------------------------------------------------------------

ClientPipe *ClientPipe::Create( LPCTSTR lpName )
{
    ClientPipe	*pPipe = NULL;	// Assume failure

    HANDLE	hMapping = OpenPipeDataMapping( lpName );

    if ( IsValidHandle(hMapping) )
    {
        PipeData	*pData = (PipeData*)MapViewOfFile( hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0 );

        if ( pData )
        {
            HANDLE	hSourceProcess = OpenProcess( PROCESS_DUP_HANDLE, FALSE, pData->dwProcessId );

            if ( IsValidHandle(hSourceProcess) )
            {
                BOOL fSuccess;
                HANDLE	hReadPipe = INVALID_HANDLE_VALUE, hWritePipe = INVALID_HANDLE_VALUE;
                
                fSuccess = DuplicateHandle( 
                    hSourceProcess,
                    pData->hReadPipe,
                    GetCurrentProcess(),
                    &hReadPipe,
                    0,
                    FALSE,
                    DUPLICATE_SAME_ACCESS );

                fSuccess = fSuccess && DuplicateHandle( 
                    hSourceProcess,
                    pData->hWritePipe,
                    GetCurrentProcess(),
                    &hWritePipe,
                    0,
                    FALSE,
                    DUPLICATE_SAME_ACCESS );

                if ( fSuccess )
                    pPipe = new ClientPipe( hReadPipe, hWritePipe );

                if ( IsValidHandle(hWritePipe) )
                    CloseHandle( hWritePipe );

                if ( IsValidHandle(hReadPipe) )
                    CloseHandle( hReadPipe );

                HANDLE	hConnectionRequest = CreatePipeConnectionSemaphore( lpName, 0, 1 );

                ReleaseSemaphore( hConnectionRequest, 1, NULL );

                CloseHandle( hConnectionRequest );
                
                CloseHandle( hSourceProcess );
            }

            UnmapViewOfFile( pData );
        }

        CloseHandle( hMapping );
    }

    return pPipe;
}



//----------------------------------------------------------------------------
//	ServerPipe ctor
//----------------------------------------------------------------------------

ServerPipe::ServerPipe( LPCTSTR	lpName, HANDLE hMapping, HANDLE hSynchronize, HANDLE hReadPipe, HANDLE hWritePipe ) : Pipe( hReadPipe, hWritePipe ),
m_hMapping( NULL ),
m_hSynchronize( NULL ),
m_lpName( NULL )
{
    DuplicateHandle( 
        GetCurrentProcess(),
        hMapping,
        GetCurrentProcess(),
        &m_hMapping,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS );

    DuplicateHandle(
        GetCurrentProcess(),
        hSynchronize,
        GetCurrentProcess(),
        &m_hSynchronize,
        0,
        FALSE,
        DUPLICATE_SAME_ACCESS
        );
    m_lpName = new TCHAR[_tcslen(lpName) + 1];
    if ( m_lpName )
        _tcscpy( m_lpName, lpName );
}

//----------------------------------------------------------------------------
//	ServerPipe dtor
//----------------------------------------------------------------------------

ServerPipe::~ServerPipe()
{
    if ( IsValidHandle(m_hMapping) )
        CloseHandle( m_hMapping );
    if ( m_lpName )
        delete[]m_lpName;
}

//----------------------------------------------------------------------------
//	ServerPipe AcceptConnection
//----------------------------------------------------------------------------

Pipe *ServerPipe::AcceptConnection()
{
    Pipe	*pPipe = NULL;	// Assume failure;

    HANDLE	hConnectionRequest = CreatePipeConnectionSemaphore( m_lpName, 0, 1 );

    if ( WAIT_OBJECT_0 == WaitForSingleObject( hConnectionRequest, INFINITE ) )
    {
        pPipe = new Pipe( *this );
        Close();

        // Create new inbound Pipe

        HANDLE	hClientWritePipe = NULL, hServerReadPipe = NULL;

        BOOL	fSuccess = CreatePipe( &hServerReadPipe, &hClientWritePipe, NULL, PIPE_BUFFER_SIZE );


        if ( fSuccess )
        {
            // Create outbound pipe

            HANDLE	hClientReadPipe = NULL, hServerWritePipe = NULL;

            if ( CreatePipe( &hClientReadPipe, &hServerWritePipe, NULL, PIPE_BUFFER_SIZE ) )
            {
                m_hReadPipe = hServerReadPipe;
                m_hWritePipe = hServerWritePipe;

                PipeData	*pData = (PipeData *)MapViewOfFile( m_hMapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(PipeData) );

                HANDLE	hSynchronize = CreatePipeDataMutex( m_lpName, TRUE );

                CloseHandle( pData->hReadPipe );
                CloseHandle( pData->hWritePipe );

                pData->hReadPipe = hClientReadPipe;
                pData->hWritePipe = hClientWritePipe;

                ReleaseMutex( hSynchronize );

                CloseHandle( hSynchronize );

            }
            else
            {
                CloseHandle( hClientWritePipe );
                CloseHandle( hServerWritePipe );	
            }
        }

        ReleaseMutex( hConnectionRequest );
    }

    CloseHandle( hConnectionRequest );

    return pPipe;
}

//----------------------------------------------------------------------------
//	Pipe creation
//----------------------------------------------------------------------------

ServerPipe *ServerPipe::Create( LPCTSTR lpName )
{
    ServerPipe	*pPipe = NULL;

    HANDLE	hMapping = CreatePipeDataMapping( lpName );

    if ( IsValidHandle(hMapping) )
    {
        if ( ERROR_FILE_EXISTS != GetLastError() )
        {
            HANDLE	hSynchronize = CreatePipeDataMutex( lpName, FALSE);
            
            WaitForSingleObject( hSynchronize, INFINITE );

            PipeData	*pData = (PipeData*)MapViewOfFile( hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0 );

            if ( pData )
            {

                // Initialize pipe data

                pData->dwProcessId = 0;
                pData->hReadPipe = NULL;
                pData->hWritePipe = NULL;

                // Create inbound pipe

                HANDLE	hServerReadPipe = NULL, hClientWritePipe = NULL;

                BOOL	fSuccess = CreatePipe( &hServerReadPipe, &hClientWritePipe, NULL, PIPE_BUFFER_SIZE );

                if ( fSuccess )
                {
                    // Create outbound pipe

                    HANDLE	hServerWritePipe = NULL, hClientReadPipe = NULL;

                    fSuccess = CreatePipe( &hClientReadPipe, &hServerWritePipe, NULL, PIPE_BUFFER_SIZE );

                    if ( fSuccess )
                    {
                        pData->dwProcessId = GetCurrentProcessId();
                        pData->hReadPipe = hClientReadPipe;
                        pData->hWritePipe = hClientWritePipe;
                        pPipe = new ServerPipe( lpName, hMapping, hSynchronize, hServerReadPipe, hServerWritePipe );

                        CloseHandle( hServerWritePipe );
                        CloseHandle( hServerReadPipe );
                    }
                    else
                    {
                        CloseHandle( hServerReadPipe );
                        CloseHandle( hClientWritePipe );
                    }
                }

                UnmapViewOfFile( pData );
            }

            ReleaseMutex( hSynchronize );
            CloseHandle( hSynchronize );
        }

        CloseHandle( hMapping );
    }

    return pPipe;
}


//----------------------------------------------------------------------------
//	C style API
//----------------------------------------------------------------------------

const TCHAR	LOCAL_PIPE_PREFIX[] = TEXT("\\\\.\\PIPE\\" );

extern "C" HANDLE WINAPI CreateSimplePipe( LPCTSTR lpName )
{
    int	nPrefixLen = _tcslen( LOCAL_PIPE_PREFIX );
    if ( 0 == _tcsnicmp( lpName, LOCAL_PIPE_PREFIX, nPrefixLen ) )
        lpName += nPrefixLen;
    return (HANDLE)ServerPipe::Create( lpName );
}

extern "C" HANDLE WINAPI OpenSimplePipe( LPCTSTR lpName )
{
    int	nPrefixLen = _tcslen( LOCAL_PIPE_PREFIX );
    if ( 0 == _tcsnicmp( lpName, LOCAL_PIPE_PREFIX, nPrefixLen ) )
        lpName += nPrefixLen;
    return (HANDLE)ClientPipe::Create( lpName );
}

extern "C" HANDLE WINAPI AcceptSimplePipeConnection( HANDLE hPipe )
{
    Pipe	*pPipe = (Pipe *)hPipe;

    if ( pPipe->is() )
        return (HANDLE)pPipe->AcceptConnection();
    else
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return NULL;
    }
}

extern "C" BOOL WINAPI WaitForSimplePipe( LPCTSTR /*lpName*/, DWORD /*dwTimeOut*/ )
{
    return FALSE;
}

extern "C" BOOL WINAPI WriteSimplePipe( HANDLE hPipe, LPCVOID lpBuffer, DWORD dwBytesToWrite, LPDWORD lpBytesWritten, BOOL bWait )
{
    Pipe	*pPipe = (Pipe *)hPipe;

    if ( pPipe->is() )
        return pPipe->Write( lpBuffer, dwBytesToWrite, lpBytesWritten, bWait );
    else
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return FALSE;
    }
}

extern "C" BOOL WINAPI ReadSimplePipe( HANDLE hPipe, LPVOID lpBuffer, DWORD dwBytesToRead, LPDWORD lpBytesRead, BOOL bWait )
{
    Pipe	*pPipe = (Pipe *)hPipe;

    if ( pPipe->is() )
        return pPipe->Read( lpBuffer, dwBytesToRead, lpBytesRead, bWait );
    else
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return FALSE;
    }
}

extern "C" BOOL WINAPI CloseSimplePipe( HANDLE hPipe )
{
    Pipe	*pPipe = (Pipe *)hPipe;

    if ( pPipe->is() )
    {
        delete pPipe;
        return TRUE;
    }
    else
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return FALSE;
    }
}

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