2020-04-22 18:56:21 +02:00
//========= 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"
2024-07-18 08:12:29 +02:00
# include "common.h"
2020-04-22 18:56:21 +02:00
# 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. " ) ;
2024-07-26 05:32:47 +02:00
static ConVar net_compresspackets_minsize ( " net_compresspackets_minsize " , " 0 " , 0 , " Don't bother compressing packets below this size. " ) ;
2020-04-22 18:56:21 +02:00
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 ( )
{
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
2024-07-18 08:12:29 +02:00
unsigned int compressedSize = COM_GetIdealDestinationCompressionBufferSize_ZSTD ( data - > bytes ) ;
2020-04-22 18:56:21 +02:00
char * compressedData = new char [ compressedSize ] ;
2024-07-18 08:12:29 +02:00
if ( COM_BufferToBufferCompress_ZSTD ( compressedData , & compressedSize , data - > buffer , data - > bytes ) & &
2020-04-22 18:56:21 +02:00
( 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 ;
2024-07-18 08:12:29 +02:00
unsigned int compressedSize = COM_GetIdealDestinationCompressionBufferSize_ZSTD ( uncompressedSize ) ;
2020-04-22 18:56:21 +02:00
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
2024-07-18 08:12:29 +02:00
if ( COM_BufferToBufferCompress_ZSTD ( compressed , & compressedSize , uncompressed , uncompressedSize ) )
2020-04-22 18:56:21 +02:00
{
// 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 \n fox \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 ;
}