//
// Asura_NetSocket.cpp
//
// Created by KOR - 03/11/2003 11:10:20
//
// Copyright (C) 2003 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

// This file's header.
#include "Network/Asura_NetSocket.h"

// Asura includes
#include "Core/Asura_Hash.h"
#include "Debugging/Asura_Logfile.h"
#include "Network/Asura_InternetSimulation.h"
#include "Network/Asura_Platform_NetSocket.h"
#include "Network/Asura_NetConnection.h"

// middleware includes
#ifdef ASURA_SUPPORT_NOVAWORLD
#include "NovaWorld/NovaworldAPI.h"
#endif

#if defined (ASURA_SUPPORT_GAMESPY)
#include "Network/GameSpy/Asura_GameSpy_Peer.h"
#include "Network/GameSpy/Asura_GameSpy_QR2.h"

#include "gamespy/natneg/natneg.h"
#endif

// ARC - 26 April 2006 10:49:42 - DemonWare integration
#if defined(ASURA_SUPPORT_DEMONWARE)
#include "Network/Demonware/Asura_DemonWare.h"
#endif // ASURA_SUPPORT_DEMONWARE

// I N T E R N A L   C L A S S E S ///////////////////////////////////////////
class Asura_NTS_CloseMessage
{
public:
	Asura_NTS_PacketHeader m_xHeader;
	u_short m_usNBOReason;
};

class Asura_NTS_ConnectionRejectedMessage
{
public:
	Asura_NTS_PacketHeader m_xHeader;
	Asura_Network_ConnectionRejectedData m_xData;
};


// D E B U G V A R S /////////////////////////////////////////////////////////
DEBUGVAR bool dbg_bDumpAllSentNetTraffic = false;

// S T A T I C   C L A S S   D A T A /////////////////////////////////////////
//bool Asura_NetSocket::s_bInitialised = false;

// C L A S S   M E T H O D S /////////////////////////////////////////////////
Asura_NetSocket::Asura_NetSocket(u_int uMaxNumConnections)
{
	m_xAddress = 0u;
// ARC - 26 April 2006 10:49:53 - Get the DemonWare socket
#if defined(ASURA_SUPPORT_DEMONWARE)
	Asura_Assert(Asura_Demonware::IsRunning(), "Asura_Demonware not running in Asura_NetSocket()");
	Asura_Demonware* const pxAsuraDemonWare = Asura_Demonware::GetInstance();
	m_xSocket = pxAsuraDemonWare->GetSocket();
	m_usPort = pxAsuraDemonWare->GetPort();
	m_bConnected = true;
#else
	m_usPort = 0;
	m_xSocket = INVALID_SOCKET;
	m_bConnected = false;
#endif // ASURA_SUPPORT_DEMONWARE
	m_bCloseRequested = false;
	m_bError = false;
	m_bListening = false;
	m_bUseAlternativeProtocol = false;
	
	if (uMaxNumConnections)
	{
		m_ppxConnections = new Asura_NetConnection*[uMaxNumConnections];
		for (u_int i=0;i<uMaxNumConnections;i++)
		{
			m_ppxConnections[i] = new Asura_NetConnection;
		}
	}
	else
	{
		m_ppxConnections = NULL;
	}
	m_uNumConnections = 0;
	m_uMaxNumConnections = uMaxNumConnections;
}

// destroy method: calls virtual functions to cleanup connection then calls delete on pointer.
void Asura_NetSocket::Destroy( Asura_NetSocket* pxSocket )
{
	// (MH - 19.07.2004) forced close connections when deleting socket,
	// in addition to closing the socket
	pxSocket->CloseAllConnections(true, ASURA_CLOSE_LOCAL);
	
	pxSocket->API_Close();

	delete pxSocket;
}

Asura_NetSocket::~Asura_NetSocket()
{
	if (m_ppxConnections)
	{
		for (u_int i=0;i<m_uMaxNumConnections;i++)
		{
			delete m_ppxConnections[i];
		}
		delete [] m_ppxConnections;
	}
}

