/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-3
**  FileName: VerifyFreezeFrameSupportAndData.c
**  Author:   EnGenius
**  Date:     2/25/2022
**  Email:    <support@autosinnovate.org>
**
**  Purpose:  SAE J1699-3 Vehicle OBD II Compliance Test Cases Source Code.
**            This source code is intended to run the tests described in
**            the SAE J1699-3 document in an automated manner, when compiled
**            and used with an SAE J2534-compatible pass-thru device.
**
**            File j1699.c contains information on building and running this test.
**
**  Description:
**
**  Modifications:  12/07/2022  Initial Version
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function declarations
#include <string.h>   // C Library character array declarations
#include <time.h>     // C Library time and date declarations
#include <windows.h>  // Windows API declarations
#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations


// Funtion prototypes
STATUS RequestFFSupportData   ( unsigned short *pFreezeFrameDTC, unsigned long FFDTCCount );
STATUS RequestFFPid2Data      ( unsigned short *pFreezeFrameDTC, BYTE *pFFDTCSupportCount, BYTE *pFFDTCCount, BOOL *pbFFSupOnPendFault );
STATUS VerifyFFPidSupportData ( unsigned short *pFreezeFrameDTC );
STATUS VerifyFFDTCinDTCList   ( unsigned short *pFreezeFrameDTC );
STATUS VerifyAllFFData        ( unsigned short *pFreezeFrameDTC, unsigned short FFDTCSupportCount, unsigned long FFDTCCount );
STATUS VerifyGroupFFSupport   ( unsigned short *pFreezeFrameDTC );
STATUS VerifyGroupFFResponse  ( unsigned short *pFreezeFrameDTC );
BOOL   IsPidSupported         ( BYTE EcuIdx, unsigned int PidIndex );


/*******************************************************************************
**
**  Function:  VerifyFFSupportAndData
**
**  Purpose:   Verify FF freeze frame support and data
**
*******************************************************************************/
STATUS VerifyFFSupportAndData ( void )
{
	STATUS                eRetCode = PASS;

	static unsigned short FreezeFramePendingDTC[MAX_ECUS] = {0};  // array of pending Freeze Frame DTCs
	unsigned short        FreezeFrameDTC[MAX_ECUS];               // array of the Freeze Frame data from SID $02 PID $02 request
	BOOL                  bFFSupOnPendFault;                      // Set if Freeze Frame stored on pending fault
	BYTE                  FFDTCCount;                             // the count of ECUs which report a Freeze Frame DTC
	BYTE                  FFDTCSupportCount;                      // the count of ECUs which support SID $02 PID $02
	unsigned long         InitialFailureCount = 0;


	// Initialize data
	FFDTCCount = 0;
	FFDTCSupportCount = 0;
	bFFSupOnPendFault = FALSE;
	memset ( FreezeFrameDTC,
	         0,
	         sizeof (FreezeFrameDTC) );

	InitialFailureCount = GetFailureCount ( );

	// Request SID 2 PID 2 data
	if ( RequestFFPid2Data ( FreezeFrameDTC,
	                         &FFDTCSupportCount,
	                         &FFDTCCount,
	                         &bFFSupOnPendFault ) != PASS )
	{
		eRetCode = FAIL;
	}

	// Request SID 2 support data
	if ( RequestFFSupportData ( FreezeFrameDTC,
	                            FFDTCCount ) != PASS )
	{
		eRetCode = FAIL;
	}

	// Check if a DTC is stored
	// If FF stored on pending fault then verify that it is stored in SID $07
	if ( gbDTCStored       == TRUE ||
	     bFFSupOnPendFault == TRUE )  // If FF stored on pending fault
	{
		// Verify the DTC in the freeze frame is in the list of stored DTCs
		if ( VerifyFFDTCinDTCList ( FreezeFrameDTC ) != PASS )
		{
			eRetCode = FAIL;
		}

		// Verify that all SID 2 PID data is valid
		if ( VerifyAllFFData ( FreezeFrameDTC,
		                       FFDTCSupportCount,
		                       FFDTCCount ) != PASS )
		{
			eRetCode = FAIL;
		}
	}
	else
	{
		// Verify that all SID 2 PID data is valid
		if ( VerifyAllFFData ( FreezeFrameDTC,
		                       FFDTCSupportCount,
		                       FFDTCCount ) != PASS )
		{
			eRetCode = FAIL;
		}
	}

	// test 6.5.1 - save FF DTC to compare during next test phase (7.5.1)
	if ( geTestPhase == eTestPendingDTC )
	{
		BYTE  EcuIdx;

		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			FreezeFramePendingDTC[EcuIdx] = FreezeFrameDTC[EcuIdx];
		}
	}

	// test 7.5.1 - compare FF DTC to previous test phase (6.5.1)
	if ( geTestPhase == eTestConfirmedDTC )
	{
		BYTE  EcuIdx;

		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			if ( FreezeFramePendingDTC[EcuIdx] != 0 &&
			     FreezeFramePendingDTC[EcuIdx] != FreezeFrameDTC[EcuIdx] )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_ALL_PROMPT,
				      "ECU %X  Confirmed Freeze Frame DTC doesn't match Pending FF DTC\n",
				      GetEcuId ( EcuIdx ) );
				eRetCode = FAIL;
			}
		}
	}

	// Try group support if ISO15765
	if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 &&
	     FFDTCCount != 0 )
	{
		STATUS eRetCode = VerifyGroupFFSupport ( FreezeFrameDTC );
		if ( eRetCode != PASS )
		{
			eRetCode = eRetCode;
		}

		// Tests 6.x - 9.x only
		if ( geTestPhase == eTestPendingDTC    || geTestPhase == eTestConfirmedDTC ||
		     geTestPhase == eTestFaultRepaired || geTestPhase == eTestNoFault3DriveCycle )
		{
			eRetCode = VerifyGroupFFResponse ( FreezeFrameDTC );
			if ( eRetCode != PASS )
			{
				eRetCode = eRetCode;
			}
		}
	}

	if ( InitialFailureCount != GetFailureCount ( ) )
	{
		// There could have been early/late responses that weren't treated as FAIL
		// or the test failed earlier
		return FAIL;
	}
	else
	{
		return eRetCode;
	}

}


