#define _WINSOCK_DEPRECATED_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <icmpapi.h>
#include <process.h>

#include "j2534.h"    // j1699 project j2534 declarations
#include "j1699.h"    // j1699 project general declarations
#include "Sockets.h"  // j1699 project Socket,UDP,TCP declarations
#include "DHCP.h"     // j1699 project DHCP declarations


t_stAddressData   gstTesterAddressData;
t_stAddressData   gstVehicleAddressData;


t_stSocketData  stDHCPSocketData;
t_stSocketData  stUDPSocketData;
t_stSocketData  stTCPSocketData;
t_stSocketData* apstSocketData[TEST_ADDRESSES];

boolean         gbSocketCheck = FALSE;

BYTE RcvdIPAddress[4];
BYTE RcvdIPAddressString[16];


unsigned short ausSocketErrors[NUM_SOCKET_ERRORS] =
{
	8,
	10004,
	10009,
	10013,
	10014,
	10022,
	10024,
	10035,
	10036,
	10037,
	10038,
	10039,
	10040,
	10041,
	10042,
	10043,
	10044,
	10045,
	10046,
	10047,
	10048,
	10049,
	10050,
	10051,
	10052,
	10053,
	10054,
	10055,
	10056,
	10057,
	10058,
	10059,
	10060,
	10061,
	10062,
	10063,
	10064,
	10065,
	10067,
	10091,
	10092,
	10093,
	10094,
	10101,
	10109,
	11001,
	11002,
	11003,
	11004,
};


char aszSocketErrorDescriptions[eMaxSocketErrorCodes + 1][MAX_ERR_DESC_LEN] =
{
	"SOCKET 8 - Not Enough Memory",                             // WSA_NOT_ENOUGH_MEMORY
	"SOCKET 10004 - Interrupted system call",                   // WSAEINTR
	"SOCKET 10009 - Bad file number",
	"SOCKET 10013 - Permission denied",                         // WSAEACCES
	"SOCKET 10014 - Bad address",                               // WSAEFAULT
	"SOCKET 10022 - Invalid argument",                          // WSAEINVAL
	"SOCKET 10024 - Too many open files",                       // WSAEMFILE
	"SOCKET 10035 - Operation would block",                     // WSAEWOULDBLOCK
	"SOCKET 10036 - Operation now in progress",                 // WSAEINPROGRESS
	"SOCKET 10037 - Operation already in progress",             // WSAEALREADY
	"SOCKET 10038 - Socket operation on nonsocket",             // WSAENOTSOCK
	"SOCKET 10039 - Destination address required",              // WSAEDESTADDRREQ
	"SOCKET 10040 - Message too long",                          // WSAEMSGSIZE
	"SOCKET 10041 - Protocol wrong type for socket",            // WSAEPROTOTYPE
	"SOCKET 10042 - Protocol not available",                    // WSAENOPROTOOPT
	"SOCKET 10043 - Protocol not supported",                    // WSAEPROTONOSUPPORT
	"SOCKET 10044 - Socket type not supported",                 // WSAESOCKTNOSUPPORT
	"SOCKET 10045 - Operation not supported on socket",         // WSAEOPNOTSUPP
	"SOCKET 10046 - Protocol family not supported",             // WSAEPFNOSUPPORT
	"SOCKET 10047 - Address family not supported by protocol",  // WSAEAFNOSUPPORT
	"SOCKET 10048 - Address already in use",                    // WSAEADDRINUSE
	"SOCKET 10049 - Cannot assign requested address",           // WSAEADDRNOTAVAIL
	"SOCKET 10050 - Network is down",                           // WSAENETDOWN
	"SOCKET 10051 - Network is unreachable",                    // WSAENETUNREACH
	"SOCKET 10052 - Network dropped connection on reset",       // WSAENETRESET
	"SOCKET 10053 - Software caused connection abort",          // WSAECONNABORTED
	"SOCKET 10054 - Connection reset by peer",                  // WSAECONNRESET
	"SOCKET 10055 - No buffer space available",                 // WSAENOBUFS
	"SOCKET 10056 - Socket is already connected",               // WSAEISCONN
	"SOCKET 10057 - Socket is not connected",                   // WSAENOTCONN
	"SOCKET 10058 - Cannot send after socket shutdown",         // WSAESHUTDOWN
	"SOCKET 10059 - Too many references: cannot splice",
	"SOCKET 10060 - Connection timed out",                      // WSAETIMEDOUT
	"SOCKET 10061 - Connection refused",                        // WSAECONNREFUSED
	"SOCKET 10062 - Too many levels of symbolic links",
	"SOCKET 10063 - File name too long",
	"SOCKET 10064 - Host is down",                              // WSAEHOSTDOWN
	"SOCKET 10065 - No route to host",                          // WSAEHOSTUNREACH
	"SOCKET 10067 - Too many processes",                        // WSAEPROCLIM
	"SOCKET 10091 - Network subsystem is unusable",             // WSASYSNOTREADY
	"SOCKET 10092 - Winsock DLL cannot support application",    // WSAVERNOTSUPPORTED
	"SOCKET 10093 - Winsock not initialized",                   // WSANOTINITIALISED
	"SOCKET 10094 - Graceful shutdown in progress",             // WSAEDISCON
	"SOCKET 10101 - Disconnect",
	"SOCKET 10109 - Disconnect",
	"SOCKET 11001 - Type not found",                            // WSATYPE_NOT_FOUND
	"SOCKET 11002 - Nonauthoritative host not found",           // WSATRY_AGAIN
	"SOCKET 11003 - Nonrecoverable error",                      // WSANO_RECOVERY
	"SOCKET 11004 - Key (name, address, and so on) not found",  // WSANO_DATA
	"SOCKET - Timeout",
	"SOCKET - Socket closed by another thread",
	"SOCKET - Peer has closed socket",
	"SOCKET - Failed lookup of Client IP address",
	"SOCKET - Failed Event creation",
	"SOCKET - Failed starting Socket Thread",
	"SOCKET - Failed waiting for event",
	"SOCKET - Failed with an Unexpected Message",
	"SOCKET - Output Queue Full",
	"SOCKET - Undefined Error"
};


