/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// net_dgrm.c

// This is enables a simple IP banning mechanism
#define BAN_TEST

#ifdef BAN_TEST
#if defined(_WIN32)
#include <windows.h>
#elif defined (NeXT)
#include <sys/socket.h>
#include <arpa/inet.h>
#else
#define AF_INET 		2	/* internet */
struct in_addr {
    union {
        struct {
            unsigned char s_b1,s_b2,s_b3,s_b4;
        } S_un_b;
        struct {
            unsigned short s_w1,s_w2;
        } S_un_w;
        unsigned long S_addr;
    } S_un;
};
#define	s_addr	S_un.S_addr	/* can be used for most tcp & ip code */
struct sockaddr_in {
    short			sin_family;
    unsigned short	sin_port;
    struct in_addr	sin_addr;
    char			sin_zero[8];
};
char *inet_ntoa(struct in_addr in);
unsigned long inet_addr(const char *cp);
#endif
#endif	// BAN_TEST

#include "quakedef.h"
#include "net_dgrm.h"

// these two macros are to make the code more readable
#define sfunc	net_landrivers[sock->landriver]
#define dfunc	net_landrivers[net_landriverlevel]

static int net_landriverlevel;

/* statistic counters */
int	packetsSent = 0;
int	packetsReSent = 0;
int packetsReceived = 0;
int receivedDuplicateCount = 0;
int shortPacketCount = 0;
int droppedDatagrams;

static int myDriverLevel;

struct {
    unsigned int	length;
    unsigned int	sequence;
    byte			data[MAX_DATAGRAM];
} packetBuffer;

extern int m_return_state;
extern int m_state;
extern qboolean m_return_onerror;
extern char m_return_reason[32];


#ifdef DEBUG
char *StrAddr(struct qsockaddr *addr)
{
    static char buf[34];
    byte *p = (byte *)addr;
    int n;

    for (n = 0; n < 16; n++) {
        sprintf(buf + n * 2, "%02x", *p++);
    }
    return buf;
}
#endif


#ifdef BAN_TEST
unsigned long banAddr = 0x00000000;
unsigned long banMask = 0xffffffff;

void NET_Ban_f(void)
{
    char	addrStr [32];
    char	maskStr [32];
    void	(*print)(char *fmt, ...);

    if (cmd_source == src_command) {
        if (!sv.active) {
            Cmd_ForwardToServer();
            return;
        }
        print = Con_Printf;
    } else {
        if (pr_global_struct->deathmatch && !host_client->privileged) {
            return;
        }
        print = SV_ClientPrintf;
    }

    switch (Cmd_Argc()) {
    case 1:
        if (((struct in_addr *)&banAddr)->s_addr) {
            Q_strcpy(addrStr, inet_ntoa(*(struct in_addr *)&banAddr));
            Q_strcpy(maskStr, inet_ntoa(*(struct in_addr *)&banMask));
            print("Banning %s [%s]\n", addrStr, maskStr);
        } else {
            print("Banning not active\n");
        }
        break;

    case 2:
        if (Q_strcasecmp(Cmd_Argv(1), "off") == 0) {
            banAddr = 0x00000000;
        } else {
            banAddr = inet_addr(Cmd_Argv(1));
        }
        banMask = 0xffffffff;
        break;

    case 3:
        banAddr = inet_addr(Cmd_Argv(1));
        banMask = inet_addr(Cmd_Argv(2));
        break;

    default:
        print("BAN ip_address [mask]\n");
        break;
    }
}
#endif


