/*
GameSpy Ping SDK 
Dan "Mr. Pants" Schoenblum
dan@gamespy.com

Copyright 1999-2001 GameSpy Industries, Inc

18002 Skypark Circle
Irvine, California 92614
949.798.4200 (Tel)
949.798.4299 (Fax)
devsupport@gamespy.com
*/

/****************
** DEFINITIONS **
*****************

UPP = UDP Ping Protocol

*/

/*************
** INCLUDES **
*************/
#include <limits.h>
#include "pinger.h"
#include "../darray.h"

/************
** DEFINES **
************/
#define PI_MAGIC             0x91
#define PI_VERSION           0x01

#define PI_TRIP2_TIMEOUT     10000

#define PI_DATA_MAX_LEN      (PINGER_UDP_PING_SIZE - sizeof(piUDPPing))

/**********
** TYPES **
**********/
// u_char must be 1 byte unsigned, and unsigned short must be 2 bytes unsigned.
////////////////////////////////////////////////////////////////////////
typedef unsigned char  uint8;
typedef unsigned short uint16;

// This is the internal data for a UDP ping.
// This is an exact copy of what gets sent over the network, minus filler bytes.
////////////////////////////////////////////////////////////////////////////////
typedef struct piUDPPing
{
	uint8  magic;    // magic number - helps to ignore bad packets
	uint8  version;  // version - UPP version
	uint16 trip;     // trip number - will always be 1, 2, or 3
	uint16 ID_A;     // this ID is used by A (the source for the first dgram)
	uint16 ID_B;     // this ID is used by B (the destination for the first dgram)
} piUDPPing;

// This is a ping on which we're waiting for a reply.
/////////////////////////////////////////////////////
typedef struct piActivePing
{
	PINGERBool     originator;    // true if we sent the first dgram
	uint16         ID;            // unique ID for this ping
	uint16         expectedTrip;  // expected trip # of the reply
	gsi_time         timestamp;     // time we sent the ping
	gsi_time         timeout;       // time this ping expires
	unsigned int          remoteIP;      // IP of the other end of this ping
	unsigned short        remotePort;    // port of the other end of this ping
	pingerGotPing  reply;         // call this callback if we're the originator (otherwise call pingerPinged)
	void *         replyParam;    // the extra param passed to reply
} piActivePing;

// This is a queued gotPing callback.
/////////////////////////////////////
typedef struct piQueuedCallback
{
	unsigned int IP;
	unsigned short port;
	int ping;
	char * data;
	int len;
	void * param;
	pingerGotPing callback;
} piQueuedCallback;

/************
** GLOBALS **
************/
static PINGERBool       piInitialized = PINGERFalse;
static SOCKET           piSocket = INVALID_SOCKET;
static pingerGotPing    piPingerPinged;
static void *           piPingerPingedParam;
static pingerSetData    piPingerSetData;
static void *           piPingerSetDataParam;
static PINGERBool       piSettingData;
static PINGERBool       piUDPEnabled;
static uint16           piNextID;
static DArray           piActivePingList;
static gsi_time    piLastThinkTime;
static DArray           piCallbacks;

/**************
** FUNCTIONS **
**************/
static uint16 piGetNextID(void)
{
	// Store the next ID.
	/////////////////////
	uint16 ID = piNextID;

	// Increment the ID.
	////////////////////
	if(piNextID == USHRT_MAX)
		piNextID = 1;
	else
		piNextID++;

	return ID;
}

