/*
gpiBuddy.c
GameSpy Presence SDK 
Dan "Mr. Pants" Schoenblum

Copyright 1999-2007 GameSpy Industries, Inc

devsupport@gamespy.com

***********************************************************************
Please see the GameSpy Presence SDK documentation for more information
**********************************************************************/

//INCLUDES
//////////
#include <stdlib.h>
#include <string.h>
#include "gpi.h"

//FUNCTIONS
///////////
static GPResult
gpiSendAuthBuddyRequest(
  GPConnection * connection,
  GPIProfile * profile
)
{
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Send the auth.
	/////////////////
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\authadd\\");
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\fromprofileid\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile->profileId);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sig\\");
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, profile->authSig);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");

	return GP_NO_ERROR;
}

GPResult
gpiProcessRecvBuddyMessage(
  GPConnection * connection,
  const char * input
)
{
	char buffer[4096];
	int type;
	int profileid;
	time_t date;
	GPICallback callback;
	GPIProfile * profile;
	GPIBuddyStatus * buddyStatus;
	char intValue[16];
	char * str;
	unsigned short port;
	int productID;
	GPIConnection * iconnection = (GPIConnection*)*connection;
	char strTemp[max(GP_STATUS_STRING_LEN, GP_LOCATION_STRING_LEN)];

	// Check the type of bm.
	////////////////////////
	if(!gpiValueForKey(input, "\\bm\\", buffer, sizeof(buffer)))
		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	type = atoi(buffer);

	// Get the profile this is from.
	////////////////////////////////
	if(!gpiValueForKey(input, "\\f\\", buffer, sizeof(buffer)))
		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	profileid = atoi(buffer);

	// Get the time.
	////////////////
	if(!gpiValueForKey(input, "\\date\\", buffer, sizeof(buffer)))
		date = time(NULL);
	else
		date = atoi(buffer);

	// What type of message is this?
	////////////////////////////////
	switch(type)
	{
	case GPI_BM_MESSAGE:
		// Call the callback.
		/////////////////////
		callback = iconnection->callbacks[GPI_RECV_BUDDY_MESSAGE];
		if(callback.callback != NULL)
		{
			GPRecvBuddyMessageArg * arg;
			arg = (GPRecvBuddyMessageArg *)gsimalloc(sizeof(GPRecvBuddyMessageArg));
			if(arg == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");

			if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer)))
				CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
#ifndef GSI_UNICODE
			arg->message = (char *)gsimalloc(strlen(buffer) + 1);
			if(arg->message == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			strcpy(arg->message, buffer);
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;
#else
			arg->message = (unsigned short*)gsimalloc(strlen(buffer)*2+2);
			if(arg->message == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			UTF8ToUCS2String(buffer, arg->message);
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;
#endif
			CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE));
		}
		break;
	case GPI_BM_UTM:
		// Call the callback.
		/////////////////////
		callback = iconnection->callbacks[GPI_RECV_BUDDY_UTM];
		if(callback.callback != NULL)
		{
			GPRecvBuddyUTMArg * arg;
			arg = (GPRecvBuddyUTMArg *)gsimalloc(sizeof(GPRecvBuddyUTMArg));
			if(arg == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");

			if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer)))
				CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