/*******************************************************************************
**
**  Function:  ReadSocket
**
**  Purpose:   Read data from the socket
**
*******************************************************************************/
BYTE* ReadSocket ( t_stSocketData *pstSocketData,
                   int             iHeaderSize,
                   BYTE           *puStaticBuffer,
                   WORD           *piByteCount,
                   unsigned long   ulTimeout )
{
	TIMEVAL         stTimeVal;
	FD_SET          stReadFD;
	FD_SET          stExceptFD;
	int             iStatus;
	int             iBytesRead;
	int             iByteRequest;
	int             iMinimumBytes;
	BYTE           *pucCurrentBuffer = puStaticBuffer;
	unsigned long   ulOrigTimeout;
	boolean         bReadingMin = TRUE;
//	t_stDOIPHeader *pstDOIPHeader;
	int             iMessageByteCount;

	*piByteCount = 0;
	ulOrigTimeout = ulTimeout;

	// A global data message can be read in multiple pieces.
	// The start of the message is the header which includes
	// the total byte count for the message.
	// Once the total length of the message is known,
	// the balance of the message is then read into a buffer.

	// The socket is configured as a blocking socket.
	// For an input pipe the recv() function is where the thread will primarily block.
	// When the thread needs to be stopped the recv() fill be terminated by closing the socket.

	// For output pipes the read will collect acknowledgments of sent data and heartbeat
	// messages. The read operation will have a timeout.
	// The thread should not get stuck in the blocking recv() so a timed out select call will be used
	// to determine that there is data to be read before calling the recv() function.

	// This function uses a select call to allow for a timeout on read operations.
	// A timeout value of zero will give an infinite timeout by skipping the select
	// call and reading the blocked socket.

	// The message pipe is more constrained. It uses a fixed buffer sized to the largest
	// possible single message so there is never the need to allocated for an oversized
	// message. That buffer can be packed with more that one message but those messages
	// will be in the sockets buffer and can be efficently extracted one message at a 
	// time as the read has no timeout and does not need to issue a select call before 
	// each recv call.

	iByteRequest = iHeaderSize;

	// Set the count of bytes that mark the end of the minumum size message
	iMinimumBytes = iByteRequest;

	while ( *piByteCount < iByteRequest )
	{
		// If there is a timeout we must pass the select call first so
		// that we don't get stuck in the recv function call.

		// If there is no timeout, we will block in the recv function until
		// we get data or the socket is closed
		if ( ulTimeout != 0 )
		{
			stTimeVal.tv_sec  = ulTimeout / 1000;
			stTimeVal.tv_usec = (ulTimeout % 1000) * 1000;

			FD_ZERO( &stReadFD );
			FD_SET( pstSocketData->hSock, &stReadFD );

			FD_ZERO( &stExceptFD );
			FD_SET( pstSocketData->hSock, &stExceptFD );

			// Returns when there is data to be read or and exception or a timeout
			iStatus = select ( 1, &stReadFD, NULL, &stExceptFD, &stTimeVal );

			// If error
			if ( iStatus == SOCKET_ERROR )
			{
				pstSocketData->iError = WSAGetLastError ( );

				return NULL;
			}

			// Timeout
			if ( iStatus == 0 )
			{
				pstSocketData->iError = eSocketTimeout;

				return NULL;
			}
		}

		// Ask for the balance of the message.
		// For the first pass this is the size of the minimum message.
		// After we know the message length the remaining data of the message is loaded
		iBytesRead = recv ( pstSocketData->hSock, (char*)&pucCurrentBuffer[*piByteCount], iByteRequest - *piByteCount, 0 );

		if ( iBytesRead == SOCKET_ERROR )
		{
			pstSocketData->iError = WSAGetLastError ( );

			return NULL;
		}

		// Closing the socket in another thread can cause the recv function
		// to return a byte count of zero
		if ( iBytesRead == 0 )
		{
			pstSocketData->iError = eSocketConnectionClosing;

			return NULL;
		}

		*piByteCount += iBytesRead;
		// It is possible that this takes multiple reads before we have 
		// assembled the entire header
		if ( *piByteCount == iMinimumBytes &&
		     bReadingMin )
		{
			// The first eight bytes of any of the message types is a DoIP header
			// with an additional byte count
//			pstDOIPHeader = (t_stDOIPHeader*)pucCurrentBuffer;

//			iMessageByteCount = ntohl(pstDOIPHeader->ulLength) + sizeof ( t_stDOIPHeader );
			iMessageByteCount = sizeof ( pucCurrentBuffer );

			// The minimum message could be the complete message
			if ( iMessageByteCount == *piByteCount )
			{
				return pucCurrentBuffer;
			}
			else
			{
				bReadingMin = FALSE;

				// Get the count of the balance of the message
				iByteRequest = iMessageByteCount;

				// Global messages are processed using static buffer sized near the size of the IP packet.
				// There is nothing in the data definitions that precludes a larger data item from being
				// transfered on the pipe. For data messages that are larger than the default static
				// buffer, allocate a buffer. The downstream processing will recognize an allocated buffer
				// and free it on completion of message processing.
				if ( iMessageByteCount > MAX_MSG_LEN )
				{
					pucCurrentBuffer = calloc ( 1, iMessageByteCount );

					memcpy ( pucCurrentBuffer, puStaticBuffer, *piByteCount );
//					memcpy ( pucCurrentBuffer, &pRxMsg->Data[0], *piByteCount );
				}
			}  // end if ( iMessageByteCount != iByteCount )
		}  // end if ( iByteCount == iMinimumBytes && bReadingMin )
	}  // end while ( iByteCount < iByteRequest )

	return pucCurrentBuffer;
}




