// Asura_PC_SteamworksReliableSocket.cpp
// Thomas Young - 16.04.09
// Copyright (C) 2009 Rebellion, All Rights Reserved.

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_PC_SUPPORT_STEAMWORKS

#include "Network/Steamworks/Asura_PC_SteamworksReliableSocket.h"	// My header

#include "Core/Asura_Timers.h"										// Asura Independent
#include "Debugging/Asura_Logfile.h"
#include "Network/Asura_NetTransport_System.h"

#include "Network/Steamworks/Asura_PC_Steamworks.h"					// Asura PC
#include "Network/Steamworks/Asura_PC_SteamworksClient.h"
#include "Network/Steamworks/Asura_PC_SteamworksMessages.h"

// E N U M E R A T I O N S ///////////////////////////////////////////////////

enum ASURA_STEAM_RELIABLESOCKET_MESSAGE
{
											// Direction			Type					Description
											// `````````			````					```````````
	ASRS_MESSAGE_CHALLENGE			= 1,	// Client  -> Host		<None>					Client asking for a connection
	ASRS_MESSAGE_CHALLENGE_RESPONSE	,		// Client <-  Host		ASRS_Msg_AuthData		Host providing its details
	ASRS_MESSAGE_AUTH_BLOB			,		// Client  -> Host		ASRS_Msg_AuthData		[Optional] Client sending auth data
	ASRS_MESSAGE_AUTH_RESULT		,		// Client <-  Host		ASRS_Msg_AcceptReject	[Optional] Accept/Reject the above
	ASRS_MESSAGE_CONNECT			,		// Client  -> Host		ASRS_Msg_ConnectData	Connection attempt (w/ connect data)
	ASRS_MESSAGE_CONNECT_RESULT		,		// Client <-  Host		ASRS_Msg_AcceptReject	Accept/Reject the above
	ASRS_MESSAGE_EXTERNAL			,		// Client <-> Host		ASRS_Msg_External		Any message sent by Asura
	ASRS_MESSAGE_DISCONNECT			,		// Client <-> Host		ASRS_Msg_Disconnect		Client/Host is disconnecting
	ASRS_MESSAGE_DISCONNECT_ACK		,		// Client <-> Host		<None>					Client/Host acknowledging a disconnect
};

// C O N S T A N T S /////////////////////////////////////////////////////////

static const int	iASURA_PC_SRS_CONNECT_TIMEOUT					= 10;
static const float	fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT			= 20.0f;
static const float	fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT_IGNORE	= 99999.0f;
static const u_int	uASURA_PC_SRS_MSG_BUFFER_SIZE					= ( 1024 * 3 );

// S T A T I C   M E M B E R   V A R I A B L E S /////////////////////////////

Asura_ReliableConnectionID Asura_SteamReliableSocket_Connection::s_uNextConnectionID;

// G L O B A L   V A R I A B L E S ///////////////////////////////////////////

static float g_fHackToFixSteamworksBug = -1.0f;		// HACK

// F U N C T I O N   D E F I N I T I O N S ///////////////////////////////////

// ____________________ Asura_SteamReliableSocket_Client _____________________

Asura_SteamReliableSocket_Client::Asura_SteamReliableSocket_Client() : Asura_ReliableSocket_Client ()
, m_pfnCallbackSocketStatus( this, &Asura_SteamReliableSocket_Client::OnSocketStatusCallback )
{
	if( !Asura_PC_Steamworks::IsInitialised() )
	{
		Asura_Assert( false, "Expected steamworks to be initialised here" );
	}

	m_xSocket	= 0;
	
	m_xAddress	= Asura_NetAddress();
	m_xPort		= Asura_NetPort();

	m_pcConnectData		= 0;
	m_usConnectDataSize	= 0;

	m_bAuthenticate = false;

	m_eState = ASRSC_STATE_INACTIVE;

	m_eDisconnectReason = ASURA_RS_DISCONNECT_NOT_DISCONNECTED;

	m_fTimeout = 0.0f;
}


Asura_SteamReliableSocket_Client::~Asura_SteamReliableSocket_Client ()
{
	if( m_eState != ASRSC_STATE_INACTIVE )
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
	}
}


bool Asura_SteamReliableSocket_Client::Connect ( const Asura_NetAddress& xAddress, Asura_NetPort& xPort, const void* pConnectData, const u_short usConnectDataSize )
{
	if( !SteamNetworking() )
	{
		Asura_Assert( false, "Steam networking API not available" );
		return false;
	}

	Asura_Assert( m_eState == ASRSC_STATE_INACTIVE, "Socket is not inactive" );
	if( m_eState == ASRSC_STATE_AWAITING_CALLBACK )
	{
		// We are waiting for a callback, it's too risky to close/reconnect here
		return false;
	}

	Asura_Assert( m_xSocket == 0, "Trying to connect an already connected socket" );
	if ( m_xSocket )
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	if ( xAddress.m_xSteamID.IsValid() )
	{
		m_xSocket = SteamNetworking()->CreateP2PConnectionSocket( xAddress.m_xSteamID, xPort.m_usVPort, iASURA_PC_SRS_CONNECT_TIMEOUT, true );
		Asura_OutputToDebugger( ( "[Steam] Created P2P socket\n" ) );
	}
	else
	{
		m_xSocket = SteamNetworking()->CreateConnectionSocket( xAddress.m_uAddress, xPort.m_usPort, iASURA_PC_SRS_CONNECT_TIMEOUT );
		Asura_OutputToDebugger( ( "[Steam] Created socket on port %u\n", xPort.m_usPort ) );
	}

	if ( !m_xSocket )
	{
		m_eState = ASRSC_STATE_INACTIVE;
		return false;
	}

	m_xAddress	= xAddress;
	m_xPort		= xPort;

	m_eState = ASRSC_STATE_AWAITING_CALLBACK;

	m_fTimeout = fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;

	// Save the connect data so we can send it when appropriate
	if( pConnectData && ( usConnectDataSize > 0 ) )
	{
		m_pcConnectData = static_cast<char*>( Asura_Allocate( usConnectDataSize ) );
		Asura_Mem::Copy( m_pcConnectData, pConnectData, usConnectDataSize );
		m_usConnectDataSize = usConnectDataSize;
	}

	return true;
}


void Asura_SteamReliableSocket_Client::Disconnect ( const bool bImmediate, const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason )
{
	if ( m_xSocket )
	{
		if( !bImmediate )
		{
			SendDisconnecting( eReason );
			return;
		}

		SteamNetworking()->DestroySocket( m_xSocket, true );

		if(		m_bAuthenticate 
			&& ( m_xAddress.m_uAddress != Asura_PC_Steamworks::GetLoopbackAddress() )
			&& ( m_xAddress.m_xSteamID != Asura_PC_SteamworksClient::GetSteamID() ) )
		{
			Asura_PC_SteamworksClient::HandleUserDisconnection	( m_xAddress.m_xSteamID );
			Asura_PC_SteamworksClient::TerminateP2PConnection	( m_xAddress.m_xSteamID );
		}

		m_xAddress	= Asura_NetAddress();
		m_xSocket	= 0;
		m_xPort		= Asura_NetPort();

		ClearConnectData();

		m_eState = ASRSC_STATE_INACTIVE;

		if( bImmediate && m_pfnCallback_Disconnection )
		{
			m_pfnCallback_Disconnection( eReason );
		}
	}
}


