//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//


#include <windows.h>
#include <tchar.h>
#include <tux.h>

/* common utils for oal tests */
#include "commonUtils.h"

/* test procs, etc */
#include "tuxInterrupts.h"

/* collateIRQs function lives here */
#include "prettyPrinting.h"

/* provides command line routines for the tests */
#include "interruptCmdLine.h"

/*
 * These test cases exercise the paramters to the IOCTLS.  As
 * opposed to the other files, which contain test cases that are
 * supposed to confirm that the IOCTLs are allocating and releasing
 * things correctly, these tests confirm that the IOCTL handles
 * odd parameter combinations correctly.
 *
 * The bulk of these tests expect the IOCTL to fail.  The IOCTLs
 * are quite strict in what they accept as input, except in the 
 * output parameter case.  In this case there are tests that both
 * force failure and expect success.
 */

/* tells us whether we are in kernel mode */
__inline BOOL inKernelMode( void )
{
  return (int)inKernelMode < 0;
}

/***** for sanity checks *****/

/*
 * defined in requestReleaseSysintr.cpp.  Not in a header because
 * require use of functions specific to requestReleaseSysintr.cpp
 */

extern BOOL
getNumSysIntrsFree (DWORD dwIRQBegin, DWORD dwIRQEnd,
                    DWORD * pdwNumSysIntrsFree);

extern BOOL
checkNumSysIntrsFree (DWORD dwIRQBegin, DWORD dwIRQEnd,
                      DWORD dwNumSysIntrsFree);

/* maximum in bound buffer length tested for IOCTL_HAL_REQUEST/RELEASE_SYSINTR */
#define MAX_INBOUND_BUF_LENGTH_DW 8

/**** general test driver functions **************************************/

/* 
 * The test has a driver that takes a structure denoting an IOCTL call.  This
 * driver makes the call and checks return values.
 *
 * The structure below provides that data needed to make the ioctl call.
 */

typedef struct
{
  DWORD dwIoctlCode;		// IOCTL code
  TCHAR * szDescription;	// test case description
  VOID * dwInputBuf;		// pointer to buffer, or null if param is null
  DWORD dwInputBufSize;		// input buf size (as told to ioctl)
  VOID * dwOutputBuf;		// pointer to buffer, or null if param is null
  DWORD dwOutputBufSize;	// output buf size (as told to ioctl)
  DWORD * dwLpBytesReturned;	// currently not used
} sAnIoctlCall;

/* function to run one test case, denoted by an instance of the struct above */
BOOL
runRow (const sAnIoctlCall *const sRow, 
	BOOL bShouldFreeSysintr = FALSE,
	BOOL bTestingLpBytesReturned = FALSE, 
	DWORD dwExpectedLpBytesReturned = 0);

/* print out data about a call that failed */
BOOL
dumpRowOnError (const sAnIoctlCall *const sRow, void * pInBuf, void * pOutBuf);


/**** IOCTL_HAL_REQUEST_SYSINTR *******************************************/

/* test procs and test functions */
BOOL
runBadInputNewModel_IncompleteArgSets();

BOOL
runBadInputNewModel_WrongInSizes ();

BOOL
runBadInputNewModel_WrongInSizes (const DWORD dwFlagForTest, 
				  const TCHAR *const szFlagName);

BOOL
runBadInSizeOldModel ();

BOOL
runBadFlags ();
BOOL
runBadFlags (const sAnIoctlCall *const sRow);

BOOL
runOutSizeOldModel (BOOL bExpectedToPass, BOOL bTestLpBytesReturned);
BOOL
runOutSizeNewModel (BOOL bExpectedToPass, BOOL bTestLpBytesReturned);
BOOL
runOutSize (const sAnIoctlCall *const sRow, BOOL bExpectedToPass,
	    BOOL bTestLpBytesReturned);

BOOL
fillInBoundArrayNewModel (__out_ecount (dwNumOfDwords) DWORD *const pdwBuf, 
			  DWORD dwNumOfDwords);
BOOL
fillInBoundArrayOldModel (__out_ecount (dwNumOfDwords) DWORD *const pdwBuf, 
			  DWORD dwNumOfDwords);

/****** IOCTL_HAL_REQUEST_IRQ **********************************************/

BOOL
runBadInSize_IOCTL_HAL_REQUEST_IRQ ();

BOOL
runBadOutSize_IOCTL_HAL_REQUEST_IRQ ();

/****** IOCTL_HAL_RELEASE_SYSINTR ******************************************/

/* 
 * no outsize is tested, since this value is not used.  The code should
 * never touch it (we currently don't verify this in these sets of tests).
 */
BOOL
runBadInSize_IOCTL_HAL_RELEASE_SYSINTR ();

/**************************** general driver function **********************/

/* 
 * maps the defines to functions.  The defines are in the function table.  This 
 * allows us to easily add and update with new tests/changes without having
 * to add functions to the main headers.
 */
