/*******************************************************************************
********************************************************************************
**
**  Copyright(c) 2022, Alliance for Automotive Innovation
**  Used only under license from the Alliance for Automotive Innovation. All Rights Reserved.
**
**  Project:  J1699-5
**  FileName: SaveSIDResponseData.c
**  Author:   EnGenius
**  Date:     2/25/2022
**  Email:    <support@autosinnovate.org>
**
**  Purpose:  SAE J1699-5 Vehicle OBD II Compliance Test Cases Source Code.
**            This source code is intended to run the tests described in
**            the SAE J1699-5 document in an automated manner, when compiled
**            and used with an SAE J2534-compatible pass-thru device.
**
**            File j1699.c contains information on building and running this test.
**
**  Description:
**
**  Modifications:  03/13/2023  Initial Version
**
********************************************************************************
*******************************************************************************/

#include <stdio.h>    // C Library input and output declarations
#include <stdlib.h>   // C Library general function declarations
#include <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


// Function Prototypes
void   ChkIFRAdjust    ( PASSTHRU_MSG  *pRxMsg );   // Logic ajustment for PID Group Reverse order request.
STATUS IsMessageUnique ( BYTE          *pBuffer,    // pointer to buffer
                         unsigned short BufSize,    // size of buffer
                         BYTE          *pMsg,       // pointer to message to be checked
                         unsigned short MsgSize );  // size of message
STATUS LookupEcuIndex  ( PASSTHRU_MSG  *pRxMsg,
                         BYTE          *pEcuIndex );

extern STATUS VerifyEcuId ( BYTE EcuId[] );


extern unsigned int  gVariablePIDSize;


