/****************************************************************************
 *
 *
 *  INTEL Corporation Prorietary Information
 *
 *  This listing is supplied under the terms of a license agreement
 *  with INTEL Corporation and may not be copied nor disclosed except
 *  in accordance with the terms of that agreement.
 *
 *	Copyright (c) 1996 Intel Corporation.
 *
 *
 *	Abstract:
 *
 *	Notes:
 *
 ***************************************************************************/

#ifndef __TSTABLE_H
#define __TSTABLE_H

typedef struct _LOCK_ENTRY
{
	HANDLE        hLock;
	int           iLockCount;
	BOOL          bCleanup,
								bDeleted;
	WORD          wNextFree,
								wUniqueID;

} LOCK_ENTRY, *PLOCK_ENTRY;


// definition of an invalid ID
#define TSTABLE_INVALID_ID				(DWORD) 0xFFFFFFFF

// return codes that the callback function used in conjunction with EnumerateEntries can return
const DWORD CALLBACK_CONTINUE                = 1;
const DWORD CALLBACK_ABORT                   = 2;
const DWORD CALLBACK_DELETE_ENTRY            = 3;
const DWORD CALLBACK_DELETE_ENTRY_AND_OBJECT = 4;



// used in call to Lock
#define TSTABLE_INVALID_UNIQUE_ID            (WORD) 0xFFFF
#define TSTABLE_INVALID_INDEX                (WORD) 0xFFFF

// This is a compare function that we aren't using right now.  It
// will be useful in the future if there is a reason to search
// the table

typedef INT (*ENTRY_COMPARE) (LPVOID ptr1, LPVOID ptr2);


template <class EntryData> class TSTable
{
typedef DWORD (*TABLE_CALLBACK) (EntryData* ptr, LPVOID context);

public:
	           TSTable         (WORD            _size);
	          ~TSTable         ();
	BOOL       Resize          (WORD            wNewSize);
	BOOL       CreateAndLock   (EntryData*      pEntryData,
															LPDWORD         lpdwID);
	BOOL       Validate        (DWORD_PTR       dwID);
	EntryData *Lock            (DWORD_PTR       dwID,
															DWORD           timeout = INFINITE);
	BOOL       Unlock          (DWORD_PTR       dwID);
	BOOL       Delete          (DWORD_PTR       dwID,
															BOOL            bCleanup = FALSE);
	EntryData *EnumerateEntries(TABLE_CALLBACK  callBackFunc,
															void*           context,
															BOOL            bUnlockTable = FALSE);
	
	BOOL       IsInitialized   () {return bInitialized;}
	WORD       GetSize         () {return wNumUsed;}

private:
	// data

	EntryData**       pDataTable;
	PLOCK_ENTRY       pLockTable;
	CRITICAL_SECTION  csTableLock;
	WORD              wSize,
					  wNumUsed,
					  wFirstFree,
				      wLastFree,
					  wUniqueID;
	BOOL              bInitialized;

	// private methods

	BOOL LockEntry   (WORD wIndex,
									 DWORD timeout = INFINITE);
	BOOL UnLockEntry(WORD wIndex);
	void LockTable  () { EnterCriticalSection(&csTableLock); };
	void UnLockTable() { LeaveCriticalSection(&csTableLock); };
	WORD GenerateUniqueID();
	DWORD MakeID(WORD wIndex, WORD wUniqueID)
		{
			DWORD theID = wUniqueID;
			theID = (theID << 16) & 0xFFFF0000;
			theID |= wIndex;
			return(theID);
		};
	void BreakID(DWORD_PTR theID, WORD* pwIndex, WORD* pwUID)
		{
			*pwIndex = (WORD) (theID & 0x0000FFFF);
			*pwUID   = (WORD) ((theID >> 16) & 0x0000FFFF);
		};

};