void Asura_NetSocket::CloseAllConnections(bool bForce, Asura_Close_Reason eReason)
{
	for(u_int u = 0; u < m_uNumConnections; ++u)
	{
		m_ppxConnections[u]->Close(bForce, eReason);
	}

	UpdateConnections();
}

Asura_NetConnection* Asura_NetSocket::CreateConnection(Asura_NetAddress xRemoteAddress, unsigned short uRemotePort, int iTimeOut, bool bBlocking, bool bInitiate)
{
	Asura_NetConnection* pxConnection = FindConnection(xRemoteAddress, uRemotePort);
	if(pxConnection)
	{
		Asura_Assert(0, "Already a connection on this address and port");
	}
	else
	{
		Asura_Assert(m_uNumConnections < m_uMaxNumConnections, "Cannot create any more connections!");
		if (m_uNumConnections < m_uMaxNumConnections)
		{
			pxConnection = m_ppxConnections[m_uNumConnections];
			pxConnection->Activate(this, xRemoteAddress, uRemotePort, iTimeOut, bBlocking, bInitiate);
			m_uNumConnections++;
		}
	}
	return pxConnection;
}

void Asura_NetSocket::FatalError()
{
	if(m_bError)	return;
	m_bError = true;

	CloseAllConnections(true, ASURA_CLOSE_ERROR_SOCKETERROR);
	Asura_NetTransport_System::SocketError(this);
	API_Close();
}

DEBUGVAR bool dbg_bDropPackets = false;
DEBUGVAR float dbg_fDropRate = 0.1f;

