/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-3
**  FileName: VerifyReverseOrderSupport.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 <conio.h>    // MS-DOS console input and output declarations
#include <windows.h>  // Windows API declarations
#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations


// Function Prototypes
BOOL   IsPidInRequest ( REQ_MSG SidReqList, BYTE Pid );
STATUS VerifyReverseGroupDiagnosticSupport ( void );
STATUS VerifySingleAndGroupPIDRequest ( void );

extern STATUS DetermineVariablePidSize ( void );


// Variables
BYTE gReverseOrderStateArray[MAX_ECUS];


/*******************************************************************************
**
**  Function:  VerifyReverseOrderSupport
**
**  Purpose:   Verify PID reverse order support
**
*******************************************************************************/
STATUS VerifyReverseOrderSupport ( void )
{
	REQ_MSG       stReqMsg;
	BYTE          EcuIdx;

	unsigned int  IdIdx;
	unsigned long InitialFailureCount = 0;
	BOOL          bTestFailed = FALSE;


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

	InitialFailureCount = GetFailureCount ( );

	// Ignore unsupported responses
	// Support info is cleared on receive of first response to reverse order request
	gbIgnoreUnsupported = TRUE;

	// reverse order request started
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		gReverseOrderStateArray[EcuIdx] = REVERSE_ORDER_REQUESTED;
	}

	// Request SID 1 support data in reverse order
	for ( IdIdx = 0xE0;
	      IdIdx < 0x100;
	      IdIdx -= 0x20 )
	{
		stReqMsg.SID    = 0x01;
		stReqMsg.NumIds = 1;
		stReqMsg.ID[0]  = (BYTE)IdIdx;
		if ( RequestSid ( &stReqMsg, REQ_MSG_NORMAL|REQ_MSG_IGNORE_NO_RESPONSE ) == FAIL )
		{
			// There must be a response to PID 0x00
			if ( IdIdx == 0x00 )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "No response to SID $01 PID $00 request\n" );
				bTestFailed = TRUE;
			}
			else
			{
				for ( EcuIdx = 0;
				      EcuIdx < gNumOfECUs;
				      EcuIdx++ )
				{
					// Check if PID is supported
					if ( gstResponse[EcuIdx].PidSupportSize != 0 &&
					     (gstCompareResponse[EcuIdx].PidSupport[(IdIdx - 1) >> 5].SupBits[((IdIdx - 1) >> 3) & 0x03] & (0x80 >> ((IdIdx - 1) & 0x07))) != 0x00 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  No response to SID $01 PID $%02X request\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						bTestFailed = TRUE;
					}
				}
			}
		}
		else if ( IdIdx != 0x00 )
		{
			for (EcuIdx = 0;
			      EcuIdx < gNumOfECUs;
			      EcuIdx++ )
			{
				// Check if data received and PID is not supported
				if ( gstResponse[EcuIdx].PidSupportSize != 0 &&
				     (gstCompareResponse[EcuIdx].PidSupport[(IdIdx - 1) >> 5].SupBits[((IdIdx - 1) >> 3) & 0x03] & (0x80 >> ((IdIdx - 1) & 0x07))) == 0 )
				{
					// If ISO15765 protocol, this is an error
					if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  Unsupported SID $01 PID $%02X response\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
						bTestFailed = TRUE;
					}
				}
			}
		}
	}


	if ( VerifyIDSupportData ( PIDREQUEST ) == FAIL )
	{
		bTestFailed = TRUE;
	}


	// No longer ignore unsupported response
	gbIgnoreUnsupported = FALSE;

	// reverse order request finished
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		gReverseOrderStateArray[EcuIdx] = NOT_REVERSE_ORDER;
	}

	// Verify that all SID 1 PID reverse/individual support data compares
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( CompareIDSupport ( EcuIdx,
		                        PIDREQUEST,
		                        gstResponse[EcuIdx].PidSupportSize,
		                        gstCompareResponse[EcuIdx].PidSupportSize,
		                        &gstResponse[EcuIdx].PidSupport[0],
		                        &gstCompareResponse[EcuIdx].PidSupport[0],
		                        FALSE ) ==FAIL )
		{
			bTestFailed = TRUE;
		}
	}


	// Determine size of PIDs $06, $07, $08, $09
	if ( DetermineVariablePidSize ( ) != PASS )
	{
		bTestFailed = TRUE;
	}

	// Warn the user that this could take a while
	Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT,
	      "Please wait (press any key to abort)...\n" );
	ClearKeyboardBuffer ( );

	// Request SID 1 PID data in reverse
	for ( IdIdx = 0xFF;
	      IdIdx < 0x100;
	      IdIdx -= 0x01 )
	{
		// Bypass PID request if it is a support PID (already done above)
		if ( (IdIdx & 0x1F) != 0 )
		{
			printf( "INFORMATION: Checking PID $%02X\r", IdIdx );
			stReqMsg.SID    = 0x01;
			stReqMsg.NumIds = 1;
			stReqMsg.ID[0]  = (BYTE)IdIdx;
			RequestSid ( &stReqMsg, REQ_MSG_NORMAL|REQ_MSG_IGNORE_NO_RESPONSE );
			for ( EcuIdx = 0;
			      EcuIdx < gNumOfECUs;
			      EcuIdx++ )
			{
				// Check if data received and PID is not supported
				if ( gstResponse[EcuIdx].PidSize != 0 &&
				     IsIDSupported ( EcuIdx, PIDREQUEST, IdIdx ) == FALSE )
				{
					// If ISO15765 protocol, this is an error
					if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  Unsupported SID $01 PID $%02X response\n",
						      GetEcuId ( EcuIdx ),
						      IdIdx );
					}
				}

				// Check if no data received and PID is supported
				if ( gstResponse[EcuIdx].PidSize == 0 &&
				     IsIDSupported ( EcuIdx, PIDREQUEST, IdIdx ) == TRUE )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $01 PID $%02X in reverse order failed\n",
					      GetEcuId ( EcuIdx ),
					      IdIdx );
				}
			}
		}

		// Abort test if user presses a key
		if ( _kbhit ( ) )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "User abort\n" );
			ClearKeyboardBuffer ( );
			return FAIL;
		}
	}

	printf ( "\n" );

	// If ISO15765 protocol, make sure the required ID values are supported
	// and try group support.  Verify group test only for ISO15765 only!
	if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
	{
		if( VerifyReverseGroupDiagnosticSupport ( ) == FAIL )
		{
			bTestFailed = TRUE;
		}
	}

	// Make sure the single requested PIDs responses match group requested PIDs response.
	if ( VerifySingleAndGroupPIDRequest () == FAIL )
	{
		bTestFailed = TRUE;
	}

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

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

}


