Skip to content
Snippets Groups Projects
d_net.c 36.3 KiB
Newer Older
Alam Ed Arias's avatar
Alam Ed Arias committed
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2018 by Sonic Team Junior.
Alam Ed Arias's avatar
Alam Ed Arias committed
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  d_net.c
/// \brief SRB2 network game communication and protocol, all OS independent parts.
//
///        Implement a Sliding window protocol without receiver window
///        (out of order reception)
///        This protocol uses a mix of "goback n" and "selective repeat" implementation
///        The NOTHING packet is sent when connection is idle to acknowledge packets

#include "doomdef.h"
#include "g_game.h"
Eidolon's avatar
Eidolon committed
#include "i_time.h"
Alam Ed Arias's avatar
Alam Ed Arias committed
#include "i_net.h"
#include "i_system.h"
#include "m_argv.h"
#include "d_net.h"
#include "w_wad.h"
#include "d_netfil.h"
#include "d_clisrv.h"
#include "z_zone.h"
#include "i_tcp.h"
Marco Z's avatar
Marco Z committed
#include "d_main.h" // srb2home
Alam Ed Arias's avatar
Alam Ed Arias committed

//
// NETWORKING
//
// gametic is the tic about to (or currently being) run
// Server:
Alam Ed Arias's avatar
Alam Ed Arias committed
//   maketic is the tic that hasn't had control made for it yet
//   nettics is the tic for each node
//   firstticstosend is the lowest value of nettics
// Client:
//   neededtic is the tic needed by the client to run the game
//   firstticstosend is used to optimize a condition
// Normally maketic >= gametic > 0
Alam Ed Arias's avatar
Alam Ed Arias committed

#define FORCECLOSE 0x8000
tic_t connectiontimeout = (10*TICRATE);
Alam Ed Arias's avatar
Alam Ed Arias committed

/// \brief network packet
doomcom_t *doomcom = NULL;
/// \brief network packet data, points inside doomcom
doomdata_t *netbuffer = NULL;
James R.'s avatar
James R. committed
/// \brief hole punching packet, also points inside doomcom
holepunch_t *holepunchpacket = NULL;
Alam Ed Arias's avatar
Alam Ed Arias committed

Alam Ed Arias's avatar
Alam Ed Arias committed
FILE *debugfile = NULL; // put some net info in a file during the game
Alam Ed Arias's avatar
Alam Ed Arias committed

#define MAXREBOUND 8
static doomdata_t reboundstore[MAXREBOUND];
static INT16 reboundsize[MAXREBOUND];
static INT32 rebound_head, rebound_tail;

/// \brief bandwith of netgame
INT32 net_bandwidth;

/// \brief max length per packet
INT16 hardware_MAXPACKETLENGTH;

Alam Ed Arias's avatar
Alam Ed Arias committed
void (*I_NetSend)(void) = NULL;
boolean (*I_NetCanSend)(void) = NULL;
boolean (*I_NetCanGet)(void) = NULL;
void (*I_NetCloseSocket)(void) = NULL;
void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
void (*I_NetRequestHolePunch)(INT32 node) = NULL;
James R.'s avatar
James R. committed
void (*I_NetRegisterHolePunch)(void) = NULL;
Alam Ed Arias's avatar
Alam Ed Arias committed
boolean (*I_NetOpenSocket)(void) = NULL;
boolean (*I_Ban) (INT32 node) = NULL;
void (*I_ClearBans)(void) = NULL;
const char *(*I_GetNodeAddress) (INT32 node) = NULL;
const char *(*I_GetBanAddress) (size_t ban) = NULL;
const char *(*I_GetBanMask) (size_t ban) = NULL;
toaster's avatar
toaster committed
const char *(*I_GetBanUsername) (size_t ban) = NULL;
const char *(*I_GetBanReason) (size_t ban) = NULL;
toaster's avatar
toaster committed
time_t (*I_GetUnbanTime) (size_t ban) = NULL;
Alam Ed Arias's avatar
Alam Ed Arias committed
boolean (*I_SetBanAddress) (const char *address, const char *mask) = NULL;
toaster's avatar
toaster committed
boolean (*I_SetBanUsername) (const char *username) = NULL;
boolean (*I_SetBanReason) (const char *reason) = NULL;
toaster's avatar
toaster committed
boolean (*I_SetUnbanTime) (time_t timestamp) = NULL;
Alam Ed Arias's avatar
Alam Ed Arias committed