void Asura_SteamReliableSocket_Client::Update ()
{
	if( m_eState == ASRSC_STATE_INACTIVE )
	{
		return;
	}

	// HACK ////////////////////////////////////////////////////////////
	
	// If it has been 1 second since our socket was 'connected', send 
	// a challenge message

	if( g_fHackToFixSteamworksBug > 0.0f )
	{
		g_fHackToFixSteamworksBug -= Asura_Timers::GetFrameTime();

		if( g_fHackToFixSteamworksBug <= 0.0f )
		{
			g_fHackToFixSteamworksBug = -1.0f;
			SendChallenge();
		}
	}

	// HACK ////////////////////////////////////////////////////////////

	if( m_eState >= ASRSC_STATE_AWAITING_CHALLENGE )
	{
		char	pBuffer[uASURA_PC_SRS_MSG_BUFFER_SIZE];
		uint32	uRecvSize;

		while( SteamNetworking()->RetrieveDataFromSocket( m_xSocket, pBuffer, uASURA_PC_SRS_MSG_BUFFER_SIZE, &uRecvSize ) )
		{
			// Check if this is a valid message
			Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( &pBuffer[0] );

			if( pxHdr->IsValid() )
			{
				HandleMessage( static_cast<char*>( pBuffer ), uRecvSize );
			}
		}
	}

	// Check if we have timed out waiting for various things

	if(		( m_eState == ASRSC_STATE_AWAITING_CALLBACK )
		||	( m_eState == ASRSC_STATE_AWAITING_CHALLENGE )
		||	( m_eState == ASRSC_STATE_AWAITING_AUTH )
		||	( m_eState == ASRSC_STATE_AWAITING_CONNECT ) 
		||	( m_eState == ASRSC_STATE_DISCONNECTING ) )
	{
		m_fTimeout -= Asura_Timers::GetFrameTime();

		if( m_fTimeout <= 0.0f )
		{
			Disconnect( true, ASURA_RS_DISCONNECT_TIMEOUT );
		}
	}
}


bool Asura_SteamReliableSocket_Client::SendMessage ( const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody )
{
	if( ( m_eState != ASRSC_STATE_CONNECTED ) || ( m_xSocket == 0 ) )
	{
		Asura_Assert( false, "Steam reliable socket is not connected, cannot send data");
		return false;
	}

	char pBuffer[ uASURA_PC_SRS_MSG_BUFFER_SIZE ];

	// Fill in the header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pBuffer );

	pxHdr->SetType( ASRS_MESSAGE_EXTERNAL );

	// Fill in the 'external message' wrapper

	ASRS_Msg_External* pxMsgBody = reinterpret_cast< ASRS_Msg_External* >( pxHdr->GetMessageBody() );

	pxMsgBody->usMsgExternalID	= usMsgID;
	pxMsgBody->usDataSize		= usMsgBodySize;

	// Fill in the message body

	Asura_Mem::Copy( pxMsgBody->GetDataAddress(), pMsgBody, usMsgBodySize );

	// Update the checksum

	pxMsgBody->usChecksum = Asura_NetTransport_System::ComputeChecksum( pxMsgBody->GetDataAddress(), pxMsgBody->usDataSize );

	// Send

	return SteamNetworking()->SendDataOnSocket( m_xSocket, pBuffer, sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_External ) + pxMsgBody->usDataSize, true );
}


ASURA_RELIABLE_SOCKET_CLIENT_STATE Asura_SteamReliableSocket_Client::GetState ()
{
	switch( m_eState )
	{
		case ASRSC_STATE_INACTIVE:			return ASURA_RS_CLIENT_STATE_INACTIVE;
		case ASRSC_STATE_AWAITING_CALLBACK:
		case ASRSC_STATE_AWAITING_CHALLENGE:
		case ASRSC_STATE_AWAITING_AUTH:	
		case ASRSC_STATE_AWAITING_CONNECT:	return ASURA_RS_CLIENT_STATE_CONNECTING;
		case ASRSC_STATE_CONNECTED:			
		case ASRSC_STATE_DISCONNECTING:		return ASURA_RS_CLIENT_STATE_CONNECTED;

		default:	
		{
			Asura_Assert( false, "Missing steam reliable socket state in enum" );
		}
	}

	return ASURA_RS_CLIENT_STATE_INACTIVE;
}


void Asura_SteamReliableSocket_Client::OnSocketStatusCallback( SocketStatusCallback_t *pxCallback)
{
	if ( pxCallback->m_hSocket != m_xSocket )
	{
		// The callback is for a different socket
		return;
	}

	// Asura_OutputToDebugger( ( "[Steam reliable socket client] Got callback for socket %u, state: %d\n", pxCallback->m_hSocket, pxCallback->m_eSNetSocketState ) );

	switch( pxCallback->m_eSNetSocketState )
	{
		case k_ESNetSocketStateConnected:
		{
			Asura_Assert( m_eState == ASRSC_STATE_AWAITING_CALLBACK, "Expected steam socket to be connecting here" );
		
			if( m_eState == ASRSC_STATE_AWAITING_CALLBACK )
			{
				// HACK ////////////////////////////////////////////////////////////
				
				// Wait for 1 second before sending our connect message, if we send straight away
				// the listener may not be ready for the message

				g_fHackToFixSteamworksBug = 1.0f;

				// HACK ////////////////////////////////////////////////////////////
			}
		}
		break;

		case k_ESNetSocketStateTimeoutDuringConnect:
		{
			Asura_Assert( false, "Steam reliable socket timed out during connection" );
			Asura_Assert( m_eState == ASRSC_STATE_AWAITING_CALLBACK , "Expected steam reliable socket to be in connecting state" );

			if( m_eState == ASRSC_STATE_AWAITING_CALLBACK )
			{
				Disconnect( true, ASURA_RS_DISCONNECT_TIMEOUT );
			}
		}
		break;

		case k_ESNetSocketStateRemoteEndDisconnected:
		case k_ESNetSocketStateConnectionBroken:
		case k_ESNetSocketStateDisconnecting:
		{
			if( m_eState == ASRSC_STATE_DISCONNECTING )
			{
				// We were expecting this disconnection
				Disconnect( true, m_eDisconnectReason );
			}
			else
			{
				// We weren't expecting this disconnection
				Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
			}
		}
		break;
	}
}


