#include "std.h"



// {6249D949-5263-4d6a-883C-78EFAF85D5E3}
static const GUID g_lhcHandleGUID =
{
    // This guid will be used to identify LCHHANDLE structures to
    // help prevent accessing invalid items
    0x6249d949, 0x5263, 0x4d6a,
    {
        0x88, 0x3c, 0x78, 0xef, 0xaf, 0x85, 0xd5, 0xe3
    }
};

// {40A71300-B2C7-4d4f-808F-52643110B329}
static const GUID g_lhcLibraryGUID =
{
    // This guid will be used to identify LIBRARY_NODE structures to
    // help prevent accessing invalid items
    0x40a71300, 0xb2c7, 0x4d4f,
    {
        0x80, 0x8f, 0x52, 0x64, 0x31, 0x10, 0xb3, 0x29
    }
};

PLIBRARY_NODE     g_pLibraryList = NULL;
PLHCSTRUCT        g_pObjectList = NULL;
CRITICAL_SECTION  g_csTableControl;


BOOL lhcpIsValidHandle(PLHCSTRUCT pObject);
PLIBRARY_NODE lhcpNewLibraryNode();
void lhcpFreeLibraryNode(PLIBRARY_NODE pNode);
PLHCSTRUCT lhcpNewObjectHandle();
void lhcpFreeObjectHandle(PLHCSTRUCT pNode);
BOOL lhcpIsValidHandle(PLHCSTRUCT pObject);
PWSTR lhcpGetExeDirectory();
PLHCSTRUCT lhcpCreateHandle(
    PLIBRARY_DESCRIPTOR   pLibrary,
    PLHCOBJECT_DESCRIPTOR pObject);
void lhcpDestroyHandle(PLHCSTRUCT pNode);


BOOL lhcpIsValidHandle(PLHCSTRUCT pObject)
{
    BOOL bResult;

    __try
    {
        bResult = IsEqualGUID(
            &g_lhcHandleGUID,
            &pObject->m_Secret);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(
            ERROR_INVALID_HANDLE);
        bResult = FALSE;
        goto Done;
    }

Done:
    return bResult;
}


PLIBRARY_NODE lhcpNewLibraryNode()
{
    //
    // Create the new node, zero out the memory and copy in the secret
    //
    PLIBRARY_NODE pNode = malloc(
        sizeof(LIBRARY_NODE));

    if (pNode!=NULL)
    {
        ZeroMemory(
            pNode,
            sizeof(LIBRARY_NODE));
        CopyMemory(
            &pNode->m_Secret,
            &g_lhcLibraryGUID,
            sizeof(GUID));
    }

    return pNode;
}


void lhcpFreeLibraryNode(PLIBRARY_NODE pNode)
{
    ZeroMemory(
        pNode,
        sizeof(LIBRARY_NODE));
    free(
        pNode);
}


PLHCSTRUCT lhcpNewObjectHandle()
{
    //
    // Create the new node, zero out the memory and copy in the secret
    //

    PLHCSTRUCT pNode = malloc(
        sizeof(LHCSTRUCT));

    if (pNode!=NULL)
    {
        ZeroMemory(
            pNode,
            sizeof(LHCSTRUCT));
        CopyMemory(
            &pNode->m_Secret,
            &g_lhcHandleGUID,
            sizeof(GUID));
    }

    return pNode;
}


void lhcpFreeObjectHandle(PLHCSTRUCT pNode)
{
    ZeroMemory(
        pNode,
        sizeof(LHCSTRUCT));
    free(
        pNode);
}


PWSTR lhcpGetExeDirectory()
{
    DWORD dwSize = 64;
    PWSTR pszBuffer = NULL;
    PWSTR pszReturn;
    DWORD dwResult;
    BOOL bResult;
    PWSTR pszLastBackslash;

    do
    {
        pszBuffer = malloc(
            dwSize * sizeof(WCHAR));

        if (NULL==pszBuffer)
        {
            SetLastError(
                ERROR_NOT_ENOUGH_MEMORY);
            goto Error;
        }

        dwResult = GetModuleFileNameW(
            NULL,
            pszBuffer,
            dwSize);

        if (0==dwResult)
        {
            goto Error;
        }

        if (dwSize==dwResult)  // INSUFFICIENT_BUFFER
        {
            dwSize *= 2;            // Double the buffer length
            free(
                pszBuffer);
            pszBuffer = NULL;
            dwResult = 0;
        }
    } while (0==dwResult && dwSize<=65536);

    if (dwSize>65536)
    {
        SetLastError(
            ERROR_INSUFFICIENT_BUFFER);
        goto Error;
    }

    pszLastBackslash = wcsrchr(
        pszBuffer,
        L'\\');

    if (NULL==pszLastBackslash)
    {
        SetLastError(
            ERROR_GEN_FAILURE);
        goto Error;
    }

    pszLastBackslash++;
    *pszLastBackslash = L'\0';

    pszReturn = malloc(
        (wcslen(pszBuffer)+MAX_PATH+1)*sizeof(WCHAR));

    if (NULL==pszReturn)
    {
        SetLastError(
            ERROR_NOT_ENOUGH_MEMORY);
        goto Error;
    }

    wcscpy(
        pszReturn,
        pszBuffer);

    free(
        pszBuffer);

    return pszReturn;

Error:
    if (pszBuffer!=NULL)
    {
        free(
            pszBuffer);
    }
    return NULL;
}


