openmohaa/code/gamespy/serverbrowsing/sb_server.c
2023-02-04 21:00:01 +01:00

892 lines
21 KiB
C

#include "sb_internal.h"
#include "sb_ascii.h"
//for the unique value list
#if defined(_NITRO)
#define LIST_NUMKEYBUCKETS 100
#define LIST_NUMKEYCHAINS 2
#else
#define LIST_NUMKEYBUCKETS 500
#define LIST_NUMKEYCHAINS 4
#endif
// for unicode version of key/value pairs
#define UKEY_LENGTH_MAX 255
#define UVALUE_LENGTH_MAX 255
#ifndef EXTERN_REFSTR_HASH
//global, shared unique value list
#if defined(_WIN32) && !defined(_DLL) && !defined (_USRDLL) && !defined(_MANAGED) && defined(GM_2B)
//for gmaster2b
__declspec( thread )
#endif
HashTable g_SBRefStrList = NULL;
#endif
/***********
* REF COUNTING STRING HASHTABLE FUNCTIONS
**********/
static int StringHash(const char *s, int numbuckets);
static int RefStringHash(const void *elem, int numbuckets)
{
return StringHash(((SBRefString *)elem)->str, numbuckets);
}
/* keyval
* Compares two gkeyvaluepair
*/
static int GS_STATIC_CALLBACK RefStringCompare(const void *entry1, const void *entry2)
{
return strcasecmp(((SBRefString *)entry1)->str,((SBRefString *)entry2)->str);
}
static void RefStringFree(void *elem)
{
gsifree((char *)((SBRefString *)elem)->str);
#ifdef GSI_UNICODE
gsifree((unsigned short *)((SBRefString *)elem)->str_W);
#endif
}
#ifndef EXTERN_REFSTR_HASH
HashTable SBRefStrHash(SBServerList *slist)
{
if (g_SBRefStrList == NULL)
g_SBRefStrList = TableNew2(sizeof(SBRefString),LIST_NUMKEYBUCKETS,LIST_NUMKEYCHAINS,RefStringHash, RefStringCompare, RefStringFree);
GSI_UNUSED(slist);
return g_SBRefStrList;
}
void SBRefStrHashCleanup(SBServerList *slist)
{
if (g_SBRefStrList != NULL && TableCount(g_SBRefStrList) == 0)
{
TableFree(g_SBRefStrList);
g_SBRefStrList = NULL;
}
GSI_UNUSED(slist);
}
#endif
void SBServerFree(void *elem)
{
SBServer server = *(SBServer *)elem;
//free all the keys..
TableFree(server->keyvals);
server->keyvals = NULL;
gsifree(server);
}
void SBServerAddKeyValue(SBServer server, const char *keyname, const char *value)
{
SBKeyValuePair kv;
kv.key = SBRefStr(NULL, keyname);
kv.value = SBRefStr(NULL, value);
TableEnter(server->keyvals, &kv);
gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Misc, GSIDebugLevel_Comment,
"SBServerAddKeyValue added %s\\%s\r\n", keyname, value);
}
void SBServerAddIntKeyValue(SBServer server, const char *keyname, int value)
{
char stemp[20];
sprintf(stemp, "%d", value);
SBServerAddKeyValue(server, keyname, stemp);
}
typedef struct
{
SBServerKeyEnumFn EnumFn;
void *instance;
} SBServerEnumData;
/* ServerEnumKeys
-----------------
Enumerates the keys/values for a given server by calling KeyEnumFn with each
key/value. The user-defined instance data will be passed to the KeyFn callback */
static void KeyMapF(void *elem, void *clientData)
{
SBKeyValuePair *kv = (SBKeyValuePair *)elem;
SBServerEnumData *ped = (SBServerEnumData *)clientData;
#ifndef GSI_UNICODE
ped->EnumFn((char *)kv->key, (char *)kv->value, ped->instance);
#else
unsigned short key_W[UKEY_LENGTH_MAX];
unsigned short value_W[UVALUE_LENGTH_MAX];
UTF8ToUCS2String(kv->key, key_W);
UTF8ToUCS2String(kv->value, value_W);
ped->EnumFn(key_W, value_W, ped->instance);
#endif
}
void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, void *instance)
{
SBServerEnumData ed;
ed.EnumFn = KeyFn;
ed.instance = instance;
TableMap(server->keyvals, KeyMapF, &ed);
}
const char *SBServerGetStringValueA(SBServer server, const char *keyname, const char *def)
{
SBKeyValuePair kv, *ptr;
// 11-03-2004 : Saad Nader
// Check if we are getting a valid server
// before doing anything!
///////////////////////////////////////////
assert(server);
if (!server)
return NULL;
kv.key = keyname;
ptr = (SBKeyValuePair *)TableLookup(server->keyvals, &kv);
if (ptr == NULL)
return def;
return ptr->value;
}
#ifdef GSI_UNICODE
const unsigned short *SBServerGetStringValueW(SBServer server, const unsigned short *keyname, const unsigned short *def)
{
char* keyname_A = UCS2ToUTF8StringAlloc(keyname);
const char* value = SBServerGetStringValueA(server, keyname_A, NULL);
if (value == NULL)
return def;
else
{
// Since we need the unicode version, we have to dig down to the
// reference counted string structure
SBRefString ref, *val;
ref.str = value;
val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref);
if (val == NULL)
return def; // this shouldn't happen
return val->str_W;
}
}
#endif
int SBServerGetIntValueA(SBServer server, const char *key, int idefault)
{
const char *s, *s2;
// check assumtions during development
GS_ASSERT(key != NULL);
GS_ASSERT(server != NULL);
if (server == NULL)
return idefault;
if (strcmp(key,"ping") == 0) //ooh! they want the ping!
return SBServerGetPing(server);
s = SBServerGetStringValueA(server, key, NULL);
if (s == NULL)
return idefault;
s2 = (*s != '-') ? s : s+1; // check for signed values
if (!isdigit((unsigned char)*s2)) // empty-string/non-numeric should return idefault
return idefault;
else
return atoi(s);
}
#ifdef GSI_UNICODE
int SBServerGetIntValueW(SBServer server, const unsigned short *key, int idefault)
{
char keyname_A[255];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetIntValueA(server, keyname_A, idefault);
}
#endif
double SBServerGetFloatValueA(SBServer server, const char *key, double fdefault)
{
const char *s;
s = SBServerGetStringValueA(server, key, NULL);
if (s == NULL)
return fdefault;
else
return atof(s);
}
#ifdef GSI_UNICODE
double SBServerGetFloatValueW(SBServer server, const unsigned short *key, double fdefault)
{
char keyname_A[255];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetFloatValueA(server, keyname_A, fdefault);
}
#endif
SBBool SBServerGetBoolValueA(SBServer server, const char *key, SBBool bdefault)
{
const char *s;
s = SBServerGetStringValueA(server, key, NULL);
if (!s || !s[0])
return bdefault;
// check the first char for known "false" values
if('0' == s[0]|| 'F' == s[0] || 'f' == s[0] || 'N' == s[0] || 'n' == s[0])
return SBFalse;
// presume that all other non-zero values are "true"
return SBTrue;
}
#ifdef GSI_UNICODE
SBBool SBServerGetBoolValueW(SBServer server, const unsigned short *key, SBBool bdefault)
{
char keyname_A[255];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetBoolValueA(server, keyname_A, bdefault);
}
#endif
const char *SBServerGetPlayerStringValueA(SBServer server, int playernum, const char *key, const char *sdefault)
{
char keyname[128];
sprintf(keyname, "%s_%d", key, playernum);
return SBServerGetStringValueA(server, keyname, sdefault);
}
#ifdef GSI_UNICODE
const unsigned short *SBServerGetPlayerStringValueW(SBServer server, int playernum, const unsigned short *key, const unsigned short *sdefault)
{
char keyname_A[UKEY_LENGTH_MAX];
char default_A[UKEY_LENGTH_MAX];
const char* value_A = NULL;
UCS2ToUTF8String(key, keyname_A);
UCS2ToUTF8String(sdefault, default_A);
value_A = SBServerGetPlayerStringValueA(server, playernum, keyname_A, default_A);
if (value_A == NULL)
return sdefault;
else
{
// Since we need the unicode version, we have to dig down to the SBRefString structure
SBRefString ref, *val;
ref.str = value_A;
val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref);
if (val == NULL)
return sdefault;
return val->str_W;
}
}
#endif
int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char *key, int idefault)
{
char keyname[128];
sprintf(keyname, "%s_%d", key, playernum);
return SBServerGetIntValueA(server, keyname, idefault);
}
#ifdef GSI_UNICODE
int SBServerGetPlayerIntValueW(SBServer server, int playernum, const unsigned short *key, int idefault)
{
char keyname_A[UKEY_LENGTH_MAX];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetPlayerIntValueA(server, playernum, keyname_A, idefault);
}
#endif
double SBServerGetPlayerFloatValueA(SBServer server, int playernum, const char *key, double fdefault)
{
char keyname[128];
sprintf(keyname, "%s_%d", key, playernum);
return SBServerGetFloatValueA(server, keyname, fdefault);
}
#ifdef GSI_UNICODE
double SBServerGetPlayerFloatValueW(SBServer server, int playernum, const unsigned short *key, double fdefault)
{
char keyname_A[UKEY_LENGTH_MAX];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetPlayerFloatValueA(server, playernum, keyname_A, fdefault);
}
#endif
const char *SBServerGetTeamStringValueA(SBServer server, int teamnum, const char *key, const char *sdefault)
{
char keyname[128];
sprintf(keyname, "%s_t%d", key, teamnum);
return SBServerGetStringValueA(server, keyname, sdefault);
}
#ifdef GSI_UNICODE
const unsigned short *SBServerGetTeamStringValueW(SBServer server, int teamnum, const unsigned short *key, const unsigned short *sdefault)
{
char keyname_A[UKEY_LENGTH_MAX];
char default_A[UKEY_LENGTH_MAX];
const char* value_A = NULL;
UCS2ToUTF8String(key, keyname_A);
UCS2ToUTF8String(sdefault, default_A);
value_A = SBServerGetTeamStringValueA(server, teamnum, keyname_A, default_A);
if (value_A == NULL)
return sdefault;
else
{
// Since we need the unicode version, we have to dig down to the SBRefString structure
SBRefString ref, *val;
ref.str = value_A;
val = (SBRefString *)TableLookup(SBRefStrHash(NULL), &ref);
if (val == NULL)
return sdefault;
return val->str_W;
}
}
#endif
int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char *key, int idefault)
{
char keyname[128];
sprintf(keyname, "%s_t%d", key, teamnum);
return SBServerGetIntValueA(server, keyname, idefault);
}
#ifdef GSI_UNICODE
int SBServerGetTeamIntValueW(SBServer server, int teamnum, const unsigned short *key, int idefault)
{
char keyname_A[UKEY_LENGTH_MAX];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetTeamIntValueA(server, teamnum, keyname_A, idefault);
}
#endif
double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char *key, double fdefault)
{
char keyname[128];
sprintf(keyname, "%s_t%d", key, teamnum);
return SBServerGetFloatValueA(server, keyname, fdefault);
}
#ifdef GSI_UNICODE
double SBServerGetTeamFloatValueW(SBServer server, int teamnum, const unsigned short *key, double fdefault)
{
char keyname_A[UKEY_LENGTH_MAX];
UCS2ToUTF8String(key, keyname_A);
return SBServerGetTeamFloatValueA(server, teamnum, keyname_A, fdefault);
}
#endif
SBBool SBServerHasBasicKeys(SBServer server)
{
return (((server->state & STATE_BASICKEYS) == STATE_BASICKEYS) ? SBTrue : SBFalse);
}
SBBool SBServerHasFullKeys(SBServer server)
{
return (((server->state & STATE_FULLKEYS) == STATE_FULLKEYS) ? SBTrue : SBFalse);
}
SBBool SBServerHasValidPing(SBServer server)
{
return (((server->state & STATE_VALIDPING) == STATE_VALIDPING) ? SBTrue : SBFalse);
}
char *SBServerGetPublicAddress(SBServer server)
{
return (char *)inet_ntoa(*(struct in_addr *)&server->publicip);
}
unsigned int SBServerGetPublicInetAddress(SBServer server)
{
return server->publicip;
}
unsigned short SBServerGetPublicQueryPort(SBServer server)
{
return ntohs(server->publicport);
}
unsigned short SBServerGetPublicQueryPortNBO(SBServer server)
{
return server->publicport;
}
SBBool SBServerHasPrivateAddress(SBServer server)
{
return (((server->flags & PRIVATE_IP_FLAG) == PRIVATE_IP_FLAG) ? SBTrue : SBFalse);
}
SBBool SBServerDirectConnect(SBServer server)
{
return (((server->flags & UNSOLICITED_UDP_FLAG) == UNSOLICITED_UDP_FLAG) ? SBTrue : SBFalse);
}
char *SBServerGetPrivateAddress(SBServer server)
{
return (char *)inet_ntoa(*(struct in_addr *)&server->privateip);
}
unsigned int SBServerGetPrivateInetAddress(SBServer server)
{
return server->privateip;
}
unsigned short SBServerGetPrivateQueryPort(SBServer server)
{
return ntohs(server->privateport);
}
void SBServerSetNext(SBServer server, SBServer next)
{
server->next = next;
}
SBServer SBServerGetNext(SBServer server)
{
return server->next;
}
static int CheckValidKey(char *key)
{
const char *InvalidKeys[]={"queryid","final"};
int i;
for (i = 0; i < sizeof(InvalidKeys)/sizeof(InvalidKeys[0]); i++)
{
if (strcmp(key,InvalidKeys[i]) == 0)
return 0;
}
return 1;
}
static char *mytok(char *instr, char delim)
{
char *result;
static char *thestr;
if (instr)
thestr = instr;
result=thestr;
while (*thestr && *thestr != delim)
{
thestr++;
}
if (thestr == result)
result = NULL;
if (*thestr) //not the null term
*thestr++ = '\0';
return result;
}
void SBServerParseKeyVals(SBServer server, char *keyvals)
{
char *k, *v;
k = mytok(++keyvals,'\\'); //skip over starting backslash
while (k != NULL)
{
v = mytok(NULL,'\\');
if (v == NULL)
v = "";
if (CheckValidKey(k))
{
//add the value if its not a Query-From-Master-Only key
if (!qr2_internal_is_master_only_key(k))
{
SBServerAddKeyValue(server, k, v);
}
}
k = mytok(NULL,'\\');
}
}
/*
Query Response Format
Packet Type: 1 Byte
Request Key: 4 Bytes (copied from request packet)
Server Keys (if number of keys requested > 0)
If server key list specified:
Server Values: NTS, one per key specified
If server key list not specified:
Server Keys / Server Values: NTS Pairs, terminated with NUL
Player Keys (if number of keys requested > 0)
Number of Players: 2 Bytes
If player key list NOT specified
Player Keys: NTS, one per key, terminated with NUL
Player Values: NTS, one per key specified, per player
Team Keys (if number of keys requested > 0)
Number of Teams: 2 Bytes
If team key list NOT specified
Team Keys: NTS, one per key, terminated with NUL
Team Values: NTS, one per key specified, per team
*/
void SBServerParseQR2FullKeysSingle(SBServer server, char *data, int len)
{
int dlen;
char *k;
char *v;
char *keys;
int nkeys;
unsigned short nunits;
int pflag;
int i,j;
char tempkey[128];
//first pull out all the server keys/values
while (*data)
{
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return; //not a full NTS
k = data;
data += dlen;
len -= dlen;
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return; //not a full NTS
v = data;
data += dlen;
len -= dlen;
//add the value if its not a Query-From-Master-Only key
if (!qr2_internal_is_master_only_key(k))
{
SBServerAddKeyValue(server, k, v);
}
}
//skip the NUL
data++;
len--;
//now get out the # of players (or teams) .. we do this whole thing 2X, once for players once for teams
for (pflag = 0 ; pflag < 2 ; pflag++)
{
if (len < 2)
return;
memcpy(&nunits, data, 2);
nunits = ntohs(nunits);
data += 2;
len -= 2;
keys = data;
nkeys = 0;
//count up the number of keys..
while (*data)
{
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return; //not all there
if (dlen > 100) //key is too long, may cause buffer overrun
return;
nkeys++;
data += dlen;
len -= dlen;
}
//skip the NUL
data++;
len--;
//now for each player/team
for (i = 0 ; i < nunits ; i++)
{
k = keys;
//for each key..
for (j = 0 ; j < nkeys ; j++)
{
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return; //not all there
sprintf(tempkey, "%s%d", k, i);
SBServerAddKeyValue(server, tempkey, data);
data += dlen;
len -= dlen;
k += strlen(k) + 1; //skip to the next key
}
}
}
}
// FullKeys with split response support
// Goes something like:
// [qr2header]["splitnum"][num (byte)][keytype]
// if keytype is server, read KV's until a NULL
// otherwise read a keyname, then a number of values until NULL
#define QR2_SPLITPACKET_NUMSTRING "splitnum"
#define QR2_SPLITPACKET_MAX 7 // -xxxxxxx
#define QR2_SPLITPACKET_FINAL (1<<7) // x-------
void SBServerParseQR2FullKeysSplit(SBServer server, char *data, int len)
{
int dlen;
char *k;
char *v;
unsigned int packetNum = 0;
gsi_bool isFinal = gsi_false;
// make sure it's valid
if (*data == '\0')
return;
// data should have "splitnum" followed by BINARY key
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return;
k = data;
data += dlen;
len -= dlen;
if (strncasecmp(QR2_SPLITPACKET_NUMSTRING,k,strlen(QR2_SPLITPACKET_NUMSTRING))!=0)
return;
if (len < 1)
return;
packetNum = (unsigned int)((unsigned char)*data);
data++;
len--;
// check final flag
if ((packetNum & QR2_SPLITPACKET_FINAL) == QR2_SPLITPACKET_FINAL)
{
isFinal = gsi_true;
packetNum ^= QR2_SPLITPACKET_FINAL;
}
// sanity check the packet num
if (packetNum > QR2_SPLITPACKET_MAX)
return;
// update the received flags
if (isFinal == SBTrue)
// mark all bits higher than the final packet
server->splitResponseBitmap |= (char)(0xFF<<packetNum);
else
// mark only the bit for the received packet
server->splitResponseBitmap |= (1<<packetNum);
// any data in this packet? (could be a final tag)
if (len < 1)
return;
// Parse the key sections (server/player/team)
while(len > 0)
{
int keyType = 0;
int nindex = 0;
// Read the key type
keyType = *data;
data++;
len--;
if (keyType < 0 || keyType > 2)
{
gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Split packet parse error, invalid key type! (%d)\r\n", keyType);
return; // invalid key type!
}
// read keys until <null> section terminator
while(*data)
{
// Read key name
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return;
k = data;
data += dlen;
len -= dlen;
if (keyType == 0)
{
// read the server key value
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return;
v = data;
data += dlen;
len -= dlen;
//add the value if its not a Query-From-Master-Only key
if (!qr2_internal_is_master_only_key(k))
{
SBServerAddKeyValue(server, k, v);
}
}
else
{
char tempkey[128];
// Read first player/team number
if (len < 1)
return;
nindex = *data;
data++;
len--;
// read values until <null>
while(*data)
{
// read the value
dlen = NTSLengthSB(data, len);
if (dlen < 0)
return;
v = data;
data += dlen;
len -= dlen;
// append team or player index before adding
sprintf(tempkey, "%s%d", k, nindex);
SBServerAddKeyValue(server, tempkey, v);
nindex++; // index increments from start
}
// skip the null (key terminator)
if (len > 0)
{
data++;
len--;
}
}
}
// skip the null (section terminator)
if (len > 0)
{
if (*data != '\0')
{
gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError,
"Split packet parse error, NULL byte expected!\r\n");
return; // ERROR!
}
data++;
len--;
}
}
}
/***********
* UTILITY FUNCTIONS
**********/
#define MULTIPLIER -1664117991
static int StringHash(const char *s, int numbuckets)
{
goa_uint32 hashcode = 0;
while (*s != 0)
{
hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s));
s++;
}
return (int)(hashcode % numbuckets);
}
static void KeyValFree(void *elem)
{
SBKeyValuePair *kv = (SBKeyValuePair *)elem;
SBReleaseStr(NULL, kv->key);
SBReleaseStr(NULL, kv->value);
}
static int KeyValHashKey(const void *elem, int numbuckets)
{
return StringHash(((SBKeyValuePair *)elem)->key, numbuckets);
}
static int GS_STATIC_CALLBACK KeyValCompareKey(const void *entry1, const void *entry2)
{
GS_ASSERT(entry1)
GS_ASSERT(entry2)
if (
(((SBKeyValuePair *)entry1)->key == NULL) ||
(((SBKeyValuePair *)entry2)->key == NULL)
)
return 1;// treat NULL as not the same
return strcasecmp(((SBKeyValuePair *)entry1)->key, ((SBKeyValuePair *)entry2)->key);
}
int SBServerGetPing(SBServer server)
{
return (int) server->updatetime;
}
#define NUM_BUCKETS 8
#define NUM_CHAINS 4
//todo: benchmark sorted darray vs. hashtable - memory + speed
SBServer SBAllocServer(SBServerList *slist, goa_uint32 publicip, unsigned short publicport)
{
SBServer server;
server = (SBServer)gsimalloc(sizeof(struct _SBServer));
if (server == NULL)
return NULL;
server->keyvals = TableNew2(sizeof(SBKeyValuePair),NUM_BUCKETS,NUM_CHAINS, KeyValHashKey, KeyValCompareKey, KeyValFree);
if (server->keyvals == NULL)
{
gsifree(server);
return NULL;
}
server->state = 0;
server->flags = 0;
server->next = NULL;
server->updatetime = 0;
server->icmpip = 0;
server->publicip = publicip;
server->publicport = publicport;
server->privateip = 0;
server->privateport = 0;
GSI_UNUSED(slist);
return server;
}
void SBServerSetFlags(SBServer server, unsigned char flags)
{
server->flags = flags;
}
void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, unsigned short port)
{
server->publicip = ip;
server->publicport = port;
}
void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, unsigned short port)
{
server->privateip = ip;
server->privateport = port;
}
void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip)
{
server->icmpip = icmpip;
}
void SBServerSetState(SBServer server, unsigned char state)
{
server->state = state;
}
unsigned char SBServerGetState(SBServer server)
{
return server->state;
}
unsigned char SBServerGetFlags(SBServer server)
{
return server->flags;
}
int SBIsNullServer(SBServer server)
{
return (server == SBNullServer) ? 1 : 0;
}
SBServer SBNullServer = NULL;