int Datagram_SendMessage(qsocket_t *sock, sizebuf_t *data)
{
    unsigned int	packetLen;
    unsigned int	dataLen;
    unsigned int	eom;

#ifdef DEBUG
    if (data->cursize == 0) {
        Sys_Error("Datagram_SendMessage: zero length message\n");
    }

    if (data->cursize > NET_MAXMESSAGE) {
        Sys_Error("Datagram_SendMessage: message too big %u\n", data->cursize);
    }

    if (sock->canSend == false) {
        Sys_Error("SendMessage: called with canSend == false\n");
    }
#endif

    Q_memcpy(sock->sendMessage, data->data, data->cursize);
    sock->sendMessageLength = data->cursize;

    if (data->cursize <= MAX_DATAGRAM) {
        dataLen = data->cursize;
        eom = NETFLAG_EOM;
    } else {
        dataLen = MAX_DATAGRAM;
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence++);
    Q_memcpy(packetBuffer.data, sock->sendMessage, dataLen);

    sock->canSend = false;

    if (sfunc.Write(sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) {
        return -1;
    }

    sock->lastSendTime = net_time;
    packetsSent++;
    return 1;
}


int SendMessageNext(qsocket_t *sock)
{
    unsigned int	packetLen;
    unsigned int	dataLen;
    unsigned int	eom;

    if (sock->sendMessageLength <= MAX_DATAGRAM) {
        dataLen = sock->sendMessageLength;
        eom = NETFLAG_EOM;
    } else {
        dataLen = MAX_DATAGRAM;
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence++);
    Q_memcpy(packetBuffer.data, sock->sendMessage, dataLen);

    sock->sendNext = false;

    if (sfunc.Write(sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) {
        return -1;
    }

    sock->lastSendTime = net_time;
    packetsSent++;
    return 1;
}


int ReSendMessage(qsocket_t *sock)
{
    unsigned int	packetLen;
    unsigned int	dataLen;
    unsigned int	eom;

    if (sock->sendMessageLength <= MAX_DATAGRAM) {
        dataLen = sock->sendMessageLength;
        eom = NETFLAG_EOM;
    } else {
        dataLen = MAX_DATAGRAM;
        eom = 0;
    }
    packetLen = NET_HEADERSIZE + dataLen;

    packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom));
    packetBuffer.sequence = BigLong(sock->sendSequence - 1);
    Q_memcpy(packetBuffer.data, sock->sendMessage, dataLen);

    sock->sendNext = false;

    if (sfunc.Write(sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) {
        return -1;
    }

    sock->lastSendTime = net_time;
    packetsReSent++;
    return 1;
}


qboolean Datagram_CanSendMessage(qsocket_t *sock)
{
    if (sock->sendNext) {
        SendMessageNext(sock);
    }

    return sock->canSend;
}


qboolean Datagram_CanSendUnreliableMessage(qsocket_t *sock)
{
    return true;
}


int Datagram_SendUnreliableMessage(qsocket_t *sock, sizebuf_t *data)
{
    int 	packetLen;

#ifdef DEBUG
    if (data->cursize == 0) {
        Sys_Error("Datagram_SendUnreliableMessage: zero length message\n");
    }

    if (data->cursize > MAX_DATAGRAM) {
        Sys_Error("Datagram_SendUnreliableMessage: message too big %u\n", data->cursize);
    }
#endif

    packetLen = NET_HEADERSIZE + data->cursize;

    packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE);
    packetBuffer.sequence = BigLong(sock->unreliableSendSequence++);
    Q_memcpy(packetBuffer.data, data->data, data->cursize);

    if (sfunc.Write(sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) {
        return -1;
    }

    packetsSent++;
    return 1;
}


int	Datagram_GetMessage(qsocket_t *sock)
{
    unsigned int	length;
    unsigned int	flags;
    int				ret = 0;
    struct qsockaddr readaddr;
    unsigned int	sequence;
    unsigned int	count;

    if (!sock->canSend)
        if ((net_time - sock->lastSendTime) > 1.0) {
            ReSendMessage(sock);
        }

    while (1) {
        length = sfunc.Read(sock->socket, (byte *)&packetBuffer, NET_DATAGRAMSIZE, &readaddr);

//	if ((rand() & 255) > 220)
//		continue;

        if (length == 0) {
            break;
        }

        if (length == -1) {
            Con_Printf("Read error\n");
            return -1;
        }

        if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0) {
#ifdef DEBUG
            Con_DPrintf("Forged packet received\n");
            Con_DPrintf("Expected: %s\n", StrAddr(&sock->addr));
            Con_DPrintf("Received: %s\n", StrAddr(&readaddr));
#endif
            continue;
        }

        if (length < NET_HEADERSIZE) {
            shortPacketCount++;
            continue;
        }

        length = BigLong(packetBuffer.length);
        flags = length & (~NETFLAG_LENGTH_MASK);
        length &= NETFLAG_LENGTH_MASK;

        if (flags & NETFLAG_CTL) {
            continue;
        }

        sequence = BigLong(packetBuffer.sequence);
        packetsReceived++;

        if (flags & NETFLAG_UNRELIABLE) {
            if (sequence < sock->unreliableReceiveSequence) {
                Con_DPrintf("Got a stale datagram\n");
                ret = 0;
                break;
            }
            if (sequence != sock->unreliableReceiveSequence) {
                count = sequence - sock->unreliableReceiveSequence;
                droppedDatagrams += count;
                Con_DPrintf("Dropped %u datagram(s)\n", count);
            }
            sock->unreliableReceiveSequence = sequence + 1;

            length -= NET_HEADERSIZE;

            SZ_Clear(&net_message);
            SZ_Write(&net_message, packetBuffer.data, length);

            ret = 2;
            break;
        }

        if (flags & NETFLAG_ACK) {
            if (sequence != (sock->sendSequence - 1)) {
                Con_DPrintf("Stale ACK received\n");
                continue;
            }
            if (sequence == sock->ackSequence) {
                sock->ackSequence++;
                if (sock->ackSequence != sock->sendSequence) {
                    Con_DPrintf("ack sequencing error\n");
                }
            } else {
                Con_DPrintf("Duplicate ACK received\n");
                continue;
            }
            sock->sendMessageLength -= MAX_DATAGRAM;
            if (sock->sendMessageLength > 0) {
                Q_memcpy(sock->sendMessage, sock->sendMessage+MAX_DATAGRAM, sock->sendMessageLength);
                sock->sendNext = true;
            } else {
                sock->sendMessageLength = 0;
                sock->canSend = true;
            }
            continue;
        }

        if (flags & NETFLAG_DATA) {
            packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK);
            packetBuffer.sequence = BigLong(sequence);
            sfunc.Write(sock->socket, (byte *)&packetBuffer, NET_HEADERSIZE, &readaddr);

            if (sequence != sock->receiveSequence) {
                receivedDuplicateCount++;
                continue;
            }
            sock->receiveSequence++;

            length -= NET_HEADERSIZE;

            if (flags & NETFLAG_EOM) {
                SZ_Clear(&net_message);
                SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength);
                SZ_Write(&net_message, packetBuffer.data, length);
                sock->receiveMessageLength = 0;

                ret = 1;
                break;
            }

            Q_memcpy(sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length);
            sock->receiveMessageLength += length;
            continue;
        }
    }

    if (sock->sendNext) {
        SendMessageNext(sock);
    }

    return ret;
}