/*******************************************************************************
**
**  Function:  FindDirectConnectInterface
**
**  Purpose:   
**
*******************************************************************************/
STATUS FindDirectConnectInterface ( void )
{
	PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
	PIP_ADAPTER_INFO pAdapter;
	PIP_ADDR_STRING  pAddr;

	unsigned long  ulOutBufLen = sizeof ( IP_ADAPTER_INFO );

	unsigned char  i;
	unsigned char  String[1024];
	struct tm      Time;
	unsigned char  ListIdx = 0;
	unsigned char  AdapterIdx = 0;


	// clear the tester address structure
	memset ( &gstTesterAddressData, 0, sizeof ( gstTesterAddressData ) );

	// Allocate enough memory to Adapter Info to be able to call GetAdaptersInfo
	pAdapterInfo = (IP_ADAPTER_INFO*)malloc ( sizeof ( IP_ADAPTER_INFO ) );
	// Make an initial call to GetAdaptersInfo to get the size necessary for all Adapter Info
	if ( GetAdaptersInfo ( pAdapterInfo, &ulOutBufLen ) != ERROR_SUCCESS )
	{
		free ( pAdapterInfo );
		pAdapterInfo = (IP_ADAPTER_INFO*)malloc ( ulOutBufLen );

	}

	DWORD dwRetVal = GetAdaptersInfo ( pAdapterInfo, &ulOutBufLen );
	if ( dwRetVal != ERROR_SUCCESS )
	{
		Log ( FAILURE, SCREENOUTPUTON, LOGOUTPUTON, CUSTOM_PROMPT,
		      "\nGetAdaptersInfo call failed with %d.  Unable to gather adapter info.\n\n",
		         dwRetVal);
		return FAIL;
	}
	else
	{
		Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "\n\n" );

		Log ( PROMPT, SCREENOUTPUTON, LOGOUTPUTON, CUSTOM_PROMPT,
		      "(Q01) Select adapter from following list:\n\n" );

		pAdapter = pAdapterInfo;
		while ( pAdapter )
		{
			// Print adapter information to the logfile
			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "Adapter  %d:\n",
			      ++ListIdx );
			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "Adapter Description: %s\n",
			      pAdapter->Description );
			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "Adapter Name:        %s\n",
			      pAdapter->AdapterName );

			sprintf_s ( String, 22,
			            "Hardware Address:    " );
			for ( i = 0;
			      i < pAdapter->AddressLength;
			      i++ )
			{
				if ( i == 0 )
				{
					sprintf_s ( &String[strlen(String)], 4,
					            "%02X", pAdapter->Address[i] );
				}
				else
				{
					sprintf_s ( &String[strlen(String)], 4,
					            ":%02X", pAdapter->Address[i] );
				}
			}
			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "%s\n",
			      String );

			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "Adapter Index:       %d\n",
			      pAdapter->Index );

			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "Adapter Type:        %d\n",
			      pAdapter->Type );

			pAddr = &pAdapter->IpAddressList;
			while ( pAddr )
			{
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "IP Address:          %s\n",
				      pAddr->IpAddress.String );
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "IP Mask:             %s\n",
				      pAddr->IpMask.String );
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "IP Context:          %ld\n",
				      pAddr->Context );
				pAddr = pAddr->Next;
			}

			pAddr = &pAdapter->GatewayList;
			while ( pAddr )
			{
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "Gateway Address:     %s\n",
				      pAddr->IpAddress.String );
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "Gateway Mask:        %s\n",
				      pAddr->IpMask.String );
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "Gateway Context:     %ld\n",
				      pAddr->Context );
				pAddr = pAddr->Next;
			}

			if ( pAdapter->DhcpEnabled )
			{
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "DHCP Enabled:        Yes\n" );

				pAddr = &pAdapter->DhcpServer;
				while ( pAddr )
				{
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "DHCP Address:        %s\n",
					      pAddr->IpAddress.String );
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "DHCP Mask:           %s\n",
					      pAddr->IpMask.String );
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "DHCP Context:        %ld\n",
					      pAddr->Context );
					pAddr = pAddr->Next;
				}
				while ( pAddr );

				_localtime32_s ( &Time, (__time32_t*) &pAdapter->LeaseObtained );
				if ( Time.tm_sec != 0xFFFFFFFF )
				{
					asctime_s ( &String[0], 32, &Time );
				}
				else
				{
					sprintf_s ( &String[0], 100,
					           "ERROR\n" );
				}
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "DHCP Lease Obtained: %s",
				      String );

				_localtime32_s ( &Time, (__time32_t*) &pAdapter->LeaseExpires );
				if ( Time.tm_sec != 0xFFFFFFFF )
				{
					asctime_s ( &String[0], 32, &Time);
				}
				else
				{
					sprintf_s ( &String[0], 100,
					           "ERROR\n" );
				}
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "DHCP Lease Expires:  %s",
				         String );
			}
			else
			{
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "DHCP Enabled:        No\n" );
			}

			if ( pAdapter->HaveWins )
			{
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "WINS Enabled:        Yes\n" );

				pAddr = &pAdapter->PrimaryWinsServer;
				while ( pAddr )
				{
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "WINS Pri Address:     %s\n",
					         pAddr->IpAddress.String );
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "WINS Pri Mask:        %s\n",
					         pAddr->IpMask.String );
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "WINS Pri Context:     %ld\n",
					         pAddr->Context );
					pAddr = pAddr->Next;
				}
				while ( pAddr );

				pAddr = &pAdapter->SecondaryWinsServer;
				while ( pAddr )
				{
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "WINS Sec Address:     %s\n",
					         pAddr->IpAddress.String );
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "WINS Sec Mask:        %s\n",
					         pAddr->IpMask.String );
					Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
					      "WINS Sec Context:     %ld\n",
					         pAddr->Context );
					pAddr = pAddr->Next;
				}
				while ( pAddr );
			}
			else
			{
				Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
				      "WINS Enabled:        No\n" );
			}

			Log ( BLANK, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "\n\n" );

			// Print the adapter selection to the screen
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTOFF, NO_PROMPT,
			      "    %d %s (%s)\n",
			      ListIdx,
			      pAdapter->Description,
			      pAdapter->IpAddressList.IpAddress.String );
			pAdapter = pAdapter->Next;
		}

		// Prompt for selectio of the adapter
		do
		{
			Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "\nEnter adapter number (1-%d): ",
			      ListIdx );
			fgets ( String, 4, stdin );
			AdapterIdx = atoi ( String );
		}
		while ( AdapterIdx < 1 || AdapterIdx > ListIdx );

		Log ( BLANK, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT, "\n" );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "Adapter %d selected\n",
		      AdapterIdx );

		// Save needed information for future use
		pAdapter = pAdapterInfo;
		ListIdx = 1;
		while ( pAdapter &&
		        ListIdx < AdapterIdx)
		{
			pAdapter = pAdapter->Next;
			ListIdx++;
		}

		// indicate valid MAC address data
		gstTesterAddressData.bValidMACAddress = TRUE;
		// copy byte array
		memcpy ( gstTesterAddressData.MACAddress, pAdapter->Address, sizeof ( gstTesterAddressData.MACAddress ) );
		// convert to string
		getMACAddressStringFromArray ( (BYTE *)&pAdapter->Address, (BYTE *)&gstTesterAddressData.MACAddressString );

		// indicate valid IPv4 address data
		gstTesterAddressData.bValidIPv4Address = TRUE;
		// convert string to byte array and ulong
		gstTesterAddressData.ulIPv4Address = getIPAddressArrayFromString ( (BYTE *)&gstTesterAddressData.IPv4Address, (char *)&pAdapter->IpAddressList.IpAddress.String );
		// copy string
		memcpy ( gstTesterAddressData.IPv4AddressString, pAdapter->Address, sizeof ( gstTesterAddressData.IPv4Address ) );

		// free the memory used for adapter info
		if ( pAdapterInfo )
		{
			free ( pAdapterInfo );
		}
	}

	return PASS;
}




