diff options
Diffstat (limited to 'engine/net_chan.cpp')
| -rw-r--r-- | engine/net_chan.cpp | 3232 |
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; +} + |