void PrintStats(qsocket_t *s)
{
    Con_Printf("canSend = %4u   \n", s->canSend);
    Con_Printf("sendSeq = %4u   ", s->sendSequence);
    Con_Printf("recvSeq = %4u   \n", s->receiveSequence);
    Con_Printf("\n");
}

void NET_Stats_f(void)
{
    qsocket_t	*s;

    if (Cmd_Argc() == 1) {
        Con_Printf("unreliable messages sent   = %i\n", unreliableMessagesSent);
        Con_Printf("unreliable messages recv   = %i\n", unreliableMessagesReceived);
        Con_Printf("reliable messages sent     = %i\n", messagesSent);
        Con_Printf("reliable messages received = %i\n", messagesReceived);
        Con_Printf("packetsSent                = %i\n", packetsSent);
        Con_Printf("packetsReSent              = %i\n", packetsReSent);
        Con_Printf("packetsReceived            = %i\n", packetsReceived);
        Con_Printf("receivedDuplicateCount     = %i\n", receivedDuplicateCount);
        Con_Printf("shortPacketCount           = %i\n", shortPacketCount);
        Con_Printf("droppedDatagrams           = %i\n", droppedDatagrams);
    } else if (Q_strcmp(Cmd_Argv(1), "*") == 0) {
        for (s = net_activeSockets; s; s = s->next) {
            PrintStats(s);
        }
        for (s = net_freeSockets; s; s = s->next) {
            PrintStats(s);
        }
    } else {
        for (s = net_activeSockets; s; s = s->next)
            if (Q_strcasecmp(Cmd_Argv(1), s->address) == 0) {
                break;
            }
        if (s == NULL)
            for (s = net_freeSockets; s; s = s->next)
                if (Q_strcasecmp(Cmd_Argv(1), s->address) == 0) {
                    break;
                }
        if (s == NULL) {
            return;
        }
        PrintStats(s);
    }
}


static qboolean testInProgress = false;
static int		testPollCount;
static int		testDriver;
static int		testSocket;

static void Test_Poll(void);
PollProcedure	testPollProcedure = {NULL, 0.0, Test_Poll};