/*******************************************************************************
**
**  Function:  RequestFFSupportData
**
**  Purpose:   Verify that SID 2 PID 00 returns a support record.
**             Continue requesting support PIDS thru highest supported group.
**
*******************************************************************************/
STATUS RequestFFSupportData ( unsigned short *pFreezeFrameDTC,
                              unsigned long   FFDTCCount )
{
	REQ_MSG        stReqMsg;
	BYTE           EcuIdx;
	STATUS         eRetCode = PASS;

	unsigned long  IdIdx;
	unsigned long  PidSupportFlags;


	PidSupportFlags = 0;  // Used to verify that if no PID support, failure may be flagged

	// Request SID 2 support data
	for ( IdIdx = 0x00;
	      IdIdx < 0x100;
	      IdIdx += 0x20 )
	{
		stReqMsg.SID    = 0x02;
		stReqMsg.NumIds = 2;
		stReqMsg.ID[0]  = (BYTE)IdIdx;
		stReqMsg.ID[1]  = 0x00; // Frame #0
		if ( RequestSid ( &stReqMsg, REQ_MSG_ALLOW_NO_RESPONSE ) == FAIL )
		{
			// If there were any Freeze Frame DTCs, there must be a response to PID 0x00
			if ( (IdIdx == 0x00 && FFDTCCount != 0 ) ||
			      IdIdx != 0x00  )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "No Response to SID $02 PID-Supported PID $%02X request\n",
				      IdIdx );
				return FAIL;
			}
		}

		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// If the next group is supported, request it
			if ( gstResponse[EcuIdx].FFSupport[IdIdx >> 5].SupBits[3] & 0x01 )
			{
				break;  // leave ECU for loop
			}
		}

		// If no ECUs support the next group, stop requests
		if ( EcuIdx >= gNumOfECUs )
		{
			break;  // leave support for loop
		}
	}


	// Flag error if ECU indicates no support
	if ( VerifyFFPidSupportData ( pFreezeFrameDTC ) == FAIL )
	{
		eRetCode = FAIL;
	}


	// Request next unsupported PID-support PID
	if ( IdIdx < 0xE0 )
	{
		stReqMsg.SID    = 0x02;
		stReqMsg.NumIds = 2;
		stReqMsg.ID[0]  = (BYTE)(IdIdx += 0x20);
		stReqMsg.ID[1]  = 0x00; // Frame #0
		if ( RequestSid ( &stReqMsg, REQ_MSG_NORMAL|REQ_MSG_IGNORE_NO_RESPONSE ) != FAIL )
		{
			// J1850 & ISO9141 - No response preferred, but positive response allowed
			if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
			{
				for ( EcuIdx = 0;
				      EcuIdx < gNumOfECUs;
				      EcuIdx++ )
				{
					if ( gstResponse[EcuIdx].FFSize != 0 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  Unexpected response to unsupported SID $02 PID $%02X!\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						eRetCode = FAIL;
					}
				}
			}
		}
	}


	// Verify ECU did not drop out
	if ( VerifyECUCommunication ( ) != PASS )
	{
		eRetCode = FAIL;
	}
	return eRetCode;
}


