/*
Pipe.cpp
Written by Matthew Fisher

A pipe is a connection between two programs, possibly on different computers.
*/

#include <aclapi.h>

Pipe::Pipe()
{
    _Handle = NULL;
}

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

void Pipe::ClosePipe()
{
    if(_Handle != NULL)
    {
        FlushFileBuffers(_Handle);
        DisconnectNamedPipe(_Handle);
        CloseHandle(_Handle);
        _Handle = NULL;
    }
}

void Pipe::CreatePipe(const String &PipeName)
{
    ClosePipe();
    const UINT PipeBufferSize = 100000;

    DWORD dwRes;
    PSID pEveryoneSID = NULL, pAdminSID = NULL;
    PACL pACL = NULL;
    PSECURITY_DESCRIPTOR pSD = NULL;
    EXPLICIT_ACCESS ea[1];
    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
    SECURITY_ATTRIBUTES Attributes;
    HKEY hkSub = NULL;

    // Create a well-known SID for the Everyone group.
    BOOL Success = AllocateAndInitializeSid(&SIDAuthWorld, 1,
                                            SECURITY_WORLD_RID,
                                            0, 0, 0, 0, 0, 0, 0,
                                            &pEveryoneSID);
    Assert(Success != FALSE, "AllocateAndInitializeSid failed in Pipe::CreatePipe");

    // Initialize an EXPLICIT_ACCESS structure for an ACE.
    // The ACE will allow Everyone read access to the key.
    ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
    ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
    ea[0].grfAccessMode = SET_ACCESS;
    ea[0].grfInheritance= NO_INHERITANCE;
    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    ea[0].Trustee.ptstrName  = (LPTSTR) pEveryoneSID;

    // Create a new ACL that contains the new ACEs.
    dwRes = SetEntriesInAcl(1, ea, NULL, &pACL);
    Assert(dwRes == ERROR_SUCCESS, "SetEntriesInAcl failed in Pipe::CreatePipe");

    // Initialize a security descriptor.  
    pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
    Assert(pSD != NULL, "LocalAlloc failed in Pipe::CreatePipe");
    
    Success = InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION);
    Assert(Success != FALSE, "InitializeSecurityDescriptor failed in Pipe::CreatePipe");
    
    // Add the ACL to the security descriptor. 
    Success = SetSecurityDescriptorDacl(pSD, 
                TRUE,     // bDaclPresent flag
                pACL, 
                FALSE);
    Assert(Success != FALSE, "SetSecurityDescriptorDacl failed in Pipe::CreatePipe");
    
    // Initialize a security attributes structure.
    Attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
    Attributes.lpSecurityDescriptor = pSD;
    Attributes.bInheritHandle = FALSE;

    String FullPipeName = String("\\\\.\\pipe\\") + PipeName;
    _Handle = CreateNamedPipe( 
        FullPipeName.CString(),     // pipe name 
        PIPE_ACCESS_DUPLEX,         // read/write access 
        PIPE_TYPE_MESSAGE |         // message type pipe 
        PIPE_READMODE_MESSAGE |     // message-read mode 
        PIPE_WAIT,                  // blocking mode 
        PIPE_UNLIMITED_INSTANCES,   // max. instances  
        PipeBufferSize,             // output buffer size 
        PipeBufferSize,             // input buffer size 
        NMPWAIT_USE_DEFAULT_WAIT,   // client time-out 
        &Attributes);               // default security attribute
    Assert(_Handle != INVALID_HANDLE_VALUE, "CreateNamedPipe failed in Pipe::CreatePipe");

    //
    // Block until a connection comes in
    //
    BOOL Connected = (ConnectNamedPipe(_Handle, NULL) != 0);
    Assert(Connected != FALSE, "ConnectNamedPipe failed in Pipe::CreatePipe");
}

void Pipe::ConnectToLocalPipe(const String &PipeName)
{
    ConnectToPipe(String("\\\\.\\pipe\\") + PipeName);
}

