//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose: 
//
//===========================================================================//

#include "mm_framework.h"

#include "vstdlib/random.h"

#include "protocol.h"
#include "proto_oob.h"
#include "filesystem.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


ConVar mm_dedicated_xlsp_max_dcs( "mm_dedicated_xlsp_max_dcs", "25", FCVAR_DEVELOPMENTONLY, "Max number of XLSP datacenters supported" );
ConVar mm_dedicated_xlsp_force_dc( "mm_dedicated_xlsp_force_dc", "", FCVAR_DEVELOPMENTONLY, "Name of XLSP datacenter to force connection to" );
ConVar mm_dedicated_xlsp_timeout( "mm_dedicated_xlsp_timeout", "20", FCVAR_DEVELOPMENTONLY, "Timeout for XLSP operations" );
ConVar mm_dedicated_xlsp_command_timeout( "mm_dedicated_xlsp_command_timeout", "10", FCVAR_DEVELOPMENTONLY, "Timeout for XLSP command" );

ConVar mm_dedicated_xlsp_force_dc_offset( "mm_dedicated_xlsp_force_dc_offset", "0", FCVAR_DEVELOPMENTONLY, "Offset of XLSP datacenter master to debug master servers" );


static int s_nXlspConnectionCmdReplyId = 0x10000000;
static int GetNextXlspConnectionCmdReplyId()
{
	return ++ s_nXlspConnectionCmdReplyId;
}

#ifdef _X360


//
// Datacenter implementation
//

static int GetBucketedRTT( int iRTT )
{
	static int s_latencyBucketLevels[] = { 5, 25, 50, 100, 200, 0xFFFFFF };
	for ( int k = 0; k < ARRAYSIZE( s_latencyBucketLevels ); ++ k )
	{
		if ( iRTT <= s_latencyBucketLevels[ k ] )
			return s_latencyBucketLevels[ k ];
	}
	return s_latencyBucketLevels[ ARRAYSIZE( s_latencyBucketLevels ) - 1 ];
}

int CXlspDatacenter::Compare( CXlspDatacenter const *dc1, CXlspDatacenter const *dc2 )
{
	int nPing1 = dc1->m_nPingBucket, nPing2 = dc2->m_nPingBucket;

	if ( nPing1 != nPing2 )
		return ( nPing1 < nPing2 ) ? -1 : 1;
	else
		return 0;
}

bool CXlspDatacenter::ParseServerInfo()
{
	char *pToken = V_stristr( m_xsi.szServerInfo, "**" );
	if ( !pToken )
		return false;

	// Get our bare gateway name
	int nTokenLength = pToken - m_xsi.szServerInfo;
	nTokenLength = MIN( sizeof( m_szGatewayName ) - 1, nTokenLength );
	sprintf( m_szGatewayName, "%.*s", nTokenLength, m_xsi.szServerInfo );

	// parse out the gateway information
	// get the master server's port and range
	pToken += 2;
	int nSupportsPII = 0;
	sscanf( pToken, "%d_%d_%d", &m_nMasterServerPortStart, &m_numMasterServers, &nSupportsPII );
	m_bSupportsPII = ( nSupportsPII != 0 );

	if ( !m_nMasterServerPortStart || !m_numMasterServers )
		return false;

	return true;
}

void CXlspDatacenter::Destroy()
{
	if ( m_adrSecure.s_addr )
	{
		g_pMatchExtensions->GetIXOnline()->XNetUnregisterInAddr( m_adrSecure );
	}

	Q_memset( this, 0, sizeof( *this ) );
}

//
// XLSP title servers enumeration implementation
//

CXlspTitleServers::CXlspTitleServers( int nPingLimit, bool bMustSupportPII ) :
	m_hEnumerate( NULL ),
	m_numServers( 0 ),
	m_pQos( NULL ),
	m_eState( STATE_INIT ),
	m_flTimeout( 0.0f ),
	m_nPingLimit( nPingLimit ),
	m_bMustSupportPII( bMustSupportPII ),
	m_pCancelOverlappedJob( NULL )
{
	ZeroMemory( &m_xOverlapped, sizeof( m_xOverlapped ) );
}

CXlspTitleServers::~CXlspTitleServers()
{
}

