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


//  Funtion prototypes
STATUS SetupRequestMsg     ( REQ_MSG *, PASSTHRU_MSG * );
STATUS ProcessLegacyMsg    ( REQ_MSG *,
                             PASSTHRU_MSG *,
                             unsigned long *,
                             BYTE *,
                             unsigned long *,
                             unsigned long *,
                             unsigned long *,
                             unsigned long );
STATUS ProcessISO15765Msg  ( REQ_MSG *,
                             PASSTHRU_MSG *,
                             unsigned long *,
                             BYTE *,
                             BYTE *,
                             unsigned long *,
                             unsigned long * );

extern STATUS ResetSidResponseData ( PASSTHRU_MSG * );
extern STATUS SaveSidResponseData  ( PASSTHRU_MSG *,
                                     REQ_MSG *,
                                     BYTE * );
extern STATUS LookupEcuIndex       ( PASSTHRU_MSG *,
                                     BYTE * );
extern void   SaveTransactionStart ( void );        // marks the start of a new transaction in the ring buffer


// Wait / Pending data
static unsigned long EcuWaitFlags = 0;            // up to 32 ECUs
static unsigned long ResponsePendingDelayTimeMsecs = 0;
static BOOL          bPadErrorPermanent = FALSE;

static unsigned long gMaxResponseTimeMsecs = 100;
static unsigned long gMinResponseTimeMsecs = 0;  // min response time