TESTPROCAPI
runGeneralIoctlTest(
		    UINT uMsg,
		    TPPARAM tpParam,
		    LPFUNCTION_TABLE_ENTRY lpFTE)
{
  /* only supporting executing the test */
  if (uMsg != TPM_EXECUTE)
    {
      return (TPR_NOT_HANDLED);
    }
  
  BOOL rVal = FALSE;

  DWORD dwNumSysIntrsFree = 0;

  breakIfUserDesires();

  if (inKernelMode() &&
      !getNumSysIntrsFree (TEST_IRQ_BEGIN, TEST_IRQ_END, &dwNumSysIntrsFree))
    {
      goto cleanAndReturn;
    }

  switch ((tGeneralIoctlTestCase) lpFTE->dwUserData)
    {
      /* IOCTL_HAL_REQUEST_SYSINTR */
    case TESTCASE_BAD_IN_SIZE_NEW_MODEL:
      rVal = runBadInputNewModel_WrongInSizes ();
      break;
    case TESTCASE_BAD_INPUT_INCOMPLETE_ARGS_NEW_MODEL:
      rVal = runBadInputNewModel_IncompleteArgSets(); 
      break;
    case TESTCASE_BAD_IN_SIZE_OLD_MODEL:
      rVal = runBadInSizeOldModel ();
      break;
    case TESTCASE_BAD_FLAGS:
      rVal = runBadFlags ();
      break;
    case TESTCASE_BAD_OUT_SIZE_OLD_MODEL:
      /*
       * bad sizes should cause failure, hence FALSE 
       * not testing lpBytesReturned, hence FALSE
       */
      rVal = runOutSizeOldModel (FALSE, FALSE);
      break;
    case TESTCASE_BAD_OUT_SIZE_NEW_MODEL:
      /* bad sizes should cause failure, hence FALSE */
      rVal = runOutSizeNewModel (FALSE, FALSE);
      break;
    case TESTCASE_GOOD_OUT_SIZE_OLD_MODEL:
      /* good sizes should cause success, hence TRUE */
      rVal = runOutSizeOldModel (TRUE, FALSE);
      break;
    case TESTCASE_GOOD_OUT_SIZE_NEW_MODEL:
      rVal = runOutSizeNewModel (TRUE, FALSE);
      break;

      /* 
       * lpBytesReturned is linked to the output parameters, so use
       * these tests to confirm that it is working in the positive
       * case.
       */
    case TESTCASE_BYTES_RETURNED_OLD_MODEL:
      /* good sizes should cause success, hence TRUE */
      rVal = runOutSizeOldModel (TRUE, TRUE);
      break;
    case TESTCASE_BYTES_RETURNED_NEW_MODEL:
      rVal = runOutSizeNewModel (TRUE, TRUE);
      break;

      /* IOCTL_HAL_RELEASE_SYSINTR */
    case IOCTL_HAL_RELEASE_SYSINTR_TESTCASE_BAD_IN_SIZE:
      rVal = runBadInSize_IOCTL_HAL_RELEASE_SYSINTR ();
      break;
      
      /* IOCTL_HAL_REQUEST_IRQ */
    case IOCTL_HAL_REQUEST_IRQ_TESTCASE_BAD_IN_SIZE:
      rVal = runBadInSize_IOCTL_HAL_REQUEST_IRQ ();
      break;
    case IOCTL_HAL_REQUEST_IRQ_TESTCASE_BAD_OUT_SIZE:
      rVal = runBadOutSize_IOCTL_HAL_REQUEST_IRQ ();
      break;

    default:
      rVal = FALSE;
      Error (_T("Wrong test case id to runGeneralIoctlTest."));
      break;
    }

  Info (_T("")); // blank line

  if (inKernelMode() &&
      !checkNumSysIntrsFree (TEST_IRQ_BEGIN, TEST_IRQ_END, dwNumSysIntrsFree))
    {
      rVal = FALSE;
      goto cleanAndReturn;
    }

 cleanAndReturn:

  if (rVal)
    {
      Info (_T("Test passed."));
      return (TPR_PASS);
    }
  else
    {
      Error (_T("Test failed!"));
      return (TPR_FAIL);
    }
}

/*
 * driver function.  sRow contains all of the info needed to make an
 * ioctl call.
 *
 * bShouldFreeSysintr.  If true, test will fail if sysintr can't be
 * allocated.  the allocated sysintr is freed before exit.
 *
 * bTestingLpBytesReturned.  If true, then IOCTL is called with
 * lpBytesReturned set.  Value must match dwExpectedLpBytesReturned.
 *
 * return true if the test worked and false if it failed.
 */