void CXlspTitleServers::Destroy()
{
	switch ( m_eState )
	{
	case STATE_XLSP_ENUMERATE_DCS:
		m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped );	// UpdateDormantOperations will clean the rest
		break;
	case STATE_XLSP_QOS_DCS:
		g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_pQos );
		m_pQos = NULL;
		break;
	}

	if ( !m_pCancelOverlappedJob )
		delete this;
	else
		MMX360_RegisterDormant( this );	// keep running UpdateDormantOperation frame loop
}

bool CXlspTitleServers::UpdateDormantOperation()
{
	if ( !m_pCancelOverlappedJob->IsFinished() )
		return true; // keep running dormant

	m_pCancelOverlappedJob->Release();
	m_pCancelOverlappedJob = NULL;

	CloseHandle( m_hEnumerate );
	m_hEnumerate = NULL;
	delete this;
	return false;	// destroyed the object, remove from dormant list
}

void CXlspTitleServers::Update()
{
	switch ( m_eState )
	{
	case STATE_INIT:
		m_flTimeout = Plat_FloatTime() + mm_dedicated_xlsp_timeout.GetFloat();
		if ( EnumerateDcs( m_hEnumerate, m_xOverlapped, m_bufXlspEnumerateDcs ) )
			m_eState = STATE_XLSP_ENUMERATE_DCS;
		else
			m_eState = STATE_FINISHED;
		break;

	case STATE_XLSP_ENUMERATE_DCS:
		if ( !XHasOverlappedIoCompleted( &m_xOverlapped ) )
		{
			if ( Plat_FloatTime() > m_flTimeout )
			{
				// Enumeration timeout elapsed
				m_pCancelOverlappedJob = ThreadExecute( MMX360_CancelOverlapped, &m_xOverlapped );	// UpdateDormantOperations will clean the rest
				m_eState = STATE_FINISHED;
			}
			return;
		}

		m_flTimeout = Plat_FloatTime() + mm_dedicated_xlsp_timeout.GetFloat();
		if ( ExecuteQosDcs( m_hEnumerate, m_xOverlapped, m_bufXlspEnumerateDcs, m_numServers, m_pQos ) )
			m_eState = STATE_XLSP_QOS_DCS;
		else
			m_eState = STATE_FINISHED;
		break;

	case STATE_XLSP_QOS_DCS:
		if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) || Plat_FloatTime() > m_flTimeout ||
			!m_pQos->cxnqosPending )
		{
			ParseDatacentersFromQos( m_arrDcs, m_bufXlspEnumerateDcs, m_pQos );
			m_eState = STATE_FINISHED;
		}
		break;
	}
}

bool CXlspTitleServers::EnumerateDcs( HANDLE &hEnumerate, XOVERLAPPED &xOverlapped, CUtlBuffer &bufResults )
{
	// If we're using a fake xlsp server, then we don't want to do any of this.
	if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
	{
		return true;
	}

	int numDcs = mm_dedicated_xlsp_max_dcs.GetInt();
	DevMsg( "Enumerating XLSP datacenters (%d max supported)...\n", numDcs );

	//
	// Create enumerator
	//
	DWORD numBytes = 0;
	DWORD ret = g_pMatchExtensions->GetIXOnline()->XTitleServerCreateEnumerator( NULL, numDcs, &numBytes, &hEnumerate );
	if ( ret != ERROR_SUCCESS )
	{
		DevWarning( "XTitleServerCreateEnumerator failed (code = 0x%08X)\n", ret );
		hEnumerate = NULL;
		return false;
	}

	// Allocate results buffer
	bufResults.EnsureCapacity( numBytes );
	XTITLE_SERVER_INFO *pXSI = ( XTITLE_SERVER_INFO * ) bufResults.Base();
	ZeroMemory( pXSI, numBytes );
	ZeroMemory( &xOverlapped, sizeof( XOVERLAPPED ) );

	//
	// Enumerate
	//
	ret = XEnumerate( hEnumerate, pXSI, numBytes, NULL, &xOverlapped );
	if ( ret != ERROR_IO_PENDING )
	{
		DevWarning( "XEnumerate of XTitleServerCreateEnumerator failed (code = 0x%08X)\n", ret );
		CloseHandle( hEnumerate );
		hEnumerate = NULL;
		return false;
	}

	return true;
}

