/********
INCLUDES
********/
#include "../common/gsCommon.h"
#include "../common/gsAvailable.h"
#include "qr2.h"
#include "qr2regkeys.h"
#include "../natneg/natneg.h"

#ifdef __cplusplus
extern "C" {
#endif	
	
#ifdef __MWERKS__ // Codewarrior requires function prototypes
qr2_error_t qr2_initW(/*[out]*/qr2_t *qrec, const unsigned short *ip, int baseport, const unsigned short *gamename, 
					 const unsigned short *secret_key, int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback, 
					 qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, qr2_keylistcallback_t key_list_callback, 
					 qr2_countcallback_t playerteam_count_callback, qr2_adderrorcallback_t adderror_callback, void *userdata);
qr2_error_t qr2_init_socketW(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const unsigned short *gamename, 
					 const unsigned short *secret_key, int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback, qr2_keylistcallback_t key_list_callback, 
					 qr2_countcallback_t playerteam_count_callback, qr2_adderrorcallback_t adderror_callback, void *userdata);
qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key,
					 int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback,
					 qr2_keylistcallback_t key_list_callback, qr2_countcallback_t playerteam_count_callback,
					 qr2_adderrorcallback_t adderror_callback, void *userdata);
qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key,
					 int ispublic, int natnegotiate, qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback, qr2_playerteamkeycallback_t team_key_callback,
					 qr2_keylistcallback_t key_list_callback, qr2_countcallback_t playerteam_count_callback,
					 qr2_adderrorcallback_t adderror_callback, void *userdata);
#endif

/********
DEFINES
********/
#define MASTER_PORT 27900
#define MASTER_ADDR "master." GSI_DOMAIN_NAME
	//#define MASTER_ADDR "207.199.80.230"
#define FIRST_HB_TIME 10000 /* 10 sec */
#define HB_TIME 60000 /* 1 minute */
#define KA_TIME 20000 /* 20 sec */
#define MIN_STATECHANGED_HB_TIME 10000 /* 10 sec */
#define MAX_FIRST_COUNT 4 /* 4 tries */
#define MAX_DATA_SIZE 1400
#define INBUF_LEN 256
#define PUBLIC_ADDR_LEN 12
#define QR2_OPTION_USE_QUERY_CHALLENGE 128
	
#define PACKET_QUERY              0x00
#define PACKET_CHALLENGE          0x01
#define PACKET_ECHO               0x02
#define PACKET_ECHO_RESPONSE      0x05  // 0x05, not 0x03 (order)
#define PACKET_HEARTBEAT          0x03
#define PACKET_ADDERROR           0x04
#define PACKET_CLIENT_MESSAGE     0x06
#define PACKET_CLIENT_MESSAGE_ACK 0x07
#define PACKET_KEEPALIVE          0x08
#define PACKET_PREQUERY_IP_VERIFY 0x09

	 
#define MAX_LOCAL_IP 5

//magic bytes for nat negotiation message
#define NATNEG_MAGIC_LEN 6
#define NN_MAGIC_0 0xFD
#define NN_MAGIC_1 0xFC
#define NN_MAGIC_2 0x1E
#define NN_MAGIC_3 0x66
#define NN_MAGIC_4 0x6A
#define NN_MAGIC_5 0xB2

// ex flags are the 11th byte in the query packet
// Old queries will end at 10 bytes.
#define QR2_EXFLAG_SPLIT		(1<<0)

// Some other settings for split packet responses
#define QR2_SPLITNUM_MAX		7
#define QR2_SPLITNUM_FINALFLAG	(1<<7)


/********
TYPEDEFS
********/
typedef unsigned char uchar;

struct qr2_keybuffer_s
{
	uchar keys[MAX_REGISTERED_KEYS];
	int numkeys;
};

struct qr2_buffer_s
{
	char buffer[MAX_DATA_SIZE];
	int len;
};

#define AVAILABLE_BUFFER_LEN(a) (MAX_DATA_SIZE - (a)->len)

/********
VARS
********/
struct qr2_implementation_s static_qr2_rec = {INVALID_SOCKET};
static qr2_t current_rec = &static_qr2_rec;
char qr2_hostname[64];

static int num_local_ips = 0;
static struct in_addr local_ip_list[MAX_LOCAL_IP];


/********
PROTOTYPES
********/
static void send_heartbeat(qr2_t qrec, int statechanged);
static void send_keepalive(qr2_t qrec);
static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent);
static void qr2_check_queries(qr2_t qrec);
static void qr2_check_send_heartbeat(qr2_t qrec);
static void enum_local_ips();
static void qr2_expire_ip_verify(qr2_t qrec);
qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port);

/****************************************************************************/
/* PUBLIC FUNCTIONS */
/****************************************************************************/

/* qr2_init: Initializes the sockets, etc. Returns an error value
if an error occured, or 0 otherwise */
qr2_error_t qr2_init_socketA(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const char *gamename, const char *secret_key,
					 int ispublic, int natnegotiate,
					 qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback,
					 qr2_playerteamkeycallback_t team_key_callback,
					 qr2_keylistcallback_t key_list_callback,
					 qr2_countcallback_t playerteam_count_callback,
					 qr2_adderrorcallback_t adderror_callback,
					 void *userdata)
{
	char hostname[64];
	int ret;
	int i;
	qr2_t cr;

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_init_socket()\r\n");

	if (qrec == NULL)
	{
		cr = &static_qr2_rec;		
	}
	else
	{
		*qrec = (qr2_t)gsimalloc(sizeof(struct qr2_implementation_s));
		cr = *qrec;
	}
	srand((unsigned int)current_time());
	strcpy(cr->gamename,gamename);
	strcpy(cr->secret_key,secret_key);
	cr->qport = boundport;
	cr->lastheartbeat = 0;
	cr->lastka = 0;
	cr->hbsock = s;
	cr->listed_state = 1;
	cr->udata = userdata;
	cr->server_key_callback = server_key_callback;
	cr->player_key_callback = player_key_callback;
	cr->team_key_callback = team_key_callback;
	cr->key_list_callback = key_list_callback;
	cr->playerteam_count_callback = playerteam_count_callback;
	cr->adderror_callback = adderror_callback;
	cr->nn_callback = NULL;
	cr->cm_callback = NULL;
	cr->cdkeyprocess = NULL;
	cr->ispublic = ispublic;
	cr->read_socket = 0;
	cr->nat_negotiate = natnegotiate;
	cr->publicip = 0;
	cr->publicport = 0;
	cr->pa_callback = NULL;
	cr->cc_callback = NULL;
	cr->userstatechangerequested = 0;
	cr->backendoptions = 0;

	for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
		cr->instance_key[i] = (char)(rand() % 0xFF);
	for (i = 0 ; i < RECENT_CLIENT_MESSAGES_TO_TRACK ; i++)
		cr->client_message_keys[i] = -1;
	cr->cur_message_key = 0;

	memset(cr->ipverify, 0, sizeof(cr->ipverify));
	
	//if (num_local_ips == 0) - caching IPs can result in problems if DHCP has allocated a new one
	enum_local_ips();

	if (ispublic)
	{
		int override = qr2_hostname[0];
		if(!override)
			sprintf(hostname, "%s.master." GSI_DOMAIN_NAME, gamename);
		ret = get_sockaddrin(override?qr2_hostname:hostname, MASTER_PORT, &(cr->hbaddr), NULL);

		if (ret == 1)
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
				"%s resolved to %s\r\n", override?qr2_hostname:hostname, inet_ntoa(cr->hbaddr.sin_addr));
		}
		else
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
				"Failed on DNS lookup for %s \r\n", override?qr2_hostname:hostname);
		}
	}
	else //don't need to look up
		ret = 1;
	if (!ret)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_HotError,
			"qr2_init_socket() returned failed (DNS error)\r\n");
		return e_qrdnserror;
	}
	else
	{
		return e_qrnoerror;
	}


}