// network stats
static tic_t statstarttic;
INT32 getbytes = 0;
INT64 sendbytes = 0;
static INT32 retransmit = 0, duppacket = 0;
static INT32 sendackpacket = 0, getackpacket = 0;
INT32 ticruned = 0, ticmiss = 0;

// globals
INT32 getbps, sendbps;
float lostpercent, duppercent, gamelostpercent;
INT32 packetheaderlength;

boolean Net_GetNetStat(void)
{
	const tic_t t = I_GetTime();
	static INT64 oldsendbyte = 0;
	if (statstarttic+STATLENGTH <= t)
	{
		const tic_t df = t-statstarttic;
		const INT64 newsendbyte = sendbytes - oldsendbyte;
		sendbps = (INT32)(newsendbyte*TICRATE)/df;
		getbps = (getbytes*TICRATE)/df;
		if (sendackpacket)
			lostpercent = 100.0f*(float)retransmit/(float)sendackpacket;
		else
			lostpercent = 0.0f;
		if (getackpacket)
			duppercent = 100.0f*(float)duppacket/(float)getackpacket;
		else
			duppercent = 0.0f;
		if (ticruned)
			gamelostpercent = 100.0f*(float)ticmiss/(float)ticruned;
		else
			gamelostpercent = 0.0f;

		ticmiss = ticruned = 0;
		oldsendbyte = sendbytes;
		getbytes = 0;
		sendackpacket = getackpacket = duppacket = retransmit = 0;
		statstarttic = t;

		return 1;
	}
	return 0;
}

// -----------------------------------------------------------------
// Some structs and functions for acknowledgement of packets
// -----------------------------------------------------------------
#define MAXACKPACKETS 96 // Minimum number of nodes (wat)
Alam Ed Arias's avatar
Alam Ed Arias committed
#define MAXACKTOSEND 96
#define URGENTFREESLOTNUM 10
Alam Ed Arias's avatar
Alam Ed Arias committed
#define ACKTOSENDTIMEOUT (TICRATE/11)
Alam Ed Arias's avatar
Alam Ed Arias committed

#ifndef NONET
typedef struct
{
	UINT8 acknum;
	UINT8 nextacknum;
	UINT8 destinationnode; // The node to send the ack to
	tic_t senttime; // The time when the ack was sent
	UINT16 length; // The packet size
	UINT16 resentnum; // The number of times the ack has been resent
Alam Ed Arias's avatar
Alam Ed Arias committed
	union {
		SINT8 raw[MAXPACKETLENGTH];
		doomdata_t data;
	} pak;
} ackpak_t;
#endif

typedef enum
{
	NF_CLOSE = 1, // Flag is set when connection is closing
	NF_TIMEOUT = 2, // Flag is set when the node got a timeout
Alam Ed Arias's avatar
Alam Ed Arias committed
} node_flags_t;

#ifndef NONET
// Table of packets that were not acknowleged can be resent (the sender window)
Alam Ed Arias's avatar
Alam Ed Arias committed
static ackpak_t ackpak[MAXACKPACKETS];
#endif

typedef struct
{
	// ack return to send (like sliding window protocol)
	UINT8 firstacktosend;

	// when no consecutive packets are received we keep in mind what packets
	// we already received in a queue
	UINT8 acktosend_head;
	UINT8 acktosend_tail;
	UINT8 acktosend[MAXACKTOSEND];

	// automatically send keep alive packet when not enough trafic
	tic_t lasttimeacktosend_sent;
	// detect connection lost
	tic_t lasttimepacketreceived;

	// flow control: do not send too many packets with ack
	UINT8 remotefirstack;
	UINT8 nextacknum;

	UINT8 flags;
} node_t;

static node_t nodes[MAXNETNODES];
#define NODETIMEOUT 14
Alam Ed Arias's avatar
Alam Ed Arias committed

#ifndef NONET
// return <0 if a < b (mod 256)
//         0 if a = n (mod 256)
//        >0 if a > b (mod 256)
// mnemonic: to use it compare to 0: cmpack(a,b)<0 is "a < b" ...
FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b)
{
	register INT32 d = a - b;

	if (d >= 127 || d < -128)
		return -d;
	return d;
}

/** Sets freeack to a free acknum and copies the netbuffer in the ackpak table
  *
  * \param freeack  The address to store the free acknum at
  * \param lowtimer ???
  * \return True if a free acknum was found
  */
