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


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


/*******************************************************************************
**
**  Function:  VerifyPendingDTCData
**
**  Purpose:  Verify SID7 DTC pending data
**
*******************************************************************************/
STATUS VerifyPendingDTCData ( void )
{
	REQ_MSG        stReqMsg;
	BYTE           EcuIdx;
	STATUS         eRetCode = PASS;

	unsigned long  NumOfDTCs;
	unsigned long  DataIdx;
	unsigned long  InitialFailureCount = 0;
	BOOL           bDTCFail = FALSE;


	InitialFailureCount = GetFailureCount ( );

	// Request SID 7 data
	stReqMsg.SID    = 0x07;
	stReqMsg.NumIds = 0;
	if ( RequestSid ( &stReqMsg, REQ_MSG_NORMAL ) == FAIL )
	{
		// If ISO15765 or ISO14230 protocol and no response, it is a failure
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 ||
		     gstProtocolList[gProtocolIdx].eProtocol == ISO14230 )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "SID $07 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 pending DTC is not supposed to be present (Test 5.15, 8.3, 9.3, 10.8, 11.10)
	if ( gbDTCPending == FALSE )
	{
		// Verify that SID $07 reports no DTCs pending
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// warn for ECUs which don't respond
			if ( gstResponse[EcuIdx].PendDTCSize == 0 )
			{
				Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  No response to SID $07 request!\n",
				      GetEcuId ( EcuIdx ) );
			}

			else
			{
				// If ISO15765 protocol, check that DTCs are $00
				if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
				{
					if ( gstResponse[EcuIdx].PendDTC[0] != 0x00 ||
					     gstResponse[EcuIdx].PendDTC[1] != 0x00 )
					{
						// if the DTCs  cannot be ignored, fail
						if ( IsDTCIgnored ( EcuIdx,
						     gstResponse[EcuIdx].PendDTCSize,
						     &gstResponse[EcuIdx].PendDTC[0] ) != PASS )
						{
							bDTCFail = TRUE;
						}
					}
				}
				// If other protocol, check that DTCs are $00
				else
				{
					// response should contain multiple of 6 data bytes
					if ( gstResponse[EcuIdx].PendDTCSize % 6 != 0 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $07 response size error\n", GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// validation of all DTCs reported in response
					for ( DataIdx = 0;
					      DataIdx < (unsigned long)(gstResponse[EcuIdx].PendDTCSize / 2);
					      DataIdx++ )
					{
						if ( gstResponse[EcuIdx].PendDTC[DataIdx*2]     != 0x00 ||
						     gstResponse[EcuIdx].PendDTC[(DataIdx*2)+1] != 0x00 )
						{
							bDTCFail = TRUE;
						}
					}
				}

				if ( bDTCFail == TRUE )
				{
					bDTCFail = FALSE;

					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $07 indicates DTC pending\n",
					      GetEcuId ( EcuIdx ) );
					eRetCode = FAIL;
				}
			}
		}
	}

	// if a DTC is supposed to be present (Test 6.3, 7.3)
	else
	{
		// Verify that SID 7 reports DTCs pending
		NumOfDTCs = 0;
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			// warn for ECUs which don't respond
			if ( gstResponse[EcuIdx].PendDTCSize == 0 )
			{
				Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  No response to SID $07 request\n",
				      GetEcuId ( EcuIdx ) );
			}

			else
			{
				// Print out all the DTCs
				for ( DataIdx = 0;
				      DataIdx < gstResponse[EcuIdx].PendDTCSize;
				      DataIdx += 2 )
				{
					if ( gstResponse[EcuIdx].PendDTC[DataIdx]     != 0 ||
						 gstResponse[EcuIdx].PendDTC[DataIdx + 1] != 0 )
					{
						// Process based on the type of DTC
						switch ( gstResponse[EcuIdx].PendDTC[DataIdx] & 0xC0 )
						{
							case 0x00:
							{
								Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Pending DTC P%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      gstResponse[EcuIdx].PendDTC[DataIdx] & 0x3F,
								      gstResponse[EcuIdx].PendDTC[DataIdx + 1] );
							}
							break;

							case 0x40:
							{
								Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Pending DTC C%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      gstResponse[EcuIdx].PendDTC[DataIdx] & 0x3F,
								      gstResponse[EcuIdx].PendDTC[DataIdx + 1] );
							}
							break;

							case 0x80:
							{
								Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Pending DTC B%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      gstResponse[EcuIdx].PendDTC[DataIdx] & 0x3F,
								      gstResponse[EcuIdx].PendDTC[DataIdx + 1] );
							}
							break;

							case 0xC0:
							{
								Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Pending DTC U%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      gstResponse[EcuIdx].PendDTC[DataIdx] & 0x3F,
								      gstResponse[EcuIdx].PendDTC[DataIdx + 1] );
							}
							break;
						}
						NumOfDTCs++;
					}
				}
			}
		}

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

	if ( eRetCode != PASS )
	{
		return FAIL;
	}

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

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


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


	// Request SID 7 data
	stReqMsg.SID    = 0x07;
	stReqMsg.NumIds = 0;
	if ( RequestSid ( &stReqMsg, Flags ) != PASS )
	{
		// Indicate request failed
		return ERRORS;
	}

	// Check if SID 7 reports DTCs pending
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( gstResponse[EcuIdx].PendDTCSize == 0 )
		{
			// If no data, ignore
		}
		// Check if there is at least one DTC
		else if ( gstResponse[EcuIdx].PendDTC[0] != 0 ||
		          gstResponse[EcuIdx].PendDTC[1] != 0 )
		{
			if ( IsDTCIgnored ( EcuIdx,
			                    gstResponse[EcuIdx].PendDTCSize,
			                    &gstResponse[EcuIdx].PendDTC[0] ) == 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 )
{
	STATUS         eRetCode = PASS;

	unsigned short ArrayIdx;
	unsigned short ListIdx;
	unsigned short DTC;
	char           DTCType = 'X';


	// check if it's in the ignore list
	for ( ArrayIdx = 0;
	      ArrayIdx < Size;
	      ArrayIdx += 2 )
	{
		DTC = (unsigned short)((pDTCArray[ArrayIdx] << 8) +
		                        pDTCArray[ArrayIdx+1]);
		// if the DTC is not in the ignore list, fail
		// Check if SID 3 reports DTCs stored
		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;
			}
		}

		// Process based on the type of DTC
		switch ( pDTCArray[ArrayIdx] & 0xC0 )
		{
			case 0x00:
			{
				DTCType = 'P';
			}
			break;
			case 0x40:
			{
				DTCType = 'C';
			}
			break;
			case 0x80:
			{
				DTCType = 'B';
			}
			break;
			case 0xC0:
			{
				DTCType = 'U';
			}
			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 ),
			      DTCType,
			      pDTCArray[ArrayIdx] & 0x3F,
			      pDTCArray[ArrayIdx + 1] );
			eRetCode = FAIL;
		}
		else
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  DTC %c%02X%02X detected but ignored\n",
			      GetEcuId ( EcuIdx ),
			      DTCType,
			      pDTCArray[ArrayIdx] & 0x3F,
			      pDTCArray[ArrayIdx + 1] );
		}
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveDTCList
**
**  Purpose:   Save the DTC list from SID $03 or $07
**
*******************************************************************************/
void SaveDTCList ( unsigned int SID )
{
	BYTE            EcuIdx;
	unsigned long   DTCIdx;
	unsigned long   DTCEnd;
	unsigned short  DTCSize;


	if ( SID != 3 && SID != 7 )
	{
		return;
	}

	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		DTCSize = gstDTCList[EcuIdx].Size = (SID == 3) ? gstResponse[EcuIdx].ConfDTCSize
		                                               : gstResponse[EcuIdx].PendDTCSize;
		for ( DTCIdx = 0;
		      DTCIdx < DTCSize;
		      DTCIdx++ )
		{
			gstDTCList[EcuIdx].DTC[DTCIdx] = (SID == 3) ? 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 ( SID == 3 &&
	     geTestPhase == eTestConfirmedDTC &&
	     IsDTCPending ( (REQ_MSG_NORMAL|REQ_MSG_ALLOW_NO_RESPONSE) ) != FAIL )
	{
		// Get DTC list from SID $07
		for ( EcuIdx = 0;
		      EcuIdx < gNumOfECUs;
		      EcuIdx++ )
		{
			DTCSize = gstResponse[EcuIdx].PendDTCSize;
			DTCEnd  = gstDTCList[EcuIdx].Size;
			gstDTCList[EcuIdx].Size += gstResponse[EcuIdx].PendDTCSize;
			for ( DTCIdx = 0;
			      DTCIdx < DTCSize;
			      DTCIdx++, DTCEnd++ )
			{
				gstDTCList[EcuIdx].DTC[DTCEnd] = gstResponse[EcuIdx].PendDTC[DTCIdx];
			}
		}
	}
}