/*******************************************************************************
**
**  Function:  RequestSid
**
**  Purpose:   Request a service ID
**
**  Returns:   RETRY  - NRC=$21 durning initialization of ISO 15765
**             ERRORS - One or more correct early/late responses
**             FAIL   - No response, wrong response, or catestrophic error
**             PASS   - One or more correct responses, all on time
**
*******************************************************************************/
STATUS RequestSid ( REQ_MSG      *stReqMsg,
                    unsigned long Flags )
{
	PASSTHRU_MSG  RxMsg;
	PASSTHRU_MSG  TxMsg;
	unsigned long MsgCnt;
	STATUS        eRetVal;
	STATUS        eRetCode = PASS;    // saves the return code from function calls
	unsigned long StartTimestampMsecs;
	unsigned long ResponseTimeoutMsecs;
	unsigned long ResponseTimeExtensionMsecs;
	unsigned long TxTimestampMsecs;
	unsigned long SOMTimestampMsecs;
	BYTE          NumResponses;
	BYTE          NumFirstFrames;
	BOOL          bFirstResponse;
	char          String[MAX_LOG_STRING_SIZE];
	BYTE          EcuTimingIdx;


	// Initialize local variables
	ResponseTimeExtensionMsecs = 0;
	TxTimestampMsecs           = 0;
	SOMTimestampMsecs          = 0;

	// Reset wait variables
	EcuWaitFlags                   = 0;
	ResponsePendingDelayTimeMsecs  = 0;

	// Initialize ECU variables
	for ( EcuTimingIdx = 0;
	      EcuTimingIdx < gNumOfECUsForAlloc;
	      EcuTimingIdx++ )
	{
		gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs    = 0;
		gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs = 0;
		gstEcuTimingData[EcuTimingIdx].bFFReceived                   = FALSE;
		gstEcuTimingData[EcuTimingIdx].bNAKReceived                  = FALSE;
	}

	// if gbSuspendLogOutput is true, then clear buffer
	if ( gbSuspendLogOutput == TRUE )
	{
		SaveTransactionStart ( );
	}

	// If not burst test, stop tester present message and delay
	//  before each request to avoid exceeding minimum OBD request timing
	if ( (Flags & REQ_MSG_NO_PERIODIC_DISABLE) == 0 )
	{
		// Stop the tester present message before each request
		if ( StopPeriodicMsg ( TRUE ) == FAIL )
		{
			return FAIL;
		}

		gstProtocolList[gProtocolIdx].TesterPresentID = -1;

		Sleep ( gRequestDelayTimeMsecs );
	}

	// Setup request message based on the protocol
	if ( SetupRequestMsg ( stReqMsg, &TxMsg ) != PASS )
	{
		return FAIL;
	}

	// Clear the transmit queue before sending request
	if ( (eRetVal = PassThruIoctl ( gstProtocolList[gProtocolIdx].ChannelID,
	                               CLEAR_TX_BUFFER, NULL, NULL )) != STATUS_NOERROR )
	{
		Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s returned %ld",
		      "PassThruIoctl(CLEAR_TX_BUFFER)",
		      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
	MsgCnt = 1;
	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;
		}
	}

	// Log the request message to compare to what is sent
	LogMsg( &TxMsg, LOG_REQ_MSG );

	// Reset the response data buffers
	if ( ResetSidResponseData ( &TxMsg ) != PASS )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Cannot reset SID $%02 response data\n",
		      TxMsg.Data[gstProtocolList[gProtocolIdx].HeaderSize] );
		return FAIL;
	}

	// Read the response(s) with a timeout of twice what is allowed
	// so we can see late responses.
	NumResponses    = 0;
	NumFirstFrames  = 0;
	bFirstResponse  = TRUE;
	StartTimestampMsecs  = GetTickCount ( );

	do
	{
		if ( bFirstResponse == TRUE )
		{
			bFirstResponse = FALSE;
			ResponseTimeoutMsecs =  5 * gMaxResponseTimeMsecs;
			sprintf_s ( String, MAX_LOG_STRING_SIZE,
			            "In RequestSid - Initial PassThruReadMsgs" );
		}
		else
		{
			ResponseTimeoutMsecs =  (5 * gMaxResponseTimeMsecs) + ResponseTimeExtensionMsecs;
			sprintf_s ( String, MAX_LOG_STRING_SIZE,
			            "In RequestSid - Loop PassThruReadMsgs" );
		}

		// Read the next response
		MsgCnt = 1;
		eRetVal = PassThruReadMsgs( gstProtocolList[gProtocolIdx].ChannelID,
		                            &RxMsg,
		                            &MsgCnt,
		                            ResponseTimeoutMsecs );

		if ( eRetVal != STATUS_NOERROR &&
		     eRetVal != ERR_BUFFER_EMPTY &&
		     eRetVal != ERR_NO_FLOW_CONTROL )
		{
			// Log undesirable returns
			Log ( J2534_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s returned %ld",
			      String,
			      eRetVal );
			eRetCode |= FAIL;
		}

		// If a message was received, process it
		if ( MsgCnt == 1 )
		{
			// Save all read messages in the log file
			LogMsg ( &RxMsg, LOG_NORMAL_MSG );

			// Process response based on protocol
			switch ( gstProtocolList[gProtocolIdx].eProtocol )
			{
				case J1850VPW:
				case J1850PWM:
				case ISO9141:
				case ISO14230:
				{
					eRetCode |= ProcessLegacyMsg ( stReqMsg,
					                               &RxMsg,
					                               &StartTimestampMsecs,
					                               &NumResponses,
					                               &TxTimestampMsecs,
					                               &ResponseTimeExtensionMsecs,
					                               &SOMTimestampMsecs,
					                               Flags );
				}
				break;

				case ISO15765:
				{
					eRetCode |= ProcessISO15765Msg ( stReqMsg,
					                                 &RxMsg,
					                                 &StartTimestampMsecs,
					                                 &NumResponses,
					                                 &NumFirstFrames,
					                                 &TxTimestampMsecs,
					                                 &ResponseTimeExtensionMsecs );
				}
				break;

				default:
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "Invalid protocol specified for response.\n" );
					return FAIL;
				}
			}
		}

		// If all expected ECUs responded and flag is set, don't wait for timeout
		// NOTE: This mechanism is only good for single message response per ECU
		if ( NumResponses >= gNumOfECUs &&
		     (Flags & REQ_MSG_RETURN_AFTER_ALL_RESPONSES) )
		{
			break;  // leave do/while loop
		}
	}
	while ( MsgCnt == 1 &&
	        (GetTickCount ( ) - StartTimestampMsecs) < ((5 * gMaxResponseTimeMsecs) + ResponseTimeExtensionMsecs) );  //extend response time

	// Restart the periodic message if protocol determined and not in burst test
	if ( gbProtocolDetermined == TRUE &&
	     (Flags & REQ_MSG_NO_PERIODIC_DISABLE ) == 0 )
	{
		if ( gstProtocolList[gProtocolIdx].TesterPresentID == -1 )
		{
			if ( StartPeriodicMsg ( ) != PASS )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Problems starting periodic messages.\n" );
				eRetCode |= FAIL;
			}
		}
	}

	// Preserve the total number of ECUs responding and
	// allow calling routine to access information.
	gNumOfECUsResp = NumResponses;

	// Return code based on whether this protocol supports OBD
	if ( NumResponses > 0 )
	{
		if ( gbProtocolDetermined == FALSE &&
		     (eRetCode & RETRY) != RETRY )
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%d OBD ECU(s) found\n",
			      gNumOfECUsResp);

			// IF this is the first ECU check save the number of responses
			if ( geTestPhase == eTestNoDTC && gTestSubsection == 2 )
			{
				if ( gNumOfECUsResp > gNumOfECUs )
				{
					gNumOfECUs = gNumOfECUsResp;
				}
			}

			// Check if number of OBD-II ECUs entered by user matches the number detected
			if ( gNumOfECUsResp != gUserNumOfECUs )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Number of OBD-II ECUs currently detected (%d) does not match the number previously entered (%d).\n",
				      gNumOfECUsResp,
				      gUserNumOfECUs );
				eRetCode |= FAIL;
			}

		}

		// If there weren't any errors since sending the request, pass
		if ( (eRetCode & FAIL) != FAIL )
		{
			if ( (eRetCode & RETRY) == RETRY )
			{
				return RETRY;
			}
			else if ( (eRetCode & ERRORS) == ERRORS )
			{
				return ERRORS;
			}
			else
			{
				return PASS;
			}
		}
	}

	// If there was at least one failure since sending the request, return FAIL)
	if ( (eRetCode & FAIL) == FAIL )
	{
		return FAIL;
	}

	if ( Flags & REQ_MSG_ALLOW_NO_RESPONSE )
	{
		// calling function must determine any actual responses
		return PASS;
	}

	if ( gbProtocolDetermined == TRUE &&
	     (Flags & REQ_MSG_IGNORE_NO_RESPONSE) != REQ_MSG_IGNORE_NO_RESPONSE )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "No response to OBD request\n" );
	}
	return FAIL;
}