bool Asura_NetSocket::HandleMessage(const char* pcMessage, int iLength, Asura_NetAddress xAddress, unsigned short usPort)
{
	Asura_Assert(pcMessage, "No message");
	if(!pcMessage) return false;

	if(dbg_bDropPackets)
	{
		if(Asura_Random::GetFloat() < dbg_fDropRate)
		{
			return true;
		}
	}

	if(!Platform_GetData(pcMessage, iLength))
	{
		Asura_Assert(0, "Failed to retrieve message data.");
		return false;
	}

#ifdef ASURA_SUPPORT_NOVAWORLD
	if (NovaworldAPI_BaffleProtocol::HandleMessage(pcMessage, iLength, xAddress, usPort))
	{
		return true;
	}
#endif

	Asura_NetConnection* pxConnection;

	pxConnection = FindConnection(xAddress, usPort);

	if (pxConnection && pxConnection->GetMachineInfo())
	{
		Asura_NetTransport_System::GetMachineInfo(pxConnection)->AddReceivedBytes(static_cast<u_int>(iLength));
	}
	else
	{
		Asura_NetTransport_System::AddDirectReceivedBytes(static_cast<u_int>(iLength));
	}

	// see if gamespy wants anything to do with this data.
#if defined(ASURA_SUPPORT_GAMESPY)
	const u_char* const pucMessage = reinterpret_cast<const u_char*>(pcMessage);
    // (MH - 22.06.2007) even if we're connected we might receive a NAT neg packet (e.g. a resend)
	// from the server, so check for that here.
	if((pucMessage[0] == NN_MAGIC_0) && (pucMessage[1] == NN_MAGIC_1) && 
		(pucMessage[2] == NN_MAGIC_2) && (pucMessage[3] == NN_MAGIC_3) && 
		(pucMessage[4] == NN_MAGIC_4) && (pucMessage[5] == NN_MAGIC_5) )
	{
		// send to NatNeg SDK
		sockaddr_in xSockAddress;
		xSockAddress.sin_family = AF_INET;
		xSockAddress.sin_port = htons(usPort);
		xSockAddress.sin_addr.s_addr = Platform_GetINetAddress(xAddress);
		NNProcessData(const_cast<char*>(pcMessage), iLength, &xSockAddress);
		return true;
	}
	
	if(!pxConnection)
	{
		// SNH - 13/10/2004 16:00:59 - need to catch peer and qr2 messages here and pass them on for processing...
		//	Based on Asura_GameSpy_GT2::UnrecognisedMessageCallback 

		// When pcMessage comes from GameSpy's servers for Peer, the first two bytes are 0xFE and 0xFD (QR magic 1 and 2).
		// Therefore the sequence number 0xFEFD is reserved (see Asura_NetConnection.cpp) and won't be used by Asura.
		//	Similarly for 0xFDFC for the NatNeg data.

		if((pucMessage[0] == QR_MAGIC_1) && (pucMessage[1] == QR_MAGIC_2))
		{
			if(Asura_GameSpy_Peer::IsInitialised())
			{
				Asura_GameSpy_Peer::ParsePeerQuery(Platform_GetINetAddress(xAddress), usPort, const_cast<char*>(pcMessage), iLength);
				return true;
			}
			else if(Asura_GameSpy_QR2::IsInitialised())
			{
				// send to QR2 SDK
				sockaddr_in xSockAddress;
				xSockAddress.sin_family = AF_INET;
				xSockAddress.sin_port = htons(usPort);
				xSockAddress.sin_addr.s_addr = Platform_GetINetAddress(xAddress);
				qr2_parse_query(NULL, const_cast<char*>(pcMessage), iLength, reinterpret_cast<sockaddr*>(&xSockAddress));
				return true;
			}
		}
	}
#endif // #if (defined(ASURA_SUPPORT_GAMESPY)

	const u_short usMessageID = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(*reinterpret_cast<const u_short*>(pcMessage + 4));

	if (usMessageID == ASURA_NETTRANSPORT_MSG_TESTNETMSG)
	{
#if _DEBUG
		Asura_NetTransport_System::ProcessTestNetMsg(xAddress, usPort, pcMessage, iLength);
#endif 		
		return true;
	}

	if(!pxConnection)
	{
		// (MH - 18.03.2005) for the moment, I'm not verifying the checksum here. It's possible
		// to cause lots of processing by flooding a host with data otherwise.
		if (usMessageID == ASURA_NETTRANSPORT_MSG_ENUMHOSTS)
		{
			if (!Asura_NetTransport_System::s_bServerReportingOn)
			{
				return false;
			}
			Asura_NetTransport_System::ProcessEnumHostsRequest(xAddress, usPort, pcMessage, static_cast<u_int>(iLength));
			return true;
		}
		else if (usMessageID == ASURA_NETTRANSPORT_MSG_HOSTREPLY)
		{
			Asura_NetTransport_System::ProcessEnumHostsResponse(xAddress, usPort, pcMessage, iLength);
			return true;
		}

		if(usMessageID != ASURA_NETTRANSPORT_MSG_CONNECTION_REQUEST)
		{
			if(usMessageID == ASURA_NETTRANSPORT_MSG_CONNECTION_CLOSE)
			{
				// Close request for a connection we don't have; ignore it
				return true;
			}
			else if (usMessageID == ASURA_NETTRANSPORT_MSG_KEEPALIVE)
			{
				// keep alive from a connection we don't have; either it's too early or too late so ignore
				return true;
			}
			else if (usMessageID == ASURA_NETTRANSPORT_MSG_CHALLENGE_REQUEST)
			{
				if( iLength < static_cast<int>( sizeof( Asura_Network_ChallengeResponseMsg ) ) )
				{
					// Too small, ignore it
					return true;
				}
				const u_short usNBOGameVer = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(Asura_NetTransport_System::Project_GetVersionID());
				const u_short* pusGameVer = reinterpret_cast<const u_short*>(pcMessage + 6);
				if (*pusGameVer == usNBOGameVer)
				{
					// send the challenge
					const u_int uAdditionalDataSize = Asura_NetConnection::Platform_GetMessageDataSize( ASURA_NETTRANSPORT_MSG_CHALLENGE_RESPONSE );
					const u_int uMsgSize = sizeof( Asura_Network_ChallengeResponseMsg ) + uAdditionalDataSize;

					char* pcMsgData = new char[ uMsgSize ];

					if( pcMsgData )
					{
						u_int uMsgPos = 0;
						
						Asura_Network_ChallengeResponseMsg* pxResponse = reinterpret_cast< Asura_Network_ChallengeResponseMsg* >( &pcMsgData[uMsgPos] );
						pxResponse->m_usNBOSequenceNumber = 0; // no need to endian swap 0
						// fill in the checksum after we've written the data
						pxResponse->m_usNBOMsgID = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL( static_cast<u_short>(ASURA_NETTRANSPORT_MSG_CHALLENGE_RESPONSE ) );
						pxResponse->m_usNBOGameVer = usNBOGameVer;
						pxResponse->m_xData.m_uChallenge = Asura_NetTransport_System::s_auServerChallenge[0];
						
						uMsgPos += sizeof( *pxResponse );

						Asura_NetConnection::Platform_FillMessageData( ASURA_NETTRANSPORT_MSG_CHALLENGE_RESPONSE, &pcMsgData[ uMsgPos ] );

						// do the checksum now.
						const char* pcData = reinterpret_cast<const char*>( &pxResponse->m_usNBOMsgID );
						pxResponse->m_usNBOChecksum = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(Asura_NetTransport_System::ComputeChecksum(pcData, static_cast<int>( uMsgSize - 4 ) ) );

						Send( xAddress, usPort, pcMsgData, uMsgSize );
						Asura_NetTransport_System::AddDirectSendBytes( uMsgSize );

						delete []pcMsgData;
					}
					return true;
				}
				else
				{
					// send version error
					SendClosed(xAddress, usPort, ASURA_CLOSE_ERROR_WRONGVERSION);
				}
				return true;
			}
			else
			{
				// First see if ANTS wants anything to do with it
				if(Asura_NetTransport_System::HandleMessage(xAddress, usPort, pcMessage + 4, iLength - 4))
				{
					return true;
				}
				else
				{
					// Unknown message. Ignore it. Sending a closed message can create a ping-pong 
					// flood if the sender also sends a reply to unknown messages.
					return true;//SendClosed(uIP, usPort);
				}
			}
		}

		// must be a connection message then.
		Asura_Assert(usMessageID == ASURA_NETTRANSPORT_MSG_CONNECTION_REQUEST, "");

		if(!m_bListening)
		{
			return true;
		}

		// TY 16.02.09 - There was a size check here ( if ( iLength - 8 != static_cast<int>(sizeof(Asura_Network_ConnectData) + uExtraConnectDataSize) ) )
		// but it has been removed since the message can be a variable size.

		// now the checksum
		const u_short usChecksum = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(*reinterpret_cast<const u_short*>(pcMessage + 2));

		if (usChecksum != Asura_NetTransport_System::ComputeChecksum(pcMessage + 4, iLength - 4))
		{
			Asura_Assert(false, "checksum failed");
			return true;
		}
		
		// check the challenge response
		const Asura_Network_ConnectData* pxConnectData = reinterpret_cast
			<const Asura_Network_ConnectData*>(pcMessage + 8);
		const u_int uCurHash = Asura_GetHashID_Binary(&Asura_NetTransport_System::s_auServerChallenge[0], sizeof(u_int));
		const u_int uChallengeResponse = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(pxConnectData->m_uNBOChallengeResponse);
		if (uCurHash != uChallengeResponse)
		{
			u_int uPrevHash = Asura_GetHashID_Binary(&Asura_NetTransport_System::s_auServerChallenge[1], sizeof(u_int));
			if (uPrevHash != uChallengeResponse)
			{
				RejectConnection(xAddress, usPort, ASURA_NDR_WRONG_CHALLENGE_RESPONSE);
				return true;
			}
		}

		// this is probably a safe packet, create a connection. If the connection is invalid 
		// (e.g. wrong version, server full, etc, it will be closed straightaway).
		pxConnection = CreateConnection(xAddress, usPort, 0, false, false);
		if (pxConnection)
		{
			Asura_Network_Result eResult = pxConnection->GetResult();
			if(eResult != ASURA_NETWORK_SUCCESS)
			{
				if(eResult != ASURA_NETWORK_DUPLICATEADDRESS)
				{
					return SendClosed(xAddress, usPort, ASURA_CLOSE_ERROR_SOCKETERROR);
				}
				return true;
			}
		}
		else
		{
			Asura_OutputToDebugger_Retail(("rejecting connection - out of connection objects\n"));
			RejectConnection(xAddress, usPort, ASURA_NDR_SERVERFULL);
			return true;
		}
	}

	if (!pxConnection)
	{
		Asura_Assert(false, "failed to create connection");
		return true;
	}

	// this message should originate from another ANTS client so the checksum should be correct.
	const u_short usChecksum = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(*reinterpret_cast<const u_short*>(pcMessage + 2));
	if (usChecksum != Asura_NetTransport_System::ComputeChecksum(pcMessage + 4, iLength - 4))
	{
		// it should be OK to send a closed message as this won't be a connection packet.
		Asura_Assert(false, "checksum failed");
		pxConnection->Close(true, ASURA_CLOSE_ERROR_CHECKSUM);
		return true;
	}

	if(pxConnection->GetState() == ASURA_CONNECTION_CLOSED)
	{
		if(usMessageID != ASURA_NETTRANSPORT_MSG_CONNECTION_CLOSE)
		{
			return pxConnection->SendClosed(ASURA_CLOSE_ERROR_COMMUNICATION);
		}
		return true;
	}

	return pxConnection->HandleMessage(pcMessage, iLength);
}

