diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-08-24 11:16:07 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-08-24 11:16:07 +0200 |
| commit | c068f22329d5cc722622a2183bbb22eef2093df7 (patch) | |
| tree | 12d56c1aede67988a55e241364606bfbb4dba933 /src/libparsec/net_stream.cpp | |
| download | openparsec-main.tar.xz openparsec-main.zip | |
Diffstat (limited to 'src/libparsec/net_stream.cpp')
| -rw-r--r-- | src/libparsec/net_stream.cpp | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/src/libparsec/net_stream.cpp b/src/libparsec/net_stream.cpp new file mode 100644 index 0000000..5901f42 --- /dev/null +++ b/src/libparsec/net_stream.cpp @@ -0,0 +1,587 @@ +/* + * PARSEC - stream class - shared + * + * $Author: uberlinuxguy $ - $Date: 2004/09/26 03:43:45 $ + * + * Orginally written by: + * Copyright (c) Clemens Beer <cbx@parsec.org> 2002 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// C library +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +// compilation flags/debug support +#include "config.h" +#include "debug.h" + +// general definitions +#include "general.h" +#include "objstruc.h" + +// global externals +#include "globals.h" + +// subsystem headers +#include "net_defs.h" +#include "sys_defs.h" + +// network code config +#include "net_conf.h" + +// local module header +#include "net_stream.h" + +// proprietary module headers +#ifdef PARSEC_SERVER + #include "con_aux_sv.h" +#else + #include "con_aux.h" +#endif +#include "e_relist.h" + + +// default retransmit timeout is 200ms ---------------------------------------- +// +#define DEFAULT_RETRANSMIT_TIMEOUT FRAME_MEASURE_TIMEBASE / ( 1000 / 200 ) + + +// flags ---------------------------------------------------------------------- +// +#define _FORCED_RELIABLE_DROPPING +//#define _IDEMPOTENCY_TESTING // enable this to ALWAYS resent a packet ONCE + + + +// reset the FIFO entry ------------------------------------------------------- +// +void StreamFIFOEntry_s::Reset() +{ + // decrease reference counter + if ( m_pREList != NULL ) { + m_pREList->Release(); + } + + m_pREList = NULL; + m_Timeout = -1; + m_nRetransmitCount = 0; +} + + +// init the FIFO entry -------------------------------------------------------- +// +void StreamFIFOEntry_s::InitEntry( E_REList* pREList ) +{ + m_pREList = pREList; + m_Timeout = -1; + m_nRetransmitCount = 0; + + // increase reference counter + m_pREList->AddRef(); +} + + + + +// default ctor --------------------------------------------------------------- +// +NET_Stream::NET_Stream() +{ + Reset(); + m_ReliableRetransmit_Frames = DEFAULT_RETRANSMIT_TIMEOUT; +} + +// reset the stream to defaults ----------------------------------------------- +// +void NET_Stream::Reset() +{ + //MSGOUT("NET_Stream::Reset()"); + + // NOTE: + // no message with # ( normal/reliable ) of 0 is sent over the wire + // + Out_MessageId = 1; // first message sent is 1 + Out_ReliableMessageId = 1; + + I_ACK_MessageId = 0; + I_ACK_ReliableMessageId = 0; + + YOU_ACK_MessageId = 0; + YOU_ACK_ReliableMessageId = 0; + + for( int hid = 0; hid < MSGID_HISTORY_SIZE; hid++ ) { + message_id_history[ hid ] = 0; + } + + FlushReliableBuffer(); + + // default to support reliable transfer + m_EnableReliable = true; + + // set ids of the partners for this stream + m_nPeerID = PLAYERID_ANONYMOUS; + m_nSenderID = PLAYERID_INVALID; + + m_bIsConnected = false; +} + + +// set the stream to connected mode ( accepts non datagrams ) ----------------- +void NET_Stream::SetConnected() +{ + m_bIsConnected = true; +} + + +// append a RE list to the FIFO ---------------------------------------------- +// +int NET_Stream::AppendToReliableFIFO( E_REList* pREList ) +{ + ASSERT( pREList != NULL ); + + //MSGOUT( "AppendToReliableFIFO(): " ); + //pREList->Dump(); + if ( pREList->GetSize() == 0 ) { + ASSERT( FALSE ); + return FALSE; + } + + if ( !m_EnableReliable ) { + ASSERT( FALSE ); + return FALSE; + } + + // check whether the new append would cause the FIFO to overflow + int nNextWritePos = _FIFO_GetNextWritePos(); + StreamFIFOEntry_s* pEntry = &m_FIFOEntries[ nNextWritePos ]; + if ( pEntry->m_pREList != NULL ) { + DBGTXT( + MSGOUT("NET_Stream::AppendToReliableFIFO(): FIFO overflow." ); + for( int nEntry = 0; nEntry < MAX_NUM_RELIABLE_BACKLOG; nEntry++ ) { + MSGOUT( "================================\n" ); + MSGOUT( "FIFO: %d\n", nEntry ); + m_FIFOEntries[ nEntry ].m_pREList->Dump(); + MSGOUT( "================================\n" ); + } + ); + + return FALSE; + } + + // otherwise append the RE list to the end of the FIFO + pEntry->InitEntry( pREList ); + + m_nFIFO_WritePos = nNextWritePos; + + return TRUE; +} + + +// update the timeout for a specific FIFO slot -------------------------------- +// +void NET_Stream::_FIFO_UpdateTimeOut( StreamFIFOEntry_s* pEntry ) +{ + ASSERT( pEntry != NULL ); + pEntry->m_Timeout = ( SYSs_GetRefFrameCount() + m_ReliableRetransmit_Frames ); +} + + +// reset all reliable handling ------------------------------------------------ +// +void NET_Stream::FlushReliableBuffer() +{ + m_MessageId_ReliableWasSent = 0; + + // reset FIFO + m_nFIFO_WritePos = -1; + m_nFIFO_ReadPos = 0; + for( int nEntry = 0; nEntry < MAX_NUM_RELIABLE_BACKLOG; nEntry++ ) { + m_FIFOEntries[ nEntry ].Reset(); + } +} + + +// retrieve the next reliable RE list from FIFO to send ----------------------- +// +E_REList* NET_Stream::GetNextReliableToSend() +{ + // sanety checks + if( YOU_ACK_MessageId >= Out_MessageId ) + return NULL; + if( YOU_ACK_ReliableMessageId >= Out_ReliableMessageId ) + return NULL; + + StreamFIFOEntry_s* pEntry = &m_FIFOEntries[ m_nFIFO_ReadPos ]; + E_REList* pREList = pEntry->m_pREList; + + // nothing in FIFO + if ( pREList == NULL ) { + //DBGOUT( "NET_Stream::GetNextReliableToSend(): %d nothing in FIFO", m_nPeerID ); + return NULL; + } + + // check for first transmit of top entry in FIFO + if ( pEntry->m_Timeout == -1 ) { + + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d first transmit FIFO entry: %d", m_nPeerID, m_nFIFO_ReadPos ); + + // update timeout value & first-transmit + _FIFO_UpdateTimeOut( pEntry ); + return pREList; + } + + // if last sent reliable is not yet ACK + // retransmit if timeout, do nothing otherwise + if ( YOU_ACK_MessageId < m_MessageId_ReliableWasSent ) { + + ASSERT( pEntry->m_Timeout != -1 ); + + // timer expired ? + if ( pEntry->m_Timeout < SYSs_GetRefFrameCount() ) { + + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d timer expired - retransmit #%d - FIFO entry: %d", m_nPeerID, pEntry->m_nRetransmitCount + 1, m_nFIFO_ReadPos ); + + // update timeout value & return for retransmit + pEntry->m_nRetransmitCount++; + _FIFO_UpdateTimeOut( pEntry ); + return pREList; + } + + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d entry %d not yet ACK", m_nPeerID, m_nFIFO_ReadPos ); + + return NULL; + } + + ASSERT( YOU_ACK_MessageId >= m_MessageId_ReliableWasSent ); + + // if last sent reliable is ACK + // remove top Pos from FIFO AND transmit next reliable in FIFO + if ( YOU_ACK_ReliableMessageId == ( Out_ReliableMessageId - 1 ) ) { + + // remove message if still in backbuffer + if ( pREList != NULL ) { +#ifdef _IDEMPOTENCY_TESTING + if ( pEntry->m_nRetransmitCount == 0 ) { + pEntry->m_nRetransmitCount++; + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d IDEMPOTENCY TESTING entry %d", m_nPeerID, m_nFIFO_ReadPos ); + + // update timeout value & return for retransmit + _FIFO_UpdateTimeOut( pEntry ); + return pREList; + + } else { + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d removing from FIFO due to ACK : %d", m_nPeerID, m_nFIFO_ReadPos ); + pEntry->Reset(); + } +#else + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d removing from FIFO due to ACK : %d", m_nPeerID, m_nFIFO_ReadPos ); + pEntry->Reset(); +#endif // _IDEMPOTENCY_TESTING + } + + // step to next entry + m_nFIFO_ReadPos = _FIFO_GetNextReadPos(); + pEntry = &m_FIFOEntries[ m_nFIFO_ReadPos ]; + pREList = pEntry->m_pREList; + + if ( pREList != NULL ) { + + ASSERT( pEntry->m_Timeout == -1 ); + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d first transmit FIFO entry: %d", m_nPeerID, m_nFIFO_ReadPos ); + + // update timeout value + _FIFO_UpdateTimeOut( pEntry ); + + return pREList; + } else { + + //DBGOUT( "NET_Stream::GetNextReliableToSend(): %d nothing in FIFO", m_nPeerID ); + return NULL; + } + + } else { + + // if last sent reliable is NACK + // retransmit top of FIFO + ASSERT( YOU_ACK_ReliableMessageId < ( Out_ReliableMessageId - 1 ) ); + + DBGOUT( "NET_Stream::GetNextReliableToSend(): %d NACK detected - retransmit - FIFO entry: %d", m_nPeerID, m_nFIFO_ReadPos ); + + // update timeout value & retransmit + pEntry->m_nRetransmitCount++; + _FIFO_UpdateTimeOut( pEntry ); + return pREList; + } +} + + +// check input packet for rejection and maintain ACKs correctly ----------- +// +int NET_Stream::InPacket( NetPacket_GMSV* gamepacket_GMSV ) +{ + ASSERT( gamepacket_GMSV != NULL ); + + // datagrams are ignored + if ( gamepacket_GMSV->MessageId == MSGID_DATAGRAM ) { + return TRUE; + } + + // nothing todo until connected + if ( m_bIsConnected == false ) { + DBGTXT( MSGOUT( "NET_Stream::InPacket(): ignoring non-datagram packets on disconnected stream" ); ); + return FALSE; + } + +#ifdef _FORCED_RELIABLE_DROPPING +#ifdef PARSEC_CLIENT + + if ( AUX_NETCODE_FLAGS & 32 ) { + return FALSE; + } + + if ( ( AUX_NETCODE_FLAGS & 64 ) && ( gamepacket_GMSV->ReliableMessageId != NO_RELIABLE ) ) { + static int numreliable = -1; + + numreliable++; + + // only accept every 5th reliable packet + if ( ( numreliable % 5 ) != 4 ) { + return FALSE; + } + } + +#endif // PARSEC_CLIENT +#endif // _FORCED_RELIABLE_DROPPING + + // debug output +#ifdef INTERNAL_VERSION + if ( AUX_DEBUG_NETSTREAM_DUMP & 1 ) { + LOGOUT(( "-------------------------------------------------------------------------------" )); + LOGOUT(( "(%2d), InPacket from %2d, MessageId %5d, ReliableMessageId %5d, AckMessageId %5d, AckReliableMessageId %5d", + m_nSenderID, + m_nPeerID, + gamepacket_GMSV->MessageId, + gamepacket_GMSV->ReliableMessageId, + gamepacket_GMSV->AckMessageId, + gamepacket_GMSV->AckReliableMessageId + )); + + E_REList* relist = E_REList::CreateAndAddRef( RE_LIST_MAXAVAIL ); + relist->AppendList( (RE_Header*)&gamepacket_GMSV->RE_List ); + relist->Dump(); + relist->Release(); + + LOGOUT(( "-------------------------------------------------------------------------------" )); + } +#endif // INTERNAL_VERSION + + // do not process older messages ( I already sent an ACK ) + //FIXME: what do we do with reliable remote events here ? -> SOLUTION: we remove them as well, as they must already have been retransmitted in later packets + if ( I_ACK_MessageId >= gamepacket_GMSV->MessageId ) { + MSGOUT( "NET_Stream::InPacket(): ignoring msgid %d ( already got %d )", gamepacket_GMSV->MessageId, I_ACK_MessageId ); + return FALSE; + } + + // ACK that we received this message + I_ACK_MessageId = max( I_ACK_MessageId, gamepacket_GMSV->MessageId ); + + // get the ACK from partner + YOU_ACK_MessageId = max( YOU_ACK_MessageId, gamepacket_GMSV->AckMessageId ); + + // if we receive a ACK for a message we didnt yet send, we discard the packet + if ( YOU_ACK_MessageId >= Out_MessageId ) { + //ASSERT( FALSE ); + DBGTXT( MSGOUT( "NET_Stream::InPacket(): receiving ACK for unsent packet %d ( next outgoing is %d )", YOU_ACK_MessageId, Out_MessageId - 1 ); ); + return FALSE; + } + + // handle reliable versions ? + if ( m_EnableReliable ) { + + if ( gamepacket_GMSV->ReliableMessageId != NO_RELIABLE ) { + // ACK that we received this message + I_ACK_ReliableMessageId = max( I_ACK_ReliableMessageId, gamepacket_GMSV->ReliableMessageId ); + } + // get the ACK from partner + YOU_ACK_ReliableMessageId = max( YOU_ACK_ReliableMessageId, gamepacket_GMSV->AckReliableMessageId ); + + // be sure the partner doesnt spoof us ( ACK for msg that was not yet sent ! ) + //ASSERT( YOU_ACK_ReliableMessageId < Out_ReliableMessageId ); + YOU_ACK_ReliableMessageId = min( YOU_ACK_ReliableMessageId, Out_ReliableMessageId ); + + } else { + + // check whether we got a reliable message + if ( gamepacket_GMSV->ReliableMessageId != NO_RELIABLE ) { + ASSERT( FALSE ); + MSGOUT( "NET_Stream::InPacket(): received reliable message on UNRELIABLE stream !" ); + return FALSE; + } + } + + return TRUE; +} + + +// fill in fields in outgoing packet -------------------------------------- +// +void NET_Stream::OutPacket( NetPacket_GMSV* gamepacket_GMSV, int reliable /*= FALSE*/ ) +{ + ASSERT( gamepacket_GMSV != NULL ); + + gamepacket_GMSV->MessageId = Out_MessageId; + gamepacket_GMSV->ReliableMessageId = NO_RELIABLE; + gamepacket_GMSV->AckMessageId = I_ACK_MessageId; + gamepacket_GMSV->AckReliableMessageId = NO_RELIABLE; + + // handle reliable message # + if( reliable ) { + + // check whether reliable transfer is disabled + if ( !m_EnableReliable ) { + ASSERT( FALSE ); + MSGOUT( "NET_Stream::OutPacket(): trying to send reliable message on UNRELIABLE stream !" ); + } else { + gamepacket_GMSV->ReliableMessageId = Out_ReliableMessageId; + + // remember the message id that carried the reliable + m_MessageId_ReliableWasSent = Out_MessageId; + + // increase reliable message counter ( wrap-around ) + Out_ReliableMessageId = ( Out_ReliableMessageId == UINT_MAX ) ? 1 : Out_ReliableMessageId + 1; + } + } + + // ACK reliable + if ( m_EnableReliable ) { + gamepacket_GMSV->AckReliableMessageId = I_ACK_ReliableMessageId; + } + + // increase message counter ( wrap-around, check for MSGID_DATAGRAM ) + Out_MessageId = ( Out_MessageId == ( MSGID_DATAGRAM - 1 ) ) ? 1 : Out_MessageId + 1; + +#ifdef INTERNAL_VERSION + // debug output + if ( AUX_DEBUG_NETSTREAM_DUMP & 2 ) { + + LOGOUT(( "-------------------------------------------------------------------------------" )); + LOGOUT(( "(%2d), OutPacket to %2d, MessageId %5d, ReliableMessageId %5d, AckMessageId %5d, AckReliableMessageId %5d", + m_nSenderID, + m_nPeerID, + gamepacket_GMSV->MessageId, + gamepacket_GMSV->ReliableMessageId, + gamepacket_GMSV->AckMessageId, + gamepacket_GMSV->AckReliableMessageId + )); + + E_REList* relist = E_REList::CreateAndAddRef( RE_LIST_MAXAVAIL ); + relist->AppendList( (RE_Header*)&gamepacket_GMSV->RE_List ); + relist->Dump(); + relist->Release(); + LOGOUT(( "------------------------------------------" )); + } +#endif // INTERNAL_VERSION +} + + +// check whether to filter out a duplicate packet ----------------------------- +// +int NET_Stream::FilterPacketDuplicate( int messageid ) +{ + // init message id for last packet with sane value + // if this is the first packet from this player + if ( I_ACK_MessageId == 0 ) { + //FIXME: cbx - 2002/02/28 - we must init message_id_history to 0 if + // this is the FIRST packet not the SECOND. This caused a very + // subtle bug, preventing recorded demos from playing a second time + // in one session, mainly because PKTP_CONNECT packets are droppped + // as they have a SendPlayerId of 0, which did not have a + // cleared message_id_history at the first packet ! + if ( messageid >= 1 ) { + int previd = messageid - 1; + I_ACK_MessageId = previd; + for ( int hid = 0; hid < MSGID_HISTORY_SIZE; hid++ ) { + message_id_history[ hid ] = previd; + } + } + } + + // discard duplicate packets remembered in history buffer + int hid =0; + for ( hid = MSGID_HISTORY_SIZE - 1; hid >= 0; hid-- ) { + if ( message_id_history[ hid ] == messageid ) { + // filter out packet + return TRUE; + } + } + + // update history buffer + for ( hid = 1; hid < MSGID_HISTORY_SIZE; hid++ ) { + message_id_history[ hid - 1 ] = message_id_history[ hid ]; + } + message_id_history[ MSGID_HISTORY_SIZE - 1 ] = messageid; + + // calculate packet loss using message id comparisons + int previd = I_ACK_MessageId; + int numlost = messageid - ( previd + 1 ); + + // log missing packets + LogPacketLossStats( numlost, FALSE, TRUE ); + + // got this packet + LogPacketLossStats( 1, TRUE, TRUE ); + + // take this packet + return FALSE; +} + + +// log packet loss statistics ------------------------------------------------- +// +void NET_Stream::LogPacketLossStats( int numpackets, int packetok, int incoming ) +{ + //FIXME: STATS() + + // negative values might happen if we used + // mismatched packet ids to calc numpackets + if ( numpackets < 1 ) { + return; + } + + // guard against more lost packets than we can display + if ( numpackets > PACKET_LOSS_METER_LENGTH ) { + numpackets = PACKET_LOSS_METER_LENGTH; + } + + // select receive/transmit graph + char *graph = incoming ? packet_graph_recv : packet_graph_send; + + // scroll packet graph leftward + int pos = 0; + for ( pos = 0; pos < PACKET_LOSS_METER_LENGTH - numpackets; pos++ ) { + graph[ pos ] = graph[ pos + numpackets ]; + } + + // insert new values at the right + for ( ; pos < PACKET_LOSS_METER_LENGTH; pos++ ) { + graph[ pos ] = ( packetok ? 0 : 1 ); + } +} + |