/*******************************************************************************
**
**  Function:  ProcessLegacyMsg
**
**  Purpose:   Process response based on connection to
**             J1850VPW, J1850PWM, ISO9141, or ISO14230.
**
**  Returns:   ERRORS, FAIL, or PASS
**
*******************************************************************************/
STATUS ProcessLegacyMsg ( REQ_MSG       *pstReqMsg,
	                      PASSTHRU_MSG  *pRxMsg,
	                      unsigned long *pStartTimestampMsecs,
	                      BYTE          *pNumResponses,
	                      unsigned long *pTxTimestampMsecs,
	                      unsigned long *pExtendResponseTimeMsecs,
	                      unsigned long *pSOMTimestampMsecs,
	                      unsigned long  Flags )
{
	unsigned long  ResponseTimeMsecs;
	unsigned long  ResponseTimeDeltaMsecs = 0;
	unsigned long  EcuTimingIdx;
	BYTE           EcuIdx;
	BYTE           HeaderSize;
	unsigned long  EcuId = 0;
	unsigned short IDIndex;
	STATUS         eRetCode = PASS;         // saves the return code from function calls
	BOOL           bRequestMsgLpbk = FALSE;


	// Assume the header is three bytes, we verify this later (can't check Indications)
	HeaderSize = 3;

	// create the ECU ID for later use
	for ( IDIndex = 0;
	      IDIndex < HeaderSize;
	      IDIndex++ )
	{
		EcuId = (EcuId << 8) + (pRxMsg->Data[IDIndex]);
	}

	if ( pRxMsg->RxStatus & TX_MSG_TYPE )
	{
		bRequestMsgLpbk = TRUE;
	}
	else
	{
		// Find this ECU's Timing Structure
		for ( EcuTimingIdx = 0;
		      EcuTimingIdx < OBD_LEGACY_MAX_ECUS;
		      EcuTimingIdx++ )
		{
			if ( gstEcuTimingData[EcuTimingIdx].EcuId == EcuId ||
			     gstEcuTimingData[EcuTimingIdx].EcuId == 0x00 )
			{
				if ( gstEcuTimingData[EcuTimingIdx].EcuId == 0x00 )
				{
					gstEcuTimingData[EcuTimingIdx].EcuId = EcuId;
				}
				break;  // leave ECU Timing for loop
			}
		}
	}

	// Check for echoed request message
	if ( pRxMsg->RxStatus & TX_MSG_TYPE )
	{
		if ( pRxMsg->DataSize >= (unsigned long)(HeaderSize + 1) &&
		     pRxMsg->Data[HeaderSize] == pstReqMsg->SID )
		{
			// Save the timestamp
			*pTxTimestampMsecs = pRxMsg->TimestampMsecs;
		}
	}

	// Get Start of Message timestamp to compute response time (ISO9141 & ISO14230 only)
	else if ( pRxMsg->RxStatus & START_OF_MESSAGE )
	{
		*pSOMTimestampMsecs = pRxMsg->TimestampMsecs;
	}

	// Check for NAK response message
	else if ( pRxMsg->DataSize >= (unsigned long)(HeaderSize+3)  &&
	          pRxMsg->Data[HeaderSize] == NAK &&
	          pRxMsg->Data[HeaderSize+1] == pstReqMsg->SID )
	{
		// Verify header is 3-byte for ISO14230
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO14230 &&  // if ISO14230 AND
		     ((pRxMsg->Data[0] & 0x80) != 0x80 ||             // NOT functional/physical address OR
		       ((pRxMsg->Data[0] & 0x3F) < 0x01 &&
		        (pRxMsg->Data[0] & 0x3F) > 0x07)) ) // bad size
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ISO14230 NAK response had a bad Format byte\n" );
			return FAIL;
			// if the software were to continue on, HeaderSize calculations would be needed
			// here, in SaveSidResponseData, and in LookupEcuIndex
		}

		if ( gstProtocolList[gProtocolIdx].eProtocol != ISO9141 &&
		     gstProtocolList[gProtocolIdx].eProtocol != ISO14230 )
		{
			*pSOMTimestampMsecs = pRxMsg->TimestampMsecs;    // for J1850xxx
		}

		ResponseTimeDeltaMsecs = *pSOMTimestampMsecs - *pTxTimestampMsecs;
		ResponseTimeMsecs = ResponseTimeDeltaMsecs / 1000;

		// same timestamp logic used to get EOM, SOM and DELTA

		// Tally up the response statistics
		gstEcuTimingData[EcuTimingIdx].AggregateResponseTimeMsecs += ResponseTimeMsecs;
		gstEcuTimingData[EcuTimingIdx].AggregateResponses++;
		if ( ResponseTimeMsecs > gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs )
		{
			gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs = ResponseTimeMsecs;
		}

		// Display for non-class 2
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO9141 ||
		     gstProtocolList[gProtocolIdx].eProtocol == ISO14230 )
		{
			// Print time stamps to log file for visual time verification.
			Log ( NETWORK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "EOM: %lu usec, SOM: %lu usec, DELTA: %lu usec\n",
			      *pTxTimestampMsecs,
			      *pSOMTimestampMsecs,
			      ResponseTimeDeltaMsecs );
		}

		// Check if response was too soon
		if ( ResponseTimeDeltaMsecs < (gMinResponseTimeMsecs * 1000) )
		{
			if ( HeaderSize >= 3 )
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  OBD Response was sooner than allowed (< %dmsec)\n",
				      pRxMsg->Data[2],
				      gMinResponseTimeMsecs );
				eRetCode = ERRORS;
			}
			else
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "OBD Response was sooner than allowed (< %dmsec)\n",
				      gMinResponseTimeMsecs );
				eRetCode = ERRORS;
			}

			gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
			gstEcuTimingData[EcuTimingIdx].RespTimeTooSoon++;

		}

		// Check if response was late
		if ( ResponseTimeDeltaMsecs > ((gMaxResponseTimeMsecs + *pExtendResponseTimeMsecs) * 1000) )
		{
			if ( HeaderSize >= 3 )
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  OBD Response was later than allowed (> %dmsec)\n",
				      pRxMsg->Data[2],
				      (gMaxResponseTimeMsecs + *pExtendResponseTimeMsecs) );
				eRetCode = ERRORS;
			}
			else
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "OBD Response was later than allowed (> %dmsec)\n",
				      (gMaxResponseTimeMsecs + *pExtendResponseTimeMsecs) );
				eRetCode = ERRORS;
			}

			gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
			gstEcuTimingData[EcuTimingIdx].RespTimeTooLate++;

		}

		// If response was not late, reset the start time
		*pStartTimestampMsecs = GetTickCount ( );
		*pTxTimestampMsecs = pRxMsg->TimestampMsecs;

		// Save the response information
		if ( SaveSidResponseData ( pRxMsg,
		                           pstReqMsg,
		                           pNumResponses ) != PASS )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Cannot save SID response data\n" );
			return FAIL;
		}

		//check the kind of response received for the vehicl
		if ( pRxMsg->Data[HeaderSize+2] == NAK_NOT_SUPPORTED ) //0x11
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "SID $%02X not supported.\n",
			      pRxMsg->Data[HeaderSize+1] );
		}
		else if ( pRxMsg->Data[HeaderSize+2] == NAK_INVALID_FORMAT ) //0x12
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "SID $%02X supported. Unsupported PID request.\n",
			      pRxMsg->Data[HeaderSize+1] );
		}
		else if ( pRxMsg->Data[HeaderSize+2] == NAK_SEQUENCE_ERROR ) //0x22
		{
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "SID $%02X supported. Conditions not correct.\n",
			      pRxMsg->Data[HeaderSize+1] );
		}

		// If response pending, extend the wait time to worst case P3 max
		if ( pRxMsg->Data[HeaderSize+2] == NAK_RESPONSE_PENDING )
		{
			// Don't allow extended response time for infotype 6
			// No group requests for legacy protocols
			if ( pstReqMsg->SID    != 9 &&
			     pstReqMsg->ID[0] != 6 )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "SID $%02X supported. Response Pending.\n",
				      pRxMsg->Data[HeaderSize+1] );
				*pExtendResponseTimeMsecs = 3000;

				if ( LookupEcuIndex ( pRxMsg, &EcuIdx ) == PASS )
				{
					gstResponse[EcuIdx].bResponseReceived = FALSE; // allow ECU to respond again
				}
				else
				{
					return FAIL;
				}
			}
		}
	}

	// Check for response message
	else if ( pRxMsg->DataSize >= (unsigned long)(HeaderSize+1) )
	{
		// Verify header is 3-byte for ISO14230
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO14230 &&  // if ISO14230 AND
		     ((pRxMsg->Data[0] & 0x80) != 0x80 ||            // NOT functional/physical address OR
		      ((pRxMsg->Data[0] & 0x3F) < 0x01 &&
		       (pRxMsg->Data[0] & 0x3F) > 0x07)) )  // bad size
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ISO14230 response had a bad Format byte\n" );
			return FAIL;
			// if the software were to continue on, HeaderSize calculations would be needed
			// here, in SaveSidResponseData, and in LookupEcuIndex
		}

		if ( gstProtocolList[gProtocolIdx].eProtocol != ISO9141 &&
		     gstProtocolList[gProtocolIdx].eProtocol != ISO14230 )
		{
			*pSOMTimestampMsecs = pRxMsg->TimestampMsecs;    // for J1850xxx
		}

		ResponseTimeDeltaMsecs = *pSOMTimestampMsecs - *pTxTimestampMsecs;
		ResponseTimeMsecs = ResponseTimeDeltaMsecs / 1000;

		// Tally up the response statistics
		gstEcuTimingData[EcuTimingIdx].AggregateResponseTimeMsecs += ResponseTimeMsecs;
		gstEcuTimingData[EcuTimingIdx].AggregateResponses++;
		if ( ResponseTimeMsecs > gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs )
		{
			gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs = ResponseTimeMsecs;
		}

		// Display for non-class 2
		if ( gstProtocolList[gProtocolIdx].eProtocol == ISO9141 ||
		     gstProtocolList[gProtocolIdx].eProtocol == ISO14230 )
		{
			// Print time stamps to log file for visual time verification.
			Log ( NETWORK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "EOM: %lu usec, SOM: %lu usec, DELTA: %lu usec\n",
			      *pTxTimestampMsecs,
			      *pSOMTimestampMsecs,
			      ResponseTimeDeltaMsecs );
		}

		// Check if response was too soon
		// Since under ISO9141 or ISO14230 we have saved off the ending timest
		// as pTxTimestampMsecs, we can now accurately calculate the difference between the
		// end of the last message to the beginning of this response.
		if ( (ResponseTimeDeltaMsecs / 1000) < gMinResponseTimeMsecs )
		{
			// Exceeded maximum response time
			if ( HeaderSize >= 3 )
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  OBD Response was sooner than allowed (< %dmsec)\n",
				      pRxMsg->Data[2],
				      (gMinResponseTimeMsecs) );
				eRetCode = ERRORS;
			}
			else
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "OBD Response was sooner than allowed (< %dmsec)\n",
				      (gMinResponseTimeMsecs) );
				eRetCode = ERRORS;
			}

			if ( !bRequestMsgLpbk )
			{
				gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
				gstEcuTimingData[EcuTimingIdx].RespTimeTooSoon++;
			}

		}

		// Check if response was late
		// Since under ISO9141 or ISO14230 we have saved off the ending timestamp
		// as pTxTimestampMsecs, we can now accurately calculate the difference between the
		// end of the last message to the beginning of this response.
		if ( ResponseTimeDeltaMsecs > ((gMaxResponseTimeMsecs + *pExtendResponseTimeMsecs) * 1000) )
		{
			// Exceeded maximum response time
			if ( HeaderSize >= 3 )
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  OBD Response was later than allowed (> %dmsec)\n",
				      pRxMsg->Data[2],
				      (gMaxResponseTimeMsecs + *pExtendResponseTimeMsecs) );
				eRetCode = ERRORS;
			}
			else
			{
				Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "OBD Response was later than allowed (> %dmsec)\n",
				      (gMaxResponseTimeMsecs + *pExtendResponseTimeMsecs) );
				eRetCode = ERRORS;
			}

			if ( !bRequestMsgLpbk )
			{
				gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
				gstEcuTimingData[EcuTimingIdx].RespTimeTooLate++;
			}

		}

		// If response was not late, reset the start time
		*pStartTimestampMsecs = GetTickCount ( );
		*pTxTimestampMsecs = pRxMsg->TimestampMsecs;

		// Check for proper SID response
		if ( pRxMsg->Data[HeaderSize] != (pstReqMsg->SID + OBD_RESPONSE_BIT) )
		{
			if ( HeaderSize >= 3 )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  Invalid SID response to request\n",
				      pRxMsg->Data[2] );
			}
			else
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "Invalid SID response to request\n" );
			}
			return FAIL;
		}

		// if a PID was requested
		if ( pstReqMsg->NumIds != 0 )
		{
			// Check for proper PID/MID/TID/INF response
			for ( IDIndex = 0;
			      IDIndex < pstReqMsg->NumIds;
			      IDIndex++ )
			{
				if ( pRxMsg->Data[HeaderSize+1] == pstReqMsg->ID[IDIndex] )
				{
					break;  // leave ID for loop
				}
			}

			if ( IDIndex == pstReqMsg->NumIds )
			{
				if ( HeaderSize >= 3 )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  Invalid PID response to request\n",
					      pRxMsg->Data[2] );
				}
				else
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "Invalid PID response to request\n" );
				}
				return FAIL;
			}
		}

		// Save the response information
		if ( SaveSidResponseData ( pRxMsg,
		                           pstReqMsg,
		                           pNumResponses ) != PASS )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Cannot save SID response data\n" );
			return FAIL;
		}
	}

	// Ignore, invalid message

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  ProcessISO15765Msg
**
**  Purpose:   Process response based on connection to
**             ISO15765.
**
**  Returns:   RETRY, ERRORS, FAIL, or PASS
**
*******************************************************************************/
STATUS ProcessISO15765Msg ( REQ_MSG       *pstReqMsg,
                            PASSTHRU_MSG  *pRxMsg,
                            unsigned long *pStartTimestampMsecs,
                            BYTE          *pNumResponses,
                            BYTE          *pNumFirstFrames,
                            unsigned long *pTxTimestampMsecs,
                            unsigned long *pExtendResponseTimeMsecs )
{
	unsigned long   ResponseTimeMsecs;
	unsigned long   EcuTimingIdx;
	BYTE            EcuIdx;
	unsigned long   EcuId = 0;
	unsigned short  IDIndex;
	STATUS          eRetCode = PASS;    // saves the return code from function calls
	BOOL            bRequestMsgLpbk = FALSE;


	// create the ECU ID for later use
	for ( IDIndex = 0;
	      IDIndex < 4;
	      IDIndex++ )
	{
		EcuId = (EcuId << 8) + (pRxMsg->Data[IDIndex]);
	}

	// if this is an echo of the request, don't track it's timing data
	if ( pRxMsg->RxStatus & TX_MSG_TYPE )
	{
		bRequestMsgLpbk = TRUE;
	}
	else
	{
		// Find this ECU's Timing Structure
		for ( EcuTimingIdx = 0;
		      EcuTimingIdx < gNumOfECUsForAlloc;
		      EcuTimingIdx++ )
		{
			if ( gstEcuTimingData[EcuTimingIdx].EcuId == EcuId ||
			     gstEcuTimingData[EcuTimingIdx].EcuId == 0x00 )
			{
				if ( gstEcuTimingData[EcuTimingIdx].EcuId == 0x00 )
				{
					gstEcuTimingData[EcuTimingIdx].EcuId = EcuId;
				}
				break;  // leave ECU Timing for loop
			}
		}
	}

	// FAIL for padding error only once
	if ( bPadErrorPermanent == FALSE )
	{
		if ( pRxMsg->RxStatus & ISO15765_PADDING_ERROR )
		{
			bPadErrorPermanent = TRUE;

			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  ISO15765 message padding error.\n",
			      EcuId );
			eRetCode = ERRORS;
		}
	}

	ResponseTimeMsecs = (pRxMsg->TimestampMsecs - *pTxTimestampMsecs) / 1000;

	// Check for FirstFrame indication
	if ( pRxMsg->RxStatus & ISO15765_FIRST_FRAME )
	{
		// Check if response was late (all first frame indications due in P2_MAX)
		if ( (pRxMsg->TimestampMsecs - *pTxTimestampMsecs) > (gMaxResponseTimeMsecs * 1000) )
		{
			// Exceeded maximum response time
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  OBD First Frame Indication was later than allowed (> %dmsec)\n",
			      EcuId,
			      gMaxResponseTimeMsecs );
			eRetCode = ERRORS;

			if ( !bRequestMsgLpbk )
			{
				gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
				gstEcuTimingData[EcuTimingIdx].RespTimeTooLate++;

				// Tally up the response statistics
				gstEcuTimingData[EcuTimingIdx].AggregateResponseTimeMsecs += ResponseTimeMsecs;
				gstEcuTimingData[EcuTimingIdx].AggregateResponses += ( ( ( pRxMsg->DataSize - 4 ) / ISO15765_MAX_BYTES_PER_FRAME ) + 1 );
				if ( ResponseTimeMsecs > gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs )
				{
					gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs = ResponseTimeMsecs;
				}
			}
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Calculated OBD Response time: %2.4f msec\n",
			      ( (float)(pRxMsg->TimestampMsecs - *pTxTimestampMsecs) / 1000) );
		}

		// Extend the response time to the worst case for segmented responses
		gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs = 30000;
		if ( gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs > *pExtendResponseTimeMsecs )
		{
			*pExtendResponseTimeMsecs = gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs;
		}
		(*pNumFirstFrames)++;
		gstEcuTimingData[EcuTimingIdx].bFFReceived = TRUE;
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Receiving segmented responses, please wait...\n" );
	}

	// Check for echoed request message
	else if ( pRxMsg->RxStatus & TX_MSG_TYPE )
	{
		if ( pRxMsg->DataSize >= 5 &&
		     pRxMsg->Data[4] == pstReqMsg->SID )
		{
			// Save the timestamp
			*pTxTimestampMsecs = pRxMsg->TimestampMsecs;
		}
	}

	// Check for NAK response message
	else if ( pRxMsg->DataSize >= 7    &&
	          pRxMsg->Data[4]  == NAK  &&
	          pRxMsg->Data[5]  == pstReqMsg->SID )
	{
		if ( gstEcuTimingData[EcuTimingIdx].bNAKReceived == FALSE )
		{
			// Tally up the response statistics
			gstEcuTimingData[EcuTimingIdx].AggregateResponseTimeMsecs += ResponseTimeMsecs;
			gstEcuTimingData[EcuTimingIdx].AggregateResponses += ( ( ( pRxMsg->DataSize - 4 ) / ISO15765_MAX_BYTES_PER_FRAME ) + 1 );
			if ( ResponseTimeMsecs > gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs )
			{
				gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs = ResponseTimeMsecs;
			}

			gstEcuTimingData[EcuTimingIdx].bNAKReceived = TRUE;
		}

		// Check if response was late (all negative respones due in P2_MAX)
		if ( (pRxMsg->TimestampMsecs - *pTxTimestampMsecs) > ((gMaxResponseTimeMsecs +  gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs) * 1000) )
		{
			// Exceeded maximum response time
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  OBD Negative Response was later than allowed (> %dmsec)\n",
			      EcuId,
			      gMaxResponseTimeMsecs + gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs );
			eRetCode = ERRORS;

			gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
			gstEcuTimingData[EcuTimingIdx].RespTimeTooLate++;
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Calculated OBD Response time: %2.4f msec\n",
			      ((float)(pRxMsg->TimestampMsecs - *pTxTimestampMsecs) / 1000) );
		}

		// Save the response information
		if ( SaveSidResponseData ( pRxMsg,
		                           pstReqMsg,
		                           pNumResponses ) != PASS )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Cannot save SID response data\n" );
			return FAIL;
		}

		// check the kind of response received for the vehicle
		switch( pRxMsg->Data[6] )
		{
			case NAK_GENERAL_REJECT: // 0x10
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  General reject to SID $%02X.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;

			case NAK_NOT_SUPPORTED: // 0x11
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $%02X not supported.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;

			case NAK_INVALID_FORMAT: // 0x12
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $%02X supported. Unsupported PID request.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;

			case NAK_REPEAT_REQUEST: // 0x21
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $%02X supported. Busy.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;

			case NAK_SEQUENCE_ERROR: // 0x22
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $%02X supported. Conditions not correct.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;

			case NAK_RESPONSE_PENDING: // 0x78
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $%02X supported. Response pending.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;

			default:
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  Unknown negative response to SID $%02X.\n",
				      EcuId,
				      pRxMsg->Data[5] );
			}
			break;
		}

		// NAK Truth table
		switch( pstReqMsg->SID )
		{
			case 0x01:
			{
				if ( gbDetermineProtocol == TRUE &&
				     pstReqMsg->ID[0] == 0x00 &&
				     pRxMsg->Data[6] == NAK_REPEAT_REQUEST )
				{
					return RETRY;
				}
				else
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $01 NAK response error!\n",
					      EcuId );
					return FAIL;
				}
			}
			break;

			case 0x04:
			{
				if ( pRxMsg->Data[6] == NAK_RESPONSE_PENDING )
				{
					// If response pending, extend the wait time
					gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs = 30000;
					if ( gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs > *pExtendResponseTimeMsecs )
					{
						*pExtendResponseTimeMsecs = gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs;
					}
					gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs = 5000;
					if ( gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs > ResponsePendingDelayTimeMsecs )
					{
						ResponsePendingDelayTimeMsecs = gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs;
					}

					// set wait flag
					if ( LookupEcuIndex ( pRxMsg, &EcuIdx ) == PASS )
					{
						EcuWaitFlags |= (1<<EcuIdx);
						gstResponse[EcuIdx].bResponseReceived = FALSE; // allow ECU to respond again
					}
					else
					{
						return FAIL;
					}
				}
				else if ( pRxMsg->Data[6] != NAK_SEQUENCE_ERROR )
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $04 NAK response error!\n",
					      EcuId );
					return FAIL;
				}
			}
			break;

			case 0x08:
			{
				if ( pRxMsg->Data[6] == NAK_NOT_SUPPORTED )
				{
					break;
				}
				else
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  SID $08 NAK response error!\n",
					      EcuId,
					      pstReqMsg->SID );
				}
			}
			break;

			case 0x09:
			{
				// IF EOBD/IOBD/OBDBr and CVN
				if ( pstReqMsg->ID[0] == 0x06 &&
				     ( gstUserInput.eComplianceType != US_OBDII &&
				       gstUserInput.eComplianceType != HD_OBD ) )
				{
					break;
				}
			}
			break;

			default :
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  SID $%02X NAK response error!\n",
				      EcuId,
				      pstReqMsg->SID );
				return FAIL;
			}
				break;
		}
	}

	// Check for response message
	else if ( pRxMsg->DataSize >= 5 )
	{
		if ( gstEcuTimingData[EcuTimingIdx].bNAKReceived == FALSE &&
		     gstEcuTimingData[EcuTimingIdx].bFFReceived == FALSE )
		{
			// Tally up the response statistics
			gstEcuTimingData[EcuTimingIdx].AggregateResponseTimeMsecs += ResponseTimeMsecs;
			gstEcuTimingData[EcuTimingIdx].AggregateResponses += ( ( ( pRxMsg->DataSize - 4 ) / ISO15765_MAX_BYTES_PER_FRAME ) + 1 );
			if ( ResponseTimeMsecs > gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs )
			{
				gstEcuTimingData[EcuTimingIdx].LongestResponsesTimeMsecs = ResponseTimeMsecs;
			}
		}
		else
		{
			gstEcuTimingData[EcuTimingIdx].bNAKReceived = FALSE;
		}

		// Check if response was late (compensate for segmented responses)
		if ( (pRxMsg->TimestampMsecs - *pTxTimestampMsecs) >
		     ((gMaxResponseTimeMsecs + gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs) * 1000) )
		{
			// Exceeded maximum response time
			Log ( ERROR_FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  OBD Response was later than allowed (> %dmsec)\n",
			      EcuId,
			      (gMaxResponseTimeMsecs + gstEcuTimingData[EcuTimingIdx].ResponseTimeExtensionMsecs) );
			eRetCode = ERRORS;

			gstEcuTimingData[EcuTimingIdx].RespTimeOutofRange++;
			gstEcuTimingData[EcuTimingIdx].RespTimeTooLate++;
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Calculated OBD Response time: %2.4f msec\n",
			      ((float)(pRxMsg->TimestampMsecs - *pTxTimestampMsecs) / 1000) );
		}

		// clear wait flag
		if ( LookupEcuIndex ( pRxMsg, &EcuIdx ) == PASS )
		{
			EcuWaitFlags &= ~(1<<EcuIdx);
			gstEcuTimingData[EcuTimingIdx].ResponsePendingDelayTimeMsecs = 0;        // ECU response no longer pending
		}
		else
		{
			return FAIL;
		}

		// Check if all ECUs with response pending have responded
		if ( EcuWaitFlags == 0 )
		{
			ResponsePendingDelayTimeMsecs = 0;         // no ECU response is pending

			if ( *pNumFirstFrames == 0 )
			{
				*pExtendResponseTimeMsecs = 0;  // no First Frames, clear extended response time
			}
		}

		// Check if we can turn off the time extension for segmented frames
		if ( pRxMsg->DataSize > (4 + ISO15765_MAX_BYTES_PER_FRAME) )
		{
			if ( *pNumFirstFrames > 0 )
			{
				// If we have received as many segmented frames as FirstFrame indications,
				// restore 'response pending' time extension
				if ( --(*pNumFirstFrames) == 0 )
				{
					*pExtendResponseTimeMsecs = ResponsePendingDelayTimeMsecs;
				}
			}
		}

		// Check for proper SID response
		if ( pRxMsg->Data[4] != ( pstReqMsg->SID + OBD_RESPONSE_BIT ) )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  Invalid SID response to request\n",
			      EcuId );
			return FAIL;
		}

		// If a PID was requested
		if ( pstReqMsg->NumIds != 0 )
		{
			// Check for proper PID/MID/TID/INF response
			for ( IDIndex = 0;
			      IDIndex < pstReqMsg->NumIds;
			      IDIndex++ )
			{
				if ( pRxMsg->Data[5] == pstReqMsg->ID[IDIndex] )
				{
					break;  // leave ID for loop
				}
				// if this is a SID $02 request, skip frame #
				if ( pstReqMsg->SID == 2 )
				{
					IDIndex++;
				}
			}

			if ( IDIndex == pstReqMsg->NumIds )
			{
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  Invalid PID response to request\n",
				      EcuId );
				return FAIL;
			}
		}

		// Save the response information
		if ( SaveSidResponseData ( pRxMsg,
		                           pstReqMsg,
		                           pNumResponses ) != PASS )
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Cannot save SID response data\n" );
			return FAIL;
		}
	}

	// Ignore, invalid message

	return eRetCode;
}


