/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-5
**  FileName: VerifyConfirmedDTCData.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 IsDTCStored ( unsigned long Flags );


// Variable Prototypes
extern unsigned char DTCTypeCharacter[];


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

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


	InitialFailureCount = GetFailureCount ( );

	Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
	      "Request Confirmed DTCs (SID $19 LEV $42 GRP $33 StatusMask $8) %s Addressed\n",
	      gbPhysicalAddressing ? "Physically" : "Functionally" );

	// Request SID $19 $42 $33 $08 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] = 0x08;  // DTCStatusMask: Stored
	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) )
	{
		// If DTC stored or historical DTC, there must be a response
		if ( State != DTC_STATE_NO_FAULT &&
		     State != DTC_STATE_INDUCE_FAULT &&
		     State != DTC_STATE_ZEV_PEND_FAULT &&
		     State != DTC_STATE_ZEV_CONF_FAULT )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "SID $19 $42 $33 $08 request failed\n" );
			return FAIL;
		}
		else if ( InitialFailureCount != GetFailureCount ( ) )
		{
			// FAIL was due to actual problems
			return FAIL;
		}
		else
		{
			// FAIL was due to no response, but none was required
			return PASS;
		}
	}

	// if a DTC is not supposed to be present (Test 5.12, 10.9, 11.9)
	if ( State == DTC_STATE_NO_FAULT )
	{
		// Verify that no confirmed DTCs reported
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// warn for ECUs which don't respond
			if ( gstResponse[EcuIdx].ConfDTCHeader.DTCSupported == FALSE )
			{
				// If ECU ID is not setup ($00 - user entered more ECUs than actually respond), don't print
				if ( GetEcuId ( EcuIdx ) != 0x00000000)
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  No response to SID $19 $42 $33 $08 request\n",
					      GetEcuId ( EcuIdx ) );
				}
			}
			else
			{
				pDTC = (DTCASVR*)&gstResponse[EcuIdx].ConfDTC[0];
				// check that DTCs are $00
				if ( pDTC->Record.Dtc.HighByte != 0x00 ||
				     pDTC->Record.Dtc.MidByte  != 0x00 ||
				     pDTC->Record.Dtc.LowByte  != 0x00 )
				{
					// if MIL is set, this is a Type A DTC,
					// note it but do not fail
					if ( gstResponse[EcuIdx].PIDF501.Data[0] & 0x80 ||
					     (geTestPhase == eTestZEV && gTestSubsection == 8 ) )
					{
						Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 $42 $33 $08 indicates Type A Confirmed DTC\n",
						      GetEcuId ( EcuIdx ) );
					}
					else
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 $42 $33 $08 indicates Confirmed DTC\n",
						      GetEcuId ( EcuIdx ) );
						eRetCode = FAIL;
					}
				}
				else
				{
					Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $19 $42 $33 $08 indicates no Confirmed DTC\n",
					      GetEcuId ( EcuIdx ) );
				}
			}
		}
	}

	// if a DTC may be (Test 6.3, 12.8, 12.10) or is supposed to be present (Test 7.3, 8.3, 9.3, 9.8, 12.9)
	else
	{
		// Verify that SID $19 $42 $33 $08 reports confirmed DTCs
		NumOfDTCs = 0;
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// warn for ECUs which don't respond
			if ( gstResponse[EcuIdx].ConfDTCHeader.DTCSupported == 0 )
			{
				// If ECU ID is not setup ($00 - user entered more ECUs than actually respond), don't print
				if ( GetEcuId ( EcuIdx ) != 0X00000000 )
				{
					Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  No response to SID $19 $42 $33 $08 request\n",
					      GetEcuId ( EcuIdx ) );
				}
			}

			else
			{
				if ( gstResponse[EcuIdx].ConfDTCHeader.DTCFormat != 0x04 )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  Confirmed DTC  Format ID = $%02X (must be $04)\n",
					      GetEcuId ( EcuIdx ),
					      gstResponse[EcuIdx].ConfDTCHeader.DTCFormat );
					eRetCode = FAIL;
				}

				// Print out all the DTCs
				for ( DataIdx = 0;
				      DataIdx < gstResponse[EcuIdx].ConfDTCHeader.DTCSize;
				      DataIdx += sizeof ( DTCASVR ) )
				{
					pDTC = (DTCASVR*)&gstResponse[EcuIdx].ConfDTC[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  Confirmed DTC %c%02X%02X %02X (Severity = $%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->Record.Dtc.LowByte,                                   // Failure Type Byte
						      pDTC->Severity,                                             // Severity of DTC
						      pDTC->Record.Status );                                      // Status of DTC

						NumOfDTCs++;

						// if Test 6.3, Bit 1 (Test Failed This Operational Cycle) must be set
						if ( State == DTC_STATE_INDUCE_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 %02X  Status does not indicates 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)
							      pDTC->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}
						// if Test 8.3, 9.3, 9.8, Bit 1 (Test Failed This Operational Cycle) must be clear
						else if ( (State == DTC_STATE_REPAIR_1DC_FAULT ||
						           State == DTC_STATE_REPAIR_3DC_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 %02X  Status indicates 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)
							      pDTC->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}

						// if Test 6.3, 7.3, Bit 2 (Pending DTC) must be set
						if ( (State == DTC_STATE_INDUCE_FAULT ||
						      State == DTC_STATE_CONTINUE_FAULT)  &&
						     (pDTC->Record.Status & DTC_STATUS_PENDING_DTC) != DTC_STATUS_PENDING_DTC )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC %c%02X%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)
							      pDTC->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}
						// if Test 8.3, 9.3, Bit 2 (Pending DTC) must be clear
						else if ( (State == DTC_STATE_REPAIR_1DC_FAULT ||
						           State == DTC_STATE_REPAIR_3DC_FAULT )  &&
						          (pDTC->Record.Status & DTC_STATUS_PENDING_DTC) == DTC_STATUS_PENDING_DTC )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC %c%02X%02X %02X  Status indicates 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)
							      pDTC->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}

						// if Test 6.3, 7.3, 8.3, 9.3, 9.8, 12.x, Bit 3 (Confirmed DTC) must be set
						if ( (State == DTC_STATE_INDUCE_FAULT     ||
						      State == DTC_STATE_CONTINUE_FAULT   ||
						      State == DTC_STATE_REPAIR_1DC_FAULT ||
						      State == DTC_STATE_REPAIR_3DC_FAULT ||
						      State == DTC_STATE_SET_PERM_FAULT   ||
						      State == DTC_STATE_ZEV_PEND_FAULT   ||
						      State == DTC_STATE_ZEV_CONF_FAULT) &&
						     (pDTC->Record.Status & DTC_STATUS_CONFIRMED_DTC) != DTC_STATUS_CONFIRMED_DTC )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  DTC %c%02X%02X %02X  Status does not indicated Confirmed DTC ($08)\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->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}

						// if Test 6.3, 7.3, 8.3, 9.3, 9.8, Bit 4 (Test Not Completed Since Last Clear) must be clear
						if ( (State == DTC_STATE_INDUCE_FAULT     ||
						      State == DTC_STATE_CONTINUE_FAULT   ||
						      State == DTC_STATE_REPAIR_1DC_FAULT ||
						      State == DTC_STATE_REPAIR_3DC_FAULT ||
						      State == DTC_STATE_SET_PERM_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 %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)
							      pDTC->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}

						// if Test 6.3, 8.3, 9.3, 9.8, Bit 6 (Test Not Completed This Operational Cycle) must be clear
						if ( (State == DTC_STATE_INDUCE_FAULT     ||
						      State == DTC_STATE_REPAIR_1DC_FAULT ||
						      State == DTC_STATE_REPAIR_3DC_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 %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)
							      pDTC->Record.Dtc.LowByte );                                 // Failure Type Byte
							eRetCode = FAIL;
						}
					}
				}
			}
		}

		// If no stored DTCs found, then fail
		if ( (State == DTC_STATE_CONTINUE_FAULT   ||
		      State == DTC_STATE_REPAIR_1DC_FAULT ||
		      State == DTC_STATE_REPAIR_3DC_FAULT ||
		      State == DTC_STATE_SET_PERM_FAULT   ||
		      State == DTC_STATE_ZEV_CONF_FAULT) &&
		     NumOfDTCs == 0 )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "No ECU indicates Confirmed 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:  IsDTCStored