static void Test_Poll(void)
{
    struct qsockaddr clientaddr;
    int		control;
    int		len;
    char	name[32];
    char	address[64];
    int		colors;
    int		frags;
    int		connectTime;
    byte	playerNumber;

    net_landriverlevel = testDriver;

    while (1) {
        len = dfunc.Read(testSocket, net_message.data, net_message.maxsize, &clientaddr);
        if (len < sizeof(int)) {
            break;
        }

        net_message.cursize = len;

        MSG_BeginReading();
        control = BigLong(*((int *)net_message.data));
        MSG_ReadLong();
        if (control == -1) {
            break;
        }
        if ((control & (~NETFLAG_LENGTH_MASK)) !=  NETFLAG_CTL) {
            break;
        }
        if ((control & NETFLAG_LENGTH_MASK) != len) {
            break;
        }

        if (MSG_ReadByte() != CCREP_PLAYER_INFO) {
            Sys_Error("Unexpected repsonse to Player Info request\n");
        }

        playerNumber = MSG_ReadByte();
        Q_strcpy(name, MSG_ReadString());
        colors = MSG_ReadLong();
        frags = MSG_ReadLong();
        connectTime = MSG_ReadLong();
        Q_strcpy(address, MSG_ReadString());

        Con_Printf("%s\n  frags:%3i  colors:%u %u  time:%u\n  %s\n", name, frags, colors >> 4, colors & 0x0f, connectTime / 60, address);
    }

    testPollCount--;
    if (testPollCount) {
        SchedulePollProcedure(&testPollProcedure, 0.1);
    } else {
        dfunc.CloseSocket(testSocket);
        testInProgress = false;
    }
}

static void Test_f(void)
{
    char	*host;
    int		n;
    int		max = MAX_SCOREBOARD;
    struct qsockaddr sendaddr;

    if (testInProgress) {
        return;
    }

    host = Cmd_Argv(1);

    if (host && hostCacheCount) {
        for (n = 0; n < hostCacheCount; n++)
            if (Q_strcasecmp(host, hostcache[n].name) == 0) {
                if (hostcache[n].driver != myDriverLevel) {
                    continue;
                }
                net_landriverlevel = hostcache[n].ldriver;
                max = hostcache[n].maxusers;
                Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
                break;
            }
        if (n < hostCacheCount) {
            goto JustDoIt;
        }
    }

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) {
        if (!net_landrivers[net_landriverlevel].initialized) {
            continue;
        }

        // see if we can resolve the host name
        if (dfunc.GetAddrFromName(host, &sendaddr) != -1) {
            break;
        }
    }
    if (net_landriverlevel == net_numlandrivers) {
        return;
    }

JustDoIt:
    testSocket = dfunc.OpenSocket(0);
    if (testSocket == -1) {
        return;
    }

    testInProgress = true;
    testPollCount = 20;
    testDriver = net_landriverlevel;

    for (n = 0; n < max; n++) {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_PLAYER_INFO);
        MSG_WriteByte(&net_message, n);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL |	(net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(testSocket, net_message.data, net_message.cursize, &sendaddr);
    }
    SZ_Clear(&net_message);
    SchedulePollProcedure(&testPollProcedure, 0.1);
}


static qboolean test2InProgress = false;
static int		test2Driver;
static int		test2Socket;

static void Test2_Poll(void);
PollProcedure	test2PollProcedure = {NULL, 0.0, Test2_Poll};

static void Test2_Poll(void)
{
    struct qsockaddr clientaddr;
    int		control;
    int		len;
    char	name[256];
    char	value[256];

    net_landriverlevel = test2Driver;
    name[0] = 0;

    len = dfunc.Read(test2Socket, net_message.data, net_message.maxsize, &clientaddr);
    if (len < sizeof(int)) {
        goto Reschedule;
    }

    net_message.cursize = len;

    MSG_BeginReading();
    control = BigLong(*((int *)net_message.data));
    MSG_ReadLong();
    if (control == -1) {
        goto Error;
    }
    if ((control & (~NETFLAG_LENGTH_MASK)) !=  NETFLAG_CTL) {
        goto Error;
    }
    if ((control & NETFLAG_LENGTH_MASK) != len) {
        goto Error;
    }

    if (MSG_ReadByte() != CCREP_RULE_INFO) {
        goto Error;
    }

    Q_strcpy(name, MSG_ReadString());
    if (name[0] == 0) {
        goto Done;
    }
    Q_strcpy(value, MSG_ReadString());

    Con_Printf("%-16.16s  %-16.16s\n", name, value);

    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
    MSG_WriteString(&net_message, name);
    *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write(test2Socket, net_message.data, net_message.cursize, &clientaddr);
    SZ_Clear(&net_message);

Reschedule:
    SchedulePollProcedure(&test2PollProcedure, 0.05);
    return;

Error:
    Con_Printf("Unexpected repsonse to Rule Info request\n");
Done:
    dfunc.CloseSocket(test2Socket);
    test2InProgress = false;
    return;
}