static PINGERBool piBytesToPing(unsigned char * buffer, piUDPPing * udpPing, char * data)
{
	assert(buffer != NULL);
	assert(udpPing != NULL);
	assert(data != NULL);

	// Magic.
	udpPing->magic = *buffer++;

	// Version.
	udpPing->version = *buffer++;

	// Trip.
	udpPing->trip = (unsigned short)(*buffer++ << 8);
	udpPing->trip |= *buffer++;

	// ID_A.
	udpPing->ID_A = (unsigned short)(*buffer++ << 8);
	udpPing->ID_A |= *buffer++;

	// ID_B.
	udpPing->ID_B = (unsigned short)(*buffer++ << 8);
	udpPing->ID_B |= *buffer++;

	// Check for the magic number.
	// This asserted on 11/17/00 at aprox. 1:40PM.
	// on a ping from 205.251.192.203:13139.
	// There were 2 empty bytes at the start of
	// the buffer, then the normal message followed.
	// Same thing on 12/14/00 at approx. 5PM.
	// From 24.156.198.26
	// JED 12/18/00 15:20, again from 24.156.198.26
	// 2001.Jan.15.JED - again from 24.156.198.26
	// 2001.Jan.23.JED - 213.65.117.53 (@11:15am)
	// 2001.Mar.26.JED - 24.159.107.0 (@7:30pm)
	// PANTS|04.20.00 - commented out per JED's request.
	////////////////////////////////////////////////////
	//assert(udpPing->magic == PI_MAGIC);
	if(udpPing->magic != PI_MAGIC)
		return PINGERFalse;

	// Check the version.
	/////////////////////
	assert(udpPing->version == PI_VERSION);
	if(udpPing->version != PI_VERSION)
		return PINGERFalse;

	// Check the trip.
	//////////////////
	assert((udpPing->trip >= 1) && (udpPing->trip <= 3));
	if((udpPing->trip < 1) || (udpPing->trip > 3))
		return PINGERFalse;

	// Fill in the data.
	////////////////////
	memcpy(data, buffer, PI_DATA_MAX_LEN);

	return PINGERTrue;
}

static void piPingToBytes(piUDPPing * udpPing, unsigned char * buffer)
{
	assert(udpPing != NULL);
	assert(buffer != NULL);

	// Magic.
	*buffer++ = udpPing->magic;

	// Version.
	*buffer++ = udpPing->version;

	// Trip.
	*buffer++ = (unsigned char)((udpPing->trip & 0xFF00) >> 8);
	*buffer++ = (unsigned char)(udpPing->trip & 0x00FF);

	// ID_A.
	*buffer++ = (unsigned char)((udpPing->ID_A & 0xFF00) >> 8);
	*buffer++ = (unsigned char)(udpPing->ID_A & 0x00FF);

	// ID_B.
	*buffer++ = (unsigned char)((udpPing->ID_B & 0xFF00) >> 8);
	*buffer++ = (unsigned char)(udpPing->ID_B & 0x00FF);
}

static PINGERBool piSendPing(SOCKADDR_IN * to, uint16 trip, uint16 ID_A, uint16 ID_B, const char * data)
{
	unsigned char buffer[PINGER_UDP_PING_SIZE];
	piUDPPing udpPing;
	int rcode;

	assert(to != NULL);
	assert((trip >= 1) && (trip <= 3));
	assert(!((trip == 2) && (ID_A == 0)));

	// Construct the outgoing ping.
	///////////////////////////////
	udpPing.magic = PI_MAGIC;
	udpPing.version = PI_VERSION;
	udpPing.trip = trip;
	udpPing.ID_A = ID_A;
	udpPing.ID_B = ID_B;
	piPingToBytes(&udpPing, buffer);
	if(data != NULL)
		memcpy(buffer + sizeof(piUDPPing), data, PI_DATA_MAX_LEN);
	else
		memset(buffer + sizeof(piUDPPing), 0, PI_DATA_MAX_LEN);

	// Send the outgoing ping.
	//////////////////////////
	rcode = sendto(piSocket, (char *)buffer, PINGER_UDP_PING_SIZE, 0, (SOCKADDR *)to, sizeof(SOCKADDR_IN));
	// Did it send ok?
	//////////////////
	if(rcode != PINGER_UDP_PING_SIZE)
		return PINGERFalse;

	return PINGERTrue;
}