/*******************************************************************************
**
**  Function:  GetEthernetErrorString
**
**  Purpose:   Return a pointer to the Error String corresponding to
**             the passed Error number
**
*******************************************************************************/
char* GetEthernetErrorString ( int iError )
{
	int i;

	for ( i = 0;
	      i < NUM_SOCKET_ERRORS;
	      i++)
	{
		if ( iError == ausSocketErrors[i] )
		{
			return aszSocketErrorDescriptions[i];
		}
	}

	return aszSocketErrorDescriptions[eMaxSocketErrorCodes];
}




/*******************************************************************************
**
**  Function:  getMACAddressStringofDecimalArray
**
**  Purpose:   Convert a byte array into a MAC Address text string
**
*******************************************************************************/
void getMACAddressStringFromArray ( BYTE *pMACAddress, char *pMACAddressString )
{
	BYTE i = 0;  // string index
	BYTE j = 0;  // decimal number index
	
	sprintf_s ( &pMACAddressString[0], MAX_MAC_ADDRESS_LENGTH*4,
	            "%d:%d:%d:%d:%d:%d",
	            pMACAddress[0],
	            pMACAddress[1],
	            pMACAddress[2],
	            pMACAddress[3],
	            pMACAddress[4],
	            pMACAddress[5] );
}