static void Test2_f(void)
{
    char	*host;
    int		n;
    struct qsockaddr sendaddr;

    if (test2InProgress) {
        return;
    }

    host = Cmd_Argv(1);

    if (host && hostCacheCount) {
        for (n = 0; n < hostCacheCount; n++)
            if (Q_strcasecmp(host, hostcache[n].name) == 0) {
                if (hostcache[n].driver != myDriverLevel) {
                    continue;
                }
                net_landriverlevel = hostcache[n].ldriver;
                Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr));
                break;
            }
        if (n < hostCacheCount) {
            goto JustDoIt;
        }
    }

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) {
        if (!net_landrivers[net_landriverlevel].initialized) {
            continue;
        }

        // see if we can resolve the host name
        if (dfunc.GetAddrFromName(host, &sendaddr) != -1) {
            break;
        }
    }
    if (net_landriverlevel == net_numlandrivers) {
        return;
    }

JustDoIt:
    test2Socket = dfunc.OpenSocket(0);
    if (test2Socket == -1) {
        return;
    }

    test2InProgress = true;
    test2Driver = net_landriverlevel;

    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREQ_RULE_INFO);
    MSG_WriteString(&net_message, "");
    *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write(test2Socket, net_message.data, net_message.cursize, &sendaddr);
    SZ_Clear(&net_message);
    SchedulePollProcedure(&test2PollProcedure, 0.05);
}


int Datagram_Init(void)
{
    int i;
    int csock;

    myDriverLevel = net_driverlevel;
    Cmd_AddCommand("net_stats", NET_Stats_f);

    if (COM_CheckParm("-nolan")) {
        return -1;
    }

    for (i = 0; i < net_numlandrivers; i++) {
        csock = net_landrivers[i].Init();
        if (csock == -1) {
            continue;
        }
        net_landrivers[i].initialized = true;
        net_landrivers[i].controlSock = csock;
    }

#ifdef BAN_TEST
    Cmd_AddCommand("ban", NET_Ban_f);
#endif
    Cmd_AddCommand("test", Test_f);
    Cmd_AddCommand("test2", Test2_f);

    return 0;
}


void Datagram_Shutdown(void)
{
    int i;

//
// shutdown the lan drivers
//
    for (i = 0; i < net_numlandrivers; i++) {
        if (net_landrivers[i].initialized) {
            net_landrivers[i].Shutdown();
            net_landrivers[i].initialized = false;
        }
    }
}


void Datagram_Close(qsocket_t *sock)
{
    sfunc.CloseSocket(sock->socket);
}


void Datagram_Listen(qboolean state)
{
    int i;

    for (i = 0; i < net_numlandrivers; i++)
        if (net_landrivers[i].initialized) {
            net_landrivers[i].Listen(state);
        }
}