qr2_error_t qr2_init_socketW(/*[out]*/qr2_t *qrec, SOCKET s, int boundport, const unsigned short *gamename, const unsigned short *secret_key,
					 int ispublic, int natnegotiate,
					 qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback,
					 qr2_playerteamkeycallback_t team_key_callback,
					 qr2_keylistcallback_t key_list_callback,
					 qr2_countcallback_t playerteam_count_callback,
					 qr2_adderrorcallback_t adderror_callback,
					 void *userdata)
{
	char gamename_A[255];
	char secretkey_A[255];

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_init_socketW()\r\n");

	UCS2ToAsciiString(gamename, gamename_A);
	UCS2ToAsciiString(secret_key, secretkey_A);

	return qr2_init_socketA(qrec, s, boundport, gamename_A, secretkey_A, ispublic, natnegotiate,
							server_key_callback, player_key_callback, team_key_callback,
							key_list_callback, playerteam_count_callback, adderror_callback, userdata);
}

qr2_error_t qr2_create_socket(/*[out]*/SOCKET *sock, const char *ip, /*[in/out]*/int * port)
{
	struct sockaddr_in saddr;	
	SOCKET hbsock;
	int maxport;
	int lasterror = 0;
	int baseport = *port;

#if defined(_LINUX)
	unsigned int saddrlen;
#else
	int saddrlen;
#endif

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_create_socket()\r\n");

	SocketStartUp();
	
	hbsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (INVALID_SOCKET == hbsock)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
			"Failed to create heartbeat socket\r\n");
		return e_qrwsockerror;
	}
	
	maxport = baseport + NUM_PORTS_TO_TRY;
	while (baseport < maxport)
	{
		get_sockaddrin(ip,baseport,&saddr,NULL);
		if (saddr.sin_addr.s_addr == htonl(0x7F000001)) //localhost -- we don't want that!
			saddr.sin_addr.s_addr = INADDR_ANY;
		
		lasterror = bind(hbsock, (struct sockaddr *)&saddr, sizeof(saddr));
		if (lasterror == 0)
			break; //we found a port
		baseport++;
	}
	
	if (lasterror != 0) //we weren't able to find a port
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
			"Failed to bind() query socket\r\n");
		return e_qrbinderror;
	}
	
	if (baseport == 0) //we bound it dynamically
	{
		saddrlen = sizeof(saddr);

		lasterror = getsockname(hbsock,(struct sockaddr *)&saddr, &saddrlen);

		if (lasterror)
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
				"Query socket bind() success, but getsockname() failed\r\n");
			return e_qrbinderror;
		}
		baseport = ntohs(saddr.sin_port);
	}

	*sock = hbsock;
	*port = baseport;

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
		"Query socket created and bound to port %d\r\n", *port);

	return e_qrnoerror;
}

qr2_error_t qr2_initA(/*[out]*/qr2_t *qrec, const char *ip, int baseport, const char *gamename, const char *secret_key,
					 int ispublic, int natnegotiate,
					 qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback,
					 qr2_playerteamkeycallback_t team_key_callback,
					 qr2_keylistcallback_t key_list_callback,
					 qr2_countcallback_t playerteam_count_callback,
					 qr2_adderrorcallback_t adderror_callback,
					 void *userdata)
{
	SOCKET hbsock;
	qr2_error_t ret;

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_init()\r\n");

	ret = qr2_create_socket(&hbsock, ip, &baseport);
	if(ret != e_qrnoerror)
	{
		SocketShutDown();
		return ret;
	}

	ret = qr2_init_socketA(qrec, hbsock, baseport, gamename, secret_key, ispublic, natnegotiate, server_key_callback, player_key_callback, team_key_callback, key_list_callback, playerteam_count_callback, adderror_callback,  userdata);
	if (qrec == NULL)
		qrec = &current_rec;
	(*qrec)->read_socket = 1;
	
	return ret;
}

qr2_error_t qr2_initW(/*[out]*/qr2_t *qrec, const unsigned short *ip, int baseport, const unsigned short *gamename, const unsigned short *secret_key,
					 int ispublic, int natnegotiate,
					 qr2_serverkeycallback_t server_key_callback,
					 qr2_playerteamkeycallback_t player_key_callback,
					 qr2_playerteamkeycallback_t team_key_callback,
					 qr2_keylistcallback_t key_list_callback,
					 qr2_countcallback_t playerteam_count_callback,
					 qr2_adderrorcallback_t adderror_callback,
					 void *userdata)
{
	char ip_A[255];
	char gamename_A[255];
	char secretkey_A[255];

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_initW()\r\n");

	if (ip != NULL) // NULL value is valid for IP
		UCS2ToAsciiString(ip, ip_A);
	UCS2ToAsciiString(gamename, gamename_A);
	UCS2ToAsciiString(secret_key, secretkey_A);

	return qr2_initA(qrec, (ip!=NULL)?ip_A:NULL, baseport, gamename_A, secretkey_A, ispublic, natnegotiate, server_key_callback, player_key_callback, team_key_callback, key_list_callback, playerteam_count_callback, adderror_callback,  userdata);
}

void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback)
{
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_register_natneg_callback()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	qrec->nn_callback = nncallback;

}
void qr2_register_clientmessage_callback(qr2_t qrec, qr2_clientmessagecallback_t cmcallback)
{
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_register_clientmessage_callback()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	qrec->cm_callback = cmcallback;
}
void qr2_register_publicaddress_callback(qr2_t qrec, qr2_publicaddresscallback_t pacallback)
{
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_register_publicaddress_callback()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	qrec->pa_callback = pacallback;
}
void qr2_register_clientconnected_callback(qr2_t qrec, qr2_clientconnectedcallback_t cccallback)
{
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_register_clientconnected_callback()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	qrec->cc_callback = cccallback;
}


/* qr2_think: Processes any waiting queries, and sends a
heartbeat if needed  */
void qr2_think(qr2_t qrec)
{
	if (qrec == NULL)
		qrec = current_rec;
	if (qrec->ispublic)
		qr2_check_send_heartbeat(qrec);
	qr2_check_queries(qrec);
	qr2_expire_ip_verify(qrec);
	NNThink();
}