/*
 ** TSTable::TSTable
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\tstable.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
TSTable<EntryData>::TSTable(WORD _size) :
														wSize(_size),
														wNumUsed((WORD) 0),
														wFirstFree((WORD) 0),
														wLastFree((WORD) (_size - 1)),
														wUniqueID((WORD) 0),
														bInitialized(TRUE),
														pDataTable(NULL),
														pLockTable(NULL)
{
	WORD wIndex;

	// Create the table lock

	InitializeCriticalSection(&csTableLock);

	// Lock the table

	LockTable();

	// Create the data table

	pDataTable = new EntryData*[wSize];
	
	if(pDataTable == NULL)
	{
		bInitialized = FALSE;
		return;
	}

	// Init the pointers

	for (wIndex = 0; wIndex < wSize; wIndex++)
	{
		pDataTable[wIndex] = NULL;
	}

	// Create the lock table

	pLockTable = new LOCK_ENTRY[wSize];

	if (pLockTable == NULL)
	{
		bInitialized = FALSE;
		return;
	}

	// Initialize the lock table entries...each entry begins with
	// a NULL mutex handle, a zero lock count and it's next free is
	// the next successive entry.

	for (wIndex = 0; wIndex < wSize; wIndex++ )
	{
		pLockTable[wIndex].hLock      = NULL;
		pLockTable[wIndex].iLockCount = 0;
		pLockTable[wIndex].wNextFree = (WORD) (wIndex + 1);
	}

	// note: the wNextFree in the last table entry points to an invalid index, however,
	// this is OK since if the table ever fills, it is automatically resized making what
	// was an invalid index, the index into the first entry of newly added part of the
	// enlargened table.  Trust me...

	// Unlock the table

	UnLockTable();
}

/*
 ** TSTable::~TSTable
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
TSTable<EntryData>::~TSTable()
{
	DWORD wIndex;

	// Lock the table

	LockTable();

	// Delete the data table

	if (pDataTable != NULL)
	{
		delete pDataTable;
	}

	// Delete the lock table

	if (pLockTable != NULL)
	{
		// Destroy the mutexes

		for (wIndex = 0; wIndex < wSize; wIndex++)
		{
			if (pLockTable[wIndex].hLock != NULL)
			{
				CloseHandle(pLockTable[wIndex].hLock);
			}
		}

		delete pLockTable;
	}

	// Unlock the table

	UnLockTable();

	// Destroy the table lock

	DeleteCriticalSection(&csTableLock);

	bInitialized = FALSE;
}

/*
 ** TSTable::CreateAndLock
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
BOOL TSTable<EntryData>::CreateAndLock(EntryData* pEntryData,
																			 LPDWORD    lpdwID)
{
	BOOL  bRetCode = FALSE;
	WORD wIndex;

	// If the pointer passed in is bad, then don't even try to do anything for them

	if (pEntryData == NULL || lpdwID == NULL)
	{
		goto EXIT;
	}

	// Lock the table

	LockTable();

	// If the table is full, then resize it.

	if (wNumUsed >= wSize - 1)
	{
		goto EXIT;
	}

	// Get the first free entry

	wIndex = wFirstFree;

	// Create the mutex for the object

	if ((pLockTable[wIndex].hLock = CreateMutexA(NULL, FALSE, NULL)) == NULL)
	{
		goto EXIT;
	}

	// Lock the entry (no need checking the return code as the entire
	// table is locked) - since this is a new entry, that means that nobody
	// could have locked the entry already.

	LockEntry(wIndex, 0);

	// Copy pointer to the data table

	pDataTable[wIndex] = pEntryData;

	// Init the corresponding lock table entry

	pLockTable[wIndex].bDeleted   = FALSE;
	pLockTable[wIndex].iLockCount = 1;
	pLockTable[wIndex].wUniqueID = GenerateUniqueID();

	// Set the id for the caller

	*lpdwID = MakeID(wIndex, pLockTable[wIndex].wUniqueID);

	// Bump up the count of number used

	wNumUsed++;

	// Fix the next free index

	wFirstFree = pLockTable[wIndex].wNextFree;

	// Signal success

	bRetCode = TRUE;

EXIT:

	// Unlock the table

	UnLockTable();
	return bRetCode;
}

/*
 ** TSTable::Lock
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
EntryData* TSTable<EntryData>::Lock(DWORD_PTR dwID,
																		DWORD timeout)
{
	EntryData* pEntryData = NULL;

	WORD wIndex,
       wUID;

	BreakID(dwID, &wIndex, &wUID);

	// Lock the table

	LockTable();

	// Verify the index is within bounds

	if (wIndex >= wSize)
	{
		goto EXIT;
	}

	// Verify that the entry is actually valid (ie the lock in non-NULL,
	// the object status is valid, and the unique ID matches).

	if (pLockTable[wIndex].hLock    == NULL ||
			pLockTable[wIndex].bDeleted == TRUE ||
			pLockTable[wIndex].wUniqueID != wUID)
	{
		goto EXIT;
	}

	// If the timeout is INFINITE, then try to lock the entry using a more
	// "thread friendly" method.	 If a timeout is specified, then don't do
	// the spin lock since it could be implemented at a higher level.

	if(timeout == INFINITE)
	{
		// simulate infinity with a pseudo "spin lock"
		// This is more "thread friendly" in that it unlocks the table allowing some
		// other thread that is trying to unlock the same entry to be able to lock the
		// table.

		while(LockEntry(wIndex, 0) == FALSE)
		{
			UnLockTable();

			// give up the rest of this thread quantum, allowing others to run and potentially
			// unlock the entry

			Sleep(0);
			LockTable();

			// If the entry has been replaced, deleted or marked for deletion then
			// bag it (give up)

			if((pLockTable[wIndex].wUniqueID != wUID)  ||
				 (pLockTable[wIndex].hLock      == NULL)  ||
				 (pLockTable[wIndex].bDeleted   == TRUE))
			{
				goto EXIT;
			}
		}

		// we got the lock

		pEntryData = pDataTable[wIndex];
	}
	
	// Otherwise, do a normal lock

	else
	{	
		if (LockEntry(wIndex, timeout) == TRUE)
		{
			pEntryData = pDataTable[wIndex];
		}
	}

EXIT:

	// Unlock the table

	UnLockTable();

	return pEntryData;
}

/*
 ** TSTable::Unlock
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
BOOL TSTable<EntryData>::Unlock(DWORD_PTR dwID)
{
	BOOL bRetCode = TRUE;

	WORD wIndex,
       wUID;

	BreakID(dwID, &wIndex, &wUID);
	
	// Lock the table

	LockTable();

	// Verify the id is within bounds

	if (wIndex >= wSize)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

	// verify that the UID matches
	if (pLockTable[wIndex].wUniqueID != wUID)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

	// Verify that the lock is actually valid and that the entry has not been
	// deleted

	if (pLockTable[wIndex].hLock == NULL)
	{
		goto EXIT;
	}

	// Make sure that that thread has the lock on the entry

	if ((bRetCode = LockEntry(wIndex, 0)) == TRUE)
	{
		// if this table entry is marked for delete and the lock count is less than 2
		// (since the thread could have called delete after unlocking the entry...although
		// this is a no-no) then clean up the table entry

		if (pLockTable[wIndex].bDeleted   == TRUE &&
				pLockTable[wIndex].iLockCount <= 2)
		{
			// If the caller specifed cleanup on delete, then get rid of memory

			if (pLockTable[wIndex].bCleanup == TRUE)
			{
				delete pDataTable[wIndex];
			}

			// Set the pointer to NULL

			pDataTable[wIndex] = NULL;

			// Decrement the count of used entries

			wNumUsed--;

			// Fix the entry so that it's next free index is what is currently
			// the next free pointed to by the current last free entry.
			// Then update the last free entry's next pointer, and finally,
			// update the last free index to this entry
			pLockTable[wIndex].wNextFree    = pLockTable[wLastFree].wNextFree;
			pLockTable[wLastFree].wNextFree = wIndex;
			wLastFree                       = wIndex;
		}

		// Do two unlocks on the entry ... one for the original lock and another for
		// the lock we obtained during the test

		UnLockEntry(wIndex);
		UnLockEntry(wIndex);

		// Since the entire table is locked, then we can get away with this.  If
		// the code is ever changed so that the entire table is not locked during
		// these operations, then this will cause a race condition.

		// If we got rid of the data, then close the handle to the mutex and
		// set the handle to NULL

		if (pDataTable[wIndex] == NULL)
		{
			CloseHandle(pLockTable[wIndex].hLock);
			pLockTable[wIndex].hLock = NULL;
		}
	}

EXIT:

	// Unlock the table

	UnLockTable();

	return bRetCode;
}

/*
 ** TSTable::Delete
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
BOOL TSTable<EntryData>::Delete(DWORD_PTR dwID,
																BOOL  bCleanup)
{
	BOOL bRetCode = TRUE;

	WORD wIndex,
       wUID;

	BreakID(dwID, &wIndex, &wUID);

	// Lock the table

	LockTable();

	// Verify that the ID is within bounds

	if (wIndex >= wSize)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

	// verify that the UID matches
	if (pLockTable[wIndex].wUniqueID != wUID)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

	// Verify that the entry is valid

	if (pDataTable[wIndex] == NULL)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

	// Try to lock the entry (ie check to see if we had the entry locked)

	if (LockEntry(wIndex, 0) == TRUE)
	{
		// mark it for deletion, set the cleanp flag and then unlock it

		pLockTable[wIndex].bDeleted = TRUE;
		pLockTable[wIndex].bCleanup = bCleanup;

		UnLockEntry(wIndex);

		// Note: this function does not call ::Unlock() on behalf of the user.
		// Thus, the entry is only marked as deleted at this point and can no
		// longer be locked by any threads (including the one that marked it for delete).
		// The thread that marked the entry as deleted must call ::Unlock() to actually
		// free up the entry.
	}

EXIT:

	// Unlock the table

	UnLockTable();

	return bRetCode;
}

/*
 ** TSTable::Lock
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:  Validates that an object still exists.  Can be called
 *								regardless if caller has entry locked or not.
 *
 *  RETURNS:
 *
 */