bool CXlspTitleServers::ExecuteQosDcs( HANDLE &hEnumerate, XOVERLAPPED &xOverlapped, CUtlBuffer &bufResults, DWORD &numServers, XNQOS *&pQos )
{
	// If we're using a fake xlsp server, then we don't want to do any of this.
	if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
	{
		return true;
	}

	numServers = 0;
	XGetOverlappedResult( &xOverlapped, &numServers, TRUE );
	CloseHandle( hEnumerate );
	hEnumerate = NULL;

	DevMsg( "Xlsp_OnEnumerateDcsCompleted found %d datacenters.\n", numServers );
	if ( !numServers )
	{
		return false;
	}

	//
	// Prepare for QOS lookup to the datacenters
	//
	XTITLE_SERVER_INFO *pXSI = ( XTITLE_SERVER_INFO * ) bufResults.Base();
	DWORD dwServiceId = g_pMatchFramework->GetMatchTitle()->GetTitleServiceID();
	CUtlVector< IN_ADDR > qosAddr;
	CUtlVector< DWORD > qosServiceId;
	for ( DWORD k = 0; k < numServers; ++ k )
	{
		qosAddr.AddToTail( pXSI[k].inaServer );
		qosServiceId.AddToTail( dwServiceId );
	}

	//
	// Submit QOS lookup
	//
	pQos = NULL;
	DWORD ret = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 0, NULL, NULL, NULL,
		numServers, qosAddr.Base(), qosServiceId.Base(),
		8, 0, 0,
		NULL, &pQos );
	if ( ret != ERROR_SUCCESS )
	{
		DevWarning( "XNetQosLookup failed to start for XLSP datacenters, code = 0x%08X!\n", ret );
		return false;
	}

	return true;
}

void CXlspTitleServers::ParseDatacentersFromQos( CUtlVector< CXlspDatacenter > &arrDcs, CUtlBuffer &bufResults, XNQOS *&pQos )
{
	// If we're using a fake xlsp server, then we don't want to do any of this and instead add our fake server to the datacenter list.
	if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
	{
		netadr_t gatewayAdr;
		gatewayAdr.SetFromString( CommandLine()->GetParm( CommandLine()->FindParm( "-xlsp_fake_gateway" ) + 1) );

		char szSG[200];
		sprintf(szSG, "%s**%d_1", gatewayAdr.ToString(true), gatewayAdr.GetPort() );

		CXlspDatacenter dc;
		ZeroMemory( &dc, sizeof( dc ) );
		Q_strncpy( dc.m_xsi.szServerInfo, szSG, sizeof(dc.m_xsi.szServerInfo) );
		dc.m_xsi.inaServer.S_un.S_addr = gatewayAdr.GetIPNetworkByteOrder();
		dc.ParseServerInfo();
		arrDcs.AddToTail( dc );
		return;
	}

	if ( !pQos )
		return;

	XTITLE_SERVER_INFO *pXSI = ( XTITLE_SERVER_INFO * ) bufResults.Base();

	for ( DWORD k = 0; k < pQos->cxnqos; ++ k )
	{
		// Datacenter info
		CXlspDatacenter dc;
		ZeroMemory( &dc, sizeof( dc ) );
		dc.m_xsi = pXSI[k];
		dc.m_qos = pQos->axnqosinfo[k];

		// Cull centers that failed to be contacted
		uint uiRequiredQosFlags = ( XNET_XNQOSINFO_COMPLETE | XNET_XNQOSINFO_TARGET_CONTACTED );
		if ( ( ( dc.m_qos.bFlags & uiRequiredQosFlags ) != uiRequiredQosFlags ) ||
			( dc.m_qos.bFlags & XNET_XNQOSINFO_TARGET_DISABLED ) )
		{
			DevWarning( "XLSP datacenter %d `%s` failed connection probe (0x%08X).\n", k, dc.m_xsi.szServerInfo, dc.m_qos.bFlags );
			continue;
		}

		// Cull any non-conformant XLSP center names
		if ( !dc.ParseServerInfo() )
		{
			DevWarning( "XLSP datacenter %d `%s` has non-conformant server info.\n", k, dc.m_xsi.szServerInfo );
			continue;
		}

		// Check if PII is required
		if ( m_bMustSupportPII && !dc.m_bSupportsPII )
		{
			DevWarning( "XLSP datacenter %d `%s` does not support PII.\n", k, dc.m_xsi.szServerInfo );
			continue;
		}

		// Check if we are forcing a specific datacenter
		bool bForcedUse = false;
		if ( char const *szForcedDcName = mm_dedicated_xlsp_force_dc.GetString() )
		{
			if ( *szForcedDcName && !Q_stricmp( szForcedDcName, dc.m_szGatewayName ) )
				bForcedUse = true;

			if ( *szForcedDcName && !bForcedUse )
			{
				// Gateway doesn't match forced datacenter
				DevWarning( "XLSP datacenter %d `%s` is ignored because we are forcing datacenter name `%s`.\n", k, dc.m_xsi.szServerInfo, szForcedDcName );
				continue;
			}
		}

		// Check ping
		if ( m_nPingLimit > 0 && dc.m_qos.wRttMedInMsecs > m_nPingLimit && !bForcedUse )
		{
			DevWarning( "XLSP datacenter %d `%s` is ignored because its ping %d is greater than max allowed %d.\n",
				k, dc.m_xsi.szServerInfo, dc.m_qos.wRttMedInMsecs, m_nPingLimit );
			continue;
		}

		// Remeber the datacenter as a potential candidate
		dc.m_nPingBucket = GetBucketedRTT( dc.m_qos.wRttMedInMsecs );
		DevMsg( "XLSP datacenter %d `%s` accepted, ping %d [<= %d]\n",
			k, dc.m_szGatewayName, dc.m_qos.wRttMedInMsecs, dc.m_nPingBucket );
		
		arrDcs.AddToTail( dc );
	}

	// Release the QOS query
	g_pMatchExtensions->GetIXOnline()->XNetQosRelease( pQos );
	pQos = NULL;
}