/*******************************************************************************
**
**  Function:  RequestFFPid2Data
**
**  Purpose:   Verify that SID 2 PID 02 returns stored DTC.
**             Assuming SID 2 PID 2 was supported by the controller,
**             data should be returned.
**
*******************************************************************************/
STATUS RequestFFPid2Data ( unsigned short *pFreezeFrameDTC,
                           BYTE           *pFFDTCSupportCount,
                           BYTE           *pFFDTCCount,
                           BOOL           *pbFFSupOnPendFault )
{
	REQ_MSG  stReqMsg;
	BYTE     EcuIdx;
	STATUS   eRetCode = PASS;


	// Request SID 2 PID 2 data
	stReqMsg.SID    = 0x02;
	stReqMsg.NumIds = 2;
	stReqMsg.ID[0]  = 0x02;
	stReqMsg.ID[1]  = 0x00; // Frame #0
	gbIgnoreUnsupported = TRUE;
	if ( RequestSid ( &stReqMsg, REQ_MSG_ALLOW_NO_RESPONSE ) == FAIL )
	{
		gbIgnoreUnsupported = FALSE;
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $02 PID $02 request error\n" );
		return FAIL;
	}
	gbIgnoreUnsupported = FALSE;

	// Check for freeze frame DTCs
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		// Clear Freeze Frame Array entry for this ECU
		pFreezeFrameDTC[EcuIdx] = 0;

		// Skip if no response or no DTCs stored
		if ( gstResponse[EcuIdx].FFSize != 0 )
		{
			// Increment the count of ECUs which support SID $02 PID $02
			*pFFDTCSupportCount = *pFFDTCSupportCount + 1;

			if ( gstResponse[EcuIdx].FF[1] != 0 ||
			     gstResponse[EcuIdx].FF[2] != 0 ||
			     gstResponse[EcuIdx].FF[3] != 0 )
			{
				// Increment the count of ECUs which report a Freeze Fram DTC
				*pFFDTCCount = *pFFDTCCount + 1;

				// Save the DTC
				pFreezeFrameDTC[EcuIdx] = (gstResponse[EcuIdx].FF[2] << 8) +
				                           gstResponse[EcuIdx].FF[3];

				// Check if a DTC is not supposed to be present
				if ( gbDTCStored     == FALSE &&
				     gbDTCPending    == FALSE &&
				     gbDTCHistorical == FALSE )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $02 PID $02 Frame %d DTC stored\n",
					      GetEcuId ( EcuIdx ),
					      gstResponse[EcuIdx].FF[1] );
					eRetCode = FAIL;
				}

				else if ( gbDTCPending == TRUE &&
				          gbDTCStored  == FALSE )
				{
					Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $02 PID $02 Frame %d DTC stored on pending fault\n",
					      GetEcuId ( EcuIdx ),
					      gstResponse[EcuIdx].FF[1] );
					*pbFFSupOnPendFault = TRUE;
				}
			}
		}
	}

	// Test 5.11.1 - If all ECUs report Freeze Frame DTCs stored, it is a Failure
	if ( geTestPhase == eTestNoDTC && gTestSubsection == 11 &&
	     *pFFDTCCount == *pFFDTCSupportCount )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "All ECUs reported Freeze Frame DTCs stored\n" );
		eRetCode = FAIL;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  VerifyFFPidSupportData
**
**  Purpose:   Verify each controller supports at a minimum one PID.
**             Any ECU that responds that does not support at least
**             one PID is flagged as an error.
**
*******************************************************************************/
STATUS VerifyFFPidSupportData ( unsigned short *pFreezeFrameDTC )
{
	BYTE           EcuIdx;
	STATUS         eRetCode = PASS;

	BOOL           bFFSupported;
	unsigned long  SupIdx;


	// For each ECU
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( pFreezeFrameDTC[EcuIdx] != 0x00 )
		{
			bFFSupported = FALSE;
			for ( SupIdx = 0;
			      SupIdx < gstResponse[EcuIdx].FFSupportSize;
			      SupIdx++ )
			{
				// If MID is supported, keep looking
				if (  gstResponse[EcuIdx].FFSupport[SupIdx].SupBits[0]  ||
				      gstResponse[EcuIdx].FFSupport[SupIdx].SupBits[1]  ||
				      gstResponse[EcuIdx].FFSupport[SupIdx].SupBits[2]  ||
				     (gstResponse[EcuIdx].FFSupport[SupIdx].SupBits[3] & 0xFE) != 0x00 )
				{
					bFFSupported = TRUE;
					break;  // leave support for loop
				}
			}

			if ( bFFSupported == FALSE && gstResponse[EcuIdx].FFSupportSize > 0 )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $02 PID-Supported PIDs indicate no PIDs supported",
				      GetEcuId ( EcuIdx ) );
				eRetCode = FAIL;
			}
		}
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  VerifyFFDTCinDTCList
**
**  Purpose:   Verify the DTC in the freeze frame is in the list of stored DTCs.
**
*******************************************************************************/
STATUS VerifyFFDTCinDTCList( unsigned short *pFreezeFrameDTC )
{
	STATUS         eRetCode = PASS;

	BYTE           FFEcuIdx;
	BYTE           DTCEcuIdx;
	unsigned long  DTCIdx;
	unsigned long  DTC;
	unsigned long  StrdDTCCount = 0;
	unsigned long  FFDTCCount = 0;
	unsigned long  MtchdDTCCount = 0;
	BOOL           bMtchdDTC = FALSE;


	// Count all DTCs
	for ( DTCEcuIdx = 0;
	      DTCEcuIdx < gNumOfECUs;
	      DTCEcuIdx++ )
	{
		if ( gstDTCList[DTCEcuIdx].Size != 0 )
		{
			for ( DTCIdx = 0;
			      DTCIdx < gstDTCList[DTCEcuIdx].Size;
			      DTCIdx += 2 )
			{
				if ( gstDTCList[DTCEcuIdx].DTC[DTCIdx]   != 0 ||
				     gstDTCList[DTCEcuIdx].DTC[DTCIdx+1] != 0 )
				{
					StrdDTCCount++;
				}
			}
		}
	}

	// Count all Freeze Frames
	for ( FFEcuIdx = 0;
	      FFEcuIdx < gNumOfECUs;
	      FFEcuIdx++ )
	{
		if ( pFreezeFrameDTC[FFEcuIdx] != 0x0000 )
		{
			FFDTCCount++;
		}
	}

	// Verify each Freeze DTCs is in the list of Confirmed or Pending DTCs
	for ( FFEcuIdx = 0;
	      FFEcuIdx < gNumOfECUs;
	      FFEcuIdx++ )
	{
		bMtchdDTC = FALSE;

		if ( pFreezeFrameDTC[FFEcuIdx] != 0x0000 )
		{
			for ( DTCEcuIdx = 0;
			      DTCEcuIdx < gNumOfECUs;
			      DTCEcuIdx++ )
			{
				for ( DTCIdx = 0;
				      DTCIdx < gstDTCList[DTCEcuIdx].Size;
				      DTCIdx += 2 )
				{
					DTC = (gstDTCList[DTCEcuIdx].DTC[DTCIdx] << 8) +
					       gstDTCList[DTCEcuIdx].DTC[DTCIdx + 1];

					if ( DTC != 0x0000 )
					{
						if ( pFreezeFrameDTC[FFEcuIdx] == DTC )
						{
							bMtchdDTC = TRUE;
							MtchdDTCCount++;
							break;  // leave DTC for loop
						}
					}
				}

				if ( bMtchdDTC == TRUE )
				{
					break;  // leave ECU for loop
				}
			}

			if ( !bMtchdDTC )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  Freeze Frame DTC $%04X not found in list of stored or pending DTCs on any ECU\n",
				      GetEcuId ( FFEcuIdx ),
				      pFreezeFrameDTC[FFEcuIdx] );
				eRetCode = FAIL;
			}
		}
	}


	if ( StrdDTCCount !=0  &&
	     MtchdDTCCount == 0 )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Stored DTC with no Freeze Frame DTC on any ECU\n" );
		eRetCode = FAIL;
	}


	return eRetCode;
}