/*******************************************************************************
**
**  Function:  VerifyReverseGroupDiagnosticSupport
**
**  Purpose:   Verify PID group support (ISO15765 only)
**
*******************************************************************************/
STATUS VerifyReverseGroupDiagnosticSupport ( void )
{
#define GROUP_PID_REQUEST_SIZE 6
	REQ_MSG        EcuSpecificSidReq[MAX_ECUS];  // holds the last 6 PIDs for each ECU
	REQ_MSG        GroupIdReqMsg;                // holds the group requests
	BYTE           EcuIdx;

	unsigned short PidCount = 0;                 // temp. count of PIDs for current ECU
	BYTE           SinglePidSize;
	unsigned short NumberOfPidsRequested = 0;
	unsigned short DesiredResponseSize = 0;      // hold expected response size for current ECU
	BOOL           bTestFailed = FALSE;


	memset ( &SinglePidSize,
	         0x00,
	         sizeof ( SinglePidSize ) );
	memset ( &EcuSpecificSidReq,
	         0x00,
	         sizeof ( EcuSpecificSidReq ) );

	// Ignore unsupported responses
	// Support info is cleared on receive of first response to reverse order request
	gbIgnoreUnsupported = TRUE;

	// reverse order request started
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		gReverseOrderStateArray[EcuIdx] = REVERSE_ORDER_REQUESTED;
	}

	// Request SID 1 support data as a group
	GroupIdReqMsg.SID    = 0x01;
	GroupIdReqMsg.NumIds = 2;
	GroupIdReqMsg.ID[0]  = 0xE0;
	GroupIdReqMsg.ID[1]  = 0xC0;
	if ( RequestSid ( &GroupIdReqMsg, REQ_MSG_ALLOW_NO_RESPONSE|REQ_MSG_IGNORE_NO_RESPONSE ) == FAIL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $01 reverse group ($E0-$C0) support request failed\n" );
		bTestFailed = TRUE;
	}

	// Request SID 1 support data as a group
	GroupIdReqMsg.SID    = 0x01;
	GroupIdReqMsg.NumIds = GROUP_PID_REQUEST_SIZE;
	GroupIdReqMsg.ID[0]  = 0xA0;
	GroupIdReqMsg.ID[1]  = 0x80;
	GroupIdReqMsg.ID[2]  = 0x60;
	GroupIdReqMsg.ID[3]  = 0x40;
	GroupIdReqMsg.ID[4]  = 0x20;
	GroupIdReqMsg.ID[5]  = 0x00;
	if ( RequestSid ( &GroupIdReqMsg, REQ_MSG_NORMAL ) == FAIL )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "SID $01 reverse group ($A0-$00) support request failed\n" );
		bTestFailed = TRUE;
	}


	// No longer ignore unsupported response
	gbIgnoreUnsupported = FALSE;

	// reverse order request finished
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		gReverseOrderStateArray[EcuIdx] = NOT_REVERSE_ORDER;
	}


	// Verify that all SID 1 PID reverse/individual group support data compares
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		if ( CompareIDSupport ( EcuIdx,
		                        PIDREQUEST,
		                        gstResponse[EcuIdx].PidSupportSize,
		                        gstCompareResponse[EcuIdx].PidSupportSize,
		                        &gstResponse[EcuIdx].PidSupport[0],
		                        &gstCompareResponse[EcuIdx].PidSupport[0],
		                        FALSE ) ==FAIL )
		{
			bTestFailed = TRUE;
		}
	}


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

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

	return PASS;
}