/* qr2_check_queries: Processes any waiting queries */
void qr2_check_queries(qr2_t qrec)
{
	static char indata[INBUF_LEN]; //256 byte input buffer
	struct sockaddr_in saddr;
	int error;

#if defined(_LINUX)
	unsigned int saddrlen = sizeof(struct sockaddr_in);
#else
	int saddrlen = sizeof(struct sockaddr_in);
#endif

	if (!qrec->read_socket)
		return; //not our job

	while(CanReceiveOnSocket(qrec->hbsock))
	{
		//else we have data
		error = (int)recvfrom(qrec->hbsock, indata, (INBUF_LEN - 1), 0, (struct sockaddr *)&saddr, &saddrlen);
		
		if (gsiSocketIsNotError(error))
		{
			indata[error] = '\0';

			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
				"Received %d bytes on query socket\r\n", error);
			gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
				indata, error);

			qr2_parse_queryA(qrec, indata, error, (struct sockaddr *)&saddr);
		}
		else if (error == 0)
		{
			// socket closed?
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
				"CanReceiveOnSocket() returned true, but recvfrom return 0!\r\n", error);
		}
		else
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
				"CanReceiveOnSocket() returned true, but recvfrom failed!\r\n", error);
		}
	}
}

/* check_send_heartbeat: Perform any scheduled outgoing
heartbeats */
void qr2_check_send_heartbeat(qr2_t qrec)
{
	gsi_time tc = current_time();

	if (INVALID_SOCKET == qrec->hbsock)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
			"HBSock is invalid\r\n");
		return; //no sockets to work with!
	}

	//check if we need to send a heartbet
	if (qrec->listed_state > 0 && tc - qrec->lastheartbeat > FIRST_HB_TIME) 
	{ //check to see if we haven't gotten a query yet
		if (qrec->listed_state >= MAX_FIRST_COUNT)
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError,
				"No response from master, generating NoChallengeResponse error\r\n");

			qrec->listed_state = 0; //we failed to get a challenge! let them know
#ifndef GSI_UNICODE
			qrec->adderror_callback(e_qrnochallengeerror, "No challenge value was received from the master server.", qrec->udata);
#else
			qrec->adderror_callback(e_qrnochallengeerror, L"No challenge value was received from the master server.", qrec->udata);
#endif
			return;
		} else
		{
			send_heartbeat(qrec, 3);
			qrec->listed_state++;
		}
	}
	else if (qrec->userstatechangerequested && (tc - qrec->lastheartbeat > MIN_STATECHANGED_HB_TIME))
		send_heartbeat(qrec,1);  // Send out pending statechange request
	else if (tc - qrec->lastheartbeat > HB_TIME || qrec->lastheartbeat == 0 || tc < qrec->lastheartbeat)
		send_heartbeat(qrec,0);  // Send out a normal hearbeat
	
	if (current_time() - qrec->lastka > KA_TIME) //send a keep alive (to keep NAT port mappings the same if possible)
		send_keepalive(qrec);
}

/* qr2_send_statechanged: Sends a statechanged heartbeat, call when
your gamemode changes */
void qr2_send_statechanged(qr2_t qrec)
{
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace,
		"qr2_send_statechanged()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	if (!qrec->ispublic)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Warning,
			"Requested send statechange for LAN game, discarding\r\n");
		return;
	}
	if (current_time() - qrec->lastheartbeat < MIN_STATECHANGED_HB_TIME)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice,
			"Queing statechange for later send (too soon)\r\n");

		// Queue up the statechange and send later
		qrec->userstatechangerequested = 1;
		return;  // don't allow the server to spam statechanges
	}

	send_heartbeat(qrec, 1);
	qrec->userstatechangerequested = 0; // clear the flag in case a queued statechange was still pending
}


/* qr2_shutdown: Cleans up the sockets and shuts down */
void qr2_shutdown(qr2_t qrec)
{
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace,
		"qr2_shutdown()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	if (qrec->ispublic)
		send_heartbeat(qrec, 2);
	if (INVALID_SOCKET != qrec->hbsock && qrec->read_socket) //if we own the socket
	{
		closesocket(qrec->hbsock);
	}
	qrec->hbsock = INVALID_SOCKET;
	qrec->lastheartbeat = 0;
	if(qrec->read_socket) //if we own the socket
	{
		SocketShutDown();
	}
	if (qrec != &static_qr2_rec) //need to gsifree it, it was dynamically allocated
	{
		gsifree(qrec);
	}

    // free ACE negotiate list
    NNFreeNegotiateList(); //comment out if ACE is not being used

	// BD: Removed - Peer SDK repeatedly calls qr2_shutdown, but
	//               keys should only be deallocated once.

	// Developers should call this manually (when in GSI_UNICODE mode)
	// qr2_internal_key_list_free();
}


gsi_bool qr2_keybuffer_add(qr2_keybuffer_t keybuffer, int keyid)
{
	// mj these are codetime not runtime errors, changing to assert
	if (keybuffer->numkeys >= MAX_REGISTERED_KEYS)
		return gsi_false;
	if (keyid < 1 || keyid > MAX_REGISTERED_KEYS)
		return gsi_false;

	keybuffer->keys[keybuffer->numkeys++] = (uchar)keyid;
	return gsi_true;
}

gsi_bool qr2_buffer_add_int(qr2_buffer_t outbuf, int value)
{
	char temp[20];
	sprintf(temp, "%d", value);
	return qr2_buffer_addA(outbuf, temp);
}

gsi_bool qr2_buffer_addA(qr2_buffer_t outbuf, const char *value)
{
	GS_ASSERT(outbuf)
	GS_ASSERT(value)
	{
		int copylen;
		copylen = (int)strlen(value) + 1;
		if (copylen > AVAILABLE_BUFFER_LEN(outbuf))
			copylen = AVAILABLE_BUFFER_LEN(outbuf); //max length we can fit in the buffer
		if (copylen <= 0)
			return gsi_false; //no space
		memcpy(outbuf->buffer + outbuf->len, value, (unsigned int)copylen);
		outbuf->len += copylen;
		outbuf->buffer[outbuf->len - 1] = 0; //make sure it's null terminated
		return gsi_true;
	}
}
#if defined(GSI_UNICODE)
gsi_bool qr2_buffer_addW(qr2_buffer_t outbuf, const unsigned short *value)
{
	char value_A[4096];
	UCS2ToUTF8String(value, value_A);
	return qr2_buffer_addA(outbuf, value_A);
}
#endif


static void enum_local_ips()
{
	struct hostent *phost;
	phost = getlocalhost();
	if (phost == NULL)
		return;
	for (num_local_ips = 0 ; num_local_ips < MAX_LOCAL_IP ; num_local_ips++)
	{
		if (phost->h_addr_list[num_local_ips] == 0)
			break;
		memcpy(&local_ip_list[num_local_ips], phost->h_addr_list[num_local_ips], sizeof(struct in_addr));
	}
}

/****************************************************************************/