bool CXlspTitleServers::IsSearchCompleted() const
{
	return m_eState == STATE_FINISHED;
}

CUtlVector< CXlspDatacenter > & CXlspTitleServers::GetDatacenters()
{
	Assert( IsSearchCompleted() );
	return m_arrDcs;
}



//
// XLSP connection implementation
//

CXlspConnection::CXlspConnection( bool bMustSupportPII ) :
	m_pTitleServers( NULL ),
	m_pCmdResult( NULL ),
	m_flTimeout( 0.0f ),
	m_eState( STATE_INIT ),
	m_idCmdReplyId( 0 ),
	m_numCmdRetriesAllowed( 0 ),
	m_flCmdRetryTimeout( 0 ),
	m_bMustSupportPII( bMustSupportPII )
{
	ZeroMemory( &m_dc, sizeof( m_dc ) );
}

CXlspConnection::~CXlspConnection()
{
	;
}

void CXlspConnection::Destroy()
{
	if ( m_pTitleServers )
		m_pTitleServers->Destroy();
	m_pTitleServers = NULL;

	if ( m_eState >= STATE_CONNECTED )
		m_dc.Destroy();

	if ( m_eState == STATE_RUNNINGCMD )
		g_pMatchEventsSubscription->Unsubscribe( this );

	if ( m_pCmdResult )
		m_pCmdResult->deleteThis();
	m_pCmdResult = NULL;

	delete this;
}

bool CXlspConnection::IsConnected() const
{
	return m_eState == STATE_CONNECTED || m_eState == STATE_RUNNINGCMD;
}

bool CXlspConnection::HasError() const
{
	return m_eState == STATE_ERROR;
}

static int XlspConnection_CompareDcs( CXlspDatacenter const *dc1, CXlspDatacenter const *dc2 )
{
	int nPing1 = dc1->m_nPingBucket, nPing2 = dc2->m_nPingBucket;

	if ( nPing1 != nPing2 )
		return ( nPing1 < nPing2 ) ? -1 : 1;
	
	// Compare by name
	if ( int iNameCmp = Q_stricmp( dc1->m_szGatewayName, dc2->m_szGatewayName ) )
		return iNameCmp;

	// Compare by IP addr
	int iAddrCmp = dc1->m_xsi.inaServer.s_addr - dc2->m_xsi.inaServer.s_addr;
	if ( iAddrCmp )
		return iAddrCmp;

	return 0;
}