#ifndef GSI_UNICODE
			arg->message = (char *)gsimalloc(strlen(buffer) + 1);
			if(arg->message == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			strcpy(arg->message, buffer);
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;
#else
			arg->message = (unsigned short*)gsimalloc(strlen(buffer)*2+2);
			if(arg->message == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			UTF8ToUCS2String(buffer, arg->message);
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;
#endif
			CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYUTM));
		}
		break;

	case GPI_BM_REQUEST:
		// Get the profile, adding if needed.
		/////////////////////////////////////
		profile = gpiProfileListAdd(connection, profileid);
		if(!profile)
			Error(connection, GP_MEMORY_ERROR, "Out of memory.");

		// Get the reason.
		//////////////////
		if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer)))
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		// Find where the sig starts.
		/////////////////////////////
		str = strstr(buffer, "|signed|");
		if(str == NULL)
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		// Get the sig out of the message.
		//////////////////////////////////
		*str = '\0';
		str += 8;
		if(strlen(str) != 32)
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
		freeclear(profile->authSig);
		profile->authSig = goastrdup(str);
		profile->requestCount++;

		// Call the callback.
		/////////////////////
		callback = iconnection->callbacks[GPI_RECV_BUDDY_REQUEST];
		if(callback.callback != NULL)
		{
			GPRecvBuddyRequestArg * arg;
			arg = (GPRecvBuddyRequestArg *)gsimalloc(sizeof(GPRecvBuddyRequestArg));
			if(arg == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
#ifndef GSI_UNICODE
			strzcpy(arg->reason, buffer, GP_REASON_LEN);
#else
			UTF8ToUCS2String(buffer, arg->reason);
#endif
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;

			CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDDYREQUEST));
		}
		break;

	case GPI_BM_AUTH:
		// call the callback
		callback = iconnection->callbacks[GPI_RECV_BUDDY_AUTH];
		if(callback.callback != NULL)
		{
			GPRecvBuddyAuthArg * arg;
			arg = (GPRecvBuddyAuthArg *)gsimalloc(sizeof(GPRecvBuddyAuthArg));

			if (arg == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;
			CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYAUTH));
		}
		break;

	case GPI_BM_REVOKE:
		// call the callback
		callback = iconnection->callbacks[GPI_RECV_BUDDY_REVOKE];
		if(callback.callback != NULL)
		{
			GPRecvBuddyRevokeArg * arg;
			arg = (GPRecvBuddyRevokeArg *)gsimalloc(sizeof(GPRecvBuddyRevokeArg));

			if (arg == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			arg->profile = (GPProfile)profileid;
			arg->date = (unsigned int)date;

			CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYREVOKE));
		}
		break;
		

	case GPI_BM_STATUS:
		// Get the profile, adding if needed.
		/////////////////////////////////////
		profile = gpiProfileListAdd(connection, profileid);
		if(!profile)
			Error(connection, GP_MEMORY_ERROR, "Out of memory.");

        // Make sure profile wasn't blocked prior to getting the status update
        //////////////////////////////////////////////////////////////////////
        if (!profile->blocked)
        {
		    // This is a buddy.
		    ///////////////////
		    if(!profile->buddyStatus)
		    {
			    profile->buddyStatus = (GPIBuddyStatus *)gsimalloc(sizeof(GPIBuddyStatus));
			    if(!profile->buddyStatus)
				    Error(connection, GP_MEMORY_ERROR, "Out of memory.");
			    memset(profile->buddyStatus, 0, sizeof(GPIBuddyStatus));
			    if (profile->buddyStatusInfo)
			    {
				    profile->buddyStatus->buddyIndex = profile->buddyStatusInfo->buddyIndex;
				    gpiRemoveBuddyStatusInfo(profile->buddyStatusInfo);
				    profile->buddyStatusInfo = NULL;
			    }
			    else
				    profile->buddyStatus->buddyIndex = iconnection->profileList.numBuddies++;
		    }

		    // Get the buddy status.
		    ////////////////////////
		    buddyStatus = profile->buddyStatus;

		    // Get the msg.
		    ///////////////
		    if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer)))
			    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		    // Get the status.
		    //////////////////
		    if(!gpiValueForKey(buffer, "|s|", intValue, sizeof(intValue)))
		    {
			    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
		    }
		    else
		    {
			    buddyStatus->status = (GPEnum)atoi(intValue);
		    }
		    // Get the status string.
		    /////////////////////////
		    freeclear(buddyStatus->statusString);
		    if(!gpiValueForKey(buffer, "|ss|", strTemp, GP_STATUS_STRING_LEN))
			    strTemp[0] = '\0';
		    buddyStatus->statusString = goastrdup(strTemp);
		    if(!buddyStatus->statusString)
			    Error(connection, GP_MEMORY_ERROR, "Out of memory.");

		    // Get the location string.
		    ///////////////////////////
		    freeclear(buddyStatus->locationString);
		    if(!gpiValueForKey(buffer, "|ls|", strTemp, GP_LOCATION_STRING_LEN))
			    strTemp[0] = '\0';
		    buddyStatus->locationString = goastrdup(strTemp);
		    if(!buddyStatus->locationString)
			    Error(connection, GP_MEMORY_ERROR, "Out of memory.");

		    // Get the ip.
		    //////////////
		    if(!gpiValueForKey(buffer, "|ip|", intValue, sizeof(intValue)))
			    buddyStatus->ip = 0;
		    else
			    buddyStatus->ip = htonl((unsigned int)atoi(intValue));

		    // Get the port.
		    ////////////////
		    if(!gpiValueForKey(buffer, "|p|", intValue, sizeof(intValue)))
			    buddyStatus->port = 0;
		    else
		    {
			    port = (unsigned short)atoi(intValue);
			    buddyStatus->port = htons(port);
		    }

		    // Get the quiet mode flags.
		    ////////////////////////////
		    if(!gpiValueForKey(buffer, "|qm|", intValue, sizeof(intValue)))
			    buddyStatus->quietModeFlags = GP_SILENCE_NONE;
		    else
			    buddyStatus->quietModeFlags = (GPEnum)atoi(intValue);

		    // Call the callback.
		    /////////////////////
		    callback = iconnection->callbacks[GPI_RECV_BUDDY_STATUS];
		    if(callback.callback != NULL)		
		    {
			    GPRecvBuddyStatusArg * arg;
			    arg = (GPRecvBuddyStatusArg *)gsimalloc(sizeof(GPRecvBuddyStatusArg));
			    if(arg == NULL)
				    Error(connection, GP_MEMORY_ERROR, "Out of memory.");

			    arg->profile = (GPProfile)profileid;
			    arg->index = buddyStatus->buddyIndex;
			    arg->date = (unsigned int)date;

			    CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_STATUS));
		    }
        }
		break;

	case GPI_BM_INVITE:
		// Get the msg.
		///////////////
		if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer)))
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		// Find the productid.
		//////////////////////
		str = strstr(buffer, "|p|");
		if(str == NULL)
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		// Skip the |p|.
		////////////////
		str += 3;
		if(str[0] == '\0')
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		// Get the productid.
		/////////////////////
		productID = atoi(str);

		// Find the location string (optional - older versions won't have)
		str = strstr(buffer, "|l|");
		if(str != NULL)
			strzcpy(strTemp, (str+3), sizeof(strTemp));
		else
			strTemp[0] = '\0'; // no location, set to empty string
		
		// Call the callback.
		/////////////////////
		callback = iconnection->callbacks[GPI_RECV_GAME_INVITE];
		if(callback.callback != NULL)
		{
			GPRecvGameInviteArg * arg;
			arg = (GPRecvGameInviteArg *)gsimalloc(sizeof(GPRecvGameInviteArg));
			if(arg == NULL)
				Error(connection, GP_MEMORY_ERROR, "Out of memory.");

			arg->profile = (GPProfile)profileid;
			arg->productID = productID;
#ifdef GSI_UNICODE
			AsciiToUCS2String(strTemp, arg->location);
#else
			strcpy(arg->location, strTemp);
#endif

			CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, 0));
		}
		break;

	case GPI_BM_PING:
		// Get the msg.
		///////////////
		if(!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer)))
			CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

		// Send back a pong.
		////////////////////
		gpiSendBuddyMessage(connection, profileid, GPI_BM_PONG, "1", 0, NULL);

		break;