void Asura_NetSocket::RejectConnection(Asura_NetAddress xAddress, u_short& usPort, int iRejectReason)
{
	// fake up a rejection message
	Asura_NTS_ConnectionRejectedMessage xRejectionMessage;
	const u_int uResponseSize = sizeof(xRejectionMessage);
	Asura_NTS_PacketHeader* pxHeader = &xRejectionMessage.m_xHeader;
	pxHeader->m_usNBOSequenceNumber = 0; // no need to endian swap 0
	pxHeader->m_usNBOMsgID = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(static_cast<u_short>(ASURA_NETTRANSPORT_MSG_CONNECTION_REJECT));
	pxHeader->m_usNBOGameVer = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(Asura_NetTransport_System::Project_GetVersionID());
	Asura_Network_ConnectionRejectedData* pxReason = &xRejectionMessage.m_xData;
	pxReason->m_uNBOReason = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(iRejectReason);
	pxHeader->m_usNBOCheckSum = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(Asura_NetTransport_System::ComputeChecksum( reinterpret_cast<const char*>(&pxHeader->m_usNBOMsgID), uResponseSize - 4 ));
	Send( xAddress, usPort, reinterpret_cast<const char*>(&xRejectionMessage), uResponseSize );
	Asura_NetTransport_System::AddDirectSendBytes( uResponseSize );
}