/* Return a sockaddrin for the given host (numeric or DNS) and port)
Returns the hostent in savehent if it is not NULL */
static int get_sockaddrin(const char *host, int port, struct sockaddr_in *saddr, struct hostent **savehent)
{
	struct hostent *hent = NULL;

	saddr->sin_family = AF_INET;
	saddr->sin_port = htons((unsigned short)port);
	if (host == NULL)
		saddr->sin_addr.s_addr = INADDR_ANY;
	else
		saddr->sin_addr.s_addr = inet_addr(host);
	
	if (saddr->sin_addr.s_addr == INADDR_NONE && strcmp(host,"255.255.255.255") != 0)
	{
		hent = gethostbyname(host);
		if (!hent)
			return 0;
		saddr->sin_addr.s_addr = *(unsigned int *)hent->h_addr_list[0];
	}
	if (savehent != NULL)
		*savehent = hent;
	return 1;

} 



/*****************************************************************************/
/* Various encryption / encoding routines */

static void swap_byte ( uchar *a, uchar *b )
{
	uchar swapByte; 
	
	swapByte = *a; 
	*a = *b;      
	*b = swapByte;
}

static uchar encode_ct ( uchar c )
{
	if (c <  26) return (uchar)('A'+c);
	if (c <  52) return (uchar)('a'+c-26);
	if (c <  62) return (uchar)('0'+c-52);
	if (c == 62) return (uchar)('+');
	if (c == 63) return (uchar)('/');
	
	return 0;
}

static void gs_encode ( uchar *ins, int size, uchar *result )
{
	int    i,pos;
	uchar  trip[3];
	uchar  kwart[4];
	
	i=0;
	while (i < size)
	{
		for (pos=0 ; pos <= 2 ; pos++, i++)
			if (i < size) trip[pos] = *ins++;
			else trip[pos] = '\0';
			kwart[0] = (unsigned char)(  (trip[0])       >> 2);
			kwart[1] = (unsigned char)((((trip[0]) &  3) << 4) + ((trip[1]) >> 4));
			kwart[2] = (unsigned char)((((trip[1]) & 15) << 2) + ((trip[2]) >> 6));
			kwart[3] = (unsigned char)(  (trip[2]) & 63);
			for (pos=0; pos <= 3; pos++) *result++ = encode_ct(kwart[pos]);
	}
	*result='\0';
}

static void gs_encrypt ( uchar *key, int key_len, uchar *buffer_ptr, int buffer_len )
{ 
	short counter;     
	uchar x, y, xorIndex;
	uchar state[256];       
	
	for ( counter = 0; counter < 256; counter++) state[counter] = (uchar) counter;
	
	x = 0; y = 0;
	for ( counter = 0; counter < 256; counter++)
	{
		y = (uchar)((key[x] + state[counter] + y) % 256);
		x = (uchar)((x + 1) % key_len);
		swap_byte ( &state[counter], &state[y] );
	}
	
	x = 0; y = 0;
	for ( counter = 0; counter < buffer_len; counter ++)
	{
		x = (uchar)((x + buffer_ptr[counter] + 1) % 256);
		y = (uchar)((state[x] + y) % 256);
		swap_byte ( &state[x], &state[y] );
		xorIndex = (uchar)((state[x] + state[y]) % 256);
		buffer_ptr[counter] ^= state[xorIndex];
	}
}

/*****************************************************************************/
/* NAT Negotiation support */

static void NatNegProgressCallback(NegotiateState state, void *userdata)
{
	// we don't do anything here
	GSI_UNUSED(state);
	GSI_UNUSED(userdata);
}

static void NatNegCompletedCallback(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata)
{
	qr2_t qrec = (qr2_t)userdata;

	if(qrec->cc_callback)
	{
		if(result == nr_success)
		{
			qrec->cc_callback(gamesocket, remoteaddr, qrec->udata);
		}
	}
}

/*****************************************************************************/


static void qr_add_packet_header(qr2_buffer_t buf, char ptype, char *reqkey)
{
	buf->buffer[0] = ptype;
	memcpy(buf->buffer + 1, reqkey, REQUEST_KEY_LEN);
	buf->len = REQUEST_KEY_LEN  + 1;
}

#define MAX_CHALLENGE 64
static void compute_challenge_response(qr2_t qrec, qr2_buffer_t buf, char *challenge, int challengelen)
{
	char encrypted_val[MAX_CHALLENGE + 1]; //don't need to null terminate
	
	if(challengelen < 1)
		return; // invalid, need room for the NUL
	if (challengelen > (MAX_CHALLENGE + 1))
		return; //invalid
	if (challenge[challengelen - 1] != 0)
		return; //invalid - must be NTS
	
	strcpy(encrypted_val, challenge);
	gs_encrypt((uchar *)qrec->secret_key, (int)strlen(qrec->secret_key), (uchar *)encrypted_val, challengelen - 1);
	gs_encode((uchar *)encrypted_val,challengelen - 1, (uchar *)(buf->buffer + buf->len));
	buf->len += (int)strlen(buf->buffer + buf->len) + 1;
	
}

static void handle_public_address(qr2_t qrec, char * buffer)
{
	unsigned int ip;
	unsigned int portTemp;
	unsigned short port;

	// get the public ip and port as the master server sees it
	sscanf(buffer, "%08X%04X", &ip, &portTemp);
	port = (unsigned short)portTemp;
	ip = htonl(ip);

	// sanity check
	if((ip == 0) || (port == 0))
		return;

#ifdef GSI_COMMON_DEBUG
	{
		IN_ADDR addr;
		addr.s_addr = ip;
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Received public address (%s:%d)\r\n", inet_ntoa(addr), port);
	}
#endif

	// has anything changed?
	if((qrec->publicip != ip) || (qrec->publicport != port))
	{
		qrec->publicip = ip;
		qrec->publicport = port;
		qrec->pa_callback(ip, port, qrec->udata);
	}
}

static void qr_build_partial_query_reply(qr2_t qrec, qr2_buffer_t buf, qr2_key_type keytype, int keycount, uchar *keys)
{
	struct qr2_keybuffer_s kb;
	int playerteamcount;
	unsigned short cttemp;
	int i;
	int pindex;
	const char *k;
	int len;

	kb.numkeys = 0;
	if (keycount == 0)
		return; //no keys wanted
	
	if (keytype == key_player || keytype == key_team) //need to add the player/team counts
	{
		if (AVAILABLE_BUFFER_LEN(buf) < sizeof(cttemp))
			return; //no more space
		playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata);
		cttemp = htons((unsigned short)playerteamcount);
		memcpy(buf->buffer + buf->len, &cttemp, sizeof(cttemp));
		buf->len += sizeof(cttemp);
	} else
		playerteamcount = 1;

	if (keycount == 0xFF) //need to get the list of keys
	{
		qrec->key_list_callback(keytype, &kb, qrec->udata);
		//add all the keys
		for (i = 0 ; i < kb.numkeys ; i++)
		{
			k = qr2_registered_key_list[kb.keys[i]];
			if (k == NULL)
				k = "unknown";
			qr2_buffer_addA(buf, k);
			if (keytype == key_server) //add the server values
			{
				len = buf->len;
				qrec->server_key_callback(kb.keys[i], buf, qrec->udata);
				if(len == buf->len)
					qr2_buffer_addA(buf, "");
			}
		}
		//add an extra null
		if (AVAILABLE_BUFFER_LEN(buf) < 1)
			return; //no space
		buf->buffer[buf->len++] = 0;
		keycount = kb.numkeys;
		keys = kb.keys;
		if (keytype == key_server)
			return;	//already added the keys
	}
	for (pindex = 0 ; pindex < playerteamcount ; pindex++)
	{
		for (i = 0 ; i < keycount ; i++)
		{
			len = buf->len;
			if (keytype == key_server) //add the server keys
				qrec->server_key_callback(keys[i], buf, qrec->udata);
			else if (keytype == key_player)
				qrec->player_key_callback(keys[i], pindex, buf, qrec->udata);
			else if (keytype == key_team)
				qrec->team_key_callback(keys[i], pindex, buf, qrec->udata);
			if(len == buf->len)
				qr2_buffer_addA(buf, "");
		}
	}		
}