void Asura_SteamReliableSocket_Client::HandleMessage ( char* pcData, const u_int uDataSize )
{
	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcData );

	if( uDataSize < sizeof( Asura_Steam_Msg_Header ) )
	{
		Asura_Assert( false, "Message to small to process" );
		return;
	}

	Asura_Assert( pxHdr->IsValid(), "Invalid message");

	switch( pxHdr->GetType() )
	{
		case ASRS_MESSAGE_CHALLENGE_RESPONSE:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket client] received challenge response\n" ) );

			if( m_eState != ASRSC_STATE_AWAITING_CHALLENGE )
			{
				// Probably timed out waiting for the message
				return;
			}
			
			// We received the host details, send our auth data
			ASRS_Msg_AuthData* pxMsgBody = reinterpret_cast< ASRS_Msg_AuthData* >( pxHdr->GetMessageBody() );

			if( pxMsgBody->bShouldAuth )
			{
				// Handle this peer connection, we will get a callback saying if the client is accepted or rejected
				
				m_bAuthenticate = true;

				Asura_PC_SteamworksClient::RegisterCallback_ClientAccept( _ClientAccepted, this );
				Asura_PC_SteamworksClient::RegisterCallback_ClientReject( _ClientRejected, this );

				if( !Asura_PC_SteamworksClient::HandleUserConnection( pxMsgBody->GetAuthTicketAddress(), pxMsgBody->usAuthTicketSize, pxMsgBody->uSteamID ) )
				{
					Disconnect( true, ASURA_RS_DISCONNECT_REJECTED );
				}
			}
			else
			{
				// Not authorising this conneciton, just send our connect data

				SendConnectData();
			}
		}
		break;

		case ASRS_MESSAGE_AUTH_RESULT:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket client] received auth response\n" ) );

			if( m_eState != ASRSC_STATE_AWAITING_AUTH )
			{
				// Probably timed out waiting for the message
				return;
			}

			// We received a message to say if authorisation was successful
			ASRS_Msg_AcceptReject* pxMsgBody = reinterpret_cast< ASRS_Msg_AcceptReject* >( pxHdr->GetMessageBody() );

			if( pxMsgBody->bAccept )
			{
				// Authorisation was successful, now send our connect data
				SendConnectData();
			}
			else
			{
				Disconnect( true, ASURA_RS_DISCONNECT_REJECTED );
			}
		}
		break;

		case ASRS_MESSAGE_CONNECT_RESULT:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket client] received connect response\n" ) );

			if( m_eState != ASRSC_STATE_AWAITING_CONNECT )
			{
				// Probably timed out waiting for the message
				return;
			}

			// We received a message to say if the server accepted us
			ASRS_Msg_AcceptReject* pxMsgBody = reinterpret_cast< ASRS_Msg_AcceptReject* >( pxHdr->GetMessageBody() );

			if( pxMsgBody->bAccept )
			{
				// We were accepted, connection established
				m_eState = ASRSC_STATE_CONNECTED;
			}
			else
			{
				Disconnect( true, ASURA_RS_DISCONNECT_REJECTED );
			}
		}
		break;

		case ASRS_MESSAGE_EXTERNAL:
		{
			// This is an Asura message
			if( m_pfnCallback_Message )
			{
				ASRS_Msg_External* pxMsgBody = reinterpret_cast< ASRS_Msg_External* >( pxHdr->GetMessageBody() );

				// confirm the checksum is correct
				if( pxMsgBody->usChecksum == Asura_NetTransport_System::ComputeChecksum( pxMsgBody->GetDataAddress(), pxMsgBody->usDataSize ) )
				{
					m_pfnCallback_Message( pxMsgBody->usMsgExternalID, pxMsgBody->usDataSize, static_cast<char*>( pxMsgBody->GetDataAddress() ) );
				}
			}
		}
		break;

		case ASRS_MESSAGE_DISCONNECT:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket client] received disconnecting notification\n" ) );

			ASRS_Msg_Disconnect* pxMsgBody = reinterpret_cast< ASRS_Msg_Disconnect* >( pxHdr->GetMessageBody() );

			// Set our disconnection reason in preperation for disconnecting, then ack the disconnection
			m_eDisconnectReason = static_cast< ASURA_RELIABLE_SOCKET_DISCONNECT_REASON >( pxMsgBody->uReason );
			m_eState = ASRSC_STATE_DISCONNECTING;

			SendDisconnectAck();
		}
		break;

		case ASRS_MESSAGE_DISCONNECT_ACK:
		{
			// We got an ack for our disconnection request
			Asura_OutputToDebugger( ( "[Steam reliable socket client] received disconnect ack\n" ) );

			if( m_eState != ASRSC_STATE_DISCONNECTING )
			{
				// Hrm...
				Asura_Assert( false, "Got disconnection ack in wrong state" );
				return;
			}

			Disconnect( true, m_eDisconnectReason );
		}
		break;

		default:
		{
			Asura_Assertf( false, ( "Unrecognised message type sent to steam reliable socket %u", pxHdr->GetType() ) );
		}
		break;
	}
}


void Asura_SteamReliableSocket_Client::SendChallenge ( )
{
	Asura_Assert( ( m_eState == ASRSC_STATE_AWAITING_CALLBACK ) && ( m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket client] Sending challenge\n" ) );

	// Our message is just the header

	Asura_Steam_Msg_Header xMsg;

	xMsg.SetType( ASRS_MESSAGE_CHALLENGE );

	if( SteamNetworking()->SendDataOnSocket( m_xSocket, &xMsg, sizeof( Asura_Steam_Msg_Header ), true) )
	{
		m_eState = ASRSC_STATE_AWAITING_CHALLENGE;
		m_fTimeout = fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
	}
	else
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
	}
}