static PINGERBool piSocketInit(const char * localAddress,
							   unsigned short localPort)
{
	int rcode;
	SOCKADDR_IN sockaddr;
	int bFlag;
	//int iLastError;

	assert(localPort != 0);

	// Setup sockets.
	/////////////////
	SocketStartUp();

	// Setup the address.
	/////////////////////
	memset(&sockaddr, 0, sizeof(SOCKADDR_IN));
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(localPort);
	if(localAddress != NULL)
	{
		unsigned int IP;

		// Check for "a.b.c.d".
		///////////////////////
		IP = inet_addr(localAddress);
		if(IP == INADDR_NONE)
		{
			HOSTENT * hostent;

			// Check for "machine.host.domain".
			///////////////////////////////////
			hostent = gethostbyname(localAddress);
			if(hostent == NULL)
			{
//				iLastError = WSAGetLastError();
				assert(0);
				return PINGERFalse;
			}

			// Grab the IP.
			///////////////
			assert(IP != 0);
			IP = *(unsigned int *)hostent->h_addr_list[0];
		}

		// Got the IP.
		//////////////
		sockaddr.sin_addr.s_addr = IP;
	}
	else
	{
		// Any local IP.
		////////////////
		sockaddr.sin_addr.s_addr = INADDR_ANY;
	}

	// Create the socket.
	/////////////////////
	piSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(piSocket == INVALID_SOCKET)
	{
//		iLastError = GOAGetLastError(piSocket);
		assert(0);
		return PINGERFalse;
	}

	// Allow reuse of the socket if not closed properly before.
	///////////////////////////////////////////////////////////
	bFlag = 1;
#ifndef INSOCK   // PS2 INSOCK network layer does not support reuse addr
	rcode = setsockopt(piSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&bFlag, sizeof(bFlag) );
#endif
	/*if(gsiSocketIsError(rcode))
	{
		iLastError = WSAGetLastError();
		assert(0);
		return PINGERFalse;
	}*/

	// Bind the socket.
	///////////////////
	rcode = bind(piSocket, (SOCKADDR *)&sockaddr, sizeof(SOCKADDR_IN));
	if (gsiSocketIsError(rcode))
	{
//		iLastError = GOAGetLastError(piSocket);
		assert(0);
		return PINGERFalse;
	}

	return PINGERTrue;
}

static void piQueueCallback
(
	unsigned int IP,
	unsigned short port,
	int ping,
	const char * data,
	int len,
	void * param,
	pingerGotPing callbackFunc
)
{
	piQueuedCallback callback;

	assert(callbackFunc);
	if(!callbackFunc)
		return;

	// Setup the callback.
	//////////////////////
	callback.IP = IP;
	callback.port = port;
	callback.ping = ping;
	callback.len = len;
	callback.param = param;
	callback.callback = callbackFunc;

	// Copy the data.
	/////////////////
	if(data)
	{
		callback.data = (char *)gsimalloc((unsigned int)len);
		if(!callback.data)
			return;
		memcpy(callback.data, data, (unsigned int)len);
	}
	else
	{
		callback.data = NULL;
	}

	// Add it.
	//////////
	ArrayAppend(piCallbacks, &callback);
}

static void piCallCallbacks(void)
{
	piQueuedCallback * callback;
	piQueuedCallback callbackCopy;

	while(ArrayLength(piCallbacks) > 0)
	{
		// Get the info.
		////////////////
		callback = (piQueuedCallback *)ArrayNth(piCallbacks, 0);
		assert(callback);
		if(!callback)
			return;

		// Copy the info.
		/////////////////
		memcpy(&callbackCopy, callback, sizeof(piQueuedCallback));
		callback = &callbackCopy;

		// Remove it from the list.
		///////////////////////////
		ArrayDeleteAt(piCallbacks, 0);

		// Call it.
		///////////
		callback->callback(callback->IP, callback->port, callback->ping, callback->data, callback->len, callback->param);

		// gsifree it.
		///////////
		gsifree(callback->data);
	}
}