/*******************************************************************************
**
**  Function:  VerifyReverseGroupDiagnosticSupport -
**
**  Purpose:   Verify SID $01 PID group support
**
*******************************************************************************/
STATUS VerifySingleAndGroupPIDRequest ( void )
{
	BYTE           EcuIdx;
	unsigned long  IdIdx;
	unsigned short ByteIdx;
	unsigned short PidCount = 0;                        // temp. count of PIDs for current ECU
	unsigned short NumberOfPidsRequested = 0;
	unsigned short DesiredResponseSize = 0;             // expected response size for current ECU
	unsigned short EcuPidSize[GROUP_PID_REQUEST_SIZE];  // the size for each PID the ECU supports
	unsigned short SinglePidSize[MAX_ECUS][GROUP_PID_REQUEST_SIZE];  // for each ECU, the size for each PID in the group
	REQ_MSG        EcuSpecificSidReq[MAX_ECUS];         // the last 6 PIDs for each ECU
	REQ_MSG        SingleIdReqMsg;                      // single requests
	REQ_MSG        GroupIdReqMsg;                       // group requests
	BOOL           bTestFailed = FALSE;

	// find the list of the last six PIDs supported by each ECU
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		for ( IdIdx = 0xFF, PidCount = 0;
		      IdIdx > 0 &&
		      PidCount < GROUP_PID_REQUEST_SIZE;
		      IdIdx-- )
		{
			// Don't use PID supported PIDs
			if ( IdIdx != 0x00 && IdIdx != 0x20 &&
			     IdIdx != 0x40 && IdIdx != 0x60 &&
			     IdIdx != 0x80 && IdIdx != 0xA0 &&
			     IdIdx != 0xC0 && IdIdx != 0xE0 )
			{
				// If PID is supported, add it to the list
				if ( IsIDSupported ( EcuIdx, PIDREQUEST, IdIdx ) == TRUE )
				{
					EcuSpecificSidReq[EcuIdx].ID[PidCount] = (BYTE)IdIdx;
					PidCount++;
				}
			}
		}
		EcuSpecificSidReq[EcuIdx].NumIds = (BYTE)PidCount;
	}


	// combine the list of PIDs from each ECU into one list with at least one PID from each ECU
	GroupIdReqMsg.SID    = 0x01;
	GroupIdReqMsg.NumIds = 0;
	IdIdx = 0;
	EcuIdx = 0;
	do
	{
		// add one PID at a time to the actual request
		if ( IdIdx < EcuSpecificSidReq[EcuIdx].NumIds )
		{
			// still some PIDs in the specific ECU list
			if ( IsPidInRequest ( GroupIdReqMsg, EcuSpecificSidReq[EcuIdx].ID[IdIdx] ) == FALSE )
			{
				// not already in the list, so add it
				GroupIdReqMsg.ID[GroupIdReqMsg.NumIds] = EcuSpecificSidReq[EcuIdx].ID[IdIdx];
				GroupIdReqMsg.NumIds++;
			}
		}

		// point to next ECU
		EcuIdx++;

		if ( EcuIdx >= gNumOfECUs )
		{
			// at the end of the ECU list, start over with next PID for each ECU
			EcuIdx = 0;
			IdIdx++;
		}

	} while ( GroupIdReqMsg.NumIds < GROUP_PID_REQUEST_SIZE && IdIdx < GROUP_PID_REQUEST_SIZE );


	if ( GroupIdReqMsg.NumIds != 0 )
	{
		// request the PIDs individually, to get the size information
		for ( PidCount = 0;
		      PidCount < GroupIdReqMsg.NumIds;
		      PidCount++ )
		{
			SingleIdReqMsg.SID    = 0x01;
			SingleIdReqMsg.NumIds = 1;
			SingleIdReqMsg.ID[0]  = GroupIdReqMsg.ID[PidCount];
			if ( RequestSid ( &SingleIdReqMsg, REQ_MSG_NORMAL ) == FAIL )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "SID $01 single PID $%02X request failed\n",
				      SingleIdReqMsg.ID[0] );
				bTestFailed = TRUE;
			}

			// save the response PID size
			for ( EcuIdx = 0;
			      EcuIdx < gNumOfECUs;
			      EcuIdx++ )
			{
				SinglePidSize[EcuIdx][PidCount] = gstResponse[EcuIdx].PidSize;
			}
		}


		// request the PIDs as a group and check the responses
		if ( RequestSid ( &GroupIdReqMsg, REQ_MSG_NORMAL) == FAIL )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "SID $01 group PID request failed\n" );
			bTestFailed = TRUE;
		}
		else
		{
			for ( EcuIdx = 0;
			      EcuIdx < gNumOfECUs;
			      EcuIdx++ )
			{
				// rebuild ECU specific PID list, based on final group request
				for ( IdIdx = 0, PidCount = 0, DesiredResponseSize = 0;
				      IdIdx < GROUP_PID_REQUEST_SIZE;
				      IdIdx++ )
				{
					if ( IsIDSupported ( EcuIdx, PIDREQUEST, GroupIdReqMsg.ID[IdIdx] ) == TRUE )
					{
						EcuSpecificSidReq[EcuIdx].ID[PidCount] = GroupIdReqMsg.ID[IdIdx];
						EcuPidSize[PidCount] = SinglePidSize[EcuIdx][IdIdx];
						DesiredResponseSize += SinglePidSize[EcuIdx][IdIdx];
						PidCount++;
					}
				}
				EcuSpecificSidReq[EcuIdx].NumIds = (BYTE)PidCount;

				// check the response size
				if ( gstResponse[EcuIdx].PidSize != DesiredResponseSize )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $01 group/single PID size mismatch (Response = %d bytes  Expected = %d bytes)\n",
					      GetEcuId ( EcuIdx ),
					      gstResponse[EcuIdx].PidSize,
					      DesiredResponseSize );
					bTestFailed = TRUE;
				}
				else if ( gstResponse[EcuIdx].PidSize != 0 )
				{
					// size is good, check message content
					NumberOfPidsRequested = EcuSpecificSidReq[EcuIdx].NumIds;

					for ( ByteIdx = 0;
					      NumberOfPidsRequested > 0 && ByteIdx < gstResponse[EcuIdx].PidSize;
					      NumberOfPidsRequested-- )
					{
						for ( IdIdx = 0;
						      IdIdx < EcuSpecificSidReq[EcuIdx].NumIds;
						      IdIdx++ )
						{
							if ( gstResponse[EcuIdx].Pid[ByteIdx] == EcuSpecificSidReq[EcuIdx].ID[IdIdx] )
							{
								break;  // leave ID for loop
							}
						}

						if ( IdIdx < EcuSpecificSidReq[EcuIdx].NumIds )
						{
							// found the PID, get the offset to the next PID
							ByteIdx += EcuPidSize[IdIdx];
						}
						else
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  SID $01 group/single PID mismatch (PID $%02X)\n",
							      GetEcuId ( EcuIdx ),
							      EcuSpecificSidReq[EcuIdx].ID[IdIdx] );
							bTestFailed = TRUE;
							break;  // leave byte for loop
						}
					} // end for (each PID)
				} // end else (size is good)
			} // end for (each ECU)
		}
	}

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

	return PASS;
}


/*******************************************************************************
**
**  Function:  IsPidInRequest
**
**  Purpose:  Check if the designated PID is already in the request
**
*******************************************************************************/
BOOL IsPidInRequest ( REQ_MSG SidReqList, BYTE Pid )
{
	unsigned short PidIdx;


	for ( PidIdx = 0;
	      PidIdx < SidReqList.NumIds;
	      PidIdx++ )
	{
		if ( SidReqList.ID[PidIdx] == Pid )
		{
			return TRUE;
		}
	}

	return FALSE;
}