/*******************************************************************************
**
**  Function:  getIPv4AddressStringofDecimalArray
**
**  Purpose:   Convert a byte array into an IP Address text string and an unsigned long single value
**
*******************************************************************************/
DWORD getIPv4AddressStringFromArray ( BYTE *pIPAddress, char *pIPAddressString )
{
	BYTE i = 0;  // string index
	BYTE j = 0;  // decimal number index
	
	sprintf_s ( &pIPAddressString[0], MAX_MAC_ADDRESS_LENGTH*4,
	            "%d.%d.%d.%d",
	            pIPAddress[0],
	            pIPAddress[1],
	            pIPAddress[2],
	            pIPAddress[3] );

	return pIPAddress[0] + ((DWORD)pIPAddress[1] << 8) + ((DWORD)pIPAddress[2] << 16) + ((DWORD)pIPAddress[3] << 24);
}




/*******************************************************************************
**
**  Function:  getIPAddressArrayFromString
**
**  Purpose:   Convert an IP Address text string into a byte array and an unsigned long single value
**
*******************************************************************************/
DWORD getIPAddressArrayFromString ( BYTE *pIPAddress, char *pIPAddressString )
{
	struct sockaddr_in sa;


	// Use inet_pton to convert the IP string to bytes
	inet_pton ( AF_INET, pIPAddressString, &sa.sin_addr );
	// Copy the byte representation of the IP address
	memcpy ( pIPAddress, &sa.sin_addr.s_addr, 4 );

	return pIPAddress[0] + ((DWORD)pIPAddress[1] << 8) + ((DWORD)pIPAddress[1] << 16) + ((DWORD)pIPAddress[3] << 24);
}




/*******************************************************************************
**
**  Function:  PurgeSocketBuffer
**
**  Purpose:   Empty a socket's data buffer in case unexpected data has accumulated
**
*******************************************************************************/
BOOL PurgeSocketBuffer ( t_stSocketData *pstSocketData )
{
	char  Buffer[PACKET_SIZE*3];
	WORD  BytesRead = 0;
	int   iSize = sizeof ( pstSocketData->remote_sin );

	if ( pstSocketData->Type == TCP_SOCKET )
	{
		while ( BytesRead = recv ( pstSocketData->hSock, Buffer, PACKET_SIZE*3, 0 ) > 0 )
		{
			// Data is read into the buffer, but no action is taken, effectively discarding it.
		}
	}
	else if ( pstSocketData->Type == UDP_SOCKET )
	{
		while ( BytesRead = recvfrom ( pstSocketData->hSock, Buffer, PACKET_SIZE*3, 0,
		                               (struct sockaddr*)&pstSocketData->remote_sin, &iSize ) > 0 )
		{
			// Data is read into the buffer, but no action is taken, effectively discarding it.
		}
	}

	// if an error occurred during recv
	if ( BytesRead < 0 )
	{
		return FALSE;
	}

	// Buffer purged
	return TRUE;
}




/*******************************************************************************
**
**  Function:  InitSocketData
**
**  Purpose:   
**
*******************************************************************************/
void InitSocketData ( t_stSocketData *pstSocketData, int Type )
{
	/////////////////////////////////////////////
	// clear socket data
	/////////////////////////////////////////////
	memset ( pstSocketData, 0, sizeof ( t_stSocketData ) );

	/////////////////////////////////////////////
	// start filling socket data
	/////////////////////////////////////////////
	pstSocketData->pAddr = &gstTesterAddressData;
	if ( Type == DHCP_SOCKET )
	{
		pstSocketData->usPort = DHCP_LCL_PORT;
	}
	else
	{
		pstSocketData->usPort = TESTERPORT;
	}
	pstSocketData->bStop = FALSE;
	pstSocketData->bConnected = FALSE;
	pstSocketData->hSock = INVALID_SOCKET;
	pstSocketData->Type = Type;
	switch ( Type )
	{
		case DHCP_SOCKET:
		{
			sprintf_s ( &pstSocketData->NameString[0], sizeof ( pstSocketData->NameString ), "DHCP UDP" );
		}
		break;
		case UDP_SOCKET:
		{
			sprintf_s ( &pstSocketData->NameString[0], sizeof ( pstSocketData->NameString ), "UDP" );
		}
		break;
		case TCP_SOCKET:
		{
			sprintf_s ( &pstSocketData->NameString[0], sizeof ( pstSocketData->NameString ), "TCP" );
		}
		break;
	}
}




