summaryrefslogtreecommitdiff
path: root/engine/net_chan.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 /engine/net_chan.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/net_chan.cpp')
-rw-r--r--engine/net_chan.cpp3232
1 files changed, 3232 insertions, 0 deletions
diff --git a/engine/net_chan.cpp b/engine/net_chan.cpp
new file mode 100644
index 0000000..bccf007
--- /dev/null
+++ b/engine/net_chan.cpp
@@ -0,0 +1,3232 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: net_chan.cpp: implementation of the CNetChan_t struct.
+//
+//=============================================================================//
+
+#include "../utils/bzip2/bzlib.h"
+#include "net_chan.h"
+#include "tier1/strtools.h"
+#include "filesystem_engine.h"
+#include "demo.h"
+#include "convar.h"
+#include "mathlib/mathlib.h"
+#include "protocol.h"
+#include "inetmsghandler.h"
+#include "host.h"
+#include "netmessages.h"
+#include "tier0/vcrmode.h"
+#include "tier0/etwprof.h"
+#include "tier0/vprof.h"
+#include "net_ws_headers.h"
+#include "net_ws_queued_packet_sender.h"
+#include "filesystem_init.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+ConVar net_showudp( "net_showudp", "0", 0, "Dump UDP packets summary to console" );
+ConVar net_showtcp( "net_showtcp", "0", 0, "Dump TCP stream summary to console" );
+ConVar net_blocksize( "net_maxfragments", NETSTRING( MAX_ROUTABLE_PAYLOAD ), 0, "Max fragment bytes per packet", true, FRAGMENT_SIZE, true, MAX_ROUTABLE_PAYLOAD );
+
+static ConVar net_showmsg( "net_showmsg", "0", 0, "Show incoming message: <0|1|name>" );
+static ConVar net_showfragments( "net_showfragments", "0", 0, "Show netchannel fragments" );
+static ConVar net_showpeaks( "net_showpeaks", "0", 0, "Show messages for large packets only: <size>" );
+static ConVar net_blockmsg( "net_blockmsg", "none", FCVAR_CHEAT, "Discards incoming message: <0|1|name>" );
+static ConVar net_showdrop( "net_showdrop", "0", 0, "Show dropped packets in console" );
+static ConVar net_drawslider( "net_drawslider", "0", 0, "Draw completion slider during signon" );
+static ConVar net_chokeloopback( "net_chokeloop", "0", 0, "Apply bandwidth choke to loopback packets" );
+static ConVar net_maxfilesize( "net_maxfilesize", "16", 0, "Maximum allowed file size for uploading in MB", true, 0, true, 64 );
+static ConVar net_compresspackets( "net_compresspackets", "1", 0, "Use compression on game packets." );
+static ConVar net_compresspackets_minsize( "net_compresspackets_minsize", "1024", 0, "Don't bother compressing packets below this size." );
+static ConVar net_maxcleartime( "net_maxcleartime", "4.0", 0, "Max # of seconds we can wait for next packets to be sent based on rate setting (0 == no limit)." );
+static ConVar net_maxpacketdrop( "net_maxpacketdrop", "5000", 0, "Ignore any packets with the sequence number more than this ahead (0 == no limit)" );
+
+extern ConVar net_maxroutable;
+
+extern int NET_ConnectSocket( int nSock, const netadr_t &addr );
+extern void NET_CloseSocket( int hSocket, int sock = -1 );
+extern int NET_SendStream( int nSock, const char * buf, int len, int flags );
+extern int NET_ReceiveStream( int nSock, char * buf, int len, int flags );
+
+
+// If the network connection hasn't been active in this many seconds, display some warning text.
+#define CONNECTION_PROBLEM_TIME 4.0f // assume network problem after this time
+
+#define BYTES2FRAGMENTS(i) ((i+FRAGMENT_SIZE-1)/FRAGMENT_SIZE)
+
+#define FLIPBIT(v,b) if (v&b) v &= ~b; else v |= b;
+
+// We only need to checksum packets on the PC and only when we're actually sending them over the network.
+static bool ShouldChecksumPackets()
+{
+ if ( !IsPC() )
+ return false;
+
+ return NET_IsMultiplayer();
+}
+
+bool CNetChan::IsLoopback() const
+{
+ return remote_address.IsLoopback();
+}
+
+bool CNetChan::IsNull() const
+{
+ return remote_address.GetType() == NA_NULL ? true : false;
+}
+
+/*
+==============================
+CNetChan::Clear
+
+==============================
+*/
+void CNetChan::Clear()
+{
+ int i;
+
+ // clear waiting lists
+
+ for ( i=0; i<MAX_STREAMS; i++ )
+ {
+ while ( m_WaitingList[i].Count() )
+ RemoveHeadInWaitingList( i );
+
+ if ( m_ReceiveList[i].buffer )
+ {
+ delete[] m_ReceiveList[i].buffer;
+ m_ReceiveList[i].buffer = NULL;
+ }
+ }
+
+ for( i=0; i<MAX_SUBCHANNELS; i++ )
+ {
+ if ( m_SubChannels[i].state == SUBCHANNEL_TOSEND )
+ {
+ int bit = 1<<i; // flip bit back since data was send yet
+
+ FLIPBIT(m_nOutReliableState, bit);
+
+ m_SubChannels[i].Free();
+ }
+ else if ( m_SubChannels[i].state == SUBCHANNEL_WAITING )
+ {
+ // data is already out, mark channel as dirty
+ m_SubChannels[i].state = SUBCHANNEL_DIRTY;
+ }
+ }
+
+ if ( m_bProcessingMessages )
+ {
+ // ProcessMessages() needs to know we just nuked the receive list from under it or bad things ensue.
+ m_bClearedDuringProcessing = true;
+ }
+
+ Reset();
+}
+
+
+void CNetChan::CompressFragments()
+{
+ // We don't want this to go in the VCR file, because the compressed size can be different. The reason is
+ // that the bf_writes that contributed to this message may have uninitialized bits at the end of the buffer
+ // (for example if it uses only the first couple bits of the last byte in the message). If the
+ // last few bits are different, it can produce a different compressed size.
+ if ( !m_bUseCompression )
+ return;
+
+ if ( VCRGetMode() != VCR_Disabled )
+ return;
+
+ VPROF_BUDGET( "CNetChan::CompressFragments", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+
+ // write fragemnts for both streams
+ for ( int i=0; i<MAX_STREAMS; i++ )
+ {
+ if ( m_WaitingList[i].Count() == 0 )
+ continue;
+
+ // get the first fragments block which is send next
+ dataFragments_t *data = m_WaitingList[i][0];
+
+ // if data is already compressed or too small, skip it
+ if ( data->isCompressed || (int)data->bytes < net_compresspackets_minsize.GetInt() )
+ continue;
+
+ // if we already started sending this block, we can't compress it anymore
+ if ( data->ackedFragments > 0 || data->pendingFragments > 0 )
+ continue;
+
+ //ok, compress it.
+
+ if ( data->buffer )
+ {
+ CFastTimer compressTimer;
+ compressTimer.Start();
+
+ // fragments data is in memory
+ unsigned int compressedSize = COM_GetIdealDestinationCompressionBufferSize_Snappy( data->bytes );
+ char * compressedData = new char[ compressedSize ];
+
+ if ( COM_BufferToBufferCompress_Snappy( compressedData, &compressedSize, data->buffer, data->bytes ) &&
+ ( compressedSize < data->bytes ) )
+ {
+ compressTimer.End();
+ DevMsg("Compressing fragments (%d -> %d bytes): %.2fms\n",
+ data->bytes, compressedSize, compressTimer.GetDuration().GetMillisecondsF() );
+
+ // copy compressed data but dont reallocate memory
+ Q_memcpy( data->buffer, compressedData, compressedSize );
+
+ data->nUncompressedSize = data->bytes;
+ data->bytes = compressedSize;
+ data->numFragments = BYTES2FRAGMENTS(data->bytes);
+ data->isCompressed = true;
+ }
+
+ delete [] compressedData; // free temp buffer
+ }
+ else // it's a file
+ {
+ Assert( data->file != FILESYSTEM_INVALID_HANDLE );
+
+ char compressedfilename[ MAX_OSPATH ];
+ int compressedFileSize = -1;
+ FileHandle_t hZipFile = FILESYSTEM_INVALID_HANDLE;
+
+ // check to see if there is a compressed version of the file
+ Q_snprintf( compressedfilename, sizeof(compressedfilename), "%s.ztmp", data->filename);
+
+ // check the timestamps
+ int compressedFileTime = g_pFileSystem->GetFileTime( compressedfilename );
+ int fileTime = g_pFileSystem->GetFileTime( data->filename );
+
+ if ( compressedFileTime >= fileTime )
+ {
+ // compressed file is newer than uncompressed file, use this one
+ hZipFile = g_pFileSystem->Open( compressedfilename, "rb", NULL );
+ }
+
+ if ( hZipFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ // use the existing compressed file
+ compressedFileSize = g_pFileSystem->Size( hZipFile );
+ }
+ else
+ {
+ // create compressed version of source file
+ unsigned int uncompressedSize = data->bytes;
+ unsigned int compressedSize = COM_GetIdealDestinationCompressionBufferSize_Snappy( uncompressedSize );
+ char *uncompressed = new char[uncompressedSize];
+ char *compressed = new char[compressedSize];
+
+ // read in source file
+ g_pFileSystem->Read( uncompressed, data->bytes, data->file );
+
+ // compress into buffer
+ if ( COM_BufferToBufferCompress_Snappy( compressed, &compressedSize, uncompressed, uncompressedSize ) )
+ {
+ // write out to disk compressed version
+ hZipFile = g_pFileSystem->Open( compressedfilename, "wb", NULL );
+
+ if ( hZipFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ DevMsg("Creating compressed version of file %s (%d -> %d)\n", data->filename, data->bytes, compressedSize);
+ g_pFileSystem->Write( compressed, compressedSize, hZipFile );
+ g_pFileSystem->Close( hZipFile );
+
+ // and open zip file it again for reading
+ hZipFile = g_pFileSystem->Open( compressedfilename, "rb", NULL );
+
+ if ( hZipFile != FILESYSTEM_INVALID_HANDLE )
+ {
+ // ok, now everything if fine
+ compressedFileSize = compressedSize;
+ }
+ }
+ }
+
+ delete [] uncompressed;
+ delete [] compressed;
+ }
+
+ if ( compressedFileSize > 0 )
+ {
+ // use compressed file handle instead of original file
+ g_pFileSystem->Close( data->file );
+ data->file = hZipFile;
+ data->nUncompressedSize = data->bytes;
+ data->bytes = compressedFileSize;
+ data->numFragments = BYTES2FRAGMENTS(data->bytes);
+ data->isCompressed = true;
+ }
+ }
+ }
+}
+
+void CNetChan::UncompressFragments( dataFragments_t *data )
+{
+ if ( !data->isCompressed )
+ return;
+
+ // allocate buffer for uncompressed data, align to 4 bytes boundary
+ char *newbuffer = new char[PAD_NUMBER( data->nUncompressedSize, 4 )];
+ unsigned int uncompressedSize = data->nUncompressedSize;
+
+ // uncompress data
+ COM_BufferToBufferDecompress( newbuffer, &uncompressedSize, data->buffer, data->bytes );
+
+ Assert( uncompressedSize == data->nUncompressedSize );
+
+ // free old buffer and set new buffer
+ delete [] data->buffer;
+ data->buffer = newbuffer;
+ data->bytes = uncompressedSize;
+ data->isCompressed = false;
+}
+
+unsigned int CNetChan::RequestFile(const char *filename)
+{
+ m_FileRequestCounter++;
+
+ if ( net_showfragments.GetInt() == 2 )
+ {
+ DevMsg("RequestFile: %s (ID %i)\n", filename, m_FileRequestCounter );
+ }
+
+ m_StreamReliable.WriteUBitLong( net_File, NETMSG_TYPE_BITS );
+ m_StreamReliable.WriteUBitLong( m_FileRequestCounter, 32 );
+ m_StreamReliable.WriteString( filename );
+ m_StreamReliable.WriteOneBit( 1 ); // reqest this file
+
+ return m_FileRequestCounter;
+}
+
+void CNetChan::RequestFile_OLD(const char *filename, unsigned int transferID)
+{
+ Error( "Called RequestFile_OLD" );
+}
+
+void CNetChan::DenyFile(const char *filename, unsigned int transferID)
+{
+ if ( net_showfragments.GetInt() == 2 )
+ {
+ DevMsg("DenyFile: %s (ID %i)\n", filename, transferID );
+ }
+
+ m_StreamReliable.WriteUBitLong( net_File, NETMSG_TYPE_BITS );
+ m_StreamReliable.WriteUBitLong( transferID, 32 );
+ m_StreamReliable.WriteString( filename );
+ m_StreamReliable.WriteOneBit( 0 ); // deny this file
+}
+
+bool CNetChan::SendFile(const char *filename, unsigned int transferID)
+{
+ // add file to waiting list
+ if ( remote_address.GetType() == NA_NULL )
+ return true;
+
+ if ( !filename )
+ return false;
+
+ const char *sendfile = filename;
+ while( sendfile[0] && PATHSEPARATOR( sendfile[0] ) )
+ {
+ sendfile = sendfile + 1;
+ }
+
+ // Don't transfer exe, vbs, com, bat-type files.
+ if ( !IsValidFileForTransfer( sendfile ) )
+ return false;
+
+ if ( !CreateFragmentsFromFile( sendfile, FRAG_FILE_STREAM, transferID ) )
+ {
+ DenyFile( sendfile, transferID ); // send host a deny message
+ return false;
+ }
+
+ if ( net_showfragments.GetInt() == 2 )
+ {
+ DevMsg("SendFile: %s (ID %i)\n", sendfile, transferID );
+ }
+
+ return true;
+}
+
+void CNetChan::Shutdown(const char *pReason)
+{
+ // send discconect
+
+ if ( m_Socket < 0 )
+ return;
+
+ Clear(); // free all buffers (reliable & unreliable)
+
+ if ( pReason )
+ {
+ // send disconnect message
+ m_StreamUnreliable.WriteUBitLong( net_Disconnect, NETMSG_TYPE_BITS );
+ m_StreamUnreliable.WriteString( pReason );
+ Transmit(); // push message out
+ }
+
+ if ( m_StreamSocket )
+ {
+ NET_CloseSocket( m_StreamSocket, m_Socket );
+ m_StreamSocket = 0;
+ m_StreamActive = false;
+ }
+
+ m_Socket = -1; // signals that netchannel isn't valid anymore
+
+ remote_address.Clear();
+
+ if ( m_MessageHandler )
+ {
+ m_MessageHandler->ConnectionClosing( pReason );
+ m_MessageHandler = NULL;
+ }
+
+ // free new messages
+
+ for (int i=0; i<m_NetMessages.Count(); i++ )
+ {
+ Assert( m_NetMessages[i] );
+ delete m_NetMessages[i];
+ }
+
+ m_NetMessages.Purge();
+
+ m_DemoRecorder = NULL;
+
+ if ( m_bProcessingMessages )
+ {
+ NET_RemoveNetChannel( this, false ); // Delay the deletion or it'll crash in the message-processing loop.
+ m_bShouldDelete = true;
+ }
+ else
+ {
+ NET_RemoveNetChannel( this, true );
+ }
+}
+
+CNetChan::CNetChan()
+{
+ m_nSplitPacketSequence = 1;
+ m_nMaxRoutablePayloadSize = MAX_ROUTABLE_PAYLOAD;
+ m_bProcessingMessages = false;
+ m_bShouldDelete = false;
+ m_bClearedDuringProcessing = false;
+ m_bStreamContainsChallenge = false;
+ m_Socket = -1; // invalid
+ remote_address.Clear();
+ last_received = 0;
+ connect_time = 0;
+ m_nProtocolVersion = -1; // invalid
+
+ Q_strncpy( m_Name, "", sizeof(m_Name) );
+
+ m_MessageHandler = NULL;
+ m_DemoRecorder = NULL;
+
+ m_StreamUnreliable.SetDebugName( "netchan_t::unreliabledata" );
+ m_StreamReliable.SetDebugName( "netchan_t::reliabledata" );
+
+ m_Rate = DEFAULT_RATE;
+ m_Timeout = SIGNON_TIME_OUT;
+
+ // Prevent the first message from getting dropped after connection is set up.
+
+ m_nOutSequenceNr = 1; // otherwise it looks like a
+ m_nInSequenceNr = 0;
+ m_nOutSequenceNrAck = 0;
+ m_nOutReliableState = 0; // our current reliable state
+ m_nInReliableState = 0; // last remote reliable state
+ // m_nLostPackets = 0;
+
+ m_ChallengeNr = 0;
+
+ m_StreamSocket = 0;
+ m_StreamActive = false;
+
+ ResetStreaming();
+
+ m_MaxReliablePayloadSize = NET_MAX_PAYLOAD;
+
+ m_FileRequestCounter = 0;
+ m_bFileBackgroundTranmission = true;
+ m_bUseCompression = false;
+ m_nQueuedPackets = 0;
+
+ m_flRemoteFrameTime = 0;
+ m_flRemoteFrameTimeStdDeviation = 0;
+
+ FlowReset();
+}
+
+CNetChan::~CNetChan()
+{
+ Shutdown("NetChannel removed.");
+}
+
+/*
+==============
+CNetChan::Setup
+
+called to open a channel to a remote system
+==============
+*/
+void CNetChan::Setup(int sock, netadr_t *adr, const char * name, INetChannelHandler * handler,
+ int nProtocolVersion )
+{
+ Assert( name );
+ Assert ( handler );
+
+ m_Socket = sock;
+
+ if ( m_StreamSocket )
+ {
+ NET_CloseSocket( m_StreamSocket );
+ m_StreamSocket = 0;
+ }
+
+ // remote_address may be NULL for fake channels (demo playback etc)
+ if ( adr )
+ {
+ remote_address = *adr;
+ }
+ else
+ {
+ remote_address.Clear(); // it's a demo fake channel
+ remote_address.SetType( NA_NULL );
+ }
+
+ last_received = net_time;
+ connect_time = net_time;
+
+ Q_strncpy( m_Name, name, sizeof(m_Name) );
+
+ m_MessageHandler = handler;
+ m_nProtocolVersion = nProtocolVersion;
+
+ m_DemoRecorder = NULL;
+
+ MEM_ALLOC_CREDIT();
+
+ SetMaxBufferSize( false, NET_MAX_DATAGRAM_PAYLOAD );
+
+ SetMaxBufferSize( false, NET_MAX_DATAGRAM_PAYLOAD, true ); //Set up voice buffer
+
+ SetMaxBufferSize( true, NET_MAX_PAYLOAD );
+
+ m_Rate = DEFAULT_RATE;
+ m_Timeout = SIGNON_TIME_OUT;
+
+ // Prevent the first message from getting dropped after connection is set up.
+
+ m_nOutSequenceNr = 1; // otherwise it looks like a
+ m_nInSequenceNr = 0;
+ m_nOutSequenceNrAck = 0;
+ m_nOutReliableState = 0; // our current reliable state
+ m_nInReliableState = 0; // last remote reliable state
+ m_nChokedPackets = 0;
+ m_fClearTime = 0.0;
+
+ m_ChallengeNr = 0;
+
+ m_StreamSocket = 0;
+ m_StreamActive = false;
+
+ m_ReceiveList[FRAG_NORMAL_STREAM].buffer = NULL;
+ m_ReceiveList[FRAG_FILE_STREAM].buffer = NULL;
+
+ // init 8 subchannels
+ for ( int i=0; i<MAX_SUBCHANNELS; i++ )
+ {
+ m_SubChannels[i].index = i; // set index once
+ m_SubChannels[i].Free();
+ }
+
+ ResetStreaming();
+
+ if ( NET_IsMultiplayer() )
+ {
+ m_MaxReliablePayloadSize = net_blocksize.GetInt();
+ }
+ else
+ {
+ m_MaxReliablePayloadSize = NET_MAX_PAYLOAD;
+ }
+
+ FlowReset();
+
+ // tell message handler to register known netmessages
+ m_MessageHandler->ConnectionStart( this );
+}
+
+void CNetChan::ResetStreaming( void )
+{
+ m_SteamType = STREAM_CMD_NONE;
+ m_StreamLength = 0;
+ m_StreamReceived = 0;
+ m_StreamSeqNr = 0;
+ m_SteamFile[0] = 0;
+}
+
+bool CNetChan::StartStreaming( unsigned int challengeNr )
+{
+ // reset stream state maschine
+ ResetStreaming();
+
+ m_ChallengeNr = challengeNr;
+
+ if ( !NET_IsMultiplayer() )
+ {
+ m_StreamSocket = 0;
+ return true; // streaming is done via loopback buffers in SP mode
+ }
+
+#ifdef _XBOX
+ // We don't want to go into here because it'll eat up 192k extra memory in the client and server's m_StreamData.
+ Error( "StartStreaming not allowed on XBOX." );
+#endif
+
+ MEM_ALLOC_CREDIT();
+
+ m_StreamSocket = NET_ConnectSocket( m_Socket, remote_address );
+ m_StreamData.EnsureCapacity( NET_MAX_PAYLOAD );
+
+ return (m_StreamSocket != 0);
+}
+
+void CNetChan::SetChallengeNr(unsigned int chnr)
+{
+ m_ChallengeNr = chnr;
+}
+
+unsigned int CNetChan::GetChallengeNr( void ) const
+{
+ return m_ChallengeNr;
+}
+
+void CNetChan::GetSequenceData( int &nOutSequenceNr, int &nInSequenceNr, int &nOutSequenceNrAck )
+{
+ nOutSequenceNr = m_nOutSequenceNr;
+ nInSequenceNr = m_nInSequenceNr;
+ nOutSequenceNrAck = m_nOutSequenceNrAck;
+}
+
+void CNetChan::SetSequenceData( int nOutSequenceNr, int nInSequenceNr, int nOutSequenceNrAck )
+{
+ Assert( IsPlayback() );
+
+ m_nOutSequenceNr = nOutSequenceNr;
+ m_nInSequenceNr = nInSequenceNr;
+ m_nOutSequenceNrAck = nOutSequenceNrAck;
+}
+
+void CNetChan::SetDemoRecorder(IDemoRecorder * recorder)
+{
+ m_DemoRecorder = recorder;
+}
+
+void CNetChan::SetTimeout(float seconds)
+{
+ m_Timeout = seconds;
+
+ if ( m_Timeout > 3600.0f )
+ {
+ m_Timeout = 3600.0f; // 1 hour maximum
+ }
+ else if ( m_Timeout <= 0.0f )
+ {
+ m_Timeout = -1.0f; // never time out (demo files)
+ }
+ else if ( m_Timeout < CONNECTION_PROBLEM_TIME )
+ {
+ m_Timeout = CONNECTION_PROBLEM_TIME; // allow at least this minimum
+ }
+}
+
+void CNetChan::SetMaxBufferSize(bool bReliable, int nBytes, bool bVoice )
+{
+ // force min/max sizes 4-96kB
+ nBytes = clamp( nBytes, NET_MAX_DATAGRAM_PAYLOAD, NET_MAX_PAYLOAD );
+
+ bf_write *stream;
+ CUtlMemory<byte> *buffer;
+
+ if ( bReliable )
+ {
+ stream = &m_StreamReliable;
+ buffer = &m_ReliableDataBuffer;
+ }
+ else if ( bVoice == true )
+ {
+ stream = &m_StreamVoice;
+ buffer = &m_VoiceDataBuffer;
+ }
+ else
+ {
+ stream = &m_StreamUnreliable;
+ buffer = &m_UnreliableDataBuffer;
+ }
+
+ if ( buffer->Count() == nBytes )
+ return;
+
+ byte copybuf[NET_MAX_DATAGRAM_PAYLOAD];
+ int copybits = stream->GetNumBitsWritten();
+ int copybytes = Bits2Bytes( copybits );
+
+ if ( copybytes >= nBytes )
+ {
+ ConMsg("CNetChan::SetMaxBufferSize: cant preserve exiting data %i>%i.\n", copybytes, nBytes );
+ return;
+ }
+
+ if ( copybits > 0 )
+ {
+ Q_memcpy( copybuf, buffer->Base(), copybytes );
+ }
+
+ buffer->Purge();
+
+ MEM_ALLOC_CREDIT();
+ buffer->EnsureCapacity( nBytes );
+
+ if ( copybits > 0 )
+ {
+ Q_memcpy( buffer->Base(), copybuf, copybytes );
+ }
+
+ stream->StartWriting( buffer->Base(), nBytes, copybits );
+
+}
+
+void CNetChan::SetFileTransmissionMode( bool bBackgroundMode )
+{
+ m_bFileBackgroundTranmission = bBackgroundMode;
+}
+
+void CNetChan::SetCompressionMode( bool bUseCompression )
+{
+ m_bUseCompression = bUseCompression;
+}
+
+void CNetChan::SetDataRate(float rate)
+{
+ m_Rate = clamp( rate, (float) MIN_RATE, (float) MAX_RATE );
+}
+
+const char * CNetChan::GetName() const
+{
+ return m_Name;
+}
+
+const char * CNetChan::GetAddress() const
+{
+ return remote_address.ToString();
+}
+
+
+int CNetChan::GetDropNumber() const
+{
+ return m_PacketDrop;
+}
+
+/*
+===============
+CNetChan::CanPacket
+
+Returns true if the bandwidth choke isn't active
+================
+*/
+bool CNetChan::CanPacket () const
+{
+ // Never choke loopback packets.
+ if ( !net_chokeloopback.GetInt() && remote_address.IsLoopback() )
+ {
+ return true;
+ }
+
+ if ( HasQueuedPackets() )
+ {
+ return false;
+ }
+
+ return m_fClearTime < net_time;
+}
+
+bool CNetChan::IsPlayback( void ) const
+{
+#if !defined(SWDS) && !defined(_XBOX)
+ return demoplayer->IsPlayingBack();
+#else
+ return false;
+#endif
+}
+
+void CNetChan::FlowReset( void )
+{
+ Q_memset( m_DataFlow, 0, sizeof( m_DataFlow ) );
+ Q_memset( m_MsgStats, 0, sizeof( m_MsgStats ) );
+}
+
+void CNetChan::FlowNewPacket(int flow, int seqnr, int acknr, int nChoked, int nDropped, int nSize )
+{
+ netflow_t * pflow = &m_DataFlow[ flow ];
+
+ // if frame_number != ( current + 1 ) mark frames between as invalid
+
+ netframe_t *pframe = NULL;
+
+ if ( seqnr > pflow->currentindex )
+ {
+ //
+ // The following loop must execute no more than NET_FRAMES_BACKUP times
+ // since that's the amount of storage in frame_headers & frames arrays,
+ // a malformed client packet pushing "seqnr" by 1,000,000 can cause this
+ // loop to watchdog.
+ //
+ for ( int i = pflow->currentindex + 1, numPacketFramesOverflow = 0;
+ ( i <= seqnr ) && ( numPacketFramesOverflow < NET_FRAMES_BACKUP );
+ ++ i, ++ numPacketFramesOverflow )
+ {
+ int nBackTrack = seqnr - i;
+
+ pframe = &pflow->frames[ i & NET_FRAMES_MASK ];
+
+ pframe->time = net_time; // now
+ pframe->valid = false;
+ pframe->size = 0;
+ pframe->latency = -1.0f; // not acknowledged yet
+ pframe->avg_latency = GetAvgLatency( FLOW_OUTGOING );
+ pframe->choked = 0; // not acknowledged yet
+ pframe->dropped = 0;
+ pframe->m_flInterpolationAmount = 0.0f;
+ Q_memset( &pframe->msggroups, 0, sizeof(pframe->msggroups) );
+
+ if ( nBackTrack < ( nChoked + nDropped ) )
+ {
+ if ( nBackTrack < nChoked )
+ {
+ pframe->choked = 1;
+ }
+ else
+ {
+ pframe->dropped = 1;
+ }
+ }
+ }
+
+ pframe->dropped = nDropped;
+ pframe->choked = nChoked;
+ pframe->size = nSize;
+ pframe->valid = true;
+ pframe->avg_latency = GetAvgLatency( FLOW_OUTGOING );
+ pframe->m_flInterpolationAmount = m_flInterpolationAmount;
+ }
+ else
+ {
+#if !defined( SWDS )
+ Assert( demoplayer->IsPlayingBack() || seqnr > pflow->currentindex );
+#endif
+ }
+
+ pflow->totalpackets++;
+ pflow->currentindex = seqnr;
+ pflow->currentframe = pframe;
+
+ // updated ping for acknowledged packet
+
+ int aflow = (flow==FLOW_OUTGOING) ? FLOW_INCOMING : FLOW_OUTGOING;
+
+ if ( acknr <= (m_DataFlow[aflow].currentindex - NET_FRAMES_BACKUP) )
+ return; // acknowledged packet isn't in backup buffer anymore
+
+ netframe_t * aframe = &m_DataFlow[aflow].frames[ acknr & NET_FRAMES_MASK ];
+
+ if ( aframe->valid && aframe->latency == -1.0f )
+ {
+ // update ping for acknowledged packet, if not already acknowledged before
+
+ aframe->latency = net_time - aframe->time;
+
+ if ( aframe->latency < 0.0f )
+ aframe->latency = 0.0f;
+ }
+
+}
+
+void CNetChan::FlowUpdate(int flow, int addbytes)
+{
+ netflow_t * pflow = &m_DataFlow[ flow ];
+ pflow->totalbytes += addbytes;
+
+ if ( pflow->nextcompute > net_time )
+ return;
+
+ pflow->nextcompute = net_time + FLOW_INTERVAL;
+
+ int totalvalid = 0;
+ int totalinvalid = 0;
+ int totalbytes = 0;
+ float totallatency = 0.0f;
+ int totallatencycount = 0;
+ int totalchoked = 0;
+
+ float starttime = FLT_MAX;
+ float endtime = 0.0f;
+
+ netframe_t *pprev = &pflow->frames[ NET_FRAMES_BACKUP-1 ];
+
+ for ( int i = 0; i < NET_FRAMES_BACKUP; i++ )
+ {
+ // Most recent message then backward from there
+ netframe_t * pcurr = &pflow->frames[ i ];
+
+ if ( pcurr->valid )
+ {
+ if ( pcurr->time < starttime )
+ starttime = pcurr->time;
+
+ if ( pcurr->time > endtime )
+ endtime = pcurr->time;
+
+ totalvalid++;
+ totalchoked += pcurr->choked;
+ totalbytes += pcurr->size;
+
+ if ( pcurr->latency > -1.0f )
+ {
+ totallatency += pcurr->latency;
+ totallatencycount++;
+ }
+ }
+ else
+ {
+ totalinvalid++;
+ }
+
+ pprev = pcurr;
+ }
+
+ float totaltime = endtime - starttime;
+
+ if ( totaltime > 0.0f )
+ {
+ pflow->avgbytespersec *= FLOW_AVG;
+ pflow->avgbytespersec += ( 1.0f - FLOW_AVG ) * ((float)totalbytes / totaltime);
+
+ pflow->avgpacketspersec *= FLOW_AVG;
+ pflow->avgpacketspersec += ( 1.0f - FLOW_AVG ) * ((float)totalvalid / totaltime);
+ }
+
+ int totalPackets = totalvalid + totalinvalid;
+
+ if ( totalPackets > 0 )
+ {
+ pflow->avgloss *= FLOW_AVG;
+ pflow->avgloss += ( 1.0f - FLOW_AVG ) * ((float)(totalinvalid-totalchoked)/totalPackets);
+
+ if ( pflow->avgloss < 0 )
+ pflow->avgloss = 0;
+
+ pflow->avgchoke *= FLOW_AVG;
+ pflow->avgchoke += ( 1.0f - FLOW_AVG ) * ((float)totalchoked/totalPackets);
+ }
+
+ if ( totallatencycount>0 )
+ {
+ float newping = totallatency / totallatencycount ;
+ pflow->latency = newping;
+ pflow->avglatency*= FLOW_AVG;
+ pflow->avglatency += ( 1.0f - FLOW_AVG ) * newping;
+ }
+}
+
+void CNetChan::SetChoked( void )
+{
+ m_nOutSequenceNr++; // sends to be done since move command use sequence number
+ m_nChokedPackets++;
+}
+
+bool CNetChan::Transmit(bool onlyReliable )
+{
+ if ( onlyReliable )
+ m_StreamUnreliable.Reset();
+
+ return (SendDatagram( NULL ) != 0);
+}
+
+bool CNetChan::IsFileInWaitingList( const char *filename )
+{
+ if ( !filename || !filename[0] )
+ return true;
+
+ for ( int stream=0; stream<MAX_STREAMS; stream++)
+ {
+ for ( int i = 0; i < m_WaitingList[stream].Count(); i++ )
+ {
+ dataFragments_t * data = m_WaitingList[stream][i];
+
+ if ( !Q_strcmp( data->filename, filename ) )
+ return true; // alread in list
+ }
+ }
+
+ return false; // file not found
+}
+
+void CNetChan::RemoveHeadInWaitingList( int nList )
+{
+ Assert( m_WaitingList[nList].Count() );
+
+ dataFragments_t * data = m_WaitingList[nList][0]; // get head
+
+ if ( data->buffer )
+ delete [] data->buffer; // free data buffer
+
+ if ( data->file != FILESYSTEM_INVALID_HANDLE )
+ {
+ g_pFileSystem->Close( data->file );
+ data->file = FILESYSTEM_INVALID_HANDLE;
+ }
+
+ // data->fragments.Purge();
+
+ m_WaitingList[nList].FindAndRemove( data ); // remove from list
+
+ delete data; //free structure itself
+}
+
+
+bool CNetChan::CreateFragmentsFromBuffer( bf_write *buffer, int stream )
+{
+ VPROF_BUDGET( "CNetChan::CreateFragmentsFromBuffer", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+
+ bf_write bfwrite;
+ dataFragments_t *data = NULL;
+
+ // if we have more than one item in the waiting list, try to add the
+ // reliable data to the last item. that doesn't work with the first item
+ // since it may have been already send and is waiting for acknowledge
+
+ int count = m_WaitingList[stream].Count();
+
+ if ( count > 1 )
+ {
+ // get last item in waiting list
+ data = m_WaitingList[stream][count-1];
+
+ int totalBytes = Bits2Bytes( data->bits + buffer->GetNumBitsWritten() );
+
+ totalBytes = PAD_NUMBER( totalBytes, 4 ); // align to 4 bytes boundary
+
+ if ( totalBytes < NET_MAX_PAYLOAD && data->buffer )
+ {
+ // we have enough space for it, create new larger mem buffer
+ char *newBuf = new char[totalBytes];
+
+ Q_memcpy( newBuf, data->buffer, data->bytes );
+
+ delete [] data->buffer; // free old buffer
+
+ data->buffer = newBuf; // set new buffer
+
+ bfwrite.StartWriting( newBuf, totalBytes, data->bits );
+ }
+ else
+ {
+ data = NULL; // reset to NULL
+ }
+ }
+
+ // if not added to existing item, create a new reliable data waiting buffer
+ if ( !data )
+ {
+ int totalBytes = Bits2Bytes( buffer->GetNumBitsWritten());
+
+ totalBytes = PAD_NUMBER( totalBytes, 4 ); // align to 4 bytes boundary
+
+ data = new dataFragments_t;
+ data->bytes = 0; // not filled yet
+ data->bits = 0;
+ data->buffer = new char[ totalBytes ];
+ data->isCompressed = false;
+ data->nUncompressedSize = 0;
+ data->file = FILESYSTEM_INVALID_HANDLE;
+ data->filename[0] = 0;
+
+ bfwrite.StartWriting( data->buffer, totalBytes );
+
+ m_WaitingList[stream].AddToTail( data ); // that's it for now
+ }
+
+ // write new reliable data to buffer
+ bfwrite.WriteBits( buffer->GetData(), buffer->GetNumBitsWritten() );
+
+ // fill last bits in last byte with NOP if necessary
+ int nRemainingBits = bfwrite.GetNumBitsWritten() % 8;
+ if ( nRemainingBits > 0 && nRemainingBits <= (8-NETMSG_TYPE_BITS) )
+ {
+ bfwrite.WriteUBitLong( net_NOP, NETMSG_TYPE_BITS );
+ }
+
+ // update bit length
+ data->bits += buffer->GetNumBitsWritten();
+ data->bytes = Bits2Bytes(data->bits);
+
+ // check if send as stream or with snapshot
+ data->asTCP = m_StreamActive && ( data->bytes > m_MaxReliablePayloadSize );
+
+ // calc number of fragments needed
+ data->numFragments = BYTES2FRAGMENTS(data->bytes);
+ data->ackedFragments = 0;
+ data->pendingFragments = 0;
+
+ return true;
+}
+
+bool CNetChan::CreateFragmentsFromFile( const char *filename, int stream, unsigned int transferID )
+{
+ if ( IsFileInWaitingList( filename ) )
+ return true; // already scheduled for upload
+
+ const char *pPathID = "GAME";
+
+ if ( !g_pFileSystem->FileExists( filename, pPathID ) )
+ {
+ ConMsg( "CreateFragmentsFromFile: '%s' doesn't exist.\n", filename );
+ return false;
+ }
+
+ int totalBytes = g_pFileSystem->Size( filename, pPathID );
+
+ if ( totalBytes >= (net_maxfilesize.GetInt()*1024*1024) )
+ {
+ ConMsg( "CreateFragmentsFromFile: '%s' size exceeds net_maxfilesize limit (%i MB).\n", filename, net_maxfilesize.GetInt() );
+ return false;
+ }
+
+ if ( totalBytes >= MAX_FILE_SIZE )
+ {
+ ConMsg( "CreateFragmentsFromFile: '%s' too big (max %i bytes).\n", filename, MAX_FILE_SIZE );
+ return false;
+ }
+
+ dataFragments_t *data = new dataFragments_t;
+ data->bytes = totalBytes;
+ data->bits = data->bytes * 8;
+ data->buffer = NULL;
+ data->isCompressed = false;
+ data->nUncompressedSize = 0;
+ data->file = g_pFileSystem->Open( filename, "rb", pPathID );
+
+ if ( data->file == FILESYSTEM_INVALID_HANDLE )
+ {
+ ConMsg( "CreateFragmentsFromFile: couldn't open '%s'.\n", filename );
+ delete data;
+ return false;
+ }
+
+ data->transferID = transferID;
+ Q_strncpy( data->filename, filename, sizeof(data->filename) );
+
+ m_WaitingList[stream].AddToTail( data ); // that's it for now
+
+ // check if send as stream or with snapshot
+ data->asTCP = false; // m_StreamActive && ( Bits2Bytes(data->length) > m_MaxReliablePayloadSize );
+
+ // calc number of fragments needed
+ data->numFragments = BYTES2FRAGMENTS(data->bytes);
+ data->ackedFragments = 0;
+ data->pendingFragments = 0;
+
+ return true;
+}
+
+void CNetChan::SendTCPData( void )
+{
+ if ( m_WaitingList[FRAG_NORMAL_STREAM].Count() == 0 )
+ return; // nothing in line
+
+ dataFragments_t *data = m_WaitingList[FRAG_NORMAL_STREAM][0];
+
+ if ( !data->asTCP )
+ return; // not as TCP
+
+ if ( data->pendingFragments > 0 )
+ return; // already send, wait for ACK
+
+ // OK send it now
+ SendReliableViaStream( data );
+}
+
+bool CNetChan::SendSubChannelData( bf_write &buf )
+{
+ VPROF_BUDGET( "CNetChan::SendSubChannelData", VPROF_BUDGETGROUP_OTHER_NETWORKING );
+
+ subChannel_s *subChan = NULL;
+ int i;
+
+ CompressFragments();
+
+ SendTCPData();
+
+ UpdateSubChannels();
+
+ // find subchannl with data to send/resend:
+ for ( i=0; i<MAX_SUBCHANNELS; i++ )
+ {
+ subChan = &m_SubChannels[i];
+
+ if ( subChan->state == SUBCHANNEL_TOSEND )
+ break;
+ }
+
+ if ( i == MAX_SUBCHANNELS )
+ return false; // no data to send in any subchannel
+
+ // first write subchannel index
+ buf.WriteUBitLong( i, 3 );
+
+ // write fragemnts for both streams
+ for ( i=0; i<MAX_STREAMS; i++ )
+ {
+ if ( subChan->numFragments[i] == 0 )
+ {
+ buf.WriteOneBit( 0 ); // no data for this stream
+ continue;
+ }
+
+ dataFragments_t *data = m_WaitingList[i][0];
+
+ buf.WriteOneBit( 1 ); // data follows:
+
+ unsigned int offset = subChan->startFraggment[i]*FRAGMENT_SIZE;
+ unsigned int length = subChan->numFragments[i]*FRAGMENT_SIZE;
+
+ if ( (subChan->startFraggment[i]+subChan->numFragments[i]) == data->numFragments )
+ {
+ // we are sending the last fragment, adjust length
+ int rest = FRAGMENT_SIZE - ( data->bytes % FRAGMENT_SIZE );
+ if ( rest < FRAGMENT_SIZE )
+ length -= rest;
+ }
+
+ // if all fragments can be send within a single packet, avoid overhead (if not a file)
+ bool bSingleBlock = (subChan->numFragments[i] == data->numFragments) &&
+ ( data->file == FILESYSTEM_INVALID_HANDLE );
+
+ if ( bSingleBlock )
+ {
+ Assert( length == data->bytes );
+ Assert( length < NET_MAX_PAYLOAD );
+ Assert( offset == 0 );
+
+ buf.WriteOneBit( 0 ); // single block bit
+
+ // data compressed ?
+ if ( data->isCompressed )
+ {
+ buf.WriteOneBit( 1 );
+ buf.WriteUBitLong( data->nUncompressedSize, MAX_FILE_SIZE_BITS );
+ }
+ else
+ {
+ buf.WriteOneBit( 0 );
+ }
+
+ buf.WriteVarInt32( data->bytes );
+ }
+ else
+ {
+ buf.WriteOneBit( 1 ); // uses fragments with start fragment offset byte
+ buf.WriteUBitLong( subChan->startFraggment[i], (MAX_FILE_SIZE_BITS-FRAGMENT_BITS) );
+ buf.WriteUBitLong( subChan->numFragments[i], 3 );
+
+ if ( offset == 0 )
+ {
+ // this is the first fragment, write header info
+
+ if ( data->file != FILESYSTEM_INVALID_HANDLE )
+ {
+ buf.WriteOneBit( 1 ); // file transmission net message stream
+ buf.WriteUBitLong( data->transferID, 32 );
+ buf.WriteString( data->filename );
+ }
+ else
+ {
+ buf.WriteOneBit( 0 ); // normal net message stream
+ }
+
+ // data compressed ?
+ if ( data->isCompressed )
+ {
+ buf.WriteOneBit( 1 );
+ buf.WriteUBitLong( data->nUncompressedSize, MAX_FILE_SIZE_BITS );
+ }
+ else
+ {
+ buf.WriteOneBit( 0 );
+ }
+
+ buf.WriteUBitLong( data->bytes, MAX_FILE_SIZE_BITS ); // 4MB max for files
+ }
+ }
+
+ // write fragments to buffer
+ if ( data->buffer )
+ {
+ Assert( data->file == FILESYSTEM_INVALID_HANDLE );
+ // send from memory block
+ buf.WriteBytes( data->buffer+offset, length );
+ }
+ else // if ( data->file != FILESYSTEM_INVALID_HANDLE )
+ {
+ // send from file
+ Assert( data->file != FILESYSTEM_INVALID_HANDLE );
+ char * tmpbuf = (char*)_alloca( length ); // alloc on stack
+ g_pFileSystem->Seek( data->file, offset, FILESYSTEM_SEEK_HEAD );
+ g_pFileSystem->Read( tmpbuf, length, data->file );
+ buf.WriteBytes( tmpbuf, length );
+ }
+
+ if ( net_showfragments.GetBool() )
+ {
+ ConMsg("Sending subchan %i: start %i, num %i\n", subChan->index, subChan->startFraggment[i], subChan->numFragments[i] );
+ }
+
+ subChan->sendSeqNr = m_nOutSequenceNr;
+ subChan->state = SUBCHANNEL_WAITING;
+ }
+
+ return true;
+}
+
+
+bool CNetChan::ReadSubChannelData( bf_read &buf, int stream )
+{
+ dataFragments_t * data = &m_ReceiveList[stream]; // get list
+ int startFragment = 0;
+ int numFragments = 0;
+ unsigned int offset = 0;
+ unsigned int length = 0;
+
+ bool bSingleBlock = buf.ReadOneBit() == 0; // is single block ?
+
+ if ( !bSingleBlock )
+ {
+ startFragment = buf.ReadUBitLong( MAX_FILE_SIZE_BITS-FRAGMENT_BITS ); // 16 MB max
+ numFragments = buf.ReadUBitLong( 3 ); // 8 fragments per packet max
+ offset = startFragment * FRAGMENT_SIZE;
+ length = numFragments * FRAGMENT_SIZE;
+ }
+
+ if ( offset == 0 ) // first fragment, read header info
+ {
+ data->filename[0] = 0;
+ data->isCompressed = false;
+ data->transferID = 0;
+
+ if ( bSingleBlock )
+ {
+ // data compressed ?
+ if ( buf.ReadOneBit() )
+ {
+ data->isCompressed = true;
+ data->nUncompressedSize = buf.ReadUBitLong( MAX_FILE_SIZE_BITS );
+ }
+ else
+ {
+ data->isCompressed = false;
+ }
+
+ data->bytes = buf.ReadVarInt32();
+ }
+ else
+ {
+
+ if ( buf.ReadOneBit() ) // is it a file ?
+ {
+ data->transferID = buf.ReadUBitLong( 32 );
+ buf.ReadString( data->filename, MAX_OSPATH );
+ }
+
+ // data compressed ?
+ if ( buf.ReadOneBit() )
+ {
+ data->isCompressed = true;
+ data->nUncompressedSize = buf.ReadUBitLong( MAX_FILE_SIZE_BITS );
+ }
+ else
+ {
+ data->isCompressed = false;
+ }
+
+ data->bytes = buf.ReadUBitLong( MAX_FILE_SIZE_BITS );
+ }
+
+ if ( data->buffer )
+ {
+ // last transmission was aborted, free data
+ delete [] data->buffer;
+ data->buffer = NULL;
+ ConDMsg( "Fragment transmission aborted at %i/%i from %s.\n", data->ackedFragments, data->numFragments, GetAddress() );
+ }
+
+ data->bits = data->bytes * 8;
+ data->asTCP = false;
+ data->numFragments = BYTES2FRAGMENTS(data->bytes);
+ data->ackedFragments = 0;
+ data->file = FILESYSTEM_INVALID_HANDLE;
+
+ if ( bSingleBlock )
+ {
+ numFragments = data->numFragments;
+ length = numFragments * FRAGMENT_SIZE;
+ }
+
+ if ( data->bytes > MAX_FILE_SIZE )
+ {
+ // This can happen with the compressed path above, which uses VarInt32 rather than MAX_FILE_SIZE_BITS
+ Warning( "Net message exceeds max size (%u / %u)\n", MAX_FILE_SIZE, data->bytes );
+ // Subsequent packets for this transfer will treated as invalid since we never setup a buffer.
+ return false;
+ }
+
+ data->buffer = new char[PAD_NUMBER(data->bytes, 4)];
+ }
+ else
+ {
+ if ( data->buffer == NULL )
+ {
+ // This can occur if the packet containing the "header" (offset == 0) is dropped. Since we need the header to arrive we'll just wait
+ // for a retry
+ // ConDMsg("Received fragment out of order: %i/%i\n", startFragment, numFragments );
+ return false;
+ }
+ }
+
+ if ( (startFragment+numFragments) == data->numFragments )
+ {
+ // we are receiving the last fragment, adjust length
+ int rest = FRAGMENT_SIZE - ( data->bytes % FRAGMENT_SIZE );
+ if ( rest < FRAGMENT_SIZE )
+ length -= rest;
+ }
+ else if ( ( startFragment + numFragments ) > data->numFragments )
+ {
+ // a malicious client can send a fragment beyond what was arranged in fragment#0 header
+ // old code will overrun the allocated buffer and likely cause a server crash
+ // it could also cause a client memory overrun because the offset can be anywhere from 0 to 16MB range
+ // drop the packet and wait for client to retry
+ ConDMsg( "Received fragment chunk out of bounds: %i+%i>%i from %s\n", startFragment, numFragments, data->numFragments, GetAddress() );
+ return false;
+ }
+
+ Assert ( (offset + length) <= data->bytes );
+ if ( length == 0 || ( offset + length > data->bytes ) )
+ {
+ delete[] data->buffer;
+ data->buffer = NULL;
+ ConMsg("Malformed fragment ofs %i len %d, buffer size %d from %s\n", offset, length, PAD_NUMBER(data->bytes, 4), remote_address.ToString() );
+ return false;
+ }
+
+ buf.ReadBytes( data->buffer + offset, length ); // read data
+
+ data->ackedFragments+= numFragments;
+
+ if ( net_showfragments.GetBool() )
+ ConMsg("Received fragments: start %i, num %i\n", startFragment, numFragments );
+
+ return true;
+}
+
+void CNetChan::UpdateSubChannels()
+{
+ // first check if there is a free subchannel
+ subChannel_s * freeSubChan = GetFreeSubChannel();
+
+ if ( freeSubChan == NULL )
+ return; //all subchannels in use right now
+
+ int i, nSendMaxFragments = m_MaxReliablePayloadSize / FRAGMENT_SIZE;
+
+ bool bSendData = false;
+
+ for ( i = 0; i < MAX_STREAMS; i++ )
+ {
+ if ( m_WaitingList[i].Count() <= 0 )
+ continue;
+
+ dataFragments_s *data = m_WaitingList[i][0]; // get head
+
+ if ( data->asTCP )
+ continue;
+
+ int nSentFragments = data->ackedFragments + data->pendingFragments;
+
+ Assert( nSentFragments <= data->numFragments );
+
+ if ( nSentFragments == data->numFragments )
+ continue; // all fragments already send
+
+ // how many fragments can we send ?
+
+ int numFragments = min( nSendMaxFragments, data->numFragments - nSentFragments );
+
+ // if we are in file background transmission mode, just send one fragment per packet
+ if ( i == FRAG_FILE_STREAM && m_bFileBackgroundTranmission )
+ numFragments = min( 1, numFragments );
+
+ // copy fragment data into subchannel
+
+ freeSubChan->startFraggment[i] = nSentFragments;
+ freeSubChan->numFragments[i] = numFragments;
+
+ data->pendingFragments += numFragments;
+
+ bSendData = true;
+
+ nSendMaxFragments -= numFragments;
+
+ if ( nSendMaxFragments <= 0 )
+ break;
+ }
+
+ if ( bSendData )
+ {
+ // flip channel bit
+ int bit = 1<<freeSubChan->index;
+
+ FLIPBIT(m_nOutReliableState, bit);
+
+ freeSubChan->state = SUBCHANNEL_TOSEND;
+ freeSubChan->sendSeqNr = 0;
+ }
+}
+
+#if 1
+
+inline unsigned short BufferToShortChecksum( const void *pvData, size_t nLength )
+{
+ CRC32_t crc = CRC32_ProcessSingleBuffer( pvData, nLength );
+
+ unsigned short lowpart = ( crc & 0xffff );
+ unsigned short highpart = ( ( crc >> 16 ) & 0xffff );
+
+ return (unsigned short)( lowpart ^ highpart );
+}
+
+#else
+
+// If the CRC version ever is deemed to expensive, here's a quick xor version.
+// It's probably not super robust.
+inline unsigned short BufferToShortChecksum( const void *pvData, size_t nSize )
+{
+ const uint32 *pData = (const uint32 *)pvData;
+
+ unsigned short us = 0;
+ while ( nSize >= sizeof( uint32 ) )
+ {
+ us ^= ( *pData & 0xffff );
+ us ^= ( ( *pData >> 16 ) & 0xffff );
+
+ nSize -= sizeof( uint32 );
+ pData += sizeof( uint32 );
+ }
+
+ const byte *pbData = (const byte *)pData;
+
+ while ( nSize > 0 )
+ {
+ us ^= *pbData;
+ ++pbData;
+ --nSize;
+ }
+
+ return us;
+}
+
+#endif
+
+// #define MIN_ROUTABLE_TESTING
+
+#if defined( _DEBUG ) || defined( MIN_ROUTABLE_TESTING )
+static ConVar net_minroutable( "net_minroutable", "16", 0, "Forces larger payloads." );
+#endif
+
+/*
+===============
+CNetChan::TransmitBits
+
+tries to send an unreliable message to a connection, and handles the
+transmition / retransmition of the reliable messages.
+
+A 0 length will still generate a packet and deal with the reliable messages.
+================
+*/
+int CNetChan::SendDatagram(bf_write *datagram)
+{
+ ALIGN4 byte send_buf[ NET_MAX_MESSAGE ] ALIGN4_POST;
+
+#ifndef NO_VCR
+ if ( vcr_verbose.GetInt() && datagram && datagram->GetNumBytesWritten() > 0 )
+ VCRGenericValueVerify( "datagram", datagram->GetBasePointer(), datagram->GetNumBytesWritten()-1 );
+#endif
+
+ // Make sure for the client that the max routable payload size is up to date
+ if ( m_Socket == NS_CLIENT )
+ {
+ if ( net_maxroutable.GetInt() != GetMaxRoutablePayloadSize() )
+ {
+ SetMaxRoutablePayloadSize( net_maxroutable.GetInt() );
+ }
+ }
+
+ // first increase out sequence number
+
+ // check, if fake client, then fake send also
+ if ( remote_address.GetType() == NA_NULL )
+ {
+ // this is a demo channel, fake sending all data
+ m_fClearTime = 0.0; // no bandwidth delay
+ m_nChokedPackets = 0; // Reset choke state
+ m_StreamReliable.Reset(); // clear current reliable buffer
+ m_StreamUnreliable.Reset(); // clear current unrelaible buffer
+ m_nOutSequenceNr++;
+ return m_nOutSequenceNr-1;
+ }
+
+ // process all new and pending reliable data, return true if reliable data should
+ // been send with this packet
+
+ if ( m_StreamReliable.IsOverflowed() )
+ {
+ ConMsg ("%s:send reliable stream overflow\n" ,remote_address.ToString());
+ return 0;
+ }
+ else if ( m_StreamReliable.GetNumBitsWritten() > 0 )
+ {
+ CreateFragmentsFromBuffer( &m_StreamReliable, FRAG_NORMAL_STREAM );
+ m_StreamReliable.Reset();
+ }
+
+ bf_write send( "CNetChan_TransmitBits->send", send_buf, sizeof(send_buf) );
+
+ // Prepare the packet header
+ // build packet flags
+ unsigned char flags = 0;
+
+ // start writing packet
+
+ send.WriteLong ( m_nOutSequenceNr );
+ send.WriteLong ( m_nInSequenceNr );
+
+ bf_write flagsPos = send; // remember flags byte position
+
+ send.WriteByte ( 0 ); // write correct flags value later
+ if ( ShouldChecksumPackets() )
+ {
+ send.WriteShort( 0 ); // write correct checksum later
+ Assert( !(send.GetNumBitsWritten() % 8 ) );
+ }
+
+ // Note, this only matters on the PC
+ int nCheckSumStart = send.GetNumBytesWritten();
+
+ send.WriteByte ( m_nInReliableState );
+
+ if ( m_nChokedPackets > 0 )
+ {
+ flags |= PACKET_FLAG_CHOKED;
+ send.WriteByte ( m_nChokedPackets & 0xFF ); // send number of choked packets
+ }
+
+ // always append a challenge number
+ flags |= PACKET_FLAG_CHALLENGE ;
+
+ // append the challenge number itself right on the end
+ send.WriteLong( m_ChallengeNr );
+
+ if ( SendSubChannelData( send ) )
+ {
+ flags |= PACKET_FLAG_RELIABLE;
+ }
+
+ // Is there room for given datagram data. the datagram data
+ // is somewhat more important than the normal unreliable data
+ // this is done to allow some kind of snapshot behavior
+ // weather all data in datagram is transmitted or none.
+ if ( datagram )
+ {
+ if( datagram->GetNumBitsWritten() < send.GetNumBitsLeft() )
+ {
+ send.WriteBits( datagram->GetData(), datagram->GetNumBitsWritten() );
+ }
+ else
+ {
+ ConDMsg("CNetChan::SendDatagram: data would overfow, ignoring\n");
+ }
+ }
+
+ // Is there room for the unreliable payload?
+ if ( m_StreamUnreliable.GetNumBitsWritten() < send.GetNumBitsLeft() )
+ {
+ send.WriteBits(m_StreamUnreliable.GetData(), m_StreamUnreliable.GetNumBitsWritten() );
+ }
+ else
+ {
+ ConDMsg("CNetChan::SendDatagram: Unreliable would overfow, ignoring\n");
+ }
+
+ m_StreamUnreliable.Reset(); // clear unreliable data buffer
+
+ // On the PC the voice data is in the main packet
+ if ( !IsX360() &&
+ m_StreamVoice.GetNumBitsWritten() > 0 && m_StreamVoice.GetNumBitsWritten() < send.GetNumBitsLeft() )
+ {
+ send.WriteBits(m_StreamVoice.GetData(), m_StreamVoice.GetNumBitsWritten() );
+ m_StreamVoice.Reset();
+ }
+
+ int nMinRoutablePayload = MIN_ROUTABLE_PAYLOAD;
+
+#if defined( _DEBUG ) || defined( MIN_ROUTABLE_TESTING )
+ if ( m_Socket == NS_SERVER )
+ {
+ nMinRoutablePayload = net_minroutable.GetInt();
+ }
+#endif
+
+ // Deal with packets that are too small for some networks
+ while ( send.GetNumBytesWritten() < nMinRoutablePayload )
+ {
+ // Go ahead and pad some bits as long as needed
+ send.WriteUBitLong( net_NOP, NETMSG_TYPE_BITS );
+ }
+
+ // Make sure we have enough bits to read a final net_NOP opcode before compressing
+ int nRemainingBits = send.GetNumBitsWritten() % 8;
+ if ( nRemainingBits > 0 && nRemainingBits <= (8-NETMSG_TYPE_BITS) )
+ {
+ send.WriteUBitLong( net_NOP, NETMSG_TYPE_BITS );
+ }
+
+ // if ( IsX360() )
+ {
+ // Now round up to byte boundary
+ nRemainingBits = send.GetNumBitsWritten() % 8;
+ if ( nRemainingBits > 0 )
+ {
+ int nPadBits = 8 - nRemainingBits;
+
+ flags |= ENCODE_PAD_BITS( nPadBits );
+
+ // Pad with ones
+ if ( nPadBits > 0 )
+ {
+ unsigned int unOnes = GetBitForBitnum( nPadBits ) - 1;
+ send.WriteUBitLong( unOnes, nPadBits );
+ }
+ }
+ }
+
+
+ // FIXME: This isn't actually correct since compression might make the main payload usage a bit smaller
+ bool bSendVoice = IsX360() && ( m_StreamVoice.GetNumBitsWritten() > 0 && m_StreamVoice.GetNumBitsWritten() < send.GetNumBitsLeft() );
+
+ bool bCompress = false;
+ if ( net_compresspackets.GetBool() )
+ {
+ if ( send.GetNumBytesWritten() >= net_compresspackets_minsize.GetInt() )
+ {
+ bCompress = true;
+ }
+ }
+
+ // write correct flags value and the checksum
+ flagsPos.WriteByte( flags );
+
+ // Compute checksum (must be aligned to a byte boundary!!)
+ if ( ShouldChecksumPackets() )
+ {
+ const void *pvData = send.GetData() + nCheckSumStart;
+ Assert( !(send.GetNumBitsWritten() % 8 ) );
+ int nCheckSumBytes = send.GetNumBytesWritten() - nCheckSumStart;
+ unsigned short usCheckSum = BufferToShortChecksum( pvData, nCheckSumBytes );
+ flagsPos.WriteUBitLong( usCheckSum, 16 );
+ }
+
+ // Send the datagram
+ int bytesSent = NET_SendPacket ( this, m_Socket, remote_address, send.GetData(), send.GetNumBytesWritten(), bSendVoice ? &m_StreamVoice : 0, bCompress );
+
+ if ( bSendVoice || !IsX360() )
+ {
+ m_StreamVoice.Reset();
+ }
+
+ if ( net_showudp.GetInt() && net_showudp.GetInt() != 2 )
+ {
+ int mask = 63;
+ char comp[ 64 ] = { 0 };
+ if ( net_compresspackets.GetBool() &&
+ bytesSent &&
+ ( bytesSent < send.GetNumBytesWritten() ) )
+ {
+ Q_snprintf( comp, sizeof( comp ), " compression=%5u [%5.2f %%]", bytesSent, 100.0f * float( bytesSent ) / float( send.GetNumBytesWritten() ) );
+ }
+
+ ConMsg ("UDP -> %12.12s: sz=%5i seq=%5i ack=%5i rel=%1i ch=%1i tm=%f rt=%f%s\n"
+ , GetName()
+ , send.GetNumBytesWritten()
+ , ( m_nOutSequenceNr ) & mask
+ , m_nInSequenceNr & mask
+ , (flags & PACKET_FLAG_RELIABLE) ? 1 : 0
+ , flags & PACKET_FLAG_CHALLENGE ? 1 : 0
+ , (float)net_time
+ , (float)Plat_FloatTime()
+ , comp );
+ }
+
+ // update stats
+
+ int nTotalSize = bytesSent + UDP_HEADER_SIZE;
+
+ FlowNewPacket( FLOW_OUTGOING, m_nOutSequenceNr, m_nInSequenceNr, m_nChokedPackets, 0, nTotalSize );
+
+ FlowUpdate( FLOW_OUTGOING, nTotalSize );
+
+ if ( m_fClearTime < net_time )
+ {
+ m_fClearTime = net_time;
+ }
+
+ // calculate net_time when channel will be ready for next packet (throttling)
+ // TODO: This doesn't exactly match size sent when packet is a "split" packet (actual bytes sent is higher, etc.)
+ double fAddTime = (float)nTotalSize / (float)m_Rate;
+
+ m_fClearTime += fAddTime;
+
+ if ( net_maxcleartime.GetFloat() > 0.0f )
+ {
+ double m_flLatestClearTime = net_time + net_maxcleartime.GetFloat();
+ if ( m_fClearTime > m_flLatestClearTime )
+ {
+ m_fClearTime = m_flLatestClearTime;
+ }
+ }
+
+ m_nChokedPackets = 0;
+ m_nOutSequenceNr++;
+
+ return m_nOutSequenceNr-1; // return send seq nr
+}
+
+bool CNetChan::ProcessControlMessage( int cmd, bf_read &buf)
+{
+ char string[1024];
+
+ if ( cmd == net_NOP )
+ {
+ return true;
+ }
+
+ if ( cmd == net_Disconnect )
+ {
+ buf.ReadString( string, sizeof(string) );
+ m_MessageHandler->ConnectionClosing( string );
+ return false;
+ }
+
+ if ( cmd == net_File )
+ {
+ unsigned int transferID = buf.ReadUBitLong( 32 );
+
+ buf.ReadString( string, sizeof(string) );
+ if ( buf.ReadOneBit() != 0 && IsValidFileForTransfer( string ) )
+ {
+ m_MessageHandler->FileRequested( string, transferID );
+ }
+ else
+ {
+ m_MessageHandler->FileDenied( string, transferID );
+ }
+ return true;
+ }
+
+ ConMsg( "Netchannel: received bad control cmd %i from %s.\n", cmd, remote_address.ToString() );
+ return false;
+
+}
+
+bool CNetChan::ProcessMessages( bf_read &buf )
+{
+ VPROF( "CNetChan::ProcessMessages" );
+
+ const char * showmsgname = net_showmsg.GetString();
+ const char * blockmsgname = net_blockmsg.GetString();
+
+ if ( !Q_strcmp(showmsgname, "0") )
+ {
+ showmsgname = NULL; // dont do strcmp all the time
+ }
+
+ if ( !Q_strcmp(blockmsgname, "0") )
+ {
+ blockmsgname = NULL; // dont do strcmp all the time
+ }
+
+ if ( net_showpeaks.GetInt() > 0 && net_showpeaks.GetInt() < buf.GetNumBytesLeft() )
+ {
+ showmsgname = "1"; // show messages for this packet only
+ }
+
+ bf_read democopy = buf; // create a copy of reading buffer state for demo recording
+
+ int startbit = buf.GetNumBitsRead();
+
+ while ( true )
+ {
+ if ( buf.IsOverflowed() )
+ {
+ m_MessageHandler->ConnectionCrashed( "Buffer overflow in net message" );
+ return false;
+ }
+
+ // Are we at the end?
+ if ( buf.GetNumBitsLeft() < NETMSG_TYPE_BITS )
+ {
+ break;
+ }
+
+ unsigned char cmd = buf.ReadUBitLong( NETMSG_TYPE_BITS );
+
+ if ( cmd <= net_File )
+ {
+ if ( !ProcessControlMessage( cmd, buf ) )
+ {
+ return false; // disconnect or error
+ }
+
+ continue;
+ }
+
+ // see if we have a registered message object for this type
+ INetMessage * netmsg = FindMessage( cmd );
+
+ if ( netmsg )
+ {
+ // let message parse itself from buffe
+ const char *msgname = netmsg->GetName();
+
+ int nMsgStartBit = buf.GetNumBitsRead();
+
+ if ( !netmsg->ReadFromBuffer( buf ) )
+ {
+ ConMsg( "Netchannel: failed reading message %s from %s.\n", msgname, remote_address.ToString() );
+ Assert ( 0 );
+ return false;
+ }
+
+ UpdateMessageStats( netmsg->GetGroup(), buf.GetNumBitsRead() - nMsgStartBit );
+
+ if ( showmsgname )
+ {
+ if ( (*showmsgname == '1') || !Q_stricmp(showmsgname, netmsg->GetName() ) )
+ {
+ ConMsg("Msg from %s: %s\n", remote_address.ToString(), netmsg->ToString() );
+ }
+ }
+
+ if ( blockmsgname )
+ {
+ if ( (*blockmsgname== '1') || !Q_stricmp(blockmsgname, netmsg->GetName() ) )
+ {
+ ConMsg("Blocking message %s\n", netmsg->ToString() );
+ continue;
+ }
+ }
+
+ // netmessage calls the Process function that was registered by it's MessageHandler
+ m_bProcessingMessages = true;
+ bool bRet = netmsg->Process();
+ m_bProcessingMessages = false;
+
+ // This means we were deleted during the processing of that message.
+ if ( m_bShouldDelete )
+ {
+ delete this;
+ return false;
+ }
+
+ if ( m_bClearedDuringProcessing )
+ {
+ // Clear() was called during processing, our buffer is no longer valid
+ m_bClearedDuringProcessing = false;
+ return false;
+ }
+
+ if ( !bRet )
+ {
+ ConDMsg( "Netchannel: failed processing message %s.\n", msgname );
+ Assert ( 0 );
+ return false;
+ }
+
+
+ if ( IsOverflowed() )
+ return false;
+ }
+ else
+ {
+ ConMsg( "Netchannel: unknown net message (%i) from %s.\n", cmd, remote_address.ToString() );
+ Assert ( 0 );
+ return false;
+ }
+ }
+
+#if !defined(SWDS) && !defined(_XBOX)
+ // all messages could be parsed, write packet to demo file
+ if ( m_DemoRecorder && !demoplayer->IsPlayingBack() )
+ {
+ // only record if any message was paresd
+ m_DemoRecorder->RecordMessages( democopy, buf.GetNumBitsRead() - startbit );
+ }
+#endif
+
+ return true; // ok fine
+}
+
+void CNetChan::ProcessPlayback( void )
+{
+#if !defined(SWDS) && !defined(_XBOX)
+ netpacket_t * packet;
+
+ while ( ( packet = demoplayer->ReadPacket() ) != NULL )
+ {
+ // Update data flow stats
+ FlowNewPacket( FLOW_INCOMING, m_nInSequenceNr, m_nOutSequenceNrAck, 0, 0, packet->wiresize );
+
+ last_received = net_time;
+
+ m_MessageHandler->PacketStart( m_nInSequenceNr, m_nOutSequenceNrAck );
+
+ if ( ProcessMessages( packet->message ) )
+ {
+ m_MessageHandler->PacketEnd();
+ }
+ else
+ {
+ break;
+ }
+ }
+#endif
+}
+
+CNetChan::subChannel_s *CNetChan::GetFreeSubChannel()
+{
+ for ( int i=0; i<MAX_SUBCHANNELS; i++ )
+ {
+ if ( m_SubChannels[i].state == SUBCHANNEL_FREE )
+ return &m_SubChannels[i];
+ }
+
+ return NULL;
+}
+
+void CNetChan::CheckWaitingList(int nList)
+{
+ // go thru waiting lists and mark fragments send with this seqnr packet
+ if ( m_WaitingList[nList].Count() == 0 || m_nOutSequenceNrAck <= 0 )
+ return; // no data in list
+
+ dataFragments_t *data = m_WaitingList[nList][0]; // get head
+
+ if ( data->ackedFragments == data->numFragments )
+ {
+ // all fragmenst were send successfully
+ if ( net_showfragments.GetBool() )
+ ConMsg("Sending complete: %i fragments, %i bytes.\n", data->numFragments, data->bytes );
+
+ RemoveHeadInWaitingList( nList );
+
+ return;
+ }
+ else if ( data->ackedFragments > data->numFragments )
+ {
+ //ConMsg("CheckWaitingList: invalid acknowledge fragments %i/%i.\n", data->ackedFragments, data->numFragments );
+ }
+ // else: still pending fragments
+}
+
+#ifdef STAGING_ONLY
+
+CON_COMMAND( netchan_test_upload, "[filename]: Uploads a file to server." )
+{
+ if ( args.ArgC() != 2 )
+ {
+ Msg( "Usage: netchan_test_upload [filename]\n" );
+ return;
+ }
+
+ //$ TODO: the con command system is truncating the filenames we're passing in. Need to workaround this...
+ const char *filename = args.GetCommandString() + V_strlen( "netchan_test_upload " );
+
+ Msg( "Sending '%s'\n", filename );
+ bool bRet = CNetChan::TestUpload( filename );
+ Msg( "%s returned %d\n", __FUNCTION__, bRet );
+}
+
+bool CNetChan::TestUpload( const char *filename )
+{
+ dataFragments_t data;
+ static char s_buf[] = "The quick brown\nfox\n";
+
+ data.file = FILESYSTEM_INVALID_HANDLE; // open file handle
+ V_strcpy_safe( data.filename, filename ); // filename
+ data.buffer = s_buf; // if NULL it's a file
+ data.bytes = sizeof( s_buf ) - 1; // size in bytes
+ data.bits = data.bytes * 8; // size in bits
+ data.transferID = 123; // only for files
+ data.isCompressed = false; // true if data is bzip compressed
+ data.nUncompressedSize = data.bytes; // full size in bytes
+ data.asTCP = 0; // send as TCP stream
+ data.numFragments = 0; // number of total fragments
+ data.ackedFragments = 0; // number of fragments send & acknowledged
+ data.pendingFragments = 0; // number of fragments send, but not acknowledged yet
+
+ return HandleUpload( &data, NULL );
+}
+
+#endif // STAGING_ONLY
+
+bool CNetChan::HandleUpload( dataFragments_t *data, INetChannelHandler *MessageHandler )
+{
+ const char *szErrorStr = NULL;
+ static ConVar *s_pAllowUpload = g_pCVar->FindVar( "sv_allowupload" );
+
+ if ( !s_pAllowUpload || !s_pAllowUpload->GetBool() )
+ {
+ szErrorStr = "ignored. File uploads are disabled!";
+ }
+ else
+ {
+ // Make sure that this file is not being written to a location above the current directory, isn't in
+ // writing to any locations we don't want, isn't an unsupported
+ if ( !CNetChan::IsValidFileForTransfer( data->filename ) )
+ {
+ szErrorStr = "has invalid path or extension!";
+ }
+ else
+ {
+ // There's a special write path for this stuff
+ const char *pszPathID = "download";
+
+ // we received a file, write it to disk and notify host
+ if ( g_pFileSystem->FileExists( data->filename, pszPathID ) )
+ {
+ szErrorStr = "already exists!";
+ }
+ else
+ {
+ // Make sure path exists
+ char szParentDir[ MAX_PATH ];
+ if ( !V_ExtractFilePath( data->filename, szParentDir, sizeof( szParentDir ) ) )
+ szParentDir[0] = '\0';
+
+ g_pFileSystem->CreateDirHierarchy( szParentDir, pszPathID );
+
+ // Open new file for write binary.
+ data->file = g_pFileSystem->Open( data->filename, "wb", pszPathID );
+
+ if ( FILESYSTEM_INVALID_HANDLE == data->file )
+ {
+ szErrorStr = "failed to write!";
+ }
+ else
+ {
+ g_pFileSystem->Write( data->buffer, data->bytes, data->file );
+ g_pFileSystem->Close( data->file );
+
+ if ( net_showfragments.GetInt() == 2 )
+ {
+ DevMsg( "FileReceived: %s, %i bytes (ID %i)\n", data->filename, data->bytes, data->transferID );
+ }
+
+ if ( MessageHandler )
+ {
+ MessageHandler->FileReceived( data->filename, data->transferID );
+ }
+ }
+ }
+ }
+
+ }
+
+ if ( szErrorStr )
+ {
+ ConMsg( "Download file '%s' %s\n", data->filename, szErrorStr );
+ }
+
+ return true;
+}
+
+bool CNetChan::CheckReceivingList(int nList)
+{
+ dataFragments_t * data = &m_ReceiveList[nList]; // get list
+
+ if ( data->buffer == NULL )
+ return true;
+
+ if ( data->ackedFragments < data->numFragments )
+ return true;
+
+ if ( data->ackedFragments > data->numFragments )
+ {
+ ConMsg( "Receiving failed: too many fragments %i/%i from %s\n", data->ackedFragments, data->numFragments, GetAddress() );
+ return false;
+ }
+
+ // Got all fragments.
+
+ if ( net_showfragments.GetBool() )
+ ConMsg("Receiving complete: %i fragments, %i bytes\n", data->numFragments, data->bytes );
+
+ if ( data->isCompressed )
+ {
+ UncompressFragments( data );
+ }
+
+ if ( !data->filename[0] )
+ {
+ bf_read buffer( data->buffer, data->bytes );
+
+ if ( !ProcessMessages( buffer ) ) // parse net message
+ {
+ return false; // stop reading any further
+ }
+ }
+ else
+ {
+ HandleUpload( data, m_MessageHandler );
+ }
+
+ // clear receiveList
+ if ( data->buffer )
+ {
+ delete [] data->buffer;
+ data->buffer = NULL;
+ }
+
+ return true;
+
+}
+
+int CNetChan::ProcessPacketHeader( netpacket_t * packet )
+{
+ // get sequence numbers
+ int sequence = packet->message.ReadLong();
+ int sequence_ack= packet->message.ReadLong();
+ int flags = packet->message.ReadByte();
+
+ if ( ShouldChecksumPackets() )
+ {
+ unsigned short usCheckSum = (unsigned short)packet->message.ReadUBitLong( 16 );
+
+ // Checksum applies to rest of packet
+ Assert( !( packet->message.GetNumBitsRead() % 8 ) );
+ int nOffset = packet->message.GetNumBitsRead() >> 3;
+ int nCheckSumBytes = packet->message.TotalBytesAvailable() - nOffset;
+
+ const void *pvData = packet->message.GetBasePointer() + nOffset;
+ unsigned short usDataCheckSum = BufferToShortChecksum( pvData, nCheckSumBytes );
+
+ if ( usDataCheckSum != usCheckSum )
+ {
+ ConMsg ("%s:corrupted packet %i at %i\n"
+ , remote_address.ToString ()
+ , sequence
+ , m_nInSequenceNr);
+ return -1;
+ }
+ }
+
+ int relState = packet->message.ReadByte(); // reliable state of 8 subchannels
+ int nChoked = 0; // read later if choked flag is set
+ int i,j;
+
+ if ( flags & PACKET_FLAG_CHOKED )
+ nChoked = packet->message.ReadByte();
+
+ if ( flags & PACKET_FLAG_CHALLENGE )
+ {
+ unsigned int nChallenge = packet->message.ReadLong();
+ if ( nChallenge != m_ChallengeNr )
+ return -1;
+ // challenge was good, latch we saw a good one
+ m_bStreamContainsChallenge = true;
+ }
+ else if ( m_bStreamContainsChallenge )
+ return -1; // what, no challenge in this packet but we got them before?
+
+ // discard stale or duplicated packets
+ if (sequence <= m_nInSequenceNr )
+ {
+ if ( net_showdrop.GetInt() )
+ {
+ if ( sequence == m_nInSequenceNr )
+ {
+ ConMsg ("%s:duplicate packet %i at %i\n"
+ , remote_address.ToString ()
+ , sequence
+ , m_nInSequenceNr);
+ }
+ else
+ {
+ ConMsg ("%s:out of order packet %i at %i\n"
+ , remote_address.ToString ()
+ , sequence
+ , m_nInSequenceNr);
+ }
+ }
+
+ return -1;
+ }
+
+//
+// dropped packets don't keep the message from being used
+//
+ m_PacketDrop = sequence - (m_nInSequenceNr + nChoked + 1);
+
+ if ( m_PacketDrop > 0 )
+ {
+ if ( net_showdrop.GetInt() )
+ {
+ ConMsg ("%s:Dropped %i packets at %i\n"
+ ,remote_address.ToString(), m_PacketDrop, sequence );
+ }
+ }
+
+ if ( net_maxpacketdrop.GetInt() > 0 && m_PacketDrop > net_maxpacketdrop.GetInt() )
+ {
+ if ( net_showdrop.GetInt() )
+ {
+ ConMsg ("%s:Too many dropped packets (%i) at %i\n"
+ ,remote_address.ToString(), m_PacketDrop, sequence );
+ }
+ return -1;
+ }
+
+
+ for ( i = 0; i<MAX_SUBCHANNELS; i++ )
+ {
+ int bitmask = (1<<i);
+
+ // data of channel i has been acknowledged
+ subChannel_s * subchan = &m_SubChannels[i];
+
+ Assert( subchan->index == i);
+
+ if ( (m_nOutReliableState & bitmask) == (relState & bitmask) )
+ {
+ if ( subchan->state == SUBCHANNEL_DIRTY )
+ {
+ // subchannel was marked dirty during changelevel, waiting list is already cleared
+ subchan->Free();
+ }
+ else if ( subchan->sendSeqNr > sequence_ack )
+ {
+ ConMsg ("%s:reliable state invalid (%i).\n" ,remote_address.ToString(), i );
+ Assert( 0 );
+ return -1;
+ }
+ else if ( subchan->state == SUBCHANNEL_WAITING )
+ {
+ for ( j=0; j<MAX_STREAMS; j++ )
+ {
+ if ( subchan->numFragments[j] == 0 )
+ continue;
+
+ Assert( m_WaitingList[j].Count() > 0 );
+
+ dataFragments_t * data = m_WaitingList[j][0];
+
+ // tell waiting list, that we received the acknowledge
+ data->ackedFragments += subchan->numFragments[j];
+ data->pendingFragments -= subchan->numFragments[j];
+ }
+
+ subchan->Free(); // mark subchannel as free again
+ }
+ }
+ else // subchannel doesn't match
+ {
+ if ( subchan->sendSeqNr <= sequence_ack )
+ {
+ Assert( subchan->state != SUBCHANNEL_FREE );
+
+ if ( subchan->state == SUBCHANNEL_WAITING )
+ {
+ if ( net_showfragments.GetBool() )
+ {
+ ConMsg("Resending subchan %i: start %i, num %i\n", subchan->index, subchan->startFraggment[0], subchan->numFragments[0] );
+ }
+
+ subchan->state = SUBCHANNEL_TOSEND; // schedule for resend
+ }
+ else if ( subchan->state == SUBCHANNEL_DIRTY )
+ {
+ // remote host lost dirty channel data, flip bit back
+ int bit = 1<<subchan->index; // flip bit back since data was send yet
+
+ FLIPBIT(m_nOutReliableState, bit);
+
+ subchan->Free();
+ }
+ }
+ }
+ }
+
+ m_nInSequenceNr = sequence;
+ m_nOutSequenceNrAck = sequence_ack;
+ ETWReadPacket( packet->from.ToString(), packet->wiresize, m_nInSequenceNr, m_nOutSequenceNr );
+
+// Update waiting list status
+
+ for( i=0; i<MAX_STREAMS;i++)
+ CheckWaitingList( i );
+
+// Update data flow stats (use wiresize (compressed))
+ FlowNewPacket( FLOW_INCOMING, m_nInSequenceNr, m_nOutSequenceNrAck, nChoked, m_PacketDrop, packet->wiresize + UDP_HEADER_SIZE );
+
+ return flags;
+}
+
+/*
+=================
+CNetChan::ProcessPacket
+
+called when a new packet has arrived for this netchannel
+sequence numbers are extracted, fragments/file streams stripped
+and then the netmessages processed
+=================
+*/
+void CNetChan::ProcessPacket( netpacket_t * packet, bool bHasHeader )
+{
+ VPROF( "CNetChan::ProcessPacket" );
+
+ Assert( packet );
+
+ bf_read &msg = packet->message; // handy shortcut
+
+ if ( remote_address.IsValid() && !packet->from.CompareAdr ( remote_address ) )
+ {
+ return;
+ }
+
+ // Update data flow stats
+ FlowUpdate( FLOW_INCOMING, packet->wiresize + UDP_HEADER_SIZE );
+
+ int flags = 0;
+
+ if ( bHasHeader )
+ {
+ flags = ProcessPacketHeader( packet );
+ }
+
+ if ( flags == -1 )
+ return; // invalid header/packet
+
+ if ( net_showudp.GetInt() && net_showudp.GetInt() != 3 )
+ {
+ ConMsg ("UDP <- %s: sz=%i seq=%i ack=%i rel=%i ch=%d, tm=%f rt=%f wire=%i\n"
+ , GetName()
+ , packet->size
+ , m_nInSequenceNr & 63
+ , m_nOutSequenceNrAck & 63
+ , flags & PACKET_FLAG_RELIABLE ? 1 : 0
+ , flags & PACKET_FLAG_CHALLENGE ? 1 : 0
+ , net_time
+ , (float)Plat_FloatTime()
+ , packet->wiresize );
+ }
+
+ last_received = net_time;
+
+// tell message handler that a new packet has arrived
+ m_MessageHandler->PacketStart( m_nInSequenceNr, m_nOutSequenceNrAck );
+
+ if ( flags & PACKET_FLAG_RELIABLE )
+ {
+ int i, bit = 1<<msg.ReadUBitLong( 3 );
+
+ for ( i=0; i<MAX_STREAMS; i++ )
+ {
+ if ( msg.ReadOneBit() != 0 )
+ {
+ if ( !ReadSubChannelData( msg, i ) )
+ return; // error while reading fragments, drop whole packet
+ }
+ }
+
+ // flip subChannel bit to signal successful receiving
+ FLIPBIT(m_nInReliableState, bit);
+
+ for ( i=0; i<MAX_STREAMS; i++ )
+ {
+ if ( !CheckReceivingList( i ) )
+ return; // error while processing
+ }
+ }
+
+// Is there anything left to process?
+ if ( msg.GetNumBitsLeft() > 0 )
+ {
+ // parse and handle all messeges
+ if ( !ProcessMessages( msg ) )
+ {
+ return; // disconnect or error
+ }
+ }
+
+// tell message handler that packet is completely parsed
+ m_MessageHandler->PacketEnd();
+
+#if !defined(SWDS) && !defined(_XBOX)
+// tell demo system that packet is completely parsed
+ if ( m_DemoRecorder && !demoplayer->IsPlayingBack() )
+ {
+ m_DemoRecorder->RecordPacket();
+ }
+#endif
+}
+
+int CNetChan::GetNumBitsWritten( bool bReliable )
+{
+ bf_write *pStream = &m_StreamUnreliable;
+ if ( bReliable )
+ {
+ pStream = &m_StreamReliable;
+ }
+ return pStream->GetNumBitsWritten();
+}
+
+bool CNetChan::SendNetMsg( INetMessage &msg, bool bForceReliable, bool bVoice )
+{
+ if ( remote_address.GetType() == NA_NULL )
+ return true;
+
+ bf_write *pStream = &m_StreamUnreliable;
+
+ if ( msg.IsReliable() || bForceReliable )
+ {
+ pStream = &m_StreamReliable;
+ }
+
+ if ( bVoice )
+ {
+ pStream = &m_StreamVoice;
+ }
+
+ if ( vcr_verbose.GetInt() )
+ {
+ bool bRet = false;
+#ifndef NO_VCR
+ int nOldBytes = pStream->GetNumBytesWritten();
+ bRet = msg.WriteToBuffer( *pStream );
+ int nNewBytes = pStream->GetNumBytesWritten();
+
+ if ( nNewBytes > nOldBytes )
+ {
+ VCRGenericValueVerify( "NetMsg", &pStream->GetBasePointer()[nOldBytes], nNewBytes-nOldBytes-1 );
+ }
+#endif
+ return bRet;
+ }
+ else
+ {
+ return msg.WriteToBuffer( *pStream );
+ }
+}
+
+INetMessage *CNetChan::FindMessage(int type)
+{
+ int numtypes = m_NetMessages.Count();
+
+ for (int i=0; i<numtypes; i++ )
+ {
+ if ( m_NetMessages[i]->GetType() == type )
+ return m_NetMessages[i];
+ }
+
+ return NULL;
+}
+
+bool CNetChan::RegisterMessage(INetMessage *msg)
+{
+ Assert( msg );
+
+ if ( FindMessage( msg->GetType() ) )
+ {
+ return false;
+ }
+
+ m_NetMessages.AddToTail( msg );
+ msg->SetNetChannel( this );
+
+ return true;
+}
+
+bool CNetChan::SendData( bf_write &msg, bool bReliable )
+{
+ // Always queue any pending reliable data ahead of the fragmentation buffer
+
+ if ( remote_address.GetType() == NA_NULL )
+ return true;
+
+ if ( msg.GetNumBitsWritten() <= 0 )
+ return true;
+
+ if ( msg.IsOverflowed() && !bReliable )
+ return true;
+
+ bf_write * buf = bReliable ? &m_StreamReliable : &m_StreamUnreliable;
+
+
+ if ( msg.GetNumBitsWritten() > buf->GetNumBitsLeft() )
+ {
+ if ( bReliable )
+ {
+ ConMsg( "ERROR! SendData reliabe data too big (%i)", msg.GetNumBytesWritten() );
+ }
+
+ return false;
+ }
+
+ return buf->WriteBits( msg.GetData(), msg.GetNumBitsWritten() );
+}
+
+bool CNetChan::SendReliableViaStream( dataFragments_t *data)
+{
+ // Always queue any pending reliable data ahead of the fragmentation buffer
+
+ ALIGN4 char headerBuf[32] ALIGN4_POST;
+ bf_write header( "outDataHeader", headerBuf, sizeof(headerBuf) );
+
+
+ data->transferID = m_nOutSequenceNr; // used for acknowledging
+ data->pendingFragments = data->numFragments; // send, but not ACKed yet
+
+ header.WriteByte( STREAM_CMD_DATA );
+ header.WriteWord( data->bytes ); // bytes
+ header.WriteLong( data->transferID );
+
+ if ( net_showtcp.GetInt() )
+ {
+ ConMsg ("TCP -> %s: sz=%i seq=%i\n", remote_address.ToString(), data->bytes, m_nOutSequenceNr );
+ }
+
+ NET_SendStream( m_StreamSocket, (char*)header.GetData(), header.GetNumBytesWritten(), 0 );
+
+ return NET_SendStream( m_StreamSocket, data->buffer, data->bytes, 0 ) != -1;
+}
+
+bool CNetChan::SendReliableAcknowledge(int seqnr)
+{
+ // Always queue any pending reliable data ahead of the fragmentation buffer
+
+ ALIGN4 char headerBuf[32] ALIGN4_POST;
+ bf_write header( "outAcknHeader", headerBuf, sizeof(headerBuf) );
+
+ header.WriteByte( STREAM_CMD_ACKN );
+ header.WriteLong( seqnr ); // used for acknowledging
+
+ if ( net_showtcp.GetInt() )
+ {
+ ConMsg ("TCP -> %s: ACKN seq=%i\n", remote_address.ToString(), seqnr );
+ }
+
+ return NET_SendStream( m_StreamSocket, (char*)header.GetData(), header.GetNumBytesWritten(), 0 ) > 0;
+}
+
+bool CNetChan::ProcessStream( void )
+{
+ char cmd;
+ ALIGN4 char headerBuf[512] ALIGN4_POST;
+
+ if ( !m_StreamSocket )
+ return true;
+
+ if ( m_SteamType == STREAM_CMD_NONE )
+ {
+ // read command byte
+ int ret = NET_ReceiveStream( m_StreamSocket, &cmd, 1, 0 );
+
+ if ( ret == 0)
+ {
+ // nothing received, but ok
+ return true;
+ }
+ else if ( ret == -1 )
+ {
+ // something failed with the TCP connection
+ return false;
+ }
+
+ ResetStreaming(); // clear all state values
+
+ m_SteamType = cmd;
+
+ }
+
+ bf_read header( "inDataHeader", headerBuf, sizeof(headerBuf) );
+
+ // now check command type
+
+ if ( m_SteamType==STREAM_CMD_AUTH )
+ {
+ // server accpeted connection, send challenge nr
+ m_StreamActive = true;
+
+ ResetStreaming();
+
+ return SendReliableAcknowledge( m_ChallengeNr );
+ }
+
+ if ( (m_SteamType==STREAM_CMD_DATA) && (m_StreamLength==0) )
+ {
+ int ret = NET_ReceiveStream( m_StreamSocket, (char*)&headerBuf, 6, 0 ) ;
+
+ if ( ret == 0)
+ {
+ // nothing received, but ok
+ return true;
+ }
+ else if ( ret == -1 )
+ {
+ // something failed with the TCP connection
+ return false;
+ }
+
+ m_StreamLength = header.ReadWord();
+ m_StreamSeqNr = header.ReadLong();
+
+ const int cMaxPayload = GetProtocolVersion() > PROTOCOL_VERSION_23 ? NET_MAX_PAYLOAD : NET_MAX_PAYLOAD_V23;
+ if ( m_StreamLength > cMaxPayload )
+ {
+ ConMsg( "ERROR! Stream indata too big (%i)", m_StreamLength );
+ return false;
+ }
+ }
+
+ if ( (m_SteamType==STREAM_CMD_FILE) && (m_SteamFile[0]==0) )
+ {
+ Assert ( 0 );
+ return false;
+ }
+
+ if ( (m_SteamType==STREAM_CMD_ACKN) && (m_StreamSeqNr==0) )
+ {
+ int ret = NET_ReceiveStream( m_StreamSocket, (char*)&headerBuf, 4, 0 );
+
+ if ( ret == 0)
+ {
+ // nothing received, but ok
+ return true;
+ }
+ else if ( ret == -1 )
+ {
+ // something failed with the TCP connection
+ return false;
+ }
+
+ m_StreamSeqNr = header.ReadLong();
+
+ dataFragments_t * data = m_WaitingList[FRAG_NORMAL_STREAM][0];
+
+ if ( data->transferID == (unsigned)m_StreamSeqNr )
+ {
+ if ( net_showtcp.GetInt() )
+ {
+ ConMsg ("TCP <- %s: ACKN seqnr=%i\n", remote_address.ToString(), m_StreamSeqNr );
+ }
+
+ Assert( data->pendingFragments == data->numFragments );
+
+ RemoveHeadInWaitingList( FRAG_NORMAL_STREAM );
+ }
+ else
+ {
+ ConMsg ("TCP <- %s: invalid ACKN seqnr=%i\n", remote_address.ToString(), m_StreamSeqNr );
+ }
+
+ ResetStreaming();
+ return true;
+ }
+
+
+ if ( m_StreamReceived < m_StreamLength )
+ {
+ // read in 4kB chuncks
+ int bytesLeft = ( m_StreamLength - m_StreamReceived );
+
+ int bytesRecv = NET_ReceiveStream( m_StreamSocket, (char*)m_StreamData.Base() + m_StreamReceived, bytesLeft, 0 );
+
+ if ( bytesRecv == 0 )
+ {
+ return true;
+ }
+ else if ( bytesRecv == -1 )
+ {
+ return false;
+ }
+
+ m_StreamReceived+= bytesRecv;
+
+ if ( m_StreamReceived > m_StreamLength )
+ {
+ ConMsg( "ERROR! Stream indata oversize." );
+ return false;
+ }
+
+ if ( m_StreamReceived == m_StreamLength )
+ {
+ int ackseqnr =m_StreamSeqNr;
+
+ bf_read buffer( m_StreamData.Base(), m_StreamLength );
+
+ ProcessMessages( buffer );
+
+ // reset stream state
+ ResetStreaming();
+
+ return SendReliableAcknowledge( ackseqnr ); // tell sender that we have it
+ }
+ }
+
+ return true;
+}
+
+int CNetChan::GetDataRate() const
+{
+ return m_Rate;
+}
+
+bool CNetChan::HasPendingReliableData( void )
+{
+ return (m_StreamReliable.GetNumBitsWritten() > 0) ||
+ (m_WaitingList[FRAG_NORMAL_STREAM].Count() > 0) ||
+ (m_WaitingList[FRAG_FILE_STREAM].Count() > 0);
+}
+
+float CNetChan::GetTimeConnected() const
+{
+ float t = net_time - connect_time;
+ return (t>0.0f) ? t : 0.0f ;
+}
+
+const netadr_t & CNetChan::GetRemoteAddress() const
+{
+ return remote_address;
+}
+
+INetChannelHandler * CNetChan::GetMsgHandler( void ) const
+{
+ return m_MessageHandler;
+}
+
+bool CNetChan::IsTimedOut() const
+{
+ if ( m_Timeout == -1.0f )
+ return false;
+ else
+ return (last_received + m_Timeout) < net_time;
+}
+
+bool CNetChan::IsTimingOut() const
+{
+ if ( m_Timeout == -1.0f )
+ return false;
+ else
+ return (last_received + CONNECTION_PROBLEM_TIME) < net_time;
+}
+
+float CNetChan::GetTimeoutSeconds() const
+{
+ return m_Timeout;
+}
+
+float CNetChan::GetTimeSinceLastReceived() const
+{
+ float t = net_time - last_received;
+ return (t>0.0f) ? t : 0.0f ;
+}
+
+bool CNetChan::IsOverflowed() const
+{
+ return m_StreamReliable.IsOverflowed();
+}
+
+void CNetChan::Reset()
+{
+ // FlowReset();
+ m_StreamUnreliable.Reset(); // clear any pending unreliable data messages
+ m_StreamReliable.Reset(); // clear any pending reliable data messages
+ m_fClearTime = 0.0; // ready to send
+ m_nChokedPackets = 0;
+
+ m_nSplitPacketSequence = 1;
+}
+
+int CNetChan::GetSocket() const
+{
+ return m_Socket;
+}
+
+float CNetChan::GetAvgData( int flow ) const
+{
+ return m_DataFlow[flow].avgbytespersec;
+}
+
+float CNetChan::GetAvgPackets( int flow ) const
+{
+ return m_DataFlow[flow].avgpacketspersec;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *chan -
+//-----------------------------------------------------------------------------
+int CNetChan::GetTotalData(int flow ) const
+{
+ return m_DataFlow[flow].totalbytes;
+}
+
+int CNetChan::GetSequenceNr( int flow ) const
+{
+ if ( flow == FLOW_OUTGOING )
+ {
+ return m_nOutSequenceNr;
+ }
+ else if ( flow == FLOW_INCOMING )
+ {
+ return m_nInSequenceNr;
+ }
+
+ return 0;
+}
+
+int CNetChan::GetBufferSize( void ) const
+{
+ return NET_FRAMES_BACKUP;
+}
+
+bool CNetChan::IsValidPacket( int flow, int frame_number ) const
+{
+ return m_DataFlow[flow].frames[ frame_number & NET_FRAMES_MASK ].valid;
+}
+
+float CNetChan::GetPacketTime( int flow, int frame_number ) const
+{
+ return m_DataFlow[flow].frames[ frame_number & NET_FRAMES_MASK ].time;
+}
+
+void CNetChan::GetPacketResponseLatency( int flow, int frame_number, int *pnLatencyMsecs, int *pnChoke ) const
+{
+ const netframe_t &nf = m_DataFlow[flow].frames[ frame_number & NET_FRAMES_MASK ];
+ if ( pnLatencyMsecs )
+ {
+ if ( nf.dropped )
+ {
+ *pnLatencyMsecs = 9999;
+ }
+ else
+ {
+ *pnLatencyMsecs = (int)( 1000.0f * nf.avg_latency );
+ }
+ }
+ if ( pnChoke )
+ {
+ *pnChoke = nf.choked;
+ }
+}
+
+void CNetChan::GetRemoteFramerate( float *pflFrameTime, float *pflRemoteFrameTimeStdDeviation ) const
+{
+ if ( pflFrameTime )
+ {
+ *pflFrameTime = m_flRemoteFrameTime;
+ }
+ if ( pflRemoteFrameTimeStdDeviation )
+ {
+ *pflRemoteFrameTimeStdDeviation = m_flRemoteFrameTimeStdDeviation;
+ }
+}
+
+
+float CNetChan::GetLatency( int flow ) const
+{
+ return m_DataFlow[flow].latency;
+}
+
+float CNetChan::GetAvgChoke( int flow ) const
+{
+ return m_DataFlow[flow].avgchoke;
+}
+
+float CNetChan::GetAvgLatency( int flow ) const
+{
+ return m_DataFlow[flow].avglatency;
+}
+
+float CNetChan::GetAvgLoss( int flow ) const
+{
+ return m_DataFlow[flow].avgloss;
+}
+
+float CNetChan::GetTime( void ) const
+{
+ return net_time;
+}
+
+bool CNetChan::GetStreamProgress( int flow, int *received, int *total ) const
+{
+ (*total) = 0;
+ (*received) = 0;
+
+ if ( flow == FLOW_INCOMING )
+ {
+ for ( int i = 0; i<MAX_STREAMS; i++ )
+ {
+ if ( m_ReceiveList[i].buffer != NULL )
+ {
+ (*total) += m_ReceiveList[i].numFragments * FRAGMENT_SIZE;
+ (*received) += m_ReceiveList[i].ackedFragments * FRAGMENT_SIZE;
+ }
+ }
+
+ return ((*total)>0);
+ }
+
+ if ( flow == FLOW_OUTGOING )
+ {
+ for ( int i = 0; i<MAX_STREAMS; i++ )
+ {
+ if ( m_WaitingList[i].Count() > 0 )
+ {
+ (*total) += m_WaitingList[i][0]->numFragments * FRAGMENT_SIZE;
+ (*received) += m_WaitingList[i][0]->ackedFragments * FRAGMENT_SIZE;
+ }
+ }
+
+ return ((*total)>0);
+ }
+
+ return false; // TODO TCP progress
+}
+
+float CNetChan::GetCommandInterpolationAmount( int flow, int frame_number ) const
+{
+ return m_DataFlow[ flow ].frames[ frame_number & NET_FRAMES_MASK ].m_flInterpolationAmount;
+}
+
+int CNetChan::GetPacketBytes( int flow, int frame_number, int group ) const
+{
+ if ( group >= INetChannelInfo::TOTAL )
+ {
+ return m_DataFlow[flow].frames[ frame_number & NET_FRAMES_MASK ].size;
+ }
+ else
+ {
+ return Bits2Bytes( m_DataFlow[flow].frames[ frame_number & NET_FRAMES_MASK ].msggroups[group] );
+ }
+}
+
+void CNetChan::UpdateMessageStats( int msggroup, int bits)
+{
+ netflow_t * pflow = &m_DataFlow[ FLOW_INCOMING ];
+ netframe_t *pframe = pflow->currentframe;
+
+ Assert( (msggroup >= INetChannelInfo::GENERIC) && (msggroup < INetChannelInfo::TOTAL) );
+
+ m_MsgStats[msggroup] += bits;
+
+ if ( pframe )
+ pframe->msggroups[msggroup] +=bits;
+
+}
+
+void CNetChan::IncrementQueuedPackets()
+{
+ ++m_nQueuedPackets;
+}
+
+void CNetChan::DecrementQueuedPackets()
+{
+ --m_nQueuedPackets;
+ Assert( m_nQueuedPackets >= 0 );
+ if ( m_nQueuedPackets < 0 )
+ m_nQueuedPackets = 0;
+}
+
+bool CNetChan::HasQueuedPackets() const
+{
+ if ( g_pQueuedPackedSender->HasQueuedPackets( this ) )
+ {
+ return true;
+ }
+
+ return m_nQueuedPackets > 0;
+}
+
+void CNetChan::SetInterpolationAmount( float flInterpolationAmount )
+{
+ m_flInterpolationAmount = flInterpolationAmount;
+}
+
+void CNetChan::SetRemoteFramerate( float flFrameTime, float flFrameTimeStdDeviation )
+{
+ m_flRemoteFrameTime = flFrameTime;
+ m_flRemoteFrameTimeStdDeviation = flFrameTimeStdDeviation;
+}
+
+// Max # of payload bytes before we must split/fragment the packet
+void CNetChan::SetMaxRoutablePayloadSize( int nSplitSize )
+{
+ if ( m_nMaxRoutablePayloadSize != nSplitSize )
+ {
+ DevMsg( "Setting max routable payload size from %d to %d for %s\n",
+ m_nMaxRoutablePayloadSize, nSplitSize, GetName() );
+ }
+ m_nMaxRoutablePayloadSize = nSplitSize;
+}
+
+int CNetChan::GetMaxRoutablePayloadSize()
+{
+ return m_nMaxRoutablePayloadSize;
+}
+
+int CNetChan::GetProtocolVersion()
+{
+ AssertMsg(
+ m_nProtocolVersion >= 0 && m_nProtocolVersion <= PROTOCOL_VERSION,
+ "This is probably not being initialized somewhere"
+ );
+ return m_nProtocolVersion;
+}
+
+int CNetChan::IncrementSplitPacketSequence()
+{
+ return ++m_nSplitPacketSequence;
+}
+
+bool CNetChan::IsValidFileForTransfer( const char *pszFilename )
+{
+ if ( !pszFilename || !pszFilename[ 0 ] )
+ return false;
+
+ // No absolute paths or weaseling up the tree with ".." allowed.
+ if ( !COM_IsValidPath( pszFilename ) || V_IsAbsolutePath( pszFilename ) )
+ return false;
+
+ int len = V_strlen( pszFilename );
+ if ( len >= MAX_PATH )
+ return false;
+
+ char szTemp[ MAX_PATH ];
+ V_strcpy_safe( szTemp, pszFilename );
+
+ // Convert so we've got all forward slashes in the path.
+ V_FixSlashes( szTemp, '/' );
+ V_FixDoubleSlashes( szTemp );
+ if ( szTemp[ len - 1 ] == '/' )
+ return false;
+
+ int slash_count = 0;
+ for ( const char *psz = szTemp; *psz; psz++ )
+ {
+ if ( *psz == '/' )
+ slash_count++;
+ }
+ // Really no reason to have deeper directory than this?
+ if ( slash_count >= 32 )
+ return false;
+
+ // Don't allow filenames with unicode whitespace in them.
+ if ( Q_RemoveAllEvilCharacters( szTemp ) )
+ return false;
+
+ if ( V_stristr( szTemp, "lua/" ) ||
+ V_stristr( szTemp, "gamemodes/" ) ||
+ V_stristr( szTemp, "addons/" ) ||
+ V_stristr( szTemp, "~/" ) ||
+ // V_stristr( szTemp, "//" ) || // Don't allow '//'. TODO: Is this check ok?
+ V_stristr( szTemp, "./././" ) || // Don't allow folks to make crazy long paths with ././././ stuff.
+ V_stristr( szTemp, " " ) || // Don't allow multiple spaces or tab (was being used for an exploit).
+ V_stristr( szTemp, "\t" ) )
+ {
+ return false;
+ }
+
+ // If .exe or .EXE or these other strings exist _anywhere_ in the filename, reject it.
+ if ( V_stristr( szTemp, ".cfg" ) ||
+ V_stristr( szTemp, ".lst" ) ||
+ V_stristr( szTemp, ".exe" ) ||
+ V_stristr( szTemp, ".vbs" ) ||
+ V_stristr( szTemp, ".com" ) ||
+ V_stristr( szTemp, ".bat" ) ||
+ V_stristr( szTemp, ".cmd" ) ||
+ V_stristr( szTemp, ".dll" ) ||
+ V_stristr( szTemp, ".so" ) ||
+ V_stristr( szTemp, ".dylib" ) ||
+ V_stristr( szTemp, ".ini" ) ||
+ V_stristr( szTemp, ".log" ) ||
+ V_stristr( szTemp, ".lua" ) ||
+ V_stristr( szTemp, ".vdf" ) ||
+ V_stristr( szTemp, ".smx" ) ||
+ V_stristr( szTemp, ".gcf" ) ||
+ V_stristr( szTemp, ".lmp" ) ||
+ V_stristr( szTemp, ".sys" ) )
+ {
+ return false;
+ }
+
+ // Search for the first . in the base filename, and bail if not found.
+ // We don't want people passing in things like 'cfg/.wp.so'...
+ const char *basename = strrchr( szTemp, '/' );
+ if ( !basename )
+ basename = szTemp;
+ const char *extension = strchr( basename, '.' );
+ if ( !extension )
+ return false;
+
+ // If the extension is not exactly 3 or 4 characters, bail.
+ int extension_len = V_strlen( extension );
+ if ( ( extension_len != 3 ) &&
+ ( extension_len != 4 ) &&
+ V_stricmp( extension, ".bsp.bz2" ) &&
+ V_stricmp( extension, ".xbox.vtx" ) &&
+ V_stricmp( extension, ".dx80.vtx" ) &&
+ V_stricmp( extension, ".dx90.vtx" ) &&
+ V_stricmp( extension, ".sw.vtx" ) )
+ {
+ return false;
+ }
+
+ // If there are any spaces in the extension, bail. (Windows exploit).
+ if ( strchr( extension, ' ' ) )
+ return false;
+
+ return true;
+}
+