void Asura_SteamReliableSocket_Client::SendAuthData ( const CSteamID xSteamID )
{
	Asura_Assert( ( m_eState == ASRSC_STATE_AWAITING_CHALLENGE ) && ( m_xSocket != 0 ), "Wrong state in steam reliable socket" );
	Asura_Assert( xSteamID != Asura_PC_SteamworksClient::GetSteamID(), "Should not be authorising myself!" );

	Asura_OutputToDebugger( ( "[Steam reliable socket client] Sending auth data\n" ) );

	// Allocate enough memory for our message

	u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AuthData ) + ASURA_PC_STEAM_MAX_AUTH_TICKET_SIZE;
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	if( !pcBuffer )
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
		return;
	}

	// Set up the message header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxHdr->SetType( ASRS_MESSAGE_AUTH_BLOB );

	ASRS_Msg_AuthData* pxMsgBody = reinterpret_cast<ASRS_Msg_AuthData*>( pxHdr->GetMessageBody() );

	pxMsgBody->uSteamID		= Asura_PC_SteamworksClient::GetSteamID_Int64();
	pxMsgBody->bShouldAuth	= true;	// actually ignored for this part of the handshake

	// Initiate a p2p connection

	char* pcAuthData = 0;
	u_int uAuthDataSize = 0;

	if( !Asura_PC_SteamworksClient::InitiateP2PConnection( xSteamID ) )
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
		Asura_Deallocate( pcBuffer );
		return;
	}

	Asura_PC_SteamworksClient::GetAuthTicketForClient( xSteamID, pcAuthData, uAuthDataSize );
	
	Asura_Mem::Copy( pxMsgBody->GetAuthTicketAddress(), pcAuthData, uAuthDataSize );
	pxMsgBody->usAuthTicketSize = static_cast<u_short>( uAuthDataSize );

	uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AuthData ) + uAuthDataSize;

	// And send the message

	if( SteamNetworking()->SendDataOnSocket( m_xSocket, pcBuffer, uMsgSize, true) )
	{
		m_eState = ASRSC_STATE_AWAITING_AUTH;
		m_fTimeout = fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
	}
	else
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
	}
	
	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Client::SendConnectData ( )
{
	Asura_Assert( ( ( m_eState == ASRSC_STATE_AWAITING_AUTH ) || ( m_eState == ASRSC_STATE_AWAITING_CHALLENGE ) ) && ( m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket client] Sending connect data\n" ) );

	// Allocate enough memory for our message

	const u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_ConnectData ) + m_usConnectDataSize;
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	if( !pcBuffer )
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
		return;
	}

	// Set up the message header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxHdr->SetType( ASRS_MESSAGE_CONNECT );

	// Copy our saved connect data into the message

	ASRS_Msg_ConnectData* pxMsgBody = reinterpret_cast< ASRS_Msg_ConnectData* >( pxHdr->GetMessageBody() );

	if( m_pcConnectData && ( m_usConnectDataSize > 0 ) )
	{
		Asura_Mem::Copy( pxMsgBody->GetDataAddress(), m_pcConnectData, m_usConnectDataSize );
	}
	pxMsgBody->usDataSize = m_usConnectDataSize;

	// And send the message

	if( SteamNetworking()->SendDataOnSocket( m_xSocket, pcBuffer, uMsgSize, true) )
	{
		m_eState = ASRSC_STATE_AWAITING_CONNECT;
		m_fTimeout = fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
	}
	else
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	ClearConnectData();
	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Client::SendDisconnecting ( const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason )
{
	Asura_Assert( ( m_eState == ASRSC_STATE_CONNECTED ) && ( m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket client] Sending Disconnection\n" ) );

	u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_Disconnect );
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	Asura_Steam_Msg_Header* pxMsgHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxMsgHdr->SetType( ASRS_MESSAGE_DISCONNECT );
	
	ASRS_Msg_Disconnect* pxMsgBody = reinterpret_cast< ASRS_Msg_Disconnect* >( pxMsgHdr->GetMessageBody() );

	pxMsgBody->uReason = static_cast< u_short >( eReason );

	if( SteamNetworking()->SendDataOnSocket( m_xSocket, pcBuffer, uMsgSize, true ) )
	{
		m_fTimeout			= fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
		m_eState			= ASRSC_STATE_DISCONNECTING;
		m_eDisconnectReason	= eReason;
	}
	else
	{
		Disconnect( true, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Client::SendDisconnectAck ( )
{
	Asura_OutputToDebugger( ( "[Steam reliable socket client] Sending disconnect ack\n" ) );

	Asura_Assert( ( m_eState == ASRSC_STATE_DISCONNECTING ) && ( m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	// Our message is just the header

	Asura_Steam_Msg_Header xMsg;

	xMsg.SetType( ASRS_MESSAGE_DISCONNECT_ACK );

	SteamNetworking()->SendDataOnSocket( m_xSocket, &xMsg, sizeof( Asura_Steam_Msg_Header ), true );

	// Note we dont bother with any send logic here, we are about to be disconnected anyway
}


void Asura_SteamReliableSocket_Client::ClearConnectData ( )
{
	if( m_pcConnectData )
	{
		Asura_Deallocate( m_pcConnectData );
		m_pcConnectData = 0;
	}
	m_usConnectDataSize = 0;
}


void Asura_SteamReliableSocket_Client::_ClientAccepted ( const CSteamID xSteamID, void* pUserData )
{
	Asura_SteamReliableSocket_Client* pxSocket = reinterpret_cast< Asura_SteamReliableSocket_Client* >( pUserData );

	if( pxSocket )
	{
		pxSocket->ClientAccepted( xSteamID );
	}
}


void Asura_SteamReliableSocket_Client::_ClientRejected ( const CSteamID xSteamID, const u_int uReason, void* pUserData )
{
	Asura_SteamReliableSocket_Client* pxSocket = reinterpret_cast< Asura_SteamReliableSocket_Client* >( pUserData );

	if( pxSocket )
	{
		pxSocket->ClientRejected( xSteamID, uReason );
	}
}


void Asura_SteamReliableSocket_Client::ClientAccepted ( const CSteamID xSteamID )
{
	// The host was authenticated, send our own auth details
	SendAuthData( xSteamID );
}


void Asura_SteamReliableSocket_Client::ClientRejected ( const CSteamID xSteamID, const u_int uReason )
{
	// The host was rejected, disconnect
	Disconnect( true, ASURA_RS_DISCONNECT_REJECTED );
}

// ___________________ Asura_SteamReliableSocket_Listener ____________________

Asura_SteamReliableSocket_Listener::Asura_SteamReliableSocket_Listener( ) : Asura_ReliableSocket_Listener()
, m_pfnCallbackSocketStatus( this, &Asura_SteamReliableSocket_Listener::OnSocketStatusCallback )
{
	if( !Asura_PC_Steamworks::IsInitialised() )
	{
		Asura_Assert( false, "Expected steamworks to be initialised here" );
	}

	m_xListenSocket	= 0;
	m_xAddress		= Asura_NetAddress();
	m_xPort			= Asura_NetPort();

	m_eState		= ASRSL_STATE_INACTIVE;

	m_bAuthenticate = false;
}


Asura_SteamReliableSocket_Listener::~Asura_SteamReliableSocket_Listener ( )
{
	if( m_eState == ASRSL_STATE_LISTENING )
	{
		Close();
	}
}


bool Asura_SteamReliableSocket_Listener::Listen ( const Asura_NetPort& xPort )
{
	Asura_NetAddress xAddress;

	return Listen( xAddress, xPort );
}


bool Asura_SteamReliableSocket_Listener::Listen ( const Asura_NetAddress& xAddress, const Asura_NetPort& xPort )
{
	if( !SteamNetworking() )
	{
		Asura_Assert( false, "Steam networking API not available" );
		return false;
	}

	if ( ( m_eState != ASRSL_STATE_INACTIVE ) || ( m_xListenSocket != 0 ) )
	{
		Asura_Assert( false, "Cannot create listen socket, inappropriate state in Asura_SteamReliableSocket_Listener (already listening?)");
		return false;
	}

	// Set the port, this is tied slightly to the steamworks server, since it will broadcast the port we use
	u_short usDesiredPort = xPort.m_usPort;

	if( usDesiredPort == 0 )
	{
		// TODO: If this port is not available we should pick another one, although
		// I am not entirely sure how to determine if ports are in use with steamworks
		usDesiredPort = ASURA_PC_STEAMWORKS_GAME_PORT;
	}

	m_xListenSocket = SteamNetworking()->CreateListenSocket( xPort.m_usVPort, xAddress.m_uAddress, usDesiredPort, true ); 

	if( m_xListenSocket == 0 )
	{
		return false;
	}

	uint32 uIP		= 0;
	uint16 usPort	= 0;

	if( SteamNetworking()->GetListenSocketInfo( m_xListenSocket, &uIP, &usPort ) )
	{
		m_xAddress.m_xSteamID	= Asura_PC_SteamworksClient::GetSteamID();
		m_xAddress.m_uAddress	= uIP;
		m_xPort.m_usPort		= usPort;

		Asura_OutputToDebugger( ( "[Steam] Created listen socket on port %u\n", m_xPort.m_usPort ) );
	}
	#ifdef ASURA_ASSERT
	else
	{
		Asura_Assert( false, "Failed to get listen socket info" );
	}
	#endif

	m_eState = ASRSL_STATE_LISTENING;

	return true;
}


void Asura_SteamReliableSocket_Listener::Close ( )
{
	if( m_eState != ASRSL_STATE_LISTENING )
	{
		Asura_Assert( false, "Steam reliable socket is not listening, cannot close" );
		return;
	}

	if( m_xListenSocket )
	{
		SteamNetworking()->DestroyListenSocket( m_xListenSocket, true );

		m_xListenSocket	= 0;
	}

	m_xAddress	= Asura_NetAddress();
	m_xPort		= Asura_NetPort();

	while( !m_xConnections.IsEmpty() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = m_xConnections.PopHeadObject();

		// sockets are implicity destroyed when destroying the listener, and we dont really
		// want to spam the 'disconnected' callback, so dont call Disconnect here
		// ( but we do still need to ensure the client authentication functions are called )
		if( m_bAuthenticate && !pxConnection->IsLocalUser() )
		{
			Asura_PC_SteamworksClient::HandleUserDisconnection	( pxConnection->m_xAddress.m_xSteamID );
			Asura_PC_SteamworksClient::TerminateP2PConnection	( pxConnection->m_xAddress.m_xSteamID );
		}

		delete pxConnection;
		pxConnection = 0;
	}

	m_eState = ASRSL_STATE_INACTIVE;
}


void Asura_SteamReliableSocket_Listener::Disconnect ( const Asura_ReliableConnectionID& xConnectionID, const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason )
{
	Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( xConnectionID );

	Asura_Assertf( pxConnection, ( "Failed to find connection for connection ID %i", xConnectionID.AsInt() ) );
	
	if( pxConnection )
	{
		SendDisconnecting( pxConnection, eReason );
	}
}


void Asura_SteamReliableSocket_Listener::Disconnect_Internal ( const Asura_ReliableConnectionID& xConnectionID, const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason )
{
	Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( xConnectionID );

	if( pxConnection )
	{
		// Call our callback, do it before we destroy the socket, although anything sent at this point probably won't get through
		if( m_pfnCallback_Disconnection )
		{
			m_pfnCallback_Disconnection( xConnectionID, eReason );
		}

		SteamNetworking()->DestroySocket( pxConnection->m_xSocket, true );

		if( m_bAuthenticate && !pxConnection->IsLocalUser() )
		{
			Asura_PC_SteamworksClient::HandleUserDisconnection	( pxConnection->m_xAddress.m_xSteamID );
			Asura_PC_SteamworksClient::TerminateP2PConnection	( pxConnection->m_xAddress.m_xSteamID );
		}

		// Remove from our list of connections
		m_xConnections.RemoveFromList( pxConnection );

		delete pxConnection;
		pxConnection = 0;
	}
}


void Asura_SteamReliableSocket_Listener::OnSocketStatusCallback( SocketStatusCallback_t *pxCallback)
{
	if ( pxCallback->m_hListenSocket != m_xListenSocket )
	{
		// The callback is for a different socket
		return;
	}

	// Asura_OutputToDebugger( ( "[Steam reliable socket listener] Got callback for listen socket %u (socket %u), state: %d\n", pxCallback->m_hListenSocket, pxCallback->m_hSocket, pxCallback->m_eSNetSocketState ) );

	if( pxCallback->m_eSNetSocketState >= k_ESNetSocketStateDisconnecting )
	{
		// This socket has closed, disconnect it
		Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( pxCallback->m_hSocket );

		if( pxConnection )
		{
			if( pxConnection->m_eState == ASRSC_STATE_DISCONNECTING )
			{
				// We were expecting this disconnection
				Disconnect_Internal( pxConnection->m_xID, pxConnection->m_eDisconnectReason );
			}
			else
			{
				// We weren't expecting this disconnection
				Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
			}
			
		}
	}
}


void Asura_SteamReliableSocket_Listener::Update ( )
{
	if( m_eState == ASURA_RS_CLIENT_STATE_INACTIVE )
	{
		return;
	}

	char			pBuffer[uASURA_PC_SRS_MSG_BUFFER_SIZE];
	uint32			uRecvSize;
	SNetSocket_t	xSourceSocket;

	while( SteamNetworking()->RetrieveData( m_xListenSocket, pBuffer, uASURA_PC_SRS_MSG_BUFFER_SIZE, &uRecvSize, &xSourceSocket ) )
	{
		// Check if this is a valid message
		Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( &pBuffer[0] );

		if( pxHdr->IsValid() )
		{
			// do we have a record of this connection?
			Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( xSourceSocket );

			if( !pxConnection )
			{
				pxConnection = AddConnection( xSourceSocket );
			}

			HandleMessage( static_cast<char*>( pBuffer ), uRecvSize, pxConnection );
		}
	}

	// Check if we have timed out waiting for connection messages or callbacks

	Asura_List_It< Asura_SteamReliableSocket_Connection > xIter( m_xConnections );
	
	for( ; !xIter.Done(); xIter.Next() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = xIter.GetCurrent();
		if(	pxConnection )
		{
			switch ( pxConnection->m_eState )
			{
				case ASRSC_STATE_AWAITING_CHALLENGE:
				case ASRSC_STATE_AWAITING_AUTH:
				case ASRSC_STATE_AWAITING_CONNECT:
				case ASRSC_STATE_DISCONNECTING:
				{
					pxConnection->m_fTimeout -= Asura_Timers::GetFrameTime();

					if( pxConnection->m_fTimeout <= 0.0f )
					{
						Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_TIMEOUT );
						
						xIter.DeleteCurrent();
					}
				}
				break;
			}
		}
	}
	
}


void Asura_SteamReliableSocket_Listener::HandleMessage ( char* pcData, const u_int uDataSize, Asura_SteamReliableSocket_Connection* pxConnection )
{
	if( !pxConnection )
	{
		return;
	}

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcData );

	if( uDataSize < sizeof( Asura_Steam_Msg_Header ) )
	{
		Asura_Assert( false, "Message to small to process" );
		return;
	}

	Asura_Assert( pxHdr->IsValid(), "Invalid message");

	switch( pxHdr->GetType() )
	{
		case ASRS_MESSAGE_CHALLENGE:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket listener] received challenge\n" ) );

			// This should be the first message we get from a client
			if( pxConnection->m_eState != ASRSC_STATE_INACTIVE )
			{
				Asura_Assert( false, "Got challenge message from client in the wrong state" );
				return;
			}

			// This message is empty, so don't read anything, just send a response
			SendChallengeResponse( pxConnection );
		}
		break;

		case ASRS_MESSAGE_AUTH_BLOB:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket listener] received auth blob\n" ) );

			if( pxConnection->m_eState != ASRSC_STATE_AWAITING_AUTH )
			{
				// Probably timed out
				return;
			}

			// We received a message asking for authorisation

			ASRS_Msg_AuthData* pxMsgBody = reinterpret_cast< ASRS_Msg_AuthData* >( pxHdr->GetMessageBody() );

			// Handle this peer connection, we will get a callback saying if the client is accepted or rejected

			Asura_PC_SteamworksClient::RegisterCallback_ClientAccept( _ClientAccepted, this );
			Asura_PC_SteamworksClient::RegisterCallback_ClientReject( _ClientRejected, this );

			if( !Asura_PC_SteamworksClient::HandleUserConnection( pxMsgBody->GetAuthTicketAddress(), pxMsgBody->usAuthTicketSize, pxMsgBody->uSteamID ) )
			{
				Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_REJECTED );
			}
		}
		break;

		case ASRS_MESSAGE_CONNECT:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket listener] received connect data\n" ) );

			if( ( pxConnection->m_eState != ASRSC_STATE_AWAITING_CONNECT ) && ( pxConnection->m_eState != ASRSC_STATE_AWAITING_AUTH ) )
			{
				// Probably timed out
				return;
			}

			// We received a message containing connect data
			ASRS_Msg_ConnectData* pxMsgBody = reinterpret_cast< ASRS_Msg_ConnectData* >( pxHdr->GetMessageBody() );

			// Call our callback to see if this client should be accepted (assume acceptance if no callback?)

			bool bAccepted = true;

			if( m_pfnCallback_Connection )
			{
				bAccepted = m_pfnCallback_Connection( pxConnection->m_xID, pxMsgBody->usDataSize, static_cast<char*>( pxMsgBody->GetDataAddress() ) );
			}
			
			SendConnectDataResponse( pxConnection, bAccepted );
		}
		break;

		case ASRS_MESSAGE_EXTERNAL:
		{
			// This is an Asura message
			if( m_pfnCallback_Message )
			{
				ASRS_Msg_External* pxMsgBody = reinterpret_cast< ASRS_Msg_External* >( pxHdr->GetMessageBody() );

				// confirm the checksum is correct
				if( pxMsgBody->usChecksum == Asura_NetTransport_System::ComputeChecksum( pxMsgBody->GetDataAddress(), pxMsgBody->usDataSize ) )
				{
					m_pfnCallback_Message( pxConnection->m_xID, pxMsgBody->usMsgExternalID, pxMsgBody->usDataSize, static_cast<char*>( pxMsgBody->GetDataAddress() ) );
				}
			}
		}
		break;

		case ASRS_MESSAGE_DISCONNECT:
		{
			Asura_OutputToDebugger( ( "[Steam reliable socket listener] received disconnecting notification\n" ) );

			ASRS_Msg_Disconnect* pxMsgBody = reinterpret_cast< ASRS_Msg_Disconnect* >( pxHdr->GetMessageBody() );

			// Set our disconnection reason in preperation for disconnecting, then ack the disconnection
			pxConnection->m_eDisconnectReason = static_cast< ASURA_RELIABLE_SOCKET_DISCONNECT_REASON >( pxMsgBody->uReason );
			pxConnection->m_eState = ASRSC_STATE_DISCONNECTING;

			SendDisconnectAck( pxConnection );
		}
		break;

		case ASRS_MESSAGE_DISCONNECT_ACK:
		{
			// We got an ack for our disconnection request
			Asura_OutputToDebugger( ( "[Steam reliable socket listener] received disconnect ack\n" ) );

			if( pxConnection->m_eState != ASRSC_STATE_DISCONNECTING )
			{
				// Hrm...
				Asura_Assert( false, "Got disconnection ack in wrong state" );
				return;
			}

			Disconnect_Internal( pxConnection->m_xID, pxConnection->m_eDisconnectReason );
		}
		break;

		default:
		{
			Asura_Assertf( false, ( "Unrecognised message type sent to steam reliable socket %u", pxHdr->GetType() ) );
		}
		break;
	}
}