/*******************************************************************************
**
**  Function:  VerifyAllFFData
**
**  Purpose:   Verify that all SID 2 PID data is valid.
**             Verification of minimum support list is performed here.
**
*******************************************************************************/
STATUS VerifyAllFFData ( unsigned short *pFreezeFrameDTC,
                         unsigned short  FFDTCSupportCount,
                         unsigned long   FFDTCCount )
{
	REQ_MSG       stReqMsg;
	BYTE          EcuIdx;
	STATUS        eRetCode = PASS;
	STATUS        eRetVal = PASS;

	unsigned long IdIdx;
	BOOL          bPIDSupported = FALSE;

	BOOL          bPid02Supported = FALSE;  // Set if SID $02 PID $02 is supported
	BOOL          bPid04Supported = FALSE;  // Set if SID $02 PID $04 is supported
	BOOL          bPid05Supported = FALSE;  // Set if SID $02 PID $05 is supported
	BOOL          bPid0CSupported = FALSE;  // Set if SID $02 PID $0C is supported
	BOOL          bPid0DSupported = FALSE;  // Set if SID $02 PID $0D is supported
	BOOL          bPid11Supported = FALSE;  // Set if SID $02 PID $11 is supported
	BOOL          bPid45Supported = FALSE;  // Set if SID $02 PID $45 is supported
	BOOL          bPid49Supported = FALSE;  // Set if SID $02 PID $49 is supported
	BOOL          bPid67Supported = FALSE;  // Set if SID $02 PID $67 is supported


	// Verify that all SID 2 PID data is valid
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		// If more than one ECU supports SID $02 Frame $00 PID $02,
		// clear the PID flags to check for support by each of those ECUs
		if ( FFDTCSupportCount > 1 )
		{
			bPid02Supported = FALSE;
			bPid04Supported = FALSE;
			bPid05Supported = FALSE;
			bPid0CSupported = FALSE;
			bPid0DSupported = FALSE;
			bPid11Supported = FALSE;
			bPid45Supported = FALSE;
			bPid49Supported = FALSE;
			bPid67Supported = FALSE;
		}


		// For each PID group
		for ( IdIdx = 0x01;
		      IdIdx < 0x100;
		      IdIdx++ )
		{
			// skip PID Supported PIDs and unsupported PIDs
			if ( IdIdx != 0x00 && IdIdx != 0x20 &&
			     IdIdx != 0x40 && IdIdx != 0x60 &&
			     IdIdx != 0x80 && IdIdx != 0xA0 &&
			     IdIdx != 0xC0 && IdIdx != 0xE0 &&
			     IsPidSupported ( EcuIdx, IdIdx ) == TRUE )
			{
				// Set flags if PID supported
				if ( IdIdx == 0x02 )
				{
					bPid02Supported = TRUE;
				}
				else if ( IdIdx == 0x04 )
				{
					bPid04Supported = TRUE;
				}
				else if ( IdIdx == 0x05 )
				{
					bPid05Supported = TRUE;
				}
				else if ( IdIdx == 0x0C )
				{
					bPid0CSupported = TRUE;
				}
				else if ( IdIdx == 0x0D )
				{
					bPid0DSupported = TRUE;
				}
				else if ( IdIdx == 0x11 )
				{
					bPid11Supported = TRUE;
				}
				else if ( IdIdx == 0x45 )
				{
					bPid45Supported = TRUE;
				}
				else if ( IdIdx == 0x49 )
				{
					bPid49Supported = TRUE;
				}
				else if ( IdIdx == 0x67 )
				{
					bPid67Supported = TRUE;
				}

				stReqMsg.SID    = 0x02;
				stReqMsg.NumIds = 2;
				stReqMsg.ID[0]  = (BYTE)IdIdx;
				stReqMsg.ID[1]  = 0x00; // Frame #0
				if ( (eRetVal = RequestSid ( &stReqMsg, REQ_MSG_NORMAL|REQ_MSG_IGNORE_NO_RESPONSE )) == FAIL )
				{
					// There must be a response for ISO15765 protocol when a freeze frame DTC is set
					if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 &&
					     pFreezeFrameDTC[EcuIdx] != 0x00 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  No response to SID $02 PID $%02X request\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						eRetCode = FAIL;
					}
				}

				else
				{
					if ( eRetVal == ERRORS )
					{
						// message was early/late; FAIL but keep processing
						eRetCode = FAIL;
					}

					//  If PID $02 is not = $0000, it is a Failure
					if ( geTestPhase == eTestNoDTC && gTestSubsection == 11 &&
					     IdIdx == 0x02 &&
					     (gstResponse[EcuIdx].FF[2] != 0x00 ||
					      gstResponse[EcuIdx].FF[3] != 0x00) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  Response to SID $02 PID $02 request indicates DTC present\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						eRetCode = FAIL;
					}

					// If ISO15765 and PID $02 == 0, there should be no response to PID request
					if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 &&
					     gstResponse[EcuIdx].FFSize != 0 &&
					     pFreezeFrameDTC[EcuIdx] == 0x00 &&
					     IdIdx != 0x02 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  Response to SID $02 PID $%02X request when PID $02 is 0\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						eRetCode = FAIL;
					}

					//  If PID is supported and there is a Freeze Frame DTC, there should be a response
					if ( gstResponse[EcuIdx].FFSize == 0 &&
					     pFreezeFrameDTC[EcuIdx] != 0x00 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  No SID $02 PID $%02X data\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						eRetCode = FAIL;
					}
				}

			} // if ( IsFFPidSupported...

		} // end for (IdIdx...


		// Test If this ECU has a Freeze Frame DTCs, it must support at least one PID
		if ( pFreezeFrameDTC[EcuIdx] != 0 )
		{
			for ( IdIdx = 0;
			      IdIdx < gstResponse[EcuIdx].FFSupportSize;
			      IdIdx++ )
			{
				// If this ECU supports a PID, no need to check anymore PIDs on this ECU
				if ( gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[0] ||
				     gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[1] ||
				     gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[2] ||
				     (gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[3] & 0xFE) != 0x00 )
				{
					bPIDSupported = TRUE;
					break;  // leave support for loop
				}
			}

			// There must be a supported PID
			if ( bPIDSupported == FALSE )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $02 no PIDs are supported",
				      GetEcuId ( EcuIdx ) );
				eRetCode = FAIL;
			}
		}


		// If more than one ECU supports SID $02 Frame $00 PID $02 and
		// the current ECU has a Freeze Frame DTC, check that the required PIDs are supported
		if ( FFDTCSupportCount > 1 &&
		     pFreezeFrameDTC[EcuIdx] )
		{
			if ( bPid02Supported == FALSE   ||
			     bPid04Supported == FALSE   ||
			     (bPid05Supported == FALSE && bPid67Supported == FALSE) ||
			     bPid0CSupported == FALSE   ||
			     ((gstUserInput.eComplianceType == US_OBDII || gstUserInput.eComplianceType == HD_OBD) && bPid0DSupported == FALSE) ||
			     (bPid11Supported == FALSE && bPid45Supported == FALSE && bPid49Supported == FALSE) )
			{
				if ( gstUserInput.eComplianceType == US_OBDII || gstUserInput.eComplianceType == HD_OBD )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  One or more required SID $02 PIDs (02, 04, either 05 or 67, 0C, 0D and either 11, 45 or 49) are not supported\n",
					      GetEcuId ( EcuIdx ) );
				}
				else
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  One or more required SID $02 PIDs (02, 04, either 05 or 67, 0C) are not supported\n",
					      GetEcuId ( EcuIdx ) );
				}
				eRetCode = FAIL;
			}
		}
	} // end for (EcuIdx...

	// If only one ECU supports SID $02 Frame $00 PID $02
	// and has a Freeze Frame DTC,
	// check that the required PIDs are supported by the vehicle
	if ( FFDTCSupportCount == 1 &&
	     FFDTCCount == 1 )
	{
		if ( bPid02Supported == FALSE    ||
		     bPid04Supported == FALSE    ||
		     (bPid05Supported == FALSE && bPid67Supported == FALSE) ||
		     bPid0CSupported == FALSE    ||
		     ((gstUserInput.eComplianceType == US_OBDII || gstUserInput.eComplianceType == HD_OBD) && bPid0DSupported == FALSE) ||
		     ((gstUserInput.eComplianceType == US_OBDII || gstUserInput.eComplianceType == HD_OBD) &&
		      bPid11Supported == FALSE && bPid45Supported == FALSE && bPid49Supported == FALSE) )
		{
			if ( gstUserInput.eComplianceType == US_OBDII || gstUserInput.eComplianceType == HD_OBD )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  One or more required SID $02 PIDs (02, 04, either 05 or 67, 0C, 0D, and either 11, 45 or 49) are not supported on this vehicle\n" ,
				      GetEcuId ( EcuIdx ) );
			}
			else
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  One or more required SID $02 PIDs (02, 04, either 05 or 67, 0C) are not supported\n",
				      GetEcuId ( EcuIdx ) );
			}
			eRetCode = FAIL;
		}
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  IsFfPidSupported
**
**  Purpose:   Determine if SID 2 PID x is supported on specific ECU.
**             Need to have obtained supported PIDs previously.
**             If EcuIdx < 0 then check all ECUs.
**
*******************************************************************************/
BOOL IsPidSupported ( BYTE         EcuIdx,
                      unsigned int PidIndex )
{
	int index1;
	int index2;
	int mask;


	if ( PidIndex == 0 )
	{
		return TRUE;  // all modules must support SID 02 PID 00
	}

	PidIndex--;

	index1 =  PidIndex >> 5;
	index2 = (PidIndex >> 3) & 0x03;
	mask   = 0x80 >> (PidIndex & 0x07);

	if ( (signed int)EcuIdx < 0 )
	{
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			if ( gstResponse[EcuIdx].FFSupport[index1].SupBits[index2] & mask )
			{
				return TRUE;
			}
		}
	}
	else
	{
		if ( gstResponse[EcuIdx].FFSupport[index1].SupBits[index2] & mask )
		{
			return TRUE;
		}
	}

	return FALSE;
}