BOOL
runRow (const sAnIoctlCall *const sRow, BOOL bShouldFreeSysintr, 
	BOOL bTestingLpBytesReturned, DWORD dwExpectedLpBytesReturned)
{
  if (!sRow)
    {
      return (FALSE);
    }

  BOOL returnVal = FALSE;

  Info (_T("description: %s"), sRow->szDescription);

  /* these are set depending on the table */
  VOID * pInBuf = NULL;
  VOID * pOutBuf = NULL;

  if (sRow->dwInputBuf)
    {
      /* alloc and copy it */

      pInBuf = guardedMalloc (sRow->dwInputBufSize);

      if (!pInBuf)
	{
	  Error (_T("Couldn't allocate memory."));
	  goto cleanAndReturn;
	}
      
      memcpy (pInBuf, sRow->dwInputBuf, sRow->dwInputBufSize);
    }

  if (sRow->dwOutputBuf)
    {
      /* alloc and copy it */

      pOutBuf = guardedMalloc (sRow->dwOutputBufSize);

      if (!pOutBuf)
	{
	  Error (_T("Couldn't allocate memory."));
	  goto cleanAndReturn;
	}
      
      memcpy (pOutBuf, sRow->dwOutputBuf, sRow->dwOutputBufSize);
    }

  /* at this point all values are set, ready for the call */

  BOOL rc;

  if (bTestingLpBytesReturned)
    {
      DWORD dwBytesReturned;

      rc = KernelIoControl (sRow->dwIoctlCode, pInBuf, sRow->dwInputBufSize,
			    pOutBuf, sRow->dwOutputBufSize, &dwBytesReturned);

      if (dwBytesReturned != dwExpectedLpBytesReturned)
	{
	  Error (_T("Bytes returned is not correct.  Expected %u and got %u"),
		 dwExpectedLpBytesReturned, dwBytesReturned);
	  dumpRowOnError (sRow, pInBuf, pOutBuf);
	  goto cleanAndReturn;
	}
    }
  else
    {
      rc = KernelIoControl (sRow->dwIoctlCode, pInBuf, sRow->dwInputBufSize,
			    pOutBuf, sRow->dwOutputBufSize, NULL);
    }

  if (rc && !bShouldFreeSysintr)
    {
      Error (_T("Expected false when calling IOCTL_HAL_REQUEST_SYSINTR ")
	     _T("and got true."));

      dumpRowOnError (sRow, pInBuf, pOutBuf);

      goto cleanAndReturn;
    }
  else if (!rc && bShouldFreeSysintr)
    {
      Error (_T("Expected true when calling IOCTL_HAL_REQUEST_SYSINTR ")
	     _T("and got false."));

      dumpRowOnError (sRow, pInBuf, pOutBuf);

      goto cleanAndReturn;
    }

  if (bShouldFreeSysintr)
    {
      /* release interrupt */

      /* make sure that user gave us enough memory */
      if (sRow->dwOutputBufSize < sizeof (DWORD))
	{
	  IntErr (_T("Test Error: User wants test to succeed but didn't give")
		  _T("enough outbound memory.  %u < %u"),
		  sRow->dwOutputBufSize, sizeof (DWORD));
	  goto cleanAndReturn;
	}
      
      /* get sysintr; make a copy so that we don't affect checks below */
      DWORD dwSysIntr = *(DWORD *) pOutBuf;

      if (!KernelIoControl (IOCTL_HAL_RELEASE_SYSINTR, 
			   &dwSysIntr, sizeof (DWORD), NULL, 0, NULL))
	{
	  Error (_T("Couldn't free SYSINTR %u."), dwSysIntr);
	  goto cleanAndReturn;
	}
    }

  /* compare bufs to original to make sure unchanged */
  if ((sRow->dwInputBuf) && 
      (memcmp (sRow->dwInputBuf, pInBuf, sRow->dwInputBufSize) != 0))
    {
      /* buffers don't match */
      Error (_T("IOCTL changed input buffer."));

      /* dump output */
      dumpRowOnError (sRow, pInBuf, pOutBuf);
      goto cleanAndReturn;
    }

  /* use guarded memory to check for overwriting edge of buffer */
  if ((sRow->dwInputBuf) && (!guardedCheck (pInBuf, sRow->dwInputBufSize)))
    {
      Error (_T("IOCTL wrote outside of array boundaries for input buffer."));

      goto cleanAndReturn;
    }

  if ((sRow->dwOutputBuf) && (!guardedCheck (pOutBuf, sRow->dwOutputBufSize)))
    {
      Error (_T("IOCTL wrote outside of array boundaries for output buffer."));

      goto cleanAndReturn;
    }

  returnVal = TRUE;

 cleanAndReturn:

  if ((pInBuf) && !guardedFree (pInBuf, sRow->dwInputBufSize))
    {
      Error (_T("Couldn't free input buffer."));
      returnVal = FALSE;
    }

  if ((pOutBuf) && !guardedFree (pOutBuf, sRow->dwOutputBufSize))
    {
      Error (_T("Couldn't free output buffer."));
      returnVal = FALSE;
    }

  return (returnVal);
}

/* print out data about a call that failed */
BOOL
dumpRowOnError (const sAnIoctlCall *const sRow, void * pInBuf, void * pOutBuf)
{

  BOOL returnVal = TRUE;

  Error (_T("The values passed into the IOCTL are:"));

  if (!sRow->dwInputBuf)
    {
      /* null */
      Error (_T("Input buffer is NULL."));
    }
  /* if not divisible by 4 */
  else if (sRow->dwInputBufSize & 0x3)
    {
      /* collateIRQs takes number of values, not bytes, so have to convert */
      Error (_T("Input buffer (truncated): %s"), 
	     collateIRQs ((DWORD *)pInBuf, 
			  sRow->dwInputBufSize / sizeof (DWORD)));
    }
  else
    {
      Error (_T("Input buffer: %s"), 
	     collateIRQs ((DWORD *)pInBuf, 
			  sRow->dwInputBufSize / sizeof (DWORD)));
    }

  Error (_T("Input size (bytes): %u"), sRow->dwInputBufSize);

  if (!sRow->dwOutputBuf)
    {
      /* null */
      Error (_T("Output buffer is NULL."));
    }
  else if (sRow->dwOutputBufSize & 0x3)
    {
      Error (_T("Output buffer (truncated): %s"), 
	     collateIRQs ((DWORD *)pOutBuf, 
			  sRow->dwOutputBufSize / sizeof (DWORD)));
    }
  else
    {
      Error (_T("Output buffer: %s"), 
	     collateIRQs ((DWORD *)pOutBuf, 
			  sRow->dwOutputBufSize / sizeof (DWORD)));
    }

  Error (_T("Output size (bytes): %u"), sRow->dwOutputBufSize);
  Error (_T(""));

  return (returnVal);
}