/*******************************************************************************
**
**  Function:  OpenSocket
**
**  Purpose:   
**
*******************************************************************************/
int OpenSocket ( t_stSocketData *pstSocketData )
{
	DWORD         timeout = 100;
	BOOL          bNoDelay = TRUE;
	BOOL          bBroadcast = TRUE;
	INT           iOpt = 1;
//	LINGER        stLinger;
//	DWORD         keepalivetime = 60000;  // 60 sec;

	char         *pErrorString;


	/////////////////////////////////////////////
	// create a socket
	/////////////////////////////////////////////
	if ( pstSocketData->Type == TCP_SOCKET )
	{
		pstSocketData->hSock = socket ( AF_INET, SOCK_STREAM, 0 );
	}
	else
	{
		pstSocketData->hSock = socket ( AF_INET, SOCK_DGRAM, 0 );
	}

	if ( pstSocketData->hSock == INVALID_SOCKET )
	{
		pstSocketData->iError = WSAGetLastError ( );
		pErrorString = GetEthernetErrorString ( pstSocketData->iError );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s Socket creation Error: %d: %s\n",
		      pstSocketData->NameString,
		      pstSocketData->iError,
		      pErrorString );
//		return stSocketData.iError;
	}
	else
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s Socket creation successful.\n",
		      pstSocketData->NameString );
	}


	/////////////////////////////////////////////
	// set socket source (tx) address structure
	/////////////////////////////////////////////
	pstSocketData->source_sin.sin_family      = AF_INET;
	pstSocketData->source_sin.sin_addr.s_addr = pstSocketData->pAddr->ulIPv4Address;
	pstSocketData->source_sin.sin_port        = htons ( pstSocketData->usPort );


	/////////////////////////////////////////////
	// set socket options
	/////////////////////////////////////////////
	// Allow the address from other sockets to be reused
	if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, SO_REUSEADDR,
	                 (char*)&iOpt, sizeof ( iOpt ) ) < 0 )
	{
		pstSocketData->iError = WSAGetLastError ( );
		pErrorString = GetEthernetErrorString ( pstSocketData->iError );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s setsockopt SO_REUSEADDR Error: %d: %s\n",
		      pstSocketData->NameString,
		      pstSocketData->iError,
		      pErrorString );
//		return pstSocketData->iError;
	}

	// set receive timeout
	if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, SO_RCVTIMEO,
	                  (const char*)&timeout, sizeof ( timeout ) ) < 0 )
	{
		pstSocketData->iError = WSAGetLastError ( );
		pErrorString = GetEthernetErrorString ( pstSocketData->iError );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s setsockopt SO_RCVTIMEO Error: %d: %s\n",
		      pstSocketData->NameString,
		      pstSocketData->iError,
		      pErrorString );
//		return stSocketData.iError;
	}

	// set send timeout
	if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, SO_SNDTIMEO,
	                  (const char*)&timeout, sizeof ( timeout ) ) < 0 )
	{
		pstSocketData->iError = WSAGetLastError ( );
		pErrorString = GetEthernetErrorString ( pstSocketData->iError );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s setsockopt SO_SNDTIMEO Error: %d: %s\n",
		      pstSocketData->NameString,
		      pstSocketData->iError,
		      pErrorString );
//		return pstSocketData->iError;
	}


	/////////////////////////////////////////////
	// TCP only socket options
	/////////////////////////////////////////////
	if ( pstSocketData->Type == TCP_SOCKET )
	{
		// set to reduce network latency
		if ( setsockopt ( pstSocketData->hSock, IPPROTO_TCP, TCP_NODELAY,
		                  (char*)&bNoDelay, sizeof ( BOOL ) ) < 0 )
		{
			pstSocketData->iError = WSAGetLastError ( );
			pErrorString = GetEthernetErrorString ( pstSocketData->iError );
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s setsockopt TCP_NODELAY Error: %d: %s\n",
			      pstSocketData->NameString,
			      pstSocketData->iError,
			      pErrorString );
//			return pstSocketData->iError;
		}

//		// set linger to 0 not initiate normal termination sequence on close
//		stLinger.l_linger = 0;
//		stLinger.l_onoff = TRUE;
//		if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, SO_LINGER,
//		                  (char*)&stLinger, sizeof ( LINGER ) ) < 0 )
//		{
//			pstSocketData->iError = WSAGetLastError ( );
//			pErrorString = GetEthernetErrorString ( pstSocketData->iError );
//			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
//			      "%s setsockopt SO_LINGER Error: %d: %s\n",
//			      pstSocketData->NameString,
//			      pstSocketData->iError,
//			      pErrorString );
////			return pstSocketData->iError;
//		}