PLHCSTRUCT lhcpCreateHandle(
    PLIBRARY_DESCRIPTOR   pLibrary,
    PLHCOBJECT_DESCRIPTOR pObject)
{
    PLHCSTRUCT pNode = lhcpNewObjectHandle();

    if (pNode!=NULL)
    {
        EnterCriticalSection(
            &g_csTableControl);

        pNode->m_pObject = pObject;
        pNode->m_pLibrary = pLibrary;
        pNode->m_pNext = g_pObjectList;
        pNode->m_ppThis = &g_pObjectList;

        if (pNode->m_pNext!=NULL)
        {
            pNode->m_pNext->m_ppThis = &pNode->m_pNext;
        }

        g_pObjectList = pNode;

        LeaveCriticalSection(
            &g_csTableControl);
    }
    else
    {
        SetLastError(
            ERROR_NOT_ENOUGH_MEMORY);
    }

    return pNode;
}


void lhcpDestroyHandle(PLHCSTRUCT pNode)
{
    EnterCriticalSection(
        &g_csTableControl);

    // Remove this node from the list of handles.

    *(pNode->m_ppThis) = pNode->m_pNext;
    if (pNode->m_pNext!=NULL)
    {
        pNode->m_pNext->m_ppThis = pNode->m_ppThis;
    }

    lhcpFreeObjectHandle(
        pNode);            // Invalidates the structure and frees the memory

    LeaveCriticalSection(
        &g_csTableControl);

}


BOOL lhcInitialize()
{
    PWSTR pszPath = NULL;
    PWSTR pszFileName;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA FindData;
    BOOL bResult;
    PLIBRARY_DESCRIPTOR pLibrary = NULL;
    PLIBRARY_NODE pNode = NULL;
    WCHAR pszLibraryName[64];

    InitializeCriticalSection(
        &g_csTableControl);

    pszPath = lhcpGetExeDirectory();

    if (NULL==pszPath)
    {
        goto Error;
    }

    pszFileName = pszPath + wcslen(pszPath);

    wcscat(
        pszFileName,
        L"*.lhc");

    hFind = FindFirstFileW(
        pszPath,
        &FindData);

    bResult = (hFind!=INVALID_HANDLE_VALUE);

    if (!bResult)
    {
        goto Error;
    }

    while (bResult)
    {
        wcscpy(
            pszFileName,
            FindData.cFileName);

        pLibrary = lhclLoadLibrary(
            pszPath);

        if (pLibrary==NULL)
        {
            wprintf(
                L"Unable to load (%u).\n",
                pszFileName,
                GetLastError());
        }
        else
        {
            lhclGetLibraryName(
                pLibrary,
                pszLibraryName,
                64);

            wprintf(
                L"Loaded %s library.\n",
                pszLibraryName);

            pNode = lhcpNewLibraryNode();

            if (NULL==pNode)
            {
                SetLastError(
                    ERROR_NOT_ENOUGH_MEMORY);
                goto Error;
                // Out of memory is fatal
            }

            pNode->m_pLibrary = pLibrary;
            pNode->m_pNext = g_pLibraryList;
            g_pLibraryList = pNode;
            pNode = NULL;
            pLibrary = NULL;
        }

        bResult = FindNextFileW(
            hFind,
            &FindData);
    }

    FindClose(hFind);

    free(pszPath);

    return g_pLibraryList!=NULL;

Error:
    if (pLibrary!=NULL)
    {
        lhclFreeLibrary(pLibrary);
    }
    if (pszPath!=NULL)
    {
        free(pszPath);
    }
    if (pNode!=NULL)
    {
        free(pszPath);
    }
    if (hFind!=INVALID_HANDLE_VALUE)
    {
        FindClose(hFind);
    }
    // We need to unload the libraries that successfully loaded.
    lhcFinalize();

    return FALSE;
}