PINGERBool pingerInit(const char * localAddress,
					  unsigned short localPort,
					  pingerGotPing pinged,
					  void * pingedParam,
					  pingerSetData setData,
					  void * setDataParam)
{
	// Check if we're already initialized.
	//////////////////////////////////////
	assert(!piInitialized);

	// Make sure the UDP ping size is at least as large as the protocol struct.
	///////////////////////////////////////////////////////////////////////////
#if !defined(_NITRO)
	assert(PINGER_UDP_PING_SIZE >= sizeof(piUDPPing));
#endif

	// Check if we're already intialized.
	/////////////////////////////////////
	if(piInitialized)
		return PINGERFalse;

	// An empty localAddress is them same as a NULL.
	////////////////////////////////////////////////
	if((localAddress != NULL) && (localAddress[0] == '\0'))
		localAddress = NULL;

	// Initialize data.
	///////////////////
	piPingerPinged = pinged;
	piPingerPingedParam = pingedParam;
	piPingerSetData = setData;
	piPingerSetDataParam = setDataParam;
	piSettingData = PINGERFalse;
	piUDPEnabled = (PINGERBool)(localPort != 0);
	piNextID = 1;
	piLastThinkTime = 0;

	// Initialize the active ping list.
	///////////////////////////////////
	piActivePingList = ArrayNew(sizeof(piActivePing), 0, NULL);
	if(piActivePingList == NULL)
		return PINGERFalse;

	// Initialize the callbacks list.
	/////////////////////////////////
	piCallbacks = ArrayNew(sizeof(piQueuedCallback), 0, NULL);
	if(!piCallbacks)
	{
		ArrayFree(piActivePingList);
		return PINGERFalse;
	}

	// Setup the UDP socket.
	////////////////////////
	if(piUDPEnabled)
	{
		if(!piSocketInit(localAddress, localPort))
		{
			assert(0);

			// Check for partial intialization.
			///////////////////////////////////
			if(piSocket != INVALID_SOCKET)
			{
				closesocket(piSocket);
				piSocket = INVALID_SOCKET;
			}

			// Shut down sockets.
			/////////////////////
			SocketShutDown();

			// gsifree the lists.
			//////////////////
			ArrayFree(piActivePingList);
			ArrayFree(piCallbacks);

			return PINGERFalse;
		}
	}

	// We're initialized!
	/////////////////////
	piInitialized = PINGERTrue;

	return PINGERTrue;
}

void pingerShutdown(void)
{
	if(!piInitialized)
		return;

	// Check for setting data.
	//////////////////////////
	if(piSettingData)
		return;

	// Shut down the socket.
	////////////////////////
	if(piSocket != INVALID_SOCKET)
	{
		closesocket(piSocket);
		piSocket = INVALID_SOCKET;
	}

	// Shut down sockets.
	/////////////////////
	SocketShutDown();

	// gsifree the active ping list.
	/////////////////////////////
	ArrayFree(piActivePingList);

	// gsifree the callbacks list.
	///////////////////////////
	ArrayFree(piCallbacks);

	// Not initialized anymore.
	///////////////////////////
	piInitialized = PINGERFalse;
}

static int GS_STATIC_CALLBACK piFindActivePingCompareFn(const void *elem1, const void *elem2)
{
	piActivePing * activePing1 = (piActivePing *)elem1;
	piActivePing * activePing2 = (piActivePing *)elem2;
	assert(activePing1 != NULL);
	assert(activePing2 != NULL);
	assert(activePing1->ID != 0);
	assert(activePing2->ID != 0);

	return (activePing1->ID - activePing2->ID);
}

static piActivePing * piFindActivePing(uint16 ID, int * index)
{
	piActivePing key;
	int n;

	// Search for a matching ID.
	////////////////////////////
	key.ID = ID;
	n = ArraySearch(piActivePingList, &key, piFindActivePingCompareFn, 0, 0);
	if(index != NULL)
		*index = n;

	// Find it?
	///////////
	if(n == NOT_FOUND)
		return NULL;

	// Found it!
	////////////
	return (piActivePing *)ArrayNth(piActivePingList, n);
}

