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


// Battery voltage limits
// Below 11VDC, OBD monitors are not required to run
#define BATTERY_MINIMUM      11000

// Above 18VDC, J1978 scan tools are not required to run
#define BATTERY_MAXIMUM      18000


//  Funtion prototypes
STATUS IdentifyOBDProtocol      ( BOOL *pbOBDFound );
STATUS LogJ2534Version          ( void );
STATUS VerifyBatteryVoltage     ( void );
STATUS CheckFor29bitTA          ( void );
STATUS SaveOBDConnectInfo       ( void );
STATUS VerifyOBDConnectInfo     ( void );
STATUS VerifyEcuId              ( BYTE EcuId[] );

extern void InitOBDProtocolList ( void );


// Variables
unsigned short    gNumOfECUs29Bit;
BOOL              gbDetermineProtocol = FALSE;         // set if trying to find an OBD protocol

typedef struct
{
	unsigned long eProtocol;
	BYTE          NumOfECUs;
	BYTE          HeaderSize;
	BYTE          Header[MAX_ECUS][4];
} INITIAL_CONNECT_INFO;
static INITIAL_CONNECT_INFO gstInitialConnect = {0}; // initialize all fields to zero

static BOOL           bFirstConnectFlag = TRUE;
static unsigned short gOBDFoundIndex = 0;            // index of OBD protocol found

extern BOOL           gbJ2534_2_Mode;
extern BYTE           gstResponseTA[MAX_ECUS];


/*******************************************************************************
**
**  Function:  DetermineOBDProtocol
**
**  Purpose:   Find what protocol is used to support OBD
**
*******************************************************************************/
STATUS DetermineOBDProtocol ( void )
{
	BOOL bOBDFound = FALSE;


	// Get the version information and log it
	if ( LogJ2534Version ( ) != PASS )
	{
		// If routine logged an error, pass error up!
		return FAIL;
	}

	// Check if battery is within a resonable range
	if ( VerifyBatteryVoltage ( ) == FAIL )
	{
		return FAIL;
	}

	// If already connected to a protocol, disconnect first
	if ( gbProtocolDetermined == TRUE )
	{
		DisconnectOBDProtocol ( );
		gbProtocolDetermined = FALSE;

		// Close J2534 device
		PassThruClose ( gDeviceID );

		// Add a delay if disconnecting from ISO9141/ISO14230 to make sure the ECUs
		// are out of any previous diagnostic session before determining protocol.
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO9141 ||
		     gstProtocolList[gProtocolIdx].eProtocol == ISO14230 )
		{
			Sleep ( 5000 );
		}

		// Open J2534 device
		{
			unsigned long eRetVal = STATUS_NOERROR;
			// If more than 10 flow controls are needed, try J2534-2 mode first
			if ( gbJ2534_2_Mode )
			{
				eRetVal = PassThruOpen ( "J2534-2:", &gDeviceID );
				if ( eRetVal != STATUS_NOERROR )
				{
					Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "%s returned %ld",
					      "PassThruOpen (\"J2534-2:\", &gDeviceID)",
					      eRetVal );
					exit (FAIL);
				}
			}
			// If J2534-2 mode not availabel, try J2534-1 mode
			else
			{
				eRetVal = PassThruOpen ( NULL, &gDeviceID );
				if ( eRetVal != STATUS_NOERROR )
				{
					Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "%s returned %ld",
					      "PassThruOpen (NULL, &gDeviceID)",
					      eRetVal );
					exit (FAIL);
				}
			}
		}
	}

	// Reset globals
	gProtocolIdx = 0;

	// Initialize protocol list
	InitOBDProtocolList ( );

	gbDetermineProtocol = TRUE;
	// Connect to each protocol in the list and check if SID 1 PID 0 is supported
	if ( IdentifyOBDProtocol ( &bOBDFound ) != FAIL )
	{
		// Connect to the OBD protocol
		if ( bOBDFound == TRUE )
		{
			gProtocolIdx  = gOBDFoundIndex;
			gbProtocolDetermined = TRUE;
			if ( ConnectOBDProtocol ( ) != PASS )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Connect to protocol failed\n" );
				return FAIL;
			}

			if ( (gstUserInput.eComplianceType == US_OBDII ||
			      gstUserInput.eComplianceType == HD_OBD) &&
			     gModelYear >= 2008 &&
			     (gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_I &&
			      gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_29_BIT_I) )
			{
#ifndef _DEBUG
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Current protocol is not allowed for OBD\n" );
				return FAIL;
#endif
			}
			else if ( (gstUserInput.eComplianceType == EOBD_WITH_IUMPR ||
			           gstUserInput.eComplianceType == EOBD_NO_IUMPR   ||
			           gstUserInput.eComplianceType == HD_EOBD) &&
			          gModelYear >= 2014 &&
			          (gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_I &&
			           gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_29_BIT_I &&
			           gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_250K_11_BIT_I &&
			           gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_250K_29_BIT_I) )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Current protocol is not allowed for EOBD\n" );
				return FAIL;
			}

			// IOBD_NO_IUMPR allows all protocols
			// HD_IOBD_NO_IUMPR allows ISO15765 only
			else if ( gstUserInput.eComplianceType == HD_IOBD_NO_IUMPR &&
			          gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_I &&
			          gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_29_BIT_I &&
			          gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_250K_11_BIT_I &&
			          gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_250K_29_BIT_I )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Current protocol is not allowed for HD_IOBD\n" );
				return FAIL;
			}

			//  CNOBD_NO_IUMPR allows 500k ISO15765 only
			else if ( gstUserInput.eComplianceType == CNOBD_NO_IUMPR &&
			          gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_I &&
			          gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_29_BIT_I )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Current protocol is not allowed for China OBD\n" );
				return FAIL;
			}

			// Start tester present to keep vehicle alive
			if ( StartPeriodicMsg ( ) != PASS )
			{
				// If routine logged an error, pass error up!
				return FAIL;
			}

			gbDetermineProtocol = FALSE;
			return PASS;
		} // end if ( bOBDFound == TRUE )
	} // end if ( IdentifyOBDProtocol ( &bOBDFound ) != FAIL )

	gbDetermineProtocol = FALSE;
	return FAIL;
}