void CXlspConnection::ConnectXlspDc()
{
	CUtlVector< CXlspDatacenter > &arrDcs = m_pTitleServers->GetDatacenters();
	arrDcs.Sort( XlspConnection_CompareDcs );

	for ( int k = 0; k < arrDcs.Count(); ++ k )
	{
		m_dc = arrDcs[k];

		DevMsg( "[XLSP] Connecting to %s:%d (%d masters) - ping %d [<= %d]\n"
			"       ProbesXmit=%3d       ProbesRecv=%3d\n"
			"    RttMinInMsecs=%3d    RttMedInMsecs=%3d\n"
			"     UpBitsPerSec=%6d  DnBitsPerSec=%6d\n",
			m_dc.m_szGatewayName, m_dc.m_nMasterServerPortStart, m_dc.m_numMasterServers, m_dc.m_qos.wRttMedInMsecs, m_dc.m_nPingBucket,
			m_dc.m_qos.cProbesXmit, m_dc.m_qos.cProbesRecv,
			m_dc.m_qos.wRttMinInMsecs, m_dc.m_qos.wRttMedInMsecs,
			m_dc.m_qos.dwUpBitsPerSec, m_dc.m_qos.dwDnBitsPerSec );

		//
		// Resolve the secure address
		//
		DWORD ret = ERROR_SUCCESS;
		if ( CommandLine()->FindParm( "-xlsp_fake_gateway" ) )
		{
			m_dc.m_adrSecure = m_dc.m_xsi.inaServer;
		}
		else
		{
			ret = g_pMatchExtensions->GetIXOnline()->XNetServerToInAddr( m_dc.m_xsi.inaServer, g_pMatchFramework->GetMatchTitle()->GetTitleServiceID(), &m_dc.m_adrSecure );
		}

		if ( ret != ERROR_SUCCESS )
		{
			DevWarning( "Failed to resolve XLSP secure address (code = 0x%08X)!\n", ret );
			continue;
		}
		else
		{
			DevMsg( "Resolved XLSP server address\n" );
			m_eState = STATE_CONNECTED;

			m_pTitleServers->Destroy();
			m_pTitleServers = NULL;
			return;
		}
	}

	ZeroMemory( &m_dc, sizeof( m_dc ) );
	m_eState = STATE_ERROR;
}

CXlspDatacenter const & CXlspConnection::GetDatacenter() const
{
	return m_dc;
}