static int piCalculatePing(gsi_time sendTime, gsi_time recvTime)
{
	int ping;

	// Simple ping calculation.
	///////////////////////////
	ping = (int)(recvTime - sendTime);

	// Take off some time based on the last time we called
	// the think function.
	//////////////////////////////////////////////////////
	if(piLastThinkTime != 0)
	{
#if 0
		ping -= ((int)(recvTime - piLastThinkTime) / 2);
		if(ping < 0)
			ping = 0;
#endif
	}

	return ping;
}

static void piProcessTrip1(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime)
{
	char dataOut[PI_DATA_MAX_LEN];
	uint16 ID;

	// Validity check.
	// udpPing->ID_A != 0 was triggered by a ping
	// from 213.105.94.117 sometime during 7/3-7/02
	///////////////////////////////////////////////
	assert(udpPing->trip == 1);
	//assert(udpPing->ID_A != 0);
	if(udpPing->ID_A == 0)
		return;
	assert(udpPing->ID_B == 0);
	if(udpPing->ID_B != 0)
		return;

	// Do we want a reply?
	//////////////////////
	if(piPingerPinged != NULL)
	{
		// Get an ID.
		/////////////
		ID = piGetNextID();
	}
	else
	{
		// No reply wanted.
		///////////////////
		ID = 0;
	}

	// Setup the data.
	//////////////////
	memset(dataOut, 0, PI_DATA_MAX_LEN);
	if(piPingerSetData != NULL)
	{
		piSettingData = PINGERTrue;
		piPingerSetData(from->sin_addr.s_addr, from->sin_port, dataOut, PI_DATA_MAX_LEN, piPingerSetDataParam);
		piSettingData = PINGERFalse;
	}

	// Send the return ping (trip 2).
	/////////////////////////////////
	piSendPing(from, 2, udpPing->ID_A, ID, dataOut);

	// Do we need to add an active ping?
	////////////////////////////////////
	if(piPingerPinged != NULL)
	{
		piActivePing activePing;

		// Setup the active ping object.
		////////////////////////////////
		activePing.originator = PINGERFalse;
		activePing.ID = ID;
		activePing.expectedTrip = 3;
		activePing.timestamp = current_time();
		activePing.timeout = (activePing.timestamp + PI_TRIP2_TIMEOUT);
		activePing.remoteIP = from->sin_addr.s_addr;
		activePing.remotePort = from->sin_port;
		activePing.reply = NULL;
		activePing.replyParam = NULL;

		// Add it to the list.
		//////////////////////
		ArrayAppend(piActivePingList, &activePing);
	}
	
	GSI_UNUSED(data);
	GSI_UNUSED(recvTime);
}

static void piProcessTrip2(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime)
{
	char dataOut[PI_DATA_MAX_LEN];
	int index;
	piActivePing * activePing;

	// Validity check.
	// ID_A != 0 was triggered by a ping from 205.251.192.203.
	// 12/4/00 at approx. 5pm
	// This is the same IP as the shifted ping assert above.
	//////////////////////////////////////////////////////////
	assert(udpPing->trip == 2);
	assert(udpPing->ID_A != 0);
	if(udpPing->ID_A == 0)
		return;
	
	// Were we waiting for this?
	////////////////////////////
	activePing = piFindActivePing(udpPing->ID_A, &index);
	if(activePing == NULL)
		return;

	// Setup the data.
	//////////////////
	memset(dataOut, 0, PI_DATA_MAX_LEN);
	if(piPingerSetData != NULL)
	{
		piSettingData = PINGERTrue;
		piPingerSetData(from->sin_addr.s_addr, from->sin_port, dataOut, PI_DATA_MAX_LEN, piPingerSetDataParam);
		piSettingData = PINGERFalse;
	}

	// Do we send a reply?
	//////////////////////
	if(udpPing->ID_B != 0)
	{
		// Send the return ping.
		////////////////////////
		piSendPing(from, 3, 0, udpPing->ID_B, dataOut);
	}

	// Call the callback?
	/////////////////////
	if(activePing->reply != NULL)
	{
		// Calculate the ping.
		//////////////////////
		int ping = piCalculatePing(activePing->timestamp, recvTime);

		// Add the callback.
		////////////////////
		piQueueCallback(from->sin_addr.s_addr, from->sin_port, ping, data, PI_DATA_MAX_LEN, activePing->replyParam, activePing->reply);
	}
	
	// gsifree the active ping.
	////////////////////////
	ArrayDeleteAt(piActivePingList, index);
}