static void qr_build_query_reply(qr2_t qrec, qr2_buffer_t buf, int serverkeycount, uchar *serverkeys, int playerkeycount, uchar *playerkeys, int teamkeycount, uchar *teamkeys)
{
	qr_build_partial_query_reply(qrec, buf, key_server, serverkeycount, serverkeys);
	qr_build_partial_query_reply(qrec, buf, key_player, playerkeycount, playerkeys);
	qr_build_partial_query_reply(qrec, buf, key_team, teamkeycount, teamkeys);
}


struct QRSplitQueryProgress
{
	qr2_key_type mCurKeyType;
	int mCurPacketNum;
	int mCurKeyIndex;		// serverkey index, playerkey index, teamkey index
	int mCurSubCount;       // number of players or number of teams
	int mCurSubIndex;		// current player num or current team num
	struct qr2_keybuffer_s mKeyBuffer; // keybuffer, for key name indexing
};


// return values:
//     gsi_true = send buffer, then call this function again
//     gsi_false = don't send buffer, don't call this function again
static gsi_bool qr_build_split_query_reply(qr2_t qrec, qr2_buffer_t buf, struct QRSplitQueryProgress* progress)
{
	unsigned char* packetNumPos = NULL; // Used to store the byte position of the packet number

	// Make sure the key type is valid
	//    (The key type is set to invalid when all keys have been processed.)
	//if (progress->mCurKeyType < 0 ||progress->mCurKeyType >= key_type_count)
	if (progress->mCurKeyType >= key_type_count)
		return gsi_false; // stop processing

	// check buffer space 
	//    (buffer should only contain header at this point)
	if (AVAILABLE_BUFFER_LEN(buf) < 32)
		return gsi_false; // no space?

	// Dump the split packet "header"
	qr2_buffer_addA(buf, "splitnum");
	packetNumPos = (unsigned char*)&buf->buffer[buf->len++];
	*packetNumPos = (gsi_u8)progress->mCurPacketNum++;

	// Resume dumping at key_type level
	while (progress->mCurKeyType < key_type_count)
	{
		// Get the list of keys if we don't have it already
		if (progress->mKeyBuffer.numkeys == 0)
			qrec->key_list_callback(progress->mCurKeyType, &progress->mKeyBuffer, qrec->udata);

		// Get the list of players/teams if we don't have it already
		if (progress->mCurSubCount == 0 && progress->mCurKeyType != key_server)
			progress->mCurSubCount = qrec->playerteam_count_callback(progress->mCurKeyType, qrec->udata);

		// check buffer space
		if (AVAILABLE_BUFFER_LEN(buf) < 100)
			return gsi_true; //no space

		// Write the key type
		buf->buffer[buf->len++] = (char)progress->mCurKeyType;

		// For each key
		while(progress->mCurKeyIndex < progress->mKeyBuffer.numkeys)
		{
			// check buffer space
			int aRegisteredKeyIndex = progress->mKeyBuffer.keys[progress->mCurKeyIndex];
			const char* aKeyName = qr2_registered_key_list[aRegisteredKeyIndex];

			// Write the key name
			if (gsi_is_false( qr2_buffer_addA(buf,aKeyName) ))
				return gsi_true; // send, then try again

			if (progress->mCurKeyType == key_server)
			{
				// write the key value
				qrec->server_key_callback(aRegisteredKeyIndex, buf, qrec->udata);

				// make sure the key was written
				if (AVAILABLE_BUFFER_LEN(buf) < 1)
					return gsi_true; //ran out of space! retry this key/value next packet
			}
			else
			{
				if (AVAILABLE_BUFFER_LEN(buf) < 1)
					return gsi_true; //ran out of space, retry this key/value next packet

				// Non-split packets implicitly being with player/team number zero,
				//    split packet explicitly specify the starting number
				buf->buffer[buf->len++] = (char)progress->mCurSubIndex;

				// For each player/team
				while(progress->mCurSubIndex < progress->mCurSubCount)
				{
					// dump the value into the buffer
					if (progress->mCurKeyType == key_player)
						qrec->player_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, buf, qrec->udata);
					else if (progress->mCurKeyType == key_team)
						qrec->team_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, buf, qrec->udata);

					// make sure the key was written
					if (AVAILABLE_BUFFER_LEN(buf) < 1)
						return gsi_true; //ran out of space, try again next packet
				
					// move onto the next player/team value
					progress->mCurSubIndex++;
				}
				// append a null to signify end of this team/player key
				if (AVAILABLE_BUFFER_LEN(buf) > 0)
					buf->buffer[buf->len++] = '\0';
			}
			// move onto next key
			progress->mCurKeyIndex++;
			progress->mCurSubIndex = 0;
		}

		// append a null to signify end of this key_type section
		if (AVAILABLE_BUFFER_LEN(buf) > 0)
			buf->buffer[buf->len++] = '\0';

		// Move onto next key type
		progress->mCurKeyType = (qr2_key_type)(progress->mCurKeyType + 1);
		progress->mCurKeyIndex = 0;
		progress->mCurSubCount = 0;
		progress->mCurSubIndex = 0;
		progress->mKeyBuffer.numkeys = 0;
	}

	// Add the "final" flag to the packet number
	*packetNumPos |= QR2_SPLITNUM_FINALFLAG;
	return gsi_true; // function will bail without sending next iteration
}