/*******************************************************************************
**
**  Function:  SaveSIDResponseData
**
**  Purpose:   Save SID response data
**
*******************************************************************************/
STATUS SaveSIDResponseData ( PASSTHRU_MSG  *pRxMsg,
                             REQ_MSG       *pstReqMsg,
                             BYTE          *pNumResponses )
{
	BYTE          HeaderSize;
	BYTE          Data0Offset;
	BYTE          Data1Offset;
	BYTE          Data2Offset;
	BYTE          EcuIdx;
	BYTE          ElementOffset;
	unsigned long Index;  // coordinate mode 6 data respones for multiple data response.
	unsigned int  ExpectedDataSize = 0;
	BOOL          bCheckDataSize = TRUE;


	// Set the response header size based on the protocol
	HeaderSize = gstProtocolList[gProtocolIdx].HeaderSize;
	Data0Offset = HeaderSize + 1;
	Data1Offset = HeaderSize + 2;
	Data2Offset = HeaderSize + 3;

	// Get index into gstResponse struct
	if ( LookupEcuIndex ( pRxMsg, &EcuIdx ) != PASS )
	{
		return FAIL;
	}

	if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
	{
		ChkIFRAdjust ( pRxMsg );
	}

	if ( gstResponse[EcuIdx].bResponseReceived == TRUE )
	{
		Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "ECU %X  has already responded\n",
		      GetEcuId ( EcuIdx ) );
	}
	else
	{
		(*pNumResponses)++;  // count the new response
	}

	// Save the data in the appropriate SID/PID/MID/TID/INFoType
	switch ( pRxMsg->Data[HeaderSize] )
	{
		// OBD Services
		// Reserved Services
		case 0x40:
		case 0x4B:
		case 0x4C:
		case 0x4D:
		case 0x4E:
		case 0x4F:
		{
			// indicate that this ECU has responded
			gstResponse[EcuIdx].bResponseReceived = TRUE;

			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  Response to Reserved Service SID $%02X.\n",
			      GetEcuId (EcuIdx),
			      pRxMsg->Data[HeaderSize] - 0x40 );
			return PASS;
		}
		break;

		// Deprecated Services
		case 0x41:
		case 0x42:
		case 0x43:
		case 0x44:
		case 0x45:
		case 0x46:
		case 0x47:
		case 0x48:
		case 0x49:
		case 0x4A:
		{
			// indicate that this ECU has responded
			gstResponse[EcuIdx].bResponseReceived = TRUE;

			Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "ECU %X  Response to J1979 Classic Service SID $%02X.\n",
			      GetEcuId (EcuIdx),
			      pRxMsg->Data[HeaderSize] - 0x40 );
			return PASS;
		}
		break;
		
		// SID $14  Clear DTCs
		case 0x54:
		{
			// indicate that this ECU has responded
			gstResponse[EcuIdx].bResponseReceived = TRUE;

			// Indicate positive response
			gstResponse[EcuIdx].ClrDTC[0] = 0x54;
			gstResponse[EcuIdx].ClrDTCSize = 1;
		}
		break;

		// SID $19  Read DTC INFormation
		case 0x59:
		{
			// Process message based on subfunction (LEV)
			switch ( pRxMsg->Data[Data0Offset] )
			{
				// Freeze Frame (Snapshot) Identification
				case 0x03:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					// Determine if there is enough room in the buffer to store the data
					if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].FF ) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 LEV $03 response data exceeded buffer size\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// check for Freeze Frame data
					if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 5) )
					{
						// if no Freeze Frames, set to zero
						memset ( gstResponse[EcuIdx].FF,
						         0,
						         4 );
						gstResponse[EcuIdx].FFSize = 4;
					}
					else
					{
						// Otherwise copy the Freeze Frame data
						memcpy ( gstResponse[EcuIdx].FF,
						         &pRxMsg->Data[Data1Offset],
						         (pRxMsg->DataSize - HeaderSize - 2) );
						gstResponse[EcuIdx].FFSize = (unsigned short)(pRxMsg->DataSize - HeaderSize - 2);
					}
				}  // end case 0x03 freeze frame identification
				break;

				// Freeze Frame (Snapshot) Data
				case 0x04:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

				}
				break;

				// ReportDTCExtDataRecordByDTCNumber ($06) or reportMirrorMemoryDTCExtDataRecordByDTCNumber ($10)
				case 0x06:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					switch ( pRxMsg->Data[HeaderSize + 6] )
					{
						// IF this is IUMPR Data
						case 0x91:
						{
							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - (HeaderSize + 3)) > sizeof ( DTCIUMPR ) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $19 LEV $06 ExtData $91 (IUMPR) response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}
	
							// check for DTC Extended Data
							if ( pRxMsg->DataSize <= (unsigned long)(Data1Offset + 8) )
							{
								// check if there is enough data for DTC
								if ( pRxMsg->DataSize >= (unsigned long)(Data1Offset + 3) )
								{
									gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte = pRxMsg->Data[Data1Offset];
									gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.MidByte  = pRxMsg->Data[Data1Offset + 1];
									gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.LowByte  = pRxMsg->Data[Data1Offset + 2];
									// if no extended data, set to zero
									memset ( &gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Status,
									         0,
									         5 );
								}
								else
								{
									// if not enough data, set to zero
									memset ( &gstResponse[EcuIdx].DTCIUMPRCurrent,
									         0,
									         sizeof ( DTCIUMPR ) );
								}
							}
							else
							{
								// otherwise, copy the DTC IUMPR data
								gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.HighByte = pRxMsg->Data[Data1Offset];
								gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.MidByte  = pRxMsg->Data[Data1Offset + 1];
								gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Dtc.LowByte  = pRxMsg->Data[Data1Offset + 2];
								gstResponse[EcuIdx].DTCIUMPRCurrent.Record.Status       = pRxMsg->Data[Data1Offset + 3];
								// skip the record number pRxMsg->Data[Data1Offset + 4]
								gstResponse[EcuIdx].DTCIUMPRCurrent.CompCounts = (pRxMsg->Data[Data1Offset + 5] << 8) + pRxMsg->Data[Data1Offset + 6];
								gstResponse[EcuIdx].DTCIUMPRCurrent.CondCounts = (pRxMsg->Data[Data1Offset + 7] << 8) + pRxMsg->Data[Data1Offset + 8];
							}
						}
						break;

						// IF this is Test Result Data
						case 0x92:
						{
							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - (HeaderSize + 3)) > sizeof ( DTCTestResult ) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $19 LEV $06 ExtData $92 (TestResult) response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// check for DTC Extended Data
							if ( pRxMsg->DataSize <= (unsigned long)(Data1Offset + 6) )
							{
								// check if there is enough data for DTC
								if ( pRxMsg->DataSize >= (unsigned long)(Data1Offset + 3) )
								{
									gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte = pRxMsg->Data[Data1Offset];
									gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.MidByte  = pRxMsg->Data[Data1Offset + 1];
									gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.LowByte  = pRxMsg->Data[Data1Offset + 2];
									// if no extended data, set to zero
									memset ( &gstResponse[EcuIdx].DTCTestResultCurrent.Record.Status,
									         0,
									         9 );
								}
								else
								{
									// if not enough data, set to zero
									memset ( &gstResponse[EcuIdx].DTCTestResultCurrent,
									         0,
									         sizeof ( DTCTestResult ) );
								}
							}
							else
							{
								// otherwise, copy the DTC Test Result data
								gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.HighByte = pRxMsg->Data[Data1Offset];
								gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.MidByte  = pRxMsg->Data[Data1Offset + 1];
								gstResponse[EcuIdx].DTCTestResultCurrent.Record.Dtc.LowByte  = pRxMsg->Data[Data1Offset + 2];
								gstResponse[EcuIdx].DTCTestResultCurrent.Record.Status       = pRxMsg->Data[Data1Offset + 3];
								// skip the record number, pRxMsg->Data[Data1Offset + 4]
								gstResponse[EcuIdx].DTCTestResultCurrent.NumTstRslts  = pRxMsg->Data[Data1Offset + 5];
								int i;
								int j;
								for ( i = 0, j = Data1Offset + 6;
								      i < gstResponse[EcuIdx].DTCTestResultCurrent.NumTstRslts;
								      i++, j += 8 )
								{
									gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].UnitScaling = (pRxMsg->Data[j] << 8) +
									                                                                  pRxMsg->Data[j + 1];
									gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].TestValue   = (pRxMsg->Data[j + 2] << 8) +
									                                                                  pRxMsg->Data[j + 3];
									gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].MinTstLmt   = (pRxMsg->Data[j + 4] << 8) +
									                                                                  pRxMsg->Data[j + 5];
									gstResponse[EcuIdx].DTCTestResultCurrent.Result[i].MaxTstLmt   = (pRxMsg->Data[j + 6] << 8) +
									                                                                  pRxMsg->Data[j + 7];
								}
							}
						}
						break;

						// IF this is SMAD Data
						case 0x93:
						{
							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - (HeaderSize + 3)) > sizeof ( DTCSMAD ) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $19 LEV $06 ExtData $93 (SMAD) response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}
	
							// check for DTC Extended Data
							if ( pRxMsg->DataSize <= (unsigned long)(Data1Offset + 6) )
							{
								// check if there is enough data for DTC
								if ( pRxMsg->DataSize >= (unsigned long)(Data1Offset + 3) )
								{
									gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte = pRxMsg->Data[Data1Offset];
									gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.MidByte  = pRxMsg->Data[Data1Offset + 1];
									gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.LowByte  = pRxMsg->Data[Data1Offset + 2];
									// if no extended data, set to zero
									memset ( &gstResponse[EcuIdx].DTCSMADCurrent.Record.Status,
									         0,
									         4 );
								}
								else
								{
									// if not enough data, set to zero
									memset ( &gstResponse[EcuIdx].DTCSMADCurrent,
									         0,
									         sizeof ( DTCSMAD ) );
								}
							}
							else
							{
								// otherwise, copy the DTC SMAD data
								gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.HighByte = pRxMsg->Data[Data1Offset];
								gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.MidByte  = pRxMsg->Data[Data1Offset + 1];
								gstResponse[EcuIdx].DTCSMADCurrent.Record.Dtc.LowByte  = pRxMsg->Data[Data1Offset + 2];
								gstResponse[EcuIdx].DTCSMADCurrent.Record.Status       = pRxMsg->Data[Data1Offset + 3];
								// skip the record number pRxMsg->Data[Data1Offset + 4]
								gstResponse[EcuIdx].DTCSMADCurrent.NumCounts   = pRxMsg->Data[Data1Offset + 5];
								gstResponse[EcuIdx].DTCSMADCurrent.DenomCounts = pRxMsg->Data[Data1Offset + 6];
								gstResponse[EcuIdx].DTCSMADCurrent.ActRatio    = pRxMsg->Data[Data1Offset + 7];
							}
						}
						break;

						default:
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  SID $19 LEV $06 unrecognized response.\n",
							      GetEcuId ( EcuIdx ) );
							return FAIL;
						}
						break;
					}  // end switch ( pRxMsg->Data[HeaderSize + 6] )
				}  //end case 0x06 ReportDTCExtDataRecordByDTCNumber
				break;

				// ReportDTCExtDataIdentification (DTCs supporting ExtData 0x91, 0x92 or 0x93)
				case 0x1A:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					// Determine if there is enough room in the buffer to store the data
					if ( (pRxMsg->DataSize - (HeaderSize + 4)) > (MAX_DTCIUMPR_COUNT * sizeof ( DTCASTR )) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 LEV $1A response data exceeded buffer size\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					switch ( pRxMsg->Data[HeaderSize + 3] )
					{
						// IF this is a IUMPR List
						case 0x91:
						{
							// Save the DTCStatusAvailabiltyMask
							gstResponse[EcuIdx].DTCIUMPRStatus = pRxMsg->Data[HeaderSize + 2];

							// DTCExtendedDTCDataRecordNumber
							// pRxMsg->Data[HeaderSize + 3];

							// check for DTC data
							if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 4) )
							{
								// If no DTCs, set to zero
								memset ( &gstResponse[EcuIdx].DTCIUMPRList[0],
								         0,
								         sizeof ( gstResponse[EcuIdx].DTCIUMPRList ) );

								gstResponse[EcuIdx].bDTCIUMPRSupported = FALSE;
								gstResponse[EcuIdx].DTCIUMPRCount      = 0;
							}
							// if there is DTC data,
							else
							{
								WORD  DTCIndex;
								WORD  RXIndex;

								for ( DTCIndex = 0, RXIndex = HeaderSize + 4;
								      RXIndex < pRxMsg->DataSize && DTCIndex < MAX_DTCIUMPR_COUNT;
								      DTCIndex++, RXIndex += sizeof ( DTCASTR ) )
								{
									// copy the DTC data
									memcpy ( &gstResponse[EcuIdx].DTCIUMPRList[DTCIndex].Record.Dtc,
									         &pRxMsg->Data[RXIndex],
									         sizeof ( DTCASTR ) );

									gstResponse[EcuIdx].DTCIUMPRList[DTCIndex].CompCounts = 0;
									gstResponse[EcuIdx].DTCIUMPRList[DTCIndex].CondCounts = 0;
								}

								if ( RXIndex < pRxMsg->DataSize && DTCIndex == MAX_DTCIUMPR_COUNT )
								{
									Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
									      "ECU %X  SID $19 LEV $1A response data contains more than %d DTCs (only the first %d DTCs were saved)\n",
									      GetEcuId ( EcuIdx ),
									      MAX_DTCIUMPR_COUNT,
									      MAX_DTCIUMPR_COUNT );
								}

								gstResponse[EcuIdx].DTCIUMPRCount = DTCIndex;

								gstResponse[EcuIdx].bDTCIUMPRSupported = TRUE;
							}
						}  // end IF this is a IUMPR List
						break;

						// IF this is a Test Result List
						case 0x92:
						{
							// Save the DTCStatusAvailabiltyMask
							gstResponse[EcuIdx].DTCTestResultStatus = pRxMsg->Data[HeaderSize + 2];

							// DTCExtendedDTCDataRecordNumber
							// pRxMsg->Data[HeaderSize + 3];

							// check for DTC data
							if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 4) )
							{
								// If no DTCs, set to zero
								memset ( &gstResponse[EcuIdx].DTCIUMPRList[0],
								         0,
								         sizeof ( gstResponse[EcuIdx].DTCIUMPRList ) );

								gstResponse[EcuIdx].bDTCTestResultSupported = FALSE;
								gstResponse[EcuIdx].DTCTestResultCount      = 0;
							}
							// if there is DTC data,
							else
							{
								WORD  DTCIndex;
								WORD  RXIndex;

								for ( DTCIndex = 0, RXIndex = HeaderSize + 4;
								      RXIndex < pRxMsg->DataSize && DTCIndex < MAX_DTCIUMPR_COUNT;
								      DTCIndex++, RXIndex += sizeof ( DTCASTR ) )
								{
									// copy the DTC data
									memcpy ( &gstResponse[EcuIdx].DTCTestResultList[DTCIndex].Record.Dtc,
									         &pRxMsg->Data[RXIndex],
									         sizeof ( DTCASTR ) );

									gstResponse[EcuIdx].DTCTestResultList[DTCIndex].NumTstRslts = 0;

									// reset the Test Result data
									memset ( &gstResponse[EcuIdx].DTCTestResultList[DTCIndex].Result[0],
									         0,
									         MAX_DTCTESTRESULT_NUM * sizeof ( TestResult ) );
								}

								if ( RXIndex < pRxMsg->DataSize && DTCIndex == MAX_DTCIUMPR_COUNT )
								{
									Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
									      "ECU %X  SID $19 LEV $1A response data contains more than %d DTCs (only the first %d DTCs were saved)\n",
									      GetEcuId ( EcuIdx ),
									      MAX_DTCIUMPR_COUNT,
									      MAX_DTCIUMPR_COUNT );
								}

								gstResponse[EcuIdx].DTCTestResultCount = DTCIndex;

								gstResponse[EcuIdx].bDTCTestResultSupported = TRUE;
							}
						}  // end IF this is a Test Result List
						break;

						// IF this is a SMAD List
						case 0x93:
						{
							// Save the DTCStatusAvailabiltyMask
							gstResponse[EcuIdx].DTCSMADStatus = pRxMsg->Data[HeaderSize + 2];

							// DTCExtendedDTCDataRecordNumber
							// pRxMsg->Data[HeaderSize + 3];

							// check for DTC data
							if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 4) )
							{
								// If no DTCs, set to zero
								memset ( &gstResponse[EcuIdx].DTCSMADList[0],
								         0,
								         sizeof ( gstResponse[EcuIdx].DTCSMADList ) );

								gstResponse[EcuIdx].bDTCSMADSupported = FALSE;
								gstResponse[EcuIdx].DTCSMADCount      = 0;
							}
							// if there is DTC data,
							else
							{
								WORD  DTCIndex;
								WORD  RXIndex;

								for ( DTCIndex = 0, RXIndex = HeaderSize + 4;
								      RXIndex < pRxMsg->DataSize && DTCIndex < MAX_DTCSMAD_COUNT;
								      DTCIndex++, RXIndex += sizeof ( DTCASTR ) )
								{
									// copy the DTC data
									memcpy ( &gstResponse[EcuIdx].DTCSMADList[DTCIndex].Record.Dtc,
									         &pRxMsg->Data[RXIndex],
									         sizeof ( DTCASTR ) );

									gstResponse[EcuIdx].DTCSMADList[DTCIndex].NumCounts   = 0;
									gstResponse[EcuIdx].DTCSMADList[DTCIndex].DenomCounts = 0;
									gstResponse[EcuIdx].DTCSMADList[DTCIndex].ActRatio    = 0;
								}

								if ( RXIndex < pRxMsg->DataSize && DTCIndex == MAX_DTCSMAD_COUNT )
								{
									Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
									      "ECU %X  SID $19 LEV $1A response data contains more than %d DTCs (only the first %d DTCs were saved)\n",
									      GetEcuId ( EcuIdx ),
									      MAX_DTCSMAD_COUNT,
									      MAX_DTCSMAD_COUNT );
								}

								gstResponse[EcuIdx].DTCSMADCount = DTCIndex;

								gstResponse[EcuIdx].bDTCSMADSupported = TRUE;
							}
						}  // end IF this is a SMAD List
						break;

						default:
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  SID $19 LEV $1A unrecognized response.\n",
							      GetEcuId ( EcuIdx ) );
							return FAIL;
						}
						break;
					}  // end switch ( pRxMsg->Data[HeaderSize + 3] )
				}  //end case 0x1A ReportDTCExtDataIdentification (DTCs supporting ExtData 0x91)
				break;

				// Confirmed or Pending DTCs
				case 0x42:
				{
					BOOL          bPendingDTC = FALSE;
					BOOL          bConfirmedDTC = FALSE;
					DTC_HEADER   *pDTCHeader = NULL;
					DTCASVR      *pDTCASVR = NULL;

					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					// Determine if this is a Confirmed or Pending DTC response
					if ( (pstReqMsg->u.ID[2] & 0x08) != 0 )
					{
						bConfirmedDTC = TRUE;
						pDTCHeader = &gstResponse[EcuIdx].ConfDTCHeader;
						pDTCASVR = (DTCASVR*)&gstResponse[EcuIdx].ConfDTC[0];
					}
					else if ( (pstReqMsg->u.ID[2] & 0x04) != 0 )
					{
						bPendingDTC = TRUE;
						pDTCHeader = &gstResponse[EcuIdx].PendDTCHeader;
						pDTCASVR = (DTCASVR*)&gstResponse[EcuIdx].PendDTC[0];
					}
					pDTCHeader->DTCSupported = TRUE;

					// Determine if there is enough room in the buffer to store the data
					if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].PendDTC ) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 LEV $42 response data exceeded buffer size\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// Save the DTCStatusAvailabiltyMask
					pDTCHeader->DTCStatus = pRxMsg->Data[HeaderSize + 3];

					// Save the DTCSeverityMask
					pDTCHeader->DTCSeverity = pRxMsg->Data[HeaderSize + 4];

					// Save the DTCFormatIdentifier
					pDTCHeader->DTCFormat = pRxMsg->Data[HeaderSize + 5];

					// check for DTC data
					if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 6) )
					{
						memset ( pDTCASVR,
						         0,
						         5 );
						pDTCHeader->DTCSize = 5;
					}
					else
					{
						// Otherwise copy the DTC data
						memcpy ( pDTCASVR,
						         &pRxMsg->Data[HeaderSize + 6],
						         (pRxMsg->DataSize - HeaderSize - 6) );
						pDTCHeader->DTCSize = (unsigned short)(pRxMsg->DataSize - HeaderSize - 6);
					}
				}  //end case 0x42 Pending or Confirmed DTCs
				break;

				// Permanent DTCs
				case 0x55:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					gstResponse[EcuIdx].bPermDTCSupported = TRUE;

					// Determine if there is enough room in the buffer to store the data
					if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].PermDTC ) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 LEV $55 response data exceeded buffer size\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// Save the DTCStatusAvailabiltyMask
					gstResponse[EcuIdx].PermDTCStatus = pRxMsg->Data[HeaderSize + 3];

					// Save the DTCFormatIdentifier
					gstResponse[EcuIdx].PermDTCFormat = pRxMsg->Data[HeaderSize + 4];

					// check for DTC data
					if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 4) )
					{
						// If no DTCs, set to zero
						memset ( &gstResponse[EcuIdx].PermDTC[0],
						         0,
						         4 );
						gstResponse[EcuIdx].PermDTCSize = 4;
					}
					else
					{
						// Otherwise copy the DTC data
						memcpy ( &gstResponse[EcuIdx].PermDTC[0],
						         &pRxMsg->Data[HeaderSize + 5],
						         (pRxMsg->DataSize - HeaderSize - 5) );
						gstResponse[EcuIdx].PermDTCSize = (unsigned short)(pRxMsg->DataSize - HeaderSize - 4);
					}
				}  //end case 0x55 Permanent DTCs
				break;

				// ReportDTCInformationByDTCReadinessGroupIdentifier
				case 0x56:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					// Determine if there is enough room in the buffer to store the data
					if ( (pRxMsg->DataSize - HeaderSize - 4) > (MAX_DTCIMR_COUNT * sizeof ( DTCASTR )) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $19 LEV $56 (IM Readiness) response data exceeded buffer size\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// check for DTC IM Readiness data
					if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 4) )
					{
						// If no DTCs, set to zero
						memset ( &gstResponse[EcuIdx].DTCIMReadinessCurrent,
						         0,
						         sizeof ( gstResponse[EcuIdx].DTCIMReadinessCurrent ) );

						gstResponse[EcuIdx].DTCIMReadinessCurrentCount = 0;
						gstResponse[EcuIdx].bDTCIMReadinessSupported = FALSE;
						gstResponse[EcuIdx].DTCIMReadinessCount      = 0;
					}
					// if there is DTC IM Readiness Data,
					else
					{
						WORD  DTCIndex;
						WORD  RXIndex;

						for ( DTCIndex = 0, RXIndex = HeaderSize + 6;
						      RXIndex < pRxMsg->DataSize && gstResponse[EcuIdx].DTCIMReadinessCount < MAX_DTCIMR_COUNT;
						      DTCIndex++,   RXIndex += sizeof ( DTCASTR ) )
						{
							// copy the DTC data
							memcpy ( &gstResponse[EcuIdx].DTCIMReadinessCurrent[DTCIndex].Record.Dtc,
							         &pRxMsg->Data[RXIndex],
							         sizeof ( DTCASTR ) );

							// Save the FunctionalGroupIdentifier
							// pRxMsg->Data[HeaderSize + 2];

							// Save the DTCStatusAvailabiltyMask
							// pRxMsg->Data[HeaderSize + 3];

							// Save the DTCFormatIdentifier
							// pRxMsg->Data[HeaderSize + 4];

							// Save the DTCReadinessGroupIdentifier  
							gstResponse[EcuIdx].DTCIMReadinessCurrent[DTCIndex].RGID = pRxMsg->Data[HeaderSize + 5];
	
							gstResponse[EcuIdx].DTCIMReadinessCount++;
						}

						if ( RXIndex < pRxMsg->DataSize && DTCIndex == MAX_DTCIMR_COUNT )
						{
							Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  SID $19 LEV $56 (IM Readiness) responses contains more than %d DTCs (only the first %d DTCs were saved)\n",
							      GetEcuId ( EcuIdx ),
							      MAX_DTCIMR_COUNT,
							      MAX_DTCIMR_COUNT );
						}

						gstResponse[EcuIdx].DTCIMReadinessCurrentCount = DTCIndex;

						gstResponse[EcuIdx].bDTCIMReadinessSupported = TRUE;
					}
				}  //end case 0x56 ReportDTCInformationByDTCReadinessGroupIdentifier
				break;
			}  // end switch ( pRxMsg->Data[Data0Offset] )
		}
		break;

		// SID $22
		case 0x62:
		{
			// indicate that this ECU has responded
			gstResponse[EcuIdx].bResponseReceived = TRUE;

			// Process message based on DID number MSB
			switch ( pRxMsg->Data[Data0Offset] )
			{
				// PID
				case 0xF4:
				{
					// Process message based on PID number (DID LSB)
					switch ( pRxMsg->Data[Data1Offset] )
					{
						case 0x00:
						{
							if ( gbVerifyLink == TRUE ||
							     gbDetermineProtocol == TRUE )
							{
								gstResponse[EcuIdx].bLinkActive = TRUE;

								if ( gstResponse[EcuIdx].PIDSupportSize != 0 )
								{
									break;
								}
							}
						}
						// 0x00 falls through

						case 0x20:
						case 0x40:
						case 0x60:
						case 0x80:
						case 0xA0:
						case 0xC0:
						case 0xE0:
						{
							// Make sure there is enough data in the message to process
							if ( pRxMsg->DataSize < (Data0Offset + sizeof ( ID_SUPPORT )) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in PID $%02X%02X support response message to process\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							for ( Index = Data1Offset;
							      Index < pRxMsg->DataSize;
							      Index += (sizeof ( ID_SUPPORT ) + 1) )
							{
								// find the element number (for the array of structures)
								ElementOffset = (BYTE)(pRxMsg->Data[Index]) >> 5;

								// if this ID is $F5x0 (from a group PID support PID response which started with a $F4x0 PID)
								if ( pRxMsg->Data[Index-1] == 0xF5 )
								{
									// add the offset to the $F5x0 segment of the PIDSupport array
									ElementOffset += 8;
								}

								// move the data into the element
								memcpy ( &gstResponse[EcuIdx].PIDSupport[ElementOffset],
								         &pRxMsg->Data[Index],
								         sizeof ( ID_SUPPORT ) );

								// increase the element count if the element in the array was previously unused
								if ( (ElementOffset + 1) > gstResponse[EcuIdx].PIDSupportSize )
								{
									gstResponse[EcuIdx].PIDSupportSize = (ElementOffset + 1);
								}
							}
						}
						//  00-E0 FALL THROUGH AND TREAT LIKE OTHER PIDS

						default:
						{
							// If not PID 0, then check if this is a response to an unsupported PID
							if ( gbIgnoreUnsupported == FALSE &&
							     pRxMsg->Data[Data1Offset] != 0x00 &&
							     (gstResponse[EcuIdx].PIDSupport[(pRxMsg->Data[Data1Offset] - 1) >> 5].SupBits[((pRxMsg->Data[Data1Offset] - 1) >> 3) & 0x03]
							       & (0x80 >> ((pRxMsg->Data[Data1Offset] - 1) & 0x07))) == 0 )
							{
								Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Unsupported PID $%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
							}

							// Make sure there is enough data in the message to process
							if ( pRxMsg->DataSize <= (Data1Offset) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in PID $%02X%02X response message to process\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
								return FAIL;
							}

							// If only one PID requested, make sure the
							// PID has the correct amount of data
							if ( pstReqMsg->NumIds == 1 )
							{
								bCheckDataSize = TRUE;

								switch ( pRxMsg->Data[Data1Offset] )
								{
									// Single byte PIDs
									case 0x04:
									case 0x05:
									case 0x0A:
									case 0x0B:
									case 0x0D:
									case 0x0E:
									case 0x0F:
									case 0x11:
									case 0x12:
									case 0x13:
									case 0x1C:
									case 0x1D:
									case 0x1E:
									case 0x2C:
									case 0x2D:
									case 0x2E:
									case 0x2F:
									case 0x30:
									case 0x33:
									case 0x45:
									case 0x46:
									case 0x47:
									case 0x48:
									case 0x49:
									case 0x4A:
									case 0x4B:
									case 0x4C:
									case 0x51:
									case 0x52:
									case 0x5A:
									case 0x5B:
									case 0x5C:
									case 0x5F:
									case 0x61:
									case 0x62:
									case 0x7D:
									case 0x7E:
									case 0x84:
									case 0x8D:
									case 0x8E:
									case 0xAA:
									case 0xC8:
									case 0xD8:
									{
										ExpectedDataSize = 1;
									}
									break;

									// Two byte PIDs
									case 0x02:
									case 0x03:
									case 0x0C:
									case 0x10:
									case 0x14:
									case 0x15:
									case 0x16:
									case 0x17:
									case 0x18:
									case 0x19:
									case 0x1A:
									case 0x1B:
									case 0x1F:
									case 0x21:
									case 0x22:
									case 0x23:
									case 0x31:
									case 0x32:
									case 0x3C:
									case 0x3D:
									case 0x3E:
									case 0x3F:
									case 0x42:
									case 0x43:
									case 0x44:
									case 0x4D:
									case 0x4E:
									case 0x53:
									case 0x54:
									case 0x59:
									case 0x5D:
									case 0x5E:
									case 0x63:
									case 0x65:
									case 0x92:
									case 0x9E:
									case 0xA2:
									case 0xAF:
									case 0xC7:
									case 0xD6:
									{
										ExpectedDataSize = 2;
									}
									break;

									// Three byte PIDs
									case 0x67:
									case 0x6F:
									case 0x90:
									case 0x93:
									case 0xC3:
									case 0xD2:
									{
										ExpectedDataSize = 3;
									}
									break;

									// Four byte PIDs
									case 0x00:
									case 0x20:
									case 0x40:
									case 0x60:
									case 0x80:
									case 0xA0:
									case 0xC0:
									case 0xE0:
									case 0x01:
									case 0x24:
									case 0x25:
									case 0x26:
									case 0x27:
									case 0x28:
									case 0x29:
									case 0x2A:
									case 0x2B:
									case 0x34:
									case 0x35:
									case 0x36:
									case 0x37:
									case 0x38:
									case 0x39:
									case 0x3A:
									case 0x3B:
									case 0x41:
									case 0x4F:
									case 0x50:
									case 0x9B:
									case 0x9D:
									case 0xA4:
									case 0xA5:
									case 0xA6:
									case 0xAC:
									case 0xD1:
									case 0xD3:
									case 0xD4:
									case 0xD9:
									case 0xDA:
									case 0xDB:
									{
										ExpectedDataSize = 4;

										// IF PID $01, save appropriate size data to allow the other ECU's to be saved and evaluated
										if ( (pRxMsg->DataSize - (HeaderSize + 3)) != 4 &&
										     pRxMsg->Data[Data1Offset] == 0x01 )
										{
											if ( (pRxMsg->DataSize - (HeaderSize + 3)) < 4 )
											{
												memset ( &pRxMsg->Data[pRxMsg->DataSize],
												         0x00,
												         4 - (pRxMsg->DataSize - (HeaderSize + 3)) );
											}
											pRxMsg->DataSize = 4 + (Data1Offset);
										}
									}
									break;

									// Five byte PIDs
									case 0x64:
									case 0x66:
									case 0x6A:
									case 0x6B:
									case 0x6C:
									case 0x72:
									case 0x73:
									case 0x74:
									case 0x77:
									case 0x86:
									case 0x87:
									case 0x91:
									case 0xAD:
									case 0xAE:
									case 0xB0:
									case 0xC5:
									case 0xCB:
									case 0xD7:
									{
										ExpectedDataSize = 5;
									}
									break;

									// Six byte PIDs
									case 0x71:
									case 0x9A:
									case 0xB1:
									{
										ExpectedDataSize = 6;
									}
									break;

									// Seven byte PIDs
									case 0x68:
									case 0x69:
									case 0x75:
									case 0x76:
									case 0x7A:
									case 0x7B:
									case 0x8B:
									case 0x8F:
									//case 0xCA:
									case 0xD0:
									{
										ExpectedDataSize = 7;
									}
									break;

									// Eight byte PIDs
									case 0xC4:
									case 0xC6:
									{
										ExpectedDataSize = 8;
									}
									break;

									// Nine byte PIDs
									case 0x6E:
									case 0x78:
									case 0x79:
									case 0x7C:
									case 0x83:
									case 0x98:
									case 0x99:
									case 0x9F:
									case 0xA1:
									case 0xA3:
									case 0xA7:
									case 0xA8:
									case 0xCE:
									case 0xCF:
									{
										ExpectedDataSize = 9;
									}
									break;

									// Ten byte PIDs
									case 0x70:
									case 0x85:
									{
										ExpectedDataSize = 10;
									}
									break;

									// Eleven byte PIDs
									case 0x6D:
									case 0xD5:
									{
										ExpectedDataSize = 11;
									}
									break;

									// Twelve byte PIDs
									case 0x94:
									{
										ExpectedDataSize = 12;
									}
									break;

									// Thirteen byte PIDs
									case 0x7F:
									case 0x88:
									{
										ExpectedDataSize = 13;
									}
									break;

									// Seventeen byte PIDs
									case 0x8C:
									case 0x9C:
									case 0xC9:
									case 0xCC:
									case 0xCD:
									{
										ExpectedDataSize = 17;
									}
									break;

									// Nineteen byte PIDs
									case 0xC1:
									{
										ExpectedDataSize = 19;
									}
									break;

									// Twenty-Eight byte PIDs
									case 0xC2:
									{
										ExpectedDataSize = 28;
									}
									break;

									// Forty-One byte PIDs
									case 0x81:
									case 0x82:
									case 0x89:
									case 0x8A:
									case 0xDC:
									{
										ExpectedDataSize = 41;
									}
									break;

									// 1 or 2 byte PIDs
									case 0x06:      // PID $06 1 / 2-Byte definition
									case 0x07:      // PID $07 1 / 2-Byte definition
									case 0x08:      // PID $08 1 / 2-Byte definition
									case 0x09:      // PID $09 1 / 2-Byte definition
									case 0x55:      // PID $55 1 / 2-Byte definition
									case 0x56:      // PID $56 1 / 2-Byte definition
									case 0x57:      // PID $57 1 / 2-Byte definition
									case 0x58:      // PID $58 1 / 2-Byte definition
									{
										// Only check if successfully determined the (variable) PID size
										if ( gVariablePIDSize > 0 )
										{
											ExpectedDataSize = gVariablePIDSize;
										}
										else
										{
											bCheckDataSize = FALSE;
										}
									}
									break;

									default:
									{
										// Non-OBD PID
										bCheckDataSize = FALSE;
									}
									break;
								}  // end switch ( pRxMsg->Data[Data1Offset] )

								// Check for correct number of data bytes
								if ( bCheckDataSize == TRUE &&
								    (pRxMsg->DataSize - (HeaderSize + 3)) != ExpectedDataSize )
								{
									Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
									      "ECU %X  PID $%02X%02X  has %d data bytes (should be %d)\n",
									      GetEcuId ( EcuIdx ),
									      pRxMsg->Data[Data0Offset],
									      pRxMsg->Data[Data1Offset],
									      (pRxMsg->DataSize - (HeaderSize + 3)),
									      ExpectedDataSize );
									return FAIL;
								}
							}  // end if ( pstReqMsg->NumIds == 1 )

							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].PID ) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $22 response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							memcpy ( &gstResponse[EcuIdx].PID[0],
							         &pRxMsg->Data[Data0Offset],
							         (pRxMsg->DataSize - HeaderSize - 1) );
							gstResponse[EcuIdx].PIDSize = (unsigned short)(pRxMsg->DataSize - HeaderSize - 1);
						}
						break;
					}  // end case 0xF4 switch (pRxMsg->Data[Data1Offset])
				}  // end case 0xF4
				break;

				// PID
				case 0xF5:
				{
					// Process message based on PID number (DID LSB)
					switch ( pRxMsg->Data[Data1Offset] )
					{
						case 0x00:
						case 0x20:
						case 0x40:
						case 0x60:
						case 0x80:
						case 0xA0:
						case 0xC0:
						case 0xE0:
						{
							// Make sure there is enough data in the message to process
							if ( pRxMsg->DataSize < (Data0Offset + sizeof ( ID_SUPPORT )) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in PID $%02X%02X support response message to process\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							for ( Index = Data1Offset;
							      Index < pRxMsg->DataSize;
							      Index += (sizeof ( ID_SUPPORT ) + 1) )
							{
								// find the element number (for the array of structures)
								ElementOffset = ((unsigned char)(pRxMsg->Data[Index]) >> 5);

								// move the data into the element
								memcpy ( &gstResponse[EcuIdx].PF5Support[ElementOffset],
								         &pRxMsg->Data[Index],
								         sizeof ( ID_SUPPORT ) );

								// increase the element count if the element in the array was previously unused
								if ( (ElementOffset + 1) > gstResponse[EcuIdx].PF5SupportSize )
								{
									gstResponse[EcuIdx].PF5SupportSize = (ElementOffset + 1);
								}
							}
						}
						//  00-E0 FALL THROUGH AND TREAT LIKE OTHER PIDS

						default:
						{
							// If not PID 0, then check if this is a response to an unsupported PID
							if ( gbIgnoreUnsupported == FALSE &&
							     pRxMsg->Data[Data1Offset] != 0x00 &&
							     (gstResponse[EcuIdx].PF5Support[(pRxMsg->Data[Data1Offset] - 1) >> 5].SupBits[((pRxMsg->Data[Data1Offset] - 1) >> 3) & 0x03]
							      & (0x80 >> ((pRxMsg->Data[Data1Offset] - 1) & 0x07))) == 0 )
							{
								Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Unsupported PID $%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
							}

							// Make sure there is enough data in the message to process
							if ( pRxMsg->DataSize <= (unsigned long)(Data1Offset) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in PID $%02X%02X response message to process\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
								return FAIL;
							}

							// If only one PID requested,
							// make sure the PID has the correct amount of data
							if ( pstReqMsg->NumIds == 1 )
							{
								bCheckDataSize = TRUE;

								switch ( pRxMsg->Data[Data1Offset] )
								{
									// Four byte PIDs
									case 0x00:
									case 0x20:
									case 0x40:
									case 0x60:
									case 0x80:
									case 0xA0:
									case 0xC0:
									case 0xE0:
									{
										ExpectedDataSize = 4;
									}
									break;

									// Six byte PIDs
									case 0x01:
									{
										ExpectedDataSize = 6;
									}
									break;

									// Seventeen byte PIDs
									case 0x02:
									case 0x03:
									case 0x04:
									case 0x05:
									{
										ExpectedDataSize = 17;
									}
									break;

									default:
									{
										// Non-OBD PID
										bCheckDataSize = FALSE;
									}
									break;
								}  // end switch ( pRxMsg->Data[Data1Offset] )

								// Check for correct number of data bytes
								if ( bCheckDataSize == TRUE &&
								     (pRxMsg->DataSize - (HeaderSize + 3)) != ExpectedDataSize )
								{
									Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
									      "ECU %X  PID $%02X%02X  has %d data bytes (should be %d)\n",
									      GetEcuId ( EcuIdx ),
									      pRxMsg->Data[Data0Offset],
									      pRxMsg->Data[Data1Offset],
									      (pRxMsg->DataSize - (HeaderSize + 3)),
									      ExpectedDataSize );
									return FAIL;
								}
							}  // end if ( stReqMsg->NumIds == 1 )

							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].PID ) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $22 response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							memcpy ( &gstResponse[EcuIdx].PID[0],
							         &pRxMsg->Data[Data0Offset],
							         (pRxMsg->DataSize - HeaderSize - 1) );
							gstResponse[EcuIdx].PIDSize = (unsigned short)(pRxMsg->DataSize - HeaderSize - 1);
						}
						break;
					}  // end case 0xF4 switch ( pRxMsg->Data[Data1Offset] )
				}  // end case 0xF4
				break;

				// MID
				case 0xF6:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					// Process message based on MID number
					switch ( pRxMsg->Data[Data1Offset] )
					{
						case 0x00:
						case 0x20:
						case 0x40:
						case 0x60:
						case 0x80:
						case 0xA0:
						case 0xC0:
						case 0xE0:
						{
							// Make sure there is enough data in the message to process
							if ( pRxMsg->DataSize < (Data1Offset + sizeof ( ID_SUPPORT )) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in SID $22 support response message to process\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							for ( Index = Data1Offset;
							      Index < pRxMsg->DataSize;
							      Index += (sizeof ( ID_SUPPORT ) + 1) )
							{
								// find the element number (for the array of structures)
								ElementOffset = (unsigned char)(pRxMsg->Data[Index]) >> 5;

								// move the data into the element
								memcpy ( &gstResponse[EcuIdx].MIDSupport[ElementOffset],
								         &pRxMsg->Data[Index],
								         sizeof ( ID_SUPPORT ) );

								// increase the element count if the element in the array was previously unused
								if ( (ElementOffset + 1) > gstResponse[EcuIdx].MIDSupportSize )
								{
									gstResponse[EcuIdx].MIDSupportSize = (ElementOffset + 1);
								}
							}
						}
						// 0x00-0xE0 FALL THROUGH AND TREAT LIKE OTHER PIDS

						default:
						{
							// Check if this is a response to an unsupported MID
							if ( IsIDSupported ( EcuIdx, MIDREQUEST, ((pRxMsg->Data[Data0Offset]<<8) + pRxMsg->Data[Data1Offset]) ) == FALSE )
							{
								Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Unsupported SID $22 MID $%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset] );
							}

							// Make sure there is enough data in the message to process
							if ( pRxMsg->DataSize <= (unsigned long)(Data1Offset) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in SID $22 response message to process\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].MID ) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $22 response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							memcpy ( &gstResponse[EcuIdx].MID[0],
							         &pRxMsg->Data[Data0Offset],
							         (pRxMsg->DataSize - HeaderSize - 1));
							gstResponse[EcuIdx].MIDSize = (unsigned short)(pRxMsg->DataSize - HeaderSize - 1);
						}  // end default
						break;
					}  // end case 0xF6 switch ( pRxMsg->Data[Data1Offset] )
				}  // end case 0xF6
				break;

				// INF
				case 0xF8:
				{
					// indicate that this ECU has responded
					gstResponse[EcuIdx].bResponseReceived = TRUE;

					// Process message based on PID number
					switch ( pRxMsg->Data[Data1Offset] )
					{
						case 0x00:
						case 0x20:
						case 0x40:
						case 0x60:
						case 0x80:
						case 0xA0:
						case 0xC0:
						case 0xE0:
						{
							Index = Data1Offset;

							// Make sure there is enough data in the message to process
							// Must contain 4-byte address, SID, INF MSB, INF LSB (ID), and 4 bytes of data
							if ( pRxMsg->DataSize < (Index + sizeof ( ID_SUPPORT )) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in SID $22 support response message to process.\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// Save the data in the buffer and set the new size
							for ( ;
							      Index < pRxMsg->DataSize;
							      Index += (sizeof ( ID_SUPPORT ) + 1) )
							{
								// find the element number (for the array of structures)
								ElementOffset = (unsigned char)(pRxMsg->Data[Index]) >> 5;

								// move the data into the element
								memcpy ( &gstResponse[EcuIdx].INFSupport[ElementOffset],
								         &pRxMsg->Data[Index],
								         sizeof ( ID_SUPPORT ) );

								// increase the element count if the element in the array was previously unused
								if ( (ElementOffset + 1) > gstResponse[EcuIdx].INFSupportSize )
								{
									gstResponse[EcuIdx].INFSupportSize = (ElementOffset + 1);
								}
							}
						}
						// 0x00-0xE0 FALL THROUGH AND TREAT LIKE OTHER PIDS

						default:
						{
							// if this is a response to $F810 when Verifying Link or Determining Protocol,
							// mark this ECU as active
							if ( pRxMsg->Data[Data1Offset] == 0x10 &&
							     (gbVerifyLink == TRUE ||
							      gbDetermineProtocol == TRUE) )
							{
								gstResponse[EcuIdx].bLinkActive = TRUE;
							}
							
							// Check if this is a response to an unsupported INF
							if ( !gbIgnoreUnsupported &&
							     !((gbDetermineProtocol == TRUE || gbVerifyLink == TRUE) && pRxMsg->Data[Data1Offset] == 0x10) &&
							     IsIDSupported ( EcuIdx, INFREQUEST, ((pRxMsg->Data[Data0Offset]<<8) + pRxMsg->Data[Data1Offset]) ) == FALSE )
							{
								Log ( WARNING, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Unsupported INF $%02X%02X detected\n",
								      GetEcuId ( EcuIdx ),
								      pRxMsg->Data[Data0Offset],
								      pRxMsg->Data[Data1Offset]);
							}

							// Make sure there is enough data in the message to process
							// Must contain at least 4-byte address, SID, INF MSB and INF LSB
							if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 3) )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  Not enough data in SID $22 response message to process\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							// Save the data in the buffer and set the new size

							// Determine if there is enough room in the buffer to store the data
							if ( (pRxMsg->DataSize - HeaderSize) > sizeof ( gstResponse[EcuIdx].INF )  )
							{
								Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
								      "ECU %X  SID $22 response data exceeded buffer size\n",
								      GetEcuId ( EcuIdx ) );
								return FAIL;
							}

							memcpy ( &gstResponse[EcuIdx].INF[0],
							         &pRxMsg->Data[Data0Offset],
							         (pRxMsg->DataSize - (Data0Offset)) );
							gstResponse[EcuIdx].INFSize = (unsigned short)(pRxMsg->DataSize - (Data0Offset));
						}  // end default
						break;
					}
				}  // end case 0xF8
				break;

			}  // end case 0x62 (0x22 response) switch ( pRxMsg->Data[Data0Offset] )
		}  // end case 0x62 (0x22 response)
		break;

		case 0x71: // Routine Control
		{
			// indicate that this ECU has responded
			gstResponse[EcuIdx].bResponseReceived = TRUE;

			// Process message based on Control Type (RCTP)
			switch ( pRxMsg->Data[Data0Offset] )
			{
				// Start Routine
				case 0x01:
				{
					// Determine if there is enough room in the buffer to store the data
					if ( (pRxMsg->DataSize - HeaderSize - 1) > sizeof ( gstResponse[EcuIdx].RIDSupport ) )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $31 response data exceeded buffer size\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// check for routine info
					if ( pRxMsg->DataSize <= (unsigned long)(HeaderSize + 5) )
					{
						// if no routine info, set to zero
						memset ( gstResponse[EcuIdx].RIDSupport,
						         0,
						         7 );
						gstResponse[EcuIdx].RIDSupportSize = 0;

						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "ECU %X  SID $31 insufficient response data\n",
						      GetEcuId ( EcuIdx ) );
						return FAIL;
					}

					// Process message based on RID number MSB
					switch ( pRxMsg->Data[Data1Offset] )
					{
						// RID
						case 0xE0:
						{
							// Process message based on RID number LSB
							switch ( pRxMsg->Data[Data2Offset] )
							{
								case 0x00:
								case 0x20:
								case 0x40:
								case 0x60:
								case 0x80:
								case 0xA0:
								case 0xC0:
								case 0xE0:
								{
									// Make sure there is enough data in the message to process
									if ( pRxMsg->DataSize < (Data0Offset + sizeof ( gstResponse[EcuIdx].RIDSupport )) )
									{
										Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
										      "ECU %X  Not enough data in RID $%02X%02X support response message to process\n",
										      GetEcuId ( EcuIdx ),
										      pRxMsg->Data[Data0Offset],
										      pRxMsg->Data[Data1Offset] );
										return FAIL;
									}

									// move the data into the element
									memcpy ( &gstResponse[EcuIdx].RIDSupport[0],
									         &pRxMsg->Data[0],
									         sizeof ( gstResponse[EcuIdx].RIDSupport ) );
								}
								//  00-E0 FALL THROUGH AND TREAT LIKE OTHER RIDS
							}  // end switch ( pRxMsg->Data[Data2Offset] ) RID number LSB
						}  //end case 0xE0
						break;

						default:
						{
							Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
							      "ECU %X  Unable to save data.  SID $31 response data contains unsupported RID $%02X%02X\n",
							      GetEcuId ( EcuIdx ),
							      pRxMsg->Data[Data1Offset],
							      pRxMsg->Data[Data2Offset] );
							return FAIL;
						}
						break;
					}  // end switch ( pRxMsg->Data[Data1Offset] ) RID number MSB
				}  // end case 0x01  Start Routine
				break;

				default:
				{
					Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
					      "ECU %X  Unable to save data.  SID $31 response data contains unsupported Routine Control Type (RCTP) $%02X\n",
					      GetEcuId ( EcuIdx ),
					      pRxMsg->Data[Data0Offset] );
				}
				break;
			}  // end switch ( pRxMsg->Data[Data0Offset] ) Routine Control Type
		}
		break;

		default:
		{
			// indicate that this ECU has responded
			gstResponse[EcuIdx].bResponseReceived = TRUE;

			// Check for a negative response code
			if ( pRxMsg->Data[HeaderSize] == NAK )
			{
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  Received negative response code\n",
				      GetEcuId ( EcuIdx ) );
				if ( pRxMsg->Data[Data1Offset] == NAK_RESPONSE_PENDING )
				{
					(*pNumResponses)--;  // don't count the 0x78 response
				}
				else if ( pRxMsg->Data[Data1Offset] == NAK_ID_NOT_SUPPORTED )
				{
					// if clear codes nak'd
					if ( pRxMsg->Data[Data0Offset] == 0x14 )
					{
						// indicate that this ECU has responded
						gstResponse[EcuIdx].bResponseReceived = TRUE;

						// save response
						gstResponse[EcuIdx].ClrDTC[0] = pRxMsg->Data[Data0Offset];
						gstResponse[EcuIdx].ClrDTC[1] = pRxMsg->Data[Data1Offset];
						gstResponse[EcuIdx].ClrDTCSize = 2;
					}
					// if request data by id nak'd
					else if ( pRxMsg->Data[Data0Offset] == 0x22 )
					{

					}
				}
			}
			else
			{
				// Unexpected SID response
				Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "ECU %X  Unexpected SID $%02X response\n",
				      GetEcuId ( EcuIdx ),
				      (pRxMsg->Data[HeaderSize] - OBD_RESPONSE_BIT) );
				return FAIL;
			}
		}

	}  // end switch ( pRxMsg->Data[HeaderSize] ) SID

	return PASS;
}