/*******************************************************************************
**
**  Function:  IdentifyOBDProtocol
**
**  Purpose:   Attempt to communicate on each specified OBDII protocol channel.
**             If the current link communicates then return result.
**
*******************************************************************************/
STATUS IdentifyOBDProtocol ( BOOL *pbOBDFound )
{
	REQ_MSG      stReqMsg;
	STATUS       eRetCode = PASS;
	STATUS       eRetVal = PASS;
	STATUS       eConnectRetVal = PASS;
	unsigned int ReqCnt  = 0;


	// Setup initial conditions
	*pbOBDFound = FALSE;

	// Connect to each protocol in the list and check if SID 1 PID 0 is supported
	for ( gProtocolIdx = 0;
	      gProtocolIdx < gstUserInput.MaxProtocols && eRetCode == PASS;
	      gProtocolIdx++ )
	{
		eRetVal = PASS;

		// Connect to the protocol
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Checking for OBD on %s protocol\n",
		      gstProtocolList[gProtocolIdx].Name );
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 &&
		     gstProtocolList[gProtocolIdx].eInitFlags & CAN_29BIT_ID )
		{
			eRetVal = CheckFor29bitTA ( );
		}

		if ( eRetVal == PASS &&
		     (eConnectRetVal = ConnectOBDProtocol ( )) == PASS )
		{
			do
			{
				// Check if SID 1 PID 0 supported
				stReqMsg.SID    = 0x01;
				stReqMsg.NumIds = 1;
				stReqMsg.ID[0]  = 0x00;
				if ( (eRetVal = RequestSid ( &stReqMsg, REQ_MSG_NORMAL )) == RETRY )
				{
					if ( ++ReqCnt >= 6 )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "Maximum number of Request retries\n" );
						eRetCode = FAIL;
					}
					else
					{
						Sleep ( 200 );
					}
				}
				else if ( eRetVal != FAIL )
				{
					// We've found an OBD supported protocol
					Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "OBD on %s protocol detected\n",
					      gstProtocolList[gProtocolIdx].Name );

					if ( gNumOfECUsResp > OBD_LEGACY_MAX_ECUS &&
					     gstProtocolList[gProtocolIdx].eProtocol != ISO15765_29_BIT_I &&
					     gstProtocolList[gProtocolIdx].eProtocol != ISO15765_250K_29_BIT_I )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "%d ECUs responding to OBD detection.  %s only supports up to %d ECUs\n",
						      gNumOfECUsResp,
						      gstProtocolList[gProtocolIdx].Name,
						      OBD_LEGACY_MAX_ECUS );
					}
					else if ( gNumOfECUsResp > MAX_ECUS &&
					          (gstProtocolList[gProtocolIdx].eProtocol == ISO15765_29_BIT_I ||
					            gstProtocolList[gProtocolIdx].eProtocol == ISO15765_250K_29_BIT_I) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "%d ECUs responding to OBD detection.  %s only supports up to %d ECUs\n",
						      gNumOfECUsResp,
						      gstProtocolList[gProtocolIdx].Name,
						      MAX_ECUS );
					}

					if ( *pbOBDFound == TRUE )
					{
						// Check if protocol is the same
						if ( gstProtocolList[gProtocolIdx].eProtocolTag != gstProtocolList[gOBDFoundIndex].eProtocolTag )
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "Multiple protocols supporting OBD detected\n" );
							eRetCode = FAIL;
						}
					}

					if ( eRetCode != FAIL )
					{
						// Save connect parameters (protocol, # ECUs, ECU IDs) on very first connect.
						// Connect parameters should match on each subsequent connect.
						if ( bFirstConnectFlag == TRUE )
						{
							SaveOBDConnectInfo ( );
							bFirstConnectFlag = FALSE;
						}
						else
						{
							if ( VerifyOBDConnectInfo ( ) == FAIL )
							{
								return FAIL;
							}
						}

						// Set the found flag and globals
						*pbOBDFound = TRUE;
						gOBDFoundIndex = gProtocolIdx;
					}
				}  //  end else if RequestSid ( &stReqMsg, REQ_MSG_NORMAL )) != FAIL

			}
			while ( eRetVal == RETRY && ReqCnt < 6 );

		}  //  end if ( eRetVal == PASS && ConnectOBDProtocol ( ) == PASS )
		 
		if ( eConnectRetVal != FAIL)
		{
			// Disconnect from the protocol
			DisconnectOBDProtocol ( );
		}


		// Add a delay for ISO9141/ISO14230 to make sure the ECUs are out of
		// any previous diagnostic session.
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO9141 ||
		     gstProtocolList[gProtocolIdx].eProtocol == ISO14230 )
		{
			Sleep ( 5000 );
		}
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  LogJ2534Version
**
**  Purpose:   Read J2534 interface version information
**             then log it to the current log file.
**
*******************************************************************************/
STATUS LogJ2534Version ( void )
{
	STATUS      eRetCode = PASS;
	STATUS      eRetVal;
	static char FirmwareVersionString[80];
	static char DllVersionString[80];
	static char ApiVersionString[80];


	if ( (eRetVal = PassThruReadVersion ( gDeviceID,
	                                     FirmwareVersionString,
	                                     DllVersionString,
	                                     ApiVersionString )) != STATUS_NOERROR )
	{
		Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s returned %ld",
		      "PassThruReadVersion",
		      eRetVal );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "J2534 Version information not available\n" );
	}
	else
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Firmware Version: %s\n",
		      FirmwareVersionString );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "DLL Version: %s\n",
		      DllVersionString );

		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "API Version: %s\n",
		      ApiVersionString );
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  VerifyBatteryVoltage
**
**  Purpose:   Read current vehicle system voltage then
**             verify if the voltage is within specified limits.
**
*******************************************************************************/
STATUS VerifyBatteryVoltage ( void )
{
	STATUS        eRetCode = FAIL;
	STATUS        eRetVal;
	unsigned long BatteryVoltage;


	// Check if battery is within a resonable range
	if ( (eRetVal = PassThruIoctl ( gDeviceID,
	                                READ_VBATT,
	                                NULL,
	                                &BatteryVoltage )) != STATUS_NOERROR )
	{
		// account for non-compliant J2534-1 devices that don't support READ_VBATT
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Battery voltage cannot be read - %s returned %ld\n",
		      "PassThruIoctl(READ_VBATT)",
		      eRetVal );
		eRetCode = PASS;
	}
	else if ( BatteryVoltage < BATTERY_MINIMUM )
	{
		if ( Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_ALL_PROMPT,
		           "Battery voltage is LOW (%ld.%03ldV)\n",
		           (BatteryVoltage / 1000), (BatteryVoltage % 1000) ) == 'Y' )
		{
			eRetCode = PASS;
		}
	}
	else if ( BatteryVoltage > BATTERY_MAXIMUM )
	{
		if ( Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, YES_NO_ALL_PROMPT,
		           "Battery voltage is HIGH (%ld.%03ldV)\n",
		           (BatteryVoltage / 1000), (BatteryVoltage % 1000) ) == 'Y' )
		{
			eRetCode = PASS;
		}
	}
	else
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Battery = %ld.%03ldV\n",
		      (BatteryVoltage / 1000),
		      (BatteryVoltage % 1000));
		eRetCode = PASS;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  CheckFor29bitTA
