#include <precomp.h>
#include "tracing.h"
#include "utils.h"
#include "intflist.h"
#include "intfhdl.h"

DWORD 
OpenIntfHandle(
    LPWSTR wszGuid,
    PHANDLE pIntfHdl)
{
    DWORD       dwErr;
    PHASH_NODE  pNode;
    PHSH_HANDLE pHshHandle;

    DbgPrint((TRC_TRACK,"[OpenIntfHandle(%S)", wszGuid));

    // the caller is supposed to expect the handle in return
    // if he doesn't, return with INVALID_PARAMETER.
    if (pIntfHdl == NULL)
    {
        dwErr = ERROR_INVALID_PARAMETER;
        goto exit;
    }

    // lock the hash, to mutual exclude concurrent calls
    EnterCriticalSection(&g_hshHandles.csMutex);

    // query the hash for the guid.
    dwErr = HshQueryObjectRef(
                g_hshHandles.pRoot,
                wszGuid,
                &pNode);

    // if the guid in the hash, we already have the handle opened..
    if (dwErr == ERROR_SUCCESS)
    {
        // return it to the caller and bump up the reference counter.
        // the caller is still supposed to close this handle when it is
        // no longer needed
        pHshHandle = (PHSH_HANDLE)pNode->pObject;
        pHshHandle->nRefCount++;
        *pIntfHdl = pHshHandle->hdlInterf;
        DbgPrint((TRC_GENERIC, "OpenIntfHandle -> 0x%x (cached)", *pIntfHdl));
    }
    // if the guid is not in the hash, we need to open a new handle for
    // this guid.
    else if (dwErr == ERROR_FILE_NOT_FOUND)
    {
        HANDLE      IntfHdl = INVALID_HANDLE_VALUE;
        DWORD       dwDummy;

        dwErr = ERROR_SUCCESS;

        // first allocate memory for the HSH_HANDLE object.
        // if this fails no need to go further to ndisuio
        pHshHandle = MemCAlloc(sizeof(HSH_HANDLE));
        if (pHshHandle == NULL)
            dwErr = GetLastError();

        // if everything looks fine so far
        if (dwErr == ERROR_SUCCESS)
        {
            // create the handle
            IntfHdl = CreateFileA(
                        "\\\\.\\\\Ndisuio",
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                        INVALID_HANDLE_VALUE);
            if (IntfHdl == INVALID_HANDLE_VALUE)
            {
                dwErr = GetLastError();
                DbgPrint((TRC_ERR,"CreateFileA failed with %d", dwErr));
            }
        }

        // if still fine so far
        if (dwErr == ERROR_SUCCESS)
        {
            // attempt to open the handle
            if (!DeviceIoControl(
                    IntfHdl,
                    IOCTL_NDISUIO_OPEN_DEVICE,
                    (LPVOID)wszGuid,
                    wcslen(wszGuid)*sizeof(WCHAR),
                    NULL,
                    0,
                    &dwDummy,
                    NULL))
            {
                dwErr = GetLastError();
                DbgPrint((TRC_ERR,"IOCTL_NDISUIO_OPEN_DEVICE failed with %d", dwErr));
            }
        }

        // if finally we're ok here, 
        if (dwErr == ERROR_SUCCESS)
        {
            // set up the HSH_HANDLE structure and insert it in the hash
            pHshHandle->hdlInterf = IntfHdl;
            pHshHandle->nRefCount = 1;

            dwErr = HshInsertObjectRef(
                        g_hshHandles.pRoot,
                        wszGuid,
                        pHshHandle,
                        &g_hshHandles.pRoot);
        }

        // if ok at the end, return the handle to the caller
        if (dwErr == ERROR_SUCCESS)
        {
            DbgPrint((TRC_GENERIC, "OpenIntfHandle -> 0x%x (new)", IntfHdl));
            *pIntfHdl = IntfHdl;   
        }
        // otherwise clear up all resources
        else
        {
            if (IntfHdl != INVALID_HANDLE_VALUE)
                CloseHandle(IntfHdl);
            MemFree(pHshHandle);
        }
    }
    // out of critical section
    LeaveCriticalSection(&g_hshHandles.csMutex);

exit:
    DbgPrint((TRC_TRACK,"OpenIntfHandle]=%d", dwErr));
    return dwErr;
}

DWORD
CloseIntfHandle(
    LPWSTR wszGuid)
{
    DWORD       dwErr;
    PHASH_NODE  pNode;

    DbgPrint((TRC_TRACK,"[CloseIntfHandle(%S)", wszGuid));

    // lock the hash
    EnterCriticalSection(&g_hshHandles.csMutex);

    // query the hash for the guid.
    dwErr = HshQueryObjectRef(
                g_hshHandles.pRoot,
                wszGuid,
                &pNode);
    // the object should be found.. but who knows
    if (dwErr == ERROR_SUCCESS)
    {
        PHSH_HANDLE pHshHandle;

        // the hash refuses to hash in NULL pObjects, so pHshHandle is guaranteed to be not NULL
        pHshHandle = (PHSH_HANDLE)pNode->pObject;
        // HshHandles are hashed in with a refCount of 1, and whenever we remove a handle
        // if the ref count reaches 0 we delete it entirely.
        // Having here a <=0 ref count is completely wrong.
        DbgAssert((pHshHandle->nRefCount > 0, "Corrupted nRefCount %d", pHshHandle->nRefCount));
        pHshHandle->nRefCount--;

        // if the ref count reached 0, remove the HSH_HANDLE from the hash..
        if (pHshHandle->nRefCount == 0)
        {
            dwErr = HshRemoveObjectRef(
                        g_hshHandles.pRoot,
                        pNode,
                        &pHshHandle,
                        &g_hshHandles.pRoot);
            if (dwErr == ERROR_SUCCESS)
            {
                // .. and clear all associated resources
                DbgPrint((TRC_GENERIC,"CloseIntfHandle -> 0x%x (closed)", pHshHandle->hdlInterf));
                CloseHandle(pHshHandle->hdlInterf);
                MemFree(pHshHandle);
            }
        }
        else
        {
                DbgPrint((TRC_GENERIC,"CloseIntfHandle -> 0x%x (deref)", pHshHandle->hdlInterf));
        }
    }

    LeaveCriticalSection(&g_hshHandles.csMutex);
    
    DbgPrint((TRC_TRACK,"CloseIntfHandle]=%d", dwErr));

    return dwErr;
}