Refactor to split connection from transport.

The connection will be responsible for end to end stuff including encryption,
message fragmentation and reliability, etc.  Transports deliver datagrams.  A
transport is always associated with a single connection, but a connection may
have more than one transport, and may switch between transports over the course
of the connection lifetime.

The goal here is to have conenction types that can switch transport.
Specifically, we need to support a connection that can detect when peers
are on the same LAN and use ordinary UDP in that case, attempt NAT piercing
using STUN, and if that fails, then relay (either using TURN or SDR).

There is more work to be done here.  For example, each transport almost
certainly has its own ping time and quality characeristics, so we probably ought
to track some stats there.  (But our stats situation is already getting pretty
crazy, so maybe just track ping seperately).

This change also includes some changes relevant for Steam Remote Play
streaming, which is going to use this protocol for relayed connections.
(And eventualy, hopefully, for all connections.)  For example, steam remote
play needs really high bandwidth and packet rate, and the tolerance for
sending the timing data for jitter tracking was too tight.  I also need to
pass a "certificate" to a subprocess in a single blob, which actually includes
the private key.
This commit is contained in:
Fletcher Dunn
2019-09-17 11:34:23 -07:00
parent 0f423938e1
commit 1f29e34e0f
12 changed files with 695 additions and 439 deletions
+1 -1
View File
@@ -383,7 +383,7 @@ extern "C" {
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets();
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamGameServerNetworkingSockets();
#elif defined( STEAMNETWORKINGSOCKETS_OPENSOURCE )
#elif defined( STEAMNETWORKINGSOCKETS_OPENSOURCE ) || defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT )
// Opensource GameNetworkingSockets
STEAMNETWORKINGSOCKETS_INTERFACE ISteamNetworkingSockets *SteamNetworkingSockets();
+7 -3
View File
@@ -55,9 +55,6 @@ public:
// If you pass NULL, the number of bytes required is returned.
virtual uint32 GetRawData( void *pData ) const = 0;
// Get raw data as a std::string
bool GetRawDataAsStdString( std::string *pResult ) const;
// Set raw data. Returns true on success. Regardless of the outcome,
// your buffer will be wiped.
bool SetRawDataAndWipeInput( void *pData, size_t cbData );
@@ -70,6 +67,13 @@ public:
bool SetFromHexEncodedString( const char *pchEncodedKey );
bool SetFromBase64EncodedString( const char *pchEncodedKey );
// Get raw data as a std::string
bool GetRawDataAsStdString( std::string *pResult ) const;
// Set raw data from a std::string. (Useful for dealing with protobuf)
// NOTE: DOES NOT WIPE THE INPUT
bool SetRawDataFromStdString( const std::string &s ) { return SetRawDataWithoutWipingInput( s.c_str(), s.length() ); }
// Load from some sort of formatted buffer. (Not the raw binary key data.)
virtual bool LoadFromAndWipeBuffer( void *pBuffer, size_t cBytes );
@@ -89,6 +89,11 @@ message CMsgSteamDatagramCertificateSigned
/// Signature over the certificate, using the key identified
/// by ca_key_id.
optional bytes ca_signature = 6;
/// In a few instances, we want to use the same message to include the private
/// key and the corresponding cert. Most of the time this field should not be
/// present!
optional bytes private_key_data = 1;
}
// A request by a client to a CA to issue a cert.
@@ -247,6 +247,7 @@ CSteamNetworkingSockets::CSteamNetworkingSockets( CSteamNetworkingUtils *pSteamN
, m_pSteamNetworkingUtils( pSteamNetworkingUtils )
{
m_connectionConfig.Init( nullptr );
m_identity.Clear();
}
CSteamNetworkingSockets::~CSteamNetworkingSockets()
@@ -286,7 +287,7 @@ void CSteamNetworkingSockets::KillConnections()
CSteamNetworkConnectionBase *pConn = g_mapConnections[idx];
if ( pConn->m_pSteamNetworkingSocketsInterface == this )
{
pConn->Destroy();
pConn->ConnectionDestroySelfNow();
Assert( !g_mapConnections.IsValidIndex( idx ) );
}
}
@@ -441,18 +442,51 @@ bool CSteamNetworkingSockets::SetCertificate( const void *pCertificate, int cbCe
return false;
}
// Make sure the cert actually matches our public key.
if ( !m_keyPrivateKey.IsValid() )
{
// WAT
V_strcpy_safe( errMsg, "Cannot set cert. No private key?" );
return false;
}
// We currently only support one key type
if ( msgCert.key_type() != CMsgSteamDatagramCertificate_EKeyType_ED25519 || msgCert.key_data().size() != 32 )
{
V_strcpy_safe( errMsg, "Cert has invalid public key" );
return false;
}
// Does cert contain a private key?
if ( msgCertSigned.has_private_key_data() )
{
// The degree to which the key is actually "private" is not
// really known to us. However there are some use cases where
// we will accept a cert
const std::string &private_key_data = msgCertSigned.private_key_data();
if ( m_keyPrivateKey.IsValid() )
{
// We already chose a private key, so the cert must match.
// For the most common use cases, we choose a private
// key and it never leaves the current process.
if ( m_keyPrivateKey.GetRawDataSize() != private_key_data.length()
|| memcmp( m_keyPrivateKey.GetRawDataPtr(), private_key_data.c_str(), private_key_data.length() ) != 0 )
{
V_strcpy_safe( errMsg, "Private key mismatch" );
return false;
}
}
else
{
// We haven't chosen a private key yet, so we'll accept this one.
if ( !m_keyPrivateKey.SetRawDataFromStdString( private_key_data ) )
{
V_strcpy_safe( errMsg, "Invalid private key" );
return false;
}
}
}
else if ( !m_keyPrivateKey.IsValid() )
{
// WAT
V_strcpy_safe( errMsg, "Cannot set cert. No private key?" );
return false;
}
// Make sure the cert actually matches our public key.
if ( memcmp( msgCert.key_data().c_str(), m_keyPrivateKey.GetPublicKeyRawData(), 32 ) != 0 )
{
V_strcpy_safe( errMsg, "Cert public key does not match our private key" );
@@ -555,8 +589,7 @@ HSteamNetConnection CSteamNetworkingSockets::ConnectByIPAddress( const SteamNetw
if ( !pConn->BInitConnect( address, nOptions, pOptions, errMsg ) )
{
SpewError( "Cannot create IPv4 connection. %s", errMsg );
pConn->FreeResources();
delete pConn;
pConn->ConnectionDestroySelfNow();
return k_HSteamNetConnection_Invalid;
}
@@ -574,11 +607,10 @@ EResult CSteamNetworkingSockets::AcceptConnection( HSteamNetConnection hConn )
return k_EResultInvalidParam;
}
// Should only be called for connections accepted on listen socket.
// (E.g., not connections initiated locally.)
if ( pConn->m_pParentListenSocket == nullptr )
// Should only be called for connections initiated remotely
if ( !pConn->m_bConnectionInitiatedRemotely )
{
SpewError( "[%s] Should not be trying to acccept this connection, it was not received on a listen socket.", pConn->GetDescription() );
SpewError( "[%s] Should not be trying to acccept this connection, it was not initiated remotely.", pConn->GetDescription() );
return k_EResultInvalidParam;
}
@@ -706,7 +738,7 @@ bool CSteamNetworkingSockets::GetConnectionInfo( HSteamNetConnection hConn, Stea
if ( !pConn )
return false;
if ( pInfo )
pConn->PopulateConnectionInfo( *pInfo );
pConn->ConnectionPopulateInfo( *pInfo );
return true;
}
@@ -940,6 +972,8 @@ void CSteamNetworkingSockets::InternalQueueCallback( int nCallback, int cbCallba
//
/////////////////////////////////////////////////////////////////////////////
CSteamNetworkingUtils::~CSteamNetworkingUtils() {}
SteamNetworkingMicroseconds CSteamNetworkingUtils::GetLocalTimestamp()
{
return SteamNetworkingSockets_GetLocalTimestamp();
@@ -144,6 +144,7 @@ protected:
class CSteamNetworkingUtils : public IClientNetworkingUtils
{
public:
virtual ~CSteamNetworkingUtils();
virtual SteamNetworkingMicroseconds GetLocalTimestamp() override;
virtual void SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ) override;
@@ -326,7 +326,7 @@ void CSteamNetworkListenSocketBase::Destroy()
Assert( pChild->m_hSelfInParentListenSocketMap == h );
int n = m_mapChildConnections.Count();
pChild->Destroy();
pChild->ConnectionDestroySelfNow();
Assert( m_mapChildConnections.Count() == n-1 );
}
@@ -419,6 +419,7 @@ CSteamNetworkConnectionBase::CSteamNetworkConnectionBase( CSteamNetworkingSocket
memset( m_szAppName, 0, sizeof( m_szAppName ) );
memset( m_szDescription, 0, sizeof( m_szDescription ) );
m_bConnectionInitiatedRemotely = false;
m_pTransport = nullptr;
// Initialize configuration using parent interface for now.
m_connectionConfig.Init( &m_pSteamNetworkingSocketsInterface->m_connectionConfig );
@@ -433,7 +434,7 @@ CSteamNetworkConnectionBase::~CSteamNetworkConnectionBase()
Assert( m_pMessagesSession == nullptr );
}
void CSteamNetworkConnectionBase::Destroy()
void CSteamNetworkConnectionBase::ConnectionDestroySelfNow()
{
// Make sure all resources have been freed, etc
@@ -443,6 +444,19 @@ void CSteamNetworkConnectionBase::Destroy()
delete this;
}
void CConnectionTransport::TransportDestroySelfNow()
{
// Call virtual functions while we still can
TransportFreeResources();
// Self destruct NOW
delete this;
}
void CConnectionTransport::TransportFreeResources()
{
}
void CSteamNetworkConnectionBase::QueueDestroy()
{
FreeResources();
@@ -507,6 +521,18 @@ void CSteamNetworkConnectionBase::FreeResources()
// Clear it, since this function should be idempotent
m_unConnectionIDLocal = 0;
}
// Clean up our transport
DestroyTransport();
}
void CSteamNetworkConnectionBase::DestroyTransport()
{
if ( m_pTransport )
{
m_pTransport->TransportDestroySelfNow();
m_pTransport = nullptr;
}
}
bool CSteamNetworkConnectionBase::BInitConnection( SteamNetworkingMicroseconds usecNow, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
@@ -1152,11 +1178,23 @@ void CSteamNetworkConnectionBase::SetUserData( int64 nUserData )
}
}
void CSteamNetworkConnectionBase::PopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const
void CConnectionTransport::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
{
}
void CConnectionTransport::TransportPopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const
{
}
void CConnectionTransport::GetDetailedConnectionStatus( SteamNetworkingDetailedConnectionStatus &stats, SteamNetworkingMicroseconds usecNow )
{
}
void CSteamNetworkConnectionBase::ConnectionPopulateInfo( SteamNetConnectionInfo_t &info ) const
{
info.m_eState = CollapseConnectionStateToAPIState( m_eConnectionState );
info.m_hListenSocket = m_pParentListenSocket ? m_pParentListenSocket->m_hListenSocketSelf : k_HSteamListenSocket_Invalid;
NetAdrToSteamNetworkingIPAddr( info.m_addrRemote, m_netAdrRemote );
NetAdrToSteamNetworkingIPAddr( info.m_addrRemote, m_netAdrRemote ); // FIXME this is in a weird place
info.m_idPOPRemote = 0;
info.m_idPOPRelay = 0;
info.m_identityRemote = m_identityRemote;
@@ -1164,6 +1202,9 @@ void CSteamNetworkConnectionBase::PopulateConnectionInfo( SteamNetConnectionInfo
info.m_eEndReason = m_eEndReason;
V_strcpy_safe( info.m_szEndDebug, m_szEndDebug );
V_strcpy_safe( info.m_szConnectionDescription, m_szDescription );
if ( m_pTransport )
m_pTransport->TransportPopulateConnectionInfo( info );
}
void CSteamNetworkConnectionBase::APIGetQuickConnectionStatus( SteamNetworkingQuickConnectionStatus &stats )
@@ -1206,7 +1247,7 @@ void CSteamNetworkConnectionBase::APIGetQuickConnectionStatus( SteamNetworkingQu
void CSteamNetworkConnectionBase::APIGetDetailedConnectionStatus( SteamNetworkingDetailedConnectionStatus &stats, SteamNetworkingMicroseconds usecNow )
{
stats.Clear();
PopulateConnectionInfo( stats.m_info );
ConnectionPopulateInfo( stats.m_info );
// Copy end-to-end stats
m_statsEndToEnd.GetLinkStats( stats.m_statsEndToEnd, usecNow );
@@ -1314,7 +1355,12 @@ int64 CSteamNetworkConnectionBase::DecryptDataChunk( uint16 nWireSeqNum, int cbP
// Adjust the IV by the packet number
*(uint64 *)&m_cryptIVRecv.m_buf += LittleQWord( nFullSequenceNumber );
//SpewMsg( "Recv decrypt IV %llu + %02x%02x%02x%02x, key %02x%02x%02x%02x\n", *(uint64 *)&m_cryptIVRecv.m_buf, m_cryptIVRecv.m_buf[8], m_cryptIVRecv.m_buf[9], m_cryptIVRecv.m_buf[10], m_cryptIVRecv.m_buf[11], m_cryptKeyRecv.m_buf[0], m_cryptKeyRecv.m_buf[1], m_cryptKeyRecv.m_buf[2], m_cryptKeyRecv.m_buf[3] );
//SpewMsg( "Recv decrypt IV %llu + %02x%02x%02x%02x encrypted %d %02x%02x%02x%02x\n",
// *(uint64 *)&m_cryptIVRecv.m_buf,
// m_cryptIVRecv.m_buf[8], m_cryptIVRecv.m_buf[9], m_cryptIVRecv.m_buf[10], m_cryptIVRecv.m_buf[11],
// cbChunk,
// *((byte*)pChunk + 0), *((byte*)pChunk + 1), *((byte*)pChunk + 2), *((byte*)pChunk + 3)
//);
// Decrypt the chunk and check the auth tag
bool bDecryptOK = m_cryptContextRecv.Decrypt(
@@ -1462,70 +1508,6 @@ void CSteamNetworkConnectionBase::SetState( ESteamNetworkingConnectionState eNew
// Remember when we entered this state
m_usecWhenEnteredConnectionState = usecNow;
// Give derived classes get a chance to take action on state changes
ConnectionStateChanged( eOldState );
}
void CSteamNetworkConnectionBase::ReceivedMessage( const void *pData, int cbData, int64 nMsgNum, SteamNetworkingMicroseconds usecNow )
{
// // !TEST! Enable this during connection test to trap bogus messages earlier
// #if 1
// struct TestMsg
// {
// int64 m_nMsgNum;
// bool m_bReliable;
// int m_cbSize;
// uint8 m_data[ 20*1000 ];
// };
// const TestMsg *pTestMsg = (const TestMsg *)pData;
//
// // Size makes sense?
// Assert( sizeof(*pTestMsg) - sizeof(pTestMsg->m_data) + pTestMsg->m_cbSize == cbData );
// #endif
SpewType( m_connectionConfig.m_LogLevel_Message.Get(), "[%s] RecvMessage MsgNum=%lld sz=%d\n",
GetDescription(),
(long long)nMsgNum,
cbData );
// Special case for internal connections used by Messages interface
if ( m_pMessagesInterface )
{
// Are we still associated with our session?
if ( !m_pMessagesSession )
{
// How did we get here? We should be closed, and once closed,
// we should not receive any more messages
AssertMsg2( false, "Received message for connection %s associated with Messages interface, but no session. Connection state is %d", GetDescription(), (int)GetState() );
}
else if ( m_pMessagesSession->m_pConnection != this )
{
AssertMsg2( false, "Connection/session linkage bookkeeping bug! %s state %d", GetDescription(), (int)GetState() );
}
else
{
m_pMessagesSession->ReceivedMessage( pData, cbData, nMsgNum, usecNow );
}
return;
}
// Create a message
CSteamNetworkingMessage *pMsg = CSteamNetworkingMessage::New( this, cbData, nMsgNum, usecNow );
// Add to end of my queue.
pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_linksSameConnection, &m_queueRecvMessages );
// If we are an inbound, accepted connection, link into the listen socket's queue
if ( m_pParentListenSocket )
pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_linksSecondaryQueue, &m_pParentListenSocket->m_queueRecvMessages );
// Copy the data
memcpy( const_cast<void*>( pMsg->GetData() ), pData, cbData );
}
void CSteamNetworkConnectionBase::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
{
// Post a notification when certain state changes occur. Note that
// "internal" state changes, where the connection is effectively closed
// from the application's perspective, are not relevant
@@ -1580,10 +1562,7 @@ void CSteamNetworkConnectionBase::ConnectionStateChanged( ESteamNetworkingConnec
case k_ESteamNetworkingConnectionState_FinWait:
case k_ESteamNetworkingConnectionState_ClosedByPeer:
// Clear out any secret state, since we can't use it anymore anyway.
ClearCrypto();
// And let stats tracking system know that it shouldn't
// Let stats tracking system know that it shouldn't
// expect to be able to get stuff acked, etc
m_statsEndToEnd.SetDisconnected( true, m_usecWhenEnteredConnectionState );
@@ -1622,10 +1601,65 @@ void CSteamNetworkConnectionBase::ConnectionStateChanged( ESteamNetworkingConnec
}
}
void CSteamNetworkConnectionBase::ReceivedMessage( const void *pData, int cbData, int64 nMsgNum, SteamNetworkingMicroseconds usecNow )
{
// // !TEST! Enable this during connection test to trap bogus messages earlier
// struct TestMsg
// {
// int64 m_nMsgNum;
// bool m_bReliable;
// int m_cbSize;
// uint8 m_data[ 20*1000 ];
// };
// const TestMsg *pTestMsg = (const TestMsg *)pData;
//
// // Size makes sense?
// Assert( sizeof(*pTestMsg) - sizeof(pTestMsg->m_data) + pTestMsg->m_cbSize == cbData );
SpewType( m_connectionConfig.m_LogLevel_Message.Get(), "[%s] RecvMessage MsgNum=%lld sz=%d\n",
GetDescription(),
(long long)nMsgNum,
cbData );
// Special case for internal connections used by Messages interface
if ( m_pMessagesInterface )
{
// Are we still associated with our session?
if ( !m_pMessagesSession )
{
// How did we get here? We should be closed, and once closed,
// we should not receive any more messages
AssertMsg2( false, "Received message for connection %s associated with Messages interface, but no session. Connection state is %d", GetDescription(), (int)GetState() );
}
else if ( m_pMessagesSession->m_pConnection != this )
{
AssertMsg2( false, "Connection/session linkage bookkeeping bug! %s state %d", GetDescription(), (int)GetState() );
}
else
{
m_pMessagesSession->ReceivedMessage( pData, cbData, nMsgNum, usecNow );
}
return;
}
// Create a message
CSteamNetworkingMessage *pMsg = CSteamNetworkingMessage::New( this, cbData, nMsgNum, usecNow );
// Add to end of my queue.
pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_linksSameConnection, &m_queueRecvMessages );
// If we are an inbound, accepted connection, link into the listen socket's queue
if ( m_pParentListenSocket )
pMsg->LinkToQueueTail( &CSteamNetworkingMessage::m_linksSecondaryQueue, &m_pParentListenSocket->m_queueRecvMessages );
// Copy the data
memcpy( const_cast<void*>( pMsg->GetData() ), pData, cbData );
}
void CSteamNetworkConnectionBase::PostConnectionStateChangedCallback( ESteamNetworkingConnectionState eOldAPIState, ESteamNetworkingConnectionState eNewAPIState )
{
SteamNetConnectionStatusChangedCallback_t c;
PopulateConnectionInfo( c.m_info );
ConnectionPopulateInfo( c.m_info );
c.m_eOldState = eOldAPIState;
c.m_hConn = m_hConnectionSelf;
m_pSteamNetworkingSocketsInterface->QueueCallback( c );
@@ -1931,12 +1965,12 @@ void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( Steam
// Time to try to send an end-to-end connection? If we cannot send packets now, then we
// really ought to be called again if something changes, but just in case we don't, set a
// reasonable polling interval.
if ( BCanSendEndToEndConnectRequest() )
if ( m_pTransport && m_pTransport->BCanSendEndToEndConnectRequest() )
{
usecRetry = m_usecWhenSentConnectRequest + k_usecConnectRetryInterval;
if ( usecNow >= usecRetry )
{
SendEndToEndConnectRequest( usecNow ); // don't return true from within BCanSendEndToEndPackets if you can't do this!
m_pTransport->SendEndToEndConnectRequest( usecNow ); // don't return true from within BCanSendEndToEndPackets if you can't do this!
m_usecWhenSentConnectRequest = usecNow;
usecRetry = m_usecWhenSentConnectRequest + k_usecConnectRetryInterval;
}
@@ -1962,7 +1996,7 @@ void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( Steam
// V
case k_ESteamNetworkingConnectionState_Connected:
{
if ( BCanSendEndToEndData() )
if ( m_pTransport && m_pTransport->BCanSendEndToEndData() )
{
SteamNetworkingMicroseconds usecNextThinkSNP = SNP_ThinkSendState( usecNow );
AssertMsg1( usecNextThinkSNP > usecNow, "SNP next think time must be in in the future. It's %lldusec in the past", (long long)( usecNow - usecNextThinkSNP ) );
@@ -1989,7 +2023,7 @@ void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( Steam
AssertMsg2( !m_statsEndToEnd.IsDisconnected(), "[%s] stats disconnected, but in state %d?", GetDescription(), (int)GetState() );
// Not able to send end-to-end data?
bool bCanSendEndToEnd = BCanSendEndToEndData();
bool bCanSendEndToEnd = m_pTransport && m_pTransport->BCanSendEndToEndData();
// Mark us as "timing out" if we are not able to send end-to-end data
if ( !bCanSendEndToEnd && m_statsEndToEnd.m_usecWhenTimeoutStarted == 0 )
@@ -2054,7 +2088,7 @@ void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( Steam
else
SpewMsg( "[%s] %d reply timeouts, last recv %.1fms ago. Sending keepalive.\n", GetDescription(), m_statsEndToEnd.m_nReplyTimeoutsSinceLastRecv, ( usecNow - m_statsEndToEnd.m_usecTimeLastRecv ) * 1e-3 );
Assert( m_statsEndToEnd.BNeedToSendPingImmediate( usecNow ) ); // Make sure logic matches
SendEndToEndStatsMsg( k_EStatsReplyRequest_Immediate, usecNow, "E2ETimingOutKeepalive" );
m_pTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_Immediate, usecNow, "E2ETimingOutKeepalive" );
AssertMsg( !m_statsEndToEnd.BNeedToSendPingImmediate( usecNow ), "SendEndToEndStatsMsg didn't do its job!" );
Assert( m_statsEndToEnd.m_usecInFlightReplyTimeout > 0 );
}
@@ -2083,7 +2117,7 @@ void CSteamNetworkConnectionBase::CheckConnectionStateAndSetNextThinkTime( Steam
if ( bCanSendEndToEnd )
{
Assert( m_statsEndToEnd.BNeedToSendKeepalive( usecNow ) ); // Make sure logic matches
SendEndToEndStatsMsg( k_EStatsReplyRequest_DelayedOK, usecNow, "E2EKeepalive" );
m_pTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_DelayedOK, usecNow, "E2EKeepalive" );
AssertMsg( !m_statsEndToEnd.BNeedToSendKeepalive( usecNow ), "SendEndToEndStatsMsg didn't do its job!" );
}
else
@@ -2269,6 +2303,7 @@ failed:
CSteamNetworkConnectionPipe::CSteamNetworkConnectionPipe( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface, const SteamNetworkingIdentity &identity )
: CSteamNetworkConnectionBase( pSteamNetworkingSocketsInterface )
, CConnectionTransport( *static_cast<CSteamNetworkConnectionBase*>( this ) ) // connection and transport object are the same
, m_pPartner( nullptr )
{
m_identityLocal = identity;
@@ -2408,7 +2443,7 @@ int CSteamNetworkConnectionPipe::SendEncryptedDataChunk( const void *pChunk, int
void CSteamNetworkConnectionPipe::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
{
CSteamNetworkConnectionBase::ConnectionStateChanged( eOldState );
CConnectionTransport::ConnectionStateChanged( eOldState );
switch ( GetState() )
{
@@ -2457,4 +2492,11 @@ void CSteamNetworkConnectionPipe::PostConnectionStateChangedCallback( ESteamNetw
CSteamNetworkConnectionBase::PostConnectionStateChangedCallback( eOldAPIState, eNewAPIState );
}
void CSteamNetworkConnectionPipe::DestroyTransport()
{
// Using the same object for connection and transport
TransportFreeResources();
m_pTransport = nullptr;
}
} // namespace SteamNetworkingSocketsLib
@@ -35,6 +35,7 @@ class CSteamNetworkingSockets;
class CSteamNetworkingMessages;
class CSteamNetworkConnectionBase;
class CSharedSocket;
class CConnectionTransport;
struct SteamNetworkingMessageQueue;
struct SNPAckSerializerHelper;
struct CertAuthScope;
@@ -141,6 +142,9 @@ struct SendPacketContext : SendPacketContext_t
//
/////////////////////////////////////////////////////////////////////////////
/// Actual implementation of SteamNetworkingMessage_t, which is the API
/// visible type. Has extra fields needed to put the message into intrusive
/// linked lists.
class CSteamNetworkingMessage : public SteamNetworkingMessage_t
{
public:
@@ -182,6 +186,7 @@ public:
void UnlinkFromQueue( Links CSteamNetworkingMessage::*pMbrLinks );
};
/// A doubly-linked list of CSteamNetworkingMessage
struct SteamNetworkingMessageQueue
{
CSteamNetworkingMessage *m_pFirst = nullptr;
@@ -270,7 +275,7 @@ protected:
/// transport. Most of the common functionality for implementing reliable
/// connections on top of unreliable datagrams, connection quality measurement,
/// etc is implemented here.
class CSteamNetworkConnectionBase : protected IThinker
class CSteamNetworkConnectionBase : public IThinker
{
public:
@@ -316,6 +321,9 @@ public:
// Debug description
inline const char *GetDescription() const { return m_szDescription; }
/// When something changes that goes into the description, call this to rebuild the description
void SetDescription();
/// High level state of the connection
ESteamNetworkingConnectionState GetState() const { return m_eConnectionState; }
@@ -336,7 +344,7 @@ public:
inline SteamNetworkingMicroseconds GetTimeEnteredConnectionState() const { return m_usecWhenEnteredConnectionState; }
/// Fill in connection details
virtual void PopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const;
virtual void ConnectionPopulateInfo( SteamNetConnectionInfo_t &info ) const;
//
// Lifetime management
@@ -348,8 +356,11 @@ public:
/// Free up all resources. Close sockets, etc
virtual void FreeResources();
/// Destroy the connection NOW
void Destroy();
/// Nuke all transports
virtual void DestroyTransport();
/// Free resources and self-destruct NOW
void ConnectionDestroySelfNow();
//
// Connection state machine
@@ -369,6 +380,11 @@ public:
/// What interface is responsible for this connection?
CSteamNetworkingSockets *const m_pSteamNetworkingSocketsInterface;
/// Current active transport for this connection.
/// MIGHT BE NULL in certain failure / edge cases!
/// Might change during the connection lifetime.
CConnectionTransport *m_pTransport;
/// Our public handle
HSteamNetConnection m_hConnectionSelf;
@@ -411,6 +427,10 @@ public:
/// Connection configuration
ConnectionConfig m_connectionConfig;
/// The reason code for why the connection was closed.
ESteamNetConnectionEnd m_eEndReason;
ConnectionEndDebugMsg m_szEndDebug;
/// MTU values for this connection
int m_cbMTUPacketSize = 0;
int m_cbMaxPlaintextPayloadSend = 0;
@@ -455,11 +475,6 @@ public:
return m_receiverState.TimeWhenFlushAcks() < INT64_MAX || SNP_TimeWhenWantToSendNextPacket() < INT64_MAX;
}
/// Called by SNP pacing layer, when it has some data to send and there is bandwidth available.
/// The derived class should setup a context, reserving the space it needs, and then call SNP_SendPacket.
/// Returns true if a packet was sent successfuly, false if there was a problem.
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) = 0;
/// Send a data packet now, even if we don't have the bandwidth available. Returns true if a packet was
/// sent successfully, false if there was a problem. This will call SendEncryptedDataChunk to do the work
bool SNP_SendPacket( SendPacketContext_t &ctx );
@@ -467,88 +482,6 @@ public:
/// Called to (maybe) post a callback
virtual void PostConnectionStateChangedCallback( ESteamNetworkingConnectionState eOldAPIState, ESteamNetworkingConnectionState eNewAPIState );
protected:
CSteamNetworkConnectionBase( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface );
virtual ~CSteamNetworkConnectionBase(); // hidden destructor, don't call directly. Use Destroy()
/// Initialize connection bookkeeping
bool BInitConnection( SteamNetworkingMicroseconds usecNow, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg );
/// Called from BInitConnection, to start obtaining certs, etc
virtual void InitConnectionCrypto( SteamNetworkingMicroseconds usecNow );
/// If this is a direct UDP connection, what is the address of the remote host?
/// FIXME - Should we delete this and move to derived classes?
/// It's not always meaningful
netadr_t m_netAdrRemote;
/// The reason code for why the connection was closed.
ESteamNetConnectionEnd m_eEndReason;
ConnectionEndDebugMsg m_szEndDebug;
/// User data
int64 m_nUserData;
/// Name assigned by app (for debugging)
char m_szAppName[ k_cchSteamNetworkingMaxConnectionDescription ];
/// More complete debug description (for debugging)
char m_szDescription[ k_cchSteamNetworkingMaxConnectionDescription ];
void SetDescription();
/// Set the connection description. Should include the connection type and peer address.
typedef char ConnectionTypeDescription_t[64];
virtual void GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const = 0;
// Implements IThinker.
// Connections do not override this. Do any periodic work in ThinkConnection()
virtual void Think( SteamNetworkingMicroseconds usecNow ) OVERRIDE final;
/// Check state of connection. Check for timeouts, and schedule time when we
/// should think next
void CheckConnectionStateAndSetNextThinkTime( SteamNetworkingMicroseconds usecNow );
/// Misc periodic processing.
/// Called from within CheckConnectionStateAndSetNextThinkTime.
virtual void ThinkConnection( SteamNetworkingMicroseconds usecNow );
/// Called when a timeout is detected
void ConnectionTimedOut( SteamNetworkingMicroseconds usecNow );
/// Called when a timeout is detected. Derived connection types can inspect this
/// to provide a more specific explanation. Base class just uses the generic reason codes.
virtual void GuessTimeoutReason( ESteamNetConnectionEnd &nReasonCode, ConnectionEndDebugMsg &msg, SteamNetworkingMicroseconds usecNow );
/// Hook to allow connections to customize message sending.
/// (E.g. loopback.)
virtual EResult _APISendMessageToConnection( const void *pData, uint32 cbData, int nSendFlags );
/// Base class calls this to ask derived class to surround the
/// "chunk" with the appropriate framing, and route it to the
/// appropriate host. A "chunk" might contain a mix of reliable
/// and unreliable data. We use the same framing for data
/// payloads for all connection types. Return value is
/// the number of bytes written to the network layer, UDP/IP
/// header is not included.
///
/// pConnectionContext is whatever the connection later passed
/// to SNP_SendPacket, if the connection initiated the sending
/// of the packet
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) = 0;
/// Called when we receive a complete message. Should allocate a message object and put it into the proper queues
void ReceivedMessage( const void *pData, int cbData, int64 nMsgNum, SteamNetworkingMicroseconds usecNow );
/// Called when the state changes
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState );
/// Return true if we are currently able to send end-to-end messages.
virtual bool BCanSendEndToEndConnectRequest() const = 0;
virtual bool BCanSendEndToEndData() const = 0;
virtual void SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow ) = 0;
virtual void SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason ) = 0;
//virtual bool BSendEndToEndPing( SteamNetworkingMicroseconds usecNow );
void QueueEndToEndAck( bool bImmediate, SteamNetworkingMicroseconds usecNow )
{
if ( bImmediate )
@@ -573,6 +506,78 @@ protected:
return m_statsEndToEnd.NeedToSend( usecNow );
}
inline const CMsgSteamDatagramSessionCryptInfoSigned &GetSignedCryptLocal() { return m_msgSignedCryptLocal; }
inline const CMsgSteamDatagramCertificateSigned &GetSignedCertLocal() { return m_msgSignedCertLocal; }
inline bool BCertHasIdentity() const { return m_bCertHasIdentity; }
inline bool BCryptKeysValid() const { return m_bCryptKeysValid; }
/// Called when we send an end-to-end connect request
void SentEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow )
{
// Reset timeout/retry for this reply. But if it fails, we'll start
// the whole handshake over again. It keeps the code simpler, and the
// challenge value has a relatively short expiry anyway.
m_usecWhenSentConnectRequest = usecNow;
EnsureMinThinkTime( usecNow + k_usecConnectRetryInterval );
}
// Check the certs, save keys, etc
bool BRecvCryptoHandshake( const CMsgSteamDatagramCertificateSigned &msgCert, const CMsgSteamDatagramSessionCryptInfoSigned &msgSessionInfo, bool bServer );
/// Check state of connection. Check for timeouts, and schedule time when we
/// should think next
void CheckConnectionStateAndSetNextThinkTime( SteamNetworkingMicroseconds usecNow );
protected:
CSteamNetworkConnectionBase( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface );
virtual ~CSteamNetworkConnectionBase(); // hidden destructor, don't call directly. Use Destroy()
/// Initialize connection bookkeeping
bool BInitConnection( SteamNetworkingMicroseconds usecNow, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg );
/// Called from BInitConnection, to start obtaining certs, etc
virtual void InitConnectionCrypto( SteamNetworkingMicroseconds usecNow );
/// If this is a direct UDP connection, what is the address of the remote host?
/// FIXME - Should we delete this and move to derived classes?
/// It's not always meaningful
netadr_t m_netAdrRemote;
/// User data
int64 m_nUserData;
/// Name assigned by app (for debugging)
char m_szAppName[ k_cchSteamNetworkingMaxConnectionDescription ];
/// More complete debug description (for debugging)
char m_szDescription[ k_cchSteamNetworkingMaxConnectionDescription ];
/// Set the connection description. Should include the connection type and peer address.
typedef char ConnectionTypeDescription_t[64];
virtual void GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const = 0;
// Implements IThinker.
// Connections do not override this. Do any periodic work in ThinkConnection()
virtual void Think( SteamNetworkingMicroseconds usecNow ) OVERRIDE final;
/// Misc periodic processing.
/// Called from within CheckConnectionStateAndSetNextThinkTime.
virtual void ThinkConnection( SteamNetworkingMicroseconds usecNow );
/// Called when a timeout is detected
void ConnectionTimedOut( SteamNetworkingMicroseconds usecNow );
/// Called when a timeout is detected. Derived connection types can inspect this
/// to provide a more specific explanation. Base class just uses the generic reason codes.
virtual void GuessTimeoutReason( ESteamNetConnectionEnd &nReasonCode, ConnectionEndDebugMsg &msg, SteamNetworkingMicroseconds usecNow );
/// Hook to allow connections to customize message sending.
/// (E.g. loopback.)
virtual EResult _APISendMessageToConnection( const void *pData, uint32 cbData, int nSendFlags );
/// Called when we receive a complete message. Should allocate a message object and put it into the proper queues
void ReceivedMessage( const void *pData, int cbData, int64 nMsgNum, SteamNetworkingMicroseconds usecNow );
/// Timestamp when we last sent an end-to-end connection request packet
SteamNetworkingMicroseconds m_usecWhenSentConnectRequest;
@@ -609,9 +614,6 @@ protected:
AutoWipeFixedSizeBuffer<12> m_cryptIVSend;
AutoWipeFixedSizeBuffer<12> m_cryptIVRecv;
// Check the certs, save keys, etc
bool BRecvCryptoHandshake( const CMsgSteamDatagramCertificateSigned &msgCert, const CMsgSteamDatagramSessionCryptInfoSigned &msgSessionInfo, bool bServer );
/// Check if the remote cert (m_msgCertRemote) is acceptable. If not, return the
/// appropriate connection code and error message. If pCACertAuthScope is NULL, the
/// cert is not signed. (The base class will check if this is allowed.) If pCACertAuthScope
@@ -700,8 +702,75 @@ private:
#endif
};
/// Abstract base class for sending end-to-end data for a connection.
///
/// NOTE: Eventually, a connection may have more than one transport,
/// and dynamically switch between them. (E.g. it will try local LAN,
/// NAT piercing, then fallback to relay)
class CConnectionTransport
{
public:
/// The connection we were created to service. A given transport object
/// is always created for a single connection (and that will not change,
/// hence this is a reference and not a pointer). However, a connection may
/// create more than one transport.
CSteamNetworkConnectionBase &m_connection;
/// Use this function to actually delete the object. Do not use operator delete
void TransportDestroySelfNow();
/// Free up transport resources. Called just before destruction. If you have cleanup
/// that might involved calling virtual methods, do it in here
virtual void TransportFreeResources();
/// Called by SNP pacing layer, when it has some data to send and there is bandwidth available.
/// The derived class should setup a context, reserving the space it needs, and then call SNP_SendPacket.
/// Returns true if a packet was sent successfully, false if there was a problem.
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) = 0;
/// Connection will call this to ask the transport to surround the
/// "chunk" with the appropriate framing, and route it to the
/// appropriate host. A "chunk" might contain a mix of reliable
/// and unreliable data. We use the same framing for data
/// payloads for all connection types. Return value is
/// the number of bytes written to the network layer, UDP/IP
/// header is not included.
///
/// ctx is whatever the transport passed to SNP_SendPacket, if the
/// connection initiated the sending of the packet
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) = 0;
/// Return true if we are currently able to send end-to-end messages.
virtual bool BCanSendEndToEndConnectRequest() const = 0;
virtual bool BCanSendEndToEndData() const = 0;
virtual void SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow ) = 0;
virtual void SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason ) = 0;
virtual void TransportPopulateConnectionInfo( SteamNetConnectionInfo_t &info ) const;
virtual void GetDetailedConnectionStatus( SteamNetworkingDetailedConnectionStatus &stats, SteamNetworkingMicroseconds usecNow );
/// Called when the connection state changes. Some transports need to do stuff
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState );
// Some accessors for commonly needed info
inline ESteamNetworkingConnectionState ConnectionState() const { return m_connection.GetState(); }
inline uint32 ConnectionIDLocal() const { return m_connection.m_unConnectionIDLocal; }
inline uint32 ConnectionIDRemote() const { return m_connection.m_unConnectionIDRemote; }
inline CSteamNetworkListenSocketBase *ListenSocket() const { return m_connection.m_pParentListenSocket; }
inline const SteamNetworkingIdentity &IdentityLocal() const { return m_connection.m_identityLocal; }
inline const SteamNetworkingIdentity &IdentityRemote() const { return m_connection.m_identityRemote; }
inline const char *ConnectionDescription() const { return m_connection.GetDescription(); }
protected:
inline CConnectionTransport( CSteamNetworkConnectionBase &conn ) : m_connection( conn ) {}
virtual ~CConnectionTransport() {} // Destructor protected -- use Destroy()
};
/// Dummy loopback/pipe connection that doesn't actually do any network work.
class CSteamNetworkConnectionPipe final : public CSteamNetworkConnectionBase
/// For these types of connections, the distinction between connection and transport
/// is not realyl useful
class CSteamNetworkConnectionPipe final : public CSteamNetworkConnectionBase, public CConnectionTransport
{
public:
@@ -711,20 +780,23 @@ public:
CSteamNetworkConnectionPipe *m_pPartner;
// CSteamNetworkConnectionBase overrides
virtual bool BCanSendEndToEndConnectRequest() const override;
virtual bool BCanSendEndToEndData() const override;
virtual void SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow ) override;
virtual void SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason ) override;
virtual EResult APIAcceptConnection() override;
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) override;
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) override;
virtual EResult _APISendMessageToConnection( const void *pData, uint32 cbData, int nSendFlags ) override;
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState ) override;
virtual void PostConnectionStateChangedCallback( ESteamNetworkingConnectionState eOldAPIState, ESteamNetworkingConnectionState eNewAPIState ) override;
virtual void InitConnectionCrypto( SteamNetworkingMicroseconds usecNow ) override;
virtual EUnsignedCert AllowRemoteUnsignedCert() override;
virtual EUnsignedCert AllowLocalUnsignedCert() override;
virtual void GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const override;
virtual void DestroyTransport() override;
// CSteamNetworkConnectionTransport
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState ) override;
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) override;
virtual bool BCanSendEndToEndConnectRequest() const override;
virtual bool BCanSendEndToEndData() const override;
virtual void SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow ) override;
virtual void SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason ) override;
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) override;
private:
@@ -1849,7 +1849,7 @@ void ReallySpewType( ESteamNetworkingSocketsDebugOutputType eType, const char *p
pfnDebugOutput( eType, buf );
}
#ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB
#if defined( STEAMNETWORKINGSOCKETS_STANDALONELIB ) && !defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT )
static SpewRetval_t SDRSpewFunc( SpewType_t type, char const *pMsg )
{
V_StripTrailingWhitespaceASCII( const_cast<char*>( pMsg ) );
@@ -1937,7 +1937,7 @@ bool BSteamNetworkingSocketsLowLevelAddRef( SteamDatagramErrMsg &errMsg )
#endif
// Latch Steam codebase's logging system so we get spew and asserts
#ifdef STEAMNETWORKINGSOCKETS_STANDALONELIB
#if defined( STEAMNETWORKINGSOCKETS_STANDALONELIB ) && !defined( STEAMNETWORKINGSOCKETS_STREAMINGCLIENT )
SpewOutputFunc( SDRSpewFunc );
#endif
@@ -1318,6 +1318,13 @@ bool CSteamNetworkConnectionBase::SNP_SendPacket( SendPacketContext_t &ctx )
Assert( BStateIsConnectedForWirePurposes() );
Assert( !m_senderState.m_mapInFlightPacketsByPktNum.empty() );
// We must have transport!
if ( !m_pTransport )
{
Assert( false );
return false;
}
SteamNetworkingMicroseconds usecNow = ctx.m_usecNow;
// Get max size of plaintext we could send.
@@ -1759,16 +1766,21 @@ bool CSteamNetworkConnectionBase::SNP_SendPacket( SendPacketContext_t &ctx )
nullptr, 0 // no AAD
) );
//SpewMsg( "Send encrypt IV %llu + %02x%02x%02x%02x encrypted %d %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],
// cbEncrypted,
// arEncryptedChunk[0], arEncryptedChunk[1], arEncryptedChunk[2],arEncryptedChunk[3]
//);
// Restore the IV to the base value
*(uint64 *)&m_cryptIVSend.m_buf -= LittleQWord( m_statsEndToEnd.m_nNextSendSequenceNumber );
Assert( (int)cbEncrypted >= cbPlainText );
Assert( (int)cbEncrypted <= k_cbSteamNetworkingSocketsMaxEncryptedPayloadSend ); // confirm that pad above was not necessary and we never exceed k_nMaxSteamDatagramTransportPayload, even after encrypting
//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] );
// Connection-specific method to send it
int nBytesSent = SendEncryptedDataChunk( arEncryptedChunk, cbEncrypted, ctx );
int nBytesSent = m_pTransport->SendEncryptedDataChunk( arEncryptedChunk, cbEncrypted, ctx );
if ( nBytesSent <= 0 )
return false;
@@ -2945,7 +2957,7 @@ SteamNetworkingMicroseconds CSteamNetworkConnectionBase::SNP_ThinkSendState( Ste
// Keep sending packets until we run out of tokens
int nPacketsSent = 0;
for (;;)
while ( m_pTransport )
{
if ( nPacketsSent > k_nMaxPacketsPerThink )
@@ -2970,7 +2982,7 @@ SteamNetworkingMicroseconds CSteamNetworkConnectionBase::SNP_ThinkSendState( Ste
}
// Send the next data packet.
if ( !SendDataPacket( usecNow ) )
if ( !m_pTransport->SendDataPacket( usecNow ) )
{
// Problem sending packet. Nuke token bucket, but request
// a wakeup relatively quick to check on our state again
@@ -3142,6 +3154,10 @@ SteamNetworkingMicroseconds CSteamNetworkConnectionBase::SNP_GetNextThinkTime( S
return k_nThinkTime_Never;
}
// We cannot send any packets if we don't have transport
if ( !m_pTransport )
return k_nThinkTime_Never;
// Start with the time when the receiver needs to flush out ack.
SteamNetworkingMicroseconds usecNextThink = m_receiverState.TimeWhenFlushAcks();
@@ -430,7 +430,7 @@ void CSteamNetworkListenSocketDirectUDP::Received_ConnectRequest( const CMsgStea
if ( !pConn->BBeginAccept( this, adrFrom, m_pSock, identityRemote, unClientConnectionID, msg.cert(), msg.crypt(), errMsg ) )
{
SpewWarning( "Failed to accept connection from %s. %s\n", CUtlNetAdrRender( adrFrom ).String(), errMsg );
pConn->Destroy();
pConn->ConnectionDestroySelfNow();
return;
}
@@ -520,21 +520,41 @@ void CSteamNetworkListenSocketDirectUDP::SendPaddedMsg( uint8 nMsgID, const goog
CSteamNetworkConnectionUDP::CSteamNetworkConnectionUDP( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface )
: CSteamNetworkConnectionBase( pSteamNetworkingSocketsInterface )
{
m_pSocket = nullptr;
}
CSteamNetworkConnectionUDP::~CSteamNetworkConnectionUDP()
{
AssertMsg( !m_pSocket, "Connection not destroyed properly" );
}
CConnectionTransportUDP::CConnectionTransportUDP( CSteamNetworkConnectionUDP &connection )
: CConnectionTransport( connection )
, m_pSocket( nullptr )
{
}
CConnectionTransportUDP::~CConnectionTransportUDP()
{
Assert( !m_pSocket ); // Use TransportDestroySelfNow!
}
void CConnectionTransportUDP::TransportFreeResources()
{
CConnectionTransport::TransportFreeResources();
if ( m_pSocket )
{
m_pSocket->Close();
m_pSocket = nullptr;
}
}
void CSteamNetworkConnectionUDP::GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const
{
char szAddr[ 64 ];
if ( m_pSocket )
if ( Transport() && Transport()->m_pSocket )
{
SteamNetworkingIPAddr adrRemote;
NetAdrToSteamNetworkingIPAddr( adrRemote, m_pSocket->GetRemoteHostAddr() );
NetAdrToSteamNetworkingIPAddr( adrRemote, Transport()->m_pSocket->GetRemoteHostAddr() );
adrRemote.ToString( szAddr, sizeof(szAddr), true );
if (
m_identityRemote.IsLocalHost()
@@ -554,18 +574,6 @@ void CSteamNetworkConnectionUDP::GetConnectionTypeDescription( ConnectionTypeDes
V_sprintf_safe( szDescription, "UDP %s@%s", sIdentity.c_str(), szAddr );
}
void CSteamNetworkConnectionUDP::FreeResources()
{
if ( m_pSocket )
{
m_pSocket->Close();
m_pSocket = nullptr;
}
// Base class cleanup
CSteamNetworkConnectionBase::FreeResources();
}
template<>
inline uint32 StatsMsgImpliedFlags<CMsgSteamSockets_UDP_Stats>( const CMsgSteamSockets_UDP_Stats &msg )
{
@@ -579,20 +587,21 @@ struct UDPSendPacketContext_t : SendPacketContext<CMsgSteamSockets_UDP_Stats>
};
void CSteamNetworkConnectionUDP::PopulateSendPacketContext( UDPSendPacketContext_t &ctx, EStatsReplyRequest eReplyRequested )
void CConnectionTransportUDP::PopulateSendPacketContext( UDPSendPacketContext_t &ctx, EStatsReplyRequest eReplyRequested )
{
SteamNetworkingMicroseconds usecNow = ctx.m_usecNow;
LinkStatsTracker<LinkStatsTrackerEndToEnd> &statsEndToEnd = m_connection.m_statsEndToEnd;
// What effective flags should we send
uint32 nFlags = 0;
int nReadyToSendTracer = 0;
if ( eReplyRequested == k_EStatsReplyRequest_Immediate || m_statsEndToEnd.BNeedToSendPingImmediate( usecNow ) )
if ( eReplyRequested == k_EStatsReplyRequest_Immediate || statsEndToEnd.BNeedToSendPingImmediate( usecNow ) )
nFlags |= ctx.msg.ACK_REQUEST_E2E | ctx.msg.ACK_REQUEST_IMMEDIATE;
else if ( eReplyRequested == k_EStatsReplyRequest_DelayedOK || m_statsEndToEnd.BNeedToSendKeepalive( usecNow ) )
else if ( eReplyRequested == k_EStatsReplyRequest_DelayedOK || statsEndToEnd.BNeedToSendKeepalive( usecNow ) )
nFlags |= ctx.msg.ACK_REQUEST_E2E;
else
{
nReadyToSendTracer = m_statsEndToEnd.ReadyToSendTracerPing( usecNow );
nReadyToSendTracer = statsEndToEnd.ReadyToSendTracerPing( usecNow );
if ( nReadyToSendTracer > 1 )
nFlags |= ctx.msg.ACK_REQUEST_E2E;
}
@@ -600,29 +609,29 @@ void CSteamNetworkConnectionUDP::PopulateSendPacketContext( UDPSendPacketContext
ctx.m_nFlags = nFlags;
// Need to send any connection stats stats?
if ( m_statsEndToEnd.BNeedToSendStats( usecNow ) )
if ( statsEndToEnd.BNeedToSendStats( usecNow ) )
{
ctx.m_nStatsNeed = 2;
m_statsEndToEnd.PopulateMessage( *ctx.msg.mutable_stats(), usecNow );
statsEndToEnd.PopulateMessage( *ctx.msg.mutable_stats(), usecNow );
if ( nReadyToSendTracer > 0 )
nFlags |= ctx.msg.ACK_REQUEST_E2E;
ctx.SlamFlagsAndCalcSize();
ctx.CalcMaxEncryptedPayloadSize( sizeof(UDPDataMsgHdr), this );
ctx.CalcMaxEncryptedPayloadSize( sizeof(UDPDataMsgHdr), &m_connection );
}
else
{
// Populate flags now, based on what is implied from what we HAVE to send
ctx.SlamFlagsAndCalcSize();
ctx.CalcMaxEncryptedPayloadSize( sizeof(UDPDataMsgHdr), this );
ctx.CalcMaxEncryptedPayloadSize( sizeof(UDPDataMsgHdr), &m_connection );
// Would we like to try to send some additional stats, if there is room?
if ( m_statsEndToEnd.BReadyToSendStats( usecNow ) )
if ( statsEndToEnd.BReadyToSendStats( usecNow ) )
{
if ( nReadyToSendTracer > 0 )
nFlags |= ctx.msg.ACK_REQUEST_E2E;
m_statsEndToEnd.PopulateMessage( *ctx.msg.mutable_stats(), usecNow );
statsEndToEnd.PopulateMessage( *ctx.msg.mutable_stats(), usecNow );
ctx.SlamFlagsAndCalcSize();
ctx.m_nStatsNeed = 1;
}
@@ -634,26 +643,26 @@ void CSteamNetworkConnectionUDP::PopulateSendPacketContext( UDPSendPacketContext
}
}
void CSteamNetworkConnectionUDP::SendStatsMsg( EStatsReplyRequest eReplyRequested, SteamNetworkingMicroseconds usecNow, const char *pszReason )
void CConnectionTransportUDP::SendStatsMsg( EStatsReplyRequest eReplyRequested, SteamNetworkingMicroseconds usecNow, const char *pszReason )
{
UDPSendPacketContext_t ctx( usecNow, pszReason );
PopulateSendPacketContext( ctx, eReplyRequested );
// Send a data packet (maybe containing ordinary data), with this piggy backed on top of it
SNP_SendPacket( ctx );
m_connection.SNP_SendPacket( ctx );
}
bool CSteamNetworkConnectionUDP::SendDataPacket( SteamNetworkingMicroseconds usecNow )
bool CConnectionTransportUDP::SendDataPacket( SteamNetworkingMicroseconds usecNow )
{
// Populate context struct with any stats we want/need to send, and how much space we need to reserve for it
UDPSendPacketContext_t ctx( usecNow, "data" );
PopulateSendPacketContext( ctx, k_EStatsReplyRequest_NothingToSend );
// Send a packet
return SNP_SendPacket( ctx );
return m_connection.SNP_SendPacket( ctx );
}
int CSteamNetworkConnectionUDP::SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctxBase )
int CConnectionTransportUDP::SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctxBase )
{
if ( !m_pSocket )
{
@@ -666,9 +675,9 @@ int CSteamNetworkConnectionUDP::SendEncryptedDataChunk( const void *pChunk, int
uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
UDPDataMsgHdr *hdr = (UDPDataMsgHdr *)pkt;
hdr->m_unMsgFlags = 0x80;
Assert( m_unConnectionIDRemote != 0 );
hdr->m_unToConnectionID = LittleDWord( m_unConnectionIDRemote );
hdr->m_unSeqNum = LittleWord( m_statsEndToEnd.ConsumeSendPacketNumberAndGetWireFmt( ctx.m_usecNow ) );
Assert( m_connection.m_unConnectionIDRemote != 0 );
hdr->m_unToConnectionID = LittleDWord( m_connection.m_unConnectionIDRemote );
hdr->m_unSeqNum = LittleWord( m_connection.m_statsEndToEnd.ConsumeSendPacketNumberAndGetWireFmt( ctx.m_usecNow ) );
byte *p = (byte*)( hdr + 1 );
@@ -738,29 +747,73 @@ int CSteamNetworkConnectionUDP::SendEncryptedDataChunk( const void *pChunk, int
return cbSend;
}
bool CSteamNetworkConnectionUDP::BInitConnect( const SteamNetworkingIPAddr &addressRemote, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
bool CConnectionTransportUDP::BConnect( const netadr_t &netadrRemote, SteamDatagramErrMsg &errMsg )
{
AssertMsg( !m_pSocket, "Trying to connect when we already have a socket?" );
// We're initiating a connection, not being accepted on a listen socket
Assert( !m_pParentListenSocket );
netadr_t netadrRemote;
SteamNetworkingIPAddrToNetAdr( netadrRemote, addressRemote );
// Create an actual OS socket. We'll bind it to talk only to this host.
// (Note: we might not actually "bind" it at the OS layer, but from our perpsective
// it is bound.)
//
// For now we're just assuming each connection will gets its own socket,
// on an ephemeral port. Later we could add a setting to enable
// sharing of the socket.
// sharing of the socket or binding to a particular local address.
Assert( !m_pSocket );
m_pSocket = OpenUDPSocketBoundToHost( netadrRemote, CRecvPacketCallback( PacketReceived, this ), errMsg );
if ( !m_pSocket )
return false;
return true;
}
bool CConnectionTransportUDP::BAccept( CSharedSocket *pSharedSock, const netadr_t &netadrRemote, SteamDatagramErrMsg &errMsg )
{
// Get an interface that is bound to talk to this address
m_pSocket = pSharedSock->AddRemoteHost( netadrRemote, CRecvPacketCallback( PacketReceived, this ) );
if ( !m_pSocket )
{
// This is really weird and shouldn't happen
V_strcpy_safe( errMsg, "Unable to create a bound socket on the shared socket." );
return false;
}
return true;
}
bool CConnectionTransportUDP::CreateLoopbackPair( CConnectionTransportUDP *pTransport[2] )
{
IBoundUDPSocket *sock[2];
SteamNetworkingErrMsg errMsg;
if ( !CreateBoundSocketPair(
CRecvPacketCallback( PacketReceived, pTransport[0] ),
CRecvPacketCallback( PacketReceived, pTransport[1] ), sock, errMsg ) )
{
// Assert, this really should only fail if we have some sort of bug
AssertMsg1( false, "Failed to create UDP socket pair. %s", errMsg );
return false;
}
pTransport[0]->m_pSocket = sock[0];
pTransport[1]->m_pSocket = sock[1];
return true;
}
bool CSteamNetworkConnectionUDP::BInitConnect( const SteamNetworkingIPAddr &addressRemote, int nOptions, const SteamNetworkingConfigValue_t *pOptions, SteamDatagramErrMsg &errMsg )
{
AssertMsg( !m_pTransport, "Trying to connect when we already have a socket?" );
// We're initiating a connection, not being accepted on a listen socket
Assert( !m_pParentListenSocket );
Assert( !m_bConnectionInitiatedRemotely );
netadr_t netadrRemote;
SteamNetworkingIPAddrToNetAdr( netadrRemote, addressRemote );
// We use identity validity to denote when our connection has been accepted,
// so it's important that it be cleared. (It should already be so.)
Assert( m_identityRemote.IsInvalid() );
m_identityRemote.Clear();
// We just opened a socket aiming at this address, so we know what the remote addr will be.
// We know what the remote addr will be.
m_netAdrRemote = netadrRemote;
// We should know our own identity, unless the app has said it's OK to go without this.
@@ -783,12 +836,20 @@ bool CSteamNetworkConnectionUDP::BInitConnect( const SteamNetworkingIPAddr &addr
}
}
// Create transport.
CConnectionTransportUDP *pTransport = new CConnectionTransportUDP( *this );
if ( !pTransport->BConnect( netadrRemote, errMsg ) )
{
pTransport->TransportDestroySelfNow();
return false;
}
m_pTransport = pTransport;
// Let base class do some common initialization
SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
if ( !CSteamNetworkConnectionBase::BInitConnection( usecNow, nOptions, pOptions, errMsg ) )
{
m_pSocket->Close();
m_pSocket = nullptr;
DestroyTransport();
return false;
}
@@ -798,24 +859,25 @@ bool CSteamNetworkConnectionUDP::BInitConnect( const SteamNetworkingIPAddr &addr
return true;
}
bool CSteamNetworkConnectionUDP::BCanSendEndToEndConnectRequest() const
bool CConnectionTransportUDP::BCanSendEndToEndConnectRequest() const
{
return m_pSocket != nullptr;
}
bool CSteamNetworkConnectionUDP::BCanSendEndToEndData() const
bool CConnectionTransportUDP::BCanSendEndToEndData() const
{
return m_pSocket != nullptr;
}
void CSteamNetworkConnectionUDP::SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow )
{
Assert( !m_pParentListenSocket );
Assert( GetState() == k_ESteamNetworkingConnectionState_Connecting ); // Why else would we be doing this?
Assert( m_unConnectionIDLocal );
Assert( !ListenSocket() );
Assert( !m_connection.m_bConnectionInitiatedRemotely );
Assert( ConnectionState() == k_ESteamNetworkingConnectionState_Connecting ); // Why else would we be doing this?
Assert( ConnectionIDLocal() );
CMsgSteamSockets_UDP_ChallengeRequest msg;
msg.set_connection_id( m_unConnectionIDLocal );
msg.set_connection_id( ConnectionIDLocal() );
//msg.set_client_steam_id( m_steamIDLocal.ConvertToUint64() );
msg.set_my_timestamp( usecNow );
msg.set_protocol_version( k_nCurrentProtocolVersion );
@@ -825,10 +887,10 @@ void CSteamNetworkConnectionUDP::SendEndToEndConnectRequest( SteamNetworkingMicr
// They are supposed to reply with a timestamps, from which we can estimate the ping.
// So this counts as a ping request
m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
m_connection.m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
}
void CSteamNetworkConnectionUDP::SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason )
void CConnectionTransportUDP::SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason )
{
SendStatsMsg( eRequest, usecNow, pszReason );
}
@@ -840,14 +902,14 @@ void CSteamNetworkConnectionUDP::ThinkConnection( SteamNetworkingMicroseconds us
// There's really nothing specific to plain UDP transport here.
// Check if we have stats we need to flush out
if ( !m_statsEndToEnd.IsDisconnected() )
if ( !m_statsEndToEnd.IsDisconnected() && m_pTransport )
{
// Do we need to send something immediately, for any reason?
const char *pszReason = NeedToSendEndToEndStatsOrAcks( usecNow );
if ( pszReason )
{
SendStatsMsg( k_EStatsReplyRequest_NothingToSend, usecNow, pszReason );
m_pTransport->SendEndToEndStatsMsg( k_EStatsReplyRequest_NothingToSend, usecNow, pszReason );
// Make sure that took care of what we needed!
@@ -878,15 +940,16 @@ bool CSteamNetworkConnectionUDP::BBeginAccept(
SteamDatagramErrMsg &errMsg
)
{
AssertMsg( !m_pSocket, "Trying to accept when we already have a socket?" );
AssertMsg( !m_pTransport, "Trying to accept when we already have transport?" );
// Get an interface just to talk just to this guy
m_pSocket = pSharedSock->AddRemoteHost( adrFrom, CRecvPacketCallback( PacketReceived, this ) );
if ( !m_pSocket )
// Setup transport
CConnectionTransportUDP *pTransport = new CConnectionTransportUDP( *this );
if ( !pTransport->BAccept( pSharedSock, adrFrom, errMsg ) )
{
V_strcpy_safe( errMsg, "Unable to create a bound socket on the shared socket." );
pTransport->TransportDestroySelfNow();
return false;
}
m_pTransport = pTransport;
m_identityRemote = identityRemote;
@@ -901,16 +964,14 @@ bool CSteamNetworkConnectionUDP::BBeginAccept(
SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
if ( !CSteamNetworkConnectionBase::BInitConnection( usecNow, 0, nullptr, errMsg ) )
{
m_pSocket->Close();
m_pSocket = nullptr;
DestroyTransport();
return false;
}
// Process crypto handshake now
if ( !BRecvCryptoHandshake( msgCert, msgCryptSessionInfo, true ) )
{
m_pSocket->Close();
m_pSocket = nullptr;
DestroyTransport();
Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
V_sprintf_safe( errMsg, "Failed crypto init. %s", m_szEndDebug );
return false;
@@ -924,8 +985,14 @@ EResult CSteamNetworkConnectionUDP::APIAcceptConnection()
{
SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
if ( !Transport() )
{
AssertMsg( false, "Cannot acception UDP connection. No transport?" );
return k_EResultFail;
}
// Send the message
SendConnectOK( usecNow );
Transport()->SendConnectOK( usecNow );
// We are fully connected
ConnectionState_Connected( usecNow );
@@ -934,7 +1001,7 @@ EResult CSteamNetworkConnectionUDP::APIAcceptConnection()
return k_EResultOK;
}
void CSteamNetworkConnectionUDP::SendMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg )
void CConnectionTransportUDP::SendMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg )
{
uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
@@ -951,7 +1018,7 @@ void CSteamNetworkConnectionUDP::SendMsg( uint8 nMsgID, const google::protobuf::
SendPacket( pkt, cbPkt );
}
void CSteamNetworkConnectionUDP::SendPaddedMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg )
void CConnectionTransportUDP::SendPaddedMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg )
{
uint8 pkt[ k_cbSteamNetworkingSocketsMaxUDPMsgLen ];
@@ -973,7 +1040,7 @@ void CSteamNetworkConnectionUDP::SendPaddedMsg( uint8 nMsgID, const google::prot
SendPacket( pkt, cbPkt );
}
void CSteamNetworkConnectionUDP::SendPacket( const void *pkt, int cbPkt )
void CConnectionTransportUDP::SendPacket( const void *pkt, int cbPkt )
{
iovec temp;
temp.iov_base = const_cast<void*>( pkt );
@@ -981,7 +1048,7 @@ void CSteamNetworkConnectionUDP::SendPacket( const void *pkt, int cbPkt )
SendPacketGather( 1, &temp, cbPkt );
}
void CSteamNetworkConnectionUDP::SendPacketGather( int nChunks, const iovec *pChunks, int cbSendTotal )
void CConnectionTransportUDP::SendPacketGather( int nChunks, const iovec *pChunks, int cbSendTotal )
{
// Safety
if ( !m_pSocket )
@@ -991,17 +1058,17 @@ void CSteamNetworkConnectionUDP::SendPacketGather( int nChunks, const iovec *pCh
}
// Update stats
m_statsEndToEnd.TrackSentPacket( cbSendTotal );
m_connection.m_statsEndToEnd.TrackSentPacket( cbSendTotal );
// Hand over to operating system
m_pSocket->BSendRawPacketGather( nChunks, pChunks );
}
void CSteamNetworkConnectionUDP::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
void CConnectionTransportUDP::ConnectionStateChanged( ESteamNetworkingConnectionState eOldState )
{
CSteamNetworkConnectionBase::ConnectionStateChanged( eOldState );
CConnectionTransport::ConnectionStateChanged( eOldState );
switch ( GetState() )
switch ( ConnectionState() )
{
case k_ESteamNetworkingConnectionState_FindingRoute: // not used for raw UDP
default:
@@ -1028,7 +1095,7 @@ void CSteamNetworkConnectionUDP::ConnectionStateChanged( ESteamNetworkingConnect
#define ReportBadPacketIPv4( pszMsgType, /* fmt */ ... ) \
ReportBadPacketFrom( m_pSocket->GetRemoteHostAddr(), pszMsgType, __VA_ARGS__ )
void CSteamNetworkConnectionUDP::PacketReceived( const void *pvPkt, int cbPkt, const netadr_t &adrFrom, CSteamNetworkConnectionUDP *pSelf )
void CConnectionTransportUDP::PacketReceived( const void *pvPkt, int cbPkt, const netadr_t &adrFrom, CConnectionTransportUDP *pSelf )
{
const uint8 *pPkt = static_cast<const uint8 *>( pvPkt );
@@ -1048,7 +1115,7 @@ void CSteamNetworkConnectionUDP::PacketReceived( const void *pvPkt, int cbPkt, c
}
// Track stats for other packet types.
pSelf->m_statsEndToEnd.TrackRecvPacket( cbPkt, usecNow );
pSelf->m_connection.m_statsEndToEnd.TrackRecvPacket( cbPkt, usecNow );
if ( *pPkt == k_ESteamNetworkingUDPMsg_ChallengeReply )
{
@@ -1100,33 +1167,33 @@ std::string DescribeStatsContents( const CMsgSteamSockets_UDP_Stats &msg )
return sWhat;
}
void CSteamNetworkConnectionUDP::RecvStats( const CMsgSteamSockets_UDP_Stats &msgStatsIn, bool bInline, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::RecvStats( const CMsgSteamSockets_UDP_Stats &msgStatsIn, bool bInline, SteamNetworkingMicroseconds usecNow )
{
// Connection quality stats?
if ( msgStatsIn.has_stats() )
m_statsEndToEnd.ProcessMessage( msgStatsIn.stats(), usecNow );
m_connection.m_statsEndToEnd.ProcessMessage( msgStatsIn.stats(), usecNow );
// Spew appropriately
SpewVerbose( "[%s] Recv %s stats:%s\n",
GetDescription(),
ConnectionDescription(),
bInline ? "inline" : "standalone",
DescribeStatsContents( msgStatsIn ).c_str()
);
// Check if we need to reply, either now or later
if ( BStateIsConnectedForWirePurposes() )
if ( m_connection.BStateIsConnectedForWirePurposes() )
{
// Check for queuing outgoing acks
bool bImmediate = ( msgStatsIn.flags() & msgStatsIn.ACK_REQUEST_IMMEDIATE ) != 0;
if ( ( msgStatsIn.flags() & msgStatsIn.ACK_REQUEST_E2E ) || msgStatsIn.has_stats() )
{
QueueEndToEndAck( bImmediate, usecNow );
m_connection.QueueEndToEndAck( bImmediate, usecNow );
}
// Do we need to send an immediate reply?
const char *pszReason = NeedToSendEndToEndStatsOrAcks( usecNow );
const char *pszReason = m_connection.NeedToSendEndToEndStatsOrAcks( usecNow );
if ( pszReason )
{
// Send a stats message
@@ -1135,7 +1202,7 @@ void CSteamNetworkConnectionUDP::RecvStats( const CMsgSteamSockets_UDP_Stats &ms
}
}
void CSteamNetworkConnectionUDP::TrackSentStats( const CMsgSteamSockets_UDP_Stats &msgStatsOut, bool bInline, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::TrackSentStats( const CMsgSteamSockets_UDP_Stats &msgStatsOut, bool bInline, SteamNetworkingMicroseconds usecNow )
{
// What effective flags will be received?
@@ -1144,22 +1211,22 @@ void CSteamNetworkConnectionUDP::TrackSentStats( const CMsgSteamSockets_UDP_Stat
// Record that we sent stats and are waiting for peer to ack
if ( msgStatsOut.has_stats() )
{
m_statsEndToEnd.TrackSentStats( msgStatsOut.stats(), usecNow, bAllowDelayedReply );
m_connection.m_statsEndToEnd.TrackSentStats( msgStatsOut.stats(), usecNow, bAllowDelayedReply );
}
else if ( msgStatsOut.flags() & msgStatsOut.ACK_REQUEST_E2E )
{
m_statsEndToEnd.TrackSentMessageExpectingSeqNumAck( usecNow, bAllowDelayedReply );
m_connection.m_statsEndToEnd.TrackSentMessageExpectingSeqNumAck( usecNow, bAllowDelayedReply );
}
// Spew appropriately
SpewVerbose( "[%s] Sent %s stats:%s\n",
GetDescription(),
ConnectionDescription(),
bInline ? "inline" : "standalone",
DescribeStatsContents( msgStatsOut ).c_str()
);
}
void CSteamNetworkConnectionUDP::Received_Data( const uint8 *pPkt, int cbPkt, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::Received_Data( const uint8 *pPkt, int cbPkt, SteamNetworkingMicroseconds usecNow )
{
if ( cbPkt < sizeof(UDPDataMsgHdr) )
@@ -1170,7 +1237,7 @@ void CSteamNetworkConnectionUDP::Received_Data( const uint8 *pPkt, int cbPkt, St
// Check cookie
const UDPDataMsgHdr *hdr = (const UDPDataMsgHdr *)pPkt;
if ( LittleDWord( hdr->m_unToConnectionID ) != m_unConnectionIDLocal )
if ( LittleDWord( hdr->m_unToConnectionID ) != ConnectionIDLocal() )
{
// Wrong session. It could be an old session, or it could be spoofed.
@@ -1184,7 +1251,7 @@ void CSteamNetworkConnectionUDP::Received_Data( const uint8 *pPkt, int cbPkt, St
uint16 nWirePktNumber = LittleWord( hdr->m_unSeqNum );
// Check state
switch ( GetState() )
switch ( ConnectionState() )
{
case k_ESteamNetworkingConnectionState_Dead:
case k_ESteamNetworkingConnectionState_None:
@@ -1261,12 +1328,12 @@ void CSteamNetworkConnectionUDP::Received_Data( const uint8 *pPkt, int cbPkt, St
// Decrypt it, and check packet number
uint8 arDecryptedChunk[ k_cbSteamNetworkingSocketsMaxPlaintextPayloadRecv ];
uint32 cbDecrypted = sizeof(arDecryptedChunk);
int64 nFullSequenceNumber = DecryptDataChunk( nWirePktNumber, cbPkt, pChunk, cbChunk, arDecryptedChunk, cbDecrypted, usecNow );
int64 nFullSequenceNumber = m_connection.DecryptDataChunk( nWirePktNumber, cbPkt, pChunk, cbChunk, arDecryptedChunk, cbDecrypted, usecNow );
if ( nFullSequenceNumber <= 0 )
return;
// Process plaintext
if ( !ProcessPlainTextDataChunk( nFullSequenceNumber, arDecryptedChunk, cbDecrypted, 0, usecNow ) )
if ( !m_connection.ProcessPlainTextDataChunk( nFullSequenceNumber, arDecryptedChunk, cbDecrypted, 0, usecNow ) )
return;
// Process the stats, if any
@@ -1274,28 +1341,28 @@ void CSteamNetworkConnectionUDP::Received_Data( const uint8 *pPkt, int cbPkt, St
RecvStats( *pMsgStatsIn, true, usecNow );
}
void CSteamNetworkConnectionUDP::Received_ChallengeReply( const CMsgSteamSockets_UDP_ChallengeReply &msg, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::Received_ChallengeReply( const CMsgSteamSockets_UDP_ChallengeReply &msg, SteamNetworkingMicroseconds usecNow )
{
// We should only be getting this if we are the "client"
if ( m_pParentListenSocket )
if ( ListenSocket() )
{
ReportBadPacketIPv4( "ChallengeReply", "Shouldn't be receiving this unless on accepted connections, only connections initiated locally." );
return;
}
// Ignore if we're not trying to connect
if ( GetState() != k_ESteamNetworkingConnectionState_Connecting )
if ( ConnectionState() != k_ESteamNetworkingConnectionState_Connecting )
return;
// Check session ID to make sure they aren't spoofing.
if ( msg.connection_id() != m_unConnectionIDLocal )
if ( msg.connection_id() != ConnectionIDLocal() )
{
ReportBadPacketIPv4( "ChallengeReply", "Incorrect connection ID. Message is stale or could be spoofed, ignoring." );
return;
}
if ( msg.protocol_version() < k_nMinRequiredProtocolVersion )
{
ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Generic, "Peer is running old software and needs to be udpated" );
m_connection.ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_Generic, "Peer is running old software and needs to be udpated" );
return;
}
@@ -1310,70 +1377,67 @@ void CSteamNetworkConnectionUDP::Received_ChallengeReply( const CMsgSteamSockets
else
{
int nPing = (usecElapsed + 500 ) / 1000;
m_statsEndToEnd.m_ping.ReceivedPing( nPing, usecNow );
m_connection.m_statsEndToEnd.m_ping.ReceivedPing( nPing, usecNow );
}
}
// Make sure we have the crypt info that we need
if ( !m_msgSignedCertLocal.has_cert() || !m_msgSignedCryptLocal.has_info() )
if ( !m_connection.GetSignedCertLocal().has_cert() || !m_connection.GetSignedCryptLocal().has_info() )
{
ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Tried to connect request, but crypt not ready" );
m_connection.ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Misc_InternalError, "Tried to connect request, but crypt not ready" );
return;
}
// Remember protocol version. They must send it again in the connect OK, but we have a valid value now,
// so we might as well save it
m_statsEndToEnd.m_nPeerProtocolVersion = msg.protocol_version();
m_connection.m_statsEndToEnd.m_nPeerProtocolVersion = msg.protocol_version();
// Reply with the challenge data and our cert
CMsgSteamSockets_UDP_ConnectRequest msgConnectRequest;
msgConnectRequest.set_client_connection_id( m_unConnectionIDLocal );
msgConnectRequest.set_client_connection_id( ConnectionIDLocal() );
msgConnectRequest.set_challenge( msg.challenge() );
msgConnectRequest.set_my_timestamp( usecNow );
if ( m_statsEndToEnd.m_ping.m_nSmoothedPing >= 0 )
msgConnectRequest.set_ping_est_ms( m_statsEndToEnd.m_ping.m_nSmoothedPing );
*msgConnectRequest.mutable_cert() = m_msgSignedCertLocal;
*msgConnectRequest.mutable_crypt() = m_msgSignedCryptLocal;
if ( m_connection.m_statsEndToEnd.m_ping.m_nSmoothedPing >= 0 )
msgConnectRequest.set_ping_est_ms( m_connection.m_statsEndToEnd.m_ping.m_nSmoothedPing );
*msgConnectRequest.mutable_cert() = m_connection.GetSignedCertLocal();
*msgConnectRequest.mutable_crypt() = m_connection.GetSignedCryptLocal();
// If the cert is generic, then we need to specify our identity
if ( !m_bCertHasIdentity )
if ( !m_connection.BCertHasIdentity() )
{
SteamNetworkingIdentityToProtobuf( m_identityLocal, msgConnectRequest, identity_string, legacy_identity_binary, legacy_client_steam_id );
SteamNetworkingIdentityToProtobuf( IdentityLocal(), msgConnectRequest, identity_string, legacy_identity_binary, legacy_client_steam_id );
}
else
{
// Identity is in the cert. But for old peers, set legacy field, if we are a SteamID
if ( m_identityLocal.GetSteamID64() )
msgConnectRequest.set_legacy_client_steam_id( m_identityLocal.GetSteamID64() );
if ( IdentityLocal().GetSteamID64() )
msgConnectRequest.set_legacy_client_steam_id( IdentityLocal().GetSteamID64() );
}
// Send it
SendMsg( k_ESteamNetworkingUDPMsg_ConnectRequest, msgConnectRequest );
// Reset timeout/retry for this reply. But if it fails, we'll start
// the whole handshake over again. It keeps the code simpler, and the
// challenge value has a relatively short expiry anyway.
m_usecWhenSentConnectRequest = usecNow;
EnsureMinThinkTime( usecNow + k_usecConnectRetryInterval );
// Update retry bookkeeping, etc
m_connection.SentEndToEndConnectRequest( usecNow );
// They are supposed to reply with a timestamps, from which we can estimate the ping.
// So this counts as a ping request
m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
m_connection.m_statsEndToEnd.TrackSentPingRequest( usecNow, false );
}
void CSteamNetworkConnectionUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_ConnectOK &msg, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_ConnectOK &msg, SteamNetworkingMicroseconds usecNow )
{
SteamDatagramErrMsg errMsg;
// We should only be getting this if we are the "client"
if ( m_pParentListenSocket )
if ( ListenSocket() )
{
ReportBadPacketIPv4( "ConnectOK", "Shouldn't be receiving this unless on accepted connections, only connections initiated locally." );
return;
}
// Check connection ID to make sure they aren't spoofing and it's the same connection we think it is
if ( msg.client_connection_id() != m_unConnectionIDLocal )
if ( msg.client_connection_id() != ConnectionIDLocal() )
{
ReportBadPacketIPv4( "ConnectOK", "Incorrect connection ID. Message is stale or could be spoofed, ignoring." );
return;
@@ -1421,7 +1485,7 @@ void CSteamNetworkConnectionUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_
if ( identityRemote.IsLocalHost() )
{
if ( m_connectionConfig.m_IP_AllowWithoutAuth.Get() == 0 )
if ( m_connection.m_connectionConfig.m_IP_AllowWithoutAuth.Get() == 0 )
{
// Should we send an explicit rejection here?
ReportBadPacketIPv4( "ConnectOK", "Unauthenticated connections not allowed." );
@@ -1458,7 +1522,7 @@ void CSteamNetworkConnectionUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_
}
// Make sure they are still who we think they are
if ( !m_identityRemote.IsInvalid() && !( m_identityRemote == identityRemote ) )
if ( !m_connection.m_identityRemote.IsInvalid() && !( m_connection.m_identityRemote == identityRemote ) )
{
ReportBadPacketIPv4( "ConnectOK", "server_steam_id doesn't match who we expect to be connecting to!" );
return;
@@ -1475,12 +1539,12 @@ void CSteamNetworkConnectionUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_
else
{
int nPing = (usecElapsed + 500 ) / 1000;
m_statsEndToEnd.m_ping.ReceivedPing( nPing, usecNow );
m_connection.m_statsEndToEnd.m_ping.ReceivedPing( nPing, usecNow );
}
}
// Check state
switch ( GetState() )
switch ( ConnectionState() )
{
case k_ESteamNetworkingConnectionState_Dead:
case k_ESteamNetworkingConnectionState_None:
@@ -1506,28 +1570,28 @@ void CSteamNetworkConnectionUDP::Received_ConnectOK( const CMsgSteamSockets_UDP_
}
// Connection ID
m_unConnectionIDRemote = msg.server_connection_id();
if ( ( m_unConnectionIDRemote & 0xffff ) == 0 )
m_connection.m_unConnectionIDRemote = msg.server_connection_id();
if ( ( m_connection.m_unConnectionIDRemote & 0xffff ) == 0 )
{
ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Didn't send valid connection ID" );
m_connection.ConnectionState_ProblemDetectedLocally( k_ESteamNetConnectionEnd_Remote_BadCrypt, "Didn't send valid connection ID" );
return;
}
m_identityRemote = identityRemote;
m_connection.m_identityRemote = identityRemote;
// Check the certs, save keys, etc
if ( !BRecvCryptoHandshake( msg.cert(), msg.crypt(), false ) )
if ( !m_connection.BRecvCryptoHandshake( msg.cert(), msg.crypt(), false ) )
{
Assert( GetState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
ReportBadPacketIPv4( "ConnectOK", "Failed crypto init. %s", m_szEndDebug );
Assert( ConnectionState() == k_ESteamNetworkingConnectionState_ProblemDetectedLocally );
ReportBadPacketIPv4( "ConnectOK", "Failed crypto init. %s", m_connection.m_szEndDebug );
return;
}
// Generic connection code will take it from here.
ConnectionState_Connected( usecNow );
m_connection.ConnectionState_Connected( usecNow );
}
void CSteamNetworkConnectionUDP::Received_ConnectionClosed( const CMsgSteamSockets_UDP_ConnectionClosed &msg, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::Received_ConnectionClosed( const CMsgSteamSockets_UDP_ConnectionClosed &msg, SteamNetworkingMicroseconds usecNow )
{
// Give them a reply to let them know we heard from them. If it's the right connection ID,
// then they probably aren't spoofing and it's critical that we give them an ack!
@@ -1537,8 +1601,8 @@ void CSteamNetworkConnectionUDP::Received_ConnectionClosed( const CMsgSteamSocke
// However, it could just be random garbage, so we need to protect ourselves from abuse,
// so limit how many of these we send.
bool bConnectionIDMatch =
msg.to_connection_id() == m_unConnectionIDLocal
|| ( msg.to_connection_id() == 0 && msg.from_connection_id() && msg.from_connection_id() == m_unConnectionIDRemote ); // they might not know our ID yet, if they are a client aborting the connection really early.
msg.to_connection_id() == ConnectionIDLocal()
|| ( msg.to_connection_id() == 0 && msg.from_connection_id() && msg.from_connection_id() == m_connection.m_unConnectionIDRemote ); // they might not know our ID yet, if they are a client aborting the connection really early.
if ( bConnectionIDMatch || BCheckGlobalSpamReplyRateLimit( usecNow ) )
{
// Send a reply, echoing exactly what they sent to us
@@ -1556,26 +1620,26 @@ void CSteamNetworkConnectionUDP::Received_ConnectionClosed( const CMsgSteamSocke
return;
// Generic connection code will take it from here.
ConnectionState_ClosedByPeer( msg.reason_code(), msg.debug().c_str() );
m_connection.ConnectionState_ClosedByPeer( msg.reason_code(), msg.debug().c_str() );
}
void CSteamNetworkConnectionUDP::Received_NoConnection( const CMsgSteamSockets_UDP_NoConnection &msg, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::Received_NoConnection( const CMsgSteamSockets_UDP_NoConnection &msg, SteamNetworkingMicroseconds usecNow )
{
// Make sure it's an ack of something we would have sent
if ( msg.to_connection_id() != m_unConnectionIDLocal || msg.from_connection_id() != m_unConnectionIDRemote )
if ( msg.to_connection_id() != ConnectionIDLocal() || msg.from_connection_id() != m_connection.m_unConnectionIDRemote )
{
ReportBadPacketIPv4( "NoConnection", "Old/incorrect connection ID. Message is for a stale connection, or is spoofed. Ignoring." );
return;
}
// Generic connection code will take it from here.
ConnectionState_ClosedByPeer( 0, nullptr );
m_connection.ConnectionState_ClosedByPeer( 0, nullptr );
}
void CSteamNetworkConnectionUDP::Received_ChallengeOrConnectRequest( const char *pszDebugPacketType, uint32 unPacketConnectionID, SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::Received_ChallengeOrConnectRequest( const char *pszDebugPacketType, uint32 unPacketConnectionID, SteamNetworkingMicroseconds usecNow )
{
// If wrong connection ID, then check for sending a generic reply and bail
if ( unPacketConnectionID != m_unConnectionIDRemote )
if ( unPacketConnectionID != m_connection.m_unConnectionIDRemote )
{
ReportBadPacketIPv4( pszDebugPacketType, "Incorrect connection ID, when we do have a connection for this address. Could be spoofed, ignoring." );
// Let's not send a reply in this case
@@ -1585,7 +1649,7 @@ void CSteamNetworkConnectionUDP::Received_ChallengeOrConnectRequest( const char
}
// Check state
switch ( GetState() )
switch ( ConnectionState() )
{
case k_ESteamNetworkingConnectionState_Dead:
case k_ESteamNetworkingConnectionState_None:
@@ -1606,7 +1670,7 @@ void CSteamNetworkConnectionUDP::Received_ChallengeOrConnectRequest( const char
case k_ESteamNetworkingConnectionState_Linger:
case k_ESteamNetworkingConnectionState_Connected:
if ( !m_pParentListenSocket )
if ( !ListenSocket() )
{
// WAT? We initiated this connection, so why are they requesting to connect?
ReportBadPacketIPv4( pszDebugPacketType, "We are the 'client' who initiated the connection, so 'server' shouldn't be sending us this!" );
@@ -1620,28 +1684,28 @@ void CSteamNetworkConnectionUDP::Received_ChallengeOrConnectRequest( const char
}
void CSteamNetworkConnectionUDP::SendConnectionClosedOrNoConnection()
void CConnectionTransportUDP::SendConnectionClosedOrNoConnection()
{
if ( GetState() == k_ESteamNetworkingConnectionState_ClosedByPeer )
if ( ConnectionState() == k_ESteamNetworkingConnectionState_ClosedByPeer )
{
SendNoConnection( m_unConnectionIDLocal, m_unConnectionIDRemote );
SendNoConnection( ConnectionIDLocal(), ConnectionIDRemote() );
}
else
{
CMsgSteamSockets_UDP_ConnectionClosed msg;
msg.set_from_connection_id( m_unConnectionIDLocal );
msg.set_from_connection_id( ConnectionIDLocal() );
if ( m_unConnectionIDRemote )
msg.set_to_connection_id( m_unConnectionIDRemote );
if ( ConnectionIDRemote() )
msg.set_to_connection_id( ConnectionIDRemote() );
msg.set_reason_code( m_eEndReason );
if ( m_szEndDebug[0] )
msg.set_debug( m_szEndDebug );
msg.set_reason_code( m_connection.m_eEndReason );
if ( m_connection.m_szEndDebug[0] )
msg.set_debug( m_connection.m_szEndDebug );
SendPaddedMsg( k_ESteamNetworkingUDPMsg_ConnectionClosed, msg );
}
}
void CSteamNetworkConnectionUDP::SendNoConnection( uint32 unFromConnectionID, uint32 unToConnectionID )
void CConnectionTransportUDP::SendNoConnection( uint32 unFromConnectionID, uint32 unToConnectionID )
{
CMsgSteamSockets_UDP_NoConnection msg;
if ( unFromConnectionID == 0 && unToConnectionID == 0 )
@@ -1656,47 +1720,47 @@ void CSteamNetworkConnectionUDP::SendNoConnection( uint32 unFromConnectionID, ui
SendMsg( k_ESteamNetworkingUDPMsg_NoConnection, msg );
}
void CSteamNetworkConnectionUDP::SendConnectOK( SteamNetworkingMicroseconds usecNow )
void CConnectionTransportUDP::SendConnectOK( SteamNetworkingMicroseconds usecNow )
{
Assert( m_unConnectionIDLocal );
Assert( m_unConnectionIDRemote );
Assert( m_pParentListenSocket );
Assert( ConnectionIDLocal() );
Assert( ConnectionIDRemote() );
Assert( ListenSocket() );
Assert( m_msgSignedCertLocal.has_cert() );
Assert( m_msgSignedCryptLocal.has_info() );
Assert( m_connection.GetSignedCertLocal().has_cert() );
Assert( m_connection.GetSignedCryptLocal().has_info() );
CMsgSteamSockets_UDP_ConnectOK msg;
msg.set_client_connection_id( m_unConnectionIDRemote );
msg.set_server_connection_id( m_unConnectionIDLocal );
*msg.mutable_cert() = m_msgSignedCertLocal;
*msg.mutable_crypt() = m_msgSignedCryptLocal;
msg.set_client_connection_id( ConnectionIDRemote() );
msg.set_server_connection_id( ConnectionIDLocal() );
*msg.mutable_cert() = m_connection.GetSignedCertLocal();
*msg.mutable_crypt() = m_connection.GetSignedCryptLocal();
// If the cert is generic, then we need to specify our identity
if ( !m_bCertHasIdentity )
if ( !m_connection.BCertHasIdentity() )
{
SteamNetworkingIdentityToProtobuf( m_identityLocal, msg, identity_string, legacy_identity_binary, legacy_server_steam_id );
SteamNetworkingIdentityToProtobuf( IdentityLocal(), msg, identity_string, legacy_identity_binary, legacy_server_steam_id );
}
else
{
// Identity is in the cert. But for old peers, set legacy field, if we are a SteamID
if ( m_identityLocal.GetSteamID64() )
msg.set_legacy_server_steam_id( m_identityLocal.GetSteamID64() );
if ( IdentityLocal().GetSteamID64() )
msg.set_legacy_server_steam_id( IdentityLocal().GetSteamID64() );
}
// Do we have a timestamp?
if ( m_usecWhenReceivedHandshakeRemoteTimestamp )
if ( m_connection.m_usecWhenReceivedHandshakeRemoteTimestamp )
{
SteamNetworkingMicroseconds usecElapsed = usecNow - m_usecWhenReceivedHandshakeRemoteTimestamp;
SteamNetworkingMicroseconds usecElapsed = usecNow - m_connection.m_usecWhenReceivedHandshakeRemoteTimestamp;
Assert( usecElapsed >= 0 );
if ( usecElapsed < 4*k_nMillion )
{
msg.set_your_timestamp( m_ulHandshakeRemoteTimestamp );
msg.set_your_timestamp( m_connection.m_ulHandshakeRemoteTimestamp );
msg.set_delay_time_usec( usecElapsed );
}
else
{
SpewWarning( "Discarding handshake timestamp that's %lldms old, not sending in ConnectOK\n", usecElapsed/1000 );
m_usecWhenReceivedHandshakeRemoteTimestamp = 0;
m_connection.m_usecWhenReceivedHandshakeRemoteTimestamp = 0;
}
}
@@ -1779,22 +1843,21 @@ failed:
return false;
}
IBoundUDPSocket *sock[2];
if ( !CreateBoundSocketPair(
CRecvPacketCallback( CSteamNetworkConnectionUDP::PacketReceived, (CSteamNetworkConnectionUDP*)pConn[0] ),
CRecvPacketCallback( CSteamNetworkConnectionUDP::PacketReceived, (CSteamNetworkConnectionUDP*)pConn[1] ), sock, errMsg ) )
{
// Use assert here, because this really should only fail if we have some sort of bug
AssertMsg1( false, "Failed to create UDP socekt pair. %s", errMsg );
CConnectionTransportUDP *pTransport[2] = {
new CConnectionTransportUDP( *pConn[0] ),
new CConnectionTransportUDP( *pConn[1] )
};
pConn[0]->m_pTransport = pTransport[0];
pConn[1]->m_pTransport = pTransport[1];
if ( !CConnectionTransportUDP::CreateLoopbackPair( pTransport ) )
goto failed;
}
SteamNetworkingMicroseconds usecNow = SteamNetworkingSockets_GetLocalTimestamp();
// Initialize both connections
for ( int i = 0 ; i < 2 ; ++i )
{
pConn[i]->m_pSocket = sock[i];
if ( !pConn[i]->BInitConnection( usecNow, 0, nullptr, errMsg ) )
{
AssertMsg1( false, "CSteamNetworkConnectionlocalhostLoopback::BInitConnection failed. %s", errMsg );
@@ -57,27 +57,77 @@ private:
//
/////////////////////////////////////////////////////////////////////////////
/// A connection over raw UDP
class CSteamNetworkConnectionUDP : public CSteamNetworkConnectionBase
class CSteamNetworkConnectionUDP;
/// Ordinary UDP transport
class CConnectionTransportUDP final : public CConnectionTransport
{
public:
CSteamNetworkConnectionUDP( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface );
virtual ~CSteamNetworkConnectionUDP();
CConnectionTransportUDP( CSteamNetworkConnectionUDP &connection );
virtual void FreeResources() override;
/// Convenience wrapper to do the upcast, since we know what sort of
/// listen socket we were connected on.
inline CSteamNetworkListenSocketDirectUDP *ListenSocket() const { return assert_cast<CSteamNetworkListenSocketDirectUDP *>( m_pParentListenSocket ); }
/// Implements CSteamNetworkConnectionBase
// Implements CSteamNetworkConnectionTransport
virtual void TransportFreeResources();
virtual bool SendDataPacket( SteamNetworkingMicroseconds usecNow ) override;
virtual int SendEncryptedDataChunk( const void *pChunk, int cbChunk, SendPacketContext_t &ctx ) override;
virtual EResult APIAcceptConnection() override;
virtual bool BCanSendEndToEndConnectRequest() const override;
virtual bool BCanSendEndToEndData() const override;
virtual void SendEndToEndConnectRequest( SteamNetworkingMicroseconds usecNow ) override;
virtual void SendEndToEndStatsMsg( EStatsReplyRequest eRequest, SteamNetworkingMicroseconds usecNow, const char *pszReason ) override;
// We need to customize our thinking to handle the connection state machine
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState ) override;
/// Interface used to talk to the remote host
IBoundUDPSocket *m_pSocket;
bool BConnect( const netadr_t &netadrRemote, SteamDatagramErrMsg &errMsg );
bool BAccept( CSharedSocket *pSharedSock, const netadr_t &netadrRemote, SteamDatagramErrMsg &errMsg );
void SendConnectOK( SteamNetworkingMicroseconds usecNow );
static bool CreateLoopbackPair( CConnectionTransportUDP *pTransport[2] );
protected:
virtual ~CConnectionTransportUDP(); // Don't call operator delete directly
static void PacketReceived( const void *pPkt, int cbPkt, const netadr_t &adrFrom, CConnectionTransportUDP *pSelf );
void Received_Data( const uint8 *pPkt, int cbPkt, SteamNetworkingMicroseconds usecNow );
void Received_ChallengeReply( const CMsgSteamSockets_UDP_ChallengeReply &msg, SteamNetworkingMicroseconds usecNow );
void Received_ConnectOK( const CMsgSteamSockets_UDP_ConnectOK &msg, SteamNetworkingMicroseconds usecNow );
void Received_ConnectionClosed( const CMsgSteamSockets_UDP_ConnectionClosed &msg, SteamNetworkingMicroseconds usecNow );
void Received_NoConnection( const CMsgSteamSockets_UDP_NoConnection &msg, SteamNetworkingMicroseconds usecNow );
void Received_ChallengeOrConnectRequest( const char *pszDebugPacketType, uint32 unPacketConnectionID, SteamNetworkingMicroseconds usecNow );
void SendPaddedMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg );
void SendMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg );
void SendPacket( const void *pkt, int cbPkt );
void SendPacketGather( int nChunks, const iovec *pChunks, int cbSendTotal );
void SendConnectionClosedOrNoConnection();
void SendNoConnection( uint32 unFromConnectionID, uint32 unToConnectionID );
/// Process stats message, either inline or standalone
void RecvStats( const CMsgSteamSockets_UDP_Stats &msgStatsIn, bool bInline, SteamNetworkingMicroseconds usecNow );
void SendStatsMsg( EStatsReplyRequest eReplyRequested, SteamNetworkingMicroseconds usecNow, const char *pszReason );
void TrackSentStats( const CMsgSteamSockets_UDP_Stats &msgStatsOut, bool bInline, SteamNetworkingMicroseconds usecNow );
void PopulateSendPacketContext( UDPSendPacketContext_t &ctx, EStatsReplyRequest eReplyRequested );
};
/// A connection over ordinary UDP
class CSteamNetworkConnectionUDP : public CSteamNetworkConnectionBase
{
public:
CSteamNetworkConnectionUDP( CSteamNetworkingSockets *pSteamNetworkingSocketsInterface );
/// Convenience wrapper to do the upcast, since we know what sort of
/// listen socket we were connected on.
inline CSteamNetworkListenSocketDirectUDP *ListenSocket() const { return assert_cast<CSteamNetworkListenSocketDirectUDP *>( m_pParentListenSocket ); }
inline CConnectionTransportUDP *Transport() const { return assert_cast<CConnectionTransportUDP *>( m_pTransport ); }
/// Implements CSteamNetworkConnectionBase
virtual EResult APIAcceptConnection() override;
virtual void ThinkConnection( SteamNetworkingMicroseconds usecNow ) override;
virtual void GetConnectionTypeDescription( ConnectionTypeDescription_t &szDescription ) const override;
virtual EUnsignedCert AllowRemoteUnsignedCert() override;
@@ -97,39 +147,8 @@ public:
const CMsgSteamDatagramSessionCryptInfoSigned &msgSessionInfo,
SteamDatagramErrMsg &errMsg
);
protected:
/// Interface used to talk to the remote host
IBoundUDPSocket *m_pSocket;
// We need to customize our thinking to handle the connection state machine
virtual void ConnectionStateChanged( ESteamNetworkingConnectionState eOldState ) override;
static void PacketReceived( const void *pPkt, int cbPkt, const netadr_t &adrFrom, CSteamNetworkConnectionUDP *pSelf );
void Received_Data( const uint8 *pPkt, int cbPkt, SteamNetworkingMicroseconds usecNow );
void Received_ChallengeReply( const CMsgSteamSockets_UDP_ChallengeReply &msg, SteamNetworkingMicroseconds usecNow );
void Received_ConnectOK( const CMsgSteamSockets_UDP_ConnectOK &msg, SteamNetworkingMicroseconds usecNow );
void Received_ConnectionClosed( const CMsgSteamSockets_UDP_ConnectionClosed &msg, SteamNetworkingMicroseconds usecNow );
void Received_NoConnection( const CMsgSteamSockets_UDP_NoConnection &msg, SteamNetworkingMicroseconds usecNow );
void Received_ChallengeOrConnectRequest( const char *pszDebugPacketType, uint32 unPacketConnectionID, SteamNetworkingMicroseconds usecNow );
void SendPaddedMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg );
void SendMsg( uint8 nMsgID, const google::protobuf::MessageLite &msg );
void SendPacket( const void *pkt, int cbPkt );
void SendPacketGather( int nChunks, const iovec *pChunks, int cbSendTotal );
void SendConnectOK( SteamNetworkingMicroseconds usecNow );
void SendConnectionClosedOrNoConnection();
void SendNoConnection( uint32 unFromConnectionID, uint32 unToConnectionID );
/// Process stats message, either inline or standalone
void RecvStats( const CMsgSteamSockets_UDP_Stats &msgStatsIn, bool bInline, SteamNetworkingMicroseconds usecNow );
void SendStatsMsg( EStatsReplyRequest eReplyRequested, SteamNetworkingMicroseconds usecNow, const char *pszReason );
void TrackSentStats( const CMsgSteamSockets_UDP_Stats &msgStatsOut, bool bInline, SteamNetworkingMicroseconds usecNow );
void PopulateSendPacketContext( UDPSendPacketContext_t &ctx, EStatsReplyRequest eReplyRequested );
virtual ~CSteamNetworkConnectionUDP(); // Use ConnectionDestroySelfNow
};
/// A connection over loopback
@@ -237,12 +237,12 @@ const unsigned k_usecTimeSinceLastPacketSerializedPrecisionShift = 4;
/// "Time since last packet sent" values should be less than this.
/// Any larger value will be discarded, and should not be sent
const uint32 k_usecTimeSinceLastPacketMaxReasonable = k_nMillion/4;
const SteamNetworkingMicroseconds k_usecTimeSinceLastPacketMaxReasonable = k_nMillion/4;
COMPILE_TIME_ASSERT( ( k_usecTimeSinceLastPacketMaxReasonable >> k_usecTimeSinceLastPacketSerializedPrecisionShift ) < 0x8000 ); // make sure all "reasonable" values can get serialized into 16-bits
/// Don't send spacing values when packets are sent extremely close together.
const uint32 k_usecTimeSinceLastPacketMinReasonable = k_nMillion/250;
COMPILE_TIME_ASSERT( ( k_usecTimeSinceLastPacketMinReasonable >> k_usecTimeSinceLastPacketSerializedPrecisionShift ) > 64 ); // make sure the minimum reasonable value can be serialized with sufficient precision.
/// Don't send spacing values when packets are sent extremely close together. The spacing
/// should be a bit higher that our serialization precision.
const SteamNetworkingMicroseconds k_usecTimeSinceLastPacketMinReasonable = 2 << k_usecTimeSinceLastPacketSerializedPrecisionShift;
/// Protocol version of this code. This is a blunt instrument, which is incremented when we
/// wish to change the wire protocol in a way that doesn't have some other easy