**
**  Purpose:   Research ECU's TargetAddress and numer of ECUs.
**
*******************************************************************************/
STATUS CheckFor29bitTA ( void )
{
	STATUS         eRetCode;
	STATUS         eRetVal;       // Return Value
	unsigned long  MsgCnt;        // Number of Messages
	unsigned short RetryCnt = 0;  // Number of times request has been resent
	PASSTHRU_MSG   MskMsg;        // structure for filter mask data
	PASSTHRU_MSG   PatMsg;        // structure for filter pattern data
	PASSTHRU_MSG   TxMsg;         // structure for Target Address transmit message
	PASSTHRU_MSG   RxMsg;         // structure for Target Address response message


	do
	{
		// Data Initalize
		gNumOfECUs29Bit = 0;
		eRetCode = FAIL;

		// Wait, if this is a retry
		if ( RetryCnt != 0 )
		{
			Sleep ( 200 );  // delay for 200 ms
		}

		// Connect to protocol
		if ( (eRetVal = PassThruConnect ( gDeviceID,
		                                  CAN, CAN_29BIT_ID,
		                                  gstProtocolList[gProtocolIdx].BaudRate,
		                                  &gstProtocolList[gProtocolIdx].ChannelID )) != STATUS_NOERROR )
		{
			Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s returned %ld",
			      "PassThruConnect(29Bit)",
			      eRetVal );
			return FAIL;
		}

		// Clear the message filters
		if ( (eRetVal = PassThruIoctl ( gstProtocolList[gProtocolIdx].ChannelID,
		                                CLEAR_MSG_FILTERS,
		                                NULL,
		                                NULL )) != STATUS_NOERROR )
		{
			Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s returned %ld",
			      "PassThruIoctl(CLEAR_MSG_FILTERS)",
			      eRetVal );
			return FAIL;
		}

		// Setup pass filter to read only OBD requests / responses
		MskMsg.eProtocolID = CAN;
		MskMsg.TxFlags     = CAN_29BIT_ID;
		MskMsg.DataSize    = 4;
		MskMsg.Data[0]     = 0xFF;
		MskMsg.Data[1]     = 0xFF;
		MskMsg.Data[2]     = 0xFF;
		MskMsg.Data[3]     = 0x00;

		PatMsg.eProtocolID = CAN;
		PatMsg.TxFlags     = CAN_29BIT_ID;
		PatMsg.DataSize    = 4;
		PatMsg.Data[0]     = 0x18;
		PatMsg.Data[1]     = 0xDA;
		PatMsg.Data[2]     = 0xF1;
		PatMsg.Data[3]     = 0x00;

		if ( (eRetVal = PassThruStartMsgFilter ( gstProtocolList[gProtocolIdx].ChannelID,
		                                         PASS_FILTER,
		                                         &MskMsg, &PatMsg, NULL,
		                                         &gstProtocolList[gProtocolIdx].FilterID )) != STATUS_NOERROR )
		{
			Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s returned %ld",
			      "PassThruStartMsgFilter",
			      eRetVal );
			return FAIL;
		}

		// Clear the receive queue before sending request
		if ( (eRetVal = PassThruIoctl ( gstProtocolList[gProtocolIdx].ChannelID,
		                                CLEAR_RX_BUFFER,
		                                NULL,
		                                NULL )) != STATUS_NOERROR )
		{
			Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s returned %ld",
			      "PassThruIoctl(CLEAR_RX_BUFFER)",
			      eRetVal );
			return FAIL;
		}

		// Send the request by CAN Format as ISO15765 29bits
		TxMsg.eProtocolID = CAN;
		TxMsg.RxStatus    = 0;
		TxMsg.TxFlags     = CAN_29BIT_ID;
		TxMsg.DataSize    = 12;
		TxMsg.Data[0]     = 0x18;  // Request Message Functional Address MSB
		TxMsg.Data[1]     = 0xDB;  // Request Message Functional Address
		TxMsg.Data[2]     = 0x33;  // Request Message Functional Address
		TxMsg.Data[3]     = 0xF1;  // Request Message Functional Address LSB
		TxMsg.Data[4]     = 0x02;  // PCI Byte
		TxMsg.Data[5]     = 0x01;  // Service ID
		TxMsg.Data[6]     = 0x00;  // PID
		TxMsg.Data[6]     = 0x00;  // Padding Data
		TxMsg.Data[7]     = 0x00;  // Padding Data
		TxMsg.Data[8]     = 0x00;  // Padding Data
		TxMsg.Data[9]     = 0x00;  // Padding Data
		TxMsg.Data[10]    = 0x00;  // Padding Data
		TxMsg.Data[11]    = 0x00;  // Padding Data

		MsgCnt = 1;
		LogMsg( &TxMsg, LOG_REQ_MSG );
		if ( (eRetVal  = PassThruWriteMsgs ( gstProtocolList[gProtocolIdx].ChannelID,
		                                     &TxMsg,
		                                     &MsgCnt,
		                                     500 )) != STATUS_NOERROR )
		{
			//  don't log timeouts during DetermineOBDProtocol
			if ( !(gbDetermineProtocol == TRUE && eRetVal == ERR_TIMEOUT) )
			{
				Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "%s returned %ld",
				      "PassThruWriteMsgs",
				      eRetVal );
			}
			return FAIL;
		}

		MsgCnt = 1;
		if ( (eRetVal  = PassThruReadMsgs ( gstProtocolList[gProtocolIdx].ChannelID,
		                                    &RxMsg,
		                                    &MsgCnt,
		                                    500 )) != STATUS_NOERROR )
		{
			//  don't fail timeouts during DetermineOBDProtocol
			if ( !(gbDetermineProtocol == TRUE &&
			       (eRetVal == ERR_TIMEOUT || eRetVal == ERR_BUFFER_EMPTY)) )
			{
				Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "%s returned %ld",
				      "PassThruReadMsgs",
				      eRetVal );
			}
			return FAIL;
		}

		// All OBD ECUs should respond within 50msec
		while ( MsgCnt == 1 )
		{
			LogMsg ( &RxMsg, LOG_NORMAL_MSG );

			if ( RxMsg.DataSize >= 5 &&
			     (RxMsg.Data[5] == 0x41 ||
			      (RxMsg.Data[5] == NAK &&
			       RxMsg.Data[7] == NAK_REPEAT_REQUEST)) )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU#%d: Target Address %02X(hex)%s\n",
				      gNumOfECUs29Bit+1,
				      RxMsg.Data[3],
				      (RxMsg.Data[5] == NAK) ? " BUSY REPEAT REQUEST" :"" );

				if ( gNumOfECUs29Bit < MAX_ECUS )
				{
					gstResponseTA[gNumOfECUs29Bit] = RxMsg.Data[3]; // ECU TA
					gNumOfECUs29Bit++;                              // Increment number of positive respond ECUs

					if ( RxMsg.Data[5] == NAK &&
					     RxMsg.Data[7] == NAK_REPEAT_REQUEST )
					{
						eRetCode = RETRY;
					}
					else if ( eRetCode != RETRY )
					{
						eRetCode = PASS;
					}
				}
				else
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "Too many ISO 15765-4 (29 bit) OBD ECU responses\n" );
					eRetCode = FAIL;
				}
			}

			MsgCnt = 1;
			if ( (eRetVal = PassThruReadMsgs ( gstProtocolList[gProtocolIdx].ChannelID,
			                                   &RxMsg,
			                                   &MsgCnt,
			                                   500 )) != STATUS_NOERROR )
			{
				//  don't fail timeouts during DetermineOBDProtocol
				if ( !(gbDetermineProtocol == TRUE &&
				       (eRetVal == ERR_TIMEOUT || eRetVal == ERR_BUFFER_EMPTY)) )
				{
					Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "%s returned %ld",
					      "PassThruReadMsgs",
					      eRetVal );
				}
			}
		}  //  end while ( MsgCnt == 1 )

		if ( eRetCode == PASS || eRetCode == RETRY )
		{
			// if 29 bit ISO 15765 was found then disconnect, but keep the filters.
			// otherwise, the calling function will disconnect and clear filters.
			if ( (eRetVal = PassThruDisconnect ( gstProtocolList[gProtocolIdx].ChannelID )) != STATUS_NOERROR )
			{
				Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "%s returned %ld",
				      "PassThruDisconnect",
				      eRetVal );
			}
		}
	}
	while ( eRetCode == RETRY && RetryCnt++ < 5 );

	if ( RetryCnt == 6 )
	{
		eRetCode = FAIL;
	}

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SaveOBDConnectInfo
**
**  Purpose:   Save connection information from initial connect for comparison
**             during subsequent connects.
**
*******************************************************************************/
STATUS SaveOBDConnectInfo ( void )
{
	BYTE EcuIdx;


	memset ( &gstInitialConnect,
	         0,
	         sizeof ( gstInitialConnect ) );

	gstInitialConnect.eProtocol  = gstProtocolList[gProtocolIdx].eProtocol;
	gstInitialConnect.NumOfECUs  = gNumOfECUsResp;
	gstInitialConnect.HeaderSize = gstProtocolList[gProtocolIdx].HeaderSize;

	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUsResp && EcuIdx < MAX_ECUS;
	      EcuIdx++ )
	{
		memcpy ( gstInitialConnect.Header[EcuIdx],
		         gstResponse[EcuIdx].Header,
		         gstProtocolList[gProtocolIdx].HeaderSize );
	}

	return PASS;
}


