From 1f29e34e0fccc4f238e91c4784df9bb144e37abe Mon Sep 17 00:00:00 2001 From: Fletcher Dunn Date: Tue, 17 Sep 2019 11:34:23 -0700 Subject: [PATCH] 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. --- include/steam/isteamnetworkingsockets.h | 2 +- src/common/keypair.h | 10 +- ...teamnetworkingsockets_messages_certs.proto | 5 + .../clientlib/csteamnetworkingsockets.cpp | 64 ++- .../clientlib/csteamnetworkingsockets.h | 1 + .../steamnetworkingsockets_connections.cpp | 206 +++++---- .../steamnetworkingsockets_connections.h | 276 +++++++----- .../steamnetworkingsockets_lowlevel.cpp | 4 +- .../clientlib/steamnetworkingsockets_snp.cpp | 26 +- .../clientlib/steamnetworkingsockets_udp.cpp | 425 ++++++++++-------- .../clientlib/steamnetworkingsockets_udp.h | 107 +++-- .../steamnetworkingsockets_internal.h | 8 +- 12 files changed, 695 insertions(+), 439 deletions(-) diff --git a/include/steam/isteamnetworkingsockets.h b/include/steam/isteamnetworkingsockets.h index 4049efd..b295002 100644 --- a/include/steam/isteamnetworkingsockets.h +++ b/include/steam/isteamnetworkingsockets.h @@ -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(); diff --git a/src/common/keypair.h b/src/common/keypair.h index 7cc52be..b5e0f63 100644 --- a/src/common/keypair.h +++ b/src/common/keypair.h @@ -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 ); diff --git a/src/common/steamnetworkingsockets_messages_certs.proto b/src/common/steamnetworkingsockets_messages_certs.proto index 40bc367..92339dd 100644 --- a/src/common/steamnetworkingsockets_messages_certs.proto +++ b/src/common/steamnetworkingsockets_messages_certs.proto @@ -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. diff --git a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp index cc3d3b6..fac0485 100644 --- a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp +++ b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp @@ -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(); diff --git a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h index 52e4693..d2a5413 100644 --- a/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h +++ b/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.h @@ -144,6 +144,7 @@ protected: class CSteamNetworkingUtils : public IClientNetworkingUtils { public: + virtual ~CSteamNetworkingUtils(); virtual SteamNetworkingMicroseconds GetLocalTimestamp() override; virtual void SetDebugOutputFunction( ESteamNetworkingSocketsDebugOutputType eDetailLevel, FSteamNetworkingSocketsDebugOutput pfnFunc ) override; diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp index 6c68ab3..72287ea 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.cpp @@ -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( 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( 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( 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 diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.h b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.h index b49ad3f..94c62ee 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.h +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_connections.h @@ -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: diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp index 84e7e94..68b4fa1 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp @@ -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( 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 diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp index ea8175e..af8ad15 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_snp.cpp @@ -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(); diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp index ae689f2..4dcb906 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.cpp @@ -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( const CMsgSteamSockets_UDP_Stats &msg ) { @@ -579,20 +587,21 @@ struct UDPSendPacketContext_t : SendPacketContext }; -void CSteamNetworkConnectionUDP::PopulateSendPacketContext( UDPSendPacketContext_t &ctx, EStatsReplyRequest eReplyRequested ) +void CConnectionTransportUDP::PopulateSendPacketContext( UDPSendPacketContext_t &ctx, EStatsReplyRequest eReplyRequested ) { SteamNetworkingMicroseconds usecNow = ctx.m_usecNow; + LinkStatsTracker &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( 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( 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 ); diff --git a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.h b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.h index f6c8be0..fb60ed2 100644 --- a/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.h +++ b/src/steamnetworkingsockets/clientlib/steamnetworkingsockets_udp.h @@ -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( 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( m_pParentListenSocket ); } + inline CConnectionTransportUDP *Transport() const { return assert_cast( 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 diff --git a/src/steamnetworkingsockets/steamnetworkingsockets_internal.h b/src/steamnetworkingsockets/steamnetworkingsockets_internal.h index ac4f98a..3feb86d 100644 --- a/src/steamnetworkingsockets/steamnetworkingsockets_internal.h +++ b/src/steamnetworkingsockets/steamnetworkingsockets_internal.h @@ -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