/******************* TEST CASES for IOCTL_HAL_REQUEST_SYSINTR **************/

/****** inBound Size Check *****/

/* 
 * a structure denoting what a call to IOCTL_HAL_REQUEST_SYSINTR in the new
 * model will look like.  Used to set up a table of test cases below.
 */
typedef struct
{
  DWORD dwFlag;
  DWORD dwMode;
  DWORD dwIRQ1;
  DWORD dwIRQ2;
  DWORD dwIRQ3;
  DWORD dwIRQ4;
} sLargeCall;

/* 
 * instances of the largeCell structure, one for each request mode supported
 * by the IOCTL.
 */
sLargeCall LargeCallDynamic =
  {SYSINTR_UNDEFINED, OAL_INTR_DYNAMIC};
sLargeCall LargeCallStatic =
  {SYSINTR_UNDEFINED, OAL_INTR_STATIC};
sLargeCall LargeCallForceStatic =
  {SYSINTR_UNDEFINED, OAL_INTR_FORCE_STATIC};

/* we need memory locations to point to in the table.  These provide them. */

/* 10 is arbitrary - this can't be -1 */
DWORD dwOldModelInBuff = 10;

DWORD dwCorrectOutputBuff = 0;

DWORD lpBytesReturnedCorrect = sizeof (DWORD);

/* thse are really runBadInSizeNewModel */
sAnIoctlCall newModelIncompleteArgSets[] =
  {
    /* standard checks */
    IOCTL_HAL_REQUEST_SYSINTR,
    _T("In buf is null"),
    
    NULL,
    sizeof (DWORD),
    &dwCorrectOutputBuff,
    sizeof (DWORD),
    &lpBytesReturnedCorrect,


    IOCTL_HAL_REQUEST_SYSINTR,
    _T("Out buf is null - old model"),

    &dwOldModelInBuff,
    sizeof (DWORD),
    NULL,
    sizeof (DWORD),
    &lpBytesReturnedCorrect,

    /* new model */
    IOCTL_HAL_REQUEST_SYSINTR,
    _T("inbound size of 2 - new mode - dynamic"),
    
    &LargeCallDynamic,
    2 * sizeof (DWORD),
    &dwCorrectOutputBuff,
    sizeof (DWORD),
    &lpBytesReturnedCorrect,


    IOCTL_HAL_REQUEST_SYSINTR,
    _T("inbound size of 2 - new mode - static"),
    
    &LargeCallStatic,
    2 * sizeof (DWORD),
    &dwCorrectOutputBuff,
    sizeof (DWORD),
    &lpBytesReturnedCorrect,


    IOCTL_HAL_REQUEST_SYSINTR,
    _T("inbound size of 2 - new mode - force static"),
    
    &LargeCallForceStatic,
    2 * sizeof (DWORD),
    &dwCorrectOutputBuff,
    sizeof (DWORD),
    &lpBytesReturnedCorrect

  };

BOOL
runBadInputNewModel_IncompleteArgSets()
{
  BOOL returnVal = TRUE;

  /* special cases. these consist mostly of incomplete argument sets */
  for (DWORD dwRow = 0; dwRow < countof (newModelIncompleteArgSets); dwRow++)
    {
      if (!runRow (newModelIncompleteArgSets + dwRow))
	{
	  returnVal = FALSE;
	}
    }

  return (returnVal);

}

/*
 * confirm that inbound sizes are correct
 */
BOOL
runBadInputNewModel_WrongInSizes ()
{
  BOOL returnVal = TRUE;

  if (!runBadInputNewModel_WrongInSizes (OAL_INTR_STATIC, 
					  _T("OAL_INTR_STATIC")))
    {
      Error (_T("Failed on checks for OAL_INTR_STATIC"));
      returnVal = FALSE;
    }

  return (returnVal);

  if (!runBadInputNewModel_WrongInSizes (OAL_INTR_FORCE_STATIC, 
					  _T("OAL_INTR_FORCE_STATIC")))
    {
      Error (_T("Failed on checks for OAL_INTR_STATIC"));
      returnVal = FALSE;
    }

  if (!runBadInputNewModel_WrongInSizes (OAL_INTR_DYNAMIC, 
					  _T("OAL_INTR_DYNAMIC")))
    {
      Error (_T("Failed on checks for OAL_INTR_DYNAMIC"));
      returnVal = FALSE;
    }

  return (returnVal);
}


/*
 */