/*******************************************************************************
**
**  Function:  VerifyOBDConnectInfo
**
**  Purpose:   Compare connection information for current connect with initial connect.
**
*******************************************************************************/
STATUS VerifyOBDConnectInfo ( void )
{
	BYTE    EcuIdx;
	STATUS  eRetCode = PASS;


	// same protocol ?
	if ( gstProtocolList[gProtocolIdx].eProtocol != gstInitialConnect.eProtocol )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Different protocol (%s) than initial connect.\n",
		      gstProtocolList[gProtocolIdx].Name );
		eRetCode = FAIL;
	}

	// same number of ECUs ?
	if ( gNumOfECUsResp != gstInitialConnect.NumOfECUs )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Number of ECUs responding (%d) different than number of ECUs from initial connect (%d)\n",
		      gNumOfECUsResp,
		      gstInitialConnect.NumOfECUs );
		eRetCode = FAIL;
	}

	// check each ECU ID
	for ( EcuIdx=0;
	      EcuIdx < gNumOfECUsResp;
	      EcuIdx++ )
	{
		if ( VerifyEcuId ( gstResponse[EcuIdx].Header ) == FAIL )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU ID %02X doesn't match IDs from initial connect\n",
			      GetEcuId ( EcuIdx ) );
			eRetCode = FAIL;
		}
	}

	return  eRetCode;
}