static qsocket_t *_Datagram_CheckNewConnections(void)
{
    struct qsockaddr clientaddr;
    struct qsockaddr newaddr;
    int			newsock;
    int			acceptsock;
    qsocket_t	*sock;
    qsocket_t	*s;
    int			len;
    int			command;
    int			control;
    int			ret;

    acceptsock = dfunc.CheckNewConnections();
    if (acceptsock == -1) {
        return NULL;
    }

    SZ_Clear(&net_message);

    len = dfunc.Read(acceptsock, net_message.data, net_message.maxsize, &clientaddr);
    if (len < sizeof(int)) {
        return NULL;
    }
    net_message.cursize = len;

    MSG_BeginReading();
    control = BigLong(*((int *)net_message.data));
    MSG_ReadLong();
    if (control == -1) {
        return NULL;
    }
    if ((control & (~NETFLAG_LENGTH_MASK)) !=  NETFLAG_CTL) {
        return NULL;
    }
    if ((control & NETFLAG_LENGTH_MASK) != len) {
        return NULL;
    }

    command = MSG_ReadByte();
    if (command == CCREQ_SERVER_INFO) {
        if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) {
            return NULL;
        }

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_SERVER_INFO);
        dfunc.GetSocketAddr(acceptsock, &newaddr);
        MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
        MSG_WriteString(&net_message, hostname.string);
        MSG_WriteString(&net_message, sv.name);
        MSG_WriteByte(&net_message, net_activeconnections);
        MSG_WriteByte(&net_message, svs.maxclients);
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);
        return NULL;
    }

    if (command == CCREQ_PLAYER_INFO) {
        int			playerNumber;
        int			activeNumber;
        int			clientNumber;
        client_t	*client;

        playerNumber = MSG_ReadByte();
        activeNumber = -1;
        for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) {
            if (client->active) {
                activeNumber++;
                if (activeNumber == playerNumber) {
                    break;
                }
            }
        }
        if (clientNumber == svs.maxclients) {
            return NULL;
        }

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_PLAYER_INFO);
        MSG_WriteByte(&net_message, playerNumber);
        MSG_WriteString(&net_message, client->name);
        MSG_WriteLong(&net_message, client->colors);
        MSG_WriteLong(&net_message, (int)client->edict->v.frags);
        MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime));
        MSG_WriteString(&net_message, client->netconnection->address);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);

        return NULL;
    }

    if (command == CCREQ_RULE_INFO) {
        char	*prevCvarName;
        cvar_t	*var;

        // find the search start location
        prevCvarName = MSG_ReadString();
        if (*prevCvarName) {
            var = Cvar_FindVar(prevCvarName);
            if (!var) {
                return NULL;
            }
            var = var->next;
        } else {
            var = cvar_vars;
        }

        // search for the next server cvar
        while (var) {
            if (var->server) {
                break;
            }
            var = var->next;
        }

        // send the response

        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_RULE_INFO);
        if (var) {
            MSG_WriteString(&net_message, var->name);
            MSG_WriteString(&net_message, var->string);
        }
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);

        return NULL;
    }

    if (command != CCREQ_CONNECT) {
        return NULL;
    }

    if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) {
        return NULL;
    }

    if (MSG_ReadByte() != NET_PROTOCOL_VERSION) {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_REJECT);
        MSG_WriteString(&net_message, "Incompatible version.\n");
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);
        return NULL;
    }

#ifdef BAN_TEST
    // check for a ban
    if (clientaddr.sa_family == AF_INET) {
        unsigned long testAddr;
        testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr;
        if ((testAddr & banMask) == banAddr) {
            SZ_Clear(&net_message);
            // save space for the header, filled in later
            MSG_WriteLong(&net_message, 0);
            MSG_WriteByte(&net_message, CCREP_REJECT);
            MSG_WriteString(&net_message, "You have been banned.\n");
            *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
            dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
            SZ_Clear(&net_message);
            return NULL;
        }
    }
#endif

    // see if this guy is already connected
    for (s = net_activeSockets; s; s = s->next) {
        if (s->driver != net_driverlevel) {
            continue;
        }
        ret = dfunc.AddrCompare(&clientaddr, &s->addr);
        if (ret >= 0) {
            // is this a duplicate connection reqeust?
            if (ret == 0 && net_time - s->connecttime < 2.0) {
                // yes, so send a duplicate reply
                SZ_Clear(&net_message);
                // save space for the header, filled in later
                MSG_WriteLong(&net_message, 0);
                MSG_WriteByte(&net_message, CCREP_ACCEPT);
                dfunc.GetSocketAddr(s->socket, &newaddr);
                MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
                *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
                dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
                SZ_Clear(&net_message);
                return NULL;
            }
            // it's somebody coming back in from a crash/disconnect
            // so close the old qsocket and let their retry get them back in
            NET_Close(s);
            return NULL;
        }
    }

    // allocate a QSocket
    sock = NET_NewQSocket();
    if (sock == NULL) {
        // no room; try to let him know
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREP_REJECT);
        MSG_WriteString(&net_message, "Server is full.\n");
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
        SZ_Clear(&net_message);
        return NULL;
    }

    // allocate a network socket
    newsock = dfunc.OpenSocket(0);
    if (newsock == -1) {
        NET_FreeQSocket(sock);
        return NULL;
    }

    // connect to the client
    if (dfunc.Connect(newsock, &clientaddr) == -1) {
        dfunc.CloseSocket(newsock);
        NET_FreeQSocket(sock);
        return NULL;
    }

    // everything is allocated, just fill in the details
    sock->socket = newsock;
    sock->landriver = net_landriverlevel;
    sock->addr = clientaddr;
    Q_strcpy(sock->address, dfunc.AddrToString(&clientaddr));

    // send him back the info about the server connection he has been allocated
    SZ_Clear(&net_message);
    // save space for the header, filled in later
    MSG_WriteLong(&net_message, 0);
    MSG_WriteByte(&net_message, CCREP_ACCEPT);
    dfunc.GetSocketAddr(newsock, &newaddr);
    MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr));