/*******************************************************************************
**
**  Function:  VerifyGroupFFSupport
**
**  Purpose:   Verify FF group support (ISO15765 only)
**
*******************************************************************************/
STATUS VerifyGroupFFSupport ( unsigned short *pFreezeFrameDTC )
{
	REQ_MSG       stReqMsg;
	BYTE          EcuIdx;

	unsigned long IdIdx;
	unsigned long IdBitIndex;
	unsigned long BitIndex;
	unsigned long BitMask;
	BOOL          bTestFailed = FALSE;
	BOOL          bReqNextGroup;  // Binary logic for request for next group!


	// Save the original response data for comparison
	memcpy ( &gstCompareResponse[0],
	         &gstResponse[0],
	         sizeof ( OBD_DATA ) * gNumOfECUsForAlloc );

	// Request SID 2 support data as a group
	stReqMsg.SID    = 0x02;
	stReqMsg.NumIds = 6;
	stReqMsg.ID[0]  = 0x00;
	stReqMsg.ID[1]  = 0x00;
	stReqMsg.ID[2]  = 0x20;
	stReqMsg.ID[3]  = 0x00;
	stReqMsg.ID[4]  = 0x40;
	stReqMsg.ID[5]  = 0x00;
	if ( RequestSid ( &stReqMsg, REQ_MSG_ALLOW_NO_RESPONSE ) == FAIL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $02 group ($00-$40) support request failed\n" );
		bTestFailed = TRUE;
	}
	else
	{
		// Set next group select to false
		bReqNextGroup = FALSE;

		// Determine if next group support request is required
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// Index 0x02 corresponds to 0x40
			if ( (gstResponse[EcuIdx].FFSupport[2].SupBits[3] & 0x01) &&
			     pFreezeFrameDTC[EcuIdx] != 0x00 )
			{
				bReqNextGroup = TRUE;
			}
		}

		// Request next group if supported by previous request!
		if ( bReqNextGroup == TRUE )
		{
			// Request SID 2 support data as a group
			stReqMsg.SID    = 0x02;
			stReqMsg.NumIds = 6;
			stReqMsg.ID[0]  = 0x60;
			stReqMsg.ID[1]  = 0x00;
			stReqMsg.ID[2]  = 0x80;
			stReqMsg.ID[3]  = 0x00;
			stReqMsg.ID[4]  = 0xA0;
			stReqMsg.ID[5]  = 0x00;
			if ( RequestSid ( &stReqMsg, REQ_MSG_NORMAL ) == FAIL )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "SID $02 group ($60-$A0) support request failed\n" );
				bTestFailed = TRUE;
			}

			// Set next group select to false
			bReqNextGroup = FALSE;

			// Determine if next group support request is required
			for ( EcuIdx = 0;
			      EcuIdx < gNumOfECUs;
			      EcuIdx++ )
			{
				// Check 0xA0 for suppport of 0xC0
				if ( (gstResponse[EcuIdx].FFSupport[5].SupBits[3] & 0x01) &&
				     pFreezeFrameDTC[EcuIdx] != 0x00 )
				{
					bReqNextGroup = TRUE;
				}
			}

			// Request next group if supported by previous request!
			if ( bReqNextGroup == TRUE )
			{
				// Request SID 2 support data as a group
				stReqMsg.SID    = 0x02;
				stReqMsg.NumIds = 4;
				stReqMsg.ID[0]  = 0xC0;
				stReqMsg.ID[1]  = 0x00;
				stReqMsg.ID[2]  = 0xE0;
				stReqMsg.ID[3]  = 0x00;
				if ( RequestSid ( &stReqMsg, REQ_MSG_NORMAL ) == FAIL )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "SID $02 group ($C0,$E0) support request failed\n" );
					bTestFailed = TRUE;
				}
			}
		}

		// if the ECU has a FF DTC, verify that all SID 2 PID support data compares
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			if ( pFreezeFrameDTC[EcuIdx] != 0x00 )
			{
				if ( gstResponse[EcuIdx].FFSupportSize !=
				     gstCompareResponse[EcuIdx].FFSupportSize )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $02 group/individual support response size mismatch\n"
					      "                          group response size = %d\n"
					      "                          individual response size = %d\n",
					      GetEcuId ( EcuIdx ),
					      gstResponse[EcuIdx].FFSupportSize,
					      gstCompareResponse[EcuIdx].FFSupportSize );
					bTestFailed = TRUE;
				}

				for ( IdIdx = 0;
				      IdIdx < 8;
				      IdIdx++ )
				{
					if ( gstCompareResponse[EcuIdx].FFSupport[IdIdx].ID != gstResponse[EcuIdx].FFSupport[IdIdx].ID )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $02 PID $%02X group/individual response Support ID mismatch\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx * 0x20,
						      gstResponse[EcuIdx].FFSupport[IdIdx].ID );
						bTestFailed = TRUE;

						if ( gstCompareResponse[EcuIdx].FFSupport[IdIdx].ID != IdIdx * 0x20 )
					{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  SID $02 PID $%02X invalid individual response Support ID ($%02X)\n",
							      GetEcuId ( EcuIdx ),
							      IdIdx * 0x20,
							      gstCompareResponse[EcuIdx].FFSupport[IdIdx].ID );
							bTestFailed = TRUE;
						}
						 if ( gstResponse[EcuIdx].FFSupport[IdIdx].ID != IdIdx * 0x20 )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  SID $02 PID $%02X invalid group response Support ID ($%02X)\n",
							      GetEcuId ( EcuIdx ),
							      IdIdx * 0x20,
							      gstResponse[EcuIdx].FFSupport[IdIdx].ID );
							bTestFailed = TRUE;
						}
					}

					if ( gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[0] != gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[0] ||
					     gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[1] != gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[1] ||
					     gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[2] != gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[2] ||
					     gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[3] != gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[3] )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $02 PID $%02X group/individual support response mismatch\n"
						      "                              group response = %02X  %02X  %02X  %02X\n"
						      "                         individual response = %02X  %02X  %02X  %02X\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx * 0x20,
						      gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[0],
						      gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[1],
						      gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[2],
						      gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[3],
						      gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[0],
						      gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[1],
						      gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[2],
						      gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[3] );
						bTestFailed = TRUE;

						for ( IdBitIndex = 0;
						      IdBitIndex < 4;
						      IdBitIndex++ )
						{
							if ( (BitMask = gstResponse[EcuIdx].FFSupport[IdIdx].SupBits[IdBitIndex] ^ gstCompareResponse[EcuIdx].FFSupport[IdIdx].SupBits[IdBitIndex]) != 0 )
							{
								for ( BitIndex = 8;
								      BitIndex != 0;
								      BitIndex-- )
								{
									if ( (BitMask & 0x80) != 0 )
									{
										Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
										      "ECU %X  SID $02 PID $%02X Byte %d:  PID $%02X support response mismatch\n",
										      GetEcuId ( EcuIdx ),
										      IdIdx * 0x20,
										      IdBitIndex,
										     (IdIdx * 0x20) + (IdBitIndex * 8) + (8 - (BitIndex - 1)) );
									}
									BitMask <<= 1;
								}
							}
						}
					}
				}
			}
		}
	}

	if ( bTestFailed == TRUE )
	{
		// Something was wrong, restore the original response data
		memcpy ( &gstResponse[0],
		         &gstCompareResponse[0],
		         sizeof ( OBD_DATA ) * gNumOfECUsForAlloc );
		return FAIL;
	}

	return PASS;
}


