
/**
 * @file: handle.c
 *
 * Implementace mechanismu handle pro ovladac objview.sys. Lze ji ale
 * pouzit i na jinych projektech pouzivajicich framework pro praci s
 * internimi datovymi strukturami spravce objektu.
 */

#include <ntddk.h>
#include "driver.h"
#include "hash_table.h"
#include "object.h"
#include "handles.h"

// Adresa struktury hashovaci tabulky
static PHASH_TABLE HandleTable = NULL;

// Hodnota, ktera bude pridelena nove vytvorenemu handle
static PVOID NextFreeHandle = (PVOID)0x4;

// Zamek nad tabulkou handle (hashovaci tabulkou)
static ERESOURCE HandleLock;


// Struktura objektu, ktere budou vkladany do hashovaci tabulky
typedef struct _TABLE_ENTRY
{
  // Hodnota handle
  PVOID Handle;
  // Adresa tela objektu exekutivy
  PVOID Object;
} TABLE_ENTRY, *PTABLE_ENTRY;


// Hashovaci funkce pro tabulku handle. Handle slouzi jako klic.
static ULONG32 HandleTableHashFunction(PVOID Handle)
{
  return (ULONG32)Handle;
}


// Porovnavaci funkce pro tabulku handle. Porovnava hodnotu klice (handle)
// s hodnotou ulozenou v zaznamu TABLE_ENTRY.
static BOOLEAN HandleTableCompareFunction(PVOID ObjectInTable, PVOID Handle)
{
  PTABLE_ENTRY Entry = (PTABLE_ENTRY)ObjectInTable;

  return Entry->Handle == Handle;
}


// Uklizeci funkce. Zodpovida za uvolneni objektu v pripade mazani cele
// hashovaci tabulky. Dekrementuje pocet odkazu na objekt exekutivy.
static VOID HandleTableFreeFunction(PVOID Item)
{
  PTABLE_ENTRY Entry = (PTABLE_ENTRY)Item;

  OBReleaseObjectPointer(Entry->Object);
  ExFreePoolWithTag(Entry, POOL_TAG);

  return;
}



/** Zamkne tabulku handle pro sdileny pristup.
 */
static VOID HandleTableLockShared(VOID)
{
  KeEnterCriticalRegion();
  ExAcquireResourceSharedLite(&HandleLock, TRUE);

  return;
}


/** Zamkne tabulku handle pro exkluzivni pristup.
 */
static VOID HandleTableLockExclusive(VOID)
{
  KeEnterCriticalRegion();
  ExAcquireResourceExclusiveLite(&HandleLock, TRUE);

  return;
}


/** Odemkne tabulku handle.
 */
static VOID HandleTableUnlock(VOID)
{
  ExReleaseResourceLite(&HandleLock);
  KeLeaveCriticalRegion();

  return;
}


/** Zjisti, zda se dane handle nachazi v tabulce. Jinak receno, zda 
 *  zadane handle existuje.
 *
 *  @param Handle Hodnota handle, kterou si volajici preje otestovat.
 *
 *  @return Funkce vraci TRUE, pokud dane handle existuje, a FALSE, pokud
 *  tomu tak neni.
 */
static BOOLEAN HandleExists(PVOID Handle)
{
  BOOLEAN Ret = FALSE;

  Ret = HashTableGet(HandleTable, Handle) != NULL;

  return Ret;
}



/** Vytvori nove handle.
 *
 * Rutina postupuje tak, ze prevede jmeno objektu zadaneho v prvnim parametru
 * na adresu jeho tela, cimz dojde i k zvyseni poctu referenci na dany objekt
 * o jednicku. Nasledne vytvori handle prislusne k tomuto objektu a
 * vlozi tuto informaci do tabulky.
 *
 * @param ObjectName Jmeno objektu, ke kteremu si volajici preje vytvorit
 * handle.
 * @param Handle Adresa promenne, do ktere funkce v pripade uspechu zapise
 * hodnotu noveho handle. V pripade neuspechu do promenne funkce zapise
 * hodnotu NULL.
 *
 * @return Funkce vraci kod NTSTATUS indikujici uspech ci neuspech operace.
 */