static void piProcessTrip3(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime)
{
	int index;
	piActivePing * activePing;
	
	// Validity check.
	//////////////////
	assert(udpPing->trip == 3);
	assert(udpPing->ID_A == 0);
	if(udpPing->ID_A != 0)
		return;
	assert(udpPing->ID_B != 0);
	if(udpPing->ID_B == 0)
		return;

	// Were we waiting for this?
	////////////////////////////
	activePing = piFindActivePing(udpPing->ID_B, &index);
	if(activePing == NULL)
		return;

	// Call the callback.
	/////////////////////
	assert(piPingerPinged != NULL);
	if(piPingerPinged != NULL)
	{
		// Calculate the ping.
		//////////////////////
		int ping = piCalculatePing(activePing->timestamp, recvTime);

		// Call it.
		///////////
		piPingerPinged(from->sin_addr.s_addr, from->sin_port, ping, data, PI_DATA_MAX_LEN, piPingerPingedParam);
	}
	
	// gsifree the active ping.
	////////////////////////
	ArrayDeleteAt(piActivePingList, index);
}

static void piProcessPing(piUDPPing * udpPing, const char * data, SOCKADDR_IN * from, gsi_time recvTime)
{
	assert(udpPing != NULL);
	assert(data != NULL);
	assert(from != NULL);

	// Process based on trip num.
	/////////////////////////////
	if(udpPing->trip == 1)
		piProcessTrip1(udpPing, data, from, recvTime);
	else if(udpPing->trip == 2)
		piProcessTrip2(udpPing, data, from, recvTime);
	else if(udpPing->trip == 3)
		piProcessTrip3(udpPing, data, from, recvTime);
}

static void piProcessIncoming(void)
{
	int rcode;
	unsigned char buffer[PINGER_UDP_PING_SIZE];
	SOCKADDR_IN from;
	int len;
	gsi_time recvTime;
	piUDPPing udpPing;
	char data[PI_DATA_MAX_LEN];
	
	// Check for incoming dgrams.
	/////////////////////////////
	while(piInitialized && CanReceiveOnSocket(piSocket))
	{
		// Read the dgram.
		//////////////////
		len = sizeof(SOCKADDR_IN);

		rcode = recvfrom(piSocket, (char *)buffer, PINGER_UDP_PING_SIZE, 0, (SOCKADDR *)&from, &len);

		// Check for an error.
		//////////////////////
		if (gsiSocketIsError(rcode))
		{
			if(GOAGetLastError(piSocket) == WSAEMSGSIZE)
			{
				// Ignore "too big" errors.
				///////////////////////////
				rcode = PINGER_UDP_PING_SIZE;
			}
			else
			{
				// Something's wrong, get the hell out of here!
				///////////////////////////////////////////////
				return; //ERRCON
			}
		}
		else if(rcode < PINGER_UDP_PING_SIZE)
		{
			// The ping was too small.
			//////////////////////////
			return;  //ERRCON
		}

		// What time did we receive it?
		///////////////////////////////
		recvTime = current_time();

		// Convert the buffer into a ping.
		//////////////////////////////////
		if(piBytesToPing(buffer, &udpPing, data))
		{
			// Process the dgram.
			/////////////////////
			piProcessPing(&udpPing, data, &from, recvTime);
		}
	}

	// Last time we checked for incoming data.
	//////////////////////////////////////////
	piLastThinkTime = current_time();
}