//		// enable TCP keepalive mechanism
//		if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, SO_KEEPALIVE,
//		                  (char*)&opt, sizeof ( opt ) ) < 0 )
//		{
//			pstSocketData->iError = WSAGetLastError ( );
//			pErrorString = GetEthernetErrorString ( pstSocketData->iError );
//			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
//			      "%s setsockopt SO_KEEPALIVE Error: %d: %s\n",
//			      pstSocketData->NameString,
//			      pstSocketData->iError,
//			      pErrorString );
////			return pstSocketData->iError;
//		}
//		else
//		{
//			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
//			      "%s setsockopt SO_KEEPALIVE set to ON\n",
//			      pstSocketData->NameString );
//		}
//		// set TCP keepalive interval
//		if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, TCP_KEEPINTVL,
//		                  (char*)&keepalivetime, sizeof ( keepalivetime ) ) < 0 )
//		{
//			pstSocketData->iError = WSAGetLastError ( );
//			pErrorString = GetEthernetErrorString ( pstSocketData->iError );
//			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
//			      "%s setsockopt TCP_KEEPINTVL Error: %d: %s\n",
//			      pstSocketData->NameString,
//			      pstSocketData->iError,
//			      pErrorString );
////			return pstSocketData->iError;
//		}
//		else
//		{
//			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
//			      "%s setsockopt TCP_KEEPINTVL set to %l ms\n",
//			      pstSocketData->NameString,
//			      keepalivetime );
//		}
	}  // end if ( pstSocketData->Type == TCP_SOCKET )

	/////////////////////////////////////////////
	// UDP/DHCP only socket options
	/////////////////////////////////////////////
	else
	{
		// Enable broadcasts on the UDP socket
		if ( setsockopt ( pstSocketData->hSock, SOL_SOCKET, SO_BROADCAST,
		                  (char*)&bBroadcast, sizeof ( BOOL ) ) < 0 )
		{
			pstSocketData->iError = WSAGetLastError ( );
			pErrorString = GetEthernetErrorString ( stUDPSocketData.iError );
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "%s setsockopt SO_BROADCAST Error: %d: %s\n",
			      pstSocketData->NameString,
			      stUDPSocketData.iError,
			      pErrorString );
//			return pstSocketData->iError;
	}

	}


	/////////////////////////////////////////////
	// bind the socket
	/////////////////////////////////////////////
	if ( bind ( pstSocketData->hSock,
	            (struct sockaddr*)&pstSocketData->source_sin,
	            sizeof ( pstSocketData->source_sin ) ) == SOCKET_ERROR )
	{
		pstSocketData->iError = WSAGetLastError ( );
		pErrorString = GetEthernetErrorString ( pstSocketData->iError );
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s Socket bind  %d.%d.%d.%d:%d  Error: %d: %s\n",
		      pstSocketData->NameString,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b1,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b2,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b3,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b4,
		      ntohs ( pstSocketData->source_sin.sin_port ),
		      pstSocketData->iError,
		      pErrorString );
//		return pstSocketData->iError;
	}
	else
	{
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "%s Socket bind  %d.%d.%d.%d:%d  successful.\n",
		      pstSocketData->NameString,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b1,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b2,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b3,
		      pstSocketData->source_sin.sin_addr.S_un.S_un_b.s_b4,
		      ntohs ( pstSocketData->source_sin.sin_port ) );

		pstSocketData->bConnected = TRUE;
	}
	return 0;
}




/*******************************************************************************
**
**  Function:  CloseSocket
**
**  Purpose:   
**
*******************************************************************************/
STATUS CloseSocket ( t_stSocketData *pstSocketData )
{
	// only attempt to close socket if it has a valid handle
	if ( pstSocketData->hSock != INVALID_SOCKET )
	{
		if ( closesocket ( pstSocketData->hSock ) != 0 )
		{
			pstSocketData->iError  = WSAGetLastError ( );
			char *pErrorString = GetEthernetErrorString ( pstSocketData->iError );
			Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "%s closesocket Error: %d: %s\n",
			      pstSocketData->NameString,
			      pstSocketData->iError,
			      pErrorString );

			return FAIL;
		}
		else
		{
			Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "%s Socket close successful.\n",
			      pstSocketData->NameString );

			pstSocketData->bConnected = FALSE;
		}
	}

	return PASS;
}






/*******************************************************************************
**
**  Function:  CloseSockets
**
**  Purpose:   
**
*******************************************************************************/
STATUS CloseSockets ( void )
{
	int   RetCode = PASS;


	// close sockets
	if ( CloseSocket ( &stDHCPSocketData) != PASS)
	{
		RetCode = FAIL;
	}
	
	if ( CloseSocket ( &stUDPSocketData ) != PASS )
	{
		RetCode = FAIL;
	}
	
	if ( CloseSocket ( &stTCPSocketData ) != PASS )
	{
		RetCode = FAIL;
	}
	
	return RetCode;
}








/*******************************************************************************
**
**  Function:  AddressAvailable
**
**  Purpose:   
**
*******************************************************************************/
boolean AddressAvailable ( DWORD  destIP, DWORD  srcIP )
{
	struct in_addr addr;
	char   ip_string[INET_ADDRSTRLEN];

	// Buffer for MAC address
	BYTE  macAddr[6];         // Ethernet/Token Ring MAC address (6 bytes)
	ULONG phyAddrLen = 6;


	addr.s_addr = destIP;
	inet_ntop ( AF_INET, &addr, ip_string, INET_ADDRSTRLEN );

	// Send ARP request
	DWORD result = SendARP ( destIP, srcIP, macAddr, &phyAddrLen );
	if ( result == NO_ERROR )
	{
		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
		      "ARP request found IP Address: %s attached to\n"
		      "MAC Address: %02X-%02X-%02X-%02X-%02X-%02X\n",
		      ip_string,
		      macAddr[0],
		      macAddr[1],
		      macAddr[2],
		      macAddr[3],
		      macAddr[4],
		      macAddr[5] );

		return FALSE;
	}
	else
	{
		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
		      "ARP request did not find IP Address %s.\n",
		      ip_string );
	}

	return TRUE;
}