bool Asura_SteamReliableSocket_Listener::SendMessage ( const Asura_ReliableConnectionID& xConnectionID, const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody )
{
	Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( xConnectionID );

	if( !pxConnection )
	{
		Asura_Assertf( false, ( "Failed to find connection with ID %i", xConnectionID.AsInt() ) );
		return false;
	}
	
	return SendMessage( pxConnection, usMsgID, usMsgBodySize, pMsgBody );
}


bool Asura_SteamReliableSocket_Listener::SendMessage ( Asura_SteamReliableSocket_Connection* pxConnection, const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody )
{
	if( !pxConnection )
	{
		Asura_Assert( false, "No connection pointer passed to SendMessage" );
		return false;
	}
	
	if( ( pxConnection->m_eState != ASRSC_STATE_CONNECTED ) || ( pxConnection->m_xSocket == 0 ) )
	{
		Asura_Assert( false, "Steam reliable socket is not connected, cannot send data");
		return false;
	}

	char pBuffer[ uASURA_PC_SRS_MSG_BUFFER_SIZE ];

	// Fill in the header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pBuffer );

	pxHdr->uMagicNumber = uASURA_PC_STEAM_MESSAGE_MAGIC_NO;
	pxHdr->SetType( ASRS_MESSAGE_EXTERNAL );

	// Fill in the 'external message' wrapper

	ASRS_Msg_External* pxMsgBody = reinterpret_cast< ASRS_Msg_External* >( pxHdr->GetMessageBody() );

	pxMsgBody->usMsgExternalID	= usMsgID;
	pxMsgBody->usDataSize		= usMsgBodySize;

	// Fill in the message body

	Asura_Mem::Copy( pxMsgBody->GetDataAddress(), pMsgBody, usMsgBodySize );

	// Update the checksum

	pxMsgBody->usChecksum = Asura_NetTransport_System::ComputeChecksum( pxMsgBody->GetDataAddress(), pxMsgBody->usDataSize );

	// Send

	return SteamNetworking()->SendDataOnSocket( pxConnection->m_xSocket, pBuffer, sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_External ) + pxMsgBody->usDataSize, true );
}