BOOL
runBadInputNewModel_WrongInSizes (const DWORD dwFlagForTest, 
				  const TCHAR *const szFlagName)
{
  BOOL returnVal = TRUE;

  /* set up for old model */
  DWORD dwInBuf [MAX_INBOUND_BUF_LENGTH_DW];  
  DWORD dwOutBuf;

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_REQUEST_SYSINTR,
      _T("New Model - Wrong Input Sizes"),
    
      dwInBuf,
      0,			// will be set later by the test 
      &dwOutBuf,
      sizeof (DWORD),
      NULL
    };

  /* used many times below */
  PREFAST_SUPPRESS (326, "by design");
  if (MAX_INBOUND_BUF_LENGTH_DW < 2)
    {
      IntErr (_T("MAX_INBOUND_BUF_LENGTH_DW must be > 2"));
      return (FALSE);
    }

  /* fill irq part */
  if (!fillInBoundArrayNewModel (dwInBuf, MAX_INBOUND_BUF_LENGTH_DW))
    {
      Error (_T("Couldn't fill in bound array for new model."));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  /* second dword in struct tells gives the allocation type. */
  dwInBuf[1] = dwFlagForTest;

  /* start from zero, even though we get overlap with other test cases */
  for (DWORD dwSize = 0; dwSize < MAX_INBOUND_BUF_LENGTH_DW * sizeof (DWORD); 
       dwSize++)
    {
      /* skip all values that are multiples of four and provide at least one IRQ */
      if ((dwSize % sizeof (DWORD) == 0) && (dwSize > sizeof (DWORD) * 2))
	{
	  continue;
	}

      Info (_T("Setting InputBufSize to %u.  Flag is %s"), dwSize, szFlagName);

      sStart.dwInputBufSize = dwSize;

     /* make call */
      if (!runRow (&sStart))
	{
	  returnVal = FALSE;
	}
    }

 cleanAndReturn:

  return (returnVal);
}


/*
 * for the old model, input must be 4 bytes.  No other values are allowed.
 */
BOOL
runBadInSizeOldModel ()
{
  BOOL returnVal = TRUE;

  /* set up for old model */
  DWORD dwInBuf [MAX_INBOUND_BUF_LENGTH_DW];  
  DWORD dwOutBuf;

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_REQUEST_SYSINTR,
      _T("Unsupported OAL_INTR mode"),
    
      dwInBuf,
      0,			// will be set later by the test 
      &dwOutBuf,
      sizeof (DWORD),
      NULL
    };

  if (!fillInBoundArrayOldModel (dwInBuf, MAX_INBOUND_BUF_LENGTH_DW))
    {
      Error (_T("Couldn't fill in bound array for old model."));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  for (DWORD dwSize = 0; dwSize < MAX_INBOUND_BUF_LENGTH_DW * sizeof (DWORD); 
       dwSize++)
    {
      if (dwSize == sizeof (DWORD))
	{
	  /* this is the valid insize, so skip it */
	  continue;
	}

      Info (_T("Setting InputBufSize to %u."), dwSize);

      sStart.dwInputBufSize = dwSize;

     /* make call */
      if (!runRow (&sStart))
	{
	  returnVal = FALSE;
	}
    }

 cleanAndReturn:

  return (returnVal);
}

/****** Bad OAL_INTR_* flag Check *****/

/* only needed for new model */

BOOL
runBadFlags ()
{
  BOOL returnVal = TRUE;

  DWORD dwInBuf [MAX_INBOUND_BUF_LENGTH_DW];  
  DWORD dwOutBuf;		// this is just used for something to
				// point to.  value doesn't matter

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_REQUEST_SYSINTR,
      _T("Unsupported OAL_INTR mode"),
    
      &dwInBuf,
      0,			// will be set later by the test 
      &dwOutBuf,
      sizeof (DWORD),
      NULL
    };

  if (!fillInBoundArrayNewModel (dwInBuf, MAX_INBOUND_BUF_LENGTH_DW))
    {
      Error (_T("Couldn't fill in bound array for new model."));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  for (DWORD dwNumIRQs = 2; dwNumIRQs < MAX_INBOUND_BUF_LENGTH_DW; dwNumIRQs++)
    {
      sStart.dwInputBufSize = sizeof (DWORD) * dwNumIRQs;
      
      if (!runBadFlags (&sStart))
	{
	  returnVal = FALSE;
	}
    }

 cleanAndReturn:

  return (returnVal);
}

  
/* inputs bad flags where OAL_INTR_BLAH is supposed to go */
BOOL
runBadFlags (const sAnIoctlCall *const sRow)
{
  sAnIoctlCall sLocal;
  
  BOOL returnVal = TRUE;
  
  /* make local copy so that we can modify it */
  memcpy (&sLocal, sRow, sizeof (sAnIoctlCall));

  for (DWORD dwFlag = 0; dwFlag <= 256; dwFlag++)
    {
      if (dwFlag == OAL_INTR_TRANSLATE || dwFlag == OAL_INTR_STATIC ||
	  dwFlag == OAL_INTR_DYNAMIC || dwFlag == OAL_INTR_FORCE_STATIC ||
	  dwFlag == 0)
	{
	  /* this is a valid flag, so skip it */
	  /*zero (no flags) behaves identical to OAL_INTR_DYNAMIC. It is kept for BC */
	  continue;
	}

      ((DWORD *)sLocal.dwInputBuf)[1] = dwFlag;

      /* make call */
      if (!runRow (&sLocal))
	{
	  returnVal = FALSE;
	}
    }

  for (DWORD dwFlag = 0x3; dwFlag != (0x3 << 30); dwFlag <<= 1)
    {
      ((DWORD *)sLocal.dwInputBuf)[1] = dwFlag;

      /* make call */
      if (!runRow (&sLocal))
	{
	  returnVal = FALSE;
	}
    }
  
  return (returnVal);
}

/****** OutSize Check *****/

