/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2010-2014 QuakeSpasm developers

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.

*/

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

#include "q_stdinc.hpp"
#include "arch_def.hpp"
#include "net_sys.hpp"
#include "quakedef.hpp"
#include "net_defs.hpp"
#include "net_dgrm.hpp"

#include <cstring>

// 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 */
static int packetsSent = 0;
static int packetsReSent = 0;
static int packetsReceived = 0;
static int receivedDuplicateCount = 0;
static int shortPacketCount = 0;
static int droppedDatagrams;

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

static int myDriverLevel;

extern bool m_return_onerror;
extern char m_return_reason[32];


static 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;
}


#ifdef BAN_TEST

static struct in_addr banAddr;
static struct in_addr banMask;

static void NET_Ban_f(void) {
    char addrStr[32];
    char maskStr[32];
    void (*print_fn)(const char *fmt, ...) FUNCP_PRINTF(1, 2);

    if (command::last_source == command::source::command) {
        if (!sv.active) {
            command::forward_to_server();
            return;
        }
        print_fn = console::info;
    } else {
        if (pr_global_struct->deathmatch)
            return;
        print_fn = SV_ClientPrintf;
    }

    switch (command::argc()) {
        case 1:
            if (banAddr.s_addr != INADDR_ANY) {
                std::strcpy(addrStr, inet_ntoa(banAddr));
                std::strcpy(maskStr, inet_ntoa(banMask));
                print_fn("Banning %s [%s]\n", addrStr, maskStr);
            } else
                print_fn("Banning not active\n");
            break;

        case 2:
            if (std::is_eq(common::strcasecmp(command::argv(1)->c_str(), "off")))
                banAddr.s_addr = INADDR_ANY;
            else
                banAddr.s_addr = inet_addr(command::argv(1)->c_str());
            banMask.s_addr = INADDR_NONE;
            break;

        case 3:
            banAddr.s_addr = inet_addr(command::argv(1)->c_str());
            banMask.s_addr = inet_addr(command::argv(2)->c_str());
            break;

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


int Datagram_SendMessage(qsocket_t *sock, net::msg &msg) {
    unsigned int packetLen;
    unsigned int dataLen;
    unsigned int eom;

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

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

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

    std::memcpy(sock->sendMessage, msg.data(), msg.size());
    sock->sendMessageLength = msg.size();

    if (msg.size() <= MAX_DATAGRAM) {
        dataLen = msg.size();
        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++);
    std::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;
}


static 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++);
    std::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;
}


static 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);
    std::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;
}


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

    return sock->canSend;
}


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


int Datagram_SendUnreliableMessage(qsocket_t *sock, net::msg &msg) {
    int packetLen;

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

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

    packetLen = NET_HEADERSIZE + msg.size();

    packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE);
    packetBuffer.sequence = BigLong(sock->unreliableSendSequence++);
    std::memcpy(packetBuffer.data, msg.data(), msg.size());

    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 = (unsigned int) sfunc.Read(sock->socket, (byte *) &packetBuffer,
                                           NET_DATAGRAMSIZE, &readaddr);

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

        if (length == 0)
            break;

        if (length == (unsigned int) -1) {
            console::info("Read error\n");
            return -1;
        }

        if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0) {
            console::info("Forged packet received\n");
            console::info("Expected: %s\n", StrAddr(&sock->addr));
            console::info("Received: %s\n", StrAddr(&readaddr));
            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) {
                console::debug("Got a stale datagram\n");
                ret = 0;
                break;
            }
            if (sequence != sock->unreliableReceiveSequence) {
                count = sequence - sock->unreliableReceiveSequence;
                droppedDatagrams += count;
                console::debug("Dropped %u datagram(s)\n", count);
            }
            sock->unreliableReceiveSequence = sequence + 1;

            length -= NET_HEADERSIZE;

            net_message.clear();
            net_message.write(packetBuffer.data, length);

            ret = 2;
            break;
        }

        if (flags & NETFLAG_ACK) {
            if (sequence != (sock->sendSequence - 1)) {
                console::debug("Stale ACK received\n");
                continue;
            }
            if (sequence == sock->ackSequence) {
                sock->ackSequence++;
                if (sock->ackSequence != sock->sendSequence)
                    console::debug("ack sequencing error\n");
            } else {
                console::debug("Duplicate ACK received\n");
                continue;
            }
            sock->sendMessageLength -= MAX_DATAGRAM;
            if (sock->sendMessageLength > 0) {
                memmove(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) {
                net_message.clear();
                net_message.write(sock->receiveMessage, sock->receiveMessageLength);
                net_message.write(packetBuffer.data, length);
                sock->receiveMessageLength = 0;

                ret = 1;
                break;
            }

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

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

    return ret;
}


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

static void NET_Stats_f(void) {
    qsocket_t *s;

    if (command::argc() == 1) {
        console::info("unreliable messages sent   = %i\n", unreliableMessagesSent);
        console::info("unreliable messages recv   = %i\n", unreliableMessagesReceived);
        console::info("reliable messages sent     = %i\n", messagesSent);
        console::info("reliable messages received = %i\n", messagesReceived);
        console::info("packetsSent                = %i\n", packetsSent);
        console::info("packetsReSent              = %i\n", packetsReSent);
        console::info("packetsReceived            = %i\n", packetsReceived);
        console::info("receivedDuplicateCount     = %i\n", receivedDuplicateCount);
        console::info("shortPacketCount           = %i\n", shortPacketCount);
        console::info("droppedDatagrams           = %i\n", droppedDatagrams);
    } else if (std::strcmp(command::argv(1)->c_str(), "*") == 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 (std::is_eq(common::strcasecmp(command::argv(1)->c_str(), s->address)))
                break;
        }

        if (s == NULL) {
            for (s = net_freeSockets; s; s = s->next) {
                if (std::is_eq(common::strcasecmp(command::argv(1)->c_str(), s->address)))
                    break;
            }
        }

        if (s == NULL)
            return;

        PrintStats(s);
    }
}


