/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-5
**  FileName: VerifyPendingDTCData.c
**  Author:   EnGenius
**  Date:     2/25/2022
**  Email:    <support@autosinnovate.org>
**
**  Purpose:  SAE J1699-5 Vehicle OBD II Compliance Test Cases Source Code.
**            This source code is intended to run the tests described in
**            the SAE J1699-5 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:  03/13/2023  Initial Version
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function 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


// Function Prototypes
STATUS IsDTCPending ( unsigned long Flags );
STATUS IsDTCIgnored ( BYTE EcuIdx, unsigned short Size, BYTE * pDTCArray, BOOL bPermanent );


// Variables
extern unsigned short  gDTC_Ignore_List_Count;
extern unsigned short *gpDTC_Ignore_List;

unsigned char DTCTypeCharacter[] =
{
	'P',
	'C',
	'B',
	'U'
};
BYTE DTCTypeCharacterSize = 4;


/*******************************************************************************
**
**  Function:  VerifyPendingDTCData
**
**  Purpose:  Verify SID $19 LEV $42 GRP $33 StatusMask $8 pending DTC data
**
*******************************************************************************/
STATUS VerifyPendingDTCData ( BYTE State )
{
	REQ_MSG        stReqMsg;
	BYTE           EcuIdx;
	STATUS         eRetCode = PASS;

	unsigned long  NumOfDTCs;
	DTCASVR       *pDTC;
	unsigned long  DataIdx;
	unsigned long  InitialFailureCount = 0;


	InitialFailureCount = GetFailureCount ( );

	if ( gbPhysicalAddressing == FALSE )
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Request Pending DTCs (SID $19 LEV $42 GRP $33 StatusMask $4) Functionally Addressed\n" );
	}
	else
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Request Pending DTCs (SID $19 LEV $42 GRP $33 StatusMask $4) Physically Addressed\n" );
	}

	// Request SID $19 $42 $33 $04 data
	stReqMsg.SID     = 0x19;
	stReqMsg.NumIds  = 4;
	stReqMsg.u.ID[0] = 0x42;  // Subfunction (LEV): reportWWHOBDDTCByMaskRecord
	stReqMsg.u.ID[1] = 0x33;  // FunctionalGroupIdentifier: Emission
	stReqMsg.u.ID[2] = 0x04;  // DTCStatusMask: Pending
	stReqMsg.u.ID[3] = 0x02;  // DTCSeverityMask: DTCClass_1
	if ( (gbPhysicalAddressing == FALSE && RequestSID ( &stReqMsg, REQ_MSG_NORMAL ) != PASS) ||
	     (gbPhysicalAddressing == TRUE  && RequestSID_PhysicallyAddressed_All ( &stReqMsg, REQ_MSG_NORMAL ) != PASS) )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $19 $42 $33 $04 request failed\n" );
		return FAIL;
	}

	// if a pending DTC is not supposed to be present (Test 5.14, 8.2, 9.2, 10.8, 11.10, 12.10)
	if ( State == DTC_STATE_NO_FAULT ||             // Test 5.14, 10.8, 11.10
	     State == DTC_STATE_REPAIR_1DC_FAULT ||     // Test 8.2
	     State == DTC_STATE_REPAIR_3DC_FAULT ||     // Test 9.2
	     State == DTC_STATE_ZEV_REPAIR_1DC_FAULT )  // Test 12.10
	{
		// Verify that SID $19 $42 $33 $04 reports no DTCs pending
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// warn for ECUs which don't respond
			if ( gstResponse[EcuIdx].PendDTCHeader.DTCSupported == 0 )
			{
				Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  No response to SID $19 $42 $33 $04 request!\n",
				      GetEcuId ( EcuIdx ) );
			}

			else
			{
				// Check that DTCs are $00
				if ( gstResponse[EcuIdx].PendDTC[1] != 0x00 ||
				     gstResponse[EcuIdx].PendDTC[2] != 0x00 ||
				     gstResponse[EcuIdx].PendDTC[3] != 0x00 )
				{
					// if the DTCs  cannot be ignored, fail
					if ( IsDTCIgnored ( EcuIdx,
					                    gstResponse[EcuIdx].PendDTCHeader.DTCSize,
					                    &gstResponse[EcuIdx].PendDTC[0],
					                    FALSE ) != PASS )
					{

						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 $42 $33 $04 indicates Pending DTC\n",
						      GetEcuId ( EcuIdx ) );
						eRetCode = FAIL;
					}
				}
				else
				{
					Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $19 $42 $33 $04 indicates no Pending DTC\n",
					      GetEcuId ( EcuIdx ) );
				}
			}
		}
	}

	// if a DTC is supposed to be present (Test 6.2, 7.2, 12.8, 12.9)
	else
	{
		// Verify that SID $19 $42 $33 $04 reports DTCs pending
		NumOfDTCs = 0;
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// warn for ECUs which don't respond
			if ( gstResponse[EcuIdx].PendDTCHeader.DTCSupported == 0 )
			{
				Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  No response to SID $19 $42 $33 $04 request\n",
				      GetEcuId ( EcuIdx ) );
			}

			else
			{
				// Print out all the DTCs
				for ( DataIdx = 0;
				      DataIdx < gstResponse[EcuIdx].PendDTCHeader.DTCSize;
				      DataIdx += 5 )
				{
					pDTC = (DTCASVR*)&gstResponse[EcuIdx].PendDTC[DataIdx];
					if ( pDTC->Record.Dtc.HighByte != 0 ||
					     pDTC->Record.Dtc.MidByte  != 0 ||
					     pDTC->Record.Dtc.LowByte  != 0 )
					{
						Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  Pending DTC $%c%02X%02X (Severity = $%02X  Failure Type = $%02X  Status = $%02X) detected\n",
						       GetEcuId ( EcuIdx ),
						       DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
						       pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
						       pDTC->Record.Dtc.MidByte,                                   // 4th and 5th characters (0-F)
						       pDTC->Severity,                                             // Severity of DTC
						       pDTC->Record.Dtc.LowByte,                                   // Failure Type Byte
						       pDTC->Record.Status );                                      // Status of DTC

						NumOfDTCs++;

						// if test 12.8 or 12.9, Bit 0 (Test Failed) must be set
						if ( (State == DTC_STATE_ZEV_PEND_FAULT ||
						      State == DTC_STATE_ZEV_CONF_FAULT) &&
						     (pDTC->Record.Status & DTC_STATUS_TEST_FAILED) != DTC_STATUS_TEST_FAILED )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC $%c%02X%02X Status does not indicate Test Failed ($01)\n",
							      GetEcuId ( EcuIdx ),
							      DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
							      pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
							      pDTC->Record.Dtc.MidByte );                                 // 4th and 5th characters (0-F)
							eRetCode = FAIL;
						}

						// if Test 6.2, 12.8, 12.9, Bit 1 (Test Failed This Operational Cycle) must be set
						if ( (State == DTC_STATE_INDUCE_FAULT   ||
						      State == DTC_STATE_ZEV_PEND_FAULT ||
						      State == DTC_STATE_ZEV_CONF_FAULT)  &&
						     (pDTC->Record.Status & DTC_STATUS_TEST_FAILED_THIS_OPERATIONAL_CYCLE) != DTC_STATUS_TEST_FAILED_THIS_OPERATIONAL_CYCLE )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC $%c%02X%02X Status does not indicate Test Failed This Operational Cycle ($02)\n",
							      GetEcuId ( EcuIdx ),
							      DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
							      pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
							      pDTC->Record.Dtc.MidByte );                                 // 4th and 5th characters (0-F)
							eRetCode = FAIL;
						}

						// if Test 6.2, 7.2, 12.8, 12.9, Bit 2 (Pending DTC) must be set
						if ( (State == DTC_STATE_INDUCE_FAULT   ||
						      State == DTC_STATE_CONTINUE_FAULT ||
						      State == DTC_STATE_ZEV_PEND_FAULT ||
						      State == DTC_STATE_ZEV_CONF_FAULT) &&
						     (pDTC->Record.Status & DTC_STATUS_PENDING_DTC) != DTC_STATUS_PENDING_DTC )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC $%c%02X%02X Status does not indicate Pending DTC ($04)\n",
							      GetEcuId ( EcuIdx ),
							      DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
							      pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
							      pDTC->Record.Dtc.MidByte );                                 // 4th and 5th characters (0-F)
							eRetCode = FAIL;
						}

						// if Test 6.2, 7.2, Bit 4 (Test Not Completed Since Last Clear) must be clear
						if ( (State == DTC_STATE_INDUCE_FAULT   ||
						      State == DTC_STATE_CONTINUE_FAULT) &&
						     (pDTC->Record.Status & DTC_STATUS_TEST_NOT_COMPLETED_SINCE_LAST_CLEAR) == DTC_STATUS_TEST_NOT_COMPLETED_SINCE_LAST_CLEAR )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC $%c%02X%02X Status indicates Test Not Completed Since Last Clear ($10)\n",
							      GetEcuId ( EcuIdx ),
							      DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
							      pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
							      pDTC->Record.Dtc.MidByte );                                 // 4th and 5th characters (0-F)
							eRetCode = FAIL;
						}

						// if Test 6.2, 7.2, Bit 5 (Test Failed Since Last Clear) must be set
						if ( (State == DTC_STATE_INDUCE_FAULT   ||
						      State == DTC_STATE_CONTINUE_FAULT) &&
						     (pDTC->Record.Status & DTC_STATUS_TEST_FAILED_SINCE_LAST_CLEAR) != DTC_STATUS_TEST_FAILED_SINCE_LAST_CLEAR )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC $%c%02X%02X Status does not indicate Test Failed Since Last Clear ($20)\n",
							      GetEcuId ( EcuIdx ),
							      DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
							      pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
							      pDTC->Record.Dtc.MidByte );                                 // 4th and 5th characters (0-F)
							eRetCode = FAIL;
						}

						// if Test 6.2, Bit 6 (Test Not Completed This Operational Cycle) must be clear
						if ( State == DTC_STATE_INDUCE_FAULT &&
						     (pDTC->Record.Status & DTC_STATUS_TEST_NOT_COMPLETED_THIS_OPERATIONAL_CYCLE) == DTC_STATUS_TEST_NOT_COMPLETED_THIS_OPERATIONAL_CYCLE )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC $%c%02X%02X Status indicates Test Not Completed This Operational Cycle ($40)\n",
							      GetEcuId ( EcuIdx ),
							      DTCTypeCharacter[(pDTC->Record.Dtc.HighByte & 0xC0) >> 6],  // 1st character (P, C, B, U)
							      pDTC->Record.Dtc.HighByte & 0x3F,                           // 2nd (0,1,2,3) and 3rd (0-F) characters
							      pDTC->Record.Dtc.MidByte );                                 // 4th and 5th characters (0-F)
							eRetCode = FAIL;
						}
					}
				}
			}
		}

		// If no pending DTCs found, then fail
		if ( NumOfDTCs == 0 )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "No ECU indicates Pending DTC\n" );
			eRetCode = FAIL;
		}
	}

	// Link active test to verify communication remained active for ALL protocols
	if ( VerifyECUCommunication ( ) != PASS )
	{
		eRetCode = FAIL;
	}

	if ( InitialFailureCount != GetFailureCount ( ) )
	{
		// There could have been early/late responses that weren't treated as FAIL
		eRetCode = FAIL;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  IsDTCPending
**
**  Purpose:   Check if SID $19 LEV $42 StatusMask $04 reports a DTC pending
**
*******************************************************************************/
STATUS IsDTCPending ( unsigned long Flags )
{
	REQ_MSG  stReqMsg;
	BYTE     EcuIdx;
	STATUS   eRetCode = FAIL;

	// Request SID $19 LEV $42 StatusMask $04 data
	stReqMsg.SID     = 0x19;
	stReqMsg.NumIds  = 4;
	stReqMsg.u.ID[0] = 0x42;  // Subfunction (LEV): reportWWHOBDDTCByMaskRecord
	stReqMsg.u.ID[1] = 0x33;  // FunctionalGroupIdentifier: Emission
	stReqMsg.u.ID[2] = 0x04;  // DTCStatusMask: Pending
	stReqMsg.u.ID[3] = 0x02;  // DTCSeverityMask: DTCClass_1
	if ( RequestSID ( &stReqMsg, Flags ) != PASS )
	{
		// Indicate request failed
		return ERRORS;
	}

	// Check if SID $19 LEV $42 StatusMask $04 reports DTCs pending
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( gstResponse[EcuIdx].PendDTCHeader.DTCSize == 0 )
		{
			// If no data, ignore
		}
		// Check if there is at least one DTC (check High, Middle, and Low bytes)
		else if ( gstResponse[EcuIdx].PendDTC[1] != 0 ||
		          gstResponse[EcuIdx].PendDTC[2] != 0 ||
		          gstResponse[EcuIdx].PendDTC[3] != 0 )
		{
			if ( IsDTCIgnored ( EcuIdx,
			                    gstResponse[EcuIdx].PendDTCHeader.DTCSize,
			                    &gstResponse[EcuIdx].PendDTC[0],
			                    FALSE ) == TRUE)
			{
				continue;
			}
			eRetCode = PASS;
		}
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  IsDTCIgnored
**  
**  Purpose:   Check if the DTC(s) are in the ignore list
**             FAIL if any DTC is not on the ignore list
**  
*******************************************************************************/
STATUS IsDTCIgnored ( BYTE            EcuIdx,
                      unsigned short  Size,
                      BYTE           *pDTCArray,
                      BOOL            bPermanent )
{
	STATUS         eRetCode = PASS;

	unsigned short ArrayIdx;
	unsigned short ListIdx;
	unsigned long  DTC;
	unsigned short ArrayInc = 5;   // initalize Array Increment for Confirmed or Pending DTCs
	unsigned short DTCOffset = 1;  // initalize DTC offset for Confirmed or Pending DTCs (DTCs are preceded by Severity Byte)

	if ( bPermanent )
	{
		ArrayInc = 4;
		DTCOffset = 0;
	}

	// check if it's in the ignore list
	for ( ArrayIdx = 0;
	      ArrayIdx < Size;
	      ArrayIdx += ArrayInc )
	{
		DTC = (unsigned short)( (pDTCArray[ArrayIdx+DTCOffset] << 8) +
		                         pDTCArray[ArrayIdx+DTCOffset+1] );
		// if the DTC is not in the ignore list, fail
		for ( ListIdx = 0;
		      ListIdx < gDTC_Ignore_List_Count;
		      ListIdx++ )
		{
			if ( DTC == gpDTC_Ignore_List[ListIdx] )
			{
				// this DTC was found, don't search list anymore
				break;
			}
		}

		// If reached the end of the ignore list, the DTC isn't ignored
		if ( ListIdx == gDTC_Ignore_List_Count /*||
		     ( geTestPhase != eTestFaultRepaired &&
		       geTestPhase != eTestNoFault3DriveCycle )*/ )
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  DTC %c%02X%02X detected\n",
			      GetEcuId ( EcuIdx ),
			      DTCTypeCharacter[(pDTCArray[ArrayIdx+DTCOffset] & 0xC0) >> 6],
			      pDTCArray[ArrayIdx+DTCOffset] & 0x3F,
			      pDTCArray[ArrayIdx+DTCOffset + 1] );
			eRetCode = FAIL;
		}
		else
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  DTC %c%02X%02X detected but ignored\n",
			      GetEcuId ( EcuIdx ),
			      DTCTypeCharacter[(pDTCArray[ArrayIdx+DTCOffset] & 0xC0) >> 6],
			      pDTCArray[ArrayIdx+DTCOffset] & 0x3F,
			      pDTCArray[ArrayIdx+DTCOffset + 1] );
		}
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveDTCList
**
**  Purpose:   Save the DTC list from SID $19 LEV $42 StatusMask $04 or $08
**
*******************************************************************************/
void SaveDTCList ( unsigned int StatusMask )
{
	BYTE            EcuIdx;
	unsigned long   DTCIdx;
	unsigned long   DTCEnd;
	unsigned short  DTCSize;

	if ( StatusMask != 8 && StatusMask != 4 )
	{
		return;
	}

	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		DTCSize = gstResponse[EcuIdx].pDTCList->Size = (StatusMask == 8) ? gstResponse[EcuIdx].ConfDTCHeader.DTCSize
		                                                                 : gstResponse[EcuIdx].PendDTCHeader.DTCSize;
		for ( DTCIdx = 0;
		      DTCIdx < DTCSize;
		      DTCIdx++ )
		{
			gstResponse[EcuIdx].pDTCList->Dtc[DTCIdx] = (StatusMask == 8) ? gstResponse[EcuIdx].ConfDTC[DTCIdx]
			                                                              : gstResponse[EcuIdx].PendDTC[DTCIdx];
		}
	}

	// If this is Test 7.4 and there is a pending DTC, append to DTC list
	if ( StatusMask == 0x04 &&
	     geTestPhase == eTestConfirmedDTC &&
	     IsDTCPending ( (REQ_MSG_NORMAL|REQ_MSG_ALLOW_NO_RESPONSE) ) != FAIL )
	{
		// Save DTC list from SID $19 $42 ($04)
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			DTCSize = gstResponse[EcuIdx].PendDTCHeader.DTCSize;
			DTCEnd  = gstResponse[EcuIdx].pDTCList->Size;
			gstResponse[EcuIdx].pDTCList[EcuIdx].Size += gstResponse[EcuIdx].PendDTCHeader.DTCSize;
			for ( DTCIdx = 0;
			      DTCIdx < DTCSize;
			      DTCIdx++, DTCEnd++ )
			{
				gstResponse[EcuIdx].pDTCList->Dtc[DTCEnd] = gstResponse[EcuIdx].PendDTC[DTCIdx];
			}
		}
	}
}