/*******************************************************************************
**
**  Function:  VerifyEcuId
**
**  Purpose:   Compare ECU ID with those from initial connect.
**
*******************************************************************************/
STATUS VerifyEcuId ( BYTE EcuId[] )
{
	BYTE EcuIdx;


	for ( EcuIdx=0;
	      EcuIdx < gstInitialConnect.NumOfECUs;
	      EcuIdx++ )
	{
		if ( memcmp ( EcuId, gstInitialConnect.Header[EcuIdx], gstInitialConnect.HeaderSize ) == 0 )
		{
			return PASS;
		}
	}

	return FAIL;
}


/*******************************************************************************
**
**  Function:  GetEcuId
**
**  Purpose:   Return ECU ID independent of protocol.
**
*******************************************************************************/
unsigned long GetEcuId ( BYTE EcuIdx )
{
	BYTE          HeaderIndex;
	unsigned int  ID = 0;


	if ( EcuIdx < gNumOfECUs )
	{
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
		{
			// ISO15765
			for ( HeaderIndex = 0;
			      HeaderIndex < gstProtocolList[gProtocolIdx].HeaderSize;
			      ++HeaderIndex )
			{
				ID = (ID << 8) | gstResponse[EcuIdx].Header[HeaderIndex];
			}
		}
		else
		{
			// J1850PWM
			// J1850VPW
			// ISO9141
			// ISO14230
			ID = gstResponse[EcuIdx].Header[2];
		}
	}

	return ID;
}