Alam Ed Arias's avatar
Alam Ed Arias committed
static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
{
	node_t *node = &nodes[doomcom->remotenode];
	INT32 i, numfreeslot = 0;
Alam Ed Arias's avatar
Alam Ed Arias committed

	if (cmpack((UINT8)((node->remotefirstack + MAXACKTOSEND) % 256), node->nextacknum) < 0)
	{
		DEBFILE(va("too fast %d %d\n",node->remotefirstack,node->nextacknum));
		return false;
	}

	for (i = 0; i < MAXACKPACKETS; i++)
		if (!ackpak[i].acknum)
		{
			// For low priority packets, make sure to let freeslots so urgent packets can be sent
			if (netbuffer->packettype >= PT_CANFAIL)
			{
				numfreeslot++;
				if (numfreeslot <= URGENTFREESLOTNUM)
					continue;
			}
Alam Ed Arias's avatar
Alam Ed Arias committed

			ackpak[i].acknum = node->nextacknum;
			ackpak[i].nextacknum = node->nextacknum;
			node->nextacknum++;
			if (!node->nextacknum)
				node->nextacknum++;
			ackpak[i].destinationnode = (UINT8)(node - nodes);
			ackpak[i].length = doomcom->datalength;
			if (lowtimer)
			{
				// Lowtime means can't be sent now so try it as soon as possible
Alam Ed Arias's avatar
Alam Ed Arias committed
				ackpak[i].senttime = 0;
				ackpak[i].resentnum = 1;
			}
			else
			{
				ackpak[i].senttime = I_GetTime();
				ackpak[i].resentnum = 0;
			}
			M_Memcpy(ackpak[i].pak.raw, netbuffer, ackpak[i].length);

			*freeack = ackpak[i].acknum;

			sendackpacket++; // For stat
Alam Ed Arias's avatar
Alam Ed Arias committed

			return true;
		}
#ifdef PARANOIA
	CONS_Debug(DBG_NETPLAY, "No more free ackpacket\n");
#endif
	if (netbuffer->packettype < PT_CANFAIL)
		I_Error("Connection lost\n");
	return false;
}

/** Counts how many acks are free
  *
  * \param urgent True if the type of the packet meant to
  *               use an ack is lower than PT_CANFAIL
  *               If for some reason you don't want use it
  *               for any packet type in particular,
  *               just set to false
  * \return The number of free acks
  *
  */
INT32 Net_GetFreeAcks(boolean urgent)
{
	INT32 i, numfreeslot = 0;
	INT32 n = 0; // Number of free acks found

	for (i = 0; i < MAXACKPACKETS; i++)
		if (!ackpak[i].acknum)
		{
			// For low priority packets, make sure to let freeslots so urgent packets can be sent
			if (!urgent)
			{
				numfreeslot++;
				if (numfreeslot <= URGENTFREESLOTNUM)
					continue;
			}

			n++;
		}

	return n;
}

// Get a ack to send in the queue of this node
Alam Ed Arias's avatar
Alam Ed Arias committed
static UINT8 GetAcktosend(INT32 node)
{
	nodes[node].lasttimeacktosend_sent = I_GetTime();
	return nodes[node].firstacktosend;
}

static void RemoveAck(INT32 i)
Alam Ed Arias's avatar
Alam Ed Arias committed
{
	INT32 node = ackpak[i].destinationnode;
	DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
	ackpak[i].acknum = 0;
Alam Ed Arias's avatar
Alam Ed Arias committed
		Net_CloseConnection(node);
}