// recognize ip:port (based on ProQuake)
static const char *Strip_Port(const char *host) {
    static char noport[MAX_QPATH];
    /* array size as in Host_Connect_f() */
    char *p;
    int port;

    if (!host || !*host)
        return host;
    q_strlcpy(noport, host, sizeof(noport));
    if ((p = std::strrchr(noport, ':')) == NULL)
        return host;
    *p++ = '\0';
    port = std::atoi(p);
    if (port > 0 && port < 65536 && port != net_hostport) {
        net_hostport = port;
        console::info("Port set to %d\n", net_hostport);
    }
    return noport;
}


static bool testInProgress = false;
static int testPollCount;
static int testDriver;
static sys_socket_t testSocket;

static void Test_Poll(void *);

static PollProcedure testPollProcedure = {NULL, 0.0, Test_Poll};

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

    net_landriverlevel = testDriver;

    while (1) {
        const auto buf = new std::uint8_t[MAX_DATAGRAM];
        len = dfunc.Read(testSocket, &buf[0], MAX_DATAGRAM, &clientaddr);
        if (len < (int) sizeof(int)) {
            delete []buf;
            break;
        }
        net_message.write(buf, len);
        delete []buf;


        net_message.begin_reading();
        control = BigLong(*((int *) net_message.data()));
        net_message.read_long();
        if (control == -1)
            break;
        if ((control & (~NETFLAG_LENGTH_MASK)) != (int) NETFLAG_CTL)
            break;
        if ((control & NETFLAG_LENGTH_MASK) != len)
            break;

        if (net_message.read_byte().value() != CCREP_PLAYER_INFO)
            Sys_Error("Unexpected response to Player Info request\n");

        net_message.read_byte().value(); /* playerNumber */
        std::strcpy(name, net_message.read_string().value().c_str());
        colors = net_message.read_long().value();
        frags = net_message.read_long().value();
        connectTime = net_message.read_long().value();
        std::strcpy(address, net_message.read_string().value().c_str());

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

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

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

    if (testInProgress)
        return;

    host = Strip_Port(command::argv(1).value_or("").c_str());

    if (host && hostCacheCount) {
        for (n = 0; n < hostCacheCount; n++) {
            if (std::is_eq(common::strcasecmp(host, hostcache[n].name))) {
                if (hostcache[n].driver != myDriverLevel)
                    continue;
                net_landriverlevel = hostcache[n].ldriver;
                maxusers = hostcache[n].maxusers;
                std::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) {
        console::info("Could not resolve %s\n", host);
        return;
    }

JustDoIt:
    testSocket = dfunc.Open_Socket(0);
    if (testSocket == INVALID_SOCKET)
        return;

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

    for (n = 0; n < maxusers; n++) {
        net_message.clear();
        // save space for the header, filled in later
        net_message.write_long(0);
        net_message.write_byte(CCREQ_PLAYER_INFO);
        net_message.write_byte(n);
        *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
        dfunc.Write(testSocket, net_message.data(), net_message.size(), &sendaddr);
    }
    net_message.clear();
    SchedulePollProcedure(&testPollProcedure, 0.1);
}


static bool test2InProgress = false;
static int test2Driver;
static sys_socket_t test2Socket;

static void Test2_Poll(void *);

static PollProcedure test2PollProcedure = {NULL, 0.0, Test2_Poll};

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

    net_landriverlevel = test2Driver;
    name[0] = 0;

    auto buf = new std::uint8_t[MAX_DATAGRAM];
    len = dfunc.Read(test2Socket, &buf[0], MAX_DATAGRAM, &clientaddr);
    if (len < (int) sizeof(int)) {
        delete []buf;
        goto Reschedule;
    }
    net_message.write(buf, len);
    delete []buf;


    net_message.begin_reading();
    control = BigLong(*((int *) net_message.data()));
    net_message.read_long();
    if (control == -1)
        goto Error;
    if ((control & (~NETFLAG_LENGTH_MASK)) != (int) NETFLAG_CTL)
        goto Error;
    if ((control & NETFLAG_LENGTH_MASK) != len)
        goto Error;

    if (net_message.read_byte().value() != CCREP_RULE_INFO)
        goto Error;

    std::strcpy(name, net_message.read_string().value().c_str());
    if (name[0] == 0)
        goto Done;
    std::strcpy(value, net_message.read_string().value().c_str());

    console::info("%-16.16s  %-16.16s\n", name, value);

    net_message.clear();
    // save space for the header, filled in later
    net_message.write_long(0);
    net_message.write_byte(CCREQ_RULE_INFO);
    net_message.write_string(name);
    *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
    dfunc.Write(test2Socket, net_message.data(), net_message.size(), &clientaddr);
    net_message.clear();

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

Error:
    console::info("Unexpected response to Rule Info request\n");
Done:
    dfunc.Close_Socket(test2Socket);
    test2InProgress = false;
    return;
}

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

    if (test2InProgress)
        return;

    host = Strip_Port(command::argv(1).value_or("").c_str());

    if (host && hostCacheCount) {
        for (n = 0; n < hostCacheCount; n++) {
            if (std::is_eq(common::strcasecmp(host, hostcache[n].name))) {
                if (hostcache[n].driver != myDriverLevel)
                    continue;
                net_landriverlevel = hostcache[n].ldriver;
                std::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) {
        console::info("Could not resolve %s\n", host);
        return;
    }

JustDoIt:
    test2Socket = dfunc.Open_Socket(0);
    if (test2Socket == INVALID_SOCKET)
        return;

    test2InProgress = true;
    test2Driver = net_landriverlevel;

    net_message.clear();
    // save space for the header, filled in later
    net_message.write_long(0);
    net_message.write_byte(CCREQ_RULE_INFO);
    net_message.write_string("");
    *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
    dfunc.Write(test2Socket, net_message.data(), net_message.size(), &sendaddr);
    net_message.clear();
    SchedulePollProcedure(&test2PollProcedure, 0.05);
}


int Datagram_Init(void) {
    int i, num_inited;
    sys_socket_t csock;

#ifdef BAN_TEST
    banAddr.s_addr = INADDR_ANY;
    banMask.s_addr = INADDR_NONE;
#endif
    myDriverLevel = net_driverlevel;

    command::add("net_stats", NET_Stats_f);

    if (common::safe_mode || common::check_param("-nolan").has_value())
        return -1;

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

    if (num_inited == 0)
        return -1;

#ifdef BAN_TEST
    command::add("ban", NET_Ban_f);
#endif
    command::add("test", Test_f);
    command::add("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.Close_Socket(sock->socket);
}


void Datagram_Listen(bool 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;
    sys_socket_t newsock;
    sys_socket_t acceptsock;
    qsocket_t *sock;
    qsocket_t *s;
    int len;
    int command;
    int control;
    int ret;

    acceptsock = dfunc.CheckNewConnections();
    if (acceptsock == INVALID_SOCKET)
        return NULL;

    net_message.clear();

    auto buf = new byte[MAX_DATAGRAM];
    len = dfunc.Read(acceptsock, &buf[0], MAX_DATAGRAM, &clientaddr);
    if (len < (int) sizeof(int)) {
        delete []buf;
        return nullptr;
    }
    net_message.write(buf, len);
    delete []buf;

    net_message.begin_reading();
    control = BigLong(*((int *) net_message.data()));
    net_message.read_long();
    if (control == -1)
        return NULL;
    if ((control & (~NETFLAG_LENGTH_MASK)) != (int) NETFLAG_CTL)
        return NULL;
    if ((control & NETFLAG_LENGTH_MASK) != len)
        return NULL;

    command = net_message.read_byte().value();
    if (command == CCREQ_SERVER_INFO) {
        if (std::strcmp(net_message.read_string().value().c_str(), "QUAKE") != 0)
            return NULL;

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

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

        playerNumber = net_message.read_byte().value();
        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;

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

        return NULL;
    }

    if (command == CCREQ_RULE_INFO) {
        // find the search start location
        auto prevCvarName = net_message.read_string().value();
        auto var = convar::find_var_after(prevCvarName, std::nullopt);

        // send the response
        net_message.clear();
        // save space for the header, filled in later
        net_message.write_long(0);
        net_message.write_byte(CCREP_RULE_INFO);
        if (var) {
            net_message.write_string(var.value()->name.c_str());
            net_message.write_string(var.value()->string);
        }
        *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data(), net_message.size(), &clientaddr);
        net_message.clear();

        return NULL;
    }

    if (command != CCREQ_CONNECT)
        return NULL;

    if (std::strcmp(net_message.read_string().value().c_str(), "QUAKE") != 0)
        return NULL;

    if (net_message.read_byte().value() != NET_PROTOCOL_VERSION) {
        net_message.clear();
        // save space for the header, filled in later
        net_message.write_long(0);
        net_message.write_byte(CCREP_REJECT);
        net_message.write_string("Incompatible version.\n");
        *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data(), net_message.size(), &clientaddr);
        net_message.clear();
        return NULL;
    }

#ifdef BAN_TEST
    // check for a ban
    if (clientaddr.qsa_family == AF_INET) {
        in_addr_t testAddr;
        testAddr = ((struct sockaddr_in *) &clientaddr)->sin_addr.s_addr;
        if ((testAddr & banMask.s_addr) == banAddr.s_addr) {
            net_message.clear();
            // save space for the header, filled in later
            net_message.write_long(0);
            net_message.write_byte(CCREP_REJECT);
            net_message.write_string("You have been banned.\n");
            *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
            dfunc.Write(acceptsock, net_message.data(), net_message.size(), &clientaddr);
            net_message.clear();
            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
                net_message.clear();
                // save space for the header, filled in later
                net_message.write_long(0);
                net_message.write_byte(CCREP_ACCEPT);
                dfunc.GetSocketAddr(s->socket, &newaddr);
                net_message.write_long(dfunc.GetSocketPort(&newaddr));
                *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
                dfunc.Write(acceptsock, net_message.data(), net_message.size(), &clientaddr);
                net_message.clear();
                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
    {
        net_message.clear();
        // save space for the header, filled in later
        net_message.write_long(0);
        net_message.write_byte(CCREP_REJECT);
        net_message.write_string("Server is full.\n");
        *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
        dfunc.Write(acceptsock, net_message.data(), net_message.size(), &clientaddr);
        net_message.clear();
        return NULL;
    }

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

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

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

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

    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(bool xmit) {
    int ret;
    int n;
    int i;
    struct qsockaddr readaddr;
    struct qsockaddr myaddr;
    int control;

    dfunc.GetSocketAddr(dfunc.controlSock, &myaddr);
    if (xmit) {
        net_message.clear();
        // save space for the header, filled in later
        net_message.write_long(0);
        net_message.write_byte(CCREQ_SERVER_INFO);
        net_message.write_string("QUAKE");
        net_message.write_byte(NET_PROTOCOL_VERSION);
        *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
        dfunc.Broadcast(dfunc.controlSock, net_message.data(), net_message.size());
        net_message.clear();
    }

    auto buf = new std::uint8_t[MAX_DATAGRAM];
    while ((ret = dfunc.Read(dfunc.controlSock, &buf[0], MAX_DATAGRAM, &readaddr)) > 0) {
        if (ret < (int) sizeof(int))
            continue;
        net_message.write(buf, ret);

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

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

        net_message.begin_reading();
        control = BigLong(*((int *) net_message.data()));
        net_message.read_long();
        if (control == -1)
            continue;
        if ((control & (~NETFLAG_LENGTH_MASK)) != (int) NETFLAG_CTL)
            continue;
        if ((control & NETFLAG_LENGTH_MASK) != ret)
            continue;

        if (net_message.read_byte().value() != CCREP_SERVER_INFO)
            continue;

        dfunc.GetAddrFromName(net_message.read_string().value().c_str(), &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++;
        std::strcpy(hostcache[n].name, net_message.read_string().value().c_str());
        std::strcpy(hostcache[n].map, net_message.read_string().value().c_str());
        hostcache[n].users = net_message.read_byte().value();
        hostcache[n].maxusers = net_message.read_byte().value();
        if (net_message.read_byte().value() != NET_PROTOCOL_VERSION) {
            std::strcpy(hostcache[n].cname, hostcache[n].name);
            hostcache[n].cname[14] = 0;
            std::strcpy(hostcache[n].name, "*");
            std::strcat(hostcache[n].name, hostcache[n].cname);
        }
        std::memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr));
        hostcache[n].driver = net_driverlevel;
        hostcache[n].ldriver = net_landriverlevel;
        std::strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr));

        // check for a name conflict
        for (i = 0; i < hostCacheCount; i++) {
            if (i == n)
                continue;
            if (std::is_eq(common::strcasecmp(hostcache[n].name, hostcache[i].name))) {
                i = std::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;
            }
        }
    }
    delete []buf;
}

void Datagram_SearchForHosts(bool 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(const char *host) {
    struct qsockaddr sendaddr;
    struct qsockaddr readaddr;
    qsocket_t *sock;
    sys_socket_t newsock;
    int ret;
    int reps;
    double start_time;
    int control;
    std::string reason;

    // see if we can resolve the host name
    if (dfunc.GetAddrFromName(host, &sendaddr) == -1) {
        console::info("Could not resolve %s\n", host);
        return NULL;
    }

    newsock = dfunc.Open_Socket(0);
    if (newsock == INVALID_SOCKET)
        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
    console::info("trying...\n");
    SCR_UpdateScreen();
    start_time = net_time;

    for (reps = 0; reps < 3; reps++) {
        net_message.clear();
        // save space for the header, filled in later
        net_message.write_long(0);
        net_message.write_byte(CCREQ_CONNECT);
        net_message.write_string("QUAKE");
        net_message.write_byte(NET_PROTOCOL_VERSION);
        *((int *) net_message.data()) = BigLong(NETFLAG_CTL | (net_message.size() & NETFLAG_LENGTH_MASK));
        dfunc.Write(newsock, net_message.data(), net_message.size(), &sendaddr);
        net_message.clear();
        auto buf = new std::uint8_t[MAX_DATAGRAM];
        do {
            ret = dfunc.Read(newsock, &buf[0], MAX_DATAGRAM, &readaddr);
            // if we got something, validate it
            if (ret > 0) {
                // is it from the right place?
                if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0) {
                    console::info("wrong reply address\n");
                    console::info("Expected: %s | %s\n", dfunc.AddrToString(&sendaddr), StrAddr(&sendaddr));
                    console::info("Received: %s | %s\n", dfunc.AddrToString(&readaddr), StrAddr(&readaddr));
                    SCR_UpdateScreen();
                    ret = 0;
                    continue;
                }

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

                net_message.write(buf, ret);
                net_message.begin_reading();

                control = BigLong(*((int *) net_message.data()));
                net_message.read_long();
                if (control == -1) {
                    ret = 0;
                    continue;
                }
                if ((control & (~NETFLAG_LENGTH_MASK)) != (int) NETFLAG_CTL) {
                    ret = 0;
                    continue;
                }
                if ((control & NETFLAG_LENGTH_MASK) != ret) {
                    ret = 0;
                    continue;
                }
            }
        } while (ret == 0 && (SetNetTime() - start_time) < 2.5);
        delete []buf;

        if (ret)
            break;

        console::info("still trying...\n");
        SCR_UpdateScreen();
        start_time = SetNetTime();
    }

    if (ret == 0) {
        reason = "No Response";
        console::info("%s\n", reason.c_str());
        std::strcpy(m_return_reason, reason.c_str());
        goto ErrorReturn;
    }

    if (ret == -1) {
        reason = "Network Error";
        console::info("%s\n", reason.c_str());
        std::strcpy(m_return_reason, reason.c_str());
        goto ErrorReturn;
    }

    ret = net_message.read_byte().value();
    if (ret == CCREP_REJECT) {
        reason = net_message.read_string().value();
        console::info("%s\n", reason.c_str());
        q_strlcpy(m_return_reason, reason.c_str(), sizeof(m_return_reason));
        goto ErrorReturn;
    }

    if (ret == CCREP_ACCEPT) {
        std::memcpy(&sock->addr, &sendaddr, sizeof(struct qsockaddr));
        dfunc.SetSocketPort(&sock->addr, net_message.read_long().value());
    } else {
        reason = "Bad Response";
        console::info("%s\n", reason.c_str());
        std::strcpy(m_return_reason, reason.c_str());
        goto ErrorReturn;
    }

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

    console::info("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";
        console::info("%s\n", reason.c_str());
        std::strcpy(m_return_reason, reason.c_str());
        goto ErrorReturn;
    }

    m_return_onerror = false;
    return sock;

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

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

    host = Strip_Port(host);
    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;
}