bool Asura_SteamReliableSocket_Listener::SendMessageToAll ( const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody )
{
	bool bAllOk = true;
	
	Asura_List_It< Asura_SteamReliableSocket_Connection > xIter( m_xConnections );

	for( ; !xIter.Done(); xIter.Next() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = xIter.GetCurrent();

		bAllOk &= SendMessage( pxConnection, usMsgID, usMsgBodySize, pMsgBody );
	}

	return bAllOk;
}


bool Asura_SteamReliableSocket_Listener::SendMessageToAllExcept ( const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody, const Asura_ReliableConnectionID& xException )
{
	bool bAllOk = true;

	Asura_List_It< Asura_SteamReliableSocket_Connection > xIter( m_xConnections );

	for( ; !xIter.Done(); xIter.Next() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = xIter.GetCurrent();

		if( pxConnection->m_xID != xException )
		{
			bAllOk &= SendMessage( pxConnection, usMsgID, usMsgBodySize, pMsgBody );
		}
	}

	return bAllOk;
}


ASURA_RELIABLE_SOCKET_LISTENER_STATE Asura_SteamReliableSocket_Listener::GetState () 
{
	switch( m_eState )
	{
		case ASRSL_STATE_INACTIVE:		return ASURA_RS_LISTENER_STATE_INACTIVE;
		case ASRSL_STATE_LISTENING:		return ASURA_RS_LISTENER_STATE_LISTENING;
	}

	Asura_Assert( false, "Unexpected state in Asura_SteamReliableSocket_Listener::GetState" );

	return ASURA_RS_LISTENER_STATE_INACTIVE;
}