// We have got a packet, proceed the ack request and ack return
Alam Ed Arias's avatar
Alam Ed Arias committed
static boolean Processackpak(void)
{
	INT32 i;
	boolean goodpacket = true;
	node_t *node = &nodes[doomcom->remotenode];

	// Received an ack return, so remove the ack in the list
Alam Ed Arias's avatar
Alam Ed Arias committed
	if (netbuffer->ackreturn && cmpack(node->remotefirstack, netbuffer->ackreturn) < 0)
	{
		node->remotefirstack = netbuffer->ackreturn;
		// Search the ackbuffer and free it
Alam Ed Arias's avatar
Alam Ed Arias committed
		for (i = 0; i < MAXACKPACKETS; i++)
			if (ackpak[i].acknum && ackpak[i].destinationnode == node - nodes
				&& cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0)
			{
				RemoveAck(i);
	// Received a packet with ack, queue it to send the ack back
Alam Ed Arias's avatar
Alam Ed Arias committed
	if (netbuffer->ack)
	{
		UINT8 ack = netbuffer->ack;
		getackpacket++;
		if (cmpack(ack, node->firstacktosend) <= 0)
		{
			DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
			duppacket++;
			goodpacket = false; // Discard packet (duplicate)
Alam Ed Arias's avatar
Alam Ed Arias committed
		}
		else
		{
			// Check if it is not already in the queue
Alam Ed Arias's avatar
Alam Ed Arias committed
			for (i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND)
				if (node->acktosend[i] == ack)
				{
					DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
					duppacket++;
					goodpacket = false; // Discard packet (duplicate)
Alam Ed Arias's avatar
Alam Ed Arias committed
					break;
				}
			if (goodpacket)
			{
				// Is a good packet so increment the acknowledge number,
				// Then search for a "hole" in the queue
Alam Ed Arias's avatar
Alam Ed Arias committed
				UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
				if (!nextfirstack)
					nextfirstack = 1;

				if (ack == nextfirstack)
				{
					UINT8 hm1; // head - 1
					boolean change = true;

					node->firstacktosend = nextfirstack++;
					if (!nextfirstack)
						nextfirstack = 1;
					hm1 = (UINT8)((node->acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND);
					while (change)
					{
						change = false;
						for (i = node->acktosend_tail; i != node->acktosend_head;
							i = (i+1) % MAXACKTOSEND)
						{
							if (cmpack(node->acktosend[i], nextfirstack) <= 0)
							{
								if (node->acktosend[i] == nextfirstack)
								{
									node->firstacktosend = nextfirstack++;
									if (!nextfirstack)
										nextfirstack = 1;
									change = true;
								}
								if (i == node->acktosend_tail)
								{
									node->acktosend[node->acktosend_tail] = 0;
									node->acktosend_tail = (UINT8)((i+1) % MAXACKTOSEND);
								}
								else if (i == hm1)
								{
									node->acktosend[hm1] = 0;
									node->acktosend_head = hm1;
									hm1 = (UINT8)((hm1-1+MAXACKTOSEND) % MAXACKTOSEND);
								}
							}
						}
					}
				}
				else // Out of order packet
Alam Ed Arias's avatar
Alam Ed Arias committed
				{
					// Don't increment firsacktosend, put it in asktosend queue
					// Will be incremented when the nextfirstack comes (code above)
Alam Ed Arias's avatar
Alam Ed Arias committed
					UINT8 newhead = (UINT8)((node->acktosend_head+1) % MAXACKTOSEND);
					DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
					if (newhead != node->acktosend_tail)
					{
						node->acktosend[node->acktosend_head] = ack;
						node->acktosend_head = newhead;
					}
					else // Buffer full discard packet, sender will resend it
					{ // We can admit the packet but we will not detect the duplication after :(
Alam Ed Arias's avatar
Alam Ed Arias committed
						DEBFILE("no more freeackret\n");
						goodpacket = false;
					}
				}
			}
		}
	}
	return goodpacket;
}
#endif

// send special packet with only ack on it
void Net_SendAcks(INT32 node)
{
#ifdef NONET
	(void)node;
#else
	netbuffer->packettype = PT_NOTHING;
	M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND);
	HSendPacket(node, false, 0, MAXACKTOSEND);
#endif
}

#ifndef NONET
static void GotAcks(void)
{
	INT32 i, j;

	for (j = 0; j < MAXACKTOSEND; j++)
		if (netbuffer->u.textcmd[j])
			for (i = 0; i < MAXACKPACKETS; i++)
				if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode)
				{
					if (ackpak[i].acknum == netbuffer->u.textcmd[j])
						RemoveAck(i);
					// nextacknum is first equal to acknum, then when receiving bigger ack
					// there is big chance the packet is lost
					// When resent, nextacknum = nodes[node].nextacknum
					// will redo the same but with different value
					else if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
							&& ackpak[i].senttime > 0)
						{
							ackpak[i].senttime--; // hurry up
						}
Alam Ed Arias's avatar
Alam Ed Arias committed
				}
}
#endif

void Net_ConnectionTimeout(INT32 node)
Alam Ed Arias's avatar
Alam Ed Arias committed
{
	// Don't timeout several times
	if (nodes[node].flags & NF_TIMEOUT)
		return;
	nodes[node].flags |= NF_TIMEOUT;

	// Send a very special packet to self (hack the reboundstore queue)
	// Main code will handle it
Alam Ed Arias's avatar
Alam Ed Arias committed
	reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
	reboundstore[rebound_head].ack = 0;
	reboundstore[rebound_head].ackreturn = 0;
	reboundstore[rebound_head].u.textcmd[0] = (UINT8)node;
	reboundsize[rebound_head] = (INT16)(BASEPACKETSIZE + 1);
	rebound_head = (rebound_head+1) % MAXREBOUND;

	// Do not redo it quickly (if we do not close connection it is
Alam Ed Arias's avatar
Alam Ed Arias committed
	// for a good reason!)
	nodes[node].lasttimepacketreceived = I_GetTime();
}