void CXlspConnection::ResolveCmdSystemValues( KeyValues *pCommand )
{
	// Expand the command data based on DC fields
	if ( KeyValues *pExp = pCommand->FindKey( "*dcpgmi" ) )
		pExp->SetInt( NULL, m_dc.m_qos.wRttMinInMsecs );
	if ( KeyValues *pExp = pCommand->FindKey( "*dcpgme" ) )
		pExp->SetInt( NULL, m_dc.m_qos.wRttMedInMsecs );
	if ( KeyValues *pExp = pCommand->FindKey( "*dcpgbu" ) )
		pExp->SetInt( NULL, m_dc.m_nPingBucket );
	if ( KeyValues *pExp = pCommand->FindKey( "*dcbwup" ) )
		pExp->SetInt( NULL, m_dc.m_qos.dwUpBitsPerSec );
	if ( KeyValues *pExp = pCommand->FindKey( "*dcbwdn" ) )
		pExp->SetInt( NULL, m_dc.m_qos.dwDnBitsPerSec );
	if ( KeyValues *pExp = pCommand->FindKey( "*net" ) )
		pExp->SetInt( NULL, g_pMatchExtensions->GetIXOnline()->XNetGetEthernetLinkStatus() );
	if ( KeyValues *pExp = pCommand->FindKey( "*nat" ) )
		pExp->SetInt( NULL, g_pMatchExtensions->GetIXOnline()->XOnlineGetNatType() );
	if ( KeyValues *pExp = pCommand->FindKey( "*mac" ) )
	{
		// Console MAC address
		XNADDR xnaddr;
		uint64 uiMachineId = 0ull;
		if ( XNET_GET_XNADDR_PENDING == g_pMatchExtensions->GetIXOnline()->XNetGetTitleXnAddr( &xnaddr ) ||
			g_pMatchExtensions->GetIXOnline()->XNetXnAddrToMachineId( &xnaddr, &uiMachineId ) )
			uiMachineId = 0ull;
		pExp->SetUint64( NULL, uiMachineId );
	}
	if ( KeyValues *pExp = pCommand->FindKey( "*diskDsn" ) )
	{
		// Serial number of GAME volume
		struct VolumeInformation_t {
			char chVolumeName[128];
			char chFsName[128];
			DWORD dwVolumeSerial;
			DWORD dwMaxComponentLen;
			DWORD dwFsFlags;
		} vi;
		memset( &vi, 0, sizeof( vi ) );
		if ( !GetVolumeInformation( "d:\\",
			vi.chVolumeName, sizeof( vi.chVolumeName ) - 1,
			&vi.dwVolumeSerial, &vi.dwMaxComponentLen, &vi.dwFsFlags,
			vi.chFsName, sizeof( vi.chFsName ) - 1
			) )
		{
			memset( &vi, 0, sizeof( vi ) );
		}
		
		uint64 uiDsn = vi.dwVolumeSerial;
		if ( g_pFullFileSystem && g_pFullFileSystem->IsDVDHosted() )
			uiDsn |= ( 1ull << 33 );	// DVD hosted
		
		pExp->SetUint64( NULL, uiDsn );
	}
	if ( KeyValues *pExp = pCommand->FindKey( "*diskDnfo" ) )
	{
		// Space information of GAME volume
		struct FreeSpace_t
		{
			ULARGE_INTEGER ulFreeTitle, ulTotal, ulFree;
		} fs;
		memset( &fs, 0, sizeof( fs ) );
		if ( !GetDiskFreeSpaceEx( "d:\\",
			&fs.ulFreeTitle, &fs.ulTotal, &fs.ulFree ) )
		{
			memset( &fs, 0, sizeof( fs ) );
		}
		uint32 uiTotalMbs = fs.ulTotal.QuadPart / ( 1024 * 1024 );
		uint32 uiFreeMbs = fs.ulFree.QuadPart / ( 1024 * 1024 );
		pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 )  );
	}
	if ( KeyValues *pExp = pCommand->FindKey( "*diskCnfo" ) )
	{
		// Space information of CACHE partition
		struct FreeSpace_t
		{
			ULARGE_INTEGER ulFreeTitle, ulTotal, ulFree;
		} fs;
		memset( &fs, 0, sizeof( fs ) );
		if ( !GetDiskFreeSpaceEx( "cache:\\",
			&fs.ulFreeTitle, &fs.ulTotal, &fs.ulFree ) )
		{
			memset( &fs, 0, sizeof( fs ) );
		}
		uint32 uiTotalMbs = fs.ulTotal.QuadPart / ( 1024 * 1024 );
		uint32 uiFreeMbs = fs.ulFree.QuadPart / ( 1024 * 1024 );
		pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 )  );
	}
	if ( KeyValues *pExp = pCommand->FindKey( "*diskHnfo" ) )
	{
		// Space information of HDD volume
		XDEVICE_DATA xdd;
		memset( &xdd, 0, sizeof( xdd ) );
		xdd.DeviceID = 1;
		if ( XContentGetDeviceData( xdd.DeviceID, &xdd ) )
		{
			memset( &xdd, 0, sizeof( xdd ) );
		}
		uint32 uiTotalMbs = xdd.ulDeviceBytes / ( 1024 * 1024 );
		uint32 uiFreeMbs = xdd.ulDeviceFreeBytes / ( 1024 * 1024 );
		pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 )  );
	}
	if ( KeyValues *pExp = pCommand->FindKey( "*disk1nfo" ) )
	{
		// Space information of user-chosen storage device
		XDEVICE_DATA xdd;
		memset( &xdd, 0, sizeof( xdd ) );
		xdd.DeviceID = XBX_GetStorageDeviceId( XBX_GetPrimaryUserId() );
		if ( XContentGetDeviceData( xdd.DeviceID, &xdd ) )
		{
			memset( &xdd, 0, sizeof( xdd ) );
		}
		uint32 uiTotalMbs = xdd.ulDeviceBytes / ( 1024 * 1024 );
		uint32 uiFreeMbs = xdd.ulDeviceFreeBytes / ( 1024 * 1024 );
		pExp->SetUint64( NULL, uint64( uiTotalMbs ) | ( uint64( uiFreeMbs ) << 32 )  );
	}
}

netadr_t CXlspConnection::GetXlspDcAddress()
{
	//
	// Convert DC address to netadr_t on a random master port
	//
	netadr_t inetAddr;
	inetAddr.SetType( NA_IP );
	inetAddr.SetIPAndPort( m_dc.m_adrSecure.s_addr,
		m_dc.m_nMasterServerPortStart + RandomInt( 0, m_dc.m_numMasterServers - 1 )
		+ mm_dedicated_xlsp_force_dc_offset.GetInt() );
	return inetAddr;
}