/*******************************************************************************
**
**  Function:  Ping
**
**  Purpose:   
**
*******************************************************************************/
int Ping ( unsigned long ipaddr )
{
#define DATA_SIZE 32
#define MAX_PACKET_SIZE 65535

	SOCKET sock;
	char send_data[DATA_SIZE];
	struct sockaddr_in dest_addr;
	struct sockaddr_in src_addr;
	int addr_len = sizeof(src_addr);
	HANDLE hIcmpFile;
	PICMP_ECHO_REPLY pIcmpEchoReply;
	DWORD dwRetVal;
	int RetCode;


	sock = socket ( AF_INET, SOCK_RAW, IPPROTO_ICMP );
	if ( sock == INVALID_SOCKET )
	{
		int iError = WSAGetLastError();
		char *pErrorString = GetEthernetErrorString ( iError );
		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
		      "ICMP socket creation Error: %d: %s\n",
		      iError,
		      pErrorString );
		return -1;
	}

	dest_addr.sin_family      = AF_INET;
	dest_addr.sin_addr.s_addr = ipaddr;
	dest_addr.sin_port        = 0;

	for ( int i = 0; i < DATA_SIZE; i++ )
	{
		send_data[i] = 'A' + i % 26;
	}

	hIcmpFile = IcmpCreateFile ( );
	if ( hIcmpFile == INVALID_HANDLE_VALUE )
	{
		int iError = GetLastError();
		char *pErrorString = GetEthernetErrorString ( iError );
		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
		      "IcmpCreateFile Error: %d: %s\n",
		      iError,
		      pErrorString );
		closesocket ( sock );
		return -1;
	}

	pIcmpEchoReply = (PICMP_ECHO_REPLY) malloc ( sizeof ( ICMP_ECHO_REPLY ) + DATA_SIZE );
	if ( pIcmpEchoReply == NULL )
	{
		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
		      "Memory allocation failed\n" );
		IcmpCloseHandle ( hIcmpFile );
		closesocket ( sock );
		return -1;
	}

	dwRetVal = IcmpSendEcho ( hIcmpFile, ipaddr, send_data, DATA_SIZE, NULL, pIcmpEchoReply, sizeof ( ICMP_ECHO_REPLY ) + DATA_SIZE, 1000 );
	if ( dwRetVal != 0 )
	{
		Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
		      "Reply from %s.\n",
		      inet_ntoa ( dest_addr.sin_addr ) );

		RetCode = 1;
	}
	else
	{
		int iError = GetLastError();
		if ( iError == 11010 )
		{
			Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "No reply from %s.\n",
			       inet_ntoa ( dest_addr.sin_addr ) );
		}
		else
		{
			char *pErrorString = GetEthernetErrorString ( iError );
			Log ( INFORMATION, SCREENOUTPUTOFF, LOGOUTPUTON, NO_PROMPT,
			      "IcmpSendEcho Error: %d: %s\n",
			      iError,
			      pErrorString );
		}
		RetCode = 0;
	}

	free ( pIcmpEchoReply );
	IcmpCloseHandle ( hIcmpFile );
	closesocket ( sock );
	return RetCode;
}




/*******************************************************************************
**
**  Function:  isSocketConnected
**
**  Purpose:   returns  1  if the socket is connected
**             returns  0  if the socket is disconnected
**             returns -1  if error checking socket status
**
*******************************************************************************/
int isSocketConnected ( SOCKET socket )
{
	fd_set read_fds;
	struct timeval timeout;
	int iError;


	// Initialize the file descriptor set
	FD_ZERO ( &read_fds );
	FD_SET ( socket, &read_fds );

	// Set the timeout value
	timeout.tv_sec = 0;
	timeout.tv_usec = 0;

	// Call select to check if the socket is readable
	int result = select ( 0, &read_fds, NULL, NULL, &timeout );

	// Check the result of select
	if ( result > 0 )
	{
		// The socket is readable, check if there is any data available
		char buffer[1];
		int recv_result = recv ( socket, buffer, sizeof ( buffer ), MSG_PEEK );
		if ( recv_result == 0 )
		{
			// The connection has been gracefully closed
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "TCP socket has been closed.\n" );
			return 0;
		}
		else if ( recv_result < 0 )
		{
			// An error occurred, check if it's a connection reset error
			if ( (iError = WSAGetLastError ( )) == WSAECONNRESET )
			{
				// The connection has been forcibly closed
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "TCP socket has been closed.\n" );
				return 0;
			}
			else
			{
				// Another error occurred
				char *pErrorString = GetEthernetErrorString ( iError );
				Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
				      "TCP socket Error: %d: %s\n",
				      iError,
				      pErrorString );
				return -1;
			}
		}
		else
		{
			// There is data available, the connection is still alive
			Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
			      "TCP socket still connected.\n" );
			gbSocketCheck = TRUE;
			return 1;
		}
	}
	else if ( result == 0 )
	{
		// Timeout occurred, the connection is still alive
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "TCP socket still connected.\n" );
		return 1;
	}
	else
	{
		// An error occurred
		Log ( INFORMATION, SCREENOUTPUTON, LOGOUTPUTON, NO_PROMPT,
		      "TCP socket select Error: %d.\n",
		      result );
		return -1;
	}
}