static void piCheckTimeouts(void)
{
	gsi_time now;
	piActivePing * activePing;
	int len;
	int n;

	// Get the number of active pings.
	//////////////////////////////////
	len = ArrayLength(piActivePingList);

	// Get out if no active pings.
	//////////////////////////////
	if(len == 0)
		return;

	// Get the current time.
	////////////////////////
	now = current_time();

	// Go through the list backwards.
	/////////////////////////////////
	for(n = (len - 1) ; n >= 0 ; n--)
	{
		// Get the nth element of the list.
		///////////////////////////////////
		activePing = (piActivePing *)ArrayNth(piActivePingList, n);
		assert(activePing != NULL);
		if(activePing != NULL)
		{
			// Does it have a timeout?
			//////////////////////////
			if(activePing->timeout != 0)
			{
				// Has it timed out?
				////////////////////
				if(activePing->timeout <= now)
				{
					// Do we need to do a failed callback?
					//////////////////////////////////////
					if((activePing->originator) && (activePing->reply != NULL))
					{
						// Queue the callback.
						//////////////////////
						piQueueCallback(activePing->remoteIP, activePing->remotePort, PINGER_TIMEOUT, NULL, 0, activePing->replyParam, activePing->reply);
					}

					// Kill it!
					///////////
					ArrayDeleteAt(piActivePingList, n);
				}
			}
		}
	}
}

void pingerThink(void)
{
	// If not initialized, don't do anything.
	/////////////////////////////////////////
	if(!piInitialized)
		return;

	// Check for setting data.
	//////////////////////////
	if(piSettingData)
		return;

	// Process incoming data.
	/////////////////////////
	piProcessIncoming();

	// Check for timeouts.
	//////////////////////
	piCheckTimeouts();

	// Call queued callbacks.
	/////////////////////////
	piCallCallbacks();
}

void pingerPing(unsigned int IP,
				unsigned short port,
				pingerGotPing reply,
				void * replyParam,
				PINGERBool blocking,
				gsi_time timeout)
{
	SOCKADDR_IN to;
	uint16 ID;

	assert(piInitialized);
	assert(IP != 0);

	// ICMP not supported yet!
	//////////////////////////
	assert(port != 0);
	assert(piUDPEnabled);
	assert(piSocket != INVALID_SOCKET);

	// Check for setting data.
	//////////////////////////
	if(piSettingData)
		return;

	// Construct the outgoing address.
	//////////////////////////////////
	memset(&to, 0, sizeof(SOCKADDR_IN));
	to.sin_family = AF_INET;
	to.sin_port = htons(port);
	to.sin_addr.s_addr = IP;

	// Get an ID for this ping.
	///////////////////////////
	ID = piGetNextID();

	// Send the outgoing ping.
	//////////////////////////
	if(piSendPing(&to, 1, ID, 0, NULL))
	{
		piActivePing activePing;

		// Setup the active ping object.
		////////////////////////////////
		activePing.originator = PINGERTrue;
		activePing.ID = ID;
		activePing.expectedTrip = 2;
		activePing.timestamp = current_time();
		if(timeout == 0)
			activePing.timeout = 0;
		else
			activePing.timeout = (activePing.timestamp + timeout);
		activePing.remoteIP = IP;
		activePing.remotePort = port;
		activePing.reply = reply;
		activePing.replyParam = replyParam;

		// Add it to the list.
		//////////////////////
		ArrayAppend(piActivePingList, &activePing);
	}

	// Is this blocking?
	////////////////////
	if(blocking)
	{
		// Keep going until this ping has been de-listed.
		/////////////////////////////////////////////////
		while(piFindActivePing(ID, NULL) != NULL)
		{
			// Think.
			/////////
			pingerThink();

			// Yield.
			/////////
			msleep(1);
		}

		// Call callbacks.
		//////////////////
		piCallCallbacks();
	}
}