void Pipe::ConnectToPipe(const String &PipeName)
{
    ClosePipe();
    bool Done = false;
    while(!Done)
    {
        _Handle = CreateFile( 
            PipeName.CString(),           // pipe name 
            GENERIC_READ |                // read and write access 
            GENERIC_WRITE, 
            0,                            // no sharing 
            NULL,                         // default security attributes
            OPEN_EXISTING,                // opens existing pipe 
            0,                            // default attributes 
            NULL);                        // no template file
        if(_Handle != INVALID_HANDLE_VALUE)
        {
            Done = true;
        }
        Sleep(100);
    }

    DWORD Mode = PIPE_READMODE_MESSAGE;
    BOOL Success = SetNamedPipeHandleState( 
        _Handle,  // pipe handle 
        &Mode,    // new pipe mode 
        NULL,     // don't set maximum bytes 
        NULL);    // don't set maximum time 
    Assert(Success != FALSE, "SetNamedPipeHandleState failed in Pipe::ConnectToPipe");
}

bool Pipe::MessagePresent()
{
    Assert(_Handle != NULL, "Pipe invalid in Pipe::MessagePresent");
    DWORD BytesReady  = 0;
    BOOL Success = PeekNamedPipe(
        _Handle,
        NULL,
        0,
        NULL,
        &BytesReady,
        NULL);
    Assert(Success != FALSE, "PeekNamedPipe failed in Pipe::MessagePresent");
    return (BytesReady > 0);
}

bool Pipe::ReadMessage(Vector<BYTE> &Message)
{
    Assert(_Handle != NULL, "Pipe invalid in Pipe::ReadMessage");
    DWORD BytesReady  = 0;
    BOOL Success = PeekNamedPipe(
        _Handle,
        NULL,
        0,
        NULL,
        &BytesReady,
        NULL);
    Assert(Success != FALSE, "PeekNamedPipe failed in Pipe::ReadMessage");
    Message.Allocate(BytesReady);
    if(BytesReady == 0)
    {
        return false;
    }

    DWORD BytesRead;
    Success = ReadFile( 
        _Handle,                // handle to pipe 
        Message.CArray(),       // buffer to receive data 
        Message.Length(),       // size of buffer 
        &BytesRead,             // number of bytes read 
        NULL);                  // not overlapped I/O 
    Assert(Success != FALSE && BytesRead > 0, "ReadFile failed in Pipe::ReadMessage");
    return true;
}

void Pipe::SendMessage(const Vector<BYTE> &Message)
{
    SendMessage(Message.CArray(), Message.Length());
}

void Pipe::SendMessage(const BYTE *Message, UINT MessageLength)
{
    Assert(_Handle != NULL, "Pipe invalid in Pipe::SendMessage");

    DWORD BytesWritten;
    BOOL Success = WriteFile( 
        _Handle,               // pipe handle
        Message,               // message
        MessageLength,         // message length
        &BytesWritten,         // bytes written
        NULL);                 // not overlapped
    Assert(Success != FALSE, "WriteFile failed in Pipe::ReadMessage");
    Assert(BytesWritten == MessageLength, "WriteFile failed to send entire message in Pipe::ReadMessage");
}

UINT Pipe::ActiveInstances()
{
    Assert(_Handle != NULL, "Pipe invalid in Pipe::ActiveInstances");
    DWORD Instances;
    BOOL Success = GetNamedPipeHandleState(
        _Handle,
        NULL,
        &Instances,
        NULL,
        NULL,
        NULL,
        0);
    Assert(Success != FALSE, "GetNamedPipeHandleState failed in Pipe::ActiveInstances");
    return Instances;
}

String Pipe::UserName()
{
    Assert(_Handle != NULL, "Pipe invalid in Pipe::UserName");
    char Buffer[512];
    BOOL Success = GetNamedPipeHandleState(
        _Handle,
        NULL,
        NULL,
        NULL,
        NULL,
        Buffer,
        512);
    Assert(Success != FALSE, "GetNamedPipeHandleState failed in Pipe::UserName");
    return String(Buffer);
}