/*******************************************************************************
**
**  Function:  SetupRequestMsg
**
**  Purpose:   Setup request message based on the protocol.
**             Routine will normalize the message data handling between
**             the regulated OBD protocol message structures.
**
*******************************************************************************/
STATUS SetupRequestMsg ( REQ_MSG      *pstReqMsg,
                         PASSTHRU_MSG *pTxMsg )
{
	// Set message data information common to all protocols
	pTxMsg->eProtocolID  = gstProtocolList[gProtocolIdx].eProtocol;
	pTxMsg->RxStatus     = TX_MSG_TYPE;
	pTxMsg->TxFlags      = 0x00;  //Change back to non-blocking

	// Configure protocol specific message data.
	switch ( gstProtocolList[gProtocolIdx].eProtocol )
	{
		case J1850VPW:
		case ISO9141:
		{
			pTxMsg->Data[0]  = 0x68;
			pTxMsg->Data[1]  = 0x6A;
			pTxMsg->Data[2]  = TESTER_NODE_ADDRESS;
			pTxMsg->Data[3]  = pstReqMsg->SID;

			memcpy ( &pTxMsg->Data[4],
			         &pstReqMsg->ID[0],
			          pstReqMsg->NumIds );
			pTxMsg->DataSize = 4 + pstReqMsg->NumIds;
		}
		break;

		case J1850PWM:
		{
			pTxMsg->Data[0]  = 0x61;
			pTxMsg->Data[1]  = 0x6A;
			pTxMsg->Data[2]  = TESTER_NODE_ADDRESS;
			pTxMsg->Data[3]  = pstReqMsg->SID;

			memcpy ( &pTxMsg->Data[4],
			         &pstReqMsg->ID[0],
			          pstReqMsg->NumIds );
			pTxMsg->DataSize = 4 + pstReqMsg->NumIds;
		}
		break;

		case ISO14230:
		{
			pTxMsg->Data[0]  = 0xC0 + pstReqMsg->NumIds + 1;
			pTxMsg->Data[1]  = 0x33;
			pTxMsg->Data[2]  = TESTER_NODE_ADDRESS;
			pTxMsg->Data[3]  = pstReqMsg->SID;

			memcpy ( &pTxMsg->Data[4],
			         &pstReqMsg->ID[0],
			          pstReqMsg->NumIds );
			pTxMsg->DataSize = 4 + pstReqMsg->NumIds;
		}
		break;

		case ISO15765:
		{
			pTxMsg->TxFlags |= ISO15765_FRAME_PAD;

			if ( gstProtocolList[gProtocolIdx].eInitFlags & CAN_29BIT_ID )
			{
				pTxMsg->TxFlags |= CAN_29BIT_ID;

				pTxMsg->Data[0]  = 0x18;
				pTxMsg->Data[1]  = 0xDB;
				pTxMsg->Data[2]  = 0x33;
				pTxMsg->Data[3]  = TESTER_NODE_ADDRESS;
			}
			else
			{
				pTxMsg->Data[0]  = 0x00;
				pTxMsg->Data[1]  = 0x00;
				pTxMsg->Data[2]  = 0x07;
				pTxMsg->Data[3]  = 0xDF;
			}
			pTxMsg->Data[4]  = pstReqMsg->SID;

			memcpy ( &pTxMsg->Data[5],
			         &pstReqMsg->ID[0],
			          pstReqMsg->NumIds );
			pTxMsg->DataSize = 5 + pstReqMsg->NumIds;
		}
		break;

		default:
		{
			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "Invalid protocol specified in SetupRequestMsg\n" );
			return FAIL;
		}
	}

	return PASS;
}