static void qr_process_query(qr2_t qrec, qr2_buffer_t buf, uchar *qdata, int len, struct sockaddr* sender)
{
	uchar serverkeycount;
	uchar playerkeycount;
	uchar teamkeycount;
	uchar exflags = 0;

	uchar *serverkeys = NULL;
	uchar *playerkeys = NULL;
	uchar *teamkeys = NULL;
	if (len < 3)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
			"Discarding invalid query (too short#1: %d bytes total)\r\n", len);
		return; //invalid
	}
	serverkeycount = qdata[0];
	qdata++;
	len--;
	if (serverkeycount != 0 && serverkeycount != 0xFF)
	{
		serverkeys = qdata;
		qdata += serverkeycount;
		len -= serverkeycount;
	}
	if (len < 2)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
			"Discarding invalid query (too short#2: %d bytes remain)\r\n", len);
		return; //invalid
	}
	playerkeycount = qdata[0];
	qdata++;
	len--;
	if (playerkeycount != 0 && playerkeycount != 0xFF)
	{
		playerkeys = qdata;
		qdata += playerkeycount;
		len -= playerkeycount;
	}
	if (len < 1)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
			"Discarding invalid query (too short#3: %d bytes remain)\r\n", len);
		return; //invalid
	}
	teamkeycount = qdata[0];
	qdata++;
	len--;
	if (teamkeycount != 0 && teamkeycount != 0xFF)
	{
		teamkeys = qdata;
		qdata += teamkeycount;
		len -= teamkeycount;
	}
	if (len < 0)
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
			"Discarding invalid query (too short#4: %d bytes remain)\r\n", len);
		return; //invalid
	}

	// check the exflags
	if (len > 0)
	{
		exflags = qdata[0];
		len--;
	}
	
	// Support split queries?
	if ((exflags & QR2_EXFLAG_SPLIT)==QR2_EXFLAG_SPLIT)
	{
		struct QRSplitQueryProgress progress;
		progress.mCurPacketNum = 0;
		progress.mCurKeyType = key_server;
		progress.mCurKeyIndex = 0;
		progress.mCurSubCount = 0;
		progress.mCurSubIndex = 0;
		progress.mKeyBuffer.numkeys = 0;
			
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
			"Building query reply (split packet supported)\r\n");

		// Send packets as long as we need to
		while (gsi_true == qr_build_split_query_reply(qrec, buf, &progress))
		{
			sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, sizeof(struct sockaddr_in));
			buf->len = 5; // reset buffer but preserve 5-byte qr2 header
			if (progress.mCurPacketNum > QR2_SPLITNUM_MAX)
				return; // more than 7 isn't supported (likely a bug if you hit it)
		}

		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
				"Finished split query reply (%d packets)\r\n", progress.mCurPacketNum);
	}
	else
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
			"Building query reply (single packet)\r\n");
		qr_build_query_reply(qrec, buf, serverkeycount, serverkeys, playerkeycount, playerkeys, teamkeycount, teamkeys);
		sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, sizeof(struct sockaddr_in));
	}

	GSI_UNUSED(sender);
}

/*
static void qr_build_partial_old_query_reply(qr2_t qrec, qr2_buffer_t buf, qr2_key_type keytype)
{
	char tempkeyname[128];
	struct qr2_keybuffer_s kb;
	int playerteamcount;
	int i;
	int pindex;
	const char *k;
	int len;

	kb.numkeys = 0;

	if (keytype == key_player || keytype == key_team) //need to add the player/team counts
	{
		playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata);
	} else
		playerteamcount = 1;

	qrec->key_list_callback(keytype, &kb, qrec->udata);
	//add all the keys
	for (i = 0 ; i < kb.numkeys ; i++)
	{
		k = qr2_registered_key_list[kb.keys[i]];
		if (k == NULL)
			k = "unknown";
		if (keytype == key_server) //add the server values
		{
			qr2_buffer_addA(buf, k);
			buf->buffer[buf->len - 1] = '\\';
			len = buf->len;
			qrec->server_key_callback(kb.keys[i], buf, qrec->udata);
			if(len == buf->len)
				qr2_buffer_addA(buf, "");
			buf->buffer[buf->len - 1] = '\\';
		} else //need to look it up for each player/team
		{

			for (pindex = 0 ; pindex < playerteamcount ; pindex++)
			{
				sprintf(tempkeyname, "%s%d", k, pindex);
				qr2_buffer_addA(buf, tempkeyname);
				buf->buffer[buf->len - 1] = '\\';
				len = buf->len;
				if (keytype == key_player)
					qrec->player_key_callback(kb.keys[i], pindex, buf, qrec->udata);
				else if (keytype == key_team)
					qrec->team_key_callback(kb.keys[i], pindex, buf, qrec->udata);
				if(len == buf->len)
					qr2_buffer_addA(buf, "");
				buf->buffer[buf->len - 1] = '\\';
			}		
		}
	}
}
*/

//we just build a status reply, since we don't have equivalent callbacks
/*static void qr_process_old_query(qr2_t qrec, qr2_buffer_t buf)
{
	buf->len = 1;
	buf->buffer[0] = '\\';

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Comment,
		"Processing QR1 style query\r\n");

	qr_build_partial_old_query_reply(qrec, buf, key_server);
	qr_build_partial_old_query_reply(qrec, buf, key_player);
	qr_build_partial_old_query_reply(qrec, buf, key_team);
	qr2_buffer_addA(buf, "final\\\\queryid\\1.1");
	buf->len--; //remove the final null;
}*/

static void qr_process_client_message(qr2_t qrec, char *buf, int len)
{
	unsigned char natNegBytes[NATNEG_MAGIC_LEN] = {NN_MAGIC_0,NN_MAGIC_1,NN_MAGIC_2,NN_MAGIC_3,NN_MAGIC_4,NN_MAGIC_5};
	int i;
	int isnatneg = 1;

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice,
		"Processing client message\r\n");

	//see if it's a natneg request..
	if (len >= NATNEG_MAGIC_LEN + 4)
	{
		for (i = 0 ; i < NATNEG_MAGIC_LEN ; i++)
			if ((unsigned char)buf[i] != natNegBytes[i])
			{
				isnatneg = 0;
				break;
			}
	} else
		isnatneg = 0;
	if (isnatneg)
	{
		int cookie;
		memcpy(&cookie, buf + NATNEG_MAGIC_LEN, 4);
		cookie = (int)ntohl((unsigned int)cookie);

		// check if we should do natneg
		if(qrec->cc_callback)
		{
			NegotiateError error;

			if(__GSIACResult != GSIACAvailable)
			{
				// fake that we passed the availability check
				__GSIACResult = GSIACAvailable;
				strcpy(__GSIACGamename, qrec->gamename);
			}

			// do the negotiation
			error = NNBeginNegotiationWithSocket(qrec->hbsock, cookie, 1, NatNegProgressCallback, NatNegCompletedCallback, qrec);
			if(error != ne_noerror)
			{
				// we can ignore errors
			}
		}
		else if (qrec->nn_callback)
		{
			qrec->nn_callback(cookie, qrec->udata); 
		}
	} else
		if (qrec->cm_callback)
		{
#ifndef GSI_UNICODE
			qrec->cm_callback(buf, len, qrec->udata);
#else
			unsigned short* buf_W;
			buf_W = UTF8ToUCS2StringAlloc(buf);
			qrec->cm_callback(buf_W, wcslen(buf_W), qrec->udata);
#endif
		}
}

static int qr_got_recent_message(qr2_t qrec, int msgkey)
{
	int i;
	for (i = 0 ; i < RECENT_CLIENT_MESSAGES_TO_TRACK ; i++)
	{
		if (qrec->client_message_keys[i] == msgkey)
			return 1;
	}
	//else, add it to the list
	qrec->cur_message_key = (qrec->cur_message_key + 1) % RECENT_CLIENT_MESSAGES_TO_TRACK;
	qrec->client_message_keys[qrec->cur_message_key] = msgkey;
	return 0;
}