template <class EntryData>
BOOL TSTable<EntryData>::Validate(DWORD_PTR dwID)
{
	BOOL bRetCode = TRUE;
	WORD wIndex,
       wUID;

	BreakID(dwID, &wIndex, &wUID);

	// Lock the table

	LockTable();

	// Verify the index is within bounds

	if (wIndex >= wSize)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

	// Verify that the entry is actually valid (ie the lock in non-NULL,
	// the object status is valid, the unique ID matches, and the data ptr is not null).

	if (pLockTable[wIndex].hLock    == NULL  ||
			pLockTable[wIndex].bDeleted == TRUE  ||
			pLockTable[wIndex].wUniqueID != wUID ||
			pDataTable[wIndex] == NULL)
	{
		bRetCode = FALSE;
		goto EXIT;
	}

EXIT:

	// Unlock the table

	UnLockTable();

	return bRetCode;
}

/*
 ** TSTable::EnumerateEntries
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
EntryData* TSTable<EntryData>::EnumerateEntries(TABLE_CALLBACK callbackFunc,
																								LPVOID         context,
																								BOOL           bUnlockTable)
{
	DWORD      dwAction;
	WORD       wIndex = wSize;
	EntryData* pEntryData = NULL;
	DWORD      dwEntryID;

	// Make sure they passed a good function

	if (callbackFunc == NULL)
	{
		goto EXIT;
	}

	// Lock the table

	LockTable();

	// Run through the data table and pass the data to the callback function

	for (wIndex = 0; wIndex < wSize; wIndex++)
	{
		// Verify that there is actually data in the entry and that the entry has not
		// been marked for deletion.

		if (pDataTable[wIndex]          == NULL ||
				pLockTable[wIndex].bDeleted == TRUE)
		{
			continue;
		}


		// Try to lock the entry...if we cannot, then we don't have the lock and
		// we will only report entries that we have locked (or are unlocked)

		if (LockEntry(wIndex, 0) == FALSE)
		{
			continue;
		}
		
		// build and remember the "full" entry ID so we can use it to unlock the entry
		dwEntryID = MakeID(wIndex, pLockTable[wIndex].wUniqueID);

		// Save the pointer to the object.

		pEntryData = pDataTable[wIndex];

		// note: only unlock the table during the callback if we are explicitly asked to (the
		// default is not to unlock the table).
		if(bUnlockTable == TRUE)
			UnLockTable();

		// Call their function
		dwAction = callbackFunc(pDataTable[wIndex], context);

		if(bUnlockTable == TRUE)
			LockTable();

		// If the action says to delete the entry, then do so...if we are also to delete
		// the object, pass in a TRUE.

		if (dwAction == CALLBACK_DELETE_ENTRY ||
				dwAction == CALLBACK_DELETE_ENTRY_AND_OBJECT)
		{
			Delete(dwEntryID, (dwAction == CALLBACK_DELETE_ENTRY ? FALSE : TRUE));
		}

		// If the action says abort, then break the loop...notice that means that
		// the entry is still locked

		else if (dwAction == CALLBACK_ABORT)
		{
			goto EXIT;
		}

		// Unlock the entry...notice we don't use UnLockEntry.  The reason is that
		// if the entry has been marked as deleted, then we need to have
		// it destroyed and UnLockEntry doesn't do that.

		Unlock(dwEntryID);
	}

EXIT:

	// Unlock the table

	UnLockTable();

	// Return NULL if we processed the entire table...if we were told to abort,
	// return a pointer to the entry we stopped on.

	return (wIndex == wSize ? NULL : pEntryData);
}

// helper functions - these assume table is locked and index is good

/*
 ** TSTable::LockEntry
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
BOOL TSTable<EntryData>::LockEntry(WORD wIndex,
																	 DWORD timeout)
{
	BOOL  bRetCode = TRUE;
	DWORD dwRetCode;


	// Try to lock the entry.  If it succeeds, we'll bump up the lock count.  If
	// the wait ended because another thread abandoned the mutex, then set the count
	// to one.

	dwRetCode = WaitForSingleObject(pLockTable[wIndex].hLock, timeout);
	
	if (dwRetCode == WAIT_OBJECT_0)
	{
		pLockTable[wIndex].iLockCount++;
	}
	else if (dwRetCode == WAIT_ABANDONED)
	{
		pLockTable[wIndex].iLockCount = 1;
	}
	else
	{
		bRetCode = FALSE;
	}

	return bRetCode;
}

/*
 ** TSTable::UnLockEntry
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION:
 *
 *  RETURNS:
 *
 */

template <class EntryData>
BOOL TSTable<EntryData>::UnLockEntry(WORD wIndex)
{
	BOOL bRetCode;

	// Release the mutex...if that succeeds, reduce the count

	if((bRetCode = ReleaseMutex(pLockTable[wIndex].hLock)) == TRUE)
	{
		pLockTable[wIndex].iLockCount--;
	}

	return bRetCode;
}


/*
 ** TSTable::GenerateUniqueID
 *
 *  FILENAME: c:\msdev\projects\firewalls\inc\table.h
 *
 *  PARAMETERS:
 *
 *  DESCRIPTION: table should be locked before calling this function.
 *
 *  RETURNS:
 *
 */

template <class EntryData>
WORD TSTable<EntryData>::GenerateUniqueID()
{
	// table must be locked
	if(++wUniqueID == TSTABLE_INVALID_UNIQUE_ID)
		wUniqueID++;
	return(wUniqueID);
}






#endif