Asura_SteamReliableSocket_Connection* Asura_SteamReliableSocket_Listener::AddConnection ( const SNetSocket_t xSocket )
{
	Asura_SteamReliableSocket_Connection* pxConnection = new Asura_SteamReliableSocket_Connection;

	if( pxConnection )
	{
		CSteamID	xSteamID;
		uint32		uAddress;
		uint16		usPort;
		int			iStatus;

		if( SteamNetworking()->GetSocketInfo( xSocket, &xSteamID, &iStatus, &uAddress, &usPort ) )
		{
			pxConnection->m_xSocket				= xSocket;
			pxConnection->m_xAddress.m_uAddress = uAddress;
			pxConnection->m_xAddress.m_xSteamID = xSteamID;
			pxConnection->m_xPort.m_usPort		= usPort;
			pxConnection->m_eState				= ASRSC_STATE_INACTIVE;
			
			Asura_OutputToDebugger( ( "Added steam socket connection from %s\n", Asura_NetTransport_System::GetStringFromAddress( Asura_NetAddress( uAddress ), usPort ) ) );

			m_xConnections.AddToList( pxConnection );
		}
		else
		{
			delete pxConnection;
			pxConnection = 0;
		}
	}

	return pxConnection;
}


Asura_SteamReliableSocket_Connection* Asura_SteamReliableSocket_Listener::FindConnection ( const Asura_ReliableConnectionID xConnectionID )
{
	Asura_List_It< Asura_SteamReliableSocket_Connection > xIter( m_xConnections );

	for( ; !xIter.Done(); xIter.Next() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = xIter.GetCurrent();

		if( pxConnection && pxConnection->m_xID == xConnectionID )
		{
			return pxConnection;
		}
	}

	return 0;
}


Asura_SteamReliableSocket_Connection* Asura_SteamReliableSocket_Listener::FindConnection ( const SNetSocket_t xSocket )
{
	Asura_List_It< Asura_SteamReliableSocket_Connection > xIter( m_xConnections );

	for( ; !xIter.Done(); xIter.Next() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = xIter.GetCurrent();

		if( pxConnection && pxConnection->m_xSocket == xSocket )
		{
			return pxConnection;
		}
	}

	return 0;
}


Asura_SteamReliableSocket_Connection* Asura_SteamReliableSocket_Listener::FindConnection ( const CSteamID xSteamID )
{
	Asura_List_It< Asura_SteamReliableSocket_Connection > xIter( m_xConnections );

	for( ; !xIter.Done(); xIter.Next() )
	{
		Asura_SteamReliableSocket_Connection* pxConnection = xIter.GetCurrent();

		if( pxConnection && pxConnection->m_xAddress.m_xSteamID == xSteamID )
		{
			return pxConnection;
		}
	}

	return 0;
}