/*******************************************************************************
**
**  Function:  VerifyGroupFFResponse
**
**  Purpose:   Verify FF group response (ISO15765 only)
**
*******************************************************************************/
STATUS VerifyGroupFFResponse ( unsigned short *pFreezeFrameDTC )
{
	REQ_MSG        stReqMsg;
	REQ_MSG        SidGroupReq;
	BYTE           EcuIdx;

	unsigned long  IdIdx;
	unsigned long  GroupIndex;
	unsigned long  SingleIndex;
	unsigned long  ByteIdx;
	unsigned long  Index;
	unsigned long  NumGroupIds;
	BOOL           bTestFailed = FALSE;

	// alloc enough memory for three (max size) Sid2 responses
	unsigned short FFSingleSize[3];
	BYTE           FFSingle[sizeof ( FREEZEFRAME ) * 3];
	unsigned short FFGroupSize;
	BYTE           FFGroup[sizeof ( FREEZEFRAME ) * 3];


	// test each ECU seperately
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( pFreezeFrameDTC[EcuIdx] != 0x00 )
		{
			SidGroupReq.SID    = 0x02;
			SidGroupReq.NumIds = 0;

			// collect first 3 supported PIDs for current ECU
			for ( IdIdx = 1, NumGroupIds = 0;
			      IdIdx < 256;
			      IdIdx++ )
			{
				// skip PID Supported PIDs and unsupported PIDs
				if ( IdIdx != 0x00 && IdIdx != 0x20 &&
				     IdIdx != 0x40 && IdIdx != 0x60 &&
				     IdIdx != 0x80 && IdIdx != 0xA0 &&
				     IdIdx != 0xC0 && IdIdx != 0xE0 &&
				     IsIDSupported ( EcuIdx, FFREQUEST, IdIdx) == TRUE )
				{
					SidGroupReq.ID[SidGroupReq.NumIds++] = (BYTE)IdIdx;
					SidGroupReq.ID[SidGroupReq.NumIds++] = 0x00;
					if ( ++NumGroupIds == 3 )
					{
						break;  // leave PID for loop
					}
				}
			}

			// if this ECUs doesn't support any PIDs, skip to next ECUS
			if ( NumGroupIds == 0 )
			{
				continue;  // just go to the next itteration of the ECU for loop
			}

			// do group request
			if ( RequestSid ( &SidGroupReq, REQ_MSG_NORMAL ) == FAIL )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $02 group request failed\n",
				      GetEcuId ( EcuIdx ) );
				bTestFailed = TRUE;
			}
			else
			{
				// save the original group response data for comparison
				FFGroupSize = gstResponse[EcuIdx].FFSize;
				memcpy ( FFGroup,
				         gstResponse[EcuIdx].FF,
				         FFGroupSize );

				// request each PID seperately
				for ( IdIdx = 0, ByteIdx = 0;
				      IdIdx < NumGroupIds;
				      IdIdx++ )
				{
					stReqMsg.SID    = 0x02;
					stReqMsg.NumIds = 2;
					stReqMsg.ID[0]  = SidGroupReq.ID[IdIdx * 2];
					stReqMsg.ID[1]  = 0x00; // Frame #0
					if ( RequestSid ( &stReqMsg, REQ_MSG_ALLOW_NO_RESPONSE ) == FAIL )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $02 PID %02X request failed\n",
						      GetEcuId ( EcuIdx ),
						      SidGroupReq.ID[IdIdx * 2] );
						bTestFailed = TRUE;
					}

					// save single response data
					FFSingleSize[IdIdx] = gstResponse[EcuIdx].FFSize;
					memcpy ( &FFSingle[ByteIdx],
					         gstResponse[EcuIdx].FF,
					         FFSingleSize[IdIdx] );
					ByteIdx += FFSingleSize[IdIdx];
				}

				// check total data
				if ( ByteIdx != FFGroupSize )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $02 PID group request length (%d) / individual request length (%d) mismatch\n",
					      GetEcuId ( EcuIdx ),
					      FFGroupSize,
					      ByteIdx );
					bTestFailed = TRUE;
				}

				// compare PID message data
				for ( Index = 0, GroupIndex = 0;
				      Index < NumGroupIds &&
				      GroupIndex < FFGroupSize;
				      Index++ )
				{
					for ( IdIdx = 0, SingleIndex = 0;
					      IdIdx < NumGroupIds &&
					      SingleIndex < FFGroupSize;
					      IdIdx++ )
					{
						if ( FFGroup[GroupIndex] == FFSingle[SingleIndex] )
						{
							if ( memcmp ( &FFGroup[GroupIndex], &FFSingle[SingleIndex], FFSingleSize[IdIdx] ) != 0 )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $02 PID $%02X group data / individual data mismatch\n",
								      GetEcuId ( EcuIdx ),
								      FFSingle[SingleIndex] );
								bTestFailed = TRUE;
							}
							break;  // leave single PID for loop
						}

						SingleIndex += FFSingleSize[IdIdx];
					}

					if ( IdIdx >= NumGroupIds )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $02 PID group response / individual response mismatch\n",
						      GetEcuId ( EcuIdx ) );
						bTestFailed = TRUE;
						break;  // leave group PID for loop
					}

					GroupIndex += FFSingleSize[IdIdx];
				}
			}
		} // end if ( ECU has FF DTC)
	} // end for (EcuIdx ...

	if ( bTestFailed == TRUE )
	{
		return FAIL;
	}

	return PASS;
}