bool CXlspConnection::ExecuteCmd( KeyValues *pCommand, int numRetriesAllowed, float flCmdRetryTimeout )
{
	if ( !pCommand )
		return false;

	if ( m_eState != STATE_CONNECTED )
	{
		Assert( !"CXlspConnection::ExecuteCmd while not connected to XLSP server!\n" );
		return false;
	}

	ResolveCmdSystemValues( pCommand );

	// Serialize the command
	CUtlBuffer bufBinData;
	bufBinData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
	if ( !pCommand->WriteAsBinary( bufBinData ) )
		return false;

	// Destroy the previous result data
	if ( m_pCmdResult )
		m_pCmdResult->deleteThis();
	m_pCmdResult = NULL;

	m_idCmdReplyId = GetNextXlspConnectionCmdReplyId();
	m_numCmdRetriesAllowed = numRetriesAllowed;
	m_flCmdRetryTimeout = ( ( flCmdRetryTimeout > 0 ) ? flCmdRetryTimeout : mm_dedicated_xlsp_command_timeout.GetFloat() );

	//
	// Prepare the request payload
	//
	char msg_buffer[ INetSupport::NC_MAX_ROUTABLE_PAYLOAD ];
	bf_write msg( msg_buffer, sizeof( msg_buffer ) );

	msg.WriteByte( A2A_KV_CMD );
	msg.WriteByte( A2A_KV_VERSION );

	// Xbox 360 -> Master server
	msg.WriteLong( MAKE_4BYTES( 'X', '-', 'M', '1' ) );

	msg.WriteLong( m_idCmdReplyId );
	msg.WriteLong( m_dc.m_adrSecure.s_addr );	// datacenter's challenge
	msg.WriteLong( 0 );
	msg.WriteLong( bufBinData.TellMaxPut() );
	msg.WriteBytes( bufBinData.Base(), bufBinData.TellMaxPut() ); // datacenter command

	DevMsg( 2, "Xbox->XLSP: 0x%08X 0x%08X ( %u bytes, %d retries allowed )\n", m_idCmdReplyId, (uint32) m_dc.m_adrSecure.s_addr, msg.GetNumBytesWritten(), m_numCmdRetriesAllowed );
	KeyValuesDumpAsDevMsg( pCommand, 1, 2 );

	g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, INetSupport::NS_SOCK_CLIENT,
		GetXlspDcAddress(), msg.GetData(), msg.GetNumBytesWritten() );

	if ( m_numCmdRetriesAllowed > 0 )
	{
		m_bufCmdRetry.SetCount( msg.GetNumBytesWritten() );
		memcpy( m_bufCmdRetry.Base(), msg.GetData(), msg.GetNumBytesWritten() );
	}
	else
	{
		m_bufCmdRetry.Purge();
	}

	g_pMatchEventsSubscription->Subscribe( this );
	m_eState = STATE_RUNNINGCMD;
	m_flTimeout = Plat_FloatTime() + m_flCmdRetryTimeout;
	return true;
}

void CXlspConnection::OnEvent( KeyValues *pEvent )
{
	char const *szName = pEvent->GetName();

	if ( !Q_stricmp( "A2A_KV_CMD", szName ) )
	{
		// Master server -> Xbox 360
		if ( pEvent->GetInt( "header" ) == MAKE_4BYTES( 'M', '-', 'X', '1' ) &&
			 pEvent->GetInt( "replyid" ) == m_idCmdReplyId )
		{
			g_pMatchEventsSubscription->Unsubscribe( this );
			m_eState = STATE_CONNECTED;

			m_pCmdResult = pEvent->GetFirstTrueSubKey();
			if ( !m_pCmdResult )
				m_pCmdResult = pEvent;

			// Keep it as a copy
			m_pCmdResult = m_pCmdResult->MakeCopy();

			DevMsg( 2, "Xbox<<XLSP: 0x%08X ( %u bytes )\n", m_idCmdReplyId, pEvent->GetInt( "size" ) );
			// KeyValuesDumpAsDevMsg( m_pCmdResult, 1, 2 );
		}
	}
}