void lhcFinalize()
{
    PLIBRARY_NODE pNode;
    WCHAR pszLibraryName[64];

    while (g_pObjectList!=NULL)
    {
        lhcClose(
            g_pObjectList);
    }

    while (g_pLibraryList!=NULL)
    {
        pNode = g_pLibraryList;
        g_pLibraryList = g_pLibraryList->m_pNext;

        lhclGetLibraryName(
            pNode->m_pLibrary,
            pszLibraryName,
            64);

        lhclFreeLibrary(
            pNode->m_pLibrary);

        wprintf(
            L"Unloaded %s library.\n",
            pszLibraryName);

        lhcpFreeLibraryNode(
            pNode);
    }
}


LHCHANDLE lhcOpen(PCWSTR pcszPortSpec)
{
    PLIBRARY_NODE pLibraryNode = g_pLibraryList;
    PLHCOBJECT_DESCRIPTOR pObject = NULL;
    PLHCSTRUCT hObject;

    while (pLibraryNode!=NULL && pObject==NULL)
    {
        // Try libraries one at a time until one opens successfully
        pObject = lhclOpen(
            pLibraryNode->m_pLibrary,
            pcszPortSpec);

        if (!pObject)
        {
            pLibraryNode = pLibraryNode->m_pNext;
        }
    }

    if (!pObject)
    {
        goto Error;
    }

    hObject = lhcpCreateHandle(
        pLibraryNode->m_pLibrary,
        pObject);

    if (hObject==NULL)
    {
        SetLastError(
            ERROR_NOT_ENOUGH_MEMORY);
        goto Error;
    }

    return hObject;

Error:
    if (pObject!=NULL)
    {
        lhclClose(
            pLibraryNode->m_pLibrary,
            pObject);
    }

    return NULL;
}


BOOL lhcRead(
    LHCHANDLE hObject,
    PVOID pBuffer,
    DWORD dwBufferSize,
    PDWORD pdwBytesRead)
{
    PLIBRARY_DESCRIPTOR   pLibrary;
    PLHCOBJECT_DESCRIPTOR pObject;

    if (!lhcpIsValidHandle(hObject))
    {
        goto Error;
    }

    EnterCriticalSection(
        &g_csTableControl);

    // Ensure consistent information
    pLibrary = ((PLHCSTRUCT)hObject)->m_pLibrary;
    pObject = ((PLHCSTRUCT)hObject)->m_pObject;

    LeaveCriticalSection(
        &g_csTableControl);

    return lhclRead(
        pLibrary,
        pObject,
        pBuffer,
        dwBufferSize,
        pdwBytesRead);

Error:
    return FALSE;
}


BOOL lhcWrite(
    LHCHANDLE hObject,
    PVOID pBuffer,
    DWORD dwBufferSize)
{
    PLIBRARY_DESCRIPTOR   pLibrary;
    PLHCOBJECT_DESCRIPTOR pObject;

    if (!lhcpIsValidHandle(hObject))
    {
        goto Error;
    }

    // Ensure consistent information by using the critical section
    EnterCriticalSection(
        &g_csTableControl);

    // Ensure consistent information
    pLibrary = ((PLHCSTRUCT)hObject)->m_pLibrary;
    pObject = ((PLHCSTRUCT)hObject)->m_pObject;

    LeaveCriticalSection(
        &g_csTableControl);

    return lhclWrite(
        pLibrary,
        pObject,
        pBuffer,
        dwBufferSize);

Error:
    return FALSE;
}


BOOL lhcClose(
    LHCHANDLE hObject)
{
    PLIBRARY_DESCRIPTOR   pLibrary;
    PLHCOBJECT_DESCRIPTOR pObject;

    if (!lhcpIsValidHandle(hObject))
    {
        goto Error;
    }

    // Ensure consistent information by using the critical section

    EnterCriticalSection(
        &g_csTableControl);

    // Ensure consistent information
    pLibrary = ((PLHCSTRUCT)hObject)->m_pLibrary;
    pObject = ((PLHCSTRUCT)hObject)->m_pObject;

    lhcpDestroyHandle(
        hObject);

    LeaveCriticalSection(
        &g_csTableControl);

    return lhclClose(
        pLibrary,
        pObject);

Error:
    return FALSE;

}