openmohaa/code/gamespy/Peer/peerPing.c

1119 lines
22 KiB
C
Raw Permalink Normal View History

2023-02-04 21:00:01 +01:00
/*
GameSpy Peer SDK
Dan "Mr. Pants" Schoenblum
dan@gamespy.com
Copyright 1999-2007 GameSpy Industries, Inc
devsupport@gamespy.com
*/
/*************
** INCLUDES **
*************/
#include <stdlib.h>
#include <stdio.h>
#include "peerPlayers.h"
#include "peerPing.h"
#include "peerGlobalCallbacks.h"
#include "peerCallbacks.h"
#include "peerMangle.h"
/************
** DEFINES **
************/
#define PI_PING_TIMEOUT 5000
#define PI_PINGS_PER_SEC 25
#define PI_PING_INTERVAL (1000 / PI_PINGS_PER_SEC)
#define PI_XPING_INTERVAL 2000
#define PI_XPING_PLAYER_INTERVAL 5000
#define PI_PINGER_PORT 13139
#define PI_MAX_PING_PLAYERS 12
#define PI_XPING_NUM_BUCKETS 32
#define PI_DONT_PING_FLAGS (PEER_FLAG_PLAYING | PEER_FLAG_AWAY)
#define PI_MAX_XPING_NUM_PLAYERS 32
#define PEER_CONNECTION_DATA piConnection * connection;\
assert(data);\
assert(data->peer);\
connection = (piConnection *)data->peer;\
GSI_UNUSED(connection);
/**********
** TYPES **
**********/
typedef struct piXping
{
char nicks[2][PI_NICK_MAX_LEN];
int ping;
} piXping;
/**************
** FUNCTIONS **
**************/
static int piXpingTableHashFn
(
const void *param,
int numBuckets
)
{
piXping * xping = (piXping *)param;
int i;
int c;
const char * str;
unsigned int hash = 0;
const char * nicks[2];
assert(xping);
assert(xping->nicks[0][0]);
assert(xping->nicks[1][0]);
nicks[0] = xping->nicks[0];
nicks[1] = xping->nicks[1];
// Reverse the order if the second is smaller.
//////////////////////////////////////////////
if(strcmp(nicks[1], nicks[0]) < 0)
{
const char * temp = nicks[0];
nicks[0] = nicks[1];
nicks[1] = temp;
}
// Get the hash.
////////////////
for(i = 0 ; i < 2 ; i++)
{
str = nicks[i];
while((c = *str++) != '\0')
hash += (unsigned int)tolower(c);
hash %= (unsigned int)numBuckets;
}
return (int)hash;
}
static int GS_STATIC_CALLBACK piXpingTableCompareFn
(
const void *param1,
const void *param2
)
{
piXping * xping1 = (piXping *)param1;
piXping * xping2 = (piXping *)param2;
int i;
int rcode;
const char * nicks[2][2];
assert(xping1);
assert(xping1->nicks[0][0]);
assert(xping1->nicks[1][0]);
assert(xping2);
assert(xping2->nicks[0][0]);
assert(xping2->nicks[1][0]);
nicks[0][0] = xping1->nicks[0];
nicks[0][1] = xping1->nicks[1];
nicks[1][0] = xping2->nicks[0];
nicks[1][1] = xping2->nicks[1];
// Reverse the order if the second is smaller.
//////////////////////////////////////////////
for(i = 0 ; i < 2 ; i++)
{
if(strcmp(nicks[i][1], nicks[i][0]) < 0)
{
const char * temp = nicks[i][0];
nicks[i][0] = nicks[i][1];
nicks[i][1] = temp;
}
}
for(i = 0 ; i < 2 ; i++)
{
rcode = strcasecmp(nicks[0][i], nicks[1][i]);
if(rcode != 0)
return rcode;
}
return 0;
}
static void piXpingTableElementFreeFn
(
void *param
)
{
piXping * xping = (piXping *)param;
assert(xping);
assert(xping->nicks[0][0]);
assert(xping->nicks[1][0]);
GSI_UNUSED(xping);
}
static void piProcessPing
(
PEER peer,
piPlayer * player,
int ping
)
{
int i;
int total;
//PEER_CONNECTION;
// One more returned.
/////////////////////
player->pingsReturned++;
player->pingsLostConsecutive = 0;
// We have one more ping for this player.
/////////////////////////////////////////
player->numPings++;
//Update last ping received.
////////////////////////////
player->lastPingRecv = current_time();
// Update the ping history.
///////////////////////////
if(player->pingHistoryNum > 0)
memmove(player->pingHistory + 1, player->pingHistory, min(player->pingHistoryNum, PI_PING_HISTORY_LEN - 1) * sizeof(int));
player->pingHistory[0] = ping;
if(player->pingHistoryNum < PI_PING_HISTORY_LEN)
player->pingHistoryNum++;
// Recompute the ping average.
//////////////////////////////
total = 0;
for(i = 0 ; i < player->pingHistoryNum ; i++)
total += player->pingHistory[i];
player->pingAverage = (total / player->pingHistoryNum);
// Add a ping callback.
///////////////////////
piAddPingCallback(peer, player->nick, ping);
// New ping, no xping yet.
//////////////////////////
player->xpingSent = PEERFalse;
// If this is a one-timer, stop pinging.
////////////////////////////////////////
if(player->pingOnce)
player->pingOnce = PEERFalse;
}
static void piPinged
(
unsigned int IP,
unsigned short port,
int ping,
const char * data,
int len,
PEER peer
)
{
piPlayer * player;
// Find the player.
///////////////////
player = piFindPlayerByIP(peer, IP);
if(!player)
return;
// Process the ping.
////////////////////
piProcessPing(peer, player, ping);
GSI_UNUSED(port);
GSI_UNUSED(data);
GSI_UNUSED(len);
}
PEERBool piPingInit
(
PEER peer
)
{
static PEERBool noPings[NumRooms];
PEER_CONNECTION;
// If there are no ping rooms, skip this.
/////////////////////////////////////////
if(memcmp(connection->pingRoom, noPings, sizeof(noPings)) == 0)
return PEERTrue;
// Init the xping table.
////////////////////////
connection->xpings = TableNew(sizeof(piXping), PI_XPING_NUM_BUCKETS, piXpingTableHashFn, piXpingTableCompareFn, piXpingTableElementFreeFn);
if(!connection->xpings)
return PEERFalse;
// Init pinger.
///////////////
if(!pingerInit(NULL, PI_PINGER_PORT, piPinged, peer, NULL, NULL))
return PEERFalse;
// No pings yet.
////////////////
connection->lastPingTimeMod = 0;
connection->lastXpingSend = 0;
// Init the random number generator.
////////////////////////////////////
srand(current_time());
// We're doing pings.
/////////////////////
connection->doPings = PEERTrue;
return PEERTrue;
}
void piPingCleanup
(
PEER peer
)
{
PEER_CONNECTION;
// Nothing to do if we weren't pinging.
///////////////////////////////////////
if(!connection->doPings)
return;
// If we're staying in the title room, get out.
///////////////////////////////////////////////
if(connection->stayInTitleRoom)
return;
// Clear timing stuff.
//////////////////////
connection->lastPingTimeMod = 0;
connection->lastXpingSend = 0;
// gsifree the xping table.
////////////////////////
if(connection->xpings)
TableFree(connection->xpings);
connection->xpings = NULL;
// Cleanup pinger.
//////////////////
pingerShutdown();
// Not doing pings.
///////////////////
connection->doPings = PEERFalse;
}
typedef struct piPingerReplyData
{
PEER peer;
unsigned int IP;
int ping;
} piPingerReplyData;
static void piPingerReplyMapFn
(
void *elem,
void *clientdata
)
{
piPlayer * player = (piPlayer *)elem;
piPingerReplyData * data = (piPingerReplyData *)clientdata;
PEER_CONNECTION_DATA;
assert(player);
// Check if waiting for a ping.
///////////////////////////////
if(!player->waitingForPing)
return;
// Check the IP.
////////////////
if(!player->gotIPAndProfileID || (player->IP != data->IP))
return;
// Not waiting anymore.
///////////////////////
player->waitingForPing = PEERFalse;
// Check timeout.
/////////////////
if(data->ping == PINGER_TIMEOUT)
{
// One more lost.
/////////////////
player->pingsLostConsecutive++;
// If a one-timer drops three in a row, give up.
////////////////////////////////////////////////
if(player->pingOnce && (player->pingsLostConsecutive >= 3))
{
player->pingsLostConsecutive = 0;
player->pingOnce = PEERFalse;
}
}
else
{
// Process the ping.
////////////////////
piProcessPing(data->peer, player, data->ping);
}
}
static void piPingerReply
(
unsigned int IP,
unsigned short port,
int ping,
const char * pingData,
int pingDataLen,
PEER peer
)
{
piPingerReplyData data;
PEER_CONNECTION;
// Find who sent the ping.
//////////////////////////
data.peer = peer;
data.IP = IP;
data.ping = ping;
TableMap(connection->players, piPingerReplyMapFn, &data);
GSI_UNUSED(port);
GSI_UNUSED(pingData);
GSI_UNUSED(pingDataLen);
}
static void piPingPlayer
(
PEER peer,
piPlayer * player
)
{
assert(player->gotIPAndProfileID);
// Make sure we're not already waiting.
///////////////////////////////////////
assert(!player->waitingForPing);
if(player->waitingForPing)
return;
// Do the ping.
///////////////
pingerPing(player->IP, PI_PINGER_PORT, piPingerReply, peer, PINGERFalse, PI_PING_TIMEOUT);
// Waiting for a ping to return.
////////////////////////////////
player->waitingForPing = PEERTrue;
// Last ping send time.
///////////////////////
player->lastPingSend = current_time();
// Clear must ping flag.
////////////////////////
player->mustPing = PEERFalse;
}
typedef struct piPickPingPlayersData
{
PEER peer;
piPlayer ** players;
int max;
int num;
} piPickPingPlayersData;
static void piPickPingPlayersMap
(
void * elem,
void * clientData
)
{
piPlayer * player = (piPlayer *)elem;
piPickPingPlayersData * data = (piPickPingPlayersData *)clientData;
PEER peer = (PEER)data->peer;
piPlayer * other;
unsigned long now;
unsigned long delay;
int i;
int j;
PEER_CONNECTION;
// Not pinging?
///////////////
if(!player->inPingRoom && !player->pingOnce)
return;
// Don't have IP?
/////////////////
if(!player->gotIPAndProfileID)
return;
// Waiting for a ping?
//////////////////////
if(player->waitingForPing)
return;
// Don't ping the local player.
///////////////////////////////
if(player->local)
return;
// Don't ping someone who's playing or away.
////////////////////////////////////////////
if((player->inRoom[TitleRoom] && (player->flags[TitleRoom] & PI_DONT_PING_FLAGS)) ||
(player->inRoom[GroupRoom] && (player->flags[GroupRoom] & PI_DONT_PING_FLAGS)) ||
(player->inRoom[StagingRoom] && (player->flags[StagingRoom] & PI_DONT_PING_FLAGS)))
return;
// Check if we have to ping this player.
////////////////////////////////////////
if(!player->mustPing)
{
// Get the current time.
////////////////////////
now = current_time();
// Slow pings for no response.
//////////////////////////////
if((player->pingsLostConsecutive >= 4) && ((now - player->lastPingSend) < 120000))
return;
// Pinged recently?
///////////////////
if(player->inRoom[StagingRoom])
delay = 2000;
else if(player->pingsReturned < 3)
delay = 5000;
else
delay = 30000;
if((now - player->lastPingSend) < delay)
return;
if((now - player->lastPingRecv) < (delay + 1500))
return;
}
// Go through all the spots.
////////////////////////////
for(i = (data->max - 1) ; i >= 0 ; i--)
{
// Is it empty?
///////////////
if(!data->players[i])
continue;
// The "other" player.
//////////////////////
other = data->players[i];
// Is this player higher priority than us?
//////////////////////////////////////////
if((!other->numPings && player->numPings) || // Not pinged.
(other->inRoom[StagingRoom] && !player->inRoom[StagingRoom]) || // In staging room.
(piIsPlayerVIP(other, StagingRoom) && !piIsPlayerVIP(player, StagingRoom)) || // Op or voice in the same staging room.
(strcasecmp(other->nick, player->nick) < 0)) // Alphabetical.
{
break;
}
}
// Bump it up one, so it's set to our spot.
///////////////////////////////////////////
i++;
// Did we not find anything?
////////////////////////////
if(i == data->max)
return;
// Bump down the other player and all below.
////////////////////////////////////////////
for(j = (data->max - 1) ; j > i ; j--)
data->players[j] = data->players[j - 1];
// Set this player.
///////////////////
data->players[i] = player;
// One more.
////////////
if(data->num < data->max)
data->num++;
}
// Returns an array of pointers to players to ping,
// or NULL if there's noone to ping.
// The number of players in the array will be no
// larger than min(PI_MAX_PING_PLAYERS, numPings).
///////////////////////////////////////////////////
static piPlayer ** piPickPingPlayers
(
PEER peer,
int * numPings
)
{
static piPlayer * players[PI_MAX_PING_PLAYERS];
piPickPingPlayersData data;
PEER_CONNECTION;
// Check for no players.
////////////////////////
if(!connection->players || !(*numPings) || !TableCount(connection->players))
{
*numPings = 0;
return NULL;
}
// Setup a list of players to ping.
///////////////////////////////////
data.peer = peer;
data.players = players;
data.max = min(PI_MAX_PING_PLAYERS, *numPings);
data.num = 0;
memset(players, 0, sizeof(piPlayer *) * data.max);
TableMap(connection->players, piPickPingPlayersMap, &data);
// Set number of pings.
///////////////////////
*numPings = data.num;
// Check for noone to ping.
///////////////////////////
if(!data.players[0])
return NULL;
return data.players;
}
static void piXpingPlayer
(
PEER peer,
piPlayer * player
)
{
int roomType;
char message[(PI_NICK_MAX_LEN * 2) + 32];
char encodedIP[11];
PEER_CONNECTION;
assert(player->inXpingRoom);
if(!player->inXpingRoom)
return;
// Setup the message.
/////////////////////
piMangleIP(encodedIP, player->IP);
sprintf(message, "%s %d", encodedIP, player->pingAverage);
// Figure out which room to send the xping in.
//////////////////////////////////////////////
for(roomType = 0 ; roomType < NumRooms ; roomType++)
{
if(player->inRoom[roomType] && connection->xpingRoom[roomType])
{
if(connection->numPlayers[roomType] <= PI_MAX_XPING_NUM_PLAYERS)
{
assert(IN_ROOM || ENTERING_ROOM);
if(IN_ROOM || ENTERING_ROOM)
piSendChannelUTM(peer, ROOM, PI_UTM_XPING, message, PEERFalse);
}
}
}
// Sent it.
///////////
player->xpingSent = PEERTrue;
player->lastXping = current_time();
connection->lastXpingSend = (unsigned int)player->lastXping;
}
typedef struct piPickXpingPlayerData
{
PEER peer;
piPlayer * player;
} piPickXpingPlayerData;
static void piPickXpingPlayerMap
(
void * elem,
void * clientData
)
{
piPlayer * player = (piPlayer *)elem;
piPickXpingPlayerData * data = (piPickXpingPlayerData *)clientData;
PEER peer = (PEER)data->peer;
unsigned long now;
PEER_CONNECTION;
// Not xpinging?
////////////////
if(!player->inXpingRoom)
return;
// Don't xping the local player.
////////////////////////////////
if(player->local)
return;
// Don't xping if no pings received.
///////////////////////////////////
if(!player->numPings)
return;
// Don't xping with the same info.
//////////////////////////////////
if(player->xpingSent)
return;
// Don't xping too often.
/////////////////////////
now = current_time();
if((now - player->lastXping) < PI_XPING_PLAYER_INTERVAL)
return;
// First player?
////////////////
if(!data->player)
{
data->player = player;
}
else if((now - player->lastXping) > (now - data->player->lastXping))
{
data->player = player;
}
}
// Returns a player to ping, or NULL if there's noone to ping.
//////////////////////////////////////////////////////////////
static piPlayer * piPickXpingPlayer
(
PEER peer
)
{
piPickXpingPlayerData data;
PEER_CONNECTION;
// Check for no players.
////////////////////////
if(!connection->players || !TableCount(connection->players))
return NULL;
// Find someone near the top of the list.
/////////////////////////////////////////
data.peer = peer;
data.player = NULL;
TableMap(connection->players, piPickXpingPlayerMap, &data);
return data.player;
}
void piPingThink
(
PEER peer
)
{
unsigned long now;
int pingTimeMod;
int numPings;
piPlayer * player;
piPlayer ** players;
int i;
PEER_CONNECTION;
// Check if we're not doing pings.
//////////////////////////////////
if(!connection->doPings)
return;
// Don't do pings while playing!
////////////////////////////////
if(connection->playing)
return;
// Don't do pings while away.
/////////////////////////////
if(connection->away)
return;
// Get the current time.
////////////////////////
now = current_time();
// Get the number of pings to send.
///////////////////////////////////
pingTimeMod = (int)(now / PI_PING_INTERVAL);
if(connection->lastPingTimeMod)
numPings = (pingTimeMod - connection->lastPingTimeMod);
else
numPings = 1;
if(numPings)
connection->lastPingTimeMod = pingTimeMod;
// Send pings.
//////////////
players = piPickPingPlayers(peer, &numPings);
if(players)
{
for(i = 0 ; (i < numPings) && players[i] ; i++)
piPingPlayer(peer, players[i]);
}
// Check for sending a crossping.
/////////////////////////////////
if((now - connection->lastXpingSend) > PI_XPING_INTERVAL)
{
// Try and pick a player to crossping.
//////////////////////////////////////
player = piPickXpingPlayer(peer);
if(player)
piXpingPlayer(peer, player);
}
// Let pinger think.
////////////////////
pingerThink();
}
PEERBool piPingInitPlayer
(
PEER peer,
piPlayer * player
)
{
int i;
PEER_CONNECTION;
assert(player);
// Check if we're not doing pings.
//////////////////////////////////
if(!connection->doPings)
return PEERTrue;
player->lastPingSend = 0;
player->lastPingRecv = 0;
player->lastXping = 0;
player->waitingForPing = PEERFalse;
player->pingsReturned = 0;
player->pingsLostConsecutive = 0;
player->pingAverage = 0;
for(i = 0 ; i < PI_PING_HISTORY_LEN ; i++)
player->pingHistory[i] = 0;
player->pingHistoryNum = 0;
player->numPings = 0;
player->xpingSent = PEERFalse;
player->inPingRoom = PEERFalse;
player->inXpingRoom = PEERFalse;
player->mustPing = PEERFalse;
player->pingOnce = PEERFalse;
return PEERTrue;
}
void piPingPlayerJoinedRoom
(
PEER peer,
piPlayer * player,
RoomType roomType
)
{
PEER_CONNECTION;
assert(player);
ASSERT_ROOMTYPE(roomType);
// Check if we're not doing pings.
//////////////////////////////////
if(!connection->doPings)
return;
// Is this a ping room?
///////////////////////
if(connection->pingRoom[roomType])
player->inPingRoom = PEERTrue;
// Is this an xping room?
/////////////////////////
if(connection->xpingRoom[roomType])
player->inXpingRoom = PEERTrue;
// Immediately ping players when they join a staging room.
//////////////////////////////////////////////////////////
if(roomType == StagingRoom)
player->mustPing = PEERTrue;
}
static void piRemoveXping
(
PEER peer,
piXping * xping
)
{
PEER_CONNECTION;
assert(xping);
TableRemove(connection->xpings, xping);
}
typedef struct piPingPlayerLeftRoomData
{
PEER peer;
const char * nick;
} piPingPlayerLeftRoomData;
static void piPingPlayerLeftRoomTableMapFn
(
void *elem,
void *clientdata
)
{
piXping * xping = (piXping *)elem;
piPingPlayerLeftRoomData * data = (piPingPlayerLeftRoomData *)clientdata;
assert(xping);
assert(data);
// Check if this player is part of this xping.
//////////////////////////////////////////////
if((strcmp(xping->nicks[0], data->nick) == 0) || (strcmp(xping->nicks[1], data->nick) == 0))
piRemoveXping(data->peer, xping);
}
void piPingPlayerLeftRoom
(
PEER peer,
piPlayer * player
)
{
PEER_CONNECTION;
assert(player);
// Check if we're not doing pings.
//////////////////////////////////
if(!connection->doPings)
return;
// Was this player in a ping room?
//////////////////////////////////
if(player->inPingRoom)
{
int i;
PEERBool inPingRoom = PEERFalse;
// Is he still in a ping room?
//////////////////////////////
for(i = 0 ; i < NumRooms ; i++)
{
if(player->inRoom[i] && connection->pingRoom[i])
inPingRoom = PEERTrue;
}
player->inPingRoom = inPingRoom;
}
// Was this player in an xping room?
////////////////////////////////////
if(player->inXpingRoom)
{
int i;
PEERBool inXpingRoom = PEERFalse;
// Is he still in a ping room?
//////////////////////////////
for(i = 0 ; i < NumRooms ; i++)
{
if(player->inRoom[i] && connection->xpingRoom[i])
inXpingRoom = PEERTrue;
}
player->inXpingRoom = inXpingRoom;
if(!player->inXpingRoom)
{
piPingPlayerLeftRoomData data;
data.peer = peer;
data.nick = player->nick;
// Get rid of all this players xpings.
//////////////////////////////////////
TableMapSafe(connection->xpings, piPingPlayerLeftRoomTableMapFn, &data);
}
}
}
static piXping * piFindXping
(
PEER peer,
const char * nick1,
const char * nick2
)
{
piXping xpingMatch;
PEER_CONNECTION;
assert(nick1);
assert(nick1[0]);
assert(piGetPlayer(peer, nick1));
assert(nick2);
assert(nick2[0]);
assert(piGetPlayer(peer, nick2));
// Setup the xping match.
/////////////////////////
strzcpy(xpingMatch.nicks[0], nick1, PI_NICK_MAX_LEN);
_strlwr(xpingMatch.nicks[0]);
strzcpy(xpingMatch.nicks[1], nick2, PI_NICK_MAX_LEN);
_strlwr(xpingMatch.nicks[0]);
// Find the xping.
//////////////////
return (piXping *)TableLookup(connection->xpings, &xpingMatch);
}
static piXping * piAddXping
(
PEER peer,
const char * nick1,
const char * nick2
)
{
piXping xpingMatch;
piXping * xping;
PEER_CONNECTION;
assert(nick1);
assert(nick1[0]);
assert(piGetPlayer(peer, nick1));
assert(nick2);
assert(nick2[0]);
assert(piGetPlayer(peer, nick2));
// Setup the one to add.
////////////////////////
xping = &xpingMatch;
strzcpy(xping->nicks[0], nick1, PI_NICK_MAX_LEN);
_strlwr(xping->nicks[0]);
strzcpy(xping->nicks[1], nick2, PI_NICK_MAX_LEN);
_strlwr(xping->nicks[1]);
// Add it.
//////////
TableEnter(connection->xpings, xping);
// Get it.
//////////
xping = (piXping *)TableLookup(connection->xpings, &xpingMatch);
// Return it.
/////////////
return xping;
}
void piUpdateXping
(
PEER peer,
const char * nick1,
const char * nick2,
int ping
)
{
piPlayer * player1;
piPlayer * player2;
piXping * xping;
PEER_CONNECTION;
assert(nick1);
assert(nick1[0]);
assert(piGetPlayer(peer, nick1));
assert(nick2);
assert(nick2[0]);
assert(piGetPlayer(peer, nick2));
assert(ping >= 0);
// Check if we're not doing pings.
//////////////////////////////////
if(!connection->doPings)
return;
player1 = piGetPlayer(peer, nick1);
if(!player1)
return;
if(!player1->inXpingRoom)
return;
player2 = piGetPlayer(peer, nick2);
if(!player2)
return;
if(!player2->inXpingRoom)
return;
// Add it.
//////////
xping = piAddXping(peer, nick1, nick2);
assert(xping);
if(!xping)
return;
// Update.
//////////
xping->ping = ping;
}
PEERBool piGetXping
(
PEER peer,
const char * nick1,
const char * nick2,
int * ping
)
{
piXping * xping;
PEER_CONNECTION;
assert(nick1);
assert(nick1[0]);
assert(piGetPlayer(peer, nick1));
assert(nick2);
assert(nick2[0]);
assert(piGetPlayer(peer, nick2));
assert(ping);
// Check if we're not doing pings.
//////////////////////////////////
if(!connection->doPings)
return PEERFalse;
// Get the xping.
/////////////////
xping = piFindXping(peer, nick1, nick2);
assert(xping);
if(!xping)
return PEERFalse;
// Return the ping.
///////////////////
*ping = xping->ping;
return PEERTrue;
}