// Send a random value to the user, to verify IP address
static gsi_bool qr2_process_ip_verify(qr2_t qrec, struct qr2_buffer_s* buf, struct sockaddr_in* sender)
{
	int i=0;
	gsi_time now = current_time();
	int firstFreeIndex = -1;
	int numDuplicates = 0;

	// if the query challenge is disabled, return 0 as the challenge
	if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == 0)
	{
		qr2_buffer_add_int(buf, 0);
		return gsi_true;
	}

	// check if this ip/port combo is already in the list
	for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++)
	{
		// Mark the first free index when/if found
		if (firstFreeIndex == -1 && qrec->ipverify[i].addr.sin_addr.s_addr == 0)
			firstFreeIndex = i;
		// Count any indexes that match this IP/port
		if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr &&
			qrec->ipverify[i].addr.sin_port == sender->sin_port)
		{
			numDuplicates++;
		}
	}

	// discard if too many duplicates or no index found
	if (numDuplicates > QR2_IPVERIFY_MAXDUPLICATES)
		return gsi_false;
	if (firstFreeIndex == -1)
		return gsi_false; // no free indexes

	
	// create a random challenge for this ip/port combo
	qrec->ipverify[firstFreeIndex].addr = *sender;
	qrec->ipverify[firstFreeIndex].challenge = htonl( (rand() << 16) | rand() );
	qrec->ipverify[firstFreeIndex].createtime = now;

	qr2_buffer_add_int(buf, (int)qrec->ipverify[firstFreeIndex].challenge);
	return gsi_true; // buffer ready to be sent
}

// Check if the returned ipverify value matches the random we sent earlier
//  If it matches, remove it
static gsi_bool qr2_check_ip_verify(qr2_t qrec, struct sockaddr_in* sender, gsi_u32 ipverify)
{
	int i=0;
	for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++)
	{
		if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr &&
			qrec->ipverify[i].addr.sin_port == sender->sin_port)
		{
			if (qrec->ipverify[i].challenge == ipverify)
			{
				// reset structure
				qrec->ipverify[i].addr.sin_addr.s_addr = 0;
				qrec->ipverify[i].addr.sin_port = 0;
				return gsi_true;
			}
			//else
			//   keep searching...a single IP may have multiple outstanding challenges.
		}
	}
	return gsi_false;
}

// Expire old verify attempts
static void qr2_expire_ip_verify(qr2_t qrec)
{
	int i=0;
	gsi_time now = current_time();

	for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++)
	{
		if (qrec->ipverify[i].addr.sin_addr.s_addr != 0 && (now - qrec->ipverify[i].createtime > QR2_IPVERIFY_TIMEOUT))
			qrec->ipverify[i].addr.sin_addr.s_addr = 0;
	}
}

/* parse_query: parse an incoming query and reply to each query */
void qr2_parse_queryA(qr2_t qrec, char *query, int len, struct sockaddr *sender)
{
	struct qr2_buffer_s buf;
	char ptype;
	char *reqkey;
	char *pos;
	gsi_u32 ipverify = 0;
	int i;
	
	buf.len = 0;

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace,
		"qr2_parse_queryA()\r\n");

	if (qrec == NULL)
		qrec = current_rec;
	if (query[0] == 0x3B) /* a cdkey query */
	{
		if (qrec->cdkeyprocess != NULL)
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
				"Forwarding cdkey query onto cdkey sdk\r\n");
			qrec->cdkeyprocess(query, len, sender);
		}
		else
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
				"Received cdkey query but not using qr2-cdkey integration!\r\n");
		}
		return;
	}
	/*if (query[0] == '\\') //it's a QR1-style query
	{
		qr_process_old_query(qrec, &buf);
		sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, sizeof(struct sockaddr_in));
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
			"Sent %d bytes as QR1 query response\r\n", buf.len);
		gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
			buf.buffer, buf.len);

		return;
	}*/
	if((len >= 6) && (memcmp(query, NNMagicData, NATNEG_MAGIC_LEN) == 0)) /* a natneg query */
	{
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Forwarding natneg query onto natneg sdk\r\n");
		NNProcessData(query, len, (struct sockaddr_in*)sender);
		return;
	}

	if (len < 7)
		return; //too small to be valid
	//check the magic...
	if ((uchar)query[0] != QR_MAGIC_1 || (uchar)query[1] != QR_MAGIC_2)
		return;

//#define SIMULATE_HARD_FIREWALL
#if defined(SIMULATE_HARD_FIREWALL)
	// ignore all QR2 packets on this port
	// generates "no challenge" error
	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning,
			"SIMULATE_HARD_FIREWALL defined, ignoring QR2 packet\r\n");
	return;
#endif

//#define SIMULATE_FIREWALL
#if defined(SIMULATE_FIREWALL) 
	// ignore messages that are not from the gamespy master
	{
		unsigned int aMasterAddr = (cr->hbaddr.sin_addr);
		if (((SOCKADDR_IN*)sender)->sin_addr.s_addr != aMasterAddr)
		{
			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Warning,
				"SIMULATE_FIREWALL defined, ignoring non-master QR2 packet\r\n");
			return;
		}
	}
#endif

	if (qrec->listed_state > 0)
		qrec->listed_state = 0;

	ptype = query[2];
	reqkey = &query[3];
	pos = query + 7;
	len -= 7;

	qr_add_packet_header(&buf, ptype, reqkey);
	switch (ptype)
	{
	case PACKET_PREQUERY_IP_VERIFY:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Received IP verify challenge request\r\n");
		if (gsi_is_true(qr2_process_ip_verify(qrec, &buf, (struct sockaddr_in*)sender)))
			break; // break so that we send below
		else
			return; // otherwise return and discard buf


	case PACKET_QUERY:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Processing query packet\r\n");

		// When using query challenge option, verify that the client sent a PREQUERY_IP_VERIFY
		if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == QR2_OPTION_USE_QUERY_CHALLENGE)
		{
			if (len < 4)
				return; // too small for an ip-verify query

			ipverify = ntohl(*(gsi_u32*)pos);
			pos += 4;
			len -= 4;

			// Has this client verified their IP? (prevent IP spoofing)
			if (gsi_is_false(qr2_check_ip_verify(qrec, (struct sockaddr_in*)sender, ipverify)))
			{
				// Don't send an error.  As nice as the debug info would be, 
				// the incompatible SBs will interpret it as a server response.
				return;
			}
		}
		
		// qr_process_query now sends packets
		qr_process_query(qrec, &buf, (uchar *)pos, len, sender);
		return;
	case PACKET_CHALLENGE:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Processing challenge packet\r\n");

		// Check the instance key (to prove this packet came from the master
		for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
		{
			if (reqkey[i] != qrec->instance_key[i])
				return; //not a valid instance key
		}

		//calculate the challenge
		if (len >= (PUBLIC_ADDR_LEN + 3))
		{
			unsigned int backendoptions;

			// read options, then public address
			sscanf(pos + len - (PUBLIC_ADDR_LEN + 3), "%02x", &backendoptions);
			qrec->backendoptions = (gsi_u8)backendoptions;
			
			#ifdef QR2_DEBUG_FORCE_USE_QUERY_CHALLENGE
			qrec->backendoptions = QR2_OPTION_USE_QUERY_CHALLENGE;
			#endif

			gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice,
				"Received setting options: %d\r\n", qrec->backendoptions);

			if(qrec->pa_callback)
				handle_public_address(qrec, pos + len - (PUBLIC_ADDR_LEN + 1));
			else
			{
				gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
					"Discarding public address (no callback set)\r\n");
			}
		}
		compute_challenge_response(qrec, &buf, pos, len);
		break;
	case PACKET_ECHO:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Processing echo packet\r\n");

		//now add the echo data
		if (len > 32)
			len = 32; //max 32 bytes
		buf.buffer[0] = PACKET_ECHO_RESPONSE;
		memcpy(buf.buffer + buf.len, pos, (size_t)len);
		buf.len += len;
		break;

	case PACKET_ADDERROR:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError,
			"Processing adderror packet\r\n");

		if (qrec->listed_state == -1)
			return; //we already got an error message
		//verify the instance code
		for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
		{
			if (reqkey[i] != qrec->instance_key[i])
				return; //not a valid instance key
		}
		if (len < 2)
			return; //not a valid message
		qrec->listed_state = -1;