void CXlspConnection::Update()
{
	switch ( m_eState )
	{
	case STATE_INIT:
		m_eState = STATE_CONNECTING;
		m_pTitleServers = new CXlspTitleServers( 0, m_bMustSupportPII );	// no ping limitation
		break;

	case STATE_CONNECTING:
		m_pTitleServers->Update();
		if ( m_pTitleServers->IsSearchCompleted() )
			ConnectXlspDc();
		break;

	case STATE_RUNNINGCMD:
		if ( Plat_FloatTime() > m_flTimeout )
		{
			if ( m_numCmdRetriesAllowed > 0 )
			{
				// Retry and increase timeout
				-- m_numCmdRetriesAllowed;
				m_flTimeout = Plat_FloatTime() + m_flCmdRetryTimeout;
				DevMsg( 2, "Xbox->XLSP: 0x%08X 0x%08X ( %u bytes, %d retries remaining )\n", m_idCmdReplyId, (uint32) m_dc.m_adrSecure.s_addr, m_bufCmdRetry.Count(), m_numCmdRetriesAllowed );
				g_pMatchExtensions->GetINetSupport()->SendPacket( NULL, INetSupport::NS_SOCK_CLIENT,
					GetXlspDcAddress(), m_bufCmdRetry.Base(), m_bufCmdRetry.Count() );
				return;
			}
			DevWarning( 2, "Xbox->XLSP: 0x%08X 0x%08X - TIMED OUT!\n", m_idCmdReplyId, (uint32) m_dc.m_adrSecure.s_addr );
			g_pMatchEventsSubscription->Unsubscribe( this );
			m_eState = STATE_ERROR;
		}
		break;
	}
}


//
// Connection batch implementation
//

CXlspConnectionCmdBatch::CXlspConnectionCmdBatch( CXlspConnection *pConnection, CUtlVector<KeyValues*> &arrCommands, int numRetriesAllowedPerEachCmd, float flCommandTimeout ) :
	m_eState( STATE_BATCH_WAITING ),
	m_iCommand( 0 ),
	m_pXlspConn( pConnection ),
	m_numRetriesAllowedPerEachCmd( numRetriesAllowedPerEachCmd ),
	m_flCommandTimeout( ( flCommandTimeout > 0 ) ? flCommandTimeout : mm_dedicated_xlsp_command_timeout.GetFloat() )
{
	m_arrCommands.Swap( arrCommands );
}

CXlspConnectionCmdBatch::~CXlspConnectionCmdBatch()
{
}

bool CXlspConnectionCmdBatch::IsFinished() const
{
	return m_eState >= STATE_FINISHED;
}

bool CXlspConnectionCmdBatch::HasAllResults() const
{
	return IsFinished() && m_arrCommands.Count() == m_arrResults.Count();
}

void CXlspConnectionCmdBatch::Update()
{
	m_pXlspConn->Update();

	if ( m_pXlspConn->HasError() )
	{
		m_eState = STATE_FINISHED;
	}

	switch ( m_eState )
	{
	case STATE_BATCH_WAITING:
		if ( m_pXlspConn->IsConnected() )
		{
			RunNextCmd();
		}
		break;

	case STATE_RUNNINGCMD:
		if ( KeyValues *pCmdResult = m_pXlspConn->GetCmdResult() )
		{
			m_arrResults.AddToTail( pCmdResult->MakeCopy() );
			RunNextCmd();
		}
		break;
	}
}

void CXlspConnectionCmdBatch::RunNextCmd()
{
	if ( m_iCommand >= m_arrCommands.Count() ||
		 m_pXlspConn->HasError() ||
		!m_pXlspConn->ExecuteCmd( m_arrCommands[ m_iCommand ], m_numRetriesAllowedPerEachCmd, m_flCommandTimeout ) )
	{
		m_eState = STATE_FINISHED;
	}
	else
	{
		++ m_iCommand;
		m_eState = STATE_RUNNINGCMD;
	}
}

void CXlspConnectionCmdBatch::Destroy()
{
	for ( int k = 0; k < m_arrCommands.Count(); ++ k )
		m_arrCommands[k]->deleteThis();

	for ( int k = 0; k < m_arrResults.Count(); ++ k )
		m_arrResults[k]->deleteThis();

	m_arrCommands.Purge();
	m_arrResults.Purge();

	m_pXlspConn = NULL;

	delete this;
}


#endif // _X360