NTSTATUS HandleCreate(PUNICODE_STRING ObjectName, PVOID *Handle)
{
  PTABLE_ENTRY Entry = NULL;
  PVOID Object = NULL;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("handles.c: HandleCreate(ObjectName=%S; Handle=0x%p)\n", ObjectName->Buffer, Handle));

  *Handle = NULL;
  Object = OBObjectNameToPointer(ObjectName);
  if (Object != NULL) {
    Entry = (PTABLE_ENTRY)ExAllocatePoolWithTag(PagedPool, sizeof(TABLE_ENTRY), POOL_TAG);
	if (Entry != NULL) {
      Entry->Object = Object;
	  HandleTableLockExclusive();
	  Entry->Handle = NextFreeHandle;
      NextFreeHandle = (PCHAR)NextFreeHandle + 1;
      while (HandleExists(NextFreeHandle))
        NextFreeHandle = (PCHAR)NextFreeHandle + 1;

	  HandleTableUnlock();

      *Handle = Entry->Handle;
      Status = HashTableInsert(HandleTable, Entry, Entry->Handle);
      if (!NT_SUCCESS(Status)) {
        *Handle = NULL;
        ExFreePoolWithTag(Entry, POOL_TAG);
		OBReleaseObjectPointer(Object);
	  }
	} else {
	  Status = STATUS_INSUFFICIENT_RESOURCES;
	  OBReleaseObjectPointer(Object);
	}
  } else Status = STATUS_NOT_FOUND;

  KdPrint(("handles.c: HandleCreate():0x%x, *Handle=0x%p\n", Status, *Handle));
  return Status;
}


/** Zrusi zadane handle.
 *
 * Funkce provadi reverzni cinnost k rutine HandleCreate(). Odstrani zaznam
 * o danem handle z tabulky a snizi pocet referenci na dany objekt exekutivy
 * o jednicku.
 *
 * @param Handle Hodnota handle, ktere chce volajici odstranit.
 *
 * @return Funkce vraci kod NSTATUS indikujici uspech ci neuspech operace.
 */
NTSTATUS HandleClose(PVOID Handle)
{
  PVOID Object = NULL;
  PTABLE_ENTRY Entry = NULL;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("handles.c: HandleClose(Handle=0x%p)\n", Handle));

  HandleTableLockExclusive();
  Entry = (PTABLE_ENTRY)HashTableDelete(HandleTable, Handle);
  if (Entry != NULL) {
    HandleTableUnlock();
    HandleTableFreeFunction(Entry);
	Status = STATUS_SUCCESS;
  } else {
    HandleTableUnlock();
    Status = STATUS_NOT_FOUND;
  }

  KdPrint(("handles.c: HandleClose():0x%x\n", Status));
  return Status;
}


/** Prevede hodnotu handle na adresu tela objektu exekutivy.
 *
 * Pri prevadeni nedochazi k zvysovani poctu referenci na objekt.
 * Volajici si musi sam zajistit, aby s cilovym objektem pracoval pouze
 * pokud handle existuje, nebo zvysil pocet referenci tak, aby objekt
 * nemohl po zruseni handle samovolne zaniknout.
 *
 * @param Handle Hodnota handle, ktere si volajici preje prelozit.
 * @param Object Adresa promene, kam funkce v pripade uspechu zapise
 * adresu tela ciloveho objektu.
 *
 * @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 * operace.
 */
NTSTATUS HandleTranslate(PVOID Handle, PVOID *Object)
{
  PTABLE_ENTRY Entry = NULL;
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("handles.c: HandleTranslate(Handle=0x%p; Object=0x%p)\n", Handle, Object));

  HandleTableLockShared();
  Entry = HashTableGet(HandleTable, Handle);
  if (Entry != NULL) {
    *Object = Entry->Object;
	Status = STATUS_SUCCESS;
  } else Status = STATUS_NOT_FOUND;

  HandleTableUnlock();

  KdPrint(("handles.c: HandleTranslate():0x%x, *Object=0x%p\n", Status, *Object));
  return Status;
}



/** Inicializuje mechanismus handle.
 *
 *  Rutina vytvori tabulku handle a inicializuje jeji zamek.
 *
 *  @return Funkce vraci hodnotu NTSTATUS indikujici uspech ci neuspech
 *  operace.
 */
NTSTATUS HandlesInit(VOID)
{
  NTSTATUS Status = STATUS_UNSUCCESSFUL;
  KdPrint(("handles.c: HandlesInit()\n"));

  ExInitializeResourceLite(&HandleLock);
  Status = HashTableCreate(37, HandleTableHashFunction, HandleTableCompareFunction, HandleTableFreeFunction, &HandleTable);
  if (!NT_SUCCESS(Status))
    ExDeleteResourceLite(&HandleLock);

  KdPrint(("handles.c: HandlesInit():0x%x\n", Status));
  return Status;
}


/** Provadi uklid modulu implementujiciho vlastni mechanismus handle.
 *
 * Rutina odstrani tabulku handle z pameti a uvolni prostredky, ktere
 * pouziva jeji zamek.
 */
VOID HandlesFinit(VOID)
{
  KdPrint(("handles.c: HandlesFinit()\n"));
  
  HashTableDestroy(HandleTable);
  ExDeleteResourceLite(&HandleLock);

  KdPrint(("handles.c: HandlesFinit(-)\n"));
  return;
}