/*******************************************************************************
**
**  Function:  ChkIFRAdjust
**
**  Purpose:   Account for checksum or IFR in data size.
**
*******************************************************************************/
void ChkIFRAdjust ( PASSTHRU_MSG *pRxMsg )
{
	// If extra data was returned (e.g. checksum, IFR, ...), remove it
	if ( pRxMsg->ExtraDataIndex != 0 &&
	     pRxMsg->DataSize != pRxMsg->ExtraDataIndex )
	{
		// Adjust the data size to ignore the extra data
		pRxMsg->DataSize = pRxMsg->ExtraDataIndex;
	}
}


/*******************************************************************************
**
**  Function:  LookupEcuIndex
**
**  Purpose:   Return index into gstResponse struct for this message
**
*******************************************************************************/
STATUS LookupEcuIndex ( PASSTHRU_MSG *pRxMsg,
                        BYTE         *pEcuIdx )
{
	BYTE           HeaderSize;
	BYTE           EcuIdx;
	unsigned long  ByteIdx;
	unsigned long  EcuId = 0;

	BOOL           bTestFailed = FALSE;


	// Set the response header size based on the protocol
	HeaderSize = gstProtocolList[gProtocolIdx].HeaderSize;

	// get the Responding ECU's ID
	if ( gstProtocolList[gProtocolIdx].eProtocol == ISO15765 )
	{
		ByteIdx = 0;
	}
	else
	{
		HeaderSize -= 2;
		ByteIdx = 8;
	}
	for ( ;
	      ByteIdx < HeaderSize;
	      ByteIdx++ )
	{
		EcuId = (EcuId << 8) + (pRxMsg->Data[ByteIdx]);
	}

	// Find the appropriate EcuIdx based on the header information
	for ( EcuIdx = 0;
	      EcuIdx < gNumOfECUs;
	      EcuIdx++ )
	{
		// Check if we have a header match
		if ( EcuId != gstResponse[EcuIdx].RespId )
		{
			// If no match, check if EcuIdx is empty
			if ( gstResponse[EcuIdx].RespId == 0x00000000 )
			{
				// if not currently in the process of determining the protocol
				// then check every Rx msg's ID
				if ( gbProtocolDetermined == TRUE )
				{
					if ( VerifyEcuId ( &pRxMsg->Data[0] ) == FAIL )
					{
						Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
						      "Response from ECU %X not in initial list\n",
						      EcuId );
						bTestFailed = TRUE;
						break;  // leave ECU for loop
					}
				}

				// If empty, add the new response
				gstResponse[EcuIdx].RespId = EcuId;

				// calculate this ECU's ID for requests
				if ( gstProtocolList[gProtocolIdx].eProtocolTag == ISO15765_29_BIT_I )
				{
					// swap MSB and LSB of LSW
					gstResponse[EcuIdx].ReqId =  (gstResponse[EcuIdx].RespId & 0xFFFF0000) +
					                            ((gstResponse[EcuIdx].RespId & 0x0000FF00) >> 8) +
					                            ((gstResponse[EcuIdx].RespId & 0x000000FF) << 8);
				}
				else
				{
					// request ID is 8 less than response ID
					gstResponse[EcuIdx].ReqId = gstResponse[EcuIdx].RespId - 0x08;
				}

				break;  // leave ECU for loop
			}
		}
		else
		{
			// We have a header match
			break;  // leave ECU for loop
		}
	}

	*pEcuIdx = EcuIdx;

	if ( (gstUserInput.eEnergyType != ZEV &&
	      (EcuIdx >= OBD_LEGACY_MAX_ECUS &&
	       gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_29_BIT_I) ||
	      (EcuIdx >= MAX_ECUS &&
	       gstProtocolList[gProtocolIdx].eProtocolTag == ISO15765_29_BIT_I)) ||
	     (gstUserInput.eEnergyType == ZEV &&
	      (EcuIdx >= OBD_LEGACY_MAX_ECUS &&
	       gstProtocolList[gProtocolIdx].eProtocolTag == ISO15765_ZEV) ||
	      (EcuIdx >= MAX_ECUS &&
	       gstProtocolList[gProtocolIdx].eProtocolTag != ISO15765_ZEV)) )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Too many OBD ECU responses\n" );
		bTestFailed = TRUE;
	}


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

	return PASS;
}


/*******************************************************************************
**
**  Function:  IsMessageUnique
**
**  Purpose:   Check to see if this is a duplicate message
**
**  RETURNS   PASS - message is unique
**            FAIL - message is a duplicate
**
*******************************************************************************/
STATUS IsMessageUnique ( BYTE          *pBuffer,     // pointer to buffer
                         unsigned short BufSize,     // size of buffer
                         BYTE          *pMsg,        // pointer to message to be checked
                         unsigned short MsgSize )    // size of message
{
	unsigned short i;
	unsigned short x;
	BOOL bDone = FALSE;


	for ( i = 0, x = 0;
	      i < BufSize && x < MsgSize;
	      i += MsgSize )
	{
		x = 0;

		while ( pBuffer[i + x] == pMsg[x] &&
		        x < MsgSize &&
		        (i + x) < BufSize )
		{
			x++;
		}

		if ( x == MsgSize )
		{
			bDone = TRUE;
		}
	}

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

	return PASS;
}