Asura_NetConnection* Asura_NetSocket::FindConnection(Asura_NetAddress xAddress, unsigned short usPort) const
{
	for(u_int u = 0; u < m_uNumConnections; ++u)
	{
		if((m_ppxConnections[u]->GetAddress() == xAddress) && (m_ppxConnections[u]->GetPort() == usPort))
		{
			return m_ppxConnections[u];
		}
	}
	return NULL;
}

bool Asura_NetSocket::ResetConnection(Asura_NetAddress xAddress, unsigned short usPort)
{
	Asura_NetConnection* pxConnection = FindConnection(xAddress, usPort);
	if(pxConnection)
	{
		pxConnection->Reset();
	}
	return true;
}

bool Asura_NetSocket::Send(Asura_NetAddress xAddress, unsigned short usPort, const char* pcMessage, int iLength, bool bEncrypted /* = true */)
{
	Asura_Assert(pcMessage, "No message.");
	if(!pcMessage)
	{
		return true;
	}

	if(iLength <= 2)
	{
		Asura_Assert(0, "Message appears to only consist of sequence number or less.");
		return true;
	}

	Asura_NetSocket_MessageInfo xMessageInfo;
	xMessageInfo.m_xAddress = xAddress;
	xMessageInfo.m_usPort = usPort;
	xMessageInfo.m_pcMessage = pcMessage;
	xMessageInfo.m_iLength = iLength;
	xMessageInfo.m_bEncrypted = bEncrypted;

	bool bSuccess = API_SendTo(&xMessageInfo);
	
	if (dbg_bDumpAllSentNetTraffic && bSuccess)
	{
		Asura_OutputToDebugger(("sent to %s: ", Asura_NetTransport_System::GetStringFromAddress(xAddress, usPort)));
		
		for (int i = 0; i < iLength; ++i)
		{
			Asura_OutputToDebugger(("%02x ", u_char(pcMessage[i])));
		}

		Asura_OutputToDebugger(("\n"));
	}
	return bSuccess;
}