//	MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr));
    *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
    dfunc.Write(acceptsock, net_message.data, net_message.cursize, &clientaddr);
    SZ_Clear(&net_message);

    return sock;
}

qsocket_t *Datagram_CheckNewConnections(void)
{
    qsocket_t *ret = NULL;

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
        if (net_landrivers[net_landriverlevel].initialized)
            if ((ret = _Datagram_CheckNewConnections()) != NULL) {
                break;
            }
    return ret;
}


static void _Datagram_SearchForHosts(qboolean xmit)
{
    int		ret;
    int		n;
    int		i;
    struct qsockaddr readaddr;
    struct qsockaddr myaddr;
    int		control;

    dfunc.GetSocketAddr(dfunc.controlSock, &myaddr);
    if (xmit) {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_SERVER_INFO);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize);
        SZ_Clear(&net_message);
    }

    while ((ret = dfunc.Read(dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0) {
        if (ret < sizeof(int)) {
            continue;
        }
        net_message.cursize = ret;

        // don't answer our own query
        if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0) {
            continue;
        }

        // is the cache full?
        if (hostCacheCount == HOSTCACHESIZE) {
            continue;
        }

        MSG_BeginReading();
        control = BigLong(*((int *)net_message.data));
        MSG_ReadLong();
        if (control == -1) {
            continue;
        }
        if ((control & (~NETFLAG_LENGTH_MASK)) !=  NETFLAG_CTL) {
            continue;
        }
        if ((control & NETFLAG_LENGTH_MASK) != ret) {
            continue;
        }

        if (MSG_ReadByte() != CCREP_SERVER_INFO) {
            continue;
        }

        dfunc.GetAddrFromName(MSG_ReadString(), &readaddr);
        // search the cache for this server
        for (n = 0; n < hostCacheCount; n++)
            if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0) {
                break;
            }

        // is it already there?
        if (n < hostCacheCount) {
            continue;
        }

        // add it
        hostCacheCount++;
        Q_strcpy(hostcache[n].name, MSG_ReadString());
        Q_strcpy(hostcache[n].map, MSG_ReadString());
        hostcache[n].users = MSG_ReadByte();
        hostcache[n].maxusers = MSG_ReadByte();
        if (MSG_ReadByte() != NET_PROTOCOL_VERSION) {
            Q_strcpy(hostcache[n].cname, hostcache[n].name);
            hostcache[n].cname[14] = 0;
            Q_strcpy(hostcache[n].name, "*");
            Q_strcat(hostcache[n].name, hostcache[n].cname);
        }
        Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr));
        hostcache[n].driver = net_driverlevel;
        hostcache[n].ldriver = net_landriverlevel;
        Q_strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr));

        // check for a name conflict
        for (i = 0; i < hostCacheCount; i++) {
            if (i == n) {
                continue;
            }
            if (Q_strcasecmp(hostcache[n].name, hostcache[i].name) == 0) {
                i = Q_strlen(hostcache[n].name);
                if (i < 15 && hostcache[n].name[i-1] > '8') {
                    hostcache[n].name[i] = '0';
                    hostcache[n].name[i+1] = 0;
                } else {
                    hostcache[n].name[i-1]++;
                }
                i = -1;
            }
        }
    }
}

void Datagram_SearchForHosts(qboolean xmit)
{
    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) {
        if (hostCacheCount == HOSTCACHESIZE) {
            break;
        }
        if (net_landrivers[net_landriverlevel].initialized) {
            _Datagram_SearchForHosts(xmit);
        }
    }
}