/*
 * confirm that the output size is being handled correctly.
 *
 * bTestLpBytesReturned set to true if testing with lpBytesReturned
 * passed to the IOCTL, false if not.
 */
BOOL
runOutSizeOldModel (BOOL bExpectToPass, BOOL bTestLpBytesReturned)
{
  BOOL returnVal = TRUE;
 
  /* setup for old model */
  DWORD dwInBuf;
  /* only used for output, so the value doesn't matter */
  DWORD dwOutBuf;

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_REQUEST_SYSINTR,
      _T("Output size"),
    
      &dwInBuf,
      0,			// will be set later by the test 
      &dwOutBuf,
      0,			// will be set later by the test
      NULL
    };

  Info (_T("Working on Old Model..."));

  /* set up for old model */
  if (!fillInBoundArrayOldModel (&dwInBuf, 1))
    {
      Error (_T("Couldn't fill inbound array for old model."));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  /* only one possible good value for the old model */
  sStart.dwInputBufSize = sizeof (DWORD);

  if (!runOutSize (&sStart, bExpectToPass, bTestLpBytesReturned))
    {
      returnVal = FALSE;
    }

 cleanAndReturn:

  return (returnVal);

}

/*
 * confirm that the output size is being handled correctly.  This test
 * pushes values through for both the new model and the old *model.
 *
 * bTestLpBytesReturned set to true if testing with lpBytesReturned
 * passed to the IOCTL, false if not.
 */
BOOL
runOutSizeNewModel (BOOL bExpectToPass, BOOL bTestLpBytesReturned)
{
  BOOL returnVal = TRUE;

  /* setup for new model */
  DWORD dwInBuf [MAX_INBOUND_BUF_LENGTH_DW];
  /* only used for output, so the value doesn't matter */
  DWORD dwOutBuf;

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_REQUEST_SYSINTR,
      _T("Output size"),
    
      dwInBuf,
      0,			// will be set later by the test 
      &dwOutBuf,
      0,			// will be set later by the test
      NULL
    };

  Info (_T("Working on New Model..."));

  if (!fillInBoundArrayNewModel (dwInBuf, MAX_INBOUND_BUF_LENGTH_DW))
    {
      Error (_T("Couldn't fill in bound array for new model."));
      returnVal = FALSE;
      goto cleanAndReturn;
    }

  /* 
   * for, now assume that it must support at least one IRQ.
   */
  sStart.dwInputBufSize = 3 * sizeof (DWORD);

  if (!runOutSize (&sStart, bExpectToPass, bTestLpBytesReturned))
    {
      returnVal = FALSE;
    }

 cleanAndReturn:

  return (returnVal);

}

/*
 * for the given structure, vary the output size.  Only sizeof (DWORD)
 * is supported.
 *
 * This supports both the old model and the model.  The inbound params
 * on the structure are not touched by this test.
 *
 * bTestLpBytesReturned set to true if testing with lpBytesReturned
 * passed to the IOCTL, false if not.
 */
BOOL
runOutSize (const sAnIoctlCall *const sRow, BOOL bExpectToPass, 
	    BOOL bTestLpBytesReturned)
{

  if (!sRow)
    {
      return (FALSE);
    }

  if (bTestLpBytesReturned && !bExpectToPass)
    {
      IntErr (_T("We must expect to pass if using lpBytesReturned."));
      return (FALSE);
    }

  BOOL returnVal = TRUE;

  /* local copy of the structure */
  sAnIoctlCall sLocal;

  memcpy (&sLocal, sRow, sizeof (sAnIoctlCall));

  /* array so that the call is "correct" */
  DWORD dwOutBuf [MAX_INBOUND_BUF_LENGTH_DW];

  sLocal.dwOutputBuf = dwOutBuf;

  if (bExpectToPass)
    {
      /* IOCTL should work for these test cases */

      /* for each possible size in the array */
      for (DWORD dwSize = sizeof (DWORD); 
	   dwSize < MAX_INBOUND_BUF_LENGTH_DW * sizeof (DWORD); dwSize++)
	{
	  Info (_T("Working on output buffer size of %u"), dwSize);

	  sLocal.dwOutputBufSize = dwSize;

	  /* make call */
	  /*
	   * if we are testing lpBytesReturned, output size must always
	   * be sizeof (DWORD).  If not testing this, runRow will
	   * ignore these args.
	   */
	  if (!runRow (&sLocal, bExpectToPass, bTestLpBytesReturned,
		       sizeof (DWORD)))
	    {
	      returnVal = FALSE;
	    }
	}
    }
  else
    {
      /* IOCTL should fail for these test cases */

      /* for each possible size in the array */
      for (DWORD dwSize = 0; dwSize < sizeof (DWORD); dwSize++)
	{
	  if (dwSize == sizeof (DWORD))
	    {
	      /* this is the valid outsize, so skip it */
	      continue;
	    }

	  Info (_T("Working on output buffer size of %u"), dwSize);

	  sLocal.dwOutputBufSize = dwSize;

	  /* make call */
	  if (!runRow (&sLocal))
	    {
	      returnVal = FALSE;
	    }
	}
    }
  
  return (returnVal);
}

/*************************  helpers ********************************/