bool Asura_NetSocket::SendClosed(Asura_NetAddress xAddress, unsigned short usPort, Asura_Close_Reason eReason )
{
	// (MH - 23.02.2005) changed this to send a proper closed message with a reason. 
	const u_int uResponseSize = sizeof(Asura_NTS_PacketHeader) + sizeof(u_short);
	Asura_NTS_CloseMessage xMessage;
	Asura_NTS_PacketHeader* pxHeader = &xMessage.m_xHeader;
	pxHeader->m_usNBOSequenceNumber = 0; // no need to endian swap 0
	pxHeader->m_usNBOMsgID = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(static_cast<u_short>(ASURA_NETTRANSPORT_MSG_CONNECTION_CLOSE));
	pxHeader->m_usNBOGameVer = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(Asura_NetTransport_System::Project_GetVersionID());
	xMessage.m_usNBOReason = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(static_cast<u_short>(eReason));
	pxHeader->m_usNBOCheckSum = ASURA_NTS_CHANGE_BYTE_ORDER_RETVAL(Asura_NetTransport_System::ComputeChecksum( reinterpret_cast<const char*>(&pxHeader->m_usNBOMsgID), uResponseSize - 4 ));
	const bool bOK = Send( xAddress, usPort, reinterpret_cast<const char*>(&xMessage), uResponseSize );
	Asura_NetTransport_System::AddDirectSendBytes( uResponseSize );
	return bOK;
}

void Asura_NetSocket::SetBroadcastEnabled(bool bEnabled)
{
	API_SetBroadcastEnabled(bEnabled);
}

void Asura_NetSocket::Update()
{
	API_ReceiveMessages();
	UpdateConnections();
	Platform_Update();
}

void Asura_NetSocket::UpdateConnections()
{
	for(u_int i = 0; i < m_uNumConnections; i++)
	{
		m_ppxConnections[i]->Update();
		// Remove any closed connections
		if(m_ppxConnections[i]->GetState() == ASURA_CONNECTION_CLOSED)
		{
			// Kill it
			if(!m_ppxConnections[i]->GetInitiated())
			{
				Asura_NetTransport_System::Server_ClientDisconnected(m_ppxConnections[i]);
			}
			else
			{
				Asura_NetTransport_System::Client_ClientDisconnected(m_ppxConnections[i]);
			}
			m_ppxConnections[i]->Deactivate();

			// (AW - 20.05.2005) - Swap the connections about so we always have all valid 
			// connections at the start of the array.
			Asura_NetConnection *pxTemp = m_ppxConnections[m_uNumConnections - 1];
			m_ppxConnections[m_uNumConnections - 1] = m_ppxConnections[i];
			m_ppxConnections[i] = pxTemp;
			--m_uNumConnections;
			// fix from SNH 1.12.05 - also decrease i to fix up loop.
			--i;
		}
	}

}

#endif //#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK
