Skip to content

Commit d7ac628

Browse files
committed
Refactor how we populate the inline "stats".
To send a data packet, first we ask the transport-specific code to prepare a context, and figure out how much space it needs to reserve. Then it passes that into the generic SNP code, which lays out the packet. Then SNP calls the transport code to actually send the encrypted data, providing the context that the transport layer originally provided. This simplified a bunch of stuff and allowed a bunch of cleanup (especially in the code for relayed connections -- not visible here). Most importantly (and the reason I realized I needed to refactor this), it facalitated fixing a serious P2P transport bug, that would have been awkward to fix without the refactor.
1 parent e6fbe46 commit d7ac628

8 files changed

+253
-232
lines changed

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
// memdbgon must be the last include file in a .cpp file!!!
1212
#include "tier0/memdbgon.h"
1313

14+
#ifdef __GNUC__
15+
// error: assuming signed overflow does not occur when assuming that (X + c) < X is always false [-Werror=strict-overflow]
16+
// current steamrt:scout gcc "g++ (SteamRT 4.8.4-1ubuntu15~12.04+steamrt1.2+srt1) 4.8.4" requires this at the top due to optimizations
17+
#pragma GCC diagnostic ignored "-Wstrict-overflow"
18+
#endif
19+
1420
// Put everything in a namespace, so we don't violate the one definition rule
1521
namespace SteamNetworkingSocketsLib {
1622

@@ -1908,12 +1914,19 @@ void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( Steam
19081914
// V
19091915
case k_ESteamNetworkingConnectionState_Connected:
19101916
{
1911-
SteamNetworkingMicroseconds usecNextThinkSNP = SNP_ThinkSendState( usecNow );
1912-
AssertMsg1( usecNextThinkSNP > usecNow, "SNP next think time must be in in the future. It's %lldusec in the past", (long long)( usecNow - usecNextThinkSNP ) );
1917+
if ( BCanSendEndToEndData() )
1918+
{
1919+
SteamNetworkingMicroseconds usecNextThinkSNP = SNP_ThinkSendState( usecNow );
1920+
AssertMsg1( usecNextThinkSNP > usecNow, "SNP next think time must be in in the future. It's %lldusec in the past", (long long)( usecNow - usecNextThinkSNP ) );
19131921

1914-
// Set a pretty tight tolerance if SNP wants to wake up at a certain time.
1915-
if ( usecNextThinkSNP < k_nThinkTime_Never )
1916-
UpdateMinThinkTime( usecNextThinkSNP, +1 );
1922+
// Set a pretty tight tolerance if SNP wants to wake up at a certain time.
1923+
if ( usecNextThinkSNP < k_nThinkTime_Never )
1924+
UpdateMinThinkTime( usecNextThinkSNP, +1 );
1925+
}
1926+
else
1927+
{
1928+
UpdateMinThinkTime( usecNow + 20*1000, +5 );
1929+
}
19171930
} break;
19181931
}
19191932

@@ -2247,7 +2260,13 @@ EResult CSteamNetworkConnectionPipe::APIAcceptConnection()
22472260
return k_EResultFail;
22482261
}
22492262

2250-
int CSteamNetworkConnectionPipe::SendEncryptedDataChunk( const void *pChunk, int cbChunk, SteamNetworkingMicroseconds usecNow, void *pConnectionContext )
2263+
bool CSteamNetworkConnectionPipe::SendDataPacket( SteamNetworkingMicroseconds usecNow )
2264+
{
2265+
AssertMsg( false, "CSteamNetworkConnectionPipe connections shouldn't try to send 'packets'!" );
2266+
return false;
2267+
}
2268+
2269+
int CSteamNetworkConnectionPipe::SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx )
22512270
{
22522271
AssertMsg( false, "CSteamNetworkConnectionPipe connections shouldn't try to send 'packets'!" );
22532272
return -1;

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.h

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,68 @@ struct RemoteConnectionKey_t
7373
}
7474
};
7575

76+
/// Base class for connection-type-specific context structure
77+
struct SendPacketContext_t
78+
{
79+
inline SendPacketContext_t( SteamNetworkingMicroseconds usecNow ) : m_usecNow( usecNow ) {}
80+
const SteamNetworkingMicroseconds m_usecNow;
81+
int m_cbMaxEncryptedPayload;
82+
};
83+
84+
template<typename TStatsMsg>
85+
struct SendPacketContext : SendPacketContext_t
86+
{
87+
inline SendPacketContext( SteamNetworkingMicroseconds usecNow ) : SendPacketContext_t( usecNow ) {}
88+
89+
uint32 m_nFlags; // Message flags that we need to set.
90+
TStatsMsg msg; // Type-specific stats message
91+
int m_cbMsgSize; // Size of message
92+
int m_cbTotalSize; // Size needed in the header, including the serialized size field
93+
94+
void SlamFlagsAndCalcSize()
95+
{
96+
SetStatsMsgFlagsIfNotImplied( msg, m_nFlags );
97+
m_cbTotalSize = m_cbMsgSize = msg.ByteSize();
98+
if ( m_cbMsgSize > 0 )
99+
m_cbTotalSize += VarIntSerializedSize( (uint32)m_cbMsgSize );
100+
}
101+
102+
bool Serialize( byte *&p )
103+
{
104+
if ( m_cbTotalSize <= 0 )
105+
return false;
106+
107+
// Serialize the stats size, var-int encoded
108+
byte *pOut = SerializeVarInt( p, uint32( m_cbMsgSize ) );
109+
110+
// Serialize the actual message
111+
pOut = msg.SerializeWithCachedSizesToArray( pOut );
112+
113+
// Make sure we wrote the number of bytes we expected
114+
if ( pOut != p + m_cbTotalSize )
115+
{
116+
// ABORT!
117+
AssertMsg( false, "Size mismatch after serializing inline stats blob" );
118+
return false;
119+
}
120+
121+
// Advance pointer
122+
p = pOut;
123+
return true;
124+
}
125+
126+
void CalcMaxEncryptedPayloadSize( size_t cbHdrReserve )
127+
{
128+
Assert( m_cbTotalSize >= 0 );
129+
m_cbMaxEncryptedPayload = k_cbSteamNetworkingSocketsMaxUDPMsgLen - (int)cbHdrReserve - m_cbTotalSize;
130+
if ( m_cbMaxEncryptedPayload < 512 )
131+
{
132+
AssertMsg2( m_cbMaxEncryptedPayload < 512, "%s is really big (%d bytes)!", msg.GetTypeName().c_str(), m_cbTotalSize );
133+
}
134+
}
135+
136+
};
137+
76138
/////////////////////////////////////////////////////////////////////////////
77139
//
78140
// Message storage implementation
@@ -379,8 +441,14 @@ class CSteamNetworkConnectionBase : protected IThinker
379441
return m_receiverState.TimeWhenFlushAcks() < INT64_MAX || SNP_TimeWhenWantToSendNextPacket() < INT64_MAX;
380442
}
381443

382-
/// Send a data packet now, even if we don't have the bandwidth available
383-
int SNP_SendPacket( SteamNetworkingMicroseconds usecNow, int cbMaxEncryptedPayload, void *pConnectionData );
444+
/// Called by SNP pacing layer, when it has some data to send and there is bandwidth available.
445+
/// The derived class should setup a context, reserving the space it needs, and then call SNP_SendPacket.
446+
/// Returns true if a packet was sent successfuly, false if there was a problem.
447+
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) = 0;
448+
449+
/// Send a data packet now, even if we don't have the bandwidth available. Returns true if a packet was
450+
/// sent successfully, false if there was a problem. This will call SendEncryptedDataChunk to do the work
451+
bool SNP_SendPacket( SendPacketContext_t &ctx );
384452

385453
// Steam memory accounting
386454
#ifdef DBGFLAG_VALIDATE
@@ -454,7 +522,7 @@ class CSteamNetworkConnectionBase : protected IThinker
454522
/// pConnectionContext is whatever the connection later passed
455523
/// to SNP_SendPacket, if the connection initiated the sending
456524
/// of the packet
457-
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SteamNetworkingMicroseconds usecNow, void *pConnectionContext ) = 0;
525+
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) = 0;
458526

459527
/// Called when we receive a complete message. Should allocate a message object and put it into the proper queues
460528
void ReceivedMessage( const void *pData, int cbData, int64 nMsgNum, SteamNetworkingMicroseconds usecNow );
@@ -632,7 +700,8 @@ class CSteamNetworkConnectionPipe : public CSteamNetworkConnectionBase
632700
virtual void SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow ) OVERRIDE;
633701
virtual void SendEndToEndPing( bool bUrgent, SteamNetworkingMicroseconds usecNow ) OVERRIDE;
634702
virtual EResult APIAcceptConnection() OVERRIDE;
635-
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SteamNetworkingMicroseconds usecNow, void *pConnectionContext ) OVERRIDE;
703+
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) OVERRIDE;
704+
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) OVERRIDE;
636705
virtual EResult _APISendMessageToConnection( const void *pData, uint32 cbData, int nSendFlags ) OVERRIDE;
637706
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState ) OVERRIDE;
638707
virtual void PostConnectionStateChangedCallback( ESteamNetworkingConnectionState eOldAPIState, ESteamNetworkingConnectionState eNewAPIState ) OVERRIDE;

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,20 +1299,18 @@ inline bool HasOverlappingRange( const SNPRange_t &range, const std::map<SNPRang
12991299
return false;
13001300
}
13011301

1302-
int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds usecNow, int cbMaxEncryptedPayload, void *pConnectionData )
1302+
bool CSteamNetworkConnectionBase::SNP_SendPacket( SendPacketContext_t &ctx )
13031303
{
1304-
// If we aren't being specifically asked to send a packet, and we don't have anything to send,
1305-
// then don't send right now.
1306-
if ( pConnectionData == nullptr && usecNow < m_receiverState.TimeWhenFlushAcks() && usecNow < SNP_TimeWhenWantToSendNextPacket() )
1307-
return 0;
1308-
13091304
// Make sure we have initialized the connection
13101305
Assert( BStateIsConnectedForWirePurposes() );
13111306
Assert( !m_senderState.m_mapInFlightPacketsByPktNum.empty() );
13121307

1308+
SteamNetworkingMicroseconds usecNow = ctx.m_usecNow;
1309+
13131310
// Get max size of plaintext we could send.
13141311
// AES-GCM has a fixed size overhead, for the tag.
1315-
int cbMaxPlaintextPayload = std::max( 0, cbMaxEncryptedPayload-k_cbSteamNetwokingSocketsEncrytionTagSize );
1312+
int cbMaxPlaintextPayload = std::max( 0, ctx.m_cbMaxEncryptedPayload-k_cbSteamNetwokingSocketsEncrytionTagSize );
1313+
cbMaxPlaintextPayload = std::min( cbMaxPlaintextPayload, k_cbSteamNetworkingSocketsMaxPlaintextPayloadSend );
13161314

13171315
uint8 payload[ k_cbSteamNetworkingSocketsMaxPlaintextPayloadSend ];
13181316
uint8 *pPayloadEnd = payload + cbMaxPlaintextPayload;
@@ -1326,7 +1324,7 @@ int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds use
13261324
// Stop waiting frame
13271325
pPayloadPtr = SNP_SerializeStopWaitingFrame( pPayloadPtr, pPayloadEnd, usecNow );
13281326
if ( pPayloadPtr == nullptr )
1329-
return -1;
1327+
return false;
13301328

13311329
// Get list of ack blocks we might want to serialize, and which
13321330
// of those acks we really want to flush out right now.
@@ -1373,11 +1371,8 @@ int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds use
13731371
}
13741372

13751373
// Check if we don't actually have bandwidth to send data, then don't.
1376-
// (This means that acks or other responsibilities are choking the pipe
1377-
// and should basically never happen in ordinary circumstances!)
13781374
if ( m_senderState.m_flTokenBucket < 0.0 )
13791375
{
1380-
SpewWarningRateLimited( usecNow, "[%s] Exceeding rate limit just sending acks / stats! Not sending any data! (Ack blocks=%d prio=%d)", GetDescription(), ackHelper.m_nBlocks, ackHelper.m_nBlocksNeedToAck );
13811376

13821377
// Serialize some acks, if we want to
13831378
if ( cbReserveForAcks > 0 )
@@ -1386,7 +1381,7 @@ int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds use
13861381
// not just the bare minimum.
13871382
pPayloadPtr = SNP_SerializeAckBlocks( ackHelper, pPayloadPtr, pPayloadEnd, usecNow );
13881383
if ( pPayloadPtr == nullptr )
1389-
return -1; // bug! Abort
1384+
return false; // bug! Abort
13901385

13911386
// We don't need to serialize any more acks
13921387
cbReserveForAcks = 0;
@@ -1597,7 +1592,7 @@ int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds use
15971592

15981593
uint8 *pAfterAcks = SNP_SerializeAckBlocks( ackHelper, pPayloadPtr, pAckEnd, usecNow );
15991594
if ( pAfterAcks == nullptr )
1600-
return -1; // bug! Abort
1595+
return false; // bug! Abort
16011596

16021597
int cbAckBytesWritten = pAfterAcks - pPayloadPtr;
16031598
if ( cbAckBytesWritten > cbReserveForAcks )
@@ -1760,9 +1755,9 @@ int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds use
17601755
//SpewMsg( "Send encrypt IV %llu + %02x%02x%02x%02x, key %02x%02x%02x%02x\n", *(uint64 *)&m_cryptIVSend.m_buf, m_cryptIVSend.m_buf[8], m_cryptIVSend.m_buf[9], m_cryptIVSend.m_buf[10], m_cryptIVSend.m_buf[11], m_cryptKeySend.m_buf[0], m_cryptKeySend.m_buf[1], m_cryptKeySend.m_buf[2], m_cryptKeySend.m_buf[3] );
17611756

17621757
// Connection-specific method to send it
1763-
int nBytesSent = SendEncryptedDataChunk( arEncryptedChunk, cbEncrypted, usecNow, pConnectionData );
1758+
int nBytesSent = SendEncryptedDataChunk( arEncryptedChunk, cbEncrypted, ctx );
17641759
if ( nBytesSent <= 0 )
1765-
return -1;
1760+
return false;
17661761

17671762
// We sent a packet. Track it
17681763
auto pairInsertResult = m_senderState.m_mapInFlightPacketsByPktNum.insert( pairInsert );
@@ -1782,7 +1777,7 @@ int CSteamNetworkConnectionBase::SNP_SendPacket( SteamNetworkingMicroseconds use
17821777

17831778
// We spent some tokens
17841779
m_senderState.m_flTokenBucket -= (float)nBytesSent;
1785-
return nBytesSent;
1780+
return true;
17861781
}
17871782

17881783
void CSteamNetworkConnectionBase::SNP_GatherAckBlocks( SNPAckSerializerHelper &helper, SteamNetworkingMicroseconds usecNow )
@@ -2950,17 +2945,8 @@ SteamNetworkingMicroseconds CSteamNetworkConnectionBase::SNP_ThinkSendState( Ste
29502945
return usecNow + 1000;
29512946
}
29522947

2953-
int nBytesSent = SNP_SendPacket( usecNow, k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend, nullptr );
2954-
if ( nBytesSent < 0 )
2955-
{
2956-
// Problem sending packet. Nuke token bucket, but request
2957-
// a wakeup relatively quick to check on our state again
2958-
m_senderState.m_flTokenBucket = m_senderState.m_n_x * -0.001f;
2959-
return usecNow + 2000;
2960-
}
2961-
2962-
// Nothing to send at this time
2963-
if ( nBytesSent == 0 )
2948+
// Check if we have anything to send.
2949+
if ( usecNow < m_receiverState.TimeWhenFlushAcks() && usecNow < SNP_TimeWhenWantToSendNextPacket() )
29642950
{
29652951

29662952
// We've sent everything we want to send. Limit our reserve to a
@@ -2970,6 +2956,15 @@ SteamNetworkingMicroseconds CSteamNetworkConnectionBase::SNP_ThinkSendState( Ste
29702956
break;
29712957
}
29722958

2959+
// Send the next data packet.
2960+
if ( !SendDataPacket( usecNow ) )
2961+
{
2962+
// Problem sending packet. Nuke token bucket, but request
2963+
// a wakeup relatively quick to check on our state again
2964+
m_senderState.m_flTokenBucket = m_senderState.m_n_x * -0.001f;
2965+
return usecNow + 2000;
2966+
}
2967+
29732968
// We spent some tokens, do we have any left?
29742969
if ( m_senderState.m_flTokenBucket < 0.0f )
29752970
break;

0 commit comments

Comments
 (0)