// Resend the data if needed
Alam Ed Arias's avatar
Alam Ed Arias committed
void Net_AckTicker(void)
{
#ifndef NONET
	INT32 i;

	for (i = 0; i < MAXACKPACKETS; i++)
	{
		const INT32 nodei = ackpak[i].destinationnode;
		node_t *node = &nodes[nodei];
		if (ackpak[i].acknum && ackpak[i].senttime + NODETIMEOUT < I_GetTime())
		{
			if (ackpak[i].resentnum > 10 && (node->flags & NF_CLOSE))
Alam Ed Arias's avatar
Alam Ed Arias committed
			{
				DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n",
					i, nodei));
				Net_CloseConnection(nodei | FORCECLOSE);

				ackpak[i].acknum = 0;
				continue;
			}
			DEBFILE(va("Resend ack %d, %u<%d at %u\n", ackpak[i].acknum, ackpak[i].senttime,
				NODETIMEOUT, I_GetTime()));
			M_Memcpy(netbuffer, ackpak[i].pak.raw, ackpak[i].length);
			ackpak[i].senttime = I_GetTime();
			ackpak[i].resentnum++;
			ackpak[i].nextacknum = node->nextacknum;
			retransmit++; // For stat
Alam Ed Arias's avatar
Alam Ed Arias committed
			HSendPacket((INT32)(node - nodes), false, ackpak[i].acknum,
				(size_t)(ackpak[i].length - BASEPACKETSIZE));
		}
	}

	for (i = 1; i < MAXNETNODES; i++)
	{
		// This is something like node open flag
Alam Ed Arias's avatar
Alam Ed Arias committed
		if (nodes[i].firstacktosend)
		{
			// We haven't sent a packet for a long time
			// Acknowledge packet if needed
Alam Ed Arias's avatar
Alam Ed Arias committed
			if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
				Net_SendAcks(i);

Alam Ed Arias's avatar
Alam Ed Arias committed
				&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
			{
				Net_ConnectionTimeout(i);
			}
		}
	}
#endif
}

