about summary refs log tree commit diff
path: root/src/engine/e_network_conn.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/e_network_conn.cpp')
-rw-r--r--src/engine/e_network_conn.cpp349
1 files changed, 349 insertions, 0 deletions
diff --git a/src/engine/e_network_conn.cpp b/src/engine/e_network_conn.cpp
new file mode 100644
index 00000000..a54c9ce3
--- /dev/null
+++ b/src/engine/e_network_conn.cpp
@@ -0,0 +1,349 @@
+#include <base/system.h>
+#include <string.h>
+#include "e_config.h"
+#include "e_network.h"
+
+void CNetConnection::ResetStats()
+{
+	mem_zero(&m_Stats, sizeof(m_Stats));
+}
+
+void CNetConnection::Reset()
+{
+	m_Sequence = 0;
+	m_Ack = 0;
+	m_RemoteClosed = 0;
+	
+	m_State = NET_CONNSTATE_OFFLINE;
+	m_LastSendTime = 0;
+	m_LastRecvTime = 0;
+	m_LastUpdateTime = 0;
+	m_Token = -1;
+	mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
+	
+	m_Buffer.Init();
+	
+	mem_zero(&m_Construct, sizeof(m_Construct));
+}
+
+const char *CNetConnection::ErrorString()
+{
+	return m_ErrorString;
+}
+
+void CNetConnection::SetError(const char *pString)
+{
+	str_copy(m_ErrorString, pString, sizeof(m_ErrorString));
+}
+
+void CNetConnection::Init(NETSOCKET Socket)
+{
+	Reset();
+	ResetStats();
+	
+	m_Socket = Socket;
+	mem_zero(m_ErrorString, sizeof(m_ErrorString));
+}
+
+void CNetConnection::AckChunks(int Ack)
+{
+	while(1)
+	{
+		CNetChunkResend *pResend = m_Buffer.First();
+		if(!pResend)
+			break;
+		
+		if(CNetBase::IsSeqInBackroom(pResend->m_Sequence, Ack))
+			m_Buffer.PopFirst();
+		else
+			break;
+	}
+}
+
+void CNetConnection::SignalResend()
+{
+	m_Construct.m_Flags |= NET_PACKETFLAG_RESEND;
+}
+
+int CNetConnection::Flush()
+{
+	int NumChunks = m_Construct.m_NumChunks;
+	if(!NumChunks && !m_Construct.m_Flags)
+		return 0;
+
+	/* send of the packets */	
+	m_Construct.m_Ack = m_Ack;
+	CNetBase::SendPacket(m_Socket, &m_PeerAddr, &m_Construct);
+	
+	/* update send times */
+	m_LastSendTime = time_get();
+	
+	/* clear construct so we can start building a new package */
+	mem_zero(&m_Construct, sizeof(m_Construct));
+	return NumChunks;
+}
+
+void CNetConnection::QueueChunkEx(int Flags, int DataSize, const void *pData, int Sequence)
+{
+	unsigned char *pChunkData;
+	
+	/* check if we have space for it, if not, flush the connection */
+	if(m_Construct.m_DataSize + DataSize + NET_MAX_CHUNKHEADERSIZE > (int)sizeof(m_Construct.m_aChunkData))
+		Flush();
+
+	/* pack all the data */
+	CNetChunkHeader Header;
+	Header.m_Flags = Flags;
+	Header.m_Size = DataSize;
+	Header.m_Sequence = Sequence;
+	pChunkData = &m_Construct.m_aChunkData[m_Construct.m_DataSize];
+	pChunkData = Header.Pack(pChunkData);
+	mem_copy(pChunkData, pData, DataSize);
+	pChunkData += DataSize;
+
+	/* */
+	m_Construct.m_NumChunks++;
+	m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData);
+	
+	/* set packet flags aswell */
+	
+	if(Flags&NET_CHUNKFLAG_VITAL && !(Flags&NET_CHUNKFLAG_RESEND))
+	{
+		/* save packet if we need to resend */
+		CNetChunkResend *pResend = m_Buffer.Allocate(sizeof(CNetChunkResend)+DataSize);
+		if(pResend)
+		{
+			pResend->m_Sequence = Sequence;
+			pResend->m_Flags = Flags;
+			pResend->m_DataSize = DataSize;
+			pResend->m_pData = (unsigned char *)(pResend+1);
+			pResend->m_FirstSendTime = time_get();
+			pResend->m_LastSendTime = pResend->m_FirstSendTime;
+			mem_copy(pResend->m_pData, pData, DataSize);
+		}
+		else
+		{
+			/* out of buffer */
+			Disconnect("too weak connection (out of buffer)");
+		}
+	}
+}
+
+void CNetConnection::QueueChunk(int Flags, int DataSize, const void *pData)
+{
+	if(Flags&NET_CHUNKFLAG_VITAL)
+		m_Sequence = (m_Sequence+1)%NET_MAX_SEQUENCE;
+	QueueChunkEx(Flags, DataSize, pData, m_Sequence);
+}
+
+void CNetConnection::SendControl(int ControlMsg, const void *pExtra, int ExtraSize)
+{
+	/* send the control message */
+	m_LastSendTime = time_get();
+	CNetBase::SendControlMsg(m_Socket, &m_PeerAddr, m_Ack, ControlMsg, pExtra, ExtraSize);
+}
+
+void CNetConnection::ResendChunk(CNetChunkResend *pResend)
+{
+	QueueChunkEx(pResend->m_Flags|NET_CHUNKFLAG_RESEND, pResend->m_DataSize, pResend->m_pData, pResend->m_Sequence);
+	pResend->m_LastSendTime = time_get();
+}
+
+void CNetConnection::Resend()
+{
+	for(CNetChunkResend *pResend = m_Buffer.First(); pResend; m_Buffer.Next(pResend))
+		ResendChunk(pResend);
+}
+
+int CNetConnection::Connect(NETADDR *pAddr)
+{
+	if(State() != NET_CONNSTATE_OFFLINE)
+		return -1;
+	
+	/* init connection */
+	Reset();
+	m_PeerAddr = *pAddr;
+	mem_zero(m_ErrorString, sizeof(m_ErrorString));
+	m_State = NET_CONNSTATE_CONNECT;
+	SendControl(NET_CTRLMSG_CONNECT, 0, 0);
+	return 0;
+}
+
+void CNetConnection::Disconnect(const char *pReason)
+{
+	if(State() == NET_CONNSTATE_OFFLINE)
+		return;
+
+	if(m_RemoteClosed == 0)
+	{
+		if(pReason)
+			SendControl(NET_CTRLMSG_CLOSE, pReason, strlen(pReason)+1);
+		else
+			SendControl(NET_CTRLMSG_CLOSE, 0, 0);
+
+		m_ErrorString[0] = 0;
+		if(pReason)
+			str_copy(m_ErrorString, pReason, sizeof(m_ErrorString));
+	}
+	
+	Reset();
+}
+
+int CNetConnection::Feed(CNetPacketConstruct *pPacket, NETADDR *pAddr)
+{
+	int64 now = time_get();
+	m_LastRecvTime = now;
+	
+	/* check if resend is requested */
+	if(pPacket->m_Flags&NET_PACKETFLAG_RESEND)
+		Resend();
+
+	/* */									
+	if(pPacket->m_Flags&NET_PACKETFLAG_CONTROL)
+	{
+		int CtrlMsg = pPacket->m_aChunkData[0];
+		
+		if(CtrlMsg == NET_CTRLMSG_CLOSE)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			m_RemoteClosed = 1;
+			
+			if(pPacket->m_DataSize)
+			{
+				/* make sure to sanitize the error string form the other party*/
+				char Str[128];
+				if(pPacket->m_DataSize < 128)
+					str_copy(Str, (char *)pPacket->m_aChunkData, pPacket->m_DataSize);
+				else
+					str_copy(Str, (char *)pPacket->m_aChunkData, sizeof(Str));
+				str_sanitize_strong(Str);
+				
+				/* set the error string */
+				SetError(Str);
+			}
+			else
+				SetError("no reason given");
+				
+			if(config.debug)
+				dbg_msg("conn", "closed reason='%s'", ErrorString());
+			return 0;			
+		}
+		else
+		{
+			if(State() == NET_CONNSTATE_OFFLINE)
+			{
+				if(CtrlMsg == NET_CTRLMSG_CONNECT)
+				{
+					/* send response and init connection */
+					Reset();
+					m_State = NET_CONNSTATE_PENDING;
+					m_PeerAddr = *pAddr;
+					m_LastSendTime = now;
+					m_LastRecvTime = now;
+					m_LastUpdateTime = now;
+					SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+					if(config.debug)
+						dbg_msg("connection", "got connection, sending connect+accept");			
+				}
+			}
+			else if(State() == NET_CONNSTATE_CONNECT)
+			{
+				/* connection made */
+				if(CtrlMsg == NET_CTRLMSG_CONNECTACCEPT)
+				{
+					SendControl(NET_CTRLMSG_ACCEPT, 0, 0);
+					m_State = NET_CONNSTATE_ONLINE;
+					if(config.debug)
+						dbg_msg("connection", "got connect+accept, sending accept. connection online");
+				}
+			}
+			else if(State() == NET_CONNSTATE_ONLINE)
+			{
+				/* connection made */
+				/*
+				if(ctrlmsg == NET_CTRLMSG_CONNECTACCEPT)
+				{
+					
+				}*/
+			}
+		}
+	}
+	else
+	{
+		if(State() == NET_CONNSTATE_PENDING)
+		{
+			m_State = NET_CONNSTATE_ONLINE;
+			if(config.debug)
+				dbg_msg("connection", "connecting online");
+		}
+	}
+	
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		AckChunks(pPacket->m_Ack);
+	}
+	
+	return 1;
+}
+
+int CNetConnection::Update()
+{
+	int64 now = time_get();
+
+	if(State() == NET_CONNSTATE_OFFLINE || State() == NET_CONNSTATE_ERROR)
+		return 0;
+	
+	/* check for timeout */
+	if(State() != NET_CONNSTATE_OFFLINE &&
+		State() != NET_CONNSTATE_CONNECT &&
+		(now-m_LastRecvTime) > time_freq()*10)
+	{
+		m_State = NET_CONNSTATE_ERROR;
+		SetError("timeout");
+	}
+
+	/* fix resends */
+	if(m_Buffer.First())
+	{
+		CNetChunkResend *pResend = m_Buffer.First();
+
+		/* check if we have some really old stuff laying around and abort if not acked */
+		if(now-pResend->m_FirstSendTime > time_freq()*10)
+		{
+			m_State = NET_CONNSTATE_ERROR;
+			SetError("too weak connection (not acked for 10 seconds)");
+		}
+		else
+		{
+			/* resend packet if we havn't got it acked in 1 second */
+			if(now-pResend->m_LastSendTime > time_freq())
+				ResendChunk(pResend);
+		}
+	}
+	
+	/* send keep alives if nothing has happend for 250ms */
+	if(State() == NET_CONNSTATE_ONLINE)
+	{
+		if(time_get()-m_LastSendTime > time_freq()/2) /* flush connection after 500ms if needed */
+		{
+			int NumFlushedChunks = Flush();
+			if(NumFlushedChunks && config.debug)
+				dbg_msg("connection", "flushed connection due to timeout. %d chunks.", NumFlushedChunks);
+		}
+			
+		if(time_get()-m_LastSendTime > time_freq())
+			SendControl(NET_CTRLMSG_KEEPALIVE, 0, 0);
+	}
+	else if(State() == NET_CONNSTATE_CONNECT)
+	{
+		if(time_get()-m_LastSendTime > time_freq()/2) /* send a new connect every 500ms */
+			SendControl(NET_CTRLMSG_CONNECT, 0, 0);
+	}
+	else if(State() == NET_CONNSTATE_PENDING)
+	{
+		if(time_get()-m_LastSendTime > time_freq()/2) /* send a new connect/accept every 500ms */
+			SendControl(NET_CTRLMSG_CONNECTACCEPT, 0, 0);
+	}
+	
+	return 0;
+}