static qsocket_t *_Datagram_Connect(char *host)
{
    struct qsockaddr sendaddr;
    struct qsockaddr readaddr;
    qsocket_t	*sock;
    int			newsock;
    int			ret;
    int			reps;
    double		start_time;
    int			control;
    char		*reason;

    // see if we can resolve the host name
    if (dfunc.GetAddrFromName(host, &sendaddr) == -1) {
        return NULL;
    }

    newsock = dfunc.OpenSocket(0);
    if (newsock == -1) {
        return NULL;
    }

    sock = NET_NewQSocket();
    if (sock == NULL) {
        goto ErrorReturn2;
    }
    sock->socket = newsock;
    sock->landriver = net_landriverlevel;

    // connect to the host
    if (dfunc.Connect(newsock, &sendaddr) == -1) {
        goto ErrorReturn;
    }

    // send the connection request
    Con_Printf("trying...\n");
    SCR_UpdateScreen();
    start_time = net_time;

    for (reps = 0; reps < 3; reps++) {
        SZ_Clear(&net_message);
        // save space for the header, filled in later
        MSG_WriteLong(&net_message, 0);
        MSG_WriteByte(&net_message, CCREQ_CONNECT);
        MSG_WriteString(&net_message, "QUAKE");
        MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
        *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
        dfunc.Write(newsock, net_message.data, net_message.cursize, &sendaddr);
        SZ_Clear(&net_message);
        do {
            ret = dfunc.Read(newsock, net_message.data, net_message.maxsize, &readaddr);
            // if we got something, validate it
            if (ret > 0) {
                // is it from the right place?
                if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0) {
#ifdef DEBUG
                    Con_Printf("wrong reply address\n");
                    Con_Printf("Expected: %s\n", StrAddr(&sendaddr));
                    Con_Printf("Received: %s\n", StrAddr(&readaddr));
                    SCR_UpdateScreen();
#endif
                    ret = 0;
                    continue;
                }

                if (ret < sizeof(int)) {
                    ret = 0;
                    continue;
                }

                net_message.cursize = ret;
                MSG_BeginReading();

                control = BigLong(*((int *)net_message.data));
                MSG_ReadLong();
                if (control == -1) {
                    ret = 0;
                    continue;
                }
                if ((control & (~NETFLAG_LENGTH_MASK)) !=  NETFLAG_CTL) {
                    ret = 0;
                    continue;
                }
                if ((control & NETFLAG_LENGTH_MASK) != ret) {
                    ret = 0;
                    continue;
                }
            }
        } while (ret == 0 && (SetNetTime() - start_time) < 2.5);
        if (ret) {
            break;
        }
        Con_Printf("still trying...\n");
        SCR_UpdateScreen();
        start_time = SetNetTime();
    }

    if (ret == 0) {
        reason = "No Response";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    if (ret == -1) {
        reason = "Network Error";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    ret = MSG_ReadByte();
    if (ret == CCREP_REJECT) {
        reason = MSG_ReadString();
        Con_Printf(reason);
        Q_strncpy(m_return_reason, reason, 31);
        goto ErrorReturn;
    }

    if (ret == CCREP_ACCEPT) {
        Q_memcpy(&sock->addr, &sendaddr, sizeof(struct qsockaddr));
        dfunc.SetSocketPort(&sock->addr, MSG_ReadLong());
    } else {
        reason = "Bad Response";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    dfunc.GetNameFromAddr(&sendaddr, sock->address);

    Con_Printf("Connection accepted\n");
    sock->lastMessageTime = SetNetTime();

    // switch the connection to the specified address
    if (dfunc.Connect(newsock, &sock->addr) == -1) {
        reason = "Connect to Game failed";
        Con_Printf("%s\n", reason);
        Q_strcpy(m_return_reason, reason);
        goto ErrorReturn;
    }

    m_return_onerror = false;
    return sock;

ErrorReturn:
    NET_FreeQSocket(sock);
ErrorReturn2:
    dfunc.CloseSocket(newsock);
    if (m_return_onerror) {
        key_dest = key_menu;
        m_state = m_return_state;
        m_return_onerror = false;
    }
    return NULL;
}

qsocket_t *Datagram_Connect(char *host)
{
    qsocket_t *ret = NULL;

    for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++)
        if (net_landrivers[net_landriverlevel].initialized)
            if ((ret = _Datagram_Connect(host)) != NULL) {
                break;
            }
    return ret;
}
