summaryrefslogtreecommitdiff
path: root/replay/cl_sessioninfodownloader.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /replay/cl_sessioninfodownloader.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'replay/cl_sessioninfodownloader.cpp')
-rw-r--r--replay/cl_sessioninfodownloader.cpp444
1 files changed, 444 insertions, 0 deletions
diff --git a/replay/cl_sessioninfodownloader.cpp b/replay/cl_sessioninfodownloader.cpp
new file mode 100644
index 0000000..4cd586d
--- /dev/null
+++ b/replay/cl_sessioninfodownloader.cpp
@@ -0,0 +1,444 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "cl_sessioninfodownloader.h"
+#include "replay/ienginereplay.h"
+#include "replay/shared_defs.h"
+#include "cl_recordingsession.h"
+#include "cl_recordingsessionblock.h"
+#include "cl_replaycontext.h"
+#include "cl_sessionblockdownloader.h"
+#include "KeyValues.h"
+#include "convar.h"
+#include "dbg.h"
+#include "vprof.h"
+#include "sessioninfoheader.h"
+#include "utlbuffer.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//----------------------------------------------------------------------------------------
+
+extern IEngineReplay *g_pEngine;
+
+//----------------------------------------------------------------------------------------
+
+CSessionInfoDownloader::CSessionInfoDownloader()
+: m_pDownloader( NULL ),
+ m_pSession( NULL ),
+ m_flLastDownloadTime( 0.0f ),
+ m_nError( ERROR_NONE ),
+ m_nHttpError( HTTP_ERROR_NONE ),
+ m_bDone( false )
+{
+}
+
+CSessionInfoDownloader::~CSessionInfoDownloader()
+{
+ Assert( m_pDownloader == NULL ); // We should have deleted the downloader already
+}
+
+void CSessionInfoDownloader::CleanupDownloader()
+{
+ if ( m_pDownloader )
+ {
+ m_pDownloader->AbortDownloadAndCleanup();
+ m_pDownloader = NULL;
+ }
+}
+
+void CSessionInfoDownloader::DownloadSessionInfoAndUpdateBlocks( CBaseRecordingSession *pSession )
+{
+ Assert( m_pDownloader == NULL );
+
+ // Cache session
+ m_pSession = pSession;
+
+ // Download the session info now
+ m_pDownloader = new CHttpDownloader( this );
+ m_pDownloader->BeginDownload( pSession->GetSessionInfoURL(), NULL );
+}
+
+float CSessionInfoDownloader::GetNextThinkTime() const
+{
+ extern ConVar replay_sessioninfo_updatefrequency;
+ return m_flLastDownloadTime + replay_sessioninfo_updatefrequency.GetFloat();
+}
+
+void CSessionInfoDownloader::Think()
+{
+ VPROF_BUDGET( "CSessionInfoDownloader::Think", VPROF_BUDGETGROUP_REPLAY );
+
+ CBaseThinker::Think();
+
+ // If we're not downloading, no need to think
+ if ( !m_pDownloader )
+ return;
+
+ // If the download's complete
+ if ( m_pDownloader->IsDone() && m_pDownloader->CanDelete() )
+ {
+ // We're done - CanDelete() will now return true
+ delete m_pDownloader;
+ m_pDownloader = NULL;
+ }
+ else
+ {
+ // Otherwise, think...
+ m_pDownloader->Think();
+ }
+}
+
+void CSessionInfoDownloader::OnDownloadComplete( CHttpDownloader *pDownloader, const unsigned char *pData )
+{
+ Assert( pDownloader );
+
+ // Clear out any previous error
+ m_nError = ERROR_NONE;
+ m_nHttpError = HTTP_ERROR_NONE;
+
+ bool bUpdatedSomething = false;
+
+#if _DEBUG
+ extern ConVar replay_simulatedownloadfailure;
+ const bool bForceError = replay_simulatedownloadfailure.GetInt() == 2;
+#else
+ const bool bForceError = false;
+#endif
+
+ if ( pDownloader->GetStatus() != HTTP_DONE || bForceError )
+ {
+ m_nError = ERROR_DOWNLOAD_FAILED;
+ m_nHttpError = pDownloader->GetError();
+ DBG( "Session info download FAILED.\n" );
+ }
+ else
+ {
+ Assert( pData );
+
+ // Read header
+ SessionInfoHeader_t header;
+ if ( !ReadSessionInfoHeader( pData, pDownloader->GetSize(), header ) )
+ {
+ m_nError = ERROR_NOT_ENOUGH_DATA;
+ }
+ else
+ {
+ IF_REPLAY_DBG( Warning( "Session info downloaded successfully for session %s\n", header.m_szSessionName ) );
+
+ // Get number of blocks for data validation
+ const int nNumBlocks = header.m_nNumBlocks;
+ if ( nNumBlocks <= 0 )
+ {
+ m_nError = ERROR_BAD_NUM_BLOCKS;
+ }
+ else
+ {
+ // The block has either been found or created - now, fill it with data
+ const char *pSessionName = header.m_szSessionName;
+ if ( !pSessionName || !pSessionName[0] )
+ {
+ m_nError = ERROR_NO_SESSION_NAME;
+ }
+ else
+ {
+ // Now that we have the session name, find it in the client session manager
+ CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSessionByName( pSessionName ) ); Assert( pSession );
+ if ( !pSession )
+ {
+ AssertMsg( 0, "Session should always exist by this point" );
+ m_nError = ERROR_UNKNOWN_SESSION;
+ }
+ else
+ {
+ // Get recording state for session
+ pSession->m_bRecording = header.m_bRecording;
+
+ const CompressorType_t nHeaderCompressorType = header.m_nCompressorType;
+ uint8 *pPayload = (uint8 *)pData + sizeof( SessionInfoHeader_t );
+ uint8 *pUncompressedPayload = NULL;
+ unsigned int uUncompressedPayloadSize = header.m_uPayloadSizeUC;
+
+ // Validate the payload with the MD5 digest
+ bool bPayloadValid = g_pEngine->MD5_HashBuffer( header.m_aHash, (const uint8 *)pPayload, header.m_uPayloadSize, NULL );
+
+ if ( !bPayloadValid )
+ {
+ m_nError = ERROR_PAYLOAD_HASH_FAILED;
+ }
+ else
+ {
+ if ( nHeaderCompressorType == COMPRESSORTYPE_INVALID )
+ {
+ // Uncompressed payload is read to go
+ pUncompressedPayload = pPayload;
+ }
+ else
+ {
+ // Attempt to decompress the payload
+ ICompressor *pCompressor = CreateCompressor( header.m_nCompressorType );
+ if ( !pCompressor )
+ {
+ bPayloadValid = false;
+ m_nError = ERROR_COULD_NOT_CREATE_COMPRESSOR;
+ }
+ else
+ {
+ // Uncompressed size big enough to read at least one block?
+ if ( header.m_uPayloadSizeUC <= MIN_SESSION_INFO_PAYLOAD_SIZE )
+ {
+ bPayloadValid = false;
+ m_nError = ERROR_INVALID_UNCOMPRESSED_SIZE;
+ }
+ else
+ {
+ // Attempt to decompress payload now
+ pUncompressedPayload = new uint8[ uUncompressedPayloadSize ];
+ if ( !pCompressor->Decompress( (char *)pUncompressedPayload, &uUncompressedPayloadSize, (const char *)pPayload, header.m_uPayloadSize ) )
+ {
+ bPayloadValid = false;
+ m_nError = ERROR_PAYLOAD_DECOMPRESS_FAILED;
+ }
+ }
+ }
+ }
+
+ if ( bPayloadValid )
+ {
+ AssertMsg( pUncompressedPayload, "This should never be NULL here." );
+ AssertMsg( uUncompressedPayloadSize >= MIN_SESSION_INFO_PAYLOAD_SIZE, "This size should always be valid here." );
+
+ RecordingSessionBlockSpec_t DummyBlock;
+ CUtlBuffer buf( pUncompressedPayload, uUncompressedPayloadSize, CUtlBuffer::READ_ONLY );
+
+ // Optimization: start the read at the first block we care about, which is one block after the last consecutive, downloaded block.
+ // This optimization should come in handy on servers that run lengthy rounds, so we're not reiterating over inconsequential blocks
+ // (i.e. they were already downloaded).
+ int iStartBlock = pSession->GetGreatestConsecutiveBlockDownloaded() + 1;
+ if ( iStartBlock > 0 )
+ {
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, iStartBlock * sizeof( RecordingSessionBlockSpec_t ) );
+ }
+
+ // If for some reason the seek caused a 'get' overflow, try reading from the start of the buffer.
+ if ( !buf.IsValid() )
+ {
+ iStartBlock = 0;
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ }
+
+ // Read blocks, starting from the calculated start block.
+ for ( int i = iStartBlock; i < header.m_nNumBlocks; ++i )
+ {
+ // Attempt to read the current block from the buffer
+ buf.Get( &DummyBlock, sizeof( DummyBlock ) );
+ if ( !buf.IsValid() )
+ {
+ m_nError = ERROR_BLOCK_READ_FAILED;
+ break;
+ }
+
+ IF_REPLAY_DBG( Warning( "processing block with recon index: %i\n", DummyBlock.m_iReconstruction ) );
+
+ // Get reconstruction index
+ const int iBlockReconstruction = (ReplayHandle_t)DummyBlock.m_iReconstruction;
+ if ( iBlockReconstruction < 0 )
+ {
+ m_nError = ERROR_INVALID_ORDER;
+ continue;
+ }
+
+ // Check status
+ const int nRemoteStatus = (int)DummyBlock.m_uRemoteStatus;
+ if ( nRemoteStatus < 0 || nRemoteStatus >= CBaseRecordingSessionBlock::MAX_STATUS )
+ {
+ // Status not found or invalid status
+ m_nError = ERROR_INVALID_REPLAY_STATUS;
+ continue;
+ }
+
+ // Get the block file size
+ const uint32 uFileSize = (uint32)DummyBlock.m_uFileSize;
+
+ // Get the uncompressed block size
+ const uint32 uUncompressedSize = (uint32)DummyBlock.m_uUncompressedSize;
+
+ // Get the compressor type
+ const int nCompressorType = (uint32)DummyBlock.m_nCompressorType;
+
+ // Attempt to find the block in the session
+ CClientRecordingSessionBlock *pBlock = CL_CastBlock( CL_GetRecordingSessionBlockManager()->FindBlockForSession( m_pSession->GetHandle(), iBlockReconstruction ) );
+
+ // If the block exists and has already been downloaded, we have nothing more to update
+ if ( pBlock && !pBlock->NeedsUpdate() )
+ continue;
+
+ bool bBlockDataChanged = false;
+
+ // If the block doesn't exist in the session block manager, create it now
+ if ( !pBlock )
+ {
+ CClientRecordingSessionBlock *pNewBlock = CL_CastBlock( CL_GetRecordingSessionBlockManager()->CreateAndGenerateHandle() );
+ pNewBlock->m_iReconstruction = iBlockReconstruction;
+ // pNewBlock->m_strFullFilename = Replay_va(
+ const char *pFullFilename = Replay_va(
+ "%s%s_part_%i.%s",
+ pNewBlock->GetPath(),
+ pSession->m_strName.Get(), pNewBlock->m_iReconstruction,
+ BLOCK_FILE_EXTENSION
+ );
+ V_strcpy( pNewBlock->m_szFullFilename, pFullFilename );
+ pNewBlock->m_hSession = pSession->GetHandle();
+
+ // Add to session block manager
+ CL_GetRecordingSessionBlockManager()->Add( pNewBlock );
+
+ // Add the block to the session (marks session as dirty)
+ pSession->AddBlock( pNewBlock, false );
+
+ // Use the new block
+ pBlock = pNewBlock;
+
+ bBlockDataChanged = true;
+ }
+
+ IF_REPLAY_DBG2( Warning( " Block %i status=%s\n", pBlock->m_iReconstruction, pBlock->GetRemoteStatusStringSafe( pBlock->m_nRemoteStatus ) ) );
+
+ // Now that we've got a block, replicate the server data/fill it in
+ if ( pBlock->m_nRemoteStatus != nRemoteStatus )
+ {
+ pBlock->m_nRemoteStatus = (CBaseRecordingSessionBlock::RemoteStatus_t)nRemoteStatus;
+ bBlockDataChanged = true;
+ }
+
+ // Block file size needs to be set?
+ if ( pBlock->m_uFileSize != uFileSize )
+ {
+ pBlock->m_uFileSize = uFileSize;
+ bBlockDataChanged = true;
+ }
+
+ // Uncompressed block file size needs to be set?
+ if ( pBlock->m_uUncompressedSize != uUncompressedSize )
+ {
+ Assert( nCompressorType >= COMPRESSORTYPE_INVALID );
+ pBlock->m_uUncompressedSize = uUncompressedSize;
+ bBlockDataChanged = true;
+ }
+
+ // Compressor type needs to be set?
+ if ( pBlock->m_nCompressorType != (CompressorType_t)nCompressorType )
+ {
+ pBlock->m_nCompressorType = (CompressorType_t)nCompressorType;
+ bBlockDataChanged = true;
+ }
+
+ // Attempt to read the hash if we haven't already done so
+ if ( !pBlock->HasValidHash() )
+ {
+ V_memcpy( pBlock->m_aHash, DummyBlock.m_aHash, sizeof( pBlock->m_aHash ) );
+ bBlockDataChanged = true;
+ }
+
+ // Shift the block's state from waiting to ready-to-download if the block is ready on the server.
+ if ( pBlock->m_nDownloadStatus == CClientRecordingSessionBlock::DOWNLOADSTATUS_WAITING &&
+ nRemoteStatus == CBaseRecordingSessionBlock::STATUS_READYFORDOWNLOAD )
+ {
+ pBlock->m_nDownloadStatus = CClientRecordingSessionBlock::DOWNLOADSTATUS_READYTODOWNLOAD;
+ }
+
+ // Save
+ if ( bBlockDataChanged )
+ {
+ CL_GetRecordingSessionBlockManager()->FlagForFlush( pBlock, false );
+
+ bUpdatedSomething = true;
+ }
+ }
+
+ // If this session is not recording, make sure the max number of blocks in the session is in check.
+ if ( !pSession->m_bRecording && pSession->GetLastBlockToDownload() >= nNumBlocks )
+ {
+ // This will adjust all replay max blocks
+ pSession->AdjustLastBlockToDownload( nNumBlocks - 1 );
+ }
+
+ // If we've updated something, set cache the current time in the session
+ if ( bUpdatedSomething )
+ {
+ pSession->RefreshLastUpdateTime();
+ }
+ else if ( pSession->GetLastUpdateTime() >= 0.0f && ( g_pEngine->GetHostTime() - pSession->GetLastUpdateTime() > DOWNLOAD_TIMEOUT_THRESHOLD ) )
+ {
+ pSession->OnDownloadTimeout();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Display a message for the given download error
+ if ( m_nError != ERROR_NONE )
+ {
+ // Report an error to the user
+ const char *pErrorToken = GetErrorString( m_nError, m_nHttpError );
+ if ( m_nError == ERROR_DOWNLOAD_FAILED )
+ {
+ KeyValues *pParams = new KeyValues( "args", "url", pDownloader->GetURL() );
+ CL_GetErrorSystem()->AddFormattedErrorFromTokenName( pErrorToken, pParams );
+ }
+ else
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( pErrorToken );
+ }
+
+ // Report error to OGS
+ CL_GetErrorSystem()->OGS_ReportSessioInfoDownloadError( pDownloader, pErrorToken );
+ }
+
+ // Flag as done
+ m_bDone = true;
+
+ // Cache download time
+ m_flLastDownloadTime = g_pEngine->GetHostTime();
+}
+
+const char *CSessionInfoDownloader::GetErrorString( int nError, HTTPError_t nHttpError ) const
+{
+ switch ( nError )
+ {
+ case ERROR_NO_SESSION_NAME: return "#Replay_DL_Err_SI_NoSessionName";
+ case ERROR_REPLAY_NOT_FOUND: return "#Replay_DL_Err_SI_ReplayNotFound";
+ case ERROR_INVALID_REPLAY_STATUS: return "#Replay_DL_Err_SI_InvalidReplayStatus";
+ case ERROR_INVALID_ORDER: return "#Replay_DL_Err_SI_InvalidOrder";
+ case ERROR_UNKNOWN_SESSION: return "#Replay_DL_Err_SI_Unknown_Session";
+ case ERROR_BLOCK_READ_FAILED: return "#Replay_DL_Err_SI_BlockReadFailed";
+ case ERROR_NOT_ENOUGH_DATA: return "#Replay_DL_Err_SI_NotEnoughData";
+ case ERROR_COULD_NOT_CREATE_COMPRESSOR: return "#Replay_DL_Err_SI_CouldNotCreateCompressor";
+ case ERROR_INVALID_UNCOMPRESSED_SIZE: return "#Replay_DL_Err_SI_InvalidUncompressedSize";
+ case ERROR_PAYLOAD_DECOMPRESS_FAILED: return "#Replay_DL_Err_SI_PayloadDecompressFailed";
+ case ERROR_PAYLOAD_HASH_FAILED: return "#Replay_DL_Err_SI_PayloadHashFailed";
+
+ case ERROR_DOWNLOAD_FAILED:
+ switch ( m_nHttpError )
+ {
+ case HTTP_ERROR_ZERO_LENGTH_FILE: return "#Replay_DL_Err_SI_DownloadFailed_ZeroLengthFile";
+ case HTTP_ERROR_CONNECTION_CLOSED: return "#Replay_DL_Err_SI_DownloadFailed_ConnectionClosed";
+ case HTTP_ERROR_INVALID_URL: return "#Replay_DL_Err_SI_DownloadFailed_InvalidURL";
+ case HTTP_ERROR_INVALID_PROTOCOL: return "#Replay_DL_Err_SI_DownloadFailed_InvalidProtocol";
+ case HTTP_ERROR_CANT_BIND_SOCKET: return "#Replay_DL_Err_SI_DownloadFailed_CantBindSocket";
+ case HTTP_ERROR_CANT_CONNECT: return "#Replay_DL_Err_SI_DownloadFailed_CantConnect";
+ case HTTP_ERROR_NO_HEADERS: return "#Replay_DL_Err_SI_DownloadFailed_NoHeaders";
+ case HTTP_ERROR_FILE_NONEXISTENT: return "#Replay_DL_Err_SI_DownloadFailed_FileNonExistent";
+ default: return "#Replay_DL_Err_SI_DownloadFailed_UnknownError";
+ }
+ }
+ return "#Replay_DL_Err_SI_Unknown";
+}
+
+//----------------------------------------------------------------------------------------