**
**  Purpose:  Check if confirmed DTCs reported
**
*******************************************************************************/
STATUS IsDTCStored ( unsigned long Flags )
{
	REQ_MSG  stReqMsg;
	BYTE     EcuIdx;


	// Request SID $19 LEV $42 StatusMask $08 data
	stReqMsg.SID     = 0x19;
	stReqMsg.NumIds  = 4;
	stReqMsg.u.ID[0] = 0x42;
	stReqMsg.u.ID[1] = 0x33;
	stReqMsg.u.ID[2] = 0x08;
	stReqMsg.u.ID[3] = 0x02;
	if ( RequestSID ( &stReqMsg, Flags ) != PASS )
	{
		// Indicate request failed
		return ERRORS;
	}

	// Check if SID $19 LEV $42 StatusMask $08 reports DTCs stored
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( gstResponse[EcuIdx].ConfDTCHeader.DTCSize == 0 )
		{
			// If no data, ignore
		}
		// Check if there is at least one DTC
		else if ( gstResponse[EcuIdx].ConfDTC[0] != 0 ||
		          gstResponse[EcuIdx].ConfDTC[1] != 0 )
		{
			// Break the loop if a DTC is stored
			break;  // leave ECU for loop
		}
	}

	// If no stored DTCs, return FALSE
	if ( EcuIdx == gNumOfECUs )
	{
		return FAIL;
	}

	return PASS;
}