void Asura_SteamReliableSocket_Listener::SendChallengeResponse ( Asura_SteamReliableSocket_Connection* pxConnection )
{
	if( !pxConnection )
	{
		return;
	}

	Asura_Assert( ( pxConnection->m_eState == ASRSC_STATE_INACTIVE ) && ( pxConnection->m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket listener] Sending challenge response\n" ) );

	// Allocate enough memory for our message

	u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AuthData ) + ASURA_PC_STEAM_MAX_AUTH_TICKET_SIZE;
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	if( !pcBuffer )
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
		return;
	}

	// Set up the message header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxHdr->SetType( ASRS_MESSAGE_CHALLENGE_RESPONSE );

	// Fill in the data about ourselves

	ASRS_Msg_AuthData* pxMsgBody = reinterpret_cast< ASRS_Msg_AuthData* >( pxHdr->GetMessageBody() );

	const bool bShouldAuthThisClient	= m_bAuthenticate && !pxConnection->IsLocalUser();

	pxMsgBody->bShouldAuth		= bShouldAuthThisClient;
	pxMsgBody->uSteamID			= Asura_PC_SteamworksClient::GetSteamID_Int64();
	pxMsgBody->usAuthTicketSize	= 0;
	
	// Grab our auth ticket if necessary (we are set to authenticate and the client isnt ourselves)

	if( bShouldAuthThisClient )
	{
		char* pcAuthData = 0;
		u_int uAuthDataSize = 0;

		if( !Asura_PC_SteamworksClient::InitiateP2PConnection( pxConnection->m_xAddress.m_xSteamID ) )
		{
			Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
			Asura_Deallocate( pcBuffer );
			return;
		}

		Asura_PC_SteamworksClient::GetAuthTicketForClient( pxConnection->m_xAddress.m_xSteamID, pcAuthData, uAuthDataSize );

		Asura_Mem::Copy( pxMsgBody->GetAuthTicketAddress(), pcAuthData, uAuthDataSize );
		pxMsgBody->usAuthTicketSize = static_cast<u_short>( uAuthDataSize );

		uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AuthData ) + uAuthDataSize;
	}
	else
	{
		uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AuthData );
	}

	// And send the message

	if( SteamNetworking()->SendDataOnSocket( pxConnection->m_xSocket, pcBuffer, uMsgSize, true) )
	{
		if( bShouldAuthThisClient ) // If authenticating we expect an auth message
		{
			pxConnection->m_eState = ASRSC_STATE_AWAITING_AUTH;	
		}
		else // if not authenticating, we expect a connect message
		{
			pxConnection->m_eState = ASRSC_STATE_AWAITING_CONNECT;	
		}
		
		pxConnection->m_fTimeout = fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
	}
	else
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Listener::SendAuthDataResponse ( Asura_SteamReliableSocket_Connection* pxConnection, const bool bAccept )
{
	if( !pxConnection )
	{
		return;
	}

	Asura_Assert( ( pxConnection->m_eState == ASRSC_STATE_AWAITING_AUTH ) && ( pxConnection->m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket listener] Sending auth data response\n" ) );

	// Allocate enough memory for our message

	const u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AcceptReject );
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	if( !pcBuffer )
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
		return;
	}

	// Set up the message header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxHdr->SetType( ASRS_MESSAGE_AUTH_RESULT );

	// Fill in the data

	ASRS_Msg_AcceptReject* pxMsgBody = reinterpret_cast< ASRS_Msg_AcceptReject* >( pxHdr->GetMessageBody() );

	pxMsgBody->bAccept = bAccept;
	
	// And send the message

	if( SteamNetworking()->SendDataOnSocket( pxConnection->m_xSocket, pcBuffer, uMsgSize, true) )
	{
		pxConnection->m_eState = ASRSC_STATE_AWAITING_CONNECT;
		pxConnection->m_fTimeout = fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
	}
	else
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Listener::SendConnectDataResponse ( Asura_SteamReliableSocket_Connection* pxConnection, const bool bAccept )
{
	if( !pxConnection )
	{
		return;
	}

	Asura_Assert( ( pxConnection->m_eState == ASRSC_STATE_AWAITING_CONNECT ) && ( pxConnection->m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket listener] Sending connect data response\n" ) );

	// Allocate enough memory for our message

	const u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_AcceptReject );
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	if( !pcBuffer )
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
		return;
	}

	// Set up the message header

	Asura_Steam_Msg_Header* pxHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxHdr->SetType( ASRS_MESSAGE_CONNECT_RESULT );

	// Fill in the data

	ASRS_Msg_AcceptReject* pxMsgBody = reinterpret_cast< ASRS_Msg_AcceptReject* >( pxHdr->GetMessageBody() );

	pxMsgBody->bAccept = bAccept;

	// And send the message

	if( SteamNetworking()->SendDataOnSocket( pxConnection->m_xSocket, pcBuffer, uMsgSize, true) )
	{
		pxConnection->m_eState = bAccept ? ASRSC_STATE_CONNECTED : ASRSC_STATE_INACTIVE;
	}
	else
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Listener::SendDisconnecting ( Asura_SteamReliableSocket_Connection* pxConnection, const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason )
{
	Asura_Assert( ( pxConnection->m_eState == ASRSC_STATE_CONNECTED ) && ( pxConnection->m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	Asura_OutputToDebugger( ( "[Steam reliable socket listener] Sending Disconnection\n" ) );

	u_int uMsgSize = sizeof( Asura_Steam_Msg_Header ) + sizeof( ASRS_Msg_Disconnect );
	char* pcBuffer = static_cast<char*>( Asura_Allocate( uMsgSize ) );

	Asura_Steam_Msg_Header* pxMsgHdr = reinterpret_cast< Asura_Steam_Msg_Header* >( pcBuffer );

	pxMsgHdr->SetType( ASRS_MESSAGE_DISCONNECT );

	ASRS_Msg_Disconnect* pxMsgBody = reinterpret_cast< ASRS_Msg_Disconnect* >( pxMsgHdr->GetMessageBody() );

	pxMsgBody->uReason = static_cast< u_short >( eReason );

	if( SteamNetworking()->SendDataOnSocket( pxConnection->m_xSocket, pcBuffer, uMsgSize, true ) )
	{
		pxConnection->m_fTimeout			= fASURA_PC_SRS_INTERNAL_CONNECT_TIMEOUT;
		pxConnection->m_eState				= ASRSC_STATE_DISCONNECTING;
		pxConnection->m_eDisconnectReason	= eReason;
	}
	else
	{
		Disconnect_Internal( pxConnection->m_xID, ASURA_RS_DISCONNECT_UNKNOWN );
	}

	Asura_Deallocate( pcBuffer );
}


void Asura_SteamReliableSocket_Listener::SendDisconnectAck ( Asura_SteamReliableSocket_Connection* pxConnection )
{
	Asura_OutputToDebugger( ( "[Steam reliable socket listener] Sending disconnect ack\n" ) );

	Asura_Assert( ( pxConnection->m_eState == ASRSC_STATE_DISCONNECTING ) && ( pxConnection->m_xSocket != 0 ), "Wrong state in steam reliable socket" );

	// Our message is just the header

	Asura_Steam_Msg_Header xMsg;

	xMsg.SetType( ASRS_MESSAGE_DISCONNECT_ACK );

	SteamNetworking()->SendDataOnSocket( pxConnection->m_xSocket, &xMsg, sizeof( Asura_Steam_Msg_Header ), true );

	// Note we dont bother with any send logic here, we are about to be disconnected anyway
}


u_short Asura_SteamReliableSocket_Listener::GetListenPort () const
{
	// Assume the caller wants the port (rather than VPort)
	return m_xPort.m_usPort;
}


u_int Asura_SteamReliableSocket_Listener::GetNumConnections () const
{
	return static_cast<u_int>( m_xConnections.GetNumEntries() );
}

void Asura_SteamReliableSocket_Listener::EnableAuthentication ( const bool bEnable )
{
	if( m_eState == ASRSL_STATE_INACTIVE )
	{
		m_bAuthenticate = bEnable; 
	}
	#ifdef ASURA_ASSERT
	else
	{
		Asura_Assert( false, "Reliable socket listener is listening. You must enable authorisation before you begin listening" );
	}
	#endif
}


void Asura_SteamReliableSocket_Listener::_ClientAccepted ( const CSteamID xSteamID, void* pUserData )
{
	Asura_SteamReliableSocket_Listener* pxSocket = reinterpret_cast< Asura_SteamReliableSocket_Listener* >( pUserData );

	if( pxSocket )
	{
		pxSocket->ClientAccepted( xSteamID );
	}
}


void Asura_SteamReliableSocket_Listener::_ClientRejected ( const CSteamID xSteamID, const u_int uReason, void* pUserData )
{
	Asura_SteamReliableSocket_Listener* pxSocket = reinterpret_cast< Asura_SteamReliableSocket_Listener* >( pUserData );

	if( pxSocket )
	{
		pxSocket->ClientRejected( xSteamID, uReason );
	}
}


void Asura_SteamReliableSocket_Listener::ClientAccepted ( const CSteamID xSteamID )
{
	// The client was authenticated, send a response
	Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( xSteamID );
	SendAuthDataResponse( pxConnection, true );
}


void Asura_SteamReliableSocket_Listener::ClientRejected ( const CSteamID xSteamID, const u_int uReason )
{
	// The client failed authentication
	Asura_SteamReliableSocket_Connection* pxConnection = FindConnection( xSteamID );
	SendAuthDataResponse( pxConnection, false );
}

#endif	// ASURA_PC_SUPPORT_STEAMWORKS

//eof