// Remove last packet received ack before resending the ackreturn
Alam Ed Arias's avatar
Alam Ed Arias committed
// (the higher layer doesn't have room, or something else ....)
void Net_UnAcknowledgePacket(INT32 node)
Alam Ed Arias's avatar
Alam Ed Arias committed
{
#ifdef NONET
	(void)node;
#else
	INT32 hm1 = (nodes[node].acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND;
	DEBFILE(va("UnAcknowledge node %d\n", node));
	if (!node)
		return;
	if (nodes[node].acktosend[hm1] == netbuffer->ack)
	{
		nodes[node].acktosend[hm1] = 0;
		nodes[node].acktosend_head = (UINT8)hm1;
	}
	else if (nodes[node].firstacktosend == netbuffer->ack)
	{
		nodes[node].firstacktosend--;
		if (!nodes[node].firstacktosend)
			nodes[node].firstacktosend = UINT8_MAX;
	}
	else
	{
		while (nodes[node].firstacktosend != netbuffer->ack)
		{
			nodes[node].acktosend_tail = (UINT8)
				((nodes[node].acktosend_tail-1+MAXACKTOSEND) % MAXACKTOSEND);
			nodes[node].acktosend[nodes[node].acktosend_tail] = nodes[node].firstacktosend;

			nodes[node].firstacktosend--;
			if (!nodes[node].firstacktosend)
				nodes[node].firstacktosend = UINT8_MAX;
		}
		nodes[node].firstacktosend++;
		if (!nodes[node].firstacktosend)
			nodes[node].firstacktosend = 1;
	}
#endif
}

LJ Sonic's avatar
LJ Sonic committed
#ifndef NONET
/** Checks if all acks have been received
  *
  * \return True if all acks have been received
  *
  */
static boolean Net_AllAcksReceived(void)
Alam Ed Arias's avatar
Alam Ed Arias committed
{
	INT32 i;

	for (i = 0; i < MAXACKPACKETS; i++)
		if (ackpak[i].acknum)
			return false;

	return true;
}
LJ Sonic's avatar
LJ Sonic committed
#endif
Alam Ed Arias's avatar
Alam Ed Arias committed

/** Waits for all ackreturns
  *
  * \param timeout Timeout in seconds
  *
  */
Alam Ed Arias's avatar
Alam Ed Arias committed
void Net_WaitAllAckReceived(UINT32 timeout)
{
#ifdef NONET
	(void)timeout;
#else
	tic_t tictac = I_GetTime();
	timeout = tictac + timeout*NEWTICRATE;

	HGetPacket();
	while (timeout > I_GetTime() && !Net_AllAcksReceived())
Alam Ed Arias's avatar
Alam Ed Arias committed
	{
		while (tictac == I_GetTime())
Eidolon's avatar
Eidolon committed
		{
			I_Sleep(cv_sleep.value);
			I_UpdateTime(cv_timescale.value);
		}
Alam Ed Arias's avatar
Alam Ed Arias committed
		tictac = I_GetTime();
		HGetPacket();
		Net_AckTicker();
	}
#endif
}

static void InitNode(node_t *node)
Alam Ed Arias's avatar
Alam Ed Arias committed
{
	node->acktosend_head = node->acktosend_tail = 0;
	node->firstacktosend = 0;
	node->nextacknum = 1;
	node->remotefirstack = 0;
	node->flags = 0;
Alam Ed Arias's avatar
Alam Ed Arias committed
}

static void InitAck(void)
{
	INT32 i;

#ifndef NONET
	for (i = 0; i < MAXACKPACKETS; i++)
		ackpak[i].acknum = 0;
#endif

	for (i = 0; i < MAXNETNODES; i++)
		InitNode(&nodes[i]);
Alam Ed Arias's avatar
Alam Ed Arias committed
}

/** Removes all acks of a given packet type
  *
  * \param packettype The packet type to forget
  *
  */
Alam Ed Arias's avatar
Alam Ed Arias committed
void Net_AbortPacketType(UINT8 packettype)
{
#ifdef NONET
	(void)packettype;
#else
	INT32 i;
	for (i = 0; i < MAXACKPACKETS; i++)
		if (ackpak[i].acknum && (ackpak[i].pak.data.packettype == packettype
			|| packettype == UINT8_MAX))
		{
			ackpak[i].acknum = 0;
		}
#endif
}

// -----------------------------------------------------------------
// end of acknowledge function
// -----------------------------------------------------------------

// remove a node, clear all ack from this node and reset askret
void Net_CloseConnection(INT32 node)
{
#ifdef NONET
	(void)node;
#else
	INT32 i;
	boolean forceclose = (node & FORCECLOSE) != 0;
	{
		DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
		return; // nope, just ignore it
Alam Ed Arias's avatar
Alam Ed Arias committed
	node &= ~FORCECLOSE;

	if (!node)
		return;

	if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
		DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
Alam Ed Arias's avatar
Alam Ed Arias committed

	// try to Send ack back (two army problem)
	if (GetAcktosend(node))
	{
		Net_SendAcks(node);
		Net_SendAcks(node);
	}

	// check if we are waiting for an ack from this node
	for (i = 0; i < MAXACKPACKETS; i++)
		if (ackpak[i].acknum && ackpak[i].destinationnode == node)
		{
			if (!forceclose)
				return; // connection will be closed when ack is returned
			else
				ackpak[i].acknum = 0;
		}

	InitNode(&nodes[node]);
	SV_AbortSendFiles(node);
Alam Ed Arias's avatar
Alam Ed Arias committed
	I_NetFreeNodenum(node);
#endif
}

#ifndef NONET
//
// Checksum
//
static UINT32 NetbufferChecksum(void)
{
	UINT32 c = 0x1234567;
	const INT32 l = doomcom->datalength - 4;
	const UINT8 *buf = (UINT8 *)netbuffer + 4;
	INT32 i;

	for (i = 0; i < l; i++, buf++)
		c += (*buf) * (i+1);

	return LONG(c);
}
#endif

#ifdef DEBUGFILE

static void fprintfstring(char *s, size_t len)
{
	INT32 mode = 0;
	size_t i;

	for (i = 0; i < len; i++)
		if (s[i] < 32)
		{
			if (!mode)
			{
				fprintf(debugfile, "[%d", (UINT8)s[i]);
				mode = 1;
			}
			else
				fprintf(debugfile, ",%d", (UINT8)s[i]);
		}
		else
		{
			if (mode)
			{
				fprintf(debugfile, "]");
				mode = 0;
			}
			fprintf(debugfile, "%c", s[i]);
		}
	if (mode)
		fprintf(debugfile, "]");
}

static void fprintfstringnewline(char *s, size_t len)
{
	fprintfstring(s, len);
Alam Ed Arias's avatar
Alam Ed Arias committed
	fprintf(debugfile, "\n");
}

/// \warning Keep this up-to-date if you add/remove/rename packet types
Alam Ed Arias's avatar
Alam Ed Arias committed
static const char *packettypename[NUMPACKETTYPE] =
{
	"NOTHING",
	"SERVERCFG",
	"CLIENTCMD",
	"CLIENTMIS",
	"CLIENT2CMD",
	"CLIENT2MIS",
	"NODEKEEPALIVE",
	"NODEKEEPALIVEMIS",
	"SERVERTICS",
	"SERVERREFUSE",
	"SERVERSHUTDOWN",
	"CLIENTQUIT",

	"ASKINFO",
	"SERVERINFO",
	"PLAYERINFO",
Alam Ed Arias's avatar
Alam Ed Arias committed
	"REQUESTFILE",
	"ASKINFOVIAMS",

	"RESYNCHEND",
	"RESYNCHGET",

fickleheart's avatar
fickleheart committed
	"CLIENT3CMD",
	"CLIENT3MIS",
	"CLIENT4CMD",
	"CLIENT4MIS",
	"BASICKEEPALIVE",

Alam Ed Arias's avatar
Alam Ed Arias committed
	"FILEFRAGMENT",
	"TEXTCMD",
	"TEXTCMD2",
Alam Ed Arias's avatar
Alam Ed Arias committed
	"CLIENTJOIN",
	"NODETIMEOUT",
	"RESYNCHING",
	"PING"
Alam Ed Arias's avatar
Alam Ed Arias committed
};

static void DebugPrintpacket(const char *header)
{
	fprintf(debugfile, "%-12s (node %d,ack %d,ackret %d,size %d) type(%d) : %s\n",
		header, doomcom->remotenode, netbuffer->ack, netbuffer->ackreturn, doomcom->datalength,
		netbuffer->packettype, packettypename[netbuffer->packettype]);

	switch (netbuffer->packettype)
	{
		case PT_ASKINFO:
		case PT_ASKINFOVIAMS:
			fprintf(debugfile, "    time %u\n", (tic_t)LONG(netbuffer->u.askinfo.time));
Alam Ed Arias's avatar
Alam Ed Arias committed
			break;
		case PT_CLIENTJOIN:
			fprintf(debugfile, "    number %d mode %d\n", netbuffer->u.clientcfg.localplayers,
Latapostrophe's avatar
Latapostrophe committed
				netbuffer->u.clientcfg.mode);
Alam Ed Arias's avatar
Alam Ed Arias committed
			break;
		case PT_SERVERTICS:
		{
			servertics_pak *serverpak = &netbuffer->u.serverpak;
			UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
			size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
Alam Ed Arias's avatar
Alam Ed Arias committed
			fprintf(debugfile, "    firsttic %u ply %d tics %d ntxtcmd %s\n    ",
				(UINT32)serverpak->starttic, serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
			/// \todo Display more readable information about net commands
			fprintfstringnewline((char *)cmd, ntxtcmd);
			/*fprintfstring((char *)cmd, 3);
			if (ntxtcmd > 4)
			{
				fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
				fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
			}
Alam Ed Arias's avatar
Alam Ed Arias committed
			break;
Alam Ed Arias's avatar
Alam Ed Arias committed
		case PT_CLIENTCMD:
		case PT_CLIENT2CMD:
		case PT_CLIENT3CMD:
		case PT_CLIENT4CMD:
Alam Ed Arias's avatar
Alam Ed Arias committed
		case PT_CLIENTMIS:
		case PT_CLIENT2MIS:
		case PT_CLIENT3MIS:
		case PT_CLIENT4MIS:
Alam Ed Arias's avatar
Alam Ed Arias committed
		case PT_NODEKEEPALIVE:
		case PT_NODEKEEPALIVEMIS:
			fprintf(debugfile, "    tic %4u resendfrom %u\n",
				(UINT32)netbuffer->u.clientpak.client_tic,
				(UINT32)netbuffer->u.clientpak.resendfrom);
Alam Ed Arias's avatar
Alam Ed Arias committed
			break;
			fprintf(debugfile, "    keep alive\n");
Alam Ed Arias's avatar
Alam Ed Arias committed
		case PT_TEXTCMD:
		case PT_TEXTCMD2:
Sal's avatar
Sal committed
		case PT_TEXTCMD3:
		case PT_TEXTCMD4:
Alam Ed Arias's avatar
Alam Ed Arias committed
			fprintf(debugfile, "    length %d\n    ", netbuffer->u.textcmd[0]);
			fprintf(debugfile, "[%s]", netxcmdnames[netbuffer->u.textcmd[1] - 1]);
			fprintfstringnewline((char *)netbuffer->u.textcmd + 2, netbuffer->u.textcmd[0] - 1);
Alam Ed Arias's avatar
Alam Ed Arias committed
			break;
		case PT_SERVERCFG:
Alam Ed Arias's avatar
Alam Ed Arias committed
			fprintf(debugfile, "    playerslots %d clientnode %d serverplayer %d "
Alam Ed Arias's avatar
Alam Ed Arias committed
				"gametic %u gamestate %d gametype %d modifiedgame %d\n",
				netbuffer->u.servercfg.totalslotnum, netbuffer->u.servercfg.clientnode,
				netbuffer->u.servercfg.serverplayer, (UINT32)LONG(netbuffer->u.servercfg.gametic),
				netbuffer->u.servercfg.gamestate, netbuffer->u.servercfg.gametype,
				netbuffer->u.servercfg.modifiedgame);
			break;
		case PT_SERVERINFO:
			fprintf(debugfile, "    '%s' player %d/%d, map %s, filenum %d, time %u \n",
				netbuffer->u.serverinfo.servername, netbuffer->u.serverinfo.numberofplayer,
				netbuffer->u.serverinfo.maxplayer, netbuffer->u.serverinfo.mapname,
				netbuffer->u.serverinfo.fileneedednum,
				(UINT32)LONG(netbuffer->u.serverinfo.time));
			fprintfstringnewline((char *)netbuffer->u.serverinfo.fileneeded,
Alam Ed Arias's avatar
Alam Ed Arias committed
				(UINT8)((UINT8 *)netbuffer + doomcom->datalength
				- (UINT8 *)netbuffer->u.serverinfo.fileneeded));
			break;
		case PT_SERVERREFUSE:
			fprintf(debugfile, "    reason %s\n", netbuffer->u.serverrefuse.reason);
			break;
		case PT_FILEFRAGMENT:
			fprintf(debugfile, "    fileid %d datasize %d position %u\n",
				netbuffer->u.filetxpak.fileid, (UINT16)SHORT(netbuffer->u.filetxpak.size),
				(UINT32)LONG(netbuffer->u.filetxpak.position));
			break;
		case PT_REQUESTFILE:
		default: // write as a raw packet
			fprintfstringnewline((char *)netbuffer->u.textcmd,
Alam Ed Arias's avatar
Alam Ed Arias committed
				(UINT8)((UINT8 *)netbuffer + doomcom->datalength - (UINT8 *)netbuffer->u.textcmd));
			break;
	}
}
#endif

#ifdef PACKETDROP
static INT32 packetdropquantity[NUMPACKETTYPE] = {0};
static INT32 packetdroprate = 0;

void Command_Drop(void)
{
	INT32 packetquantity;
	const char *packetname;
	size_t i;

	if (COM_Argc() < 2)
	{
		CONS_Printf("drop <packettype> [quantity]: drop packets\n"
					"drop reset: cancel all packet drops\n");
		return;
	}

	if (!(stricmp(COM_Argv(1), "reset") && stricmp(COM_Argv(1), "cancel") && stricmp(COM_Argv(1), "stop")))
	{
		memset(packetdropquantity, 0, sizeof(packetdropquantity));
		return;
	}

	if (COM_Argc() >= 3)
	{
		packetquantity = atoi(COM_Argv(2));
		if (packetquantity <= 0 && COM_Argv(2)[0] != '0')
		{
			CONS_Printf("Invalid quantity\n");
			return;
		}
	}
	else
		packetquantity = -1;

	packetname = COM_Argv(1);

	if (!(stricmp(packetname, "all") && stricmp(packetname, "any")))
		for (i = 0; i < NUMPACKETTYPE; i++)
			packetdropquantity[i] = packetquantity;
	else
	{
		for (i = 0; i < NUMPACKETTYPE; i++)
			if (!stricmp(packetname, packettypename[i]))
			{
				packetdropquantity[i] = packetquantity;
				return;
			}

		CONS_Printf("Unknown packet name\n");
	}
}

void Command_Droprate(void)
{
	INT32 droprate;

	if (COM_Argc() < 2)
	{
		CONS_Printf("Packet drop rate: %d%%\n", packetdroprate);
		return;
	}

	droprate = atoi(COM_Argv(1));
	if ((droprate <= 0 && COM_Argv(1)[0] != '0') || droprate > 100)
	{
		CONS_Printf("Packet drop rate must be between 0 and 100!\n");
		return;
	}

	packetdroprate = droprate;
}