BOOL
fillInBoundArrayNewModel (__out_ecount (dwNumOfDwords) DWORD *const pdwBuf, 
			  DWORD dwNumOfDwords)
{
  if (!pdwBuf)
    {
      return (FALSE);
    }

  /* new model uses first two DWORDS as flags */
  if (dwNumOfDwords == 0) return (TRUE);

  pdwBuf[0] = SYSINTR_UNDEFINED;

  if (dwNumOfDwords == 1) return (TRUE);

  /* need to assume something.  use dynamic.  arbitrary. */
  pdwBuf[1] = OAL_INTR_DYNAMIC;

  return (fillInBoundArrayOldModel (pdwBuf + 2, dwNumOfDwords - 2));

}

/*
 * command line args or defines tell us which IRQ range can be used.
 * for these tests we want an IRQ range that is valid.  We therefore
 * take what the user gives us and start searching for valid IRQs.  
 * If we find them we fill in the array and return it.  If not we
 * error out.
 *
 * const says that pointers don't change, but objects can
 */
BOOL
fillInBoundArrayOldModel (__out_ecount (dwNumOfDwords) DWORD *const pdwBuf, 
			  DWORD dwNumOfDwords)
{
  if ((!pdwBuf) || (dwNumOfDwords == 0))
    {
      return (FALSE);
    }

  BOOL returnVal = FALSE;

  if (!inKernelMode())
    {
      Info (_T("Not in K-mode, so can't determine which IRQs are valid."));
      Info (_T("Choosing ones that will probably work."));

      for (DWORD dwPos = 0; dwPos < dwNumOfDwords; dwPos++)
	{
	  /* arbitrary */
	  pdwBuf[dwPos] = dwPos + 10;
	}
      returnVal = TRUE;
      goto cleanAndReturn;
    }

  /* the ranges supported by the test, inclusive */
  DWORD dwIRQBegin = TEST_IRQ_BEGIN;
  DWORD dwIRQEnd = TEST_IRQ_END;

  /* adjust the ranges if need be */
  if (!handleCmdLineIRQRange (&dwIRQBegin, &dwIRQEnd))
    {
      Error (_T("Couldn't parse command line."));
      goto cleanAndReturn;
    }

  DWORD dwArrayPosToFill = 0;

  DWORD dwIRQ = dwIRQBegin;

  for (BOOL bJustRolled = FALSE;
       dwIRQ <= dwIRQEnd && !bJustRolled; dwIRQ++)
    {
      DWORD dwSysIntr;
      
      DWORD dwInBuf[3] = {-1, OAL_INTR_STATIC};
      dwInBuf[2] = dwIRQ;

      if (!KernelIoControl (IOCTL_HAL_REQUEST_SYSINTR, 
			   dwInBuf, sizeof (dwInBuf), 
			   &dwSysIntr, sizeof (dwSysIntr), NULL))
	{
	  /* already in use */
	  Info (_T("IRQ %u is already in use."), dwIRQ);
	  continue;
	}

      /* this one is good */
      
      /* free it */
      if (!KernelIoControl (IOCTL_HAL_RELEASE_SYSINTR, 
			    &dwSysIntr, sizeof (dwSysIntr), 
			    NULL, NULL, NULL))
	{
	  Error (_T("Couldn't free %u, which was just allocated."),
		 dwSysIntr);
	  goto cleanAndReturn;
	}

      PREFAST_SUPPRESS (2015, "See check right below");
      pdwBuf[dwArrayPosToFill] = dwIRQ;

      if (dwArrayPosToFill == dwNumOfDwords - 1)
	{
	  /* done filling when position equals length - 1 */
	  break;
	}

      dwArrayPosToFill++;

      /* if we just worked on MAX_DWORD then want to bail */
      bJustRolled = (dwIRQ == MAX_DWORD);
    }

  if (dwArrayPosToFill == dwNumOfDwords - 1)
    {
      /* we successfully filled the array */
      returnVal = TRUE;
    }
  else
    {
      Error (_T("Couldn't fill array with valid IRQs."));
    }

 cleanAndReturn:

  return (returnVal);
}

/***************** TEST CASES for IOCTL_HAL_RELEASE_SYSINTR ******************/

/*
 * input must be 4 bytes.  No other values are allowed.
 */