#ifndef NOFILE
	case GPI_BM_PONG:
		// Lets the transfers handle this.
		//////////////////////////////////
		gpiTransfersHandlePong(connection, profileid, NULL);

		break;
#endif
	}

	return GP_NO_ERROR;
}

GPResult gpiProcessRecvBuddyStatusInfo(GPConnection *connection, const char *input)
{
	char buffer[1024];
	int profileid;
	time_t date;
	GPICallback callback;
	GPIProfile * profile;
	GPIBuddyStatusInfo * buddyStatusInfo;
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// This is what the message should look like.  Its broken up for easy viewing.
	//
	// "\bsi\\state\\profile\\bip\\bport\\hostip\\hprivip\"
	// "\qport\\hport\\sessflags\\rstatus\\gameType\"
	// "\gameVnt\\gameMn\\product\\qmodeflags\"
	////////////////////////////////
	date = time(NULL);
	// Get the buddy's profile
	////////////////////////////////
	if(!gpiValueForKey(input, "\\profile\\", buffer, sizeof(buffer)))
		CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	profileid = atoi(buffer);

	// Get the profile from the SDK's list, adding it if needed.
	/////////////////////////////////////
	profile = gpiProfileListAdd(connection, profileid);
	if(!profile)
		Error(connection, GP_MEMORY_ERROR, "Out of memory.");

    // Make sure profile wasn't blocked prior to getting the status update
    //////////////////////////////////////////////////////////////////////
    if (!profile->blocked)
    {
	    // This is a buddy.
	    ///////////////////
	    if(!profile->buddyStatusInfo)
	    {
		    profile->buddyStatusInfo = (GPIBuddyStatusInfo *)gsimalloc(sizeof(GPIBuddyStatusInfo));
		    if(!profile->buddyStatusInfo)
			    Error(connection, GP_MEMORY_ERROR, "Out of memory.");
		    memset(profile->buddyStatusInfo, 0, sizeof(GPIBuddyStatusInfo));
		    if (profile->buddyStatus)
		    {
			    profile->buddyStatusInfo->buddyIndex = profile->buddyStatus->buddyIndex;
			    gpiRemoveBuddyStatus(profile->buddyStatus);
			    profile->buddyStatus = NULL;
		    }
		    else
			    profile->buddyStatusInfo->buddyIndex = iconnection->profileList.numBuddies++;
		    profile->buddyStatusInfo->extendedInfoKeys = ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree);
		    if (!profile->buddyStatusInfo->extendedInfoKeys)
			    Error(connection, GP_MEMORY_ERROR, "Out of memory.");
	    }

	    // extract the buddy status information and 
	    // fill in appropriate information.
	    /////////////////////////////////////////////
	    buddyStatusInfo = profile->buddyStatusInfo;
    	
	    if (!gpiValueForKey(input, "\\state\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->statusState = (GPEnum)atoi(buffer);

	    if (!gpiValueForKey(input, "\\bip\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->buddyIp = htonl((unsigned int)atoi(buffer));
    	
	    if (!gpiValueForKey(input, "\\bport\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->buddyPort = (unsigned short)atoi(buffer);
    	
	    if (!gpiValueForKey(input, "\\hostip\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->hostIp = htonl((unsigned int)atoi(buffer));

	    if (!gpiValueForKey(input, "\\hprivip\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->hostPrivateIp = htonl((unsigned int)atoi(buffer));

	    if (!gpiValueForKey(input, "\\qport\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->queryPort = (unsigned short)atoi(buffer);

	    if (!gpiValueForKey(input, "\\hport\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->hostPort = (unsigned short)atoi(buffer);
    	
	    if (!gpiValueForKey(input, "\\sessflags\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->sessionFlags = (unsigned int)atoi(buffer);

	    freeclear(buddyStatusInfo->richStatus);
	    if (!gpiValueForKey(input, "\\rstatus\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->richStatus = goastrdup(buffer);
    	
	    freeclear(buddyStatusInfo->gameType);
	    if (!gpiValueForKey(input, "\\gameType\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->gameType = goastrdup(buffer);

	    freeclear(buddyStatusInfo->gameVariant);
	    if (!gpiValueForKey(input, "\\gameVnt\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->gameVariant = goastrdup(buffer);

	    freeclear(buddyStatusInfo->gameMapName);
	    if (!gpiValueForKey(input, "\\gameMn\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->gameMapName = goastrdup(buffer);

	    if (!gpiValueForKey(input, "\\product\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->productId = (int)atoi(buffer);

	    if (!gpiValueForKey(input, "\\qmodeflags\\", buffer, sizeof(buffer)))
		    CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
	    buddyStatusInfo->quietModeFlags = (GPEnum)atoi(buffer);
    	
	    callback = iconnection->callbacks[GPI_RECV_BUDDY_STATUS];
	    if (callback.callback != NULL)
	    {
		    GPRecvBuddyStatusArg *anArg;
		    anArg = (GPRecvBuddyStatusArg *)gsimalloc(sizeof(GPRecvBuddyStatusArg));
		    if (anArg == NULL)
			    Error(connection, GP_MEMORY_ERROR, "Out of memory.");

		    anArg->date = (unsigned int)date;
		    anArg->index = buddyStatusInfo->buddyIndex;
		    anArg->profile = profileid;

		    CHECK_RESULT(gpiAddCallback(connection, callback, anArg, NULL, 0));
	    }
    }
	return GP_NO_ERROR;
	
}

GPResult
gpiProcessRecvBuddyList(
  GPConnection * connection,
  const char * input
)
{
    int i=0, j=0;
    int num = 0;
    int index = 0;
    char c;
    char *str = NULL;
    char buffer[512];
    GPIProfile * profile;
    GPProfile profileid;
    GPIConnection * iconnection = (GPIConnection*)*connection;

    // Check for an error.
    //////////////////////
    if(gpiCheckForError(connection, input, GPITrue))
        return GP_SERVER_ERROR;

    // Process Buddy List Retrieval msg - Format like:
    /* ===============================================
       \bdy\<num in list>\list\<block list - comma delimited>\final\
       =============================================== */

    if(!gpiValueForKeyWithIndex(input, "\\bdy\\", &index, buffer, sizeof(buffer)))
        CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
    num = atoi(buffer);

    // Check to make sure list is there 
    ///////////////////////////////////
    str = strstr(input, "\\list\\");
    if (str == NULL)
        CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");

    // Then increment index to get ready for parsing
    ////////////////////////////////////////////////
    str += 6;
    index += 6;

    for (i=0; i < num; i++)
    {     
        if (i==0)
        {
            // Manually grab first profile in list - comma delimiter
            ////////////////////////////////////////////////////////
            for(j=0 ; (j < sizeof(buffer)) && ((c = str[j]) != '\0') && (c != ',') ; j++)
            {
                buffer[j] = c;
            }
            buffer[j] = '\0';
            index += j;
        }
        else
        {
            if(!gpiValueForKeyWithIndex(input, ",", &index, buffer, sizeof(buffer)))
                CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Unexpected data was received from the server.");
        }

        profileid = atoi(buffer);

        // Get the profile, adding if needed.
        /////////////////////////////////////
        profile = gpiProfileListAdd(connection, profileid);
        if(!profile)
            Error(connection, GP_MEMORY_ERROR, "Out of memory.");

        // Mark as offline buddy for now until we get the real status
        /////////////////////////////////////////////////////////////
#ifdef GP_NEW_STATUS_INFO
        // Use new status info as placeholder
        profile->buddyStatusInfo = (GPIBuddyStatusInfo *)gsimalloc(sizeof(GPIBuddyStatusInfo));
        if(!profile->buddyStatusInfo)
            Error(connection, GP_MEMORY_ERROR, "Out of memory.");
        memset(profile->buddyStatusInfo, 0, sizeof(GPIBuddyStatusInfo));

        profile->buddyStatusInfo->extendedInfoKeys = ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree);
        if (!profile->buddyStatusInfo->extendedInfoKeys)
            Error(connection, GP_MEMORY_ERROR, "Out of memory.");

        profile->buddyStatusInfo->buddyIndex = iconnection->profileList.numBuddies++;
        profile->buddyStatusInfo->statusState = GP_OFFLINE; 
#else
        // Use buddy status as placeholder
        profile->buddyStatus = (GPIBuddyStatus *)gsimalloc(sizeof(GPIBuddyStatus));
        if(!profile->buddyStatus)
            Error(connection, GP_MEMORY_ERROR, "Out of memory.");
        memset(profile->buddyStatus, 0, sizeof(GPIBuddyStatus));
        profile->buddyStatus->buddyIndex = iconnection->profileList.numBuddies++;
        profile->buddyStatus->status = GP_OFFLINE; 
#endif
    }

    return GP_NO_ERROR;
}

GPResult
gpiSendServerBuddyMessage(
  GPConnection * connection,
  int profileid,
  int type,
  const char * message
)
{
	char buffer[3501];
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Copy the message into an internal buffer.
	////////////////////////////////////////////
	strzcpy(buffer, message, sizeof(buffer));

	// Setup the message.
	/////////////////////
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\bm\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, type);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\t\\");
	gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\msg\\");
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, buffer);
	gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");

	return GP_NO_ERROR;
}

GPResult
gpiSendBuddyMessage(
  GPConnection * connection,
  int profileid,
  int type,
  const char * message,
  int sendOption,
  GPIPeerOp *peerOp
)
{
	GPIPeer * peer;
	GPIProfile * profile;
	//GPIConnection *iconnection = (GPIConnection *)*connection;
	peer = gpiGetPeerByProfile(connection, profileid);
	if(!peer)
	{
		// Check if we should send this through the server.
		////////////////////////////////////////////////////
		if(!gpiGetProfile(connection, profileid, &profile) ||
		   (!profile->buddyStatusInfo || !profile->buddyStatusInfo->buddyPort))
		{
			if (sendOption == GP_DONT_ROUTE)
				return GP_NETWORK_ERROR;
			return gpiSendServerBuddyMessage(connection, profileid, type, message);
		}

		// Create a new peer connection for this message.
		/////////////////////////////////////////////////
		peer = gpiAddPeer(connection, profileid, GPITrue);
		if(!peer)
			return GP_MEMORY_ERROR;

		// Check if we need a sig.
		//////////////////////////
		if(!profile->peerSig)
		{
			// Get the sig.
			///////////////
			CHECK_RESULT(gpiPeerGetSig(connection, peer));
		}
		else
		{
			// Try to connect to the peer.
			//////////////////////////////
			CHECK_RESULT(gpiPeerStartConnect(connection, peer));
		}
	}
	else if (peer->state == GPI_PEER_DISCONNECTED)
	{
		if (gpiGetProfile(connection, profileid, &profile))
		{
			// clear the buddy port to prevent future messages from 
			// being sent via UDP layer
			if (profile->buddyStatusInfo)
				profile->buddyStatusInfo->buddyPort = 0;

			// send the message through the server
			if (sendOption == GP_DONT_ROUTE)
				return GP_NETWORK_ERROR;
			if (type < 100)
				return gpiSendServerBuddyMessage(connection, profileid, type, message);
		}
	}
	
	if (peerOp)
	{
		gpiPeerAddOp(peer, peerOp);
	}
	// Copy the message.
	////////////////////
	CHECK_RESULT(gpiPeerAddMessage(connection, peer, type, message));

	return GP_NO_ERROR;
}

GPResult gpiBuddyHandleKeyRequest(GPConnection *connection, GPIPeer *peer)
{
	char *message;
	
	// get all the keys and put them in the message part of bm 
	//////////////////////////////////////////////////////////
	CHECK_RESULT(gpiSaveKeysToBuffer(connection, &message));
	
	// Done in case we haven't set any keys
	if (message == NULL)
		message = "";
	
	CHECK_RESULT(gpiSendBuddyMessage(connection, peer->profile, GPI_BM_KEYS_REPLY, message, GP_DONT_ROUTE, NULL));
	
	if (strcmp(message, "")!= 0)
		freeclear(message);
	return GP_NO_ERROR;
}

GPResult gpiBuddyHandleKeyReply(GPConnection *connection, GPIPeer *peer, char *buffer)
{
	GPIProfile *pProfile;
		
	// Get the profile object to store the keys internally
	//////////////////////////////////////////////////////
	
	if(!gpiGetProfile(connection, peer->profile, &pProfile))
		Error(connection, GP_PARAMETER_ERROR, "Invalid profile.");
	
	// calculate the B64Decoded string len
	if (strcmp(buffer, "") == 0)
	{
		GPIPeerOp *anIterator;
		
		for (anIterator = peer->peerOpQueue.first; anIterator != NULL; anIterator = anIterator->next)
			if (anIterator->type == GPI_BM_KEYS_REQUEST)
				break;
		
		if (!anIterator)
		{
			return GP_NO_ERROR;
		}
		else if (anIterator->type == GPI_BM_KEYS_REQUEST && anIterator->callback)
		{
			GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg));
			GPICallback callback;
			callback.callback = anIterator->callback;
			callback.param = anIterator->userData;
			
			arg->keys = NULL;
			arg->numKeys = 0;
			arg->values = NULL;
			arg->profile = peer->profile;
			gpiAddCallback(connection, callback, arg, NULL, 0);
			gpiPeerRemoveOp(peer, anIterator);
		}
	}
	else
	{
		int decodedLen = 0,
			index = 0, numKeys, i;
		char keyName[512];
		char keyVal[512];
		char decodeKey[512];
		char decodeVal[512];
		gsi_char **keys;
		gsi_char **values;
		GPIPeerOp *anIterator;
		char *checkKey = NULL;

		// start by getting the number of keys 
		gpiReadKeyAndValue(connection, buffer, &index,  keyName, keyVal);
		
		// do not continue further if the header is missing
		if (strcmp(keyName, "keys") != 0)
			CallbackError(connection, GP_NETWORK_ERROR, GP_PARSE, "Error reading keys reply message");
		
		numKeys = atoi(keyVal);

		if (numKeys == 0)
		{
			GPIPeerOp *anIterator;
			
			for (anIterator = peer->peerOpQueue.first; anIterator != NULL; anIterator = anIterator->next)
				if (anIterator->type == GPI_BM_KEYS_REQUEST)
					break;

			if (!anIterator)
			{
				return GP_NO_ERROR;
			}
			else if (anIterator->type == GPI_BM_KEYS_REQUEST && anIterator->callback)
			{
				GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg));
				GPICallback callback;
				callback.callback = anIterator->callback;
				callback.param = anIterator->userData;

				arg->keys = NULL;
				arg->numKeys = 0;
				arg->values = NULL;
				arg->profile = peer->profile;
				gpiAddCallback(connection, callback, arg, NULL, 0);
				gpiPeerRemoveOp(peer, anIterator);
			}
		}
		else
		{
			keys = (gsi_char **)gsimalloc(sizeof(gsi_char *) * numKeys);
			values = (gsi_char **)gsimalloc(sizeof(gsi_char *) * numKeys);

			for (i = 0; i < numKeys; i++)
			{			
				gpiReadKeyAndValue(connection, buffer, &index,  keyName, keyVal);
				B64Decode(keyName, decodeKey, (int)strlen(keyName), &decodedLen, 2);
				decodeKey[decodedLen] = '\0';
				B64Decode(keyVal, decodeVal, (int)strlen(keyVal), &decodedLen, 2);
				decodeVal[decodedLen] = '\0';
	#ifdef GSI_UNICODE
				keys[i] = UTF8ToUCS2StringAlloc(decodeKey);
				values[i]= UTF8ToUCS2StringAlloc(decodeVal);
	#else
				keys[i] = goastrdup(decodeKey);
				values[i] = goastrdup(decodeVal);
	#endif

				if (gpiStatusInfoCheckKey(connection, pProfile->buddyStatusInfo->extendedInfoKeys, decodeKey, &checkKey) == GP_NO_ERROR
					&& checkKey == NULL)
				{
					gpiStatusInfoAddKey(connection, pProfile->buddyStatusInfo->extendedInfoKeys, decodeKey, decodeVal);
				}
				else
				{
					gpiStatusInfoSetKey(connection, pProfile->buddyStatusInfo->extendedInfoKeys, decodeKey, decodeVal);
				}
			}
			
			for (anIterator = peer->peerOpQueue.first; anIterator != NULL; anIterator = anIterator->next)
				if (anIterator->type == GPI_BM_KEYS_REQUEST)
					break;

			if (!anIterator)
			{
				return GP_NO_ERROR;
			}
			else if (anIterator->type == GPI_BM_KEYS_REQUEST && anIterator->callback)
			{
				GPICallback callback;
				GPGetBuddyStatusInfoKeysArg *arg = (GPGetBuddyStatusInfoKeysArg *)gsimalloc(sizeof(GPGetBuddyStatusInfoKeysArg));
				
				callback.callback = anIterator->callback;
				callback.param = anIterator->userData;
				
				// allocate a key array that points to each extended info key for that player
				arg->numKeys = numKeys;

				arg->keys = keys;
				arg->values = values;
				arg->profile = peer->profile;
				
				gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYKEYS);
				gpiPeerRemoveOp(peer, anIterator);
			}
		}
	}
	
	return GP_NO_ERROR;
}

GPResult gpiAuthBuddyRequest
( 
	GPConnection * connection,
	GPProfile profile
)
{
	GPIProfile * pProfile;
	GPIConnection * iconnection = (GPIConnection*)*connection;

	// Get the profile object.
	//////////////////////////
	if(!gpiGetProfile(connection, profile, &pProfile))
		Error(connection, GP_PARAMETER_ERROR, "Invalid profile.");

	// Check for a valid sig.
	/////////////////////////
	if(!pProfile->authSig)
		Error(connection, GP_PARAMETER_ERROR, "Invalid profile.");

	// Send the request.
	////////////////////
	CHECK_RESULT(gpiSendAuthBuddyRequest(connection, pProfile));

	// freeclear the sig if no more requests.
	////////////////////////////////////
	pProfile->requestCount--;
	if(!iconnection->infoCaching && (pProfile->requestCount <= 0))
	{
		freeclear(pProfile->authSig);
		if(gpiCanFreeProfile(pProfile))
			gpiRemoveProfile(connection, pProfile);
	}

	return GP_NO_ERROR;
}

GPIBool
gpiFixBuddyIndices(
  GPConnection * connection,
  GPIProfile * profile,
  void * data
)
{
#ifndef _PS2
	int baseIndex = (int)(unsigned long)data;
#else
	int baseIndex = (int)data;
#endif

	GSI_UNUSED(connection);

	if(profile->buddyStatus && (profile->buddyStatus->buddyIndex > baseIndex))
		profile->buddyStatus->buddyIndex--;
	else if (profile->buddyStatusInfo && profile->buddyStatusInfo->buddyIndex > baseIndex)
		profile->buddyStatusInfo->buddyIndex--;
	return GPITrue;
}

GPResult
gpiDeleteBuddy(
  GPConnection * connection,
  GPProfile profile,
  GPIBool sendServerRequest
)
{
	GPIProfile * pProfile;
	GPIConnection * iconnection = (GPIConnection*)*connection;
	int index;

	// Get the profile object.
	//////////////////////////
	if(!gpiGetProfile(connection, profile, &pProfile))
		Error(connection, GP_PARAMETER_ERROR, "Invalid profile.");

	// Check that this is a buddy.
	//////////////////////////////
	// Removed - 092404 BED - User could be a buddy even though we don't have the status
	//if(!pProfile->buddyStatus)
	//	Error(connection, GP_PARAMETER_ERROR, "Profile not a buddy.");

	// Send the request.
	////////////////////
	if (GPITrue == sendServerRequest)
	{
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\delbuddy\\");
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sesskey\\");
		gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, iconnection->sessKey);
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\delprofileid\\");
		gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, pProfile->profileId);
		gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\");
	}

	// Need to fix up the buddy indexes.
	////////////////////////////////////
	if (pProfile->buddyStatus)
	{
		index = pProfile->buddyStatus->buddyIndex;
		assert(index >= 0);
		freeclear(pProfile->buddyStatus->statusString);
		freeclear(pProfile->buddyStatus->locationString);
		freeclear(pProfile->buddyStatus);
		if(gpiCanFreeProfile(pProfile))
			gpiRemoveProfile(connection, pProfile);
		iconnection->profileList.numBuddies--;
		assert(iconnection->profileList.numBuddies >= 0);
#ifndef _PS2
		gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index);
#else
		gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index);
#endif
	}
	if (pProfile->buddyStatusInfo)
	{
		index = pProfile->buddyStatusInfo->buddyIndex;
		assert(index >= 0);
		freeclear(pProfile->buddyStatusInfo->richStatus);
		freeclear(pProfile->buddyStatusInfo->gameType);
		freeclear(pProfile->buddyStatusInfo->gameVariant);
		freeclear(pProfile->buddyStatusInfo->gameMapName);
		freeclear(pProfile->buddyStatusInfo);
		if (pProfile->buddyStatusInfo->extendedInfoKeys)
		{
			ArrayFree(pProfile->buddyStatusInfo->extendedInfoKeys);
			pProfile->buddyStatusInfo->extendedInfoKeys = NULL;
		}

		if(gpiCanFreeProfile(pProfile))
			gpiRemoveProfile(connection, pProfile);
		iconnection->profileList.numBuddies--;
		assert(iconnection->profileList.numBuddies >= 0);
#ifndef _PS2
		gpiProfileMap(connection, gpiFixBuddyIndices, (void *)(unsigned long)index);
#else
		gpiProfileMap(connection, gpiFixBuddyIndices, (void *)index);
#endif
	}
	return GP_NO_ERROR;
}