#ifndef GSI_UNICODE
		qrec->adderror_callback((qr2_error_t)*pos, pos + 1, qrec->udata);
#else
		{
			unsigned short* message_W;
			message_W = UTF8ToUCS2StringAlloc(pos+1);
			qrec->adderror_callback((qr2_error_t)*pos, message_W, qrec->udata);
			gsifree(message_W);
		}
#endif
		return; //we don't need to send anything back for this type of message
	case PACKET_CLIENT_MESSAGE:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice,
			"Processing clientmessage packet\r\n");

		//verify the instance code
		for (i = 0 ; i < REQUEST_KEY_LEN ; i++)
		{
			if (reqkey[i] != qrec->instance_key[i])
				return; //not a valid instance key
		}
		if (len < 4) //no message key?
			return;
		buf.buffer[0] = PACKET_CLIENT_MESSAGE_ACK;
		//add the msg key
		memcpy(buf.buffer + buf.len, pos, (size_t)4);
		buf.len += 4;
		//see if we've recently gotten this same message, to help avoid dupes
		memcpy(&i, pos, (size_t)4);
		if (!qr_got_recent_message(qrec, i))
			qr_process_client_message(qrec, pos + 4, len - 4);
		//send an ack response
		break;
	case PACKET_KEEPALIVE:
		gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
			"Processing keepalive packet\r\n");
		return; //if we get a keep alive, ignore it and return (just used to tell us the server knows about us)
	default:
		return; //not valid type

	}
	//send the reply
	sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, sizeof(struct sockaddr_in));

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
		"Sent %d bytes as QR2 query response\r\n", buf.len);
	gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
		buf.buffer, buf.len);
}

/* NOT necessary since qr2_parse_query uses char not unsigned short
#if defined(GSI_UNICODE)
void qr2_parse_queryW(qr2_t qrec, unsigned short *query, int len, struct sockaddr *sender)
{
	char query_A[4096];
	UCS2ToUTF8String(query, query_A);
	qr2_parse_queryA(qrec, query_A, len, sender);
}
#endif*/


/* send_keepalive: Send a keepalive packet to the hbmaster3 */
static void send_keepalive(qr2_t qrec)
{
	struct qr2_buffer_s buf;
	buf.len = 0;
	qr_add_packet_header(&buf, PACKET_KEEPALIVE, qrec->instance_key);
	sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in));

	//set the ka time to now
	qrec->lastka = current_time();

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
		"Sent keepalive to master\r\n", buf);
}

/* send_heartbeat: Sends a heartbeat to the gamemaster,
adds \statechanged\ if statechanged != 0 */
static void send_heartbeat(qr2_t qrec, int statechanged)
{
	struct qr2_buffer_s buf;
	//int ret;
	int i;
	char ipkey[20];

	buf.len = 0;
	qr_add_packet_header(&buf, PACKET_HEARTBEAT, qrec->instance_key);
	//now we add our special keys
	for (i = 0 ; i < num_local_ips ; i++)
	{
		sprintf(ipkey, "localip%d", i);
		qr2_buffer_addA(&buf, ipkey);
		qr2_buffer_addA(&buf, inet_ntoa(local_ip_list[i]));
	}
	qr2_buffer_addA(&buf, "localport");
	qr2_buffer_add_int(&buf, qrec->qport);
	qr2_buffer_addA(&buf, "natneg");
	qr2_buffer_addA(&buf, qrec->nat_negotiate ? "1" : "0");
	if(statechanged)
	{
		qr2_buffer_addA(&buf, "statechanged");
		qr2_buffer_add_int(&buf, statechanged);
	}
	qr2_buffer_addA(&buf, "gamename");
	qr2_buffer_addA(&buf, qrec->gamename);
	if(qrec->pa_callback)
	{
		qr2_buffer_addA(&buf, "publicip");
		qr2_buffer_add_int(&buf, (int)qrec->publicip);
		qr2_buffer_addA(&buf, "publicport");
		qr2_buffer_add_int(&buf, qrec->publicport);
	}
	
	//add the rest of our keys
	if (statechanged != 2) //don't need if we are exiting
	{
		// The hbmaster will crap out if the packet is malformed
		//   which might happen if the buffer isn't large enough
		// So first copy dump the keys into a temporary buffer
		struct qr2_buffer_s temp;
		memcpy(temp.buffer, buf.buffer, (size_t)buf.len);
		temp.len = buf.len;
		qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0xFF, NULL, 0xFF, NULL);

		// If we maxxed out the packet, try again using only the server keys
		if(AVAILABLE_BUFFER_LEN(&temp) < 1)
		{
			temp.len = buf.len;
			qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0, NULL, 0, NULL);
		}
		// copy temp back into buffer
		memcpy(buf.buffer, temp.buffer, (size_t)temp.len);
		buf.len = temp.len;
	}	
	else
	{
		// PANTS - 2002.6.28
		// add an extra NUL to end the server keys
		if (AVAILABLE_BUFFER_LEN(&buf) >= 1)
			buf.buffer[buf.len++] = 0;
	}

	//ret = (int)sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in));
	sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr *)&(qrec->hbaddr), sizeof(struct sockaddr_in));

	//set the ka time and hb time to now
	qrec->lastka = qrec->lastheartbeat = current_time();

	// clear the pending heartbeat request flag
	if (statechanged != 0)
		qrec->userstatechangerequested = 0;

	gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment,
		"Sent heartbeat to master (size %d)\r\n", buf.len);
	gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump,
		buf.buffer, buf.len);
}

#ifdef __cplusplus
}
#endif