BOOL
runBadInSize_IOCTL_HAL_RELEASE_SYSINTR ()
{
  BOOL returnVal = FALSE;

  /* set up */
  DWORD dwInBuf [MAX_INBOUND_BUF_LENGTH_DW];  

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_RELEASE_SYSINTR,
      _T("Bad inBoundSize"),
    
      dwInBuf,
      0,			// will be set later by the test 
      NULL,			// ignored by code
      0,
      NULL
    };

  /* 
   * sysintr for the k-mode case.  This is the sysintr that is
   * actually allocated to make the code work.
   */
  DWORD dwSysIntr;

  if (inKernelMode())
    {
      /* 
       * we need to find a valid IRQ that we can then allocate a sysIntr
       * to.  This makes sure that we have something valid to free.
       */
  
      DWORD dwGoodIRQ;

      if (!fillInBoundArrayOldModel (&dwGoodIRQ, 1))
	{
	  Error (_T("Couldn't find an IRQ to allocate."));
	  goto cleanAndReturn;
	}

      /* now, allocate this IRQ */

      DWORD dwBuf[3] = {-1, OAL_INTR_STATIC};
      dwBuf[2] = dwGoodIRQ;
      if (!KernelIoControl (IOCTL_HAL_REQUEST_SYSINTR, 
			    dwBuf, sizeof (dwBuf), 
			    &dwSysIntr, sizeof (dwSysIntr), NULL))
	{
	  Error (_T("IRQ %u should be free for static and now isn't."),
		 dwGoodIRQ);
	  goto cleanAndReturn;
	}

      /* 
       * IRQ is allocated, and we therefore have a sysintr that can now
       * be freed.  Use this as the test case.
       */
      dwInBuf[0] = dwSysIntr;
    }
  else /* no in k-mode */
    {
      /* arbitrary */
      dwInBuf[0] = 18;
    }


  /* fill in the rest with random stuff */
  for (DWORD dwPos = 1; dwPos < MAX_INBOUND_BUF_LENGTH_DW; dwPos++)
    {
      /* fill with random values */
      dwInBuf[dwPos] = getRandomInRange(0, 71);
    }

  for (DWORD dwSize = 0; dwSize < MAX_INBOUND_BUF_LENGTH_DW * sizeof (DWORD); 
       dwSize++)
    {
      if (dwSize == sizeof (DWORD))
	{
	  /* this is the valid insize, so skip it */
	  continue;
	}

      Info (_T("Setting InputBufSize to %u."), dwSize);

      sStart.dwInputBufSize = dwSize;

     /* make call */
      if (!runRow (&sStart))
	{
	  /* 
	   * something when wrong.  We freed the sysintr.  At this point
	   * we can't continue since the preconditions are wrong.  Bail instead
	   * of trying to fix it.
	   */
	  goto cleanAndReturn;
	}
    }

  if (inKernelMode())
    {
      /* release sysintr */
      if (!KernelIoControl (IOCTL_HAL_RELEASE_SYSINTR, 
			    &dwSysIntr, sizeof (dwSysIntr),
			    NULL, NULL, NULL))
	{
	  Error (_T("Couldn't free sysintr %u."), dwSysIntr);
	  goto cleanAndReturn;
	}
    }

  returnVal = TRUE;

 cleanAndReturn:

  return (returnVal);
}

/***************** TEST CASES for IOCTL_HAL_REQUEST_IRQ *********************/

/*
 */
BOOL
runBadInSize_IOCTL_HAL_REQUEST_IRQ ()
{
  BOOL returnVal = TRUE;

  /* set up - size if arbitrary */
  DWORD dwInBuf [sizeof (DEVICE_LOCATION) * 4];  
  /* ignored in this test */
  DWORD dwOutBuf;

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_REQUEST_IRQ,
      _T("Bad inBoundSize"),
    
      dwInBuf,
      0,			// will be set later by the test 
      &dwOutBuf,
      sizeof (dwOutBuf),
      NULL
    };

  for (DWORD dwPos = 0; dwPos < sizeof (DEVICE_LOCATION) * 4; dwPos++)
    {
      /* fill with random values */
      /* 1000000 is arbitrary */
      dwInBuf[dwPos] = getRandomInRange(0, 1000000);
    }

  for (DWORD dwSize = 0; dwSize < sizeof (DEVICE_LOCATION) * 4; dwSize++)
    {
      if (dwSize == sizeof (DEVICE_LOCATION))
	{
	  /* this is the valid insize, so skip it */
	  continue;
	}

      Info (_T("Setting InputBufSize to %u."), dwSize);

      sStart.dwInputBufSize = dwSize;

     /* make call */
      if (!runRow (&sStart))
	{
	  returnVal = FALSE;
	}
    }

  return (returnVal);
}


/*
 * note that we have no good way to determine what a valid REQUEST_IRQ 
 * contains.  The inputs are too spread out.  Without this, we push 
 * some values through it and hope that we see errors.  We really 
 * can't do much else.
 */
BOOL
runBadOutSize_IOCTL_HAL_REQUEST_IRQ ()
{
  BOOL returnVal = TRUE;

  /* set up - size is arbitrary */
  DWORD dwInBuf [sizeof (DEVICE_LOCATION)];

  /* outbound buffer used for test */
  DWORD dwOutBuf [MAX_INBOUND_BUF_LENGTH_DW];

  sAnIoctlCall sStart = 
    {
      IOCTL_HAL_RELEASE_SYSINTR,
      _T("Bad inBoundSize"),
    
      dwInBuf,			// input that will be constant
      sizeof (DEVICE_LOCATION),
      dwOutBuf,			// input under test
      0,			// will be set by test
      NULL
    };

  /*
   * fill inbound with random.  Not nearly ideal, but we can do
   * nothing else. 
   */
  for (DWORD dwPos = 0; dwPos < sizeof (DEVICE_LOCATION); dwPos++)
    {
      /* fill with random values */
      dwInBuf[dwPos] = getRandomInRange(0, 71);
    }

  /* fill outbound with random for test. */
  for (DWORD dwPos = 0; dwPos < MAX_INBOUND_BUF_LENGTH_DW; dwPos++)
    {
      /* fill with random values */
      dwOutBuf[dwPos] = getRandomInRange(0, 71);
    }

  for (DWORD dwSize = 0; dwSize < sizeof (DWORD); dwSize++)
    {
      Info (_T("Setting OutputBufSize to %u."), dwSize);

      sStart.dwOutputBufSize = dwSize;

     /* make call */
      if (!runRow (&sStart))
	{
	  returnVal = FALSE;
	}
